Commit 8335e88c authored by Martin Wortschack's avatar Martin Wortschack

Merge branch '217939-add-mr-approval-settings-column-to-the-compliance-dashboard' into 'master'

Add MR approval status column to the compliance dashboard

See merge request gitlab-org/gitlab!36589
parents 160bd002 07cc58d4
<script>
import { GlLink, GlTooltipDirective } from '@gitlab/ui';
import { s__ } from '~/locale';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
const APPROVAL_WARNING_ICON = 'success-with-warnings';
export default {
directives: {
GlTooltip: GlTooltipDirective,
},
components: {
CiIcon,
GlLink,
},
props: {
status: {
type: String,
required: true,
},
},
computed: {
tooltip() {
return this.$options.tooltips[this.status];
},
icon() {
return `status_${this.status}`;
},
group() {
const { status } = this;
if (status === 'warning') {
return APPROVAL_WARNING_ICON;
}
return status;
},
},
tooltips: {
success: s__('ApprovalStatusTooltip|Adheres to separation of duties'),
warning: s__('ApprovalStatusTooltip|At least one rule does not adhere to separation of duties'),
failed: s__('ApprovalStatusTooltip|Fails to adhere to separation of duties'),
},
};
</script>
<template>
<gl-link
href="https://docs.gitlab.com/ee/user/compliance/compliance_dashboard/#approval-status-and-separation-of-duties"
>
<ci-icon v-gl-tooltip.left="tooltip" class="gl-display-flex" :status="{ icon, group }" />
</gl-link>
</template>
......@@ -5,6 +5,7 @@ import { isEmpty } from 'lodash';
import { sprintf, __, s__ } from '~/locale';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import ApprovalStatus from './approval_status.vue';
import Approvers from './approvers.vue';
import EmptyState from './empty_state.vue';
import MergeRequest from './merge_request.vue';
......@@ -18,6 +19,7 @@ export default {
GlTooltip: GlTooltipDirective,
},
components: {
ApprovalStatus,
Approvers,
EmptyState,
GridColumnHeading,
......@@ -58,7 +60,7 @@ export default {
timeTooltip(mergedAt) {
return this.tooltipTitle(mergedAt);
},
hasPipeline(status) {
hasStatus(status) {
return !isEmpty(status);
},
},
......@@ -66,6 +68,7 @@ export default {
heading: __('Compliance Dashboard'),
subheading: __('Here you will find recent merge request activity'),
mergeRequestLabel: __('Merge Request'),
approvalStatusLabel: __('Approval Status'),
pipelineStatusLabel: __('Pipeline'),
updatesLabel: __('Updates'),
},
......@@ -80,18 +83,28 @@ export default {
</header>
<div class="dashboard-grid">
<grid-column-heading :heading="$options.strings.mergeRequestLabel" />
<grid-column-heading :heading="$options.strings.approvalStatusLabel" class="gl-text-center" />
<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, 'approvalStatus')"
class="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"
>
<approval-status
v-if="hasStatus(mergeRequest.approval_status)"
:status="mergeRequest.approval_status"
/>
</div>
<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)"
v-if="hasStatus(mergeRequest.pipeline_status)"
:status="mergeRequest.pipeline_status"
/>
</div>
......
......@@ -4,7 +4,7 @@
.dashboard-grid {
display: grid;
grid-template-columns: 1fr auto auto;
grid-template-columns: 1fr auto auto auto;
grid-template-rows: auto;
}
......
---
title: Add MR approval settings column to the compliance dashboard
merge_request: 36589
author:
type: added
......@@ -23,6 +23,11 @@ exports[`ComplianceDashboard component when there are merge requests matches the
heading="Merge Request"
/>
<grid-column-heading-stub
class="gl-text-center"
heading="Approval Status"
/>
<grid-column-heading-stub
class="gl-text-center"
heading="Pipeline"
......@@ -39,6 +44,12 @@ exports[`ComplianceDashboard component when there are merge requests matches the
Merge request 0
</div>
<div
class="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="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"
>
......@@ -66,6 +77,12 @@ exports[`ComplianceDashboard component when there are merge requests matches the
Merge request 1
</div>
<div
class="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="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"
>
......
import { shallowMount } from '@vue/test-utils';
import { GlLink } from '@gitlab/ui';
import ApprovalStatus from 'ee/compliance_dashboard/components/approval_status.vue';
describe('ApprovalStatus component', () => {
let wrapper;
const findIcon = () => wrapper.find('.ci-icon');
const findLink = () => wrapper.find(GlLink);
const createComponent = status => {
return shallowMount(ApprovalStatus, {
propsData: { status },
stubs: {
CiIcon: {
props: { status: Object },
template: `<div class="ci-icon">{{ status.icon }}</div>`,
},
},
});
};
afterEach(() => {
wrapper.destroy();
});
describe('with an approval status', () => {
const approvalStatus = 'success';
beforeEach(() => {
wrapper = createComponent(approvalStatus);
});
it('links to the approval status', () => {
expect(findLink().attributes('href')).toEqual(
'https://docs.gitlab.com/ee/user/compliance/compliance_dashboard/#approval-status-and-separation-of-duties',
);
});
it('renders an icon with the approval status', () => {
expect(findIcon().text()).toEqual(`status_${approvalStatus}`);
});
describe.each`
status | icon | group | tooltip
${'success'} | ${'status_success'} | ${'success'} | ${'Adheres to separation of duties'}
${'warning'} | ${'status_warning'} | ${'success-with-warnings'} | ${'At least one rule does not adhere to separation of duties'}
${'failed'} | ${'status_failed'} | ${'failed'} | ${'Fails to adhere to separation of duties'}
`('returns the correct values for $status', ({ status, icon, group, tooltip }) => {
beforeEach(() => {
wrapper = createComponent(status);
});
it('returns the correct icon', () => {
expect(wrapper.vm.icon).toEqual(icon);
});
it('returns the correct group', () => {
expect(wrapper.vm.group).toEqual(group);
});
it('returns the correct tooltip', () => {
expect(wrapper.vm.tooltip).toEqual(tooltip);
});
});
});
});
import { shallowMount } from '@vue/test-utils';
import ComplianceDashboard from 'ee/compliance_dashboard/components/dashboard.vue';
import ApprovalStatus from 'ee/compliance_dashboard/components/approval_status.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';
......@@ -10,13 +11,14 @@ describe('ComplianceDashboard component', () => {
const findMergeRequests = () => wrapper.findAll('[data-testid="merge-request"]');
const findTime = () => wrapper.find('time');
const findApprovalStatus = () => wrapper.find(ApprovalStatus);
const findPipelineStatus = () => wrapper.find(PipelineStatus);
const findApprovers = () => wrapper.find(Approvers);
const createComponent = (props = {}, addPipeline = false) => {
const createComponent = (props = {}, options = {}) => {
return shallowMount(ComplianceDashboard, {
propsData: {
mergeRequests: createMergeRequests({ count: 2, addPipeline }),
mergeRequests: createMergeRequests({ count: 2, options }),
isLastPage: false,
emptyStateSvgPath: 'empty.svg',
...props,
......@@ -47,13 +49,24 @@ describe('ComplianceDashboard component', () => {
expect(findMergeRequests().length).toEqual(2);
});
describe('approval status', () => {
it('does not render if there is no approval status', () => {
expect(findApprovalStatus().exists()).toBe(false);
});
it('renders if there is an approval status', () => {
wrapper = createComponent({}, { approvalStatus: 'success' });
expect(findApprovalStatus().exists()).toBe(true);
});
});
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);
wrapper = createComponent({}, { addPipeline: true });
expect(findPipelineStatus().exists()).toBe(true);
});
});
......
......@@ -13,7 +13,7 @@ const createUser = id => ({
web_url: `http://localhost:3000/user-${id}`,
});
export const createMergeRequest = ({ id = 1, pipeline, approvers } = {}) => {
export const createMergeRequest = ({ id = 1, pipeline, approvers, approvalStatus } = {}) => {
const mergeRequest = {
id,
approved_by_users: [],
......@@ -29,9 +29,15 @@ export const createMergeRequest = ({ id = 1, pipeline, approvers } = {}) => {
if (pipeline) {
mergeRequest.pipeline_status = pipeline;
}
if (approvers) {
mergeRequest.approved_by_users = approvers;
}
if (approvalStatus) {
mergeRequest.approval_status = approvalStatus;
}
return mergeRequest;
};
......@@ -53,10 +59,14 @@ export const createApprovers = count => {
.map((_, id) => createUser(id));
};
export const createMergeRequests = ({ count = 1, addPipeline = false } = {}) => {
export const createMergeRequests = ({ count = 1, options = {} } = {}) => {
return Array(count)
.fill()
.map((_, id) =>
createMergeRequest({ id, pipeline: addPipeline ? createPipelineStatus('success') : null }),
createMergeRequest({
id,
approvalStatus: options.approvalStatus,
pipeline: options.addPipeline ? createPipelineStatus('success') : null,
}),
);
};
......@@ -2937,6 +2937,9 @@ msgstr ""
msgid "Applying suggestions..."
msgstr ""
msgid "Approval Status"
msgstr ""
msgid "Approval rules"
msgstr ""
......@@ -2984,6 +2987,15 @@ msgstr ""
msgid "ApprovalRule|e.g. QA, Security, etc."
msgstr ""
msgid "ApprovalStatusTooltip|Adheres to separation of duties"
msgstr ""
msgid "ApprovalStatusTooltip|At least one rule does not adhere to separation of duties"
msgstr ""
msgid "ApprovalStatusTooltip|Fails to adhere to separation of duties"
msgstr ""
msgid "Approvals|Section: %section"
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