Commit 08e1047a authored by Simon Knox's avatar Simon Knox

Merge branch '352887-refetch-on-delete' into 'master'

Use events to refetch runners data

See merge request gitlab-org/gitlab!81163
parents da9f22f9 bc82f520
......@@ -224,6 +224,10 @@ export default {
}
return '';
},
onDeleted({ message }) {
this.$root.$toast?.show(message);
this.$apollo.queries.runners.refetch();
},
reportToSentry(error) {
captureException({ error, component: this.$options.name });
},
......@@ -282,7 +286,11 @@ export default {
</gl-link>
</template>
<template #runner-actions-cell="{ runner }">
<runner-actions-cell :runner="runner" :edit-url="runner.editAdminUrl" />
<runner-actions-cell
:runner="runner"
:edit-url="runner.editAdminUrl"
@deleted="onDeleted"
/>
</template>
</runner-list>
<runner-pagination
......
......@@ -23,6 +23,7 @@ export default {
required: false,
},
},
emits: ['deleted'],
computed: {
canUpdate() {
return this.runner.userPermissions?.updateRunner;
......@@ -31,6 +32,11 @@ export default {
return this.runner.userPermissions?.deleteRunner;
},
},
methods: {
onDeleted(value) {
this.$emit('deleted', value);
},
},
};
</script>
......@@ -38,6 +44,6 @@ export default {
<gl-button-group>
<runner-edit-button v-if="canUpdate && editUrl" :href="editUrl" />
<runner-pause-button v-if="canUpdate" :runner="runner" :compact="true" />
<runner-delete-button v-if="canDelete" :runner="runner" :compact="true" />
<runner-delete-button v-if="canDelete" :runner="runner" :compact="true" @deleted="onDeleted" />
</gl-button-group>
</template>
......@@ -2,14 +2,12 @@
import { GlButton, GlModalDirective, GlTooltipDirective } from '@gitlab/ui';
import runnerDeleteMutation from '~/runner/graphql/shared/runner_delete.mutation.graphql';
import { createAlert } from '~/flash';
import { s__, sprintf } from '~/locale';
import { sprintf } from '~/locale';
import { captureException } from '~/runner/sentry_utils';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { I18N_DELETE_RUNNER } from '../constants';
import { I18N_DELETE_RUNNER, I18N_DELETED_TOAST } from '../constants';
import RunnerDeleteModal from './runner_delete_modal.vue';
const I18N_DELETED_TOAST = s__('Runners|Runner %{name} was deleted');
export default {
name: 'RunnerDeleteButton',
components: {
......@@ -34,6 +32,7 @@ export default {
default: false,
},
},
emits: ['deleted'],
data() {
return {
deleting: false,
......@@ -102,12 +101,13 @@ export default {
id: this.runner.id,
},
},
refetchQueries: ['getRunners', 'getGroupRunners'],
});
if (errors && errors.length) {
throw new Error(errors.join(' '));
} else {
this.$root.$toast?.show(sprintf(I18N_DELETED_TOAST, { name: this.runnerName }));
this.$emit('deleted', {
message: sprintf(I18N_DELETED_TOAST, { name: this.runnerName }),
});
}
} catch (e) {
this.deleting = false;
......
......@@ -40,6 +40,7 @@ export const I18N_EDIT = __('Edit');
export const I18N_PAUSE = __('Pause');
export const I18N_RESUME = __('Resume');
export const I18N_DELETE_RUNNER = s__('Runners|Delete runner');
export const I18N_DELETED_TOAST = s__('Runners|Runner %{name} was deleted');
export const I18N_LOCKED_RUNNER_DESCRIPTION = s__('Runners|You cannot assign to other projects');
export const I18N_PAUSED_RUNNER_DESCRIPTION = s__('Runners|Not available to run jobs');
......
......@@ -16,13 +16,13 @@ import RunnerActionsCell from '../components/cells/runner_actions_cell.vue';
import { statusTokenConfig } from '../components/search_tokens/status_token_config';
import {
I18N_FETCH_ERROR,
GROUP_FILTERED_SEARCH_NAMESPACE,
GROUP_TYPE,
PROJECT_TYPE,
STATUS_ONLINE,
STATUS_OFFLINE,
STATUS_STALE,
I18N_FETCH_ERROR,
} from '../constants';
import groupRunnersQuery from '../graphql/list/group_runners.query.graphql';
import groupRunnersCountQuery from '../graphql/list/group_runners_count.query.graphql';
......@@ -241,6 +241,10 @@ export default {
editUrl(runner) {
return this.runners.urlsById[runner.id]?.edit;
},
onDeleted({ message }) {
this.$root.$toast?.show(message);
this.$apollo.queries.runners.refetch();
},
reportToSentry(error) {
captureException({ error, component: this.$options.name });
},
......@@ -298,7 +302,7 @@ export default {
</gl-link>
</template>
<template #runner-actions-cell="{ runner }">
<runner-actions-cell :runner="runner" :edit-url="editUrl(runner)" />
<runner-actions-cell :runner="runner" :edit-url="editUrl(runner)" @deleted="onDeleted" />
</template>
</runner-list>
<runner-pagination
......
import Vue from 'vue';
import { GlLink } from '@gitlab/ui';
import { GlToast, GlLink } from '@gitlab/ui';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import setWindowLocation from 'helpers/set_window_location_helper';
......@@ -18,8 +18,8 @@ import RunnerTypeTabs from '~/runner/components/runner_type_tabs.vue';
import RunnerFilteredSearchBar from '~/runner/components/runner_filtered_search_bar.vue';
import RunnerList from '~/runner/components/runner_list.vue';
import RunnerStats from '~/runner/components/stat/runner_stats.vue';
import RegistrationDropdown from '~/runner/components/registration/registration_dropdown.vue';
import RunnerActionsCell from '~/runner/components/cells/runner_actions_cell.vue';
import RegistrationDropdown from '~/runner/components/registration/registration_dropdown.vue';
import RunnerPagination from '~/runner/components/runner_pagination.vue';
import {
......@@ -52,6 +52,7 @@ jest.mock('~/lib/utils/url_utility', () => ({
}));
Vue.use(VueApollo);
Vue.use(GlToast);
describe('AdminRunnersApp', () => {
let wrapper;
......@@ -59,6 +60,7 @@ describe('AdminRunnersApp', () => {
let mockRunnersCountQuery;
const findRunnerStats = () => wrapper.findComponent(RunnerStats);
const findRunnerActionsCell = () => wrapper.findComponent(RunnerActionsCell);
const findRegistrationDropdown = () => wrapper.findComponent(RegistrationDropdown);
const findRunnerTypeTabs = () => wrapper.findComponent(RunnerTypeTabs);
const findRunnerList = () => wrapper.findComponent(RunnerList);
......@@ -95,6 +97,7 @@ describe('AdminRunnersApp', () => {
afterEach(() => {
mockRunnersQuery.mockReset();
mockRunnersCountQuery.mockReset();
wrapper.destroy();
});
......@@ -228,6 +231,41 @@ describe('AdminRunnersApp', () => {
]);
});
describe('Single runner row', () => {
let showToast;
const mockRunner = runnersData.data.runners.nodes[0];
const { id: graphqlId, shortSha } = mockRunner;
const id = getIdFromGraphQLId(graphqlId);
beforeEach(async () => {
mockRunnersQuery.mockClear();
createComponent({ mountFn: mountExtended });
showToast = jest.spyOn(wrapper.vm.$root.$toast, 'show');
await waitForPromises();
});
it('Links to the runner page', async () => {
const runnerLink = wrapper.find('tr [data-testid="td-summary"]').find(GlLink);
expect(runnerLink.text()).toBe(`#${id} (${shortSha})`);
expect(runnerLink.attributes('href')).toBe(`http://localhost/admin/runners/${id}`);
});
it('When runner is deleted, data is refetched and a toast message is shown', async () => {
expect(mockRunnersQuery).toHaveBeenCalledTimes(1);
findRunnerActionsCell().vm.$emit('deleted', { message: 'Runner deleted' });
expect(mockRunnersQuery).toHaveBeenCalledTimes(2);
expect(showToast).toHaveBeenCalledTimes(1);
expect(showToast).toHaveBeenCalledWith('Runner deleted');
});
});
describe('when a filter is preselected', () => {
beforeEach(async () => {
setWindowLocation(`?status[]=${STATUS_ACTIVE}&runner_type[]=${INSTANCE_TYPE}&tag[]=tag1`);
......
......@@ -92,6 +92,18 @@ describe('RunnerActionsCell', () => {
expect(findDeleteBtn().props('compact')).toBe(true);
});
it('Emits delete events', () => {
const value = { name: 'Runner' };
createComponent();
expect(wrapper.emitted('deleted')).toBe(undefined);
findDeleteBtn().vm.$emit('deleted', value);
expect(wrapper.emitted('deleted')).toEqual([[value]]);
});
it('Does not render the runner delete button when user cannot delete', () => {
createComponent({
runner: {
......
import Vue from 'vue';
import { GlButton, GlToast } from '@gitlab/ui';
import { GlButton } from '@gitlab/ui';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
......@@ -19,7 +19,6 @@ const mockRunner = runnersData.data.runners.nodes[0];
const mockRunnerId = getIdFromGraphQLId(mockRunner.id);
Vue.use(VueApollo);
Vue.use(GlToast);
jest.mock('~/flash');
jest.mock('~/runner/sentry_utils');
......@@ -27,7 +26,6 @@ jest.mock('~/runner/sentry_utils');
describe('RunnerDeleteButton', () => {
let wrapper;
let runnerDeleteHandler;
let showToast;
const getTooltip = () => getBinding(wrapper.element, 'gl-tooltip').value;
const getModal = () => getBinding(wrapper.element, 'gl-modal').value;
......@@ -52,8 +50,6 @@ describe('RunnerDeleteButton', () => {
GlModal: createMockDirective(),
},
});
showToast = jest.spyOn(wrapper.vm.$root.$toast, 'show').mockImplementation(() => {});
};
const clickOkAndWait = async () => {
......@@ -128,7 +124,7 @@ describe('RunnerDeleteButton', () => {
await clickOkAndWait();
});
it('The mutation to delete is called', async () => {
it('The mutation to delete is called', () => {
expect(runnerDeleteHandler).toHaveBeenCalledTimes(1);
expect(runnerDeleteHandler).toHaveBeenCalledWith({
input: {
......@@ -137,8 +133,12 @@ describe('RunnerDeleteButton', () => {
});
});
it('The user is notified', async () => {
expect(showToast).toHaveBeenCalledTimes(1);
it('The user can be notified with an event', () => {
const deleted = wrapper.emitted('deleted');
expect(deleted).toHaveLength(1);
expect(deleted[0][0].message).toMatch(`#${mockRunnerId}`);
expect(deleted[0][0].message).toMatch(`${mockRunner.shortSha}`);
});
});
......
import Vue, { nextTick } from 'vue';
import { GlButton, GlLink } from '@gitlab/ui';
import { GlButton, GlLink, GlToast } from '@gitlab/ui';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import setWindowLocation from 'helpers/set_window_location_helper';
......@@ -17,6 +17,7 @@ import RunnerTypeTabs from '~/runner/components/runner_type_tabs.vue';
import RunnerFilteredSearchBar from '~/runner/components/runner_filtered_search_bar.vue';
import RunnerList from '~/runner/components/runner_list.vue';
import RunnerStats from '~/runner/components/stat/runner_stats.vue';
import RunnerActionsCell from '~/runner/components/cells/runner_actions_cell.vue';
import RegistrationDropdown from '~/runner/components/registration/registration_dropdown.vue';
import RunnerPagination from '~/runner/components/runner_pagination.vue';
......@@ -40,6 +41,7 @@ import FilteredSearch from '~/vue_shared/components/filtered_search_bar/filtered
import { groupRunnersData, groupRunnersDataPaginated, groupRunnersCountData } from '../mock_data';
Vue.use(VueApollo);
Vue.use(GlToast);
const mockGroupFullPath = 'group1';
const mockRegistrationToken = 'AABBCC';
......@@ -59,6 +61,7 @@ describe('GroupRunnersApp', () => {
let mockGroupRunnersCountQuery;
const findRunnerStats = () => wrapper.findComponent(RunnerStats);
const findRunnerActionsCell = () => wrapper.findComponent(RunnerActionsCell);
const findRegistrationDropdown = () => wrapper.findComponent(RegistrationDropdown);
const findRunnerTypeTabs = () => wrapper.findComponent(RunnerTypeTabs);
const findRunnerList = () => wrapper.findComponent(RunnerList);
......@@ -187,12 +190,17 @@ describe('GroupRunnersApp', () => {
});
describe('Single runner row', () => {
let showToast;
const { webUrl, editUrl, node } = mockGroupRunnersEdges[0];
const { id: graphqlId, shortSha } = node;
const id = getIdFromGraphQLId(graphqlId);
beforeEach(async () => {
mockGroupRunnersQuery.mockClear();
createComponent({ mountFn: mountExtended });
showToast = jest.spyOn(wrapper.vm.$root.$toast, 'show');
await waitForPromises();
});
......@@ -212,6 +220,17 @@ describe('GroupRunnersApp', () => {
href: editUrl,
});
});
it('When runner is deleted, data is refetched and a toast is shown', async () => {
expect(mockGroupRunnersQuery).toHaveBeenCalledTimes(1);
findRunnerActionsCell().vm.$emit('deleted', { message: 'Runner deleted' });
expect(mockGroupRunnersQuery).toHaveBeenCalledTimes(2);
expect(showToast).toHaveBeenCalledTimes(1);
expect(showToast).toHaveBeenCalledWith('Runner deleted');
});
});
describe('when a filter is preselected', () => {
......
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