Commit 70e0893b authored by Robert Hunt's avatar Robert Hunt Committed by Enrique Alcántara

Changed compliance dashboard to use CSS grid

- Updated components to be more table-like
- Updated styling, removed page style for component
- Fixed approvers to be lowercase like everything else
- Added responsive design
- Updated translations
- Updated tests and snapshots
- Added changelog
parent 74f28ece
<script> <script>
import { sprintf, __ } from '~/locale'; 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'; import { PRESENTABLE_APPROVERS_LIMIT } from '../constants';
export default { export default {
...@@ -8,6 +8,7 @@ export default { ...@@ -8,6 +8,7 @@ export default {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
}, },
components: { components: {
GlAvatarsInline,
GlAvatarLink, GlAvatarLink,
GlAvatar, GlAvatar,
}, },
...@@ -36,15 +37,16 @@ export default { ...@@ -36,15 +37,16 @@ export default {
}); });
}, },
}, },
PRESENTABLE_APPROVERS_LIMIT,
strings: { strings: {
approvedBy: __('Approved by: '), approvedBy: __('approved by: '),
noApprovers: __('No approvers'), noApprovers: __('no approvers'),
}, },
}; };
</script> </script>
<template> <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"> <span class="gl-text-gray-700">
<template v-if="hasApprovers"> <template v-if="hasApprovers">
{{ $options.strings.approvedBy }} {{ $options.strings.approvedBy }}
...@@ -53,6 +55,33 @@ export default { ...@@ -53,6 +55,33 @@ export default {
{{ $options.strings.noApprovers }} {{ $options.strings.noApprovers }}
</template> </template>
</span> </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 <gl-avatar-link
v-for="approver in approversToPresent" v-for="approver in approversToPresent"
:key="approver.id" :key="approver.id"
...@@ -60,7 +89,7 @@ export default { ...@@ -60,7 +89,7 @@ export default {
:href="approver.web_url" :href="approver.web_url"
:data-user-id="approver.id" :data-user-id="approver.id"
:data-name="approver.name" :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 <gl-avatar
:src="approver.avatar_url" :src="approver.avatar_url"
...@@ -74,8 +103,8 @@ export default { ...@@ -74,8 +103,8 @@ export default {
<span <span
v-if="isApproversOverLimit" v-if="isApproversOverLimit"
v-gl-tooltip.top="approversOverLimitString" 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 >+ {{ amountOfApproversOverLimit }}</span
> >
</li> </div>
</template> </template>
<script> <script>
import { __ } from '~/locale'; import { GlTooltipDirective } from '@gitlab/ui';
import MergeRequest from './merge_request.vue'; 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 EmptyState from './empty_state.vue';
import MergeRequest from './merge_request.vue';
import Pagination from './pagination.vue'; import Pagination from './pagination.vue';
import PipelineStatus from './pipeline_status.vue';
import GridColumnHeading from './grid_column_heading.vue';
export default { export default {
name: 'ComplianceDashboard', name: 'ComplianceDashboard',
directives: {
GlTooltip: GlTooltipDirective,
},
components: { components: {
MergeRequest, Approvers,
EmptyState, EmptyState,
GridColumnHeading,
MergeRequest,
Pagination, Pagination,
PipelineStatus,
}, },
mixins: [timeagoMixin],
props: { props: {
emptyStateSvgPath: { emptyStateSvgPath: {
type: String, type: String,
...@@ -22,7 +37,8 @@ export default { ...@@ -22,7 +37,8 @@ export default {
}, },
isLastPage: { isLastPage: {
type: Boolean, type: Boolean,
required: true, required: false,
default: false,
}, },
}, },
computed: { computed: {
...@@ -30,23 +46,70 @@ export default { ...@@ -30,23 +46,70 @@ export default {
return this.mergeRequests.length > 0; 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: { strings: {
heading: __('Compliance Dashboard'), heading: __('Compliance Dashboard'),
subheading: __('Here you will find recent merge request activity'), subheading: __('Here you will find recent merge request activity'),
mergeRequestLabel: __('Merge Request'),
pipelineStatusLabel: __('Pipeline'),
updatesLabel: __('Updates'),
}, },
}; };
</script> </script>
<template> <template>
<div v-if="hasMergeRequests" class="compliance-dashboard"> <div v-if="hasMergeRequests" class="compliance-dashboard">
<header class="my-3"> <header class="gl-my-5">
<h4>{{ $options.strings.heading }}</h4> <h4>{{ $options.strings.heading }}</h4>
<p>{{ $options.strings.subheading }}</p> <p>{{ $options.strings.subheading }}</p>
</header> </header>
<ul class="content-list issuable-list issues-list"> <div class="dashboard-grid">
<merge-request v-for="mr in mergeRequests" :key="mr.id" :merge-request="mr" /> <grid-column-heading :heading="$options.strings.mergeRequestLabel" />
</ul> <grid-column-heading :heading="$options.strings.pipelineStatusLabel" class="gl-text-center" />
<pagination class="my-3" :is-last-page="isLastPage" /> <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> </div>
<empty-state v-else :image-path="emptyStateSvgPath" /> <empty-state v-else :image-path="emptyStateSvgPath" />
</template> </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> <script>
import { sprintf, s__ } from '~/locale'; import { GlAvatar, GlAvatarLink } from '@gitlab/ui';
import { GlAvatar, GlAvatarLink, GlTooltipDirective } from '@gitlab/ui';
import CiIcon from '~/vue_shared/components/ci_icon.vue'; import { s__ } from '~/locale';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import Approvers from './approvers.vue';
export default { export default {
directives: {
GlTooltip: GlTooltipDirective,
},
components: { components: {
CiIcon,
Approvers,
GlAvatar, GlAvatar,
GlAvatarLink, GlAvatarLink,
}, },
mixins: [timeagoMixin],
props: { props: {
mergeRequest: { mergeRequest: {
type: Object, type: Object,
required: true, 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: { strings: {
createdBy: s__('ComplianceDashboard|created by:'), createdBy: s__('ComplianceDashboard|created by:'),
}, },
...@@ -52,56 +21,33 @@ export default { ...@@ -52,56 +21,33 @@ export default {
</script> </script>
<template> <template>
<li class="merge-request"> <div
<div class="issuable-info-container"> class="grid-merge-request gl-border-b-solid gl-border-b-1 gl-border-b-gray-100 gl-p-5"
<div class="issuable-main-info"> data-testid="merge-request"
<div class="title"> >
<a :href="mergeRequest.path"> <a :href="mergeRequest.path" class="gl-display-block gl-text-gray-900 gl-font-weight-bold">
{{ mergeRequest.title }} {{ mergeRequest.title }}
</a> </a>
</div> <span class="gl-text-gray-700">{{ mergeRequest.issuable_reference }}</span>
<span class="gl-text-gray-700"> <span class="issuable-authored gl-text-gray-700 gl-display-inline-flex gl-align-items-center">
{{ mergeRequest.issuable_reference }} - {{ $options.strings.createdBy }}
</span> <gl-avatar-link
<span class="issuable-authored gl-text-gray-700 d-inline-flex align-items-center"> :key="mergeRequest.author.id"
- {{ $options.strings.createdBy }} :title="mergeRequest.author.name"
<gl-avatar-link :href="mergeRequest.author.web_url"
:key="mergeRequest.author.id" :data-user-id="mergeRequest.author.id"
:title="mergeRequest.author.name" :data-name="mergeRequest.author.name"
:href="mergeRequest.author.web_url" class="gl-display-inline-flex gl-align-items-center gl-ml-3 gl-text-gray-900 author-link js-user-link"
:data-user-id="mergeRequest.author.id" >
:data-name="mergeRequest.author.name" <gl-avatar
class="d-inline-flex align-items-center ml-2 author-link js-user-link" :src="mergeRequest.author.avatar_url"
> :entity-id="mergeRequest.author.id"
<gl-avatar :entity-name="mergeRequest.author.name"
:src="mergeRequest.author.avatar_url" :size="16"
:entity-id="mergeRequest.author.id" class="mr-1"
:entity-name="mergeRequest.author.name" />
:size="16" <span>{{ mergeRequest.author.name }}</span>
class="mr-1" </gl-avatar-link>
/> </span>
<span>{{ mergeRequest.author.name }}</span> </div>
</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> </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 ...@@ -31,7 +31,7 @@ RSpec.describe 'Compliance Dashboard', :js do
it 'shows merge requests with details' do it 'shows merge requests with details' do
expect(page).to have_link(merge_request.title) expect(page).to have_link(merge_request.title)
expect(page).to have_content('merged 10 minutes ago') 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 end
end end
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`MergeRequest component when there are approvers matches snapshot 1`] = ` exports[`MergeRequest component when there are approvers matches snapshot 1`] = `
<li <div
class="issuable-status d-flex approvers align-items-center" class="gl-display-flex gl-align-items-center gl-justify-content-end"
data-testid="approvers"
> >
<span <span
class="gl-text-gray-700" class="gl-text-gray-700"
> >
Approved by: approved by:
</span> </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 <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-name="User 0"
data-user-id="0" data-user-id="0"
href="http://localhost:3000/user-0" href="http://localhost:3000/user-0"
...@@ -35,5 +45,5 @@ exports[`MergeRequest component when there are approvers matches snapshot 1`] = ...@@ -35,5 +45,5 @@ exports[`MergeRequest component when there are approvers matches snapshot 1`] =
</gl-link-stub> </gl-link-stub>
<!----> <!---->
</li> </div>
`; `;
...@@ -5,7 +5,7 @@ exports[`ComplianceDashboard component when there are merge requests matches the ...@@ -5,7 +5,7 @@ exports[`ComplianceDashboard component when there are merge requests matches the
class="compliance-dashboard" class="compliance-dashboard"
> >
<header <header
class="my-3" class="gl-my-5"
> >
<h4> <h4>
Compliance Dashboard Compliance Dashboard
...@@ -16,24 +16,80 @@ exports[`ComplianceDashboard component when there are merge requests matches the ...@@ -16,24 +16,80 @@ exports[`ComplianceDashboard component when there are merge requests matches the
</p> </p>
</header> </header>
<ul <div
class="content-list issuable-list issues-list" 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 <div
class="merge-request" data-testid="merge-request"
> >
Merge request 0 Merge request 0
</div> </div>
<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 <div
class="merge-request" 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 Merge request 1
</div> </div>
</ul>
<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 <pagination-stub />
class="my-3"
/>
</div> </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 // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`MergeRequest component when there is a merge request matches the snapshot 1`] = ` exports[`MergeRequest component when there is a merge request matches the snapshot 1`] = `
<li <div
class="merge-request" 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 <a
class="issuable-info-container" class="gl-display-block gl-text-gray-900 gl-font-weight-bold"
href="/h5bp/html5-boilerplate/-/merge_requests/1"
> >
<div
class="issuable-main-info" Merge request 1
>
<div </a>
class="title"
> <span
<a class="gl-text-gray-700"
href="/h5bp/html5-boilerplate/-/merge_requests/1" >
> !1
</span>
Merge request 1
<span
</a> class="issuable-authored gl-text-gray-700 gl-display-inline-flex gl-align-items-center"
</div> >
<span - created by:
class="gl-text-gray-700"
> <gl-avatar-link-stub
class="gl-display-inline-flex gl-align-items-center gl-ml-3 gl-text-gray-900 author-link js-user-link"
!1 data-name="User 1"
data-user-id="1"
</span> href="http://localhost:3000/user-1"
title="User 1"
<span
class="issuable-authored gl-text-gray-700 d-inline-flex align-items-center"
>
- created by:
<gl-avatar-link-stub
class="d-inline-flex align-items-center ml-2 author-link js-user-link"
data-name="User 1"
data-user-id="1"
href="http://localhost:3000/user-1"
title="User 1"
>
<gl-avatar-stub
alt="avatar"
class="mr-1"
entityid="1"
entityname="User 1"
shape="circle"
size="16"
src="https://1"
/>
<span>
User 1
</span>
</gl-avatar-link-stub>
</span>
</div>
<div
class="issuable-meta"
> >
<ul <gl-avatar-stub
class="controls" alt="avatar"
> class="mr-1"
<!----> entityid="1"
entityname="User 1"
<approvers-stub shape="circle"
approvers="" size="16"
/> src="https://1"
</ul> />
<span <span>
class="gl-text-gray-700" User 1
>
<time>
merged 2 days ago
</time>
</span> </span>
</div> </gl-avatar-link-stub>
</div> </span>
</li> </div>
`; `;
...@@ -8,7 +8,7 @@ import { createApprovers } from '../mock_data'; ...@@ -8,7 +8,7 @@ import { createApprovers } from '../mock_data';
describe('MergeRequest component', () => { describe('MergeRequest component', () => {
let wrapper; let wrapper;
const findMessage = () => wrapper.find('li > span'); const findMessage = () => wrapper.find('[data-testid="approvers"]');
const findCounter = () => wrapper.find('.avatar-counter'); const findCounter = () => wrapper.find('.avatar-counter');
const findAvatarLinks = () => wrapper.findAll(GlAvatarLink); const findAvatarLinks = () => wrapper.findAll(GlAvatarLink);
...@@ -32,8 +32,8 @@ describe('MergeRequest component', () => { ...@@ -32,8 +32,8 @@ describe('MergeRequest component', () => {
wrapper = createComponent(); wrapper = createComponent();
}); });
it('displays the "No approvers" message', () => { it('displays the "no approvers" message', () => {
expect(findMessage().text()).toEqual('No approvers'); expect(findMessage().text()).toEqual('no approvers');
}); });
}); });
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import ComplianceDashboard from 'ee/compliance_dashboard/components/dashboard.vue'; 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'; import { createMergeRequests } from '../mock_data';
describe('ComplianceDashboard component', () => { describe('ComplianceDashboard component', () => {
let wrapper; 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, { return shallowMount(ComplianceDashboard, {
propsData: { propsData: {
mergeRequests: createMergeRequests({ count: 2 }), mergeRequests: createMergeRequests({ count: 2, addPipeline }),
isLastPage: false, isLastPage: false,
emptyStateSvgPath: 'empty.svg', emptyStateSvgPath: 'empty.svg',
...props, ...props,
...@@ -19,7 +24,7 @@ describe('ComplianceDashboard component', () => { ...@@ -19,7 +24,7 @@ describe('ComplianceDashboard component', () => {
stubs: { stubs: {
MergeRequest: { MergeRequest: {
props: { mergeRequest: Object }, 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', () => { ...@@ -41,6 +46,25 @@ describe('ComplianceDashboard component', () => {
it('renders a list of merge requests', () => { it('renders a list of merge requests', () => {
expect(findMergeRequests().length).toEqual(2); 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', () => { 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 { 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 MergeRequest from 'ee/compliance_dashboard/components/merge_request.vue';
import { createMergeRequest, createPipelineStatus } from '../mock_data'; import { createMergeRequest } from '../mock_data';
describe('MergeRequest component', () => { describe('MergeRequest component', () => {
let wrapper; 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 findAuthorAvatarLink = () => wrapper.find('.issuable-authored').find(GlAvatarLink);
const createComponent = mergeRequest => { const createComponent = mergeRequest => {
...@@ -43,47 +39,23 @@ describe('MergeRequest component', () => { ...@@ -43,47 +39,23 @@ describe('MergeRequest component', () => {
}); });
it('renders the title', () => { it('renders the title', () => {
expect( expect(wrapper.text()).toContain(mergeRequest.title);
findInfo()
.find('.title')
.text(),
).toEqual(mergeRequest.title);
}); });
it('renders the issuable reference', () => { it('renders the issuable reference', () => {
expect(wrapper.text()).toContain(mergeRequest.issuable_reference);
});
it('renders the author avatar', () => {
expect( expect(
findInfo() findAuthorAvatarLink()
.find('span') .find(GlAvatar)
.text(), .exists(),
).toEqual(mergeRequest.issuable_reference); ).toEqual(true);
}); });
it('renders the author name', () => { it('renders the author name', () => {
expect(findAuthorAvatarLink().text()).toEqual(mergeRequest.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 => { ...@@ -53,8 +53,10 @@ export const createApprovers = count => {
.map((_, id) => createUser(id)); .map((_, id) => createUser(id));
}; };
export const createMergeRequests = ({ count = 1 } = {}) => { export const createMergeRequests = ({ count = 1, addPipeline = false } = {}) => {
return Array(count) return Array(count)
.fill() .fill()
.map((_, id) => createMergeRequest({ id })); .map((_, id) =>
createMergeRequest({ id, pipeline: addPipeline ? createPipelineStatus('success') : null }),
);
}; };
...@@ -2924,9 +2924,6 @@ msgstr "" ...@@ -2924,9 +2924,6 @@ msgstr ""
msgid "Approved MRs" msgid "Approved MRs"
msgstr "" msgstr ""
msgid "Approved by: "
msgstr ""
msgid "Approved the current merge request." msgid "Approved the current merge request."
msgstr "" msgstr ""
...@@ -15427,9 +15424,6 @@ msgstr "" ...@@ -15427,9 +15424,6 @@ msgstr ""
msgid "No application_settings found" msgid "No application_settings found"
msgstr "" msgstr ""
msgid "No approvers"
msgstr ""
msgid "No authentication methods configured." msgid "No authentication methods configured."
msgstr "" msgstr ""
...@@ -25103,6 +25097,9 @@ msgstr "" ...@@ -25103,6 +25097,9 @@ msgstr ""
msgid "Updated to %{linkStart}chart v%{linkEnd}" msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr "" msgstr ""
msgid "Updates"
msgstr ""
msgid "Updating" msgid "Updating"
msgstr "" msgstr ""
...@@ -27130,6 +27127,9 @@ msgstr "" ...@@ -27130,6 +27127,9 @@ msgstr ""
msgid "any-approver for the project already exists" msgid "any-approver for the project already exists"
msgstr "" msgstr ""
msgid "approved by: "
msgstr ""
msgid "archived" msgid "archived"
msgstr "" msgstr ""
...@@ -27751,7 +27751,7 @@ msgid_plural "merge requests" ...@@ -27751,7 +27751,7 @@ msgid_plural "merge requests"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
msgid "merged %{time_ago}" msgid "merged %{timeAgo}"
msgstr "" msgstr ""
msgid "missing" msgid "missing"
...@@ -28084,6 +28084,9 @@ msgstr "" ...@@ -28084,6 +28084,9 @@ msgstr ""
msgid "new merge request" msgid "new merge request"
msgstr "" msgstr ""
msgid "no approvers"
msgstr ""
msgid "no contributions" msgid "no contributions"
msgstr "" 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