Commit eb50675e authored by Jose Ivan Vargas's avatar Jose Ivan Vargas

Merge branch '329658-add-delete-action-to-runner' into 'master'

Add Delete runner action to Runners Vue UI

See merge request gitlab-org/gitlab!63176
parents 76080438 42af780b
<script> <script>
import { GlButton, GlButtonGroup, GlTooltipDirective } from '@gitlab/ui'; import { GlButton, GlButtonGroup, GlTooltipDirective } from '@gitlab/ui';
import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { __ } from '~/locale'; import { __, s__ } from '~/locale';
import deleteRunnerMutation from '~/runner/graphql/delete_runner.mutation.graphql';
import updateRunnerMutation from '~/runner/graphql/update_runner.mutation.graphql'; import updateRunnerMutation from '~/runner/graphql/update_runner.mutation.graphql';
const i18n = { const i18n = {
I18N_EDIT: __('Edit'), I18N_EDIT: __('Edit'),
I18N_PAUSE: __('Pause'), I18N_PAUSE: __('Pause'),
I18N_RESUME: __('Resume'), I18N_RESUME: __('Resume'),
I18N_REMOVE: __('Remove'),
I18N_REMOVE_CONFIRMATION: s__('Runners|Are you sure you want to delete this runner?'),
}; };
export default { export default {
...@@ -27,6 +30,7 @@ export default { ...@@ -27,6 +30,7 @@ export default {
data() { data() {
return { return {
updating: false, updating: false,
deleting: false,
}; };
}, },
computed: { computed: {
...@@ -46,12 +50,16 @@ export default { ...@@ -46,12 +50,16 @@ export default {
toggleActiveTitle() { toggleActiveTitle() {
if (this.updating) { if (this.updating) {
// Prevent a "sticky" tooltip: If this button is disabled, // Prevent a "sticky" tooltip: If this button is disabled,
// mouseout listeners will not run and the tooltip will // mouseout listeners don't run leaving the tooltip stuck
// stay stuck on the button.
return ''; return '';
} }
return this.isActive ? i18n.I18N_PAUSE : i18n.I18N_RESUME; return this.isActive ? i18n.I18N_PAUSE : i18n.I18N_RESUME;
}, },
deleteTitle() {
// Prevent a "sticky" tooltip: If element gets removed,
// mouseout listeners don't run and leaving the tooltip stuck
return this.deleting ? '' : i18n.I18N_REMOVE;
},
}, },
methods: { methods: {
async onToggleActive() { async onToggleActive() {
...@@ -87,6 +95,39 @@ export default { ...@@ -87,6 +95,39 @@ export default {
} }
}, },
async onDelete() {
// TODO Replace confirmation with gl-modal
// eslint-disable-next-line no-alert
if (!window.confirm(i18n.I18N_REMOVE_CONFIRMATION)) {
return;
}
this.deleting = true;
try {
const {
data: {
runnerDelete: { errors },
},
} = await this.$apollo.mutate({
mutation: deleteRunnerMutation,
variables: {
input: {
id: this.runner.id,
},
},
awaitRefetchQueries: true,
refetchQueries: ['getRunners'],
});
if (errors && errors.length) {
this.onError(new Error(errors[0]));
}
} catch (e) {
this.onError(e);
} finally {
this.deleting = false;
}
},
onError(error) { onError(error) {
// TODO Render errors when "delete" action is done // TODO Render errors when "delete" action is done
// `active` toggle would not fail due to user input. // `active` toggle would not fail due to user input.
...@@ -116,6 +157,15 @@ export default { ...@@ -116,6 +157,15 @@ export default {
data-testid="toggle-active-runner" data-testid="toggle-active-runner"
@click="onToggleActive" @click="onToggleActive"
/> />
<!-- TODO add delete action to update runners --> <gl-button
v-gl-tooltip.hover.viewport
:title="deleteTitle"
:aria-label="deleteTitle"
icon="close"
:loading="deleting"
variant="danger"
data-testid="delete-runner"
@click="onDelete"
/>
</gl-button-group> </gl-button-group>
</template> </template>
mutation runnerDelete($input: RunnerDeleteInput!) {
runnerDelete(input: $input) {
errors
}
}
...@@ -28342,6 +28342,9 @@ msgstr "" ...@@ -28342,6 +28342,9 @@ msgstr ""
msgid "Runners|Architecture" msgid "Runners|Architecture"
msgstr "" msgstr ""
msgid "Runners|Are you sure you want to delete this runner?"
msgstr ""
msgid "Runners|Can run untagged jobs" msgid "Runners|Can run untagged jobs"
msgstr "" msgstr ""
......
...@@ -2,16 +2,21 @@ import { shallowMount } from '@vue/test-utils'; ...@@ -2,16 +2,21 @@ import { shallowMount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import RunnerActionCell from '~/runner/components/cells/runner_actions_cell.vue'; import RunnerActionCell from '~/runner/components/cells/runner_actions_cell.vue';
import deleteRunnerMutation from '~/runner/graphql/delete_runner.mutation.graphql';
import getRunnersQuery from '~/runner/graphql/get_runners.query.graphql';
import updateRunnerMutation from '~/runner/graphql/update_runner.mutation.graphql'; import updateRunnerMutation from '~/runner/graphql/update_runner.mutation.graphql';
const mockId = '1'; const mockId = '1';
const getRunnersQueryName = getRunnersQuery.definitions[0].name.value;
describe('RunnerTypeCell', () => { describe('RunnerTypeCell', () => {
let wrapper; let wrapper;
let mutate; let mutate;
const findEditBtn = () => wrapper.findByTestId('edit-runner'); const findEditBtn = () => wrapper.findByTestId('edit-runner');
const findToggleActiveBtn = () => wrapper.findByTestId('toggle-active-runner'); const findToggleActiveBtn = () => wrapper.findByTestId('toggle-active-runner');
const findDeleteBtn = () => wrapper.findByTestId('delete-runner');
const createComponent = ({ active = true } = {}, options) => { const createComponent = ({ active = true } = {}, options) => {
wrapper = extendedWrapper( wrapper = extendedWrapper(
...@@ -78,6 +83,11 @@ describe('RunnerTypeCell', () => { ...@@ -78,6 +83,11 @@ describe('RunnerTypeCell', () => {
await findToggleActiveBtn().vm.$emit('click'); await findToggleActiveBtn().vm.$emit('click');
expect(findToggleActiveBtn().props('loading')).toBe(true); expect(findToggleActiveBtn().props('loading')).toBe(true);
});
it(`After the ${icon} button is clicked, stale tooltip is removed`, async () => {
await findToggleActiveBtn().vm.$emit('click');
expect(findToggleActiveBtn().attributes('title')).toBe(''); expect(findToggleActiveBtn().attributes('title')).toBe('');
expect(findToggleActiveBtn().attributes('aria-label')).toBe(''); expect(findToggleActiveBtn().attributes('aria-label')).toBe('');
}); });
...@@ -106,4 +116,86 @@ describe('RunnerTypeCell', () => { ...@@ -106,4 +116,86 @@ describe('RunnerTypeCell', () => {
}); });
}); });
}); });
describe('When the user clicks a runner', () => {
beforeEach(() => {
createComponent();
mutate.mockResolvedValue({
data: {
runnerDelete: {
runner: {
id: `gid://gitlab/Ci::Runner/1`,
__typename: 'CiRunner',
},
},
},
});
jest.spyOn(window, 'confirm');
});
describe('When the user confirms deletion', () => {
beforeEach(async () => {
window.confirm.mockReturnValue(true);
await findDeleteBtn().vm.$emit('click');
});
it('The user sees a confirmation alert', async () => {
expect(window.confirm).toHaveBeenCalledTimes(1);
expect(window.confirm).toHaveBeenCalledWith(expect.any(String));
});
it('The delete mutation is called correctly', () => {
expect(mutate).toHaveBeenCalledTimes(1);
expect(mutate).toHaveBeenCalledWith({
mutation: deleteRunnerMutation,
variables: {
input: {
id: `gid://gitlab/Ci::Runner/${mockId}`,
},
},
awaitRefetchQueries: true,
refetchQueries: [getRunnersQueryName],
});
});
it('The delete button does not have a loading state', () => {
expect(findDeleteBtn().props('loading')).toBe(false);
expect(findDeleteBtn().attributes('title')).toBe('Remove');
});
it('After the delete button is clicked, loading state is shown', async () => {
await findDeleteBtn().vm.$emit('click');
expect(findDeleteBtn().props('loading')).toBe(true);
});
it('After the delete button is clicked, stale tooltip is removed', async () => {
await findDeleteBtn().vm.$emit('click');
expect(findDeleteBtn().attributes('title')).toBe('');
});
});
describe('When the user does not confirm deletion', () => {
beforeEach(async () => {
window.confirm.mockReturnValue(false);
await findDeleteBtn().vm.$emit('click');
});
it('The user sees a confirmation alert', () => {
expect(window.confirm).toHaveBeenCalledTimes(1);
});
it('The delete mutation is not called', () => {
expect(mutate).toHaveBeenCalledTimes(0);
});
it('The delete button does not have a loading state', () => {
expect(findDeleteBtn().props('loading')).toBe(false);
expect(findDeleteBtn().attributes('title')).toBe('Remove');
});
});
});
}); });
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