From 84cd78541eb1b544165641ce0a3aba198e58612e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Caplette?= Date: Fri, 12 Dec 2025 12:08:50 -0500 Subject: [PATCH 1/2] Support badge on agents in MR When in MR, we want to show a badge on agents when assigning so that users are aware that this is not a user, but an agent/flow. --- ...te_users_with_mr_permissions.query.graphql | 1 + .../reviewers/reviewer_dropdown.vue | 12 +++- .../assignees/collapsed_assignee.vue | 1 + .../assignees/uncollapsed_assignee_list.vue | 6 +- .../assignees/user_name_with_status.vue | 14 +++- .../queries/get_alert_assignees.query.graphql | 1 + .../queries/get_issue_assignees.query.graphql | 2 + .../reviewers/reviewer_dropdown_spec.js | 1 + .../assignees/user_name_with_status_spec.js | 65 ++++++++++++++++--- .../groups/participants_service_spec.rb | 3 +- 10 files changed, 90 insertions(+), 16 deletions(-) diff --git a/app/assets/javascripts/graphql_shared/queries/project_autocomplete_users_with_mr_permissions.query.graphql b/app/assets/javascripts/graphql_shared/queries/project_autocomplete_users_with_mr_permissions.query.graphql index 8155451fb7cef4..854842839eb323 100644 --- a/app/assets/javascripts/graphql_shared/queries/project_autocomplete_users_with_mr_permissions.query.graphql +++ b/app/assets/javascripts/graphql_shared/queries/project_autocomplete_users_with_mr_permissions.query.graphql @@ -11,6 +11,7 @@ query projectAutocompleteUsersSearchWithMRPermissions( users: autocompleteUsers(search: $search) { ...User ...UserAvailability + compositeIdentityEnforced @gl_introduced(version: "18.7.0") mergeRequestInteraction(id: $mergeRequestId) { canMerge } diff --git a/app/assets/javascripts/merge_requests/components/reviewers/reviewer_dropdown.vue b/app/assets/javascripts/merge_requests/components/reviewers/reviewer_dropdown.vue index a43cb8119d4fed..73fc19d052671c 100644 --- a/app/assets/javascripts/merge_requests/components/reviewers/reviewer_dropdown.vue +++ b/app/assets/javascripts/merge_requests/components/reviewers/reviewer_dropdown.vue @@ -1,6 +1,6 @@ @@ -49,10 +58,13 @@ export default { > + + {{ __('Agent') }} + diff --git a/app/assets/javascripts/sidebar/queries/get_alert_assignees.query.graphql b/app/assets/javascripts/sidebar/queries/get_alert_assignees.query.graphql index 171eca50eabeca..0a928448f2e4cb 100644 --- a/app/assets/javascripts/sidebar/queries/get_alert_assignees.query.graphql +++ b/app/assets/javascripts/sidebar/queries/get_alert_assignees.query.graphql @@ -15,6 +15,7 @@ query alertAssignees( nodes { ...User ...UserAvailability + compositeIdentityEnforced @gl_introduced(version: "18.7.0") } } } diff --git a/app/assets/javascripts/sidebar/queries/get_issue_assignees.query.graphql b/app/assets/javascripts/sidebar/queries/get_issue_assignees.query.graphql index 6df2a1f5829d70..fb7e701eb632a7 100644 --- a/app/assets/javascripts/sidebar/queries/get_issue_assignees.query.graphql +++ b/app/assets/javascripts/sidebar/queries/get_issue_assignees.query.graphql @@ -9,11 +9,13 @@ query issueAssignees($fullPath: ID!, $iid: String!) { author { ...UserWithType ...UserAvailability + compositeIdentityEnforced @gl_introduced(version: "18.7.0") } assignees { nodes { ...UserWithType ...UserAvailability + compositeIdentityEnforced @gl_introduced(version: "18.7.0") } } } diff --git a/spec/frontend/merge_requests/components/reviewers/reviewer_dropdown_spec.js b/spec/frontend/merge_requests/components/reviewers/reviewer_dropdown_spec.js index 4d77934a6a3a57..d1388e75fc1635 100644 --- a/spec/frontend/merge_requests/components/reviewers/reviewer_dropdown_spec.js +++ b/spec/frontend/merge_requests/components/reviewers/reviewer_dropdown_spec.js @@ -28,6 +28,7 @@ const createMockUser = ({ id = 1, name = 'Administrator', username = 'root' } = webUrl: `/${username}`, webPath: `/${username}`, status: null, + compositeIdentityEnforced: false, mergeRequestInteraction: { canMerge: true, applicableApprovalRules: [], diff --git a/spec/frontend/sidebar/components/assignees/user_name_with_status_spec.js b/spec/frontend/sidebar/components/assignees/user_name_with_status_spec.js index e54ba31a30c091..b5fd5b9b0526c3 100644 --- a/spec/frontend/sidebar/components/assignees/user_name_with_status_spec.js +++ b/spec/frontend/sidebar/components/assignees/user_name_with_status_spec.js @@ -1,4 +1,5 @@ -import { mount } from '@vue/test-utils'; +import { GlSprintf } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; import { AVAILABILITY_STATUS } from '~/set_status_modal/constants'; import UserNameWithStatus from '~/sidebar/components/assignees/user_name_with_status.vue'; @@ -8,9 +9,15 @@ const containerClasses = 'gl-cool-class gl-over-9000'; describe('UserNameWithStatus', () => { let wrapper; + const findBusyBadge = () => wrapper.find('[data-testid="busy-badge"]'); + const findAgentBadge = () => wrapper.find('[data-testid="agent-badge"]'); + function createComponent(props = {}) { - wrapper = mount(UserNameWithStatus, { + wrapper = shallowMount(UserNameWithStatus, { propsData: { name, containerClasses, ...props }, + stubs: { + GlSprintf, + }, }); } @@ -22,22 +29,62 @@ describe('UserNameWithStatus', () => { expect(wrapper.html()).toContain(name); }); - it('will not render "Busy"', () => { - expect(wrapper.html()).not.toContain('Busy'); - }); - it('will render all relevant containerClasses', () => { const classes = wrapper.find('span').classes().join(' '); expect(classes).toBe(containerClasses); }); - describe(`with availability="${AVAILABILITY_STATUS.BUSY}"`, () => { + describe('when user is not busy and is not agent', () => { + it('will not render "Busy" badge', () => { + expect(findBusyBadge().exists()).toBe(false); + }); + + it('will not render agent badge', () => { + expect(findAgentBadge().exists()).toBe(false); + }); + }); + + describe(`when user is busy`, () => { beforeEach(() => { createComponent({ availability: AVAILABILITY_STATUS.BUSY }); }); - it('will render "Busy"', () => { - expect(wrapper.text()).toContain('Busy'); + it('will render "Busy" badge', () => { + expect(findBusyBadge().exists()).toBe(true); + expect(findBusyBadge().text()).toBe('Busy'); + }); + + it('will not render agent badge', () => { + expect(findAgentBadge().exists()).toBe(false); + }); + }); + + describe('when user is agent', () => { + beforeEach(() => { + createComponent({ compositeIdentityEnforced: true }); + }); + + it('will render agent badge', () => { + expect(findAgentBadge().exists()).toBe(true); + expect(findAgentBadge().text()).toBe('Agent'); + }); + + it('will not render busy badge', () => { + expect(findBusyBadge().exists()).toBe(false); + }); + }); + + describe('when user is busy and is agent', () => { + beforeEach(() => { + createComponent({ + availability: AVAILABILITY_STATUS.BUSY, + compositeIdentityEnforced: true, + }); + }); + + it('will render both busy and agent badges', () => { + expect(findBusyBadge().exists()).toBe(true); + expect(findAgentBadge().exists()).toBe(true); }); }); diff --git a/spec/services/groups/participants_service_spec.rb b/spec/services/groups/participants_service_spec.rb index 7760f58dfe8002..868d1be50d1d4b 100644 --- a/spec/services/groups/participants_service_spec.rb +++ b/spec/services/groups/participants_service_spec.rb @@ -120,8 +120,7 @@ def user_to_autocompletable(user) username: user.username, name: user.name, avatar_url: user.avatar_url, - availability: user&.status&.availability, - composite_identity_enforced: user.composite_identity_enforced + availability: user&.status&.availability } end end -- GitLab From 5dce429afa8c1dd4ed06f225d79994c04698228b Mon Sep 17 00:00:00 2001 From: Lindsey Shelton Date: Thu, 18 Dec 2025 14:52:33 -0600 Subject: [PATCH 2/2] Add composite_identity_enforced to graphql user types to fix graphql errors --- .../components/reviewers/reviewer_dropdown.vue | 2 +- .../components/assignees/user_name_with_status.vue | 1 - app/graphql/types/user_interface.rb | 6 ++++++ doc/api/graphql/reference/_index.md | 8 ++++++++ 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/merge_requests/components/reviewers/reviewer_dropdown.vue b/app/assets/javascripts/merge_requests/components/reviewers/reviewer_dropdown.vue index 73fc19d052671c..8eef48bc5e720c 100644 --- a/app/assets/javascripts/merge_requests/components/reviewers/reviewer_dropdown.vue +++ b/app/assets/javascripts/merge_requests/components/reviewers/reviewer_dropdown.vue @@ -348,7 +348,7 @@ export default {