Commit c5006455 authored by Robert Hunt's avatar Robert Hunt Committed by Andrew Fontaine

Created new branch details component for the compliance dashboard

The new component is only shown when we have at least the source and
target branch names. If we have that then we will render at least the
names of each branch

If we also have URI's we will show the branch names as links instead
parent 46695c58
<script>
import { GlLink, GlSprintf, GlTooltipDirective, GlTruncate } from '@gitlab/ui';
import { __ } from '~/locale';
export default {
directives: {
GlTooltip: GlTooltipDirective,
},
components: {
GlLink,
GlSprintf,
GlTruncate,
},
props: {
sourceBranch: {
type: Object,
required: true,
},
targetBranch: {
type: Object,
required: true,
},
},
strings: {
branchDetails: __('%{sourceBranch} into %{targetBranch}'),
},
};
</script>
<template>
<div class="gl-display-flex gl-align-items-center gl-justify-content-end">
<gl-sprintf :message="this.$options.strings.branchDetails">
<template #sourceBranch>
<span class="gl-mr-2 gl-min-w-0">
<gl-link v-if="sourceBranch.uri" :href="targetBranch.uri" data-testid="source-branch-uri">
<gl-truncate
v-gl-tooltip
:title="sourceBranch.name"
:text="sourceBranch.name"
position="middle"
/>
</gl-link>
<gl-truncate
v-else
v-gl-tooltip
:title="sourceBranch.name"
:text="sourceBranch.name"
position="middle"
/>
</span>
</template>
<template #targetBranch>
<span class="gl-ml-2 gl-min-w-0">
<gl-link v-if="targetBranch.uri" :href="targetBranch.uri" data-testid="target-branch-uri">
<gl-truncate
v-gl-tooltip
:title="targetBranch.name"
:text="targetBranch.name"
position="middle"
/>
</gl-link>
<gl-truncate
v-else
v-gl-tooltip
:title="targetBranch.name"
:text="targetBranch.name"
position="middle"
/>
</span>
</template>
</gl-sprintf>
</div>
</template>
......@@ -7,6 +7,7 @@ import timeagoMixin from '~/vue_shared/mixins/timeago';
import ApprovalStatus from './approval_status.vue';
import Approvers from './approvers.vue';
import BranchDetails from './branch_details.vue';
import MergeRequest from './merge_request.vue';
import PipelineStatus from './pipeline_status.vue';
import GridColumnHeading from '../shared/grid_column_heading.vue';
......@@ -19,6 +20,7 @@ export default {
components: {
ApprovalStatus,
Approvers,
BranchDetails,
GridColumnHeading,
MergeRequest,
PipelineStatus,
......@@ -51,6 +53,9 @@ export default {
hasStatus(status) {
return !isEmpty(status);
},
hasBranchDetails(mergeRequest) {
return mergeRequest.target_branch && mergeRequest.source_branch;
},
},
strings: {
mergeRequestLabel: __('Merge Request'),
......@@ -105,6 +110,17 @@ export default {
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" />
<branch-details
v-if="hasBranchDetails(mergeRequest)"
:source-branch="{
name: mergeRequest.source_branch,
uri: mergeRequest.source_branch_uri,
}"
:target-branch="{
name: mergeRequest.target_branch,
uri: mergeRequest.target_branch_uri,
}"
/>
<span class="gl-text-gray-700">
<time v-gl-tooltip.bottom="timeTooltip(mergeRequest.merged_at)">{{
timeAgoString(mergeRequest.merged_at)
......
......@@ -3,6 +3,6 @@
min-width: 550px;
.dashboard-grid {
grid-template-columns: 1fr auto auto auto;
grid-template-columns: 1fr auto auto 35%;
}
}
---
title: Add source and destination branch data to Compliance Dashboard
merge_request: 37628
author:
type: added
......@@ -49,6 +49,8 @@ exports[`MergeRequestsGrid component when intialized matches the snapshot 1`] =
approvers=""
/>
<!---->
<span
class="gl-text-gray-700"
>
......@@ -82,6 +84,8 @@ exports[`MergeRequestsGrid component when intialized matches the snapshot 1`] =
approvers=""
/>
<!---->
<span
class="gl-text-gray-700"
>
......
import { mount } from '@vue/test-utils';
import { GlLink } from '@gitlab/ui';
import BranchDetails from 'ee/compliance_dashboard/components/merge_requests/branch_details.vue';
describe('BranchDetails component', () => {
let wrapper;
// The truncate component adds left-to-right marks into the text that we have to remove
const getText = () => wrapper.text().replace(/\u200E/gi, '');
const linkExists = testId => wrapper.find(`[data-testid="${testId}"]`).exists();
const createComponent = ({ sourceUri = '', targetUri = '' } = {}) => {
return mount(BranchDetails, {
propsData: {
sourceBranch: {
name: 'feature',
uri: sourceUri,
},
targetBranch: {
name: 'master',
uri: targetUri,
},
},
});
};
afterEach(() => {
wrapper.destroy();
});
describe('with branch details', () => {
describe('and no branch URIs', () => {
beforeEach(() => {
wrapper = createComponent();
});
it('has no links', () => {
expect(wrapper.find(GlLink).exists()).toBe(false);
});
it('has the correct text', () => {
expect(getText()).toEqual('feature into master');
});
});
describe('and one branch URI', () => {
beforeEach(() => {
wrapper = createComponent({ targetUri: '/master-uri' });
});
it('has one link', () => {
expect(wrapper.findAll(GlLink)).toHaveLength(1);
});
it('has a link to the target branch', () => {
expect(linkExists('target-branch-uri')).toBe(true);
});
it('has the correct text', () => {
expect(getText()).toEqual('feature into master');
});
});
describe('and both branch URIs', () => {
beforeEach(() => {
wrapper = createComponent({ sourceUri: '/feature-uri', targetUri: '/master-uri' });
});
it('has two links', () => {
expect(wrapper.findAll(GlLink)).toHaveLength(2);
});
it('has a link to the source branch', () => {
expect(linkExists('source-branch-uri')).toBe(true);
});
it('has a link to the target branch', () => {
expect(linkExists('target-branch-uri')).toBe(true);
});
it('has the correct text', () => {
expect(getText()).toEqual('feature into master');
});
});
});
});
......@@ -2,9 +2,10 @@ import { shallowMount } from '@vue/test-utils';
import MergeRequestsGrid from 'ee/compliance_dashboard/components/merge_requests/grid.vue';
import ApprovalStatus from 'ee/compliance_dashboard/components/merge_requests/approval_status.vue';
import BranchDetails from 'ee/compliance_dashboard/components/merge_requests/branch_details.vue';
import PipelineStatus from 'ee/compliance_dashboard/components/merge_requests/pipeline_status.vue';
import Approvers from 'ee/compliance_dashboard/components/merge_requests/approvers.vue';
import { createMergeRequests } from '../../mock_data';
import { createMergeRequests, createPipelineStatus } from '../../mock_data';
describe('MergeRequestsGrid component', () => {
let wrapper;
......@@ -14,11 +15,12 @@ describe('MergeRequestsGrid component', () => {
const findApprovalStatus = () => wrapper.find(ApprovalStatus);
const findPipelineStatus = () => wrapper.find(PipelineStatus);
const findApprovers = () => wrapper.find(Approvers);
const findBranchDetails = () => wrapper.find(BranchDetails);
const createComponent = (options = {}) => {
const createComponent = (mergeRequestProps = {}) => {
return shallowMount(MergeRequestsGrid, {
propsData: {
mergeRequests: createMergeRequests({ count: 2, options }),
mergeRequests: createMergeRequests({ count: 2, props: mergeRequestProps }),
isLastPage: false,
},
stubs: {
......@@ -53,7 +55,7 @@ describe('MergeRequestsGrid component', () => {
});
it('renders if there is an approval status', () => {
wrapper = createComponent({ approvalStatus: 'success' });
wrapper = createComponent({ approval_status: 'success' });
expect(findApprovalStatus().exists()).toBe(true);
});
});
......@@ -64,11 +66,22 @@ describe('MergeRequestsGrid component', () => {
});
it('renders if there is a pipeline', () => {
wrapper = createComponent({ addPipeline: true });
wrapper = createComponent({ pipeline_status: createPipelineStatus('success') });
expect(findPipelineStatus().exists()).toBe(true);
});
});
describe('branch details', () => {
it('does not render if there are no branch details', () => {
expect(findBranchDetails().exists()).toBe(false);
});
it('renders if there are branch details', () => {
wrapper = createComponent({ target_branch: 'master', source_branch: 'feature' });
expect(findBranchDetails().exists()).toBe(true);
});
});
it('renders the approvers list', () => {
expect(findApprovers().exists()).toBe(true);
});
......
......@@ -13,7 +13,7 @@ const createUser = id => ({
web_url: `http://localhost:3000/user-${id}`,
});
export const createMergeRequest = ({ id = 1, pipeline, approvers, approvalStatus } = {}) => {
export const createMergeRequest = ({ id = 1, props } = {}) => {
const mergeRequest = {
id,
approved_by_users: [],
......@@ -26,19 +26,7 @@ export const createMergeRequest = ({ id = 1, pipeline, approvers, approvalStatus
mergeRequest.author = createUser(id);
if (pipeline) {
mergeRequest.pipeline_status = pipeline;
}
if (approvers) {
mergeRequest.approved_by_users = approvers;
}
if (approvalStatus) {
mergeRequest.approval_status = approvalStatus;
}
return mergeRequest;
return { ...mergeRequest, ...props };
};
export const createPipelineStatus = status => ({
......@@ -59,14 +47,13 @@ export const createApprovers = count => {
.map((_, id) => createUser(id));
};
export const createMergeRequests = ({ count = 1, options = {} } = {}) => {
export const createMergeRequests = ({ count = 1, props = {} } = {}) => {
return Array(count)
.fill()
.map((_, id) =>
createMergeRequest({
id,
approvalStatus: options.approvalStatus,
pipeline: options.addPipeline ? createPipelineStatus('success') : null,
props,
}),
);
};
......@@ -670,6 +670,9 @@ msgstr ""
msgid "%{size} bytes"
msgstr ""
msgid "%{sourceBranch} into %{targetBranch}"
msgstr ""
msgid "%{spammable_titlecase} was submitted to Akismet successfully."
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