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'; ...@@ -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,31 +62,47 @@ describe('RunnerRegistrationTokenReset', () => { ...@@ -59,31 +62,47 @@ describe('RunnerRegistrationTokenReset', () => {
}); });
describe('On click and confirmation', () => { describe('On click and confirmation', () => {
beforeEach(async () => { const mockGroupId = '11';
window.confirm.mockReturnValueOnce(true); const mockProjectId = '22';
await findButton().vm.$emit('click');
}); 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', () => { it('resets token', () => {
expect(runnersRegistrationTokenResetMutationHandler).toHaveBeenCalledTimes(1); expect(runnersRegistrationTokenResetMutationHandler).toHaveBeenCalledTimes(1);
expect(runnersRegistrationTokenResetMutationHandler).toHaveBeenCalledWith({ expect(runnersRegistrationTokenResetMutationHandler).toHaveBeenCalledWith({
input: { type: INSTANCE_TYPE }, input: expectedInput,
});
}); });
});
it('emits result', () => { it('emits result', () => {
expect(wrapper.emitted('tokenReset')).toHaveLength(1); expect(wrapper.emitted('tokenReset')).toHaveLength(1);
expect(wrapper.emitted('tokenReset')[0]).toEqual([mockNewToken]); expect(wrapper.emitted('tokenReset')[0]).toEqual([mockNewToken]);
}); });
it('does not show a loading state', () => { it('does not show a loading state', () => {
expect(findButton().props('loading')).toBe(false); expect(findButton().props('loading')).toBe(false);
}); });
it('shows confirmation', () => { it('shows confirmation', () => {
expect(createFlash).toHaveBeenLastCalledWith({ expect(createFlash).toHaveBeenLastCalledWith({
message: expect.stringContaining('registration token generated'), message: expect.stringContaining('registration token generated'),
type: FLASH_TYPES.SUCCESS, type: FLASH_TYPES.SUCCESS,
});
}); });
}); });
}); });
...@@ -91,7 +110,8 @@ describe('RunnerRegistrationTokenReset', () => { ...@@ -91,7 +110,8 @@ 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