Commit 1834d622 authored by Enrique Alcántara's avatar Enrique Alcántara

Merge branch '217938-update-compliance-dashboard-to-use-a-table-layout' into 'master'

Update Compliance Dashboard to use a grid layout

See merge request gitlab-org/gitlab!32440
parents 97083e16 70e0893b
<script>
import { sprintf, __ } from '~/locale';
import { GlAvatarLink, GlAvatar, GlTooltipDirective } from '@gitlab/ui';
import { GlAvatarLink, GlAvatar, GlAvatarsInline, GlTooltipDirective } from '@gitlab/ui';
import { PRESENTABLE_APPROVERS_LIMIT } from '../constants';
export default {
......@@ -8,6 +8,7 @@ export default {
GlTooltip: GlTooltipDirective,
},
components: {
GlAvatarsInline,
GlAvatarLink,
GlAvatar,
},
......@@ -36,15 +37,16 @@ export default {
});
},
},
PRESENTABLE_APPROVERS_LIMIT,
strings: {
approvedBy: __('Approved by: '),
noApprovers: __('No approvers'),
approvedBy: __('approved by: '),
noApprovers: __('no approvers'),
},
};
</script>
<template>
<li class="issuable-status d-flex approvers align-items-center">
<div class="gl-display-flex gl-align-items-center gl-justify-content-end" data-testid="approvers">
<span class="gl-text-gray-700">
<template v-if="hasApprovers">
{{ $options.strings.approvedBy }}
......@@ -53,6 +55,33 @@ export default {
{{ $options.strings.noApprovers }}
</template>
</span>
<gl-avatars-inline
v-if="hasApprovers"
:avatars="approvers"
:collapsed="true"
:max-visible="$options.PRESENTABLE_APPROVERS_LIMIT"
:avatar-size="24"
class="gl-display-inline-flex d-lg-none gl-ml-3"
badge-tooltip-prop="name"
>
<template #avatar="{ avatar }">
<gl-avatar-link
v-gl-tooltip
target="blank"
:href="avatar.web_url"
:title="avatar.name"
class="gl-text-gray-900 author-link js-user-link"
>
<gl-avatar
:src="avatar.avatar_url"
:entity-id="avatar.id"
:entity-name="avatar.name"
:size="24"
/>
</gl-avatar-link>
</template>
</gl-avatars-inline>
<gl-avatar-link
v-for="approver in approversToPresent"
:key="approver.id"
......@@ -60,7 +89,7 @@ export default {
:href="approver.web_url"
:data-user-id="approver.id"
:data-name="approver.name"
class="d-flex align-items-center ml-2 author-link js-user-link "
class="gl-display-none d-lg-inline-flex gl-align-items-center gl-justify-content-end gl-ml-3 gl-text-gray-900 author-link js-user-link"
>
<gl-avatar
:src="approver.avatar_url"
......@@ -74,8 +103,8 @@ export default {
<span
v-if="isApproversOverLimit"
v-gl-tooltip.top="approversOverLimitString"
class="avatar-counter ml-2"
class="gl-display-none d-lg-inline-block avatar-counter gl-ml-3 gl-px-2 gl-flex-shrink-0 flex-grow-0"
>+ {{ amountOfApproversOverLimit }}</span
>
</li>
</div>
</template>
<script>
import { __ } from '~/locale';
import MergeRequest from './merge_request.vue';
import { GlTooltipDirective } from '@gitlab/ui';
import { isEmpty } from 'lodash';
import { sprintf, __, s__ } from '~/locale';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import Approvers from './approvers.vue';
import EmptyState from './empty_state.vue';
import MergeRequest from './merge_request.vue';
import Pagination from './pagination.vue';
import PipelineStatus from './pipeline_status.vue';
import GridColumnHeading from './grid_column_heading.vue';
export default {
name: 'ComplianceDashboard',
directives: {
GlTooltip: GlTooltipDirective,
},
components: {
MergeRequest,
Approvers,
EmptyState,
GridColumnHeading,
MergeRequest,
Pagination,
PipelineStatus,
},
mixins: [timeagoMixin],
props: {
emptyStateSvgPath: {
type: String,
......@@ -22,7 +37,8 @@ export default {
},
isLastPage: {
type: Boolean,
required: true,
required: false,
default: false,
},
},
computed: {
......@@ -30,23 +46,70 @@ export default {
return this.mergeRequests.length > 0;
},
},
methods: {
key(id, value) {
return `${id}-${value}`;
},
timeAgoString(mergedAt) {
return sprintf(s__('merged %{timeAgo}'), {
timeAgo: this.timeFormatted(mergedAt),
});
},
timeTooltip(mergedAt) {
return this.tooltipTitle(mergedAt);
},
hasPipeline(status) {
return !isEmpty(status);
},
},
strings: {
heading: __('Compliance Dashboard'),
subheading: __('Here you will find recent merge request activity'),
mergeRequestLabel: __('Merge Request'),
pipelineStatusLabel: __('Pipeline'),
updatesLabel: __('Updates'),
},
};
</script>
<template>
<div v-if="hasMergeRequests" class="compliance-dashboard">
<header class="my-3">
<header class="gl-my-5">
<h4>{{ $options.strings.heading }}</h4>
<p>{{ $options.strings.subheading }}</p>
</header>
<ul class="content-list issuable-list issues-list">
<merge-request v-for="mr in mergeRequests" :key="mr.id" :merge-request="mr" />
</ul>
<pagination class="my-3" :is-last-page="isLastPage" />
<div class="dashboard-grid">
<grid-column-heading :heading="$options.strings.mergeRequestLabel" />
<grid-column-heading :heading="$options.strings.pipelineStatusLabel" class="gl-text-center" />
<grid-column-heading :heading="$options.strings.updatesLabel" class="gl-text-right" />
<template v-for="mergeRequest in mergeRequests">
<merge-request :key="key(mergeRequest.id, 'MR')" :merge-request="mergeRequest" />
<div
:key="key(mergeRequest.id, 'pipeline')"
class="dashboard-pipeline gl-display-flex gl-align-items-center gl-justify-content-center gl-border-b-solid gl-border-b-1 gl-border-b-gray-100 gl-p-5"
>
<pipeline-status
v-if="hasPipeline(mergeRequest.pipeline_status)"
:status="mergeRequest.pipeline_status"
/>
</div>
<div
:key="key(mergeRequest.id, 'updates')"
class="gl-text-right gl-border-b-solid gl-border-b-1 gl-border-b-gray-100 gl-p-5 gl-relative"
>
<approvers :approvers="mergeRequest.approved_by_users" />
<span class="gl-text-gray-700">
<time v-gl-tooltip.bottom="timeTooltip(mergeRequest.merged_at)">{{
timeAgoString(mergeRequest.merged_at)
}}</time>
</span>
</div>
</template>
</div>
<pagination :is-last-page="isLastPage" />
</div>
<empty-state v-else :image-path="emptyStateSvgPath" />
</template>
<script>
export default {
props: {
heading: {
type: String,
required: true,
},
},
};
</script>
<template>
<p
class="grid-column-heading gl-text-gray-700 gl-border-b-solid gl-border-b-2 gl-border-b-gray-100 gl-mb-0 gl-p-5"
>
{{ heading }}
</p>
</template>
<script>
import { sprintf, s__ } from '~/locale';
import { GlAvatar, GlAvatarLink, GlTooltipDirective } from '@gitlab/ui';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import Approvers from './approvers.vue';
import { GlAvatar, GlAvatarLink } from '@gitlab/ui';
import { s__ } from '~/locale';
export default {
directives: {
GlTooltip: GlTooltipDirective,
},
components: {
CiIcon,
Approvers,
GlAvatar,
GlAvatarLink,
},
mixins: [timeagoMixin],
props: {
mergeRequest: {
type: Object,
required: true,
},
},
computed: {
hasCiPipeline() {
return Boolean(this.mergeRequest.pipeline_status);
},
pipelineCiStatus() {
const details = this.mergeRequest.pipeline_status;
return { ...details, group: details.group || details.label };
},
pipelineTitle() {
const { tooltip } = this.mergeRequest.pipeline_status;
return sprintf(s__('PipelineStatusTooltip|Pipeline: %{ci_status}'), {
ci_status: tooltip,
});
},
timeAgoString() {
return sprintf(s__('merged %{time_ago}'), {
time_ago: this.timeFormatted(this.mergeRequest.merged_at),
});
},
timeTooltip() {
return this.tooltipTitle(this.mergeRequest.merged_at);
},
},
strings: {
createdBy: s__('ComplianceDashboard|created by:'),
},
......@@ -52,18 +21,15 @@ export default {
</script>
<template>
<li class="merge-request">
<div class="issuable-info-container">
<div class="issuable-main-info">
<div class="title">
<a :href="mergeRequest.path">
<div
class="grid-merge-request gl-border-b-solid gl-border-b-1 gl-border-b-gray-100 gl-p-5"
data-testid="merge-request"
>
<a :href="mergeRequest.path" class="gl-display-block gl-text-gray-900 gl-font-weight-bold">
{{ mergeRequest.title }}
</a>
</div>
<span class="gl-text-gray-700">
{{ mergeRequest.issuable_reference }}
</span>
<span class="issuable-authored gl-text-gray-700 d-inline-flex align-items-center">
<span class="gl-text-gray-700">{{ mergeRequest.issuable_reference }}</span>
<span class="issuable-authored gl-text-gray-700 gl-display-inline-flex gl-align-items-center">
- {{ $options.strings.createdBy }}
<gl-avatar-link
:key="mergeRequest.author.id"
......@@ -71,7 +37,7 @@ export default {
:href="mergeRequest.author.web_url"
:data-user-id="mergeRequest.author.id"
:data-name="mergeRequest.author.name"
class="d-inline-flex align-items-center ml-2 author-link js-user-link"
class="gl-display-inline-flex gl-align-items-center gl-ml-3 gl-text-gray-900 author-link js-user-link"
>
<gl-avatar
:src="mergeRequest.author.avatar_url"
......@@ -84,24 +50,4 @@ export default {
</gl-avatar-link>
</span>
</div>
<div class="issuable-meta">
<ul class="controls">
<li v-if="hasCiPipeline" class="mr-2">
<a :href="pipelineCiStatus.details_path">
<ci-icon
v-gl-tooltip.left="pipelineTitle"
class="d-flex"
:status="pipelineCiStatus"
/>
</a>
</li>
<approvers :approvers="mergeRequest.approved_by_users" />
</ul>
<span class="gl-text-gray-700">
<time v-gl-tooltip.bottom="timeTooltip">{{ timeAgoString }}</time>
</span>
</div>
</div>
</li>
</template>
<script>
import { GlTooltipDirective } from '@gitlab/ui';
import { sprintf, s__ } from '~/locale';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
export default {
directives: {
GlTooltip: GlTooltipDirective,
},
components: {
CiIcon,
},
props: {
status: {
type: Object,
required: true,
},
},
computed: {
pipelineCiStatus() {
return { ...this.status, group: this.status.group || this.status.label };
},
pipelineTitle() {
return sprintf(s__('PipelineStatusTooltip|Pipeline: %{ci_status}'), {
ci_status: this.status.tooltip,
});
},
},
};
</script>
<template>
<a :href="pipelineCiStatus.details_path">
<ci-icon v-gl-tooltip.left="pipelineTitle" class="gl-display-flex" :status="pipelineCiStatus" />
</a>
</template>
.compliance-dashboard {
// Remove this restriction when working on https://gitlab.com/gitlab-org/gitlab/-/issues/218826
min-width: 550px;
.dashboard-grid {
display: grid;
grid-template-columns: 1fr auto auto;
grid-template-rows: auto;
}
.grid-merge-request {
grid-column-start: 1;
}
}
.compliance-dashboard {
.content-list {
border-top: 1px solid $border-color;
border-bottom: 1px solid $border-color;
}
}
---
title: Update Compliance Dashboard to use a table layout
merge_request: 32440
author:
type: changed
......@@ -31,7 +31,7 @@ RSpec.describe 'Compliance Dashboard', :js do
it 'shows merge requests with details' do
expect(page).to have_link(merge_request.title)
expect(page).to have_content('merged 10 minutes ago')
expect(page).to have_content('No approvers')
expect(page).to have_content('no approvers')
end
end
end
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`MergeRequest component when there are approvers matches snapshot 1`] = `
<li
class="issuable-status d-flex approvers align-items-center"
<div
class="gl-display-flex gl-align-items-center gl-justify-content-end"
data-testid="approvers"
>
<span
class="gl-text-gray-700"
>
Approved by:
approved by:
</span>
<gl-avatars-inline-stub
avatars="[object Object]"
avatarsize="24"
badgetooltipprop="name"
class="gl-display-inline-flex d-lg-none gl-ml-3"
collapsed="true"
maxvisible="2"
/>
<gl-link-stub
class="gl-avatar-link d-flex align-items-center ml-2 author-link js-user-link "
class="gl-avatar-link gl-display-none d-lg-inline-flex gl-align-items-center gl-justify-content-end gl-ml-3 gl-text-gray-900 author-link js-user-link"
data-name="User 0"
data-user-id="0"
href="http://localhost:3000/user-0"
......@@ -35,5 +45,5 @@ exports[`MergeRequest component when there are approvers matches snapshot 1`] =
</gl-link-stub>
<!---->
</li>
</div>
`;
......@@ -5,7 +5,7 @@ exports[`ComplianceDashboard component when there are merge requests matches the
class="compliance-dashboard"
>
<header
class="my-3"
class="gl-my-5"
>
<h4>
Compliance Dashboard
......@@ -16,24 +16,80 @@ exports[`ComplianceDashboard component when there are merge requests matches the
</p>
</header>
<ul
class="content-list issuable-list issues-list"
<div
class="dashboard-grid"
>
<grid-column-heading-stub
heading="Merge Request"
/>
<grid-column-heading-stub
class="gl-text-center"
heading="Pipeline"
/>
<grid-column-heading-stub
class="gl-text-right"
heading="Updates"
/>
<div
class="merge-request"
data-testid="merge-request"
>
Merge request 0
</div>
<div
class="merge-request"
class="dashboard-pipeline gl-display-flex gl-align-items-center gl-justify-content-center gl-border-b-solid gl-border-b-1 gl-border-b-gray-100 gl-p-5"
>
<!---->
</div>
<div
class="gl-text-right gl-border-b-solid gl-border-b-1 gl-border-b-gray-100 gl-p-5 gl-relative"
>
<approvers-stub
approvers=""
/>
<span
class="gl-text-gray-700"
>
<time>
merged 2 days ago
</time>
</span>
</div>
<div
data-testid="merge-request"
>
Merge request 1
</div>
</ul>
<pagination-stub
class="my-3"
<div
class="dashboard-pipeline gl-display-flex gl-align-items-center gl-justify-content-center gl-border-b-solid gl-border-b-1 gl-border-b-gray-100 gl-p-5"
>
<!---->
</div>
<div
class="gl-text-right gl-border-b-solid gl-border-b-1 gl-border-b-gray-100 gl-p-5 gl-relative"
>
<approvers-stub
approvers=""
/>
<span
class="gl-text-gray-700"
>
<time>
merged 2 days ago
</time>
</span>
</div>
</div>
<pagination-stub />
</div>
`;
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`GridColumnHeading component behaviour matches the screenshot 1`] = `
<p
class="grid-column-heading gl-text-gray-700 gl-border-b-solid gl-border-b-2 gl-border-b-gray-100 gl-mb-0 gl-p-5"
>
Test heading
</p>
`;
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`MergeRequest component when there is a merge request matches the snapshot 1`] = `
<li
class="merge-request"
<div
class="grid-merge-request gl-border-b-solid gl-border-b-1 gl-border-b-gray-100 gl-p-5"
data-testid="merge-request"
>
<div
class="issuable-info-container"
>
<div
class="issuable-main-info"
>
<div
class="title"
>
<a
class="gl-display-block gl-text-gray-900 gl-font-weight-bold"
href="/h5bp/html5-boilerplate/-/merge_requests/1"
>
Merge request 1
</a>
</div>
<span
class="gl-text-gray-700"
>
!1
</span>
<span
class="issuable-authored gl-text-gray-700 d-inline-flex align-items-center"
class="issuable-authored gl-text-gray-700 gl-display-inline-flex gl-align-items-center"
>
- created by:
<gl-avatar-link-stub
class="d-inline-flex align-items-center ml-2 author-link js-user-link"
class="gl-display-inline-flex gl-align-items-center gl-ml-3 gl-text-gray-900 author-link js-user-link"
data-name="User 1"
data-user-id="1"
href="http://localhost:3000/user-1"
......@@ -58,29 +48,5 @@ exports[`MergeRequest component when there is a merge request matches the snapsh
</span>
</gl-avatar-link-stub>
</span>
</div>
<div
class="issuable-meta"
>
<ul
class="controls"
>
<!---->
<approvers-stub
approvers=""
/>
</ul>
<span
class="gl-text-gray-700"
>
<time>
merged 2 days ago
</time>
</span>
</div>
</div>
</li>
</div>
`;
......@@ -8,7 +8,7 @@ import { createApprovers } from '../mock_data';
describe('MergeRequest component', () => {
let wrapper;
const findMessage = () => wrapper.find('li > span');
const findMessage = () => wrapper.find('[data-testid="approvers"]');
const findCounter = () => wrapper.find('.avatar-counter');
const findAvatarLinks = () => wrapper.findAll(GlAvatarLink);
......@@ -32,8 +32,8 @@ describe('MergeRequest component', () => {
wrapper = createComponent();
});
it('displays the "No approvers" message', () => {
expect(findMessage().text()).toEqual('No approvers');
it('displays the "no approvers" message', () => {
expect(findMessage().text()).toEqual('no approvers');
});
});
......
import { shallowMount } from '@vue/test-utils';
import ComplianceDashboard from 'ee/compliance_dashboard/components/dashboard.vue';
import PipelineStatus from 'ee/compliance_dashboard/components/pipeline_status.vue';
import Approvers from 'ee/compliance_dashboard/components/approvers.vue';
import { createMergeRequests } from '../mock_data';
describe('ComplianceDashboard component', () => {
let wrapper;
const findMergeRequests = () => wrapper.findAll('.merge-request');
const findMergeRequests = () => wrapper.findAll('[data-testid="merge-request"]');
const findTime = () => wrapper.find('time');
const findPipelineStatus = () => wrapper.find(PipelineStatus);
const findApprovers = () => wrapper.find(Approvers);
const createComponent = (props = {}) => {
const createComponent = (props = {}, addPipeline = false) => {
return shallowMount(ComplianceDashboard, {
propsData: {
mergeRequests: createMergeRequests({ count: 2 }),
mergeRequests: createMergeRequests({ count: 2, addPipeline }),
isLastPage: false,
emptyStateSvgPath: 'empty.svg',
...props,
......@@ -19,7 +24,7 @@ describe('ComplianceDashboard component', () => {
stubs: {
MergeRequest: {
props: { mergeRequest: Object },
template: `<div class="merge-request">{{ mergeRequest.title }}</div>`,
template: `<div data-testid="merge-request">{{ mergeRequest.title }}</div>`,
},
},
});
......@@ -41,6 +46,25 @@ describe('ComplianceDashboard component', () => {
it('renders a list of merge requests', () => {
expect(findMergeRequests().length).toEqual(2);
});
describe('pipeline status', () => {
it('does not render if there is no pipeline', () => {
expect(findPipelineStatus().exists()).toBe(false);
});
it('renders if there is a pipeline', () => {
wrapper = createComponent({}, true);
expect(findPipelineStatus().exists()).toBe(true);
});
});
it('renders the approvers list', () => {
expect(findApprovers().exists()).toBe(true);
});
it('renders the "merged at" time', () => {
expect(findTime().text()).toEqual('merged 2 days ago');
});
});
describe('when there are no merge requests', () => {
......
import { shallowMount } from '@vue/test-utils';
import GridColumnHeading from 'ee/compliance_dashboard/components/grid_column_heading.vue';
describe('GridColumnHeading component', () => {
let wrapper;
const createComponent = heading => {
return shallowMount(GridColumnHeading, {
propsData: { heading },
});
};
afterEach(() => {
wrapper.destroy();
});
describe('behaviour', () => {
beforeEach(() => {
wrapper = createComponent('Test heading');
});
it('matches the screenshot', () => {
expect(wrapper.element).toMatchSnapshot();
});
it('has the the heading text', () => {
expect(wrapper.text()).toEqual('Test heading');
});
});
});
import { shallowMount } from '@vue/test-utils';
import { GlAvatarLink } from '@gitlab/ui';
import { GlAvatarLink, GlAvatar } from '@gitlab/ui';
import MergeRequest from 'ee/compliance_dashboard/components/merge_request.vue';
import { createMergeRequest, createPipelineStatus } from '../mock_data';
import { createMergeRequest } from '../mock_data';
describe('MergeRequest component', () => {
let wrapper;
const findCiIcon = () => wrapper.find('.ci-icon');
const findCiLink = () => wrapper.find('.controls').find('a');
const findInfo = () => wrapper.find('.issuable-main-info');
const findTime = () => wrapper.find('time');
const findAuthorAvatarLink = () => wrapper.find('.issuable-authored').find(GlAvatarLink);
const createComponent = mergeRequest => {
......@@ -43,47 +39,23 @@ describe('MergeRequest component', () => {
});
it('renders the title', () => {
expect(
findInfo()
.find('.title')
.text(),
).toEqual(mergeRequest.title);
expect(wrapper.text()).toContain(mergeRequest.title);
});
it('renders the issuable reference', () => {
expect(wrapper.text()).toContain(mergeRequest.issuable_reference);
});
it('renders the author avatar', () => {
expect(
findInfo()
.find('span')
.text(),
).toEqual(mergeRequest.issuable_reference);
findAuthorAvatarLink()
.find(GlAvatar)
.exists(),
).toEqual(true);
});
it('renders the author name', () => {
expect(findAuthorAvatarLink().text()).toEqual(mergeRequest.author.name);
});
it('renders the "merged at" time', () => {
expect(findTime().text()).toEqual('merged 2 days ago');
});
it('does not link to a pipeline', () => {
expect(findCiLink().exists()).toEqual(false);
});
describe('with a pipeline', () => {
const pipeline = createPipelineStatus('success');
beforeEach(() => {
wrapper = createComponent(createMergeRequest({ pipeline }));
});
it('links to the pipeline', () => {
expect(findCiLink().attributes('href')).toEqual(pipeline.details_path);
});
it('renders a CI icon with the pipeline status', () => {
expect(findCiIcon().text()).toEqual(pipeline.group);
});
});
});
});
import { shallowMount } from '@vue/test-utils';
import PipelineStatus from 'ee/compliance_dashboard/components/pipeline_status.vue';
import { createPipelineStatus } from '../mock_data';
describe('PipelineStatus component', () => {
let wrapper;
const findCiIcon = () => wrapper.find('.ci-icon');
const findCiLink = () => wrapper.find('a');
const createComponent = status => {
return shallowMount(PipelineStatus, {
propsData: { status },
stubs: {
CiIcon: {
props: { status: Object },
template: `<div class="ci-icon">{{ status.group }}</div>`,
},
},
});
};
afterEach(() => {
wrapper.destroy();
});
describe('with a pipeline', () => {
const pipeline = createPipelineStatus('success');
beforeEach(() => {
wrapper = createComponent(pipeline);
});
it('links to the pipeline', () => {
expect(findCiLink().attributes('href')).toEqual(pipeline.details_path);
});
it('renders a CI icon with the pipeline status', () => {
expect(findCiIcon().text()).toEqual(pipeline.group);
});
});
});
......@@ -53,8 +53,10 @@ export const createApprovers = count => {
.map((_, id) => createUser(id));
};
export const createMergeRequests = ({ count = 1 } = {}) => {
export const createMergeRequests = ({ count = 1, addPipeline = false } = {}) => {
return Array(count)
.fill()
.map((_, id) => createMergeRequest({ id }));
.map((_, id) =>
createMergeRequest({ id, pipeline: addPipeline ? createPipelineStatus('success') : null }),
);
};
......@@ -2924,9 +2924,6 @@ msgstr ""
msgid "Approved MRs"
msgstr ""
msgid "Approved by: "
msgstr ""
msgid "Approved the current merge request."
msgstr ""
......@@ -15433,9 +15430,6 @@ msgstr ""
msgid "No application_settings found"
msgstr ""
msgid "No approvers"
msgstr ""
msgid "No authentication methods configured."
msgstr ""
......@@ -25109,6 +25103,9 @@ msgstr ""
msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updates"
msgstr ""
msgid "Updating"
msgstr ""
......@@ -27139,6 +27136,9 @@ msgstr ""
msgid "any-approver for the project already exists"
msgstr ""
msgid "approved by: "
msgstr ""
msgid "archived"
msgstr ""
......@@ -27760,7 +27760,7 @@ msgid_plural "merge requests"
msgstr[0] ""
msgstr[1] ""
msgid "merged %{time_ago}"
msgid "merged %{timeAgo}"
msgstr ""
msgid "missing"
......@@ -28093,6 +28093,9 @@ msgstr ""
msgid "new merge request"
msgstr ""
msgid "no approvers"
msgstr ""
msgid "no contributions"
msgstr ""
......
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