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 @@
+
+
+
+
+
+
{{ availabilityText }}
+
+
+ {{ errorMessage }}
+
+
+
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 {
}}
-
-
-
{{ getAvailabilityText(item.availability) }}
-
+
{
+ 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 ""