Commit 02ab624c authored by Miguel Rincon's avatar Miguel Rincon

Update runner status badges and locked icon

This change updates runner list with a status badge for each runner that
indicates if a runner is offline, online and/or paused.

It adds a lock icon for runners that are locked to their projects.

Changelog: changed
parent 2b5e3a6a
...@@ -3,7 +3,7 @@ import { GlButton, GlButtonGroup, GlTooltipDirective } from '@gitlab/ui'; ...@@ -3,7 +3,7 @@ import { GlButton, GlButtonGroup, GlTooltipDirective } from '@gitlab/ui';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { __, s__ } from '~/locale'; import { __, s__ } from '~/locale';
import runnerDeleteMutation from '~/runner/graphql/runner_delete.mutation.graphql'; import runnerDeleteMutation from '~/runner/graphql/runner_delete.mutation.graphql';
import runnerUpdateMutation from '~/runner/graphql/runner_update.mutation.graphql'; import runnerActionsUpdateMutation from '~/runner/graphql/runner_actions_update.mutation.graphql';
import { captureException } from '~/runner/sentry_utils'; import { captureException } from '~/runner/sentry_utils';
const i18n = { const i18n = {
...@@ -71,7 +71,7 @@ export default { ...@@ -71,7 +71,7 @@ export default {
runnerUpdate: { errors }, runnerUpdate: { errors },
}, },
} = await this.$apollo.mutate({ } = await this.$apollo.mutate({
mutation: runnerUpdateMutation, mutation: runnerActionsUpdateMutation,
variables: { variables: {
input: { input: {
id: this.runner.id, id: this.runner.id,
......
<script> <script>
import { GlTooltipDirective } from '@gitlab/ui'; import { GlTooltipDirective } from '@gitlab/ui';
import RunnerTypeBadge from '../runner_type_badge.vue';
import RunnerStateLockedBadge from '../runner_state_locked_badge.vue'; import RunnerContactedStateBadge from '../runner_contacted_state_badge.vue';
import RunnerStatePausedBadge from '../runner_state_paused_badge.vue'; import RunnerPausedBadge from '../runner_paused_badge.vue';
import { I18N_LOCKED_RUNNER_DESCRIPTION, I18N_PAUSED_RUNNER_DESCRIPTION } from '../../constants'; import { I18N_LOCKED_RUNNER_DESCRIPTION, I18N_PAUSED_RUNNER_DESCRIPTION } from '../../constants';
export default { export default {
components: { components: {
RunnerTypeBadge, RunnerContactedStateBadge,
RunnerStateLockedBadge, RunnerPausedBadge,
RunnerStatePausedBadge,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
...@@ -21,12 +21,6 @@ export default { ...@@ -21,12 +21,6 @@ export default {
}, },
}, },
computed: { computed: {
runnerType() {
return this.runner.runnerType;
},
locked() {
return this.runner.locked;
},
paused() { paused() {
return !this.runner.active; return !this.runner.active;
}, },
...@@ -40,8 +34,7 @@ export default { ...@@ -40,8 +34,7 @@ export default {
<template> <template>
<div> <div>
<runner-type-badge :type="runnerType" size="sm" /> <runner-contacted-state-badge :runner="runner" size="sm" />
<runner-state-locked-badge v-if="locked" size="sm" /> <runner-paused-badge v-if="paused" size="sm" />
<runner-state-paused-badge v-if="paused" size="sm" />
</div> </div>
</template> </template>
<script> <script>
import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue'; import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
import RunnerName from '../runner_name.vue'; import RunnerName from '../runner_name.vue';
import RunnerTypeBadge from '../runner_type_badge.vue';
import { I18N_LOCKED_RUNNER_DESCRIPTION } from '../../constants';
export default { export default {
components: { components: {
GlIcon,
TooltipOnTruncate, TooltipOnTruncate,
RunnerName, RunnerName,
RunnerTypeBadge,
},
directives: {
GlTooltip: GlTooltipDirective,
}, },
props: { props: {
runner: { runner: {
...@@ -14,10 +24,19 @@ export default { ...@@ -14,10 +24,19 @@ export default {
}, },
}, },
computed: { computed: {
runnerType() {
return this.runner.runnerType;
},
locked() {
return this.runner.locked;
},
description() { description() {
return this.runner.description; return this.runner.description;
}, },
}, },
i18n: {
I18N_LOCKED_RUNNER_DESCRIPTION,
},
}; };
</script> </script>
...@@ -26,6 +45,14 @@ export default { ...@@ -26,6 +45,14 @@ export default {
<slot :runner="runner" name="runner-name"> <slot :runner="runner" name="runner-name">
<runner-name :runner="runner" /> <runner-name :runner="runner" />
</slot> </slot>
<runner-type-badge :type="runnerType" size="sm" />
<gl-icon
v-if="locked"
v-gl-tooltip
:title="$options.i18n.I18N_LOCKED_RUNNER_DESCRIPTION"
name="lock"
/>
<tooltip-on-truncate class="gl-display-block" :title="description" truncate-target="child"> <tooltip-on-truncate class="gl-display-block" :title="description" truncate-target="child">
<div class="gl-text-truncate"> <div class="gl-text-truncate">
{{ description }} {{ description }}
......
<script>
import { GlBadge, GlTooltipDirective } from '@gitlab/ui';
import { s__, sprintf } from '~/locale';
import { getTimeago } from '~/lib/utils/datetime_utility';
import {
I18N_ONLINE_RUNNER_DESCRIPTION,
I18N_OFFLINE_RUNNER_DESCRIPTION,
I18N_NOT_CONNECTED_RUNNER_DESCRIPTION,
STATUS_ONLINE,
STATUS_OFFLINE,
STATUS_NOT_CONNECTED,
} from '../constants';
export default {
components: {
GlBadge,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
runner: {
required: true,
type: Object,
},
},
computed: {
contactedAtTimeAgo() {
if (this.runner.contactedAt) {
return getTimeago().format(this.runner.contactedAt);
}
return null;
},
badge() {
switch (this.runner.status) {
case STATUS_ONLINE:
return {
variant: 'success',
label: s__('Runners|online'),
tooltip: sprintf(I18N_ONLINE_RUNNER_DESCRIPTION, {
timeAgo: this.contactedAtTimeAgo,
}),
};
case STATUS_OFFLINE:
return {
variant: 'muted',
label: s__('Runners|offline'),
tooltip: sprintf(I18N_OFFLINE_RUNNER_DESCRIPTION, {
timeAgo: this.contactedAtTimeAgo,
}),
};
case STATUS_NOT_CONNECTED:
return {
variant: 'muted',
label: s__('Runners|not connected'),
tooltip: I18N_NOT_CONNECTED_RUNNER_DESCRIPTION,
};
default:
return null;
}
},
},
};
</script>
<template>
<gl-badge v-if="badge" v-gl-tooltip="badge.tooltip" :variant="badge.variant" v-bind="$attrs">
{{ badge.label }}
</gl-badge>
</template>
...@@ -5,7 +5,7 @@ import { __, s__ } from '~/locale'; ...@@ -5,7 +5,7 @@ import { __, s__ } from '~/locale';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import RunnerActionsCell from './cells/runner_actions_cell.vue'; import RunnerActionsCell from './cells/runner_actions_cell.vue';
import RunnerSummaryCell from './cells/runner_summary_cell.vue'; import RunnerSummaryCell from './cells/runner_summary_cell.vue';
import RunnerTypeCell from './cells/runner_type_cell.vue'; import RunnerStatusCell from './cells/runner_status_cell.vue';
import RunnerTags from './runner_tags.vue'; import RunnerTags from './runner_tags.vue';
const tableField = ({ key, label = '', width = 10 }) => { const tableField = ({ key, label = '', width = 10 }) => {
...@@ -36,7 +36,7 @@ export default { ...@@ -36,7 +36,7 @@ export default {
RunnerActionsCell, RunnerActionsCell,
RunnerSummaryCell, RunnerSummaryCell,
RunnerTags, RunnerTags,
RunnerTypeCell, RunnerStatusCell,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
...@@ -63,8 +63,8 @@ export default { ...@@ -63,8 +63,8 @@ export default {
}, },
}, },
fields: [ fields: [
tableField({ key: 'type', label: __('Type/State') }), tableField({ key: 'status', label: s__('Runners|Status') }),
tableField({ key: 'summary', label: s__('Runners|Runner'), width: 30 }), tableField({ key: 'summary', label: s__('Runners|Runner ID'), width: 30 }),
tableField({ key: 'version', label: __('Version') }), tableField({ key: 'version', label: __('Version') }),
tableField({ key: 'ipAddress', label: __('IP Address') }), tableField({ key: 'ipAddress', label: __('IP Address') }),
tableField({ key: 'tagList', label: __('Tags'), width: 20 }), tableField({ key: 'tagList', label: __('Tags'), width: 20 }),
...@@ -88,8 +88,8 @@ export default { ...@@ -88,8 +88,8 @@ export default {
<gl-skeleton-loader v-for="i in 4" :key="i" /> <gl-skeleton-loader v-for="i in 4" :key="i" />
</template> </template>
<template #cell(type)="{ item }"> <template #cell(status)="{ item }">
<runner-type-cell :runner="item" /> <runner-status-cell :runner="item" />
</template> </template>
<template #cell(summary)="{ item, index }"> <template #cell(summary)="{ item, index }">
......
<script>
import { GlBadge, GlTooltipDirective } from '@gitlab/ui';
import { I18N_LOCKED_RUNNER_DESCRIPTION } from '../constants';
export default {
components: {
GlBadge,
},
directives: {
GlTooltip: GlTooltipDirective,
},
i18n: {
I18N_LOCKED_RUNNER_DESCRIPTION,
},
};
</script>
<template>
<gl-badge
v-gl-tooltip="$options.i18n.I18N_LOCKED_RUNNER_DESCRIPTION"
variant="warning"
v-bind="$attrs"
>
{{ s__('Runners|locked') }}
</gl-badge>
</template>
...@@ -9,17 +9,14 @@ const ALERT_DATA = { ...@@ -9,17 +9,14 @@ const ALERT_DATA = {
message: s__( message: s__(
'Runners|This runner is available to all groups and projects in your GitLab instance.', 'Runners|This runner is available to all groups and projects in your GitLab instance.',
), ),
variant: 'success',
anchor: 'shared-runners', anchor: 'shared-runners',
}, },
[GROUP_TYPE]: { [GROUP_TYPE]: {
message: s__('Runners|This runner is available to all projects and subgroups in a group.'), message: s__('Runners|This runner is available to all projects and subgroups in a group.'),
variant: 'success',
anchor: 'group-runners', anchor: 'group-runners',
}, },
[PROJECT_TYPE]: { [PROJECT_TYPE]: {
message: s__('Runners|This runner is associated with one or more projects.'), message: s__('Runners|This runner is associated with one or more projects.'),
variant: 'info',
anchor: 'specific-runners', anchor: 'specific-runners',
}, },
}; };
...@@ -50,7 +47,7 @@ export default { ...@@ -50,7 +47,7 @@ export default {
}; };
</script> </script>
<template> <template>
<gl-alert v-if="alert" :variant="alert.variant" :dismissible="false"> <gl-alert v-if="alert" variant="info" :dismissible="false">
{{ alert.message }} {{ alert.message }}
<gl-link :href="helpHref">{{ __('Learn more.') }}</gl-link> <gl-link :href="helpHref">{{ __('Learn more.') }}</gl-link>
</gl-alert> </gl-alert>
......
...@@ -12,17 +12,14 @@ import { ...@@ -12,17 +12,14 @@ import {
const BADGE_DATA = { const BADGE_DATA = {
[INSTANCE_TYPE]: { [INSTANCE_TYPE]: {
variant: 'success',
text: s__('Runners|shared'), text: s__('Runners|shared'),
tooltip: I18N_INSTANCE_RUNNER_DESCRIPTION, tooltip: I18N_INSTANCE_RUNNER_DESCRIPTION,
}, },
[GROUP_TYPE]: { [GROUP_TYPE]: {
variant: 'success',
text: s__('Runners|group'), text: s__('Runners|group'),
tooltip: I18N_GROUP_RUNNER_DESCRIPTION, tooltip: I18N_GROUP_RUNNER_DESCRIPTION,
}, },
[PROJECT_TYPE]: { [PROJECT_TYPE]: {
variant: 'info',
text: s__('Runners|specific'), text: s__('Runners|specific'),
tooltip: I18N_PROJECT_RUNNER_DESCRIPTION, tooltip: I18N_PROJECT_RUNNER_DESCRIPTION,
}, },
...@@ -53,7 +50,7 @@ export default { ...@@ -53,7 +50,7 @@ export default {
}; };
</script> </script>
<template> <template>
<gl-badge v-if="badge" v-gl-tooltip="badge.tooltip" :variant="badge.variant" v-bind="$attrs"> <gl-badge v-if="badge" v-gl-tooltip="badge.tooltip" variant="info" v-bind="$attrs">
{{ badge.text }} {{ badge.text }}
</gl-badge> </gl-badge>
</template> </template>
...@@ -6,11 +6,24 @@ export const GROUP_RUNNER_COUNT_LIMIT = 1000; ...@@ -6,11 +6,24 @@ export const GROUP_RUNNER_COUNT_LIMIT = 1000;
export const I18N_FETCH_ERROR = s__('Runners|Something went wrong while fetching runner data.'); export const I18N_FETCH_ERROR = s__('Runners|Something went wrong while fetching runner data.');
export const I18N_DETAILS_TITLE = s__('Runners|Runner #%{runner_id}'); export const I18N_DETAILS_TITLE = s__('Runners|Runner #%{runner_id}');
// Type
export const I18N_INSTANCE_RUNNER_DESCRIPTION = s__('Runners|Available to all projects'); export const I18N_INSTANCE_RUNNER_DESCRIPTION = s__('Runners|Available to all projects');
export const I18N_GROUP_RUNNER_DESCRIPTION = s__( export const I18N_GROUP_RUNNER_DESCRIPTION = s__(
'Runners|Available to all projects and subgroups in the group', 'Runners|Available to all projects and subgroups in the group',
); );
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
export const I18N_ONLINE_RUNNER_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_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');
......
#import "~/runner/graphql/runner_node.fragment.graphql"
# Mutation for updates within the runners list via action
# buttons (play, pause, ...), loads attributes shown in the
# runner list.
mutation runnerActionsUpdate($input: RunnerUpdateInput!) {
runnerUpdate(input: $input) {
runner {
...RunnerNode
}
errors
}
}
...@@ -10,4 +10,5 @@ fragment RunnerNode on CiRunner { ...@@ -10,4 +10,5 @@ fragment RunnerNode on CiRunner {
locked locked
tagList tagList
contactedAt contactedAt
status
} }
#import "ee_else_ce/runner/graphql/runner_details.fragment.graphql" #import "ee_else_ce/runner/graphql/runner_details.fragment.graphql"
# Mutation for updates from the runner form, loads
# attributes shown in the runner details.
mutation runnerUpdate($input: RunnerUpdateInput!) { mutation runnerUpdate($input: RunnerUpdateInput!) {
runnerUpdate(input: $input) { runnerUpdate(input: $input) {
runner { runner {
......
...@@ -29691,6 +29691,9 @@ msgstr "" ...@@ -29691,6 +29691,9 @@ msgstr ""
msgid "Runners|New runner, has not connected yet" msgid "Runners|New runner, has not connected yet"
msgstr "" msgstr ""
msgid "Runners|No recent contact from this runner; last contact was %{timeAgo}"
msgstr ""
msgid "Runners|Not available to run jobs" msgid "Runners|Not available to run jobs"
msgstr "" msgstr ""
...@@ -29742,6 +29745,9 @@ msgstr "" ...@@ -29742,6 +29745,9 @@ msgstr ""
msgid "Runners|Runner #%{runner_id}" msgid "Runners|Runner #%{runner_id}"
msgstr "" msgstr ""
msgid "Runners|Runner ID"
msgstr ""
msgid "Runners|Runner assigned to project." msgid "Runners|Runner assigned to project."
msgstr "" msgstr ""
...@@ -29751,6 +29757,9 @@ msgstr "" ...@@ -29751,6 +29757,9 @@ msgstr ""
msgid "Runners|Runner is online, last contact was %{runner_contact} ago" msgid "Runners|Runner is online, last contact was %{runner_contact} ago"
msgstr "" msgstr ""
msgid "Runners|Runner is online; last contact was %{timeAgo}"
msgstr ""
msgid "Runners|Runner is paused, last contact was %{runner_contact} ago" msgid "Runners|Runner is paused, last contact was %{runner_contact} ago"
msgstr "" msgstr ""
...@@ -29781,12 +29790,18 @@ msgstr "" ...@@ -29781,12 +29790,18 @@ 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|Status"
msgstr ""
msgid "Runners|Stop the runner from accepting new jobs." msgid "Runners|Stop the runner from accepting new jobs."
msgstr "" msgstr ""
msgid "Runners|Tags" msgid "Runners|Tags"
msgstr "" msgstr ""
msgid "Runners|This runner has never connected to this instance"
msgstr ""
msgid "Runners|This runner is associated with one or more projects." msgid "Runners|This runner is associated with one or more projects."
msgstr "" msgstr ""
...@@ -29853,6 +29868,15 @@ msgstr "" ...@@ -29853,6 +29868,15 @@ msgstr ""
msgid "Runners|locked" msgid "Runners|locked"
msgstr "" msgstr ""
msgid "Runners|not connected"
msgstr ""
msgid "Runners|offline"
msgstr ""
msgid "Runners|online"
msgstr ""
msgid "Runners|paused" msgid "Runners|paused"
msgstr "" msgstr ""
......
...@@ -8,12 +8,11 @@ import RunnerActionCell from '~/runner/components/cells/runner_actions_cell.vue' ...@@ -8,12 +8,11 @@ import RunnerActionCell from '~/runner/components/cells/runner_actions_cell.vue'
import getGroupRunnersQuery from '~/runner/graphql/get_group_runners.query.graphql'; import getGroupRunnersQuery from '~/runner/graphql/get_group_runners.query.graphql';
import getRunnersQuery from '~/runner/graphql/get_runners.query.graphql'; import getRunnersQuery from '~/runner/graphql/get_runners.query.graphql';
import runnerDeleteMutation from '~/runner/graphql/runner_delete.mutation.graphql'; import runnerDeleteMutation from '~/runner/graphql/runner_delete.mutation.graphql';
import runnerUpdateMutation from '~/runner/graphql/runner_update.mutation.graphql'; import runnerActionsUpdateMutation from '~/runner/graphql/runner_actions_update.mutation.graphql';
import { captureException } from '~/runner/sentry_utils'; import { captureException } from '~/runner/sentry_utils';
import { runnersData, runnerData } from '../../mock_data'; import { runnersData } from '../../mock_data';
const mockRunner = runnersData.data.runners.nodes[0]; const mockRunner = runnersData.data.runners.nodes[0];
const mockRunnerDetails = runnerData.data.runner;
const getRunnersQueryName = getRunnersQuery.definitions[0].name.value; const getRunnersQueryName = getRunnersQuery.definitions[0].name.value;
const getGroupRunnersQueryName = getGroupRunnersQuery.definitions[0].name.value; const getGroupRunnersQueryName = getGroupRunnersQuery.definitions[0].name.value;
...@@ -27,7 +26,7 @@ jest.mock('~/runner/sentry_utils'); ...@@ -27,7 +26,7 @@ jest.mock('~/runner/sentry_utils');
describe('RunnerTypeCell', () => { describe('RunnerTypeCell', () => {
let wrapper; let wrapper;
const runnerDeleteMutationHandler = jest.fn(); const runnerDeleteMutationHandler = jest.fn();
const runnerUpdateMutationHandler = jest.fn(); const runnerActionsUpdateMutationHandler = jest.fn();
const findEditBtn = () => wrapper.findByTestId('edit-runner'); const findEditBtn = () => wrapper.findByTestId('edit-runner');
const findToggleActiveBtn = () => wrapper.findByTestId('toggle-active-runner'); const findToggleActiveBtn = () => wrapper.findByTestId('toggle-active-runner');
...@@ -46,7 +45,7 @@ describe('RunnerTypeCell', () => { ...@@ -46,7 +45,7 @@ describe('RunnerTypeCell', () => {
localVue, localVue,
apolloProvider: createMockApollo([ apolloProvider: createMockApollo([
[runnerDeleteMutation, runnerDeleteMutationHandler], [runnerDeleteMutation, runnerDeleteMutationHandler],
[runnerUpdateMutation, runnerUpdateMutationHandler], [runnerActionsUpdateMutation, runnerActionsUpdateMutationHandler],
]), ]),
...options, ...options,
}), }),
...@@ -62,10 +61,10 @@ describe('RunnerTypeCell', () => { ...@@ -62,10 +61,10 @@ describe('RunnerTypeCell', () => {
}, },
}); });
runnerUpdateMutationHandler.mockResolvedValue({ runnerActionsUpdateMutationHandler.mockResolvedValue({
data: { data: {
runnerUpdate: { runnerUpdate: {
runner: mockRunnerDetails, runner: mockRunner,
errors: [], errors: [],
}, },
}, },
...@@ -74,7 +73,7 @@ describe('RunnerTypeCell', () => { ...@@ -74,7 +73,7 @@ describe('RunnerTypeCell', () => {
afterEach(() => { afterEach(() => {
runnerDeleteMutationHandler.mockReset(); runnerDeleteMutationHandler.mockReset();
runnerUpdateMutationHandler.mockReset(); runnerActionsUpdateMutationHandler.mockReset();
wrapper.destroy(); wrapper.destroy();
}); });
...@@ -116,12 +115,12 @@ describe('RunnerTypeCell', () => { ...@@ -116,12 +115,12 @@ describe('RunnerTypeCell', () => {
describe(`When clicking on the ${icon} button`, () => { describe(`When clicking on the ${icon} button`, () => {
it(`The apollo mutation to set active to ${newActiveValue} is called`, async () => { it(`The apollo mutation to set active to ${newActiveValue} is called`, async () => {
expect(runnerUpdateMutationHandler).toHaveBeenCalledTimes(0); expect(runnerActionsUpdateMutationHandler).toHaveBeenCalledTimes(0);
await findToggleActiveBtn().vm.$emit('click'); await findToggleActiveBtn().vm.$emit('click');
expect(runnerUpdateMutationHandler).toHaveBeenCalledTimes(1); expect(runnerActionsUpdateMutationHandler).toHaveBeenCalledTimes(1);
expect(runnerUpdateMutationHandler).toHaveBeenCalledWith({ expect(runnerActionsUpdateMutationHandler).toHaveBeenCalledWith({
input: { input: {
id: mockRunner.id, id: mockRunner.id,
active: newActiveValue, active: newActiveValue,
...@@ -145,7 +144,7 @@ describe('RunnerTypeCell', () => { ...@@ -145,7 +144,7 @@ describe('RunnerTypeCell', () => {
const mockErrorMsg = 'Update error!'; const mockErrorMsg = 'Update error!';
beforeEach(async () => { beforeEach(async () => {
runnerUpdateMutationHandler.mockRejectedValueOnce(new Error(mockErrorMsg)); runnerActionsUpdateMutationHandler.mockRejectedValueOnce(new Error(mockErrorMsg));
await findToggleActiveBtn().vm.$emit('click'); await findToggleActiveBtn().vm.$emit('click');
}); });
...@@ -167,10 +166,10 @@ describe('RunnerTypeCell', () => { ...@@ -167,10 +166,10 @@ describe('RunnerTypeCell', () => {
const mockErrorMsg2 = 'User not allowed!'; const mockErrorMsg2 = 'User not allowed!';
beforeEach(async () => { beforeEach(async () => {
runnerUpdateMutationHandler.mockResolvedValue({ runnerActionsUpdateMutationHandler.mockResolvedValue({
data: { data: {
runnerUpdate: { runnerUpdate: {
runner: runnerData.data.runner, runner: mockRunner,
errors: [mockErrorMsg, mockErrorMsg2], errors: [mockErrorMsg, mockErrorMsg2],
}, },
}, },
......
import { GlBadge } from '@gitlab/ui'; import { GlBadge } from '@gitlab/ui';
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import RunnerTypeCell from '~/runner/components/cells/runner_type_cell.vue'; import RunnerStatusCell from '~/runner/components/cells/runner_status_cell.vue';
import { INSTANCE_TYPE } from '~/runner/constants'; import { INSTANCE_TYPE, STATUS_ONLINE, STATUS_OFFLINE } from '~/runner/constants';
describe('RunnerTypeCell', () => { describe('RunnerTypeCell', () => {
let wrapper; let wrapper;
const findBadges = () => wrapper.findAllComponents(GlBadge); const findBadgeAt = (i) => wrapper.findAllComponents(GlBadge).at(i);
const createComponent = ({ runner = {} } = {}) => { const createComponent = ({ runner = {} } = {}) => {
wrapper = mount(RunnerTypeCell, { wrapper = mount(RunnerStatusCell, {
propsData: { propsData: {
runner: { runner: {
runnerType: INSTANCE_TYPE, runnerType: INSTANCE_TYPE,
active: true, active: true,
locked: false, status: STATUS_ONLINE,
...runner, ...runner,
}, },
}, },
...@@ -25,24 +25,45 @@ describe('RunnerTypeCell', () => { ...@@ -25,24 +25,45 @@ describe('RunnerTypeCell', () => {
wrapper.destroy(); wrapper.destroy();
}); });
it('Displays the runner type', () => { it('Displays online status', () => {
createComponent(); createComponent();
expect(findBadges()).toHaveLength(1); expect(wrapper.text()).toMatchInterpolatedText('online');
expect(findBadges().at(0).text()).toBe('shared'); expect(findBadgeAt(0).text()).toBe('online');
}); });
it('Displays locked and paused states', () => { it('Displays offline status', () => {
createComponent({
runner: {
status: STATUS_OFFLINE,
},
});
expect(wrapper.text()).toMatchInterpolatedText('offline');
expect(findBadgeAt(0).text()).toBe('offline');
});
it('Displays paused status', () => {
createComponent({ createComponent({
runner: { runner: {
active: false, active: false,
locked: true, status: STATUS_ONLINE,
},
});
expect(wrapper.text()).toMatchInterpolatedText('online paused');
expect(findBadgeAt(0).text()).toBe('online');
expect(findBadgeAt(1).text()).toBe('paused');
});
it('Is empty when data is missing', () => {
createComponent({
runner: {
status: null,
}, },
}); });
expect(findBadges()).toHaveLength(3); expect(wrapper.text()).toBe('');
expect(findBadges().at(0).text()).toBe('shared');
expect(findBadges().at(1).text()).toBe('locked');
expect(findBadges().at(2).text()).toBe('paused');
}); });
}); });
import { mount } from '@vue/test-utils'; import { mountExtended } from 'helpers/vue_test_utils_helper';
import RunnerSummaryCell from '~/runner/components/cells/runner_summary_cell.vue'; import RunnerSummaryCell from '~/runner/components/cells/runner_summary_cell.vue';
import { INSTANCE_TYPE, PROJECT_TYPE } from '~/runner/constants';
const mockId = '1'; const mockId = '1';
const mockShortSha = '2P6oDVDm'; const mockShortSha = '2P6oDVDm';
...@@ -8,13 +9,17 @@ const mockDescription = 'runner-1'; ...@@ -8,13 +9,17 @@ const mockDescription = 'runner-1';
describe('RunnerTypeCell', () => { describe('RunnerTypeCell', () => {
let wrapper; let wrapper;
const createComponent = (options) => { const findLockIcon = () => wrapper.findByTestId('lock-icon');
wrapper = mount(RunnerSummaryCell, {
const createComponent = (runner, options) => {
wrapper = mountExtended(RunnerSummaryCell, {
propsData: { propsData: {
runner: { runner: {
id: `gid://gitlab/Ci::Runner/${mockId}`, id: `gid://gitlab/Ci::Runner/${mockId}`,
shortSha: mockShortSha, shortSha: mockShortSha,
description: mockDescription, description: mockDescription,
runnerType: INSTANCE_TYPE,
...runner,
}, },
}, },
...options, ...options,
...@@ -33,6 +38,23 @@ describe('RunnerTypeCell', () => { ...@@ -33,6 +38,23 @@ describe('RunnerTypeCell', () => {
expect(wrapper.text()).toContain(`#${mockId} (${mockShortSha})`); expect(wrapper.text()).toContain(`#${mockId} (${mockShortSha})`);
}); });
it('Displays the runner type', () => {
expect(wrapper.text()).toContain('shared');
});
it('Does not display the locked icon', () => {
expect(findLockIcon().exists()).toBe(false);
});
it('Displays the locked icon for locked runners', () => {
createComponent({
runnerType: PROJECT_TYPE,
locked: true,
});
expect(findLockIcon().exists()).toBe(true);
});
it('Displays the runner description', () => { it('Displays the runner description', () => {
expect(wrapper.text()).toContain(mockDescription); expect(wrapper.text()).toContain(mockDescription);
}); });
...@@ -40,11 +62,14 @@ describe('RunnerTypeCell', () => { ...@@ -40,11 +62,14 @@ describe('RunnerTypeCell', () => {
it('Displays a custom slot', () => { it('Displays a custom slot', () => {
const slotContent = 'My custom runner summary'; const slotContent = 'My custom runner summary';
createComponent({ createComponent(
slots: { {},
'runner-name': slotContent, {
slots: {
'runner-name': slotContent,
},
}, },
}); );
expect(wrapper.text()).toContain(slotContent); expect(wrapper.text()).toContain(slotContent);
}); });
......
import { GlBadge } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import RunnerContactedStateBadge from '~/runner/components/runner_contacted_state_badge.vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { STATUS_ONLINE, STATUS_OFFLINE, STATUS_NOT_CONNECTED } from '~/runner/constants';
describe('RunnerTypeBadge', () => {
let wrapper;
const findBadge = () => wrapper.findComponent(GlBadge);
const getTooltip = () => getBinding(findBadge().element, 'gl-tooltip');
const createComponent = ({ runner = {} } = {}) => {
wrapper = shallowMount(RunnerContactedStateBadge, {
propsData: {
runner: {
contactedAt: '2021-01-01T00:00:00Z',
status: STATUS_ONLINE,
...runner,
},
},
directives: {
GlTooltip: createMockDirective(),
},
});
};
beforeEach(() => {
jest.useFakeTimers('modern');
});
afterEach(() => {
jest.useFakeTimers('legacy');
wrapper.destroy();
});
it('renders online state', () => {
jest.setSystemTime(new Date('2021-01-01T00:01:00Z'));
createComponent();
expect(wrapper.text()).toBe('online');
expect(findBadge().props('variant')).toBe('success');
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'));
createComponent({
runner: {
status: STATUS_OFFLINE,
},
});
expect(wrapper.text()).toBe('offline');
expect(findBadge().props('variant')).toBe('muted');
expect(getTooltip().value).toBe(
'No recent contact from this runner; last contact was 1 day ago',
);
});
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('does not fail when data is missing', () => {
createComponent({
runner: {
status: null,
},
});
expect(wrapper.text()).toBe('');
});
});
...@@ -42,8 +42,8 @@ describe('RunnerList', () => { ...@@ -42,8 +42,8 @@ describe('RunnerList', () => {
const headerLabels = findHeaders().wrappers.map((w) => w.text()); const headerLabels = findHeaders().wrappers.map((w) => w.text());
expect(headerLabels).toEqual([ expect(headerLabels).toEqual([
'Type/State', 'Status',
'Runner', 'Runner ID',
'Version', 'Version',
'IP Address', 'IP Address',
'Tags', 'Tags',
...@@ -62,7 +62,7 @@ describe('RunnerList', () => { ...@@ -62,7 +62,7 @@ describe('RunnerList', () => {
const { id, description, version, ipAddress, shortSha } = mockRunners[0]; const { id, description, version, ipAddress, shortSha } = mockRunners[0];
// Badges // Badges
expect(findCell({ fieldKey: 'type' }).text()).toMatchInterpolatedText('specific paused'); expect(findCell({ fieldKey: 'status' }).text()).toMatchInterpolatedText('not connected paused');
// Runner summary // Runner summary
expect(findCell({ fieldKey: 'summary' }).text()).toContain( expect(findCell({ fieldKey: 'summary' }).text()).toContain(
......
import { GlBadge } from '@gitlab/ui'; import { GlBadge } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import RunnerStatePausedBadge from '~/runner/components/runner_state_paused_badge.vue'; import RunnerStatePausedBadge from '~/runner/components/runner_paused_badge.vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
describe('RunnerTypeBadge', () => { describe('RunnerTypeBadge', () => {
......
import { GlBadge } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import RunnerStateLockedBadge from '~/runner/components/runner_state_locked_badge.vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
describe('RunnerTypeBadge', () => {
let wrapper;
const findBadge = () => wrapper.findComponent(GlBadge);
const getTooltip = () => getBinding(findBadge().element, 'gl-tooltip');
const createComponent = ({ props = {} } = {}) => {
wrapper = shallowMount(RunnerStateLockedBadge, {
propsData: {
...props,
},
directives: {
GlTooltip: createMockDirective(),
},
});
};
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
});
it('renders locked state', () => {
expect(wrapper.text()).toBe('locked');
expect(findBadge().props('variant')).toBe('warning');
});
it('renders tooltip', () => {
expect(getTooltip().value).toBeDefined();
});
it('passes arbitrary attributes to the badge', () => {
createComponent({ props: { size: 'sm' } });
expect(findBadge().props('size')).toBe('sm');
});
});
...@@ -23,11 +23,11 @@ describe('RunnerTypeAlert', () => { ...@@ -23,11 +23,11 @@ describe('RunnerTypeAlert', () => {
}); });
describe.each` describe.each`
type | exampleText | anchor | variant type | exampleText | anchor
${INSTANCE_TYPE} | ${'This runner is available to all groups and projects'} | ${'#shared-runners'} | ${'success'} ${INSTANCE_TYPE} | ${'This runner is available to all groups and projects'} | ${'#shared-runners'}
${GROUP_TYPE} | ${'This runner is available to all projects and subgroups in a group'} | ${'#group-runners'} | ${'success'} ${GROUP_TYPE} | ${'This runner is available to all projects and subgroups in a group'} | ${'#group-runners'}
${PROJECT_TYPE} | ${'This runner is associated with one or more projects'} | ${'#specific-runners'} | ${'info'} ${PROJECT_TYPE} | ${'This runner is associated with one or more projects'} | ${'#specific-runners'}
`('When it is an $type level runner', ({ type, exampleText, anchor, variant }) => { `('When it is an $type level runner', ({ type, exampleText, anchor }) => {
beforeEach(() => { beforeEach(() => {
createComponent({ props: { type } }); createComponent({ props: { type } });
}); });
...@@ -36,8 +36,8 @@ describe('RunnerTypeAlert', () => { ...@@ -36,8 +36,8 @@ describe('RunnerTypeAlert', () => {
expect(wrapper.text()).toMatch(exampleText); expect(wrapper.text()).toMatch(exampleText);
}); });
it(`Shows a ${variant} variant`, () => { it(`Shows an "info" variant`, () => {
expect(findAlert().props('variant')).toBe(variant); expect(findAlert().props('variant')).toBe('info');
}); });
it(`Links to anchor "${anchor}"`, () => { it(`Links to anchor "${anchor}"`, () => {
......
...@@ -26,18 +26,18 @@ describe('RunnerTypeBadge', () => { ...@@ -26,18 +26,18 @@ describe('RunnerTypeBadge', () => {
}); });
describe.each` describe.each`
type | text | variant type | text
${INSTANCE_TYPE} | ${'shared'} | ${'success'} ${INSTANCE_TYPE} | ${'shared'}
${GROUP_TYPE} | ${'group'} | ${'success'} ${GROUP_TYPE} | ${'group'}
${PROJECT_TYPE} | ${'specific'} | ${'info'} ${PROJECT_TYPE} | ${'specific'}
`('displays $type runner', ({ type, text, variant }) => { `('displays $type runner', ({ type, text }) => {
beforeEach(() => { beforeEach(() => {
createComponent({ props: { type } }); createComponent({ props: { type } });
}); });
it(`as "${text}" with a ${variant} variant`, () => { it(`as "${text}" with an "info" variant`, () => {
expect(findBadge().text()).toBe(text); expect(findBadge().text()).toBe(text);
expect(findBadge().props('variant')).toBe(variant); expect(findBadge().props('variant')).toBe('info');
}); });
it('with a tooltip', () => { it('with a tooltip', () => {
......
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