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';
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';
......
<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) {
......
<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>
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,
},
});
},
});
};
......@@ -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 } }
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);
});
......
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);
});
});
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