Commit afbbe302 authored by Simon Knox's avatar Simon Knox

Merge branch '342799-stale-status-runners-ui' into 'master'

Add stale runners filters and badge

See merge request gitlab-org/gitlab!75244
parents 58d963e3 82e179e8
<script> <script>
import { GlTooltipDirective } from '@gitlab/ui'; import { GlTooltipDirective } from '@gitlab/ui';
import RunnerContactedStateBadge from '../runner_contacted_state_badge.vue'; import RunnerStatusBadge from '../runner_status_badge.vue';
import RunnerPausedBadge from '../runner_paused_badge.vue'; import RunnerPausedBadge from '../runner_paused_badge.vue';
import { I18N_LOCKED_RUNNER_DESCRIPTION, I18N_PAUSED_RUNNER_DESCRIPTION } from '../../constants';
export default { export default {
components: { components: {
RunnerContactedStateBadge, RunnerStatusBadge,
RunnerPausedBadge, RunnerPausedBadge,
}, },
directives: { directives: {
...@@ -25,16 +23,12 @@ export default { ...@@ -25,16 +23,12 @@ export default {
return !this.runner.active; return !this.runner.active;
}, },
}, },
i18n: {
I18N_LOCKED_RUNNER_DESCRIPTION,
I18N_PAUSED_RUNNER_DESCRIPTION,
},
}; };
</script> </script>
<template> <template>
<div> <div>
<runner-contacted-state-badge :runner="runner" size="sm" /> <runner-status-badge :runner="runner" size="sm" />
<runner-paused-badge v-if="paused" size="sm" /> <runner-paused-badge v-if="paused" size="sm" />
</div> </div>
</template> </template>
<script> <script>
import { GlBadge, GlTooltipDirective } from '@gitlab/ui'; import { GlBadge, GlTooltipDirective } from '@gitlab/ui';
import { s__, sprintf } from '~/locale'; import { __, s__, sprintf } from '~/locale';
import { getTimeago } from '~/lib/utils/datetime_utility'; import { getTimeago } from '~/lib/utils/datetime_utility';
import { import {
I18N_ONLINE_RUNNER_DESCRIPTION, I18N_ONLINE_RUNNER_TIMEAGO_DESCRIPTION,
I18N_OFFLINE_RUNNER_DESCRIPTION,
I18N_NOT_CONNECTED_RUNNER_DESCRIPTION, I18N_NOT_CONNECTED_RUNNER_DESCRIPTION,
I18N_OFFLINE_RUNNER_TIMEAGO_DESCRIPTION,
I18N_STALE_RUNNER_DESCRIPTION,
STATUS_ONLINE, STATUS_ONLINE,
STATUS_OFFLINE,
STATUS_NOT_CONNECTED, STATUS_NOT_CONNECTED,
STATUS_OFFLINE,
STATUS_STALE,
} from '../constants'; } from '../constants';
export default { export default {
...@@ -29,31 +31,38 @@ export default { ...@@ -29,31 +31,38 @@ export default {
if (this.runner.contactedAt) { if (this.runner.contactedAt) {
return getTimeago().format(this.runner.contactedAt); return getTimeago().format(this.runner.contactedAt);
} }
return null; // Prevent "just now" from being rendered, in case data is missing.
return __('n/a');
}, },
badge() { badge() {
switch (this.runner.status) { switch (this.runner?.status) {
case STATUS_ONLINE: case STATUS_ONLINE:
return { return {
variant: 'success', variant: 'success',
label: s__('Runners|online'), label: s__('Runners|online'),
tooltip: sprintf(I18N_ONLINE_RUNNER_DESCRIPTION, { tooltip: sprintf(I18N_ONLINE_RUNNER_TIMEAGO_DESCRIPTION, {
timeAgo: this.contactedAtTimeAgo, timeAgo: this.contactedAtTimeAgo,
}), }),
}; };
case STATUS_NOT_CONNECTED:
return {
variant: 'muted',
label: s__('Runners|not connected'),
tooltip: I18N_NOT_CONNECTED_RUNNER_DESCRIPTION,
};
case STATUS_OFFLINE: case STATUS_OFFLINE:
return { return {
variant: 'muted', variant: 'muted',
label: s__('Runners|offline'), label: s__('Runners|offline'),
tooltip: sprintf(I18N_OFFLINE_RUNNER_DESCRIPTION, { tooltip: sprintf(I18N_OFFLINE_RUNNER_TIMEAGO_DESCRIPTION, {
timeAgo: this.contactedAtTimeAgo, timeAgo: this.contactedAtTimeAgo,
}), }),
}; };
case STATUS_NOT_CONNECTED: case STATUS_STALE:
return { return {
variant: 'muted', variant: 'warning',
label: s__('Runners|not connected'), label: s__('Runners|stale'),
tooltip: I18N_NOT_CONNECTED_RUNNER_DESCRIPTION, tooltip: I18N_STALE_RUNNER_DESCRIPTION,
}; };
default: default:
return null; return null;
......
...@@ -7,6 +7,7 @@ import { ...@@ -7,6 +7,7 @@ import {
STATUS_ONLINE, STATUS_ONLINE,
STATUS_OFFLINE, STATUS_OFFLINE,
STATUS_NOT_CONNECTED, STATUS_NOT_CONNECTED,
STATUS_STALE,
PARAM_KEY_STATUS, PARAM_KEY_STATUS,
} from '../../constants'; } from '../../constants';
...@@ -16,6 +17,7 @@ const options = [ ...@@ -16,6 +17,7 @@ const options = [
{ value: STATUS_ONLINE, title: s__('Runners|Online') }, { value: STATUS_ONLINE, title: s__('Runners|Online') },
{ value: STATUS_OFFLINE, title: s__('Runners|Offline') }, { value: STATUS_OFFLINE, title: s__('Runners|Offline') },
{ value: STATUS_NOT_CONNECTED, title: s__('Runners|Not connected') }, { value: STATUS_NOT_CONNECTED, title: s__('Runners|Not connected') },
{ value: STATUS_STALE, title: s__('Runners|Stale') },
]; ];
export const statusTokenConfig = { export const statusTokenConfig = {
......
...@@ -14,15 +14,18 @@ export const I18N_GROUP_RUNNER_DESCRIPTION = s__( ...@@ -14,15 +14,18 @@ export const I18N_GROUP_RUNNER_DESCRIPTION = s__(
export const I18N_PROJECT_RUNNER_DESCRIPTION = s__('Runners|Associated with one or more projects'); export const I18N_PROJECT_RUNNER_DESCRIPTION = s__('Runners|Associated with one or more projects');
// Status // Status
export const I18N_ONLINE_RUNNER_DESCRIPTION = s__( export const I18N_ONLINE_RUNNER_TIMEAGO_DESCRIPTION = s__(
'Runners|Runner is online; last contact was %{timeAgo}', 'Runners|Runner is online; last contact was %{timeAgo}',
); );
export const I18N_OFFLINE_RUNNER_DESCRIPTION = s__(
'Runners|No recent contact from this runner; last contact was %{timeAgo}',
);
export const I18N_NOT_CONNECTED_RUNNER_DESCRIPTION = s__( export const I18N_NOT_CONNECTED_RUNNER_DESCRIPTION = s__(
'Runners|This runner has never connected to this instance', 'Runners|This runner has never connected to this instance',
); );
export const I18N_OFFLINE_RUNNER_TIMEAGO_DESCRIPTION = s__(
'Runners|No recent contact from this runner; last contact was %{timeAgo}',
);
export const I18N_STALE_RUNNER_DESCRIPTION = s__(
'Runners|No contact from this runner in over 3 months',
);
export const I18N_LOCKED_RUNNER_DESCRIPTION = s__('Runners|You cannot assign to other projects'); 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'); export const I18N_PAUSED_RUNNER_DESCRIPTION = s__('Runners|Not available to run jobs');
...@@ -54,9 +57,11 @@ export const PROJECT_TYPE = 'PROJECT_TYPE'; ...@@ -54,9 +57,11 @@ export const PROJECT_TYPE = 'PROJECT_TYPE';
export const STATUS_ACTIVE = 'ACTIVE'; export const STATUS_ACTIVE = 'ACTIVE';
export const STATUS_PAUSED = 'PAUSED'; export const STATUS_PAUSED = 'PAUSED';
export const STATUS_ONLINE = 'ONLINE'; export const STATUS_ONLINE = 'ONLINE';
export const STATUS_OFFLINE = 'OFFLINE';
export const STATUS_NOT_CONNECTED = 'NOT_CONNECTED'; export const STATUS_NOT_CONNECTED = 'NOT_CONNECTED';
export const STATUS_OFFLINE = 'OFFLINE';
export const STATUS_STALE = 'STALE';
// CiRunnerAccessLevel // CiRunnerAccessLevel
......
...@@ -10,5 +10,5 @@ fragment RunnerNode on CiRunner { ...@@ -10,5 +10,5 @@ fragment RunnerNode on CiRunner {
locked locked
tagList tagList
contactedAt contactedAt
status status(legacyMode: null)
} }
...@@ -30139,6 +30139,9 @@ msgstr "" ...@@ -30139,6 +30139,9 @@ msgstr ""
msgid "Runners|New runner, has not connected yet" msgid "Runners|New runner, has not connected yet"
msgstr "" msgstr ""
msgid "Runners|No contact from this runner in over 3 months"
msgstr ""
msgid "Runners|No recent contact from this runner; last contact was %{timeAgo}" msgid "Runners|No recent contact from this runner; last contact was %{timeAgo}"
msgstr "" msgstr ""
...@@ -30244,6 +30247,9 @@ msgstr "" ...@@ -30244,6 +30247,9 @@ msgstr ""
msgid "Runners|Something went wrong while fetching the tags suggestions" msgid "Runners|Something went wrong while fetching the tags suggestions"
msgstr "" msgstr ""
msgid "Runners|Stale"
msgstr ""
msgid "Runners|Status" msgid "Runners|Status"
msgstr "" msgstr ""
...@@ -30340,6 +30346,9 @@ msgstr "" ...@@ -30340,6 +30346,9 @@ msgstr ""
msgid "Runners|specific" msgid "Runners|specific"
msgstr "" msgstr ""
msgid "Runners|stale"
msgstr ""
msgid "Running" msgid "Running"
msgstr "" msgstr ""
......
import { GlBadge } from '@gitlab/ui'; import { GlBadge } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import RunnerContactedStateBadge from '~/runner/components/runner_contacted_state_badge.vue'; import RunnerStatusBadge from '~/runner/components/runner_status_badge.vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { STATUS_ONLINE, STATUS_OFFLINE, STATUS_NOT_CONNECTED } from '~/runner/constants'; import {
STATUS_ONLINE,
STATUS_OFFLINE,
STATUS_STALE,
STATUS_NOT_CONNECTED,
} from '~/runner/constants';
describe('RunnerTypeBadge', () => { describe('RunnerTypeBadge', () => {
let wrapper; let wrapper;
...@@ -10,14 +15,14 @@ describe('RunnerTypeBadge', () => { ...@@ -10,14 +15,14 @@ describe('RunnerTypeBadge', () => {
const findBadge = () => wrapper.findComponent(GlBadge); const findBadge = () => wrapper.findComponent(GlBadge);
const getTooltip = () => getBinding(findBadge().element, 'gl-tooltip'); const getTooltip = () => getBinding(findBadge().element, 'gl-tooltip');
const createComponent = ({ runner = {} } = {}) => { const createComponent = (props = {}) => {
wrapper = shallowMount(RunnerContactedStateBadge, { wrapper = shallowMount(RunnerStatusBadge, {
propsData: { propsData: {
runner: { runner: {
contactedAt: '2021-01-01T00:00:00Z', contactedAt: '2020-12-31T23:59:00Z',
status: STATUS_ONLINE, status: STATUS_ONLINE,
...runner,
}, },
...props,
}, },
directives: { directives: {
GlTooltip: createMockDirective(), GlTooltip: createMockDirective(),
...@@ -27,6 +32,7 @@ describe('RunnerTypeBadge', () => { ...@@ -27,6 +32,7 @@ describe('RunnerTypeBadge', () => {
beforeEach(() => { beforeEach(() => {
jest.useFakeTimers('modern'); jest.useFakeTimers('modern');
jest.setSystemTime(new Date('2021-01-01T00:00:00Z'));
}); });
afterEach(() => { afterEach(() => {
...@@ -36,8 +42,6 @@ describe('RunnerTypeBadge', () => { ...@@ -36,8 +42,6 @@ describe('RunnerTypeBadge', () => {
}); });
it('renders online state', () => { it('renders online state', () => {
jest.setSystemTime(new Date('2021-01-01T00:01:00Z'));
createComponent(); createComponent();
expect(wrapper.text()).toBe('online'); expect(wrapper.text()).toBe('online');
...@@ -45,11 +49,23 @@ describe('RunnerTypeBadge', () => { ...@@ -45,11 +49,23 @@ describe('RunnerTypeBadge', () => {
expect(getTooltip().value).toBe('Runner is online; last contact was 1 minute ago'); expect(getTooltip().value).toBe('Runner is online; last contact was 1 minute ago');
}); });
it('renders offline state', () => { it('renders not connected state', () => {
jest.setSystemTime(new Date('2021-01-02T00:00:00Z')); createComponent({
runner: {
contactedAt: null,
status: STATUS_NOT_CONNECTED,
},
});
expect(wrapper.text()).toBe('not connected');
expect(findBadge().props('variant')).toBe('muted');
expect(getTooltip().value).toMatch('This runner has never connected');
});
it('renders offline state', () => {
createComponent({ createComponent({
runner: { runner: {
contactedAt: '2020-12-31T00:00:00Z',
status: STATUS_OFFLINE, status: STATUS_OFFLINE,
}, },
}); });
...@@ -61,20 +77,33 @@ describe('RunnerTypeBadge', () => { ...@@ -61,20 +77,33 @@ describe('RunnerTypeBadge', () => {
); );
}); });
it('renders not connected state', () => { it('renders stale state', () => {
createComponent({
runner: {
contactedAt: '2020-01-01T00:00:00Z',
status: STATUS_STALE,
},
});
expect(wrapper.text()).toBe('stale');
expect(findBadge().props('variant')).toBe('warning');
expect(getTooltip().value).toBe('No contact from this runner in over 3 months');
});
describe('does not fail when data is missing', () => {
it('contacted_at is missing', () => {
createComponent({ createComponent({
runner: { runner: {
contactedAt: null, contactedAt: null,
status: STATUS_NOT_CONNECTED, status: STATUS_ONLINE,
}, },
}); });
expect(wrapper.text()).toBe('not connected'); expect(wrapper.text()).toBe('online');
expect(findBadge().props('variant')).toBe('muted'); expect(getTooltip().value).toBe('Runner is online; last contact was n/a');
expect(getTooltip().value).toMatch('This runner has never connected');
}); });
it('does not fail when data is missing', () => { it('status is missing', () => {
createComponent({ createComponent({
runner: { runner: {
status: null, status: null,
...@@ -83,4 +112,5 @@ describe('RunnerTypeBadge', () => { ...@@ -83,4 +112,5 @@ describe('RunnerTypeBadge', () => {
expect(wrapper.text()).toBe(''); expect(wrapper.text()).toBe('');
}); });
});
}); });
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