Commit 69fe83f2 authored by Miguel Rincon's avatar Miguel Rincon

Add registration token reset to group runners

This change adds the possibility for users to reset their registration
token in their group.
parent 4ad862fa
...@@ -5,6 +5,7 @@ export const TYPE_ITERATION = 'Iteration'; ...@@ -5,6 +5,7 @@ export const TYPE_ITERATION = 'Iteration';
export const TYPE_ITERATIONS_CADENCE = 'Iterations::Cadence'; export const TYPE_ITERATIONS_CADENCE = 'Iterations::Cadence';
export const TYPE_MERGE_REQUEST = 'MergeRequest'; export const TYPE_MERGE_REQUEST = 'MergeRequest';
export const TYPE_MILESTONE = 'Milestone'; export const TYPE_MILESTONE = 'Milestone';
export const TYPE_PROJECT = 'Project';
export const TYPE_SCANNER_PROFILE = 'DastScannerProfile'; export const TYPE_SCANNER_PROFILE = 'DastScannerProfile';
export const TYPE_SITE_PROFILE = 'DastSiteProfile'; export const TYPE_SITE_PROFILE = 'DastSiteProfile';
export const TYPE_USER = 'User'; export const TYPE_USER = 'User';
......
<script> <script>
import { GlButton } from '@gitlab/ui'; import { GlButton } from '@gitlab/ui';
import createFlash, { FLASH_TYPES } from '~/flash'; 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 { __, s__ } from '~/locale';
import runnersRegistrationTokenResetMutation from '~/runner/graphql/runners_registration_token_reset.mutation.graphql'; import runnersRegistrationTokenResetMutation from '~/runner/graphql/runners_registration_token_reset.mutation.graphql';
import { captureException } from '~/runner/sentry_utils'; import { captureException } from '~/runner/sentry_utils';
...@@ -11,6 +13,14 @@ export default { ...@@ -11,6 +13,14 @@ export default {
components: { components: {
GlButton, GlButton,
}, },
inject: {
groupId: {
default: null,
},
projectId: {
default: null,
},
},
props: { props: {
type: { type: {
type: String, type: String,
...@@ -25,7 +35,28 @@ export default { ...@@ -25,7 +35,28 @@ export default {
loading: false, 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: { methods: {
async resetToken() { async resetToken() {
// TODO Replace confirmation with gl-modal // TODO Replace confirmation with gl-modal
...@@ -44,13 +75,7 @@ export default { ...@@ -44,13 +75,7 @@ export default {
} = await this.$apollo.mutate({ } = await this.$apollo.mutate({
mutation: runnersRegistrationTokenResetMutation, mutation: runnersRegistrationTokenResetMutation,
variables: { variables: {
// TODO Currently INTANCE_TYPE only is supported input: this.resetTokenInput,
// In future iterations this component will support
// other registration token types.
// See: https://gitlab.com/gitlab-org/gitlab/-/issues/19819
input: {
type: this.type,
},
}, },
}); });
if (errors && errors.length) { if (errors && errors.length) {
......
<script> <script>
import RunnerManualSetupHelp from '../components/runner_manual_setup_help.vue';
import RunnerTypeHelp from '../components/runner_type_help.vue'; import RunnerTypeHelp from '../components/runner_type_help.vue';
import { GROUP_TYPE } from '../constants';
export default { export default {
components: { components: {
RunnerManualSetupHelp,
RunnerTypeHelp, RunnerTypeHelp,
}, },
props: {
registrationToken: {
type: String,
required: true,
},
},
GROUP_TYPE,
}; };
</script> </script>
...@@ -14,6 +24,12 @@ export default { ...@@ -14,6 +24,12 @@ export default {
<div class="col-sm-6"> <div class="col-sm-6">
<runner-type-help /> <runner-type-help />
</div> </div>
<div class="col-sm-6">
<runner-manual-setup-help
:registration-token="registrationToken"
:type="$options.GROUP_TYPE"
/>
</div>
</div> </div>
</div> </div>
</template> </template>
import Vue from 'vue'; import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import GroupRunnersApp from './group_runners_app.vue'; import GroupRunnersApp from './group_runners_app.vue';
Vue.use(VueApollo);
export const initGroupRunners = (selector = '#js-group-runners') => { export const initGroupRunners = (selector = '#js-group-runners') => {
const el = document.querySelector(selector); const el = document.querySelector(selector);
...@@ -8,10 +12,29 @@ export const initGroupRunners = (selector = '#js-group-runners') => { ...@@ -8,10 +12,29 @@ export const initGroupRunners = (selector = '#js-group-runners') => {
return null; return null;
} }
const { registrationToken, groupId } = el.dataset;
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(
{},
{
assumeImmutableResults: true,
},
),
});
return new Vue({ return new Vue({
el, el,
apolloProvider,
provide: {
groupId,
},
render(h) { render(h) {
return h(GroupRunnersApp); return h(GroupRunnersApp, {
props: {
registrationToken,
},
});
}, },
}); });
}; };
...@@ -3,4 +3,4 @@ ...@@ -3,4 +3,4 @@
%h2.page-title %h2.page-title
= s_('Runners|Group Runners') = s_('Runners|Group Runners')
#js-group-runners #js-group-runners{ data: { registration_token: @group.runners_token, group_id: @group.id } }
import { GlButton } from '@gitlab/ui'; import { GlButton } from '@gitlab/ui';
import { createLocalVue, shallowMount } from '@vue/test-utils'; import { createLocalVue, shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import createFlash, { FLASH_TYPES } from '~/flash'; import createFlash, { FLASH_TYPES } from '~/flash';
import RunnerRegistrationTokenReset from '~/runner/components/runner_registration_token_reset.vue'; 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 runnersRegistrationTokenResetMutation from '~/runner/graphql/runners_registration_token_reset.mutation.graphql';
import { captureException } from '~/runner/sentry_utils'; import { captureException } from '~/runner/sentry_utils';
...@@ -23,11 +24,13 @@ describe('RunnerRegistrationTokenReset', () => { ...@@ -23,11 +24,13 @@ describe('RunnerRegistrationTokenReset', () => {
const findButton = () => wrapper.findComponent(GlButton); const findButton = () => wrapper.findComponent(GlButton);
const createComponent = () => { const createComponent = ({ props, provide = {} } = {}) => {
wrapper = shallowMount(RunnerRegistrationTokenReset, { wrapper = shallowMount(RunnerRegistrationTokenReset, {
localVue, localVue,
provide,
propsData: { propsData: {
type: INSTANCE_TYPE, type: INSTANCE_TYPE,
...props,
}, },
apolloProvider: createMockApollo([ apolloProvider: createMockApollo([
[runnersRegistrationTokenResetMutation, runnersRegistrationTokenResetMutationHandler], [runnersRegistrationTokenResetMutation, runnersRegistrationTokenResetMutationHandler],
...@@ -59,15 +62,30 @@ describe('RunnerRegistrationTokenReset', () => { ...@@ -59,15 +62,30 @@ describe('RunnerRegistrationTokenReset', () => {
}); });
describe('On click and confirmation', () => { describe('On click and confirmation', () => {
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 () => { beforeEach(async () => {
createComponent({
provide,
props: { type },
});
window.confirm.mockReturnValueOnce(true); window.confirm.mockReturnValueOnce(true);
await findButton().vm.$emit('click'); findButton().vm.$emit('click');
await waitForPromises();
}); });
it('resets token', () => { it('resets token', () => {
expect(runnersRegistrationTokenResetMutationHandler).toHaveBeenCalledTimes(1); expect(runnersRegistrationTokenResetMutationHandler).toHaveBeenCalledTimes(1);
expect(runnersRegistrationTokenResetMutationHandler).toHaveBeenCalledWith({ expect(runnersRegistrationTokenResetMutationHandler).toHaveBeenCalledWith({
input: { type: INSTANCE_TYPE }, input: expectedInput,
}); });
}); });
...@@ -87,11 +105,13 @@ describe('RunnerRegistrationTokenReset', () => { ...@@ -87,11 +105,13 @@ describe('RunnerRegistrationTokenReset', () => {
}); });
}); });
}); });
});
describe('On click without confirmation', () => { describe('On click without confirmation', () => {
beforeEach(async () => { beforeEach(async () => {
window.confirm.mockReturnValueOnce(false); window.confirm.mockReturnValueOnce(false);
await findButton().vm.$emit('click'); findButton().vm.$emit('click');
await waitForPromises();
}); });
it('does not reset token', () => { it('does not reset token', () => {
...@@ -118,7 +138,7 @@ describe('RunnerRegistrationTokenReset', () => { ...@@ -118,7 +138,7 @@ describe('RunnerRegistrationTokenReset', () => {
runnersRegistrationTokenResetMutationHandler.mockRejectedValueOnce(new Error(mockErrorMsg)); runnersRegistrationTokenResetMutationHandler.mockRejectedValueOnce(new Error(mockErrorMsg));
window.confirm.mockReturnValueOnce(true); window.confirm.mockReturnValueOnce(true);
await findButton().vm.$emit('click'); findButton().vm.$emit('click');
await waitForPromises(); await waitForPromises();
expect(createFlash).toHaveBeenLastCalledWith({ expect(createFlash).toHaveBeenLastCalledWith({
...@@ -144,7 +164,7 @@ describe('RunnerRegistrationTokenReset', () => { ...@@ -144,7 +164,7 @@ describe('RunnerRegistrationTokenReset', () => {
}); });
window.confirm.mockReturnValueOnce(true); window.confirm.mockReturnValueOnce(true);
await findButton().vm.$emit('click'); findButton().vm.$emit('click');
await waitForPromises(); await waitForPromises();
expect(createFlash).toHaveBeenLastCalledWith({ expect(createFlash).toHaveBeenLastCalledWith({
...@@ -160,7 +180,8 @@ describe('RunnerRegistrationTokenReset', () => { ...@@ -160,7 +180,8 @@ describe('RunnerRegistrationTokenReset', () => {
describe('Immediately after click', () => { describe('Immediately after click', () => {
it('shows loading state', async () => { it('shows loading state', async () => {
window.confirm.mockReturnValue(true); window.confirm.mockReturnValue(true);
await findButton().vm.$emit('click'); findButton().vm.$emit('click');
await nextTick();
expect(findButton().props('loading')).toBe(true); expect(findButton().props('loading')).toBe(true);
}); });
......
import { shallowMount } from '@vue/test-utils'; 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 RunnerTypeHelp from '~/runner/components/runner_type_help.vue';
import GroupRunnersApp from '~/runner/group_runners/group_runners_app.vue'; import GroupRunnersApp from '~/runner/group_runners/group_runners_app.vue';
const mockRegistrationToken = 'AABBCC';
describe('GroupRunnersApp', () => { describe('GroupRunnersApp', () => {
let wrapper; let wrapper;
const findRunnerTypeHelp = () => wrapper.findComponent(RunnerTypeHelp); const findRunnerTypeHelp = () => wrapper.findComponent(RunnerTypeHelp);
const findRunnerManualSetupHelp = () => wrapper.findComponent(RunnerManualSetupHelp);
const createComponent = ({ mountFn = shallowMount } = {}) => { const createComponent = ({ mountFn = shallowMount } = {}) => {
wrapper = mountFn(GroupRunnersApp); wrapper = mountFn(GroupRunnersApp, {
propsData: {
registrationToken: mockRegistrationToken,
},
});
}; };
beforeEach(() => { beforeEach(() => {
...@@ -18,4 +26,9 @@ describe('GroupRunnersApp', () => { ...@@ -18,4 +26,9 @@ describe('GroupRunnersApp', () => {
it('shows the runner type help', () => { it('shows the runner type help', () => {
expect(findRunnerTypeHelp().exists()).toBe(true); expect(findRunnerTypeHelp().exists()).toBe(true);
}); });
it('shows the runner setup instructions', () => {
expect(findRunnerManualSetupHelp().exists()).toBe(true);
expect(findRunnerManualSetupHelp().props('registrationToken')).toBe(mockRegistrationToken);
});
}); });
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment