Skip to content
Snippets Groups Projects
Commit 69fe83f2 authored by Miguel Rincon's avatar Miguel Rincon
Browse files

Add registration token reset to group runners

This change adds the possibility for users to reset their registration
token in their group.
parent 4ad862fa
No related branches found
No related tags found
No related merge requests found
Loading
Loading
@@ -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';
Loading
Loading
<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';
Loading
Loading
@@ -11,6 +13,14 @@ export default {
components: {
GlButton,
},
inject: {
groupId: {
default: null,
},
projectId: {
default: null,
},
},
props: {
type: {
type: String,
Loading
Loading
@@ -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
Loading
Loading
@@ -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) {
Loading
Loading
<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>
 
Loading
Loading
@@ -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);
 
Loading
Loading
@@ -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,
},
});
},
});
};
Loading
Loading
@@ -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';
 
Loading
Loading
@@ -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],
Loading
Loading
@@ -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,
});
});
});
});
Loading
Loading
@@ -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', () => {
Loading
Loading
@@ -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({
Loading
Loading
@@ -144,7 +164,7 @@ describe('RunnerRegistrationTokenReset', () => {
});
 
window.confirm.mockReturnValueOnce(true);
await findButton().vm.$emit('click');
findButton().vm.$emit('click');
await waitForPromises();
 
expect(createFlash).toHaveBeenLastCalledWith({
Loading
Loading
@@ -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);
});
Loading
Loading
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(() => {
Loading
Loading
@@ -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);
});
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment