Commit a68a1849 authored by Miguel Rincon's avatar Miguel Rincon

Refetch runners list data after runner is updated

This change updates data refresh mechanism for the runners UI, when
a runner is deleted or paused/unpaused.

Total count of runners should stay up to date as users interact with
the runner data, and remove some ambiguity as to when data is refreshed.

Changelog: fixed
parent 3dbb300d
<script>
import { GlBadge, GlLink } from '@gitlab/ui';
import { createAlert } from '~/flash';
import { fetchPolicies } from '~/lib/graphql';
import { updateHistory } from '~/lib/utils/url_utility';
import { formatNumber } from '~/locale';
import { fetchPolicies } from '~/lib/graphql';
import RegistrationDropdown from '../components/registration/registration_dropdown.vue';
import RunnerFilteredSearchBar from '../components/runner_filtered_search_bar.vue';
......@@ -37,7 +37,7 @@ import { captureException } from '../sentry_utils';
const runnersCountSmartQuery = {
query: runnersAdminCountQuery,
fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
fetchPolicy: fetchPolicies.NETWORK_ONLY,
update(data) {
return data?.runners?.count;
},
......@@ -78,10 +78,7 @@ export default {
apollo: {
runners: {
query: runnersAdminQuery,
// Runners can be updated by users directly in this list.
// A "cache and network" policy prevents outdated filtered
// results.
fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
fetchPolicy: fetchPolicies.NETWORK_ONLY,
variables() {
return this.variables;
},
......@@ -224,9 +221,19 @@ export default {
}
return '';
},
refetchFilteredCounts() {
this.$apollo.queries.allRunnersCount.refetch();
this.$apollo.queries.instanceRunnersCount.refetch();
this.$apollo.queries.groupRunnersCount.refetch();
this.$apollo.queries.projectRunnersCount.refetch();
},
onToggledPaused() {
// When a runner is Paused, the tab count can
// become stale, refetch outdated counts.
this.refetchFilteredCounts();
},
onDeleted({ message }) {
this.$root.$toast?.show(message);
this.$apollo.queries.runners.refetch();
},
reportToSentry(error) {
captureException({ error, component: this.$options.name });
......@@ -289,6 +296,7 @@ export default {
<runner-actions-cell
:runner="runner"
:edit-url="runner.editAdminUrl"
@toggledPaused="onToggledPaused"
@deleted="onDeleted"
/>
</template>
......
......@@ -23,7 +23,7 @@ export default {
required: false,
},
},
emits: ['deleted'],
emits: ['toggledPaused', 'deleted'],
computed: {
canUpdate() {
return this.runner.userPermissions?.updateRunner;
......@@ -33,6 +33,9 @@ export default {
},
},
methods: {
onToggledPaused() {
this.$emit('toggledPaused');
},
onDeleted(value) {
this.$emit('deleted', value);
},
......@@ -43,7 +46,12 @@ export default {
<template>
<gl-button-group>
<runner-edit-button v-if="canUpdate && editUrl" :href="editUrl" />
<runner-pause-button v-if="canUpdate" :runner="runner" :compact="true" />
<runner-pause-button
v-if="canUpdate"
:runner="runner"
:compact="true"
@toggledPaused="onToggledPaused"
/>
<runner-delete-button
:disabled="!canDelete"
:runner="runner"
......
......@@ -126,6 +126,11 @@ export default {
id: this.runner.id,
},
},
update: (cache) => {
// Remove deleted runner from the cache
const cacheId = cache.identify(this.runner);
cache.evict({ id: cacheId });
},
});
if (errors && errors.length) {
throw new Error(errors.join(' '));
......
......@@ -24,6 +24,7 @@ export default {
default: false,
},
},
emits: ['toggledPaused'],
data() {
return {
updating: false,
......@@ -83,6 +84,7 @@ export default {
if (errors && errors.length) {
throw new Error(errors.join(' '));
}
this.$emit('toggledPaused');
} catch (e) {
this.onError(e);
} finally {
......
<script>
import { GlBadge, GlLink } from '@gitlab/ui';
import { createAlert } from '~/flash';
import { fetchPolicies } from '~/lib/graphql';
import { updateHistory } from '~/lib/utils/url_utility';
import { formatNumber } from '~/locale';
import { fetchPolicies } from '~/lib/graphql';
import RegistrationDropdown from '../components/registration/registration_dropdown.vue';
import RunnerFilteredSearchBar from '../components/runner_filtered_search_bar.vue';
......@@ -35,7 +35,7 @@ import { captureException } from '../sentry_utils';
const runnersCountSmartQuery = {
query: groupRunnersCountQuery,
fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
fetchPolicy: fetchPolicies.NETWORK_ONLY,
update(data) {
return data?.group?.runners?.count;
},
......@@ -85,10 +85,7 @@ export default {
apollo: {
runners: {
query: groupRunnersQuery,
// Runners can be updated by users directly in this list.
// A "cache and network" policy prevents outdated filtered
// results.
fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
fetchPolicy: fetchPolicies.NETWORK_ONLY,
variables() {
return this.variables;
},
......@@ -241,9 +238,18 @@ export default {
editUrl(runner) {
return this.runners.urlsById[runner.id]?.edit;
},
refetchFilteredCounts() {
this.$apollo.queries.allRunnersCount.refetch();
this.$apollo.queries.groupRunnersCount.refetch();
this.$apollo.queries.projectRunnersCount.refetch();
},
onToggledPaused() {
// When a runner is Paused, the tab count can
// become stale, refetch outdated counts.
this.refetchFilteredCounts();
},
onDeleted({ message }) {
this.$root.$toast?.show(message);
this.$apollo.queries.runners.refetch();
},
reportToSentry(error) {
captureException({ error, component: this.$options.name });
......@@ -302,7 +308,12 @@ export default {
</gl-link>
</template>
<template #runner-actions-cell="{ runner }">
<runner-actions-cell :runner="runner" :edit-url="editUrl(runner)" @deleted="onDeleted" />
<runner-actions-cell
:runner="runner"
:edit-url="editUrl(runner)"
@toggledPaused="onToggledPaused"
@deleted="onDeleted"
/>
</template>
</runner-list>
<runner-pagination
......
......@@ -235,9 +235,11 @@ describe('AdminRunnersApp', () => {
const mockRunner = runnersData.data.runners.nodes[0];
const { id: graphqlId, shortSha } = mockRunner;
const id = getIdFromGraphQLId(graphqlId);
const COUNT_QUERIES = 7; // Smart queries that display a filtered count of runners
const FILTERED_COUNT_QUERIES = 4; // Smart queries that display a count of runners in tabs
beforeEach(async () => {
mockRunnersQuery.mockClear();
mockRunnersCountQuery.mockClear();
createComponent({ mountFn: mountExtended });
showToast = jest.spyOn(wrapper.vm.$root.$toast, 'show');
......@@ -252,12 +254,18 @@ describe('AdminRunnersApp', () => {
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);
it('When runner is paused or unpaused, some data is refetched', async () => {
expect(mockRunnersCountQuery).toHaveBeenCalledTimes(COUNT_QUERIES);
findRunnerActionsCell().vm.$emit('deleted', { message: 'Runner deleted' });
findRunnerActionsCell().vm.$emit('toggledPaused');
expect(mockRunnersQuery).toHaveBeenCalledTimes(2);
expect(mockRunnersCountQuery).toHaveBeenCalledTimes(COUNT_QUERIES + FILTERED_COUNT_QUERIES);
expect(showToast).toHaveBeenCalledTimes(0);
});
it('When runner is deleted, data is refetched and a toast message is shown', async () => {
findRunnerActionsCell().vm.$emit('deleted', { message: 'Runner deleted' });
expect(showToast).toHaveBeenCalledTimes(1);
expect(showToast).toHaveBeenCalledWith('Runner deleted');
......
......@@ -100,6 +100,16 @@ describe('RunnerActionsCell', () => {
expect(findDeleteBtn().props('runner')).toEqual(mockRunner);
});
it('Emits toggledPaused events', () => {
createComponent();
expect(wrapper.emitted('toggledPaused')).toBe(undefined);
findRunnerPauseBtn().vm.$emit('toggledPaused');
expect(wrapper.emitted('toggledPaused')).toHaveLength(1);
});
it('Emits delete events', () => {
const value = { name: 'Runner' };
......
......@@ -146,6 +146,10 @@ describe('RunnerPauseButton', () => {
it('The button does not have a loading state', () => {
expect(findBtn().props('loading')).toBe(false);
});
it('The button emits toggledPaused', () => {
expect(wrapper.emitted('toggledPaused')).toHaveLength(1);
});
});
describe('When update fails', () => {
......
......@@ -193,9 +193,11 @@ describe('GroupRunnersApp', () => {
const { webUrl, editUrl, node } = mockGroupRunnersEdges[0];
const { id: graphqlId, shortSha } = node;
const id = getIdFromGraphQLId(graphqlId);
const COUNT_QUERIES = 6; // Smart queries that display a filtered count of runners
const FILTERED_COUNT_QUERIES = 3; // Smart queries that display a count of runners in tabs
beforeEach(async () => {
mockGroupRunnersQuery.mockClear();
mockGroupRunnersCountQuery.mockClear();
createComponent({ mountFn: mountExtended });
showToast = jest.spyOn(wrapper.vm.$root.$toast, 'show');
......@@ -219,12 +221,20 @@ describe('GroupRunnersApp', () => {
});
});
it('When runner is deleted, data is refetched and a toast is shown', async () => {
expect(mockGroupRunnersQuery).toHaveBeenCalledTimes(1);
it('When runner is paused or unpaused, some data is refetched', async () => {
expect(mockGroupRunnersCountQuery).toHaveBeenCalledTimes(COUNT_QUERIES);
findRunnerActionsCell().vm.$emit('deleted', { message: 'Runner deleted' });
findRunnerActionsCell().vm.$emit('toggledPaused');
expect(mockGroupRunnersCountQuery).toHaveBeenCalledTimes(
COUNT_QUERIES + FILTERED_COUNT_QUERIES,
);
expect(mockGroupRunnersQuery).toHaveBeenCalledTimes(2);
expect(showToast).toHaveBeenCalledTimes(0);
});
it('When runner is deleted, data is refetched and a toast message is shown', async () => {
findRunnerActionsCell().vm.$emit('deleted', { message: 'Runner deleted' });
expect(showToast).toHaveBeenCalledTimes(1);
expect(showToast).toHaveBeenCalledWith('Runner deleted');
......
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