Skip to content
代码片段 群组 项目
提交 d0dfeae6 编辑于 作者: Alexander Turinske's avatar Alexander Turinske
浏览文件

Merge branch '355766-add-an-owner-of-a-runner-in-the-admin-area-runners-table' into 'master'

No related branches found
No related tags found
无相关合并请求
<script>
import { GlLink, GlTooltipDirective } from '@gitlab/ui';
import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE, I18N_ADMIN } from '../../constants';
export default {
components: {
GlLink,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
runner: {
type: Object,
required: true,
},
},
computed: {
cell() {
switch (this.runner?.runnerType) {
case INSTANCE_TYPE:
return {
text: I18N_ADMIN,
};
case GROUP_TYPE: {
const { name, fullName, webUrl } = this.runner?.groups?.nodes[0] || {};
return {
text: name,
href: webUrl,
tooltip: fullName !== name ? fullName : '',
};
}
case PROJECT_TYPE: {
const { name, nameWithNamespace, webUrl } = this.runner?.ownerProject || {};
return {
text: name,
href: webUrl,
tooltip: nameWithNamespace !== name ? nameWithNamespace : '',
};
}
default:
return {};
}
},
},
};
</script>
<template>
<div>
<gl-link
v-if="cell.href"
v-gl-tooltip="cell.tooltip"
:href="cell.href"
class="gl-text-body gl-text-decoration-underline"
>
{{ cell.text }}
</gl-link>
<span v-else>{{ cell.text }}</span>
</div>
</template>
......@@ -2,15 +2,18 @@
import { GlFormCheckbox, GlTableLite, GlTooltipDirective, GlSkeletonLoader } from '@gitlab/ui';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { s__ } from '~/locale';
import HelpPopover from '~/vue_shared/components/help_popover.vue';
import checkedRunnerIdsQuery from '../graphql/list/checked_runner_ids.query.graphql';
import { formatJobCount, tableField } from '../utils';
import RunnerStackedSummaryCell from './cells/runner_stacked_summary_cell.vue';
import RunnerStatusPopover from './runner_status_popover.vue';
import RunnerStatusCell from './cells/runner_status_cell.vue';
import RunnerOwnerCell from './cells/runner_owner_cell.vue';
const defaultFields = [
tableField({ key: 'status', label: s__('Runners|Status'), thClasses: ['gl-w-15p'] }),
tableField({ key: 'summary', label: s__('Runners|Runner') }),
tableField({ key: 'owner', label: s__('Runners|Owner'), thClasses: ['gl-w-20p'] }),
tableField({ key: 'actions', label: '', thClasses: ['gl-w-15p'] }),
];
......@@ -19,9 +22,11 @@ export default {
GlFormCheckbox,
GlTableLite,
GlSkeletonLoader,
HelpPopover,
RunnerStatusPopover,
RunnerStackedSummaryCell,
RunnerStatusCell,
RunnerOwnerCell,
},
directives: {
GlTooltip: GlTooltipDirective,
......@@ -140,6 +145,21 @@ export default {
</runner-stacked-summary-cell>
</template>
<template #head(owner)="{ label }">
{{ label }}
<help-popover>
{{
s__(
'Runners|The project, group or instance where the runner was registered. Instance runners are always owned by Administrator.',
)
}}
</help-popover>
</template>
<template #cell(owner)="{ item }">
<runner-owner-cell :runner="item" />
</template>
<template #cell(actions)="{ item }">
<slot name="runner-actions-cell" :runner="item"></slot>
</template>
......
......@@ -89,6 +89,7 @@ export const I18N_VERSION_LABEL = s__('Runners|Version %{version}');
export const I18N_LAST_CONTACT_LABEL = s__('Runners|Last contact: %{timeAgo}');
export const I18N_CREATED_AT_LABEL = s__('Runners|Created %{timeAgo}');
export const I18N_SHOW_ONLY_INHERITED = s__('Runners|Show only inherited');
export const I18N_ADMIN = s__('Runners|Administrator');
// Runner details
......
......@@ -16,4 +16,18 @@ fragment ListItemShared on CiRunner {
updateRunner
deleteRunner
}
groups(first: 1) {
nodes {
id
name
fullName
webUrl
}
}
ownerProject {
id
name
nameWithNamespace
webUrl
}
}
......@@ -34575,6 +34575,9 @@ msgstr ""
msgid "Runners|Add your feedback in the issue"
msgstr ""
 
msgid "Runners|Administrator"
msgstr ""
msgid "Runners|All"
msgstr ""
 
......@@ -34781,6 +34784,9 @@ msgstr ""
msgid "Runners|Online:"
msgstr ""
 
msgid "Runners|Owner"
msgstr ""
msgid "Runners|Pause from accepting jobs"
msgstr ""
 
......@@ -34963,6 +34969,9 @@ msgstr ""
msgid "Runners|The new view gives you more space and better visibility into your fleet of runners."
msgstr ""
 
msgid "Runners|The project, group or instance where the runner was registered. Instance runners are always owned by Administrator."
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 ""
 
import { shallowMount } from '@vue/test-utils';
import { GlLink } from '@gitlab/ui';
import { s__ } from '~/locale';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import RunnerOwnerCell from '~/runner/components/cells/runner_owner_cell.vue';
import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE } from '~/runner/constants';
describe('RunnerOwnerCell', () => {
let wrapper;
const findLink = () => wrapper.findComponent(GlLink);
const getLinkTooltip = () => getBinding(findLink().element, 'gl-tooltip').value;
const createComponent = ({ runner } = {}) => {
wrapper = shallowMount(RunnerOwnerCell, {
directives: {
GlTooltip: createMockDirective(),
},
propsData: {
runner,
},
});
};
afterEach(() => {
wrapper.destroy();
});
describe('When its an instance runner', () => {
beforeEach(() => {
createComponent({
runner: {
runnerType: INSTANCE_TYPE,
},
});
});
it('shows an administrator label', () => {
expect(findLink().exists()).toBe(false);
expect(wrapper.text()).toBe(s__('Runners|Administrator'));
});
});
describe('When its a group runner', () => {
const mockName = 'Group 2';
const mockFullName = 'Group 1 / Group 2';
const mockWebUrl = '/group-1/group-2';
beforeEach(() => {
createComponent({
runner: {
runnerType: GROUP_TYPE,
groups: {
nodes: [
{
name: mockName,
fullName: mockFullName,
webUrl: mockWebUrl,
},
],
},
},
});
});
it('Displays a group link', () => {
expect(findLink().attributes('href')).toBe(mockWebUrl);
expect(wrapper.text()).toBe(mockName);
expect(getLinkTooltip()).toBe(mockFullName);
});
});
describe('When its a project runner', () => {
const mockName = 'Project 1';
const mockNameWithNamespace = 'Group 1 / Project 1';
const mockWebUrl = '/group-1/project-1';
beforeEach(() => {
createComponent({
runner: {
runnerType: PROJECT_TYPE,
ownerProject: {
name: mockName,
nameWithNamespace: mockNameWithNamespace,
webUrl: mockWebUrl,
},
},
});
});
it('Displays a project link', () => {
expect(findLink().attributes('href')).toBe(mockWebUrl);
expect(wrapper.text()).toBe(mockName);
expect(getLinkTooltip()).toBe(mockNameWithNamespace);
});
});
describe('When its an empty runner', () => {
beforeEach(() => {
createComponent({
runner: {},
});
});
it('shows no label', () => {
expect(wrapper.text()).toBe('');
});
});
});
import { GlTableLite, GlSkeletonLoader } from '@gitlab/ui';
import HelpPopover from '~/vue_shared/components/help_popover.vue';
import {
extendedWrapper,
shallowMountExtended,
mountExtended,
} from 'helpers/vue_test_utils_helper';
import { s__ } from '~/locale';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import RunnerList from '~/runner/components/runner_list.vue';
import RunnerStatusPopover from '~/runner/components/runner_status_popover.vue';
import { I18N_PROJECT_TYPE, I18N_STATUS_NEVER_CONTACTED } from '~/runner/constants';
import { allRunnersData, onlineContactTimeoutSecs, staleTimeoutSecs } from '../mock_data';
......@@ -50,7 +51,7 @@ describe('RunnerList', () => {
createComponent(
{
stubs: {
RunnerStatusPopover: {
HelpPopover: {
template: '<div/>',
},
},
......@@ -60,11 +61,13 @@ describe('RunnerList', () => {
const headerLabels = findHeaders().wrappers.map((w) => w.text());
expect(findHeaders().at(0).findComponent(RunnerStatusPopover).exists()).toBe(true);
expect(findHeaders().at(0).findComponent(HelpPopover).exists()).toBe(true);
expect(findHeaders().at(2).findComponent(HelpPopover).exists()).toBe(true);
expect(headerLabels).toEqual([
'Status',
'Runner',
s__('Runners|Status'),
s__('Runners|Runner'),
s__('Runners|Owner'),
'', // actions has no label
]);
});
......
0% 加载中 .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册