Commit 718b30c2 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '19819-add-token-reset-block' into 'master'

Add registration token reset to group runners

See merge request gitlab-org/gitlab!66825
parents 439eabd6 69fe83f2
......@@ -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