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 8155451fb7cef46862911d753c20947f5622a37a..854842839eb3238329a5fa9c4f7f0041137fae48 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 a43cb8119d4fedd910be2e1a897e901d3e3e3380..8eef48bc5e720cbbd9a16bcfecc74f226e3acb83 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 +57,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 171eca50eabecaad1b2e8f279c2c813a44335fd4..0a928448f2e4cbe13ed4880bcba488898e906330 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 6df2a1f5829d700a805bca9f917da20d9565678a..fb7e701eb632a7f68c785b4177b96830607d7527 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/app/graphql/types/user_interface.rb b/app/graphql/types/user_interface.rb index 8d5bcb0a8fdecbd644d2d9b42c74131eb8e7b61e..b2c2427d6d1cdbaf28289329ab3869f15e6e9200 100644 --- a/app/graphql/types/user_interface.rb +++ b/app/graphql/types/user_interface.rb @@ -267,6 +267,12 @@ module UserInterface description: 'Personal access tokens of the user.', experiment: { milestone: '18.6' } + field :composite_identity_enforced, + GraphQL::Types::Boolean, + null: false, + description: 'Indicates if the user has composite identity enforcement enabled.', + method: :composite_identity_enforced? + definition_methods do def resolve_type(object, context) # in the absence of other information, we cannot tell - just default to diff --git a/doc/api/graphql/reference/_index.md b/doc/api/graphql/reference/_index.md index 17ce62c8f4a3a89fd681f0aeca47ee39f34093c1..22e94d9e0d6e2ca765397b3a5e28eb7a6ee09b77 100644 --- a/doc/api/graphql/reference/_index.md +++ b/doc/api/graphql/reference/_index.md @@ -24885,6 +24885,7 @@ A user with add-on data. | `bot` | [`Boolean!`](#boolean) | Indicates if the user is a bot. | | `callouts` | [`UserCalloutConnection`](#usercalloutconnection) | User callouts that belong to the user. (see [Connections](#connections)) | | `commitEmail` | [`String`](#string) | User's default commit email. | +| `compositeIdentityEnforced` | [`Boolean!`](#boolean) | Indicates if the user has composite identity enforcement enabled. | | `createdAt` | [`Time`](#time) | Timestamp of when the user was created. | | `discord` | [`String`](#string) | Discord ID of the user. | | `email` {{< icon name="warning-solid" >}} | [`String`](#string) | **Deprecated** in GitLab 13.7. This was renamed. Use: [`User.publicEmail`](#userpublicemail). | @@ -29916,6 +29917,7 @@ The currently authenticated GitLab user. | `callouts` | [`UserCalloutConnection`](#usercalloutconnection) | User callouts that belong to the user. (see [Connections](#connections)) | | `codeSuggestionsContexts` {{< icon name="warning-solid" >}} | [`[String!]!`](#string) | **Introduced** in GitLab 17.9. **Status**: Experiment. List of additional contexts enabled for Code Suggestions. | | `commitEmail` | [`String`](#string) | User's default commit email. | +| `compositeIdentityEnforced` | [`Boolean!`](#boolean) | Indicates if the user has composite identity enforcement enabled. | | `createdAt` | [`Time`](#time) | Timestamp of when the user was created. | | `discord` | [`String`](#string) | Discord ID of the user. | | `duoChatAvailable` {{< icon name="warning-solid" >}} | [`Boolean!`](#boolean) | **Introduced** in GitLab 16.8. **Status**: Experiment. User access to AI chat feature. | @@ -37542,6 +37544,7 @@ A user assigned to a merge request. | `bot` | [`Boolean!`](#boolean) | Indicates if the user is a bot. | | `callouts` | [`UserCalloutConnection`](#usercalloutconnection) | User callouts that belong to the user. (see [Connections](#connections)) | | `commitEmail` | [`String`](#string) | User's default commit email. | +| `compositeIdentityEnforced` | [`Boolean!`](#boolean) | Indicates if the user has composite identity enforcement enabled. | | `createdAt` | [`Time`](#time) | Timestamp of when the user was created. | | `discord` | [`String`](#string) | Discord ID of the user. | | `email` {{< icon name="warning-solid" >}} | [`String`](#string) | **Deprecated** in GitLab 13.7. This was renamed. Use: [`User.publicEmail`](#userpublicemail). | @@ -38000,6 +38003,7 @@ The author of the merge request. | `bot` | [`Boolean!`](#boolean) | Indicates if the user is a bot. | | `callouts` | [`UserCalloutConnection`](#usercalloutconnection) | User callouts that belong to the user. (see [Connections](#connections)) | | `commitEmail` | [`String`](#string) | User's default commit email. | +| `compositeIdentityEnforced` | [`Boolean!`](#boolean) | Indicates if the user has composite identity enforcement enabled. | | `createdAt` | [`Time`](#time) | Timestamp of when the user was created. | | `discord` | [`String`](#string) | Discord ID of the user. | | `email` {{< icon name="warning-solid" >}} | [`String`](#string) | **Deprecated** in GitLab 13.7. This was renamed. Use: [`User.publicEmail`](#userpublicemail). | @@ -38509,6 +38513,7 @@ A user participating in a merge request. | `bot` | [`Boolean!`](#boolean) | Indicates if the user is a bot. | | `callouts` | [`UserCalloutConnection`](#usercalloutconnection) | User callouts that belong to the user. (see [Connections](#connections)) | | `commitEmail` | [`String`](#string) | User's default commit email. | +| `compositeIdentityEnforced` | [`Boolean!`](#boolean) | Indicates if the user has composite identity enforcement enabled. | | `createdAt` | [`Time`](#time) | Timestamp of when the user was created. | | `discord` | [`String`](#string) | Discord ID of the user. | | `email` {{< icon name="warning-solid" >}} | [`String`](#string) | **Deprecated** in GitLab 13.7. This was renamed. Use: [`User.publicEmail`](#userpublicemail). | @@ -38986,6 +38991,7 @@ A user assigned to a merge request as a reviewer. | `bot` | [`Boolean!`](#boolean) | Indicates if the user is a bot. | | `callouts` | [`UserCalloutConnection`](#usercalloutconnection) | User callouts that belong to the user. (see [Connections](#connections)) | | `commitEmail` | [`String`](#string) | User's default commit email. | +| `compositeIdentityEnforced` | [`Boolean!`](#boolean) | Indicates if the user has composite identity enforcement enabled. | | `createdAt` | [`Time`](#time) | Timestamp of when the user was created. | | `discord` | [`String`](#string) | Discord ID of the user. | | `email` {{< icon name="warning-solid" >}} | [`String`](#string) | **Deprecated** in GitLab 13.7. This was renamed. Use: [`User.publicEmail`](#userpublicemail). | @@ -47670,6 +47676,7 @@ Core representation of a GitLab user. | `bot` | [`Boolean!`](#boolean) | Indicates if the user is a bot. | | `callouts` | [`UserCalloutConnection`](#usercalloutconnection) | User callouts that belong to the user. (see [Connections](#connections)) | | `commitEmail` | [`String`](#string) | User's default commit email. | +| `compositeIdentityEnforced` | [`Boolean!`](#boolean) | Indicates if the user has composite identity enforcement enabled. | | `createdAt` | [`Time`](#time) | Timestamp of when the user was created. | | `discord` | [`String`](#string) | Discord ID of the user. | | `email` {{< icon name="warning-solid" >}} | [`String`](#string) | **Deprecated** in GitLab 13.7. This was renamed. Use: [`User.publicEmail`](#userpublicemail). | @@ -57998,6 +58005,7 @@ Implementations: | `bot` | [`Boolean!`](#boolean) | Indicates if the user is a bot. | | `callouts` | [`UserCalloutConnection`](#usercalloutconnection) | User callouts that belong to the user. (see [Connections](#connections)) | | `commitEmail` | [`String`](#string) | User's default commit email. | +| `compositeIdentityEnforced` | [`Boolean!`](#boolean) | Indicates if the user has composite identity enforcement enabled. | | `createdAt` | [`Time`](#time) | Timestamp of when the user was created. | | `discord` | [`String`](#string) | Discord ID of the user. | | `email` {{< icon name="warning-solid" >}} | [`String`](#string) | **Deprecated** in GitLab 13.7. This was renamed. Use: [`User.publicEmail`](#userpublicemail). | 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 4d77934a6a3a57218bab9f0a7f55660ee1d29b4b..d1388e75fc1635f97237e6ea1c6d6f44c6f2714c 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 e54ba31a30c0916da8f2f9ffd24075d738badd89..b5fd5b9b0526c3a957083ee122b65200537a6b9e 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 7760f58dfe800230d4cb09de42d4d32c9895b119..868d1be50d1d4b1e7c124d41a95a8be5237d19a8 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