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