diff --git a/ee/app/assets/javascripts/workspaces/admin_settings/components/availability_popover.vue b/ee/app/assets/javascripts/workspaces/admin_settings/components/availability_popover.vue index 572d4a56a1c12998b65d208ced6633bd16bdb126..5b9b0a588d3a4e2e114689c3b10e95397bc3d0ea 100644 --- a/ee/app/assets/javascripts/workspaces/admin_settings/components/availability_popover.vue +++ b/ee/app/assets/javascripts/workspaces/admin_settings/components/availability_popover.vue @@ -8,10 +8,7 @@ export default { GlIcon, GlPopover, }, - AGENT_CONFIG_NOTE_POPOVER_CONTENT: s__( - "Workspaces|In order to make an agent available/blocked, workspaces must be enabled in the agent's configuration.", - ), - BLOCKING_AGENT_POPOVER_CONTENT: s__( + POPOVER_TEXT: s__( "Workspaces|Blocking an agent doesn't delete it. Agents can only be deleted in the project where they were created. In addition, existing workspaces using a blocked agent will continue to run.", ), }; @@ -20,8 +17,7 @@ export default {
-

{{ $options.AGENT_CONFIG_NOTE_POPOVER_CONTENT }}

-

{{ $options.BLOCKING_AGENT_POPOVER_CONTENT }}

+ {{ $options.POPOVER_TEXT }}
diff --git a/ee/app/assets/javascripts/workspaces/admin_settings/components/availability_toggle.vue b/ee/app/assets/javascripts/workspaces/admin_settings/components/availability_toggle.vue new file mode 100644 index 0000000000000000000000000000000000000000..329e7a43d0c260a7662faaaaf6b933f421f4ad64 --- /dev/null +++ b/ee/app/assets/javascripts/workspaces/admin_settings/components/availability_toggle.vue @@ -0,0 +1,136 @@ + + diff --git a/ee/app/assets/javascripts/workspaces/admin_settings/components/get_organization_workspaces_cluster_agents_query.vue b/ee/app/assets/javascripts/workspaces/admin_settings/components/get_organization_workspaces_cluster_agents_query.vue index 66a0097f55fb7f86187af35b1781d25ddda731ac..2480381bd32e667ac8fb8688115eabfe6cf8532c 100644 --- a/ee/app/assets/javascripts/workspaces/admin_settings/components/get_organization_workspaces_cluster_agents_query.vue +++ b/ee/app/assets/javascripts/workspaces/admin_settings/components/get_organization_workspaces_cluster_agents_query.vue @@ -4,7 +4,6 @@ import { logError } from '~/lib/logger'; import organizationWorkspacesClusterAgentsQuery from '../graphql/queries/organization_workspaces_cluster_agents.query.graphql'; import mappedOrganizationClusterAgentsQuery from '../graphql/queries/organization_mapped_agents.query.graphql'; -import { AVAILABILITY_OPTIONS } from '../constants'; export default { props: { @@ -17,7 +16,7 @@ export default { return { mappedAgentsLoaded: false, mappedAgents: null, - agents: [], + rawAgents: null, error: null, beforeCursor: null, afterCursor: null, @@ -25,6 +24,16 @@ export default { hasPreviousPage: false, }; }, + computed: { + agents() { + if (!this.rawAgents || !this.mappedAgents) return null; + + return this.rawAgents.map((agent) => ({ + ...agent, + isMapped: this.mappedAgents.has(agent.id), + })); + }, + }, apollo: { mappedAgents: { query: mappedOrganizationClusterAgentsQuery, @@ -49,7 +58,7 @@ export default { return new Set(mappedAgentIds); }, }, - agents: { + rawAgents: { query: organizationWorkspacesClusterAgentsQuery, variables() { return { @@ -66,39 +75,35 @@ export default { update(data) { this.error = null; - const { pageInfo } = data.organization.organizationWorkspacesClusterAgents; + const { pageInfo, nodes } = data.organization.organizationWorkspacesClusterAgents; this.hasNextPage = pageInfo.hasNextPage; this.hasPreviousPage = pageInfo.hasPreviousPage; this.beforeCursor = pageInfo.startCursor; this.afterCursor = pageInfo.endCursor; - const agents = data.organization.organizationWorkspacesClusterAgents.nodes.map((agent) => ({ + return nodes.map((agent) => ({ + id: agent.id, name: agent.name, url: joinPaths(window.gon.gitlab_url, agent.webPath), group: agent.project?.group?.name || '', project: agent.project?.name || '', isConnected: Boolean(agent.connections?.nodes.length), workspacesEnabled: Boolean(agent.workspacesAgentConfig?.enabled), - availability: this.mappedAgents.has(agent.id) - ? AVAILABILITY_OPTIONS.AVAILABLE - : AVAILABILITY_OPTIONS.BLOCKED, })); - - return agents; }, }, }, methods: { nextPage() { - this.$apollo.queries.agents.refetch({ + this.$apollo.queries.rawAgents.refetch({ organizationId: this.organizationId, before: null, after: this.afterCursor, }); }, prevPage() { - this.$apollo.queries.agents.refetch({ + this.$apollo.queries.rawAgents.refetch({ organizationId: this.organizationId, before: this.beforeCursor, after: null, diff --git a/ee/app/assets/javascripts/workspaces/admin_settings/constants.js b/ee/app/assets/javascripts/workspaces/admin_settings/constants.js deleted file mode 100644 index 1d30a9c9940ec3e1d654bfa2f9c73d3043e14fdd..0000000000000000000000000000000000000000 --- a/ee/app/assets/javascripts/workspaces/admin_settings/constants.js +++ /dev/null @@ -1,11 +0,0 @@ -import { s__ } from '~/locale'; - -export const AVAILABILITY_OPTIONS = { - AVAILABLE: 'available', - BLOCKED: 'blocked', -}; - -export const AVAILABILITY_TEXT = { - [AVAILABILITY_OPTIONS.AVAILABLE]: s__('Workspaces|Available'), - [AVAILABILITY_OPTIONS.BLOCKED]: s__('Workspaces|Blocked'), -}; diff --git a/ee/app/assets/javascripts/workspaces/admin_settings/graphql/mutations/create_org_cluster_agent_mapping.mutation.graphql b/ee/app/assets/javascripts/workspaces/admin_settings/graphql/mutations/create_org_cluster_agent_mapping.mutation.graphql new file mode 100644 index 0000000000000000000000000000000000000000..023a6c942e8deb5c6059652e4d612c6a483dd2bd --- /dev/null +++ b/ee/app/assets/javascripts/workspaces/admin_settings/graphql/mutations/create_org_cluster_agent_mapping.mutation.graphql @@ -0,0 +1,7 @@ +mutation createOrganizationClusterAgentMapping( + $input: OrganizationCreateClusterAgentMappingInput! +) { + organizationCreateClusterAgentMapping(input: $input) { + errors + } +} diff --git a/ee/app/assets/javascripts/workspaces/admin_settings/graphql/mutations/delete_org_cluster_agent_mapping.mutation.graphql b/ee/app/assets/javascripts/workspaces/admin_settings/graphql/mutations/delete_org_cluster_agent_mapping.mutation.graphql new file mode 100644 index 0000000000000000000000000000000000000000..504eb0da10365afa27bf9e2c21d34db704409cb7 --- /dev/null +++ b/ee/app/assets/javascripts/workspaces/admin_settings/graphql/mutations/delete_org_cluster_agent_mapping.mutation.graphql @@ -0,0 +1,7 @@ +mutation deleteOrganizationClusterAgentMapping( + $input: OrganizationDeleteClusterAgentMappingInput! +) { + organizationDeleteClusterAgentMapping(input: $input) { + errors + } +} diff --git a/ee/app/assets/javascripts/workspaces/admin_settings/graphql/queries/organization_workspaces_cluster_agents.query.graphql b/ee/app/assets/javascripts/workspaces/admin_settings/graphql/queries/organization_workspaces_cluster_agents.query.graphql index 833c3892009e5a9d9573e97459a64a788b5e6b8a..14b75f568429920ecb4608c8dde822f2bf53fe17 100644 --- a/ee/app/assets/javascripts/workspaces/admin_settings/graphql/queries/organization_workspaces_cluster_agents.query.graphql +++ b/ee/app/assets/javascripts/workspaces/admin_settings/graphql/queries/organization_workspaces_cluster_agents.query.graphql @@ -6,7 +6,7 @@ query organizationWorkspacesClusterAgents( organization(id: $organizationId) { id organizationWorkspacesClusterAgents: workspacesClusterAgents( - filter: DIRECTLY_MAPPED + filter: ALL first: 5 before: $before after: $after diff --git a/ee/app/assets/javascripts/workspaces/admin_settings/pages/app.vue b/ee/app/assets/javascripts/workspaces/admin_settings/pages/app.vue index c0338217d1a10815b4bdb935c330cc109011735a..64d87f416b33226c9e519623630f2cf3acac5cc5 100644 --- a/ee/app/assets/javascripts/workspaces/admin_settings/pages/app.vue +++ b/ee/app/assets/javascripts/workspaces/admin_settings/pages/app.vue @@ -2,7 +2,6 @@ import { GlBadge, GlTableLite, - GlToggle, GlSkeletonLoader, GlLink, GlAlert, @@ -12,19 +11,18 @@ import { s__ } from '~/locale'; import SettingsBlock from '~/vue_shared/components/settings/settings_block.vue'; import AvailabilityPopover from '../components/availability_popover.vue'; +import ClusterAgentAvailabilityToggle from '../components/availability_toggle.vue'; import GetOrganizationWorkspacesClusterAgentsQuery from '../components/get_organization_workspaces_cluster_agents_query.vue'; -import { AVAILABILITY_TEXT } from '../constants'; - export default { name: 'WorkspacesAgentAvailabilityApp', components: { SettingsBlock, GetOrganizationWorkspacesClusterAgentsQuery, AvailabilityPopover, + ClusterAgentAvailabilityToggle, GlTableLite, GlBadge, - GlToggle, GlSkeletonLoader, GlLink, GlAlert, @@ -41,9 +39,6 @@ export default { }, }, methods: { - getAvailabilityText(availability) { - return AVAILABILITY_TEXT[availability] ?? null; - }, getStatusBadgeMetadata(item) { const { isConnected } = item; return { @@ -126,15 +121,10 @@ export default { }} { + let createOrgClusterAgentMappingMutationHandler; + let deleteOrgClusterAgentMappingMutationHandler; + let apolloProvider; + let wrapper; + + const setupApolloProvider = ( + MOCK_CREATE_RESULT = CREATE_ORG_CLUSTER_AGENT_MAPPING_MUTATION_RESULT, + MOCK_DELETE_RESULT = DELETE_ORG_CLUSTER_AGENT_MAPPING_MUTATION_RESULT, + ) => { + createOrgClusterAgentMappingMutationHandler = jest + .fn() + .mockResolvedValueOnce(MOCK_CREATE_RESULT); + deleteOrgClusterAgentMappingMutationHandler = jest + .fn() + .mockResolvedValueOnce(MOCK_DELETE_RESULT); + + apolloProvider = createMockApollo([ + [createClusterAgentMappingMutation, createOrgClusterAgentMappingMutationHandler], + [deleteClusterAgentMappingMutation, deleteOrgClusterAgentMappingMutationHandler], + ]); + + apolloProvider.clients.defaultClient.writeQuery({ + query: mappedOrganizationClusterAgentsQuery, + variables: { + organizationId: MOCK_ORG_ID, + }, + data: ORGANIZATION_MAPPED_AGENTS_QUERY_RESULT.data, + }); + }; + + const getAgentFromMappedAgentsStore = (agentId) => { + const data = apolloProvider.clients.defaultClient.cache.readQuery({ + query: mappedOrganizationClusterAgentsQuery, + variables: { + organizationId: MOCK_ORG_ID, + }, + }); + + const mappedAgents = data.organization.mappedAgents.nodes; + return mappedAgents.filter((agent) => agent.id === agentId); + }; + + const buildWrapper = async (propsData = {}) => { + wrapper = shallowMount(ClusterAgentAvailabilityToggle, { + apolloProvider, + propsData: { + agentId: MOCK_AGENT_ID, + isMapped: true, + ...propsData, + }, + provide: { + organizationId: MOCK_ORG_ID, + }, + }); + + await waitForPromises(); + }; + + const findToggle = () => wrapper.findComponent(GlToggle); + const findAvailabilityText = () => wrapper.find('[data-test-id="availability-text"]'); + const findErrorMessage = () => wrapper.find('[data-test-id="error-message"]'); + + describe('when agent is mapped', () => { + beforeEach(() => { + setupApolloProvider(); + buildWrapper({ + isMapped: true, + }); + }); + + it('renders toggle with correct text', () => { + expect(findToggle().props('label')).toEqual('Available'); + expect(findToggle().props('value')).toBe(true); + expect(findAvailabilityText().text()).toEqual('Available'); + }); + + it('calls delete org cluster agent mutation on toggle', async () => { + findToggle().vm.$emit('change'); + await nextTick(); + + expect(deleteOrgClusterAgentMappingMutationHandler).toHaveBeenCalledTimes(1); + expect(deleteOrgClusterAgentMappingMutationHandler).toHaveBeenCalledWith({ + input: { + organizationId: MOCK_ORG_ID, + clusterAgentId: MOCK_AGENT_ID, + }, + }); + }); + + it('removes agent from mappedAgents data in store when mutation is successful', async () => { + // This agent ID exists in ORGANIZATION_MAPPED_AGENTS_QUERY_RESULT + const MOCK_MAPPED_AGENT_ID = 'gid://gitlab/Clusters::Agent/10'; + + buildWrapper({ + agentId: MOCK_MAPPED_AGENT_ID, + isMapped: true, + }); + + expect(getAgentFromMappedAgentsStore(MOCK_MAPPED_AGENT_ID)).toHaveLength(1); + + findToggle().vm.$emit('change'); + await waitForPromises(); + await nextTick(); + + expect(findToggle().props('disabled')).toBe(false); + expect(getAgentFromMappedAgentsStore(MOCK_MAPPED_AGENT_ID)).toHaveLength(0); + }); + }); + + describe('when agent is unmapped', () => { + beforeEach(() => { + setupApolloProvider(); + buildWrapper({ + isMapped: false, + }); + }); + + it('renders toggle with correct text', () => { + expect(findToggle().props('label')).toEqual('Blocked'); + expect(findToggle().props('value')).toBe(false); + expect(findAvailabilityText().text()).toEqual('Blocked'); + }); + + it('calls create org cluster agent mutation on toggle', async () => { + findToggle().vm.$emit('change'); + await nextTick(); + + expect(createOrgClusterAgentMappingMutationHandler).toHaveBeenCalledTimes(1); + expect(createOrgClusterAgentMappingMutationHandler).toHaveBeenCalledWith({ + input: { + organizationId: MOCK_ORG_ID, + clusterAgentId: MOCK_AGENT_ID, + }, + }); + }); + + it('adds agent to mappedAgents data in store when mutation is successful', async () => { + // This agent ID does not exist in ORGANIZATION_MAPPED_AGENTS_QUERY_RESULT + const MOCK_UNMAPPED_AGENT_ID = 'gid://gitlab/Clusters::Agent/6'; + + buildWrapper({ + agentId: MOCK_UNMAPPED_AGENT_ID, + isMapped: false, + }); + + expect(getAgentFromMappedAgentsStore(MOCK_UNMAPPED_AGENT_ID)).toHaveLength(0); + + findToggle().vm.$emit('change'); + await waitForPromises(); + await nextTick(); + + expect(findToggle().props('disabled')).toBe(false); + expect(getAgentFromMappedAgentsStore(MOCK_UNMAPPED_AGENT_ID)).toHaveLength(1); + }); + }); + + describe('on mutation error', () => { + beforeEach(() => { + setupApolloProvider( + CREATE_ORG_CLUSTER_AGENT_MAPPING_MUTATION_RESULT_WITH_ERROR, + DELETE_ORG_CLUSTER_AGENT_MAPPING_MUTATION_RESULT_WITH_ERROR, + ); + }); + + it.each` + isMapped | expectedErrorMessage + ${true} | ${'This agent is already blocked.'} + ${false} | ${'This agent is already available.'} + `( + 'displays correct error message when mutation fails when agent is mapped: $isMapped', + async ({ isMapped, expectedErrorMessage }) => { + buildWrapper({ + isMapped, + }); + findToggle().vm.$emit('change'); + await waitForPromises(); + await nextTick(); + + expect(findToggle().props('disabled')).toBe(false); + expect(findErrorMessage().text()).toEqual(expectedErrorMessage); + }, + ); + }); +}); diff --git a/ee/spec/frontend/workspaces/admin_settings/components/get_organization_workspaces_cluster_agents_query_spec.js b/ee/spec/frontend/workspaces/admin_settings/components/get_organization_workspaces_cluster_agents_query_spec.js index 660c8b2d0aad244a801cde0b9624a978b874dbd0..8f17b4b5b1f2fc51a1c70a44ca60c16efde4b749 100644 --- a/ee/spec/frontend/workspaces/admin_settings/components/get_organization_workspaces_cluster_agents_query_spec.js +++ b/ee/spec/frontend/workspaces/admin_settings/components/get_organization_workspaces_cluster_agents_query_spec.js @@ -18,6 +18,88 @@ Vue.use(VueApollo); jest.mock('~/lib/logger'); const MOCK_ORG_ID = 'gid://gitlab/Organizations::Organization/1'; +const MOCK_AGENTS_RESULT = [ + { + id: 'gid://gitlab/Clusters::Agent/14', + isMapped: true, + group: 'Gitlab Org', + isConnected: false, + name: 'midnightowlgarden', + project: 'gitlab-agent-configurations', + url: 'http://test.host/gitlab-org/gitlab-agent-configurations/-/cluster_agents/midnightowlgarden', + workspacesEnabled: true, + }, + { + id: 'gid://gitlab/Clusters::Agent/13', + isMapped: false, + group: 'Gitlab Org', + isConnected: false, + name: 'coastalechovalley', + project: 'gitlab-agent-configurations', + url: 'http://test.host/gitlab-org/gitlab-agent-configurations/-/cluster_agents/coastalechovalley', + workspacesEnabled: true, + }, + { + id: 'gid://gitlab/Clusters::Agent/12', + isMapped: true, + group: 'Gitlab Org', + isConnected: false, + name: 'wandingbreezetale', + project: 'gitlab-agent-configurations', + url: 'http://test.host/gitlab-org/gitlab-agent-configurations/-/cluster_agents/wandingbreezetale', + workspacesEnabled: false, + }, + { + id: 'gid://gitlab/Clusters::Agent/11', + isMapped: false, + group: 'Gitlab Org', + isConnected: false, + name: 'crimsonmapleshadow', + project: 'gitlab-agent-configurations', + url: 'http://test.host/gitlab-org/gitlab-agent-configurations/-/cluster_agents/crimsonmapleshadow', + workspacesEnabled: false, + }, + { + id: 'gid://gitlab/Clusters::Agent/10', + isMapped: true, + group: 'Gitlab Org', + isConnected: true, + name: 'meadowsageharbor', + project: 'gitlab-agent-configurations', + url: 'http://test.host/gitlab-org/gitlab-agent-configurations/-/cluster_agents/meadowsageharbor', + workspacesEnabled: true, + }, + { + id: 'gid://gitlab/Clusters::Agent/16', + isMapped: false, + group: 'Gitlab Org', + isConnected: true, + name: 'silvermoonharbor', + project: 'gitlab-agent-configurations', + url: 'http://test.host/gitlab-org/gitlab-agent-configurations/-/cluster_agents/silvermoonharbor', + workspacesEnabled: true, + }, + { + id: 'gid://gitlab/Clusters::Agent/17', + isMapped: true, + group: 'Gitlab Org', + isConnected: true, + name: 'silvermoonharbor', + project: 'gitlab-agent-configurations', + url: 'http://test.host/gitlab-org/gitlab-agent-configurations/-/cluster_agents/silvermoonharbor', + workspacesEnabled: false, + }, + { + id: 'gid://gitlab/Clusters::Agent/18', + isMapped: false, + group: 'Gitlab Org', + isConnected: true, + name: 'oceanbreezecliff', + project: 'gitlab-agent-configurations', + url: 'http://test.host/gitlab-org/gitlab-agent-configurations/-/cluster_agents/oceanbreezecliff', + workspacesEnabled: false, + }, +]; describe('workspaces/admin_settings/components/get_organization_workspaces_cluster_agents_query.vue', () => { const defaultSlotSpy = jest.fn(); @@ -93,7 +175,7 @@ describe('workspaces/admin_settings/components/get_organization_workspaces_clust loading: false, pagination: null, error: mockError, - agents: [], + agents: null, }); }); }); @@ -138,7 +220,7 @@ describe('workspaces/admin_settings/components/get_organization_workspaces_clust loading: false, pagination: null, error: mockError, - agents: [], + agents: null, }); }); @@ -149,11 +231,16 @@ describe('workspaces/admin_settings/components/get_organization_workspaces_clust ORGANIZATION_WORKSPACES_CLUSTER_AGENTS_QUERY_RESULT, ); await buildWrapperWithOrg(); + await nextTick(); }); - it('renders correct data to scoped slot', () => { + const getAgentInScopedSlot = (agentId) => { + return defaultSlotSpy.mock.lastCall[0].agents.find((agent) => agent.id === agentId); + }; + + it('returns correct data to scoped slot', () => { const scopedSlotCall = defaultSlotSpy.mock.lastCall[0]; - const expectedPaginationData = { + const expectedPaginationResult = { show: true, hasPreviousPage: false, hasNextPage: true, @@ -161,97 +248,42 @@ describe('workspaces/admin_settings/components/get_organization_workspaces_clust prevPage: wrapper.vm.prevPage, }; - const expectedAgentsResult = [ - { - // gid://gitlab/Clusters::Agent/14 - availability: 'available', - group: 'Gitlab Org', - isConnected: false, - name: 'midnightowlgarden', - project: 'gitlab-agent-configurations', - url: 'http://test.host/gitlab-org/gitlab-agent-configurations/-/cluster_agents/midnightowlgarden', - workspacesEnabled: true, - }, - // gid://gitlab/Clusters::Agent/13 - { - availability: 'blocked', - group: 'Gitlab Org', - isConnected: false, - name: 'coastalechovalley', - project: 'gitlab-agent-configurations', - url: 'http://test.host/gitlab-org/gitlab-agent-configurations/-/cluster_agents/coastalechovalley', - workspacesEnabled: true, - }, - // gid://gitlab/Clusters::Agent/12 - { - availability: 'available', - group: 'Gitlab Org', - isConnected: false, - name: 'wandingbreezetale', - project: 'gitlab-agent-configurations', - url: 'http://test.host/gitlab-org/gitlab-agent-configurations/-/cluster_agents/wandingbreezetale', - workspacesEnabled: false, - }, - // gid://gitlab/Clusters::Agent/11 - { - availability: 'blocked', - group: 'Gitlab Org', - isConnected: false, - name: 'crimsonmapleshadow', - project: 'gitlab-agent-configurations', - url: 'http://test.host/gitlab-org/gitlab-agent-configurations/-/cluster_agents/crimsonmapleshadow', - workspacesEnabled: false, - }, - // gid://gitlab/Clusters::Agent/10 - { - availability: 'available', - group: 'Gitlab Org', - isConnected: true, - name: 'meadowsageharbor', - project: 'gitlab-agent-configurations', - url: 'http://test.host/gitlab-org/gitlab-agent-configurations/-/cluster_agents/meadowsageharbor', - workspacesEnabled: true, - }, - // gid://gitlab/Clusters::Agent/16 - { - availability: 'blocked', - group: 'Gitlab Org', - isConnected: true, - name: 'silvermoonharbor', - project: 'gitlab-agent-configurations', - url: 'http://test.host/gitlab-org/gitlab-agent-configurations/-/cluster_agents/silvermoonharbor', - workspacesEnabled: true, - }, - // gid://gitlab/Clusters::Agent/17 - { - availability: 'available', - group: 'Gitlab Org', - isConnected: true, - name: 'silvermoonharbor', - project: 'gitlab-agent-configurations', - url: 'http://test.host/gitlab-org/gitlab-agent-configurations/-/cluster_agents/silvermoonharbor', - workspacesEnabled: false, - }, - // gid://gitlab/Clusters::Agent/18 - { - availability: 'blocked', - group: 'Gitlab Org', - isConnected: true, - name: 'oceanbreezecliff', - project: 'gitlab-agent-configurations', - url: 'http://test.host/gitlab-org/gitlab-agent-configurations/-/cluster_agents/oceanbreezecliff', - workspacesEnabled: false, - }, - ]; - expect(scopedSlotCall).toMatchObject({ loading: false, - pagination: expectedPaginationData, + pagination: expectedPaginationResult, error: null, - agents: expectedAgentsResult, + agents: MOCK_AGENTS_RESULT, }); }); + it('returns correct agents to scopped slot when mappedAgents is updated', async () => { + const MOCK_AGENT_ID = 'gid://gitlab/Clusters::Agent/14'; + const agentResult = MOCK_AGENTS_RESULT.find((agent) => agent.id === MOCK_AGENT_ID); + + const agentBefore = getAgentInScopedSlot(MOCK_AGENT_ID); + + expect(agentBefore).toStrictEqual(agentResult); + + // Unmap the agent + const newMappedAgentIds = + ORGANIZATION_MAPPED_AGENTS_QUERY_RESULT.data.organization.mappedAgents.nodes.filter( + (agent) => agent.id !== MOCK_AGENT_ID, + ); + const newMappedAgents = new Set(newMappedAgentIds); + + // Directly set the data to simulate Apollo's update + wrapper.vm.mappedAgents = newMappedAgents; + + await nextTick(); + + const agentAfter = getAgentInScopedSlot(MOCK_AGENT_ID); + const expectedAgentResult = { + ...agentResult, + isMapped: false, + }; + expect(agentAfter).toStrictEqual(expectedAgentResult); + }); + it.each` methodName | expectedVariables ${'nextPage'} | ${{ organizationId: MOCK_ORG_ID, before: null, after: 'eyJpZCI6IjEwIn0' }} diff --git a/ee/spec/frontend/workspaces/admin_settings/pages/app_spec.js b/ee/spec/frontend/workspaces/admin_settings/pages/app_spec.js index 71b0446d65297504906f6ded9da9c9537ac93e3a..3d970cf9e9a097b20f7bf078206417aef8e482fe 100644 --- a/ee/spec/frontend/workspaces/admin_settings/pages/app_spec.js +++ b/ee/spec/frontend/workspaces/admin_settings/pages/app_spec.js @@ -11,6 +11,7 @@ import { mountExtended } from 'helpers/vue_test_utils_helper'; import WorkspacesAgentAvailabilityApp from 'ee_component/workspaces/admin_settings/pages/app.vue'; import AvailabilityPopover from 'ee_component/workspaces/admin_settings/components/availability_popover.vue'; import GetOrganizationWorkspacesClusterAgentsQuery from 'ee_component/workspaces/admin_settings/components/get_organization_workspaces_cluster_agents_query.vue'; +import AvailabilityToggle from 'ee_component/workspaces/admin_settings/components/availability_toggle.vue'; import { stubComponent } from 'helpers/stub_component'; const MOCK_ORG_ID = 'gid://gitlab/Organizations::Organization/1'; @@ -18,13 +19,14 @@ const MOCK_ORG_ID = 'gid://gitlab/Organizations::Organization/1'; const createMockAgents = (customAgent = {}) => { return [ { - availability: 'available', - group: 'Gitlab Org', - isConnected: false, + id: 'gid://gitlab/Clusters::Agent/14', name: 'midnightowlgarden', - project: 'gitlab-agent-configurations', url: 'http://test.host/gitlab-org/gitlab-agent-configurations/-/cluster_agents/midnightowlgarden', + project: 'gitlab-agent-configurations', + group: 'Gitlab Org', workspacesEnabled: true, + isConnected: false, + isMapped: true, ...customAgent, }, ]; @@ -73,6 +75,7 @@ describe('workspaces/admin_settings/pages/app.vue', () => { const findAlert = () => wrapper.findComponent(GlAlert); const findPagination = () => wrapper.findComponent(GlKeysetPagination); const findLoadingState = () => wrapper.findComponent(GlSkeletonLoader); + const findAvailabilityToggle = () => wrapper.findComponent(AvailabilityToggle); describe('default', () => { beforeEach(async () => { @@ -150,6 +153,12 @@ describe('workspaces/admin_settings/pages/app.vue', () => { expect(nameElement.exists()).toBe(true); expect(nameElement.attributes('href')).toBe(mockResult[0].url); }); + + it('renders agent availability toggle', () => { + expect(findAvailabilityToggle().exists()).toBe(true); + expect(findAvailabilityToggle().props('agentId')).toBe(mockResult[0].id); + expect(findAvailabilityToggle().props('isMapped')).toBe(mockResult[0].isMapped); + }); }); }); diff --git a/ee/spec/frontend/workspaces/mock_data/index.js b/ee/spec/frontend/workspaces/mock_data/index.js index 129db9a6c2b2b5033c74930a1042cd19f6bebbdc..038bb58da2106841080e18ed5c1f183bbfd94b0d 100644 --- a/ee/spec/frontend/workspaces/mock_data/index.js +++ b/ee/spec/frontend/workspaces/mock_data/index.js @@ -907,3 +907,35 @@ export const ORGANIZATION_WORKSPACES_CLUSTER_AGENTS_QUERY_RESULT = { }, }, }; + +export const CREATE_ORG_CLUSTER_AGENT_MAPPING_MUTATION_RESULT = { + data: { + organizationCreateClusterAgentMapping: { + errors: [], + }, + }, +}; + +export const CREATE_ORG_CLUSTER_AGENT_MAPPING_MUTATION_RESULT_WITH_ERROR = { + data: { + organizationCreateClusterAgentMapping: { + errors: ['Cluster agent mapping already exists'], + }, + }, +}; + +export const DELETE_ORG_CLUSTER_AGENT_MAPPING_MUTATION_RESULT = { + data: { + organizationDeleteClusterAgentMapping: { + errors: [], + }, + }, +}; + +export const DELETE_ORG_CLUSTER_AGENT_MAPPING_MUTATION_RESULT_WITH_ERROR = { + data: { + organizationDeleteClusterAgentMapping: { + errors: ['Cluster agent mapping already exists'], + }, + }, +}; diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 9d6fb02941f271ccdf80dc68983fd55baebe9f77..f63a886604a6bde4cf9dee0bfba91510b4997ee0 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -70754,9 +70754,6 @@ msgstr "" msgid "Workspaces|If your devfile is not in the root directory of your project, specify a relative path." msgstr "" -msgid "Workspaces|In order to make an agent available/blocked, workspaces must be enabled in the agent's configuration." -msgstr "" - msgid "Workspaces|Instant development environments" msgstr "" @@ -70841,6 +70838,9 @@ msgstr "" msgid "Workspaces|This agent is already allowed." msgstr "" +msgid "Workspaces|This agent is already available." +msgstr "" + msgid "Workspaces|This agent is already blocked." msgstr ""