diff --git a/app/assets/javascripts/graphql_shared/constants.js b/app/assets/javascripts/graphql_shared/constants.js index aad7712a9f05f0d54a8214f6410f9c6e6b0a1a4b..95ddf12afae7f8b1d6d779dc6d951f188c46b77c 100644 --- a/app/assets/javascripts/graphql_shared/constants.js +++ b/app/assets/javascripts/graphql_shared/constants.js @@ -5,6 +5,7 @@ export const TYPE_ITERATION = 'Iteration'; export const TYPE_ITERATIONS_CADENCE = 'Iterations::Cadence'; export const TYPE_MERGE_REQUEST = 'MergeRequest'; export const TYPE_MILESTONE = 'Milestone'; +export const TYPE_PROJECT = 'Project'; export const TYPE_SCANNER_PROFILE = 'DastScannerProfile'; export const TYPE_SITE_PROFILE = 'DastSiteProfile'; export const TYPE_USER = 'User'; diff --git a/app/assets/javascripts/runner/components/runner_registration_token_reset.vue b/app/assets/javascripts/runner/components/runner_registration_token_reset.vue index 2335faa4f859d5c496f3aa1996d168b3ade02b7e..cdf14abd4f9fa9227fe191f98413b2346fd29d7d 100644 --- a/app/assets/javascripts/runner/components/runner_registration_token_reset.vue +++ b/app/assets/javascripts/runner/components/runner_registration_token_reset.vue @@ -1,6 +1,8 @@ <script> import { GlButton } from '@gitlab/ui'; import createFlash, { FLASH_TYPES } from '~/flash'; +import { TYPE_GROUP, TYPE_PROJECT } from '~/graphql_shared/constants'; +import { convertToGraphQLId } from '~/graphql_shared/utils'; import { __, s__ } from '~/locale'; import runnersRegistrationTokenResetMutation from '~/runner/graphql/runners_registration_token_reset.mutation.graphql'; import { captureException } from '~/runner/sentry_utils'; @@ -11,6 +13,14 @@ export default { components: { GlButton, }, + inject: { + groupId: { + default: null, + }, + projectId: { + default: null, + }, + }, props: { type: { type: String, @@ -25,7 +35,28 @@ export default { loading: false, }; }, - computed: {}, + computed: { + resetTokenInput() { + switch (this.type) { + case INSTANCE_TYPE: + return { + type: this.type, + }; + case GROUP_TYPE: + return { + id: convertToGraphQLId(TYPE_GROUP, this.groupId), + type: this.type, + }; + case PROJECT_TYPE: + return { + id: convertToGraphQLId(TYPE_PROJECT, this.projectId), + type: this.type, + }; + default: + return null; + } + }, + }, methods: { async resetToken() { // TODO Replace confirmation with gl-modal @@ -44,13 +75,7 @@ export default { } = await this.$apollo.mutate({ mutation: runnersRegistrationTokenResetMutation, variables: { - // TODO Currently INTANCE_TYPE only is supported - // In future iterations this component will support - // other registration token types. - // See: https://gitlab.com/gitlab-org/gitlab/-/issues/19819 - input: { - type: this.type, - }, + input: this.resetTokenInput, }, }); if (errors && errors.length) { diff --git a/app/assets/javascripts/runner/group_runners/group_runners_app.vue b/app/assets/javascripts/runner/group_runners/group_runners_app.vue index 92d881c43ea564ca722555ca4f334eea245773a8..07bbf60c4538b5b9fe017b08bc0e1047885d3b15 100644 --- a/app/assets/javascripts/runner/group_runners/group_runners_app.vue +++ b/app/assets/javascripts/runner/group_runners/group_runners_app.vue @@ -1,10 +1,20 @@ <script> +import RunnerManualSetupHelp from '../components/runner_manual_setup_help.vue'; import RunnerTypeHelp from '../components/runner_type_help.vue'; +import { GROUP_TYPE } from '../constants'; export default { components: { + RunnerManualSetupHelp, RunnerTypeHelp, }, + props: { + registrationToken: { + type: String, + required: true, + }, + }, + GROUP_TYPE, }; </script> @@ -14,6 +24,12 @@ export default { <div class="col-sm-6"> <runner-type-help /> </div> + <div class="col-sm-6"> + <runner-manual-setup-help + :registration-token="registrationToken" + :type="$options.GROUP_TYPE" + /> + </div> </div> </div> </template> diff --git a/app/assets/javascripts/runner/group_runners/index.js b/app/assets/javascripts/runner/group_runners/index.js index 5a72b09de3a4f75752e20cc1d27ae795275cfb62..e14c583d73ecad65065130b97189dce74196a423 100644 --- a/app/assets/javascripts/runner/group_runners/index.js +++ b/app/assets/javascripts/runner/group_runners/index.js @@ -1,6 +1,10 @@ import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import createDefaultClient from '~/lib/graphql'; import GroupRunnersApp from './group_runners_app.vue'; +Vue.use(VueApollo); + export const initGroupRunners = (selector = '#js-group-runners') => { const el = document.querySelector(selector); @@ -8,10 +12,29 @@ export const initGroupRunners = (selector = '#js-group-runners') => { return null; } + const { registrationToken, groupId } = el.dataset; + + const apolloProvider = new VueApollo({ + defaultClient: createDefaultClient( + {}, + { + assumeImmutableResults: true, + }, + ), + }); + return new Vue({ el, + apolloProvider, + provide: { + groupId, + }, render(h) { - return h(GroupRunnersApp); + return h(GroupRunnersApp, { + props: { + registrationToken, + }, + }); }, }); }; diff --git a/app/views/groups/runners/index.html.haml b/app/views/groups/runners/index.html.haml index 08be0f93d82dafeab0d3e7fcc06a8770529b2c25..4e7bc99b1f0c305a85acd678b9c0a847f4a47fb3 100644 --- a/app/views/groups/runners/index.html.haml +++ b/app/views/groups/runners/index.html.haml @@ -3,4 +3,4 @@ %h2.page-title = s_('Runners|Group Runners') -#js-group-runners +#js-group-runners{ data: { registration_token: @group.runners_token, group_id: @group.id } } diff --git a/spec/frontend/runner/components/runner_registration_token_reset_spec.js b/spec/frontend/runner/components/runner_registration_token_reset_spec.js index 6dc207e369c3031ca68c9d68c05b2b336a683d4f..8b360b88417d1bd50776874d93dff6ddcfb56568 100644 --- a/spec/frontend/runner/components/runner_registration_token_reset_spec.js +++ b/spec/frontend/runner/components/runner_registration_token_reset_spec.js @@ -1,11 +1,12 @@ import { GlButton } from '@gitlab/ui'; import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import createFlash, { FLASH_TYPES } from '~/flash'; import RunnerRegistrationTokenReset from '~/runner/components/runner_registration_token_reset.vue'; -import { INSTANCE_TYPE } from '~/runner/constants'; +import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE } from '~/runner/constants'; import runnersRegistrationTokenResetMutation from '~/runner/graphql/runners_registration_token_reset.mutation.graphql'; import { captureException } from '~/runner/sentry_utils'; @@ -23,11 +24,13 @@ describe('RunnerRegistrationTokenReset', () => { const findButton = () => wrapper.findComponent(GlButton); - const createComponent = () => { + const createComponent = ({ props, provide = {} } = {}) => { wrapper = shallowMount(RunnerRegistrationTokenReset, { localVue, + provide, propsData: { type: INSTANCE_TYPE, + ...props, }, apolloProvider: createMockApollo([ [runnersRegistrationTokenResetMutation, runnersRegistrationTokenResetMutationHandler], @@ -59,31 +62,47 @@ describe('RunnerRegistrationTokenReset', () => { }); describe('On click and confirmation', () => { - beforeEach(async () => { - window.confirm.mockReturnValueOnce(true); - await findButton().vm.$emit('click'); - }); + const mockGroupId = '11'; + const mockProjectId = '22'; + + describe.each` + type | provide | expectedInput + ${INSTANCE_TYPE} | ${{}} | ${{ type: INSTANCE_TYPE }} + ${GROUP_TYPE} | ${{ groupId: mockGroupId }} | ${{ type: GROUP_TYPE, id: `gid://gitlab/Group/${mockGroupId}` }} + ${PROJECT_TYPE} | ${{ projectId: mockProjectId }} | ${{ type: PROJECT_TYPE, id: `gid://gitlab/Project/${mockProjectId}` }} + `('Resets token of type $type', ({ type, provide, expectedInput }) => { + beforeEach(async () => { + createComponent({ + provide, + props: { type }, + }); + + window.confirm.mockReturnValueOnce(true); + findButton().vm.$emit('click'); + await waitForPromises(); + }); - it('resets token', () => { - expect(runnersRegistrationTokenResetMutationHandler).toHaveBeenCalledTimes(1); - expect(runnersRegistrationTokenResetMutationHandler).toHaveBeenCalledWith({ - input: { type: INSTANCE_TYPE }, + it('resets token', () => { + expect(runnersRegistrationTokenResetMutationHandler).toHaveBeenCalledTimes(1); + expect(runnersRegistrationTokenResetMutationHandler).toHaveBeenCalledWith({ + input: expectedInput, + }); }); - }); - it('emits result', () => { - expect(wrapper.emitted('tokenReset')).toHaveLength(1); - expect(wrapper.emitted('tokenReset')[0]).toEqual([mockNewToken]); - }); + it('emits result', () => { + expect(wrapper.emitted('tokenReset')).toHaveLength(1); + expect(wrapper.emitted('tokenReset')[0]).toEqual([mockNewToken]); + }); - it('does not show a loading state', () => { - expect(findButton().props('loading')).toBe(false); - }); + it('does not show a loading state', () => { + expect(findButton().props('loading')).toBe(false); + }); - it('shows confirmation', () => { - expect(createFlash).toHaveBeenLastCalledWith({ - message: expect.stringContaining('registration token generated'), - type: FLASH_TYPES.SUCCESS, + it('shows confirmation', () => { + expect(createFlash).toHaveBeenLastCalledWith({ + message: expect.stringContaining('registration token generated'), + type: FLASH_TYPES.SUCCESS, + }); }); }); }); @@ -91,7 +110,8 @@ describe('RunnerRegistrationTokenReset', () => { describe('On click without confirmation', () => { beforeEach(async () => { window.confirm.mockReturnValueOnce(false); - await findButton().vm.$emit('click'); + findButton().vm.$emit('click'); + await waitForPromises(); }); it('does not reset token', () => { @@ -118,7 +138,7 @@ describe('RunnerRegistrationTokenReset', () => { runnersRegistrationTokenResetMutationHandler.mockRejectedValueOnce(new Error(mockErrorMsg)); window.confirm.mockReturnValueOnce(true); - await findButton().vm.$emit('click'); + findButton().vm.$emit('click'); await waitForPromises(); expect(createFlash).toHaveBeenLastCalledWith({ @@ -144,7 +164,7 @@ describe('RunnerRegistrationTokenReset', () => { }); window.confirm.mockReturnValueOnce(true); - await findButton().vm.$emit('click'); + findButton().vm.$emit('click'); await waitForPromises(); expect(createFlash).toHaveBeenLastCalledWith({ @@ -160,7 +180,8 @@ describe('RunnerRegistrationTokenReset', () => { describe('Immediately after click', () => { it('shows loading state', async () => { window.confirm.mockReturnValue(true); - await findButton().vm.$emit('click'); + findButton().vm.$emit('click'); + await nextTick(); expect(findButton().props('loading')).toBe(true); }); diff --git a/spec/frontend/runner/group_runners/group_runners_app_spec.js b/spec/frontend/runner/group_runners/group_runners_app_spec.js index 06329686a0032ec76b0ab1065e5d673d7092d3c9..6a0863e92b4201b685a6d7c26f41632088d0fe29 100644 --- a/spec/frontend/runner/group_runners/group_runners_app_spec.js +++ b/spec/frontend/runner/group_runners/group_runners_app_spec.js @@ -1,14 +1,22 @@ import { shallowMount } from '@vue/test-utils'; +import RunnerManualSetupHelp from '~/runner/components/runner_manual_setup_help.vue'; import RunnerTypeHelp from '~/runner/components/runner_type_help.vue'; import GroupRunnersApp from '~/runner/group_runners/group_runners_app.vue'; +const mockRegistrationToken = 'AABBCC'; + describe('GroupRunnersApp', () => { let wrapper; const findRunnerTypeHelp = () => wrapper.findComponent(RunnerTypeHelp); + const findRunnerManualSetupHelp = () => wrapper.findComponent(RunnerManualSetupHelp); const createComponent = ({ mountFn = shallowMount } = {}) => { - wrapper = mountFn(GroupRunnersApp); + wrapper = mountFn(GroupRunnersApp, { + propsData: { + registrationToken: mockRegistrationToken, + }, + }); }; beforeEach(() => { @@ -18,4 +26,9 @@ describe('GroupRunnersApp', () => { it('shows the runner type help', () => { expect(findRunnerTypeHelp().exists()).toBe(true); }); + + it('shows the runner setup instructions', () => { + expect(findRunnerManualSetupHelp().exists()).toBe(true); + expect(findRunnerManualSetupHelp().props('registrationToken')).toBe(mockRegistrationToken); + }); });