Commit 6ab9eac8 authored by Miguel Rincon's avatar Miguel Rincon

Adds status popover in runners table header

Add a status description help popover in the runners list that provides
a summary of each of the statuses a runner can have.

Changelog: changed
parent cb4f76e0
......@@ -26,7 +26,12 @@ export const initAdminRunners = (selector = '#js-admin-runners') => {
return null;
}
const { runnerInstallHelpPage, registrationToken } = el.dataset;
const {
runnerInstallHelpPage,
registrationToken,
onlineContactTimeoutSecs,
staleTimeoutSecs,
} = el.dataset;
const { cacheConfig, typeDefs, localMutations } = createLocalState();
......@@ -40,6 +45,8 @@ export const initAdminRunners = (selector = '#js-admin-runners') => {
provide: {
runnerInstallHelpPage,
localMutations,
onlineContactTimeoutSecs,
staleTimeoutSecs,
},
render(h) {
return h(AdminRunnersApp, {
......
......@@ -7,6 +7,7 @@ import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import checkedRunnerIdsQuery from '../graphql/list/checked_runner_ids.query.graphql';
import { formatJobCount, tableField } from '../utils';
import RunnerSummaryCell from './cells/runner_summary_cell.vue';
import RunnerStatusPopover from './runner_status_popover.vue';
import RunnerStatusCell from './cells/runner_status_cell.vue';
import RunnerTags from './runner_tags.vue';
......@@ -26,6 +27,7 @@ export default {
GlSkeletonLoader,
TooltipOnTruncate,
TimeAgo,
RunnerStatusPopover,
RunnerSummaryCell,
RunnerTags,
RunnerStatusCell,
......@@ -136,6 +138,11 @@ export default {
/>
</template>
<template #head(status)="{ label }">
{{ label }}
<runner-status-popover />
</template>
<template #cell(status)="{ item }">
<runner-status-cell :runner="item" />
</template>
......
<script>
import { GlSprintf } from '@gitlab/ui';
import { duration } from '~/lib/utils/datetime/timeago_utility';
import HelpPopover from '~/vue_shared/components/help_popover.vue';
import {
I18N_STATUS_POPOVER_TITLE,
I18N_STATUS_POPOVER_NEVER_CONTACTED,
I18N_STATUS_POPOVER_NEVER_CONTACTED_DESCRIPTION,
I18N_STATUS_POPOVER_ONLINE,
I18N_STATUS_POPOVER_ONLINE_DESCRIPTION,
I18N_STATUS_POPOVER_OFFLINE,
I18N_STATUS_POPOVER_OFFLINE_DESCRIPTION,
I18N_STATUS_POPOVER_STALE,
I18N_STATUS_POPOVER_STALE_DESCRIPTION,
} from '~/runner/constants';
export default {
name: 'RunnerStatusPopover',
components: {
GlSprintf,
HelpPopover,
},
inject: ['onlineContactTimeoutSecs', 'staleTimeoutSecs'],
computed: {
onlineContactTimeoutDuration() {
return duration(this.onlineContactTimeoutSecs * 1000);
},
staleTimeoutDuration() {
return duration(this.staleTimeoutSecs * 1000);
},
},
I18N_STATUS_POPOVER_TITLE,
I18N_STATUS_POPOVER_NEVER_CONTACTED,
I18N_STATUS_POPOVER_NEVER_CONTACTED_DESCRIPTION,
I18N_STATUS_POPOVER_ONLINE,
I18N_STATUS_POPOVER_ONLINE_DESCRIPTION,
I18N_STATUS_POPOVER_OFFLINE,
I18N_STATUS_POPOVER_OFFLINE_DESCRIPTION,
I18N_STATUS_POPOVER_STALE,
I18N_STATUS_POPOVER_STALE_DESCRIPTION,
};
</script>
<template>
<help-popover>
<template #title>{{ $options.I18N_STATUS_POPOVER_TITLE }}</template>
<p class="gl-mb-0">
<strong>{{ $options.I18N_STATUS_POPOVER_NEVER_CONTACTED }}</strong>
<gl-sprintf :message="$options.I18N_STATUS_POPOVER_NEVER_CONTACTED_DESCRIPTION">
<template #code="{ content }">
<code>{{ content }}</code>
</template>
</gl-sprintf>
</p>
<p class="gl-mb-0">
<strong>{{ $options.I18N_STATUS_POPOVER_ONLINE }}</strong>
<gl-sprintf :message="$options.I18N_STATUS_POPOVER_ONLINE_DESCRIPTION">
<template #elapsedTime>{{ onlineContactTimeoutDuration }}</template>
</gl-sprintf>
</p>
<p class="gl-mb-0">
<strong>{{ $options.I18N_STATUS_POPOVER_OFFLINE }}</strong>
<gl-sprintf :message="$options.I18N_STATUS_POPOVER_OFFLINE_DESCRIPTION">
<template #elapsedTime>{{ onlineContactTimeoutDuration }}</template>
</gl-sprintf>
</p>
<p class="gl-mb-0">
<strong>{{ $options.I18N_STATUS_POPOVER_STALE }}</strong>
<gl-sprintf :message="$options.I18N_STATUS_POPOVER_STALE_DESCRIPTION">
<template #elapsedTime>{{ staleTimeoutDuration }}</template>
</gl-sprintf>
</p>
</help-popover>
</template>
......@@ -21,6 +21,26 @@ export const I18N_GROUP_RUNNER_DESCRIPTION = s__(
);
export const I18N_PROJECT_RUNNER_DESCRIPTION = s__('Runners|Associated with one or more projects');
// Status help popover
export const I18N_STATUS_POPOVER_TITLE = s__('Runners|Runner statuses');
export const I18N_STATUS_POPOVER_NEVER_CONTACTED = s__('Runners|Never contacted:');
export const I18N_STATUS_POPOVER_NEVER_CONTACTED_DESCRIPTION = s__(
'Runners|Runner has never contacted GitLab (when you register a runner, use %{codeStart}gitlab-runner run%{codeEnd} to bring it online)',
);
export const I18N_STATUS_POPOVER_ONLINE = s__('Runners|Online:');
export const I18N_STATUS_POPOVER_ONLINE_DESCRIPTION = s__(
'Runners|Runner has contacted GitLab within the last %{elapsedTime}',
);
export const I18N_STATUS_POPOVER_OFFLINE = s__('Runners|Offline:');
export const I18N_STATUS_POPOVER_OFFLINE_DESCRIPTION = s__(
'Runners|Runner has not contacted GitLab in more than %{elapsedTime}',
);
export const I18N_STATUS_POPOVER_STALE = s__('Runners|Stale:');
export const I18N_STATUS_POPOVER_STALE_DESCRIPTION = s__(
'Runners|Runner has not contacted GitLab in more than %{elapsedTime}',
);
// Status tooltips
export const I18N_ONLINE_TIMEAGO_TOOLTIP = s__(
'Runners|Runner is online; last contact was %{timeAgo}',
......@@ -63,7 +83,7 @@ export const I18N_LOCKED_RUNNER_DESCRIPTION = s__(
export const I18N_ASSIGNED_PROJECTS = s__('Runners|Assigned Projects (%{projectCount})');
export const I18N_NONE = __('None');
export const I18N_NO_JOBS_FOUND = s__('Runner|This runner has not run any jobs.');
export const I18N_NO_JOBS_FOUND = s__('Runners|This runner has not run any jobs.');
// Styles
......
......@@ -20,6 +20,8 @@ export const initGroupRunners = (selector = '#js-group-runners') => {
groupId,
groupFullPath,
groupRunnersLimitedCount,
onlineContactTimeoutSecs,
staleTimeoutSecs,
} = el.dataset;
const apolloProvider = new VueApollo({
......@@ -32,6 +34,8 @@ export const initGroupRunners = (selector = '#js-group-runners') => {
provide: {
runnerInstallHelpPage,
groupId,
onlineContactTimeoutSecs: parseInt(onlineContactTimeoutSecs, 10),
staleTimeoutSecs: parseInt(staleTimeoutSecs, 10),
},
render(h) {
return h(GroupRunnersApp, {
......
......@@ -63,7 +63,9 @@ module Ci
# Runner install help page is external, located at
# https://gitlab.com/gitlab-org/gitlab-runner
runner_install_help_page: 'https://docs.gitlab.com/runner/install/',
registration_token: Gitlab::CurrentSettings.runners_registration_token
registration_token: Gitlab::CurrentSettings.runners_registration_token,
online_contact_timeout_secs: ::Ci::Runner::ONLINE_CONTACT_TIMEOUT.to_i,
stale_timeout_secs: ::Ci::Runner::STALE_TIMEOUT.to_i
}
end
......@@ -83,7 +85,9 @@ module Ci
registration_token: group.runners_token,
group_id: group.id,
group_full_path: group.full_path,
runner_install_help_page: 'https://docs.gitlab.com/runner/install/'
runner_install_help_page: 'https://docs.gitlab.com/runner/install/',
online_contact_timeout_secs: ::Ci::Runner::ONLINE_CONTACT_TIMEOUT.to_i,
stale_timeout_secs: ::Ci::Runner::STALE_TIMEOUT.to_i
}
end
......
......@@ -32367,6 +32367,9 @@ msgstr ""
msgid "Runners|Never contacted"
msgstr ""
msgid "Runners|Never contacted:"
msgstr ""
msgid "Runners|New group runners view"
msgstr ""
......@@ -32388,12 +32391,18 @@ msgstr ""
msgid "Runners|Offline runners"
msgstr ""
msgid "Runners|Offline:"
msgstr ""
msgid "Runners|Online"
msgstr ""
msgid "Runners|Online runners"
msgstr ""
msgid "Runners|Online:"
msgstr ""
msgid "Runners|Pause from accepting jobs"
msgstr ""
......@@ -32462,9 +32471,18 @@ msgstr ""
msgid "Runners|Runner cannot be deleted, please contact your administrator"
msgstr ""
msgid "Runners|Runner has contacted GitLab within the last %{elapsedTime}"
msgstr ""
msgid "Runners|Runner has never contacted GitLab (when you register a runner, use %{codeStart}gitlab-runner run%{codeEnd} to bring it online)"
msgstr ""
msgid "Runners|Runner has never contacted this instance"
msgstr ""
msgid "Runners|Runner has not contacted GitLab in more than %{elapsedTime}"
msgstr ""
msgid "Runners|Runner is locked and available for currently assigned projects only. Only administrators can change the assigned projects."
msgstr ""
......@@ -32492,6 +32510,9 @@ msgstr ""
msgid "Runners|Runner registration"
msgstr ""
msgid "Runners|Runner statuses"
msgstr ""
msgid "Runners|Runner unassigned from project."
msgstr ""
......@@ -32522,6 +32543,9 @@ msgstr ""
msgid "Runners|Stale runners"
msgstr ""
msgid "Runners|Stale:"
msgstr ""
msgid "Runners|Status"
msgstr ""
......@@ -32540,6 +32564,9 @@ msgstr ""
msgid "Runners|The runner will be permanently deleted and no longer available for projects or groups in the instance. Are you sure you want to continue?"
msgstr ""
msgid "Runners|This runner has not run any jobs."
msgstr ""
msgid "Runners|This runner is associated with specific projects."
msgstr ""
......@@ -32618,9 +32645,6 @@ msgstr ""
msgid "Runners|stale"
msgstr ""
msgid "Runner|This runner has not run any jobs."
msgstr ""
msgid "Running"
msgstr ""
......
......@@ -41,7 +41,13 @@ import adminRunnersCountQuery from '~/runner/graphql/list/admin_runners_count.qu
import { captureException } from '~/runner/sentry_utils';
import FilteredSearch from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
import { runnersData, runnersCountData, runnersDataPaginated } from '../mock_data';
import {
runnersData,
runnersCountData,
runnersDataPaginated,
onlineContactTimeoutSecs,
staleTimeoutSecs,
} from '../mock_data';
const mockRegistrationToken = 'MOCK_REGISTRATION_TOKEN';
const mockRunners = runnersData.data.runners.nodes;
......@@ -94,6 +100,8 @@ describe('AdminRunnersApp', () => {
},
provide: {
localMutations,
onlineContactTimeoutSecs,
staleTimeoutSecs,
...provide,
},
...options,
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`RunnerStatusPopover renders complete text 1`] = `"Never contacted: Runner has never contacted GitLab (when you register a runner, use gitlab-runner run to bring it online) Online: Runner has contacted GitLab within the last 2 hours Offline: Runner has not contacted GitLab in more than 2 hours Stale: Runner has not contacted GitLab in more than 2 months"`;
......@@ -6,7 +6,8 @@ import {
} from 'helpers/vue_test_utils_helper';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import RunnerList from '~/runner/components/runner_list.vue';
import { runnersData } from '../mock_data';
import RunnerStatusPopover from '~/runner/components/runner_status_popover.vue';
import { runnersData, onlineContactTimeoutSecs, staleTimeoutSecs } from '../mock_data';
const mockRunners = runnersData.data.runners.nodes;
const mockActiveRunnersCount = mockRunners.length;
......@@ -28,21 +29,34 @@ describe('RunnerList', () => {
activeRunnersCount: mockActiveRunnersCount,
...props,
},
provide: {
onlineContactTimeoutSecs,
staleTimeoutSecs,
},
...options,
});
};
beforeEach(() => {
createComponent({}, mountExtended);
});
afterEach(() => {
wrapper.destroy();
});
it('Displays headers', () => {
createComponent(
{
stubs: {
RunnerStatusPopover: {
template: '<div/>',
},
},
},
mountExtended,
);
const headerLabels = findHeaders().wrappers.map((w) => w.text());
expect(findHeaders().at(0).findComponent(RunnerStatusPopover).exists()).toBe(true);
expect(headerLabels).toEqual([
'Status',
'Runner',
......@@ -61,6 +75,8 @@ describe('RunnerList', () => {
});
it('Displays a list of runners', () => {
createComponent({}, mountExtended);
expect(findRows()).toHaveLength(4);
expect(findSkeletonLoader().exists()).toBe(false);
......@@ -69,6 +85,8 @@ describe('RunnerList', () => {
it('Displays details of a runner', () => {
const { id, description, version, shortSha } = mockRunners[0];
createComponent({}, mountExtended);
// Badges
expect(findCell({ fieldKey: 'status' }).text()).toMatchInterpolatedText(
'never contacted paused',
......@@ -183,6 +201,8 @@ describe('RunnerList', () => {
const { id, shortSha } = mockRunners[0];
const numericId = getIdFromGraphQLId(id);
createComponent({}, mountExtended);
expect(findCell({ fieldKey: 'summary' }).text()).toContain(`#${numericId} (${shortSha})`);
});
......
import { GlSprintf } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import RunnerStatusPopover from '~/runner/components/runner_status_popover.vue';
import HelpPopover from '~/vue_shared/components/help_popover.vue';
import { onlineContactTimeoutSecs, staleTimeoutSecs } from '../mock_data';
describe('RunnerStatusPopover', () => {
let wrapper;
const createComponent = ({ provide = {} } = {}) => {
wrapper = shallowMountExtended(RunnerStatusPopover, {
provide: {
onlineContactTimeoutSecs,
staleTimeoutSecs,
...provide,
},
stubs: {
GlSprintf,
},
});
};
const findHelpPopover = () => wrapper.findComponent(HelpPopover);
it('renders popoover', () => {
createComponent();
expect(findHelpPopover().exists()).toBe(true);
});
it('renders complete text', () => {
createComponent();
expect(findHelpPopover().text()).toMatchSnapshot();
});
});
......@@ -38,7 +38,13 @@ import getGroupRunnersCountQuery from '~/runner/graphql/list/group_runners_count
import GroupRunnersApp from '~/runner/group_runners/group_runners_app.vue';
import { captureException } from '~/runner/sentry_utils';
import FilteredSearch from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
import { groupRunnersData, groupRunnersDataPaginated, groupRunnersCountData } from '../mock_data';
import {
groupRunnersData,
groupRunnersDataPaginated,
groupRunnersCountData,
onlineContactTimeoutSecs,
staleTimeoutSecs,
} from '../mock_data';
Vue.use(VueApollo);
Vue.use(GlToast);
......@@ -90,6 +96,10 @@ describe('GroupRunnersApp', () => {
groupRunnersLimitedCount: mockGroupRunnersLimitedCount,
...props,
},
provide: {
onlineContactTimeoutSecs,
staleTimeoutSecs,
},
});
};
......
......@@ -14,6 +14,10 @@ import runnerWithGroupData from 'test_fixtures/graphql/runner/details/runner.que
import runnerProjectsData from 'test_fixtures/graphql/runner/details/runner_projects.query.graphql.json';
import runnerJobsData from 'test_fixtures/graphql/runner/details/runner_jobs.query.graphql.json';
// Other mock data
export const onlineContactTimeoutSecs = 2 * 60 * 60;
export const staleTimeoutSecs = 5259492; // Ruby's `2.months`
export {
runnersData,
runnersCountData,
......
......@@ -86,7 +86,9 @@ RSpec.describe Ci::RunnersHelper do
it 'returns the data in format' do
expect(helper.admin_runners_data_attributes).to eq({
runner_install_help_page: 'https://docs.gitlab.com/runner/install/',
registration_token: Gitlab::CurrentSettings.runners_registration_token
registration_token: Gitlab::CurrentSettings.runners_registration_token,
online_contact_timeout_secs: 7200,
stale_timeout_secs: 7889238
})
end
end
......@@ -128,12 +130,14 @@ RSpec.describe Ci::RunnersHelper do
let(:group) { create(:group) }
it 'returns group data to render a runner list' do
data = helper.group_runners_data_attributes(group)
expect(data[:registration_token]).to eq(group.runners_token)
expect(data[:group_id]).to eq(group.id)
expect(data[:group_full_path]).to eq(group.full_path)
expect(data[:runner_install_help_page]).to eq('https://docs.gitlab.com/runner/install/')
expect(helper.group_runners_data_attributes(group)).to eq({
registration_token: group.runners_token,
group_id: group.id,
group_full_path: group.full_path,
runner_install_help_page: 'https://docs.gitlab.com/runner/install/',
online_contact_timeout_secs: 7200,
stale_timeout_secs: 7889238
})
end
end
......
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