diff --git a/ee/app/assets/javascripts/compliance_dashboard/components/dashboard.vue b/ee/app/assets/javascripts/compliance_dashboard/components/dashboard.vue index 4b17a11be068171a13831879757fcd0787564bc5..eac357429a1405b60c903851176189d155f1a2ad 100644 --- a/ee/app/assets/javascripts/compliance_dashboard/components/dashboard.vue +++ b/ee/app/assets/javascripts/compliance_dashboard/components/dashboard.vue @@ -2,7 +2,9 @@ import { GlTabs, GlTab } from '@gitlab/ui'; import Cookies from 'js-cookie'; import { __ } from '~/locale'; +import { DRAWER_Z_INDEX } from '~/lib/utils/constants'; import { COMPLIANCE_TAB_COOKIE_KEY } from '../constants'; +import { mapDashboardToDrawerData } from '../utils'; import MergeRequestDrawer from './drawer.vue'; import EmptyState from './empty_state.vue'; import MergeRequestsGrid from './merge_requests/grid.vue'; @@ -41,7 +43,8 @@ export default { data() { return { showDrawer: false, - drawerData: {}, + drawerMergeRequest: {}, + drawerProject: {}, }; }, computed: { @@ -51,27 +54,33 @@ export default { hasMergeCommitsCsvExportPath() { return this.mergeCommitsCsvExportPath !== ''; }, + drawerMergeRequests() { + return this.mergeRequests.map(mapDashboardToDrawerData); + }, }, methods: { showTabs() { return Cookies.get(COMPLIANCE_TAB_COOKIE_KEY) === 'true'; }, toggleDrawer(mergeRequest) { - if (this.showDrawer && mergeRequest.id === this.drawerData.id) { + if (this.showDrawer && mergeRequest.id === this.drawerMergeRequest.id) { this.closeDrawer(); } else { - this.openDrawer(mergeRequest); + this.openDrawer(this.drawerMergeRequests.find((mr) => mr.id === mergeRequest.id)); } }, - openDrawer(mergeRequest) { + openDrawer(data) { this.showDrawer = true; - this.drawerData = mergeRequest; + this.drawerMergeRequest = data.mergeRequest; + this.drawerProject = data.project; }, closeDrawer() { this.showDrawer = false; - this.drawerData = {}; + this.drawerMergeRequest = {}; + this.drawerProject = {}; }, }, + DRAWER_Z_INDEX, strings: { heading: __('Compliance report'), subheading: __('Here you will find recent merge request activity'), @@ -113,8 +122,9 @@ export default { /> <merge-request-drawer :show-drawer="showDrawer" - :merge-request="drawerData" - z-index="252" + :merge-request="drawerMergeRequest" + :project="drawerProject" + :z-index="$options.DRAWER_Z_INDEX" @close="closeDrawer" /> </div> diff --git a/ee/app/assets/javascripts/compliance_dashboard/components/drawer.vue b/ee/app/assets/javascripts/compliance_dashboard/components/drawer.vue index 978987f4dea87e30bc606b2ebb2d51284acb7dd4..d8130975d99fa1c4de0c8825d1694fafc8790c10 100644 --- a/ee/app/assets/javascripts/compliance_dashboard/components/drawer.vue +++ b/ee/app/assets/javascripts/compliance_dashboard/components/drawer.vue @@ -1,7 +1,6 @@ <script> import { GlDrawer } from '@gitlab/ui'; import { DRAWER_Z_INDEX } from '~/lib/utils/constants'; -import { convertArrayOfObjectsToCamelCase } from '~/lib/utils/common_utils'; import { COMPLIANCE_DRAWER_CONTAINER_CLASS } from '../constants'; import { getContentWrapperHeight } from '../../threat_monitoring/utils'; import BranchPath from './drawer_sections/branch_path.vue'; @@ -26,6 +25,10 @@ export default { type: Object, required: true, }, + project: { + type: Object, + required: true, + }, showDrawer: { type: Boolean, required: false, @@ -34,20 +37,11 @@ export default { }, computed: { hasBranchDetails() { - return this.mergeRequest.source_branch && this.mergeRequest.target_branch; + return this.mergeRequest.sourceBranch && this.mergeRequest.targetBranch; }, drawerHeaderHeight() { return getContentWrapperHeight(COMPLIANCE_DRAWER_CONTAINER_CLASS); }, - committers() { - return convertArrayOfObjectsToCamelCase(this.mergeRequest.committers); - }, - approvedByUsers() { - return convertArrayOfObjectsToCamelCase(this.mergeRequest.approved_by_users); - }, - commenters() { - return convertArrayOfObjectsToCamelCase(this.mergeRequest.participants); - }, }, DRAWER_Z_INDEX, }; @@ -64,22 +58,25 @@ export default { </template> <template v-if="showDrawer" #default> <project - :avatar-url="mergeRequest.project.avatar_url" - :compliance-framework="mergeRequest.compliance_management_framework" - :name="mergeRequest.project.name" - :url="mergeRequest.project.web_url" + :avatar-url="project.avatarUrl" + :compliance-framework="project.complianceFramework" + :name="project.name" + :url="project.webUrl" /> - <reference :path="mergeRequest.path" :reference="mergeRequest.reference" /> + <reference :path="mergeRequest.webUrl" :reference="mergeRequest.reference" /> <branch-path v-if="hasBranchDetails" - :source-branch="mergeRequest.source_branch" - :source-branch-uri="mergeRequest.source_branch_uri" - :target-branch="mergeRequest.target_branch" - :target-branch-uri="mergeRequest.target_branch_uri" + :source-branch="mergeRequest.sourceBranch" + :source-branch-uri="mergeRequest.sourceBranchUri" + :target-branch="mergeRequest.targetBranch" + :target-branch-uri="mergeRequest.targetBranchUri" + /> + <committers :committers="mergeRequest.committers" /> + <reviewers + :approvers="mergeRequest.approvedByUsers" + :commenters="mergeRequest.participants" /> - <committers :committers="committers" /> - <reviewers :approvers="approvedByUsers" :commenters="commenters" /> - <merged-by :merged-by="mergeRequest.merged_by" /> + <merged-by :merged-by="mergeRequest.mergedBy" /> </template> </gl-drawer> </template> diff --git a/ee/app/assets/javascripts/compliance_dashboard/components/drawer_sections/project.vue b/ee/app/assets/javascripts/compliance_dashboard/components/drawer_sections/project.vue index aacbe3267a47c3f7ea32668ae15500156a7a4ef2..d1f537bccfac4ae401bce6b074b83bfd97876702 100644 --- a/ee/app/assets/javascripts/compliance_dashboard/components/drawer_sections/project.vue +++ b/ee/app/assets/javascripts/compliance_dashboard/components/drawer_sections/project.vue @@ -1,5 +1,6 @@ <script> import { GlAvatarLabeled, GlAvatarLink } from '@gitlab/ui'; +import { isEmpty } from 'lodash'; import ComplianceFrameworkLabel from 'ee/vue_shared/components/compliance_framework_label/compliance_framework_label.vue'; import { __ } from '~/locale'; import { DRAWER_AVATAR_SIZE } from '../../constants'; @@ -32,6 +33,11 @@ export default { required: true, }, }, + computed: { + hasComplianceFramework() { + return !isEmpty(this.complianceFramework); + }, + }, i18n: { header: __('Project'), }, @@ -52,7 +58,7 @@ export default { /> </gl-avatar-link> <compliance-framework-label - v-if="complianceFramework" + v-if="hasComplianceFramework" class="gl-ml-3" :name="complianceFramework.name" :color="complianceFramework.color" diff --git a/ee/app/assets/javascripts/compliance_dashboard/components/report.vue b/ee/app/assets/javascripts/compliance_dashboard/components/report.vue index 9997ccd552c46f30b5e2abae89dbc222b64802a4..cf043fe8782c6af10e6fa3d459188e8975cacace 100644 --- a/ee/app/assets/javascripts/compliance_dashboard/components/report.vue +++ b/ee/app/assets/javascripts/compliance_dashboard/components/report.vue @@ -4,9 +4,12 @@ import * as Sentry from '@sentry/browser'; import { __ } from '~/locale'; import { thWidthClass } from '~/lib/utils/table_utility'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; +import { DRAWER_Z_INDEX } from '~/lib/utils/constants'; import complianceViolationsQuery from '../graphql/compliance_violations.query.graphql'; +import { mapViolations } from '../graphql/mappers'; import EmptyState from './empty_state.vue'; import MergeCommitsExportButton from './merge_requests/merge_commits_export_button.vue'; +import MergeRequestDrawer from './drawer.vue'; import ViolationReason from './violations/reason.vue'; export default { @@ -17,6 +20,7 @@ export default { GlLoadingIcon, GlTable, MergeCommitsExportButton, + MergeRequestDrawer, ViolationReason, TimeAgoTooltip, }, @@ -35,6 +39,9 @@ export default { return { queryError: false, violations: [], + showDrawer: false, + drawerMergeRequest: {}, + drawerProject: {}, }; }, apollo: { @@ -46,7 +53,7 @@ export default { }; }, update(data) { - return data?.group?.mergeRequestViolations?.nodes; + return mapViolations(data?.group?.mergeRequestViolations?.nodes); }, error(e) { Sentry.captureException(e); @@ -65,6 +72,30 @@ export default { return this.mergeCommitsCsvExportPath !== ''; }, }, + methods: { + toggleDrawer(rows) { + const { id, mergeRequest, project } = rows[0] || {}; + + if (!mergeRequest || (this.showDrawer && id === this.drawerMergeRequest.id)) { + this.closeDrawer(); + } else { + this.openDrawer(id, mergeRequest, project); + } + }, + openDrawer(id, mergeRequest, project) { + this.showDrawer = true; + this.drawerMergeRequest = mergeRequest; + this.drawerProject = project; + }, + closeDrawer() { + this.showDrawer = false; + // Refs are required by BTable to manipulate the selection + // issue: https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1531 + this.$refs.table.$children[0].clearSelected(); + this.drawerMergeRequest = {}; + this.drawerProject = {}; + }, + }, fields: [ { key: 'severity', @@ -96,6 +127,7 @@ export default { 'Retrieving the compliance report failed. Please refresh the page and try again.', ), }, + DRAWER_Z_INDEX, }; </script> @@ -120,11 +152,17 @@ export default { /> <gl-table v-else-if="hasViolations" + ref="table" :fields="$options.fields" :items="violations" head-variant="white" stacked="lg" + select-mode="single" + selectable + hover + selected-variant="primary" thead-class="gl-border-b-solid gl-border-b-1 gl-border-b-gray-100" + @row-selected="toggleDrawer" > <template #cell(reason)="{ item: { reason, violatingUser } }"> <violation-reason :reason="reason" :user="violatingUser" /> @@ -137,5 +175,12 @@ export default { </template> </gl-table> <empty-state v-else :image-path="emptyStateSvgPath" /> + <merge-request-drawer + :show-drawer="showDrawer" + :merge-request="drawerMergeRequest" + :project="drawerProject" + :z-index="$options.DRAWER_Z_INDEX" + @close="closeDrawer" + /> </section> </template> diff --git a/ee/app/assets/javascripts/compliance_dashboard/graphql/compliance_violations.query.graphql b/ee/app/assets/javascripts/compliance_dashboard/graphql/compliance_violations.query.graphql index 0a476a1bae833f87ea2e4000580a0a5fdc11955e..dbfdc8b6c60561454a4431548091500939940b40 100644 --- a/ee/app/assets/javascripts/compliance_dashboard/graphql/compliance_violations.query.graphql +++ b/ee/app/assets/javascripts/compliance_dashboard/graphql/compliance_violations.query.graphql @@ -59,7 +59,7 @@ query getComplianceViolations($fullPath: ID!) { webUrl } } - ref: reference + reference fullRef: reference(full: true) sourceBranch sourceBranchExists diff --git a/ee/app/assets/javascripts/compliance_dashboard/graphql/mappers.js b/ee/app/assets/javascripts/compliance_dashboard/graphql/mappers.js new file mode 100644 index 0000000000000000000000000000000000000000..3fc482e7484cfaba343fe6fa32eb4558b1fc6e60 --- /dev/null +++ b/ee/app/assets/javascripts/compliance_dashboard/graphql/mappers.js @@ -0,0 +1,15 @@ +export const mapViolations = (nodes = []) => { + return nodes.map((node) => ({ + ...node, + mergeRequest: { + ...node.mergeRequest, + committers: node.mergeRequest.committers?.nodes || [], + approvedByUsers: node.mergeRequest.approvedBy?.nodes || [], + participants: node.mergeRequest.participants?.nodes || [], + }, + project: { + ...node.project, + complianceFramework: node.project?.complianceFrameworks?.nodes[0] || null, + }, + })); +}; diff --git a/ee/app/assets/javascripts/compliance_dashboard/graphql/resolvers.js b/ee/app/assets/javascripts/compliance_dashboard/graphql/resolvers.js index 8bb08eb19cd6dc7fc4fbd3623f584037c7d61192..72e072aca094f53687a8754325412c35e6ac2478 100644 --- a/ee/app/assets/javascripts/compliance_dashboard/graphql/resolvers.js +++ b/ee/app/assets/javascripts/compliance_dashboard/graphql/resolvers.js @@ -32,7 +32,7 @@ export default { title: 'Officiis architecto voluptas ut sit qui qui quisquam sequi consectetur porro.', mergedAt: '2021-11-25T11:56:52.215Z', - webUrl: 'https://gdk.localhost:3443/gitlab-org/gitlab-shell/-/merge_requests/2', + webUrl: 'https://gdk.localhost:3443/gitlab-org/gitlab-shell/-/merge_requests/1', author: { __typename: 'Author', id: 50, @@ -92,8 +92,8 @@ export default { }, ], }, - ref: 'gitlab-shell!2', - fullRef: '!2', + fullRef: 'gitlab-shell!1', + reference: '!1', sourceBranch: 'ut-171ad4e263', sourceBranchExists: false, targetBranch: 'master', @@ -111,6 +111,104 @@ export default { { __typename: 'ComplianceFrameworks', id: 1, + name: 'GDPR', + description: 'General Data Protection Regulation', + color: '#009966', + }, + ], + }, + }, + }, + { + __typename: 'MergeRequestViolation', + id: 2, + severity: 2, + reason: 2, + violatingUser: { + __typename: 'Violator', + id: 50, + name: 'John Doe6', + username: 'user6', + avatarUrl: + 'https://secure.gravatar.com/avatar/7ff9b8111da2e2109e7b66f37aa632cc?s=80&d=identicon', + webUrl: 'https://gdk.localhost:3443/user6', + }, + mergeRequest: { + __typename: 'MergeRequest', + id: 25, + title: + 'Officiis architecto voluptas ut sit qui qui quisquam sequi consectetur porro.', + mergedAt: '2021-11-25T11:56:52.215Z', + webUrl: 'https://gdk.localhost:3443/gitlab-org/gitlab-test/-/merge_requests/2', + author: { + __typename: 'Author', + id: 50, + name: 'John Doe6', + username: 'user6', + avatarUrl: + 'https://secure.gravatar.com/avatar/7ff9b8111da2e2109e7b66f37aa632cc?s=80&d=identicon', + webUrl: 'https://gdk.localhost:3443/user6', + }, + mergedBy: { + __typename: 'MergedBy', + id: 50, + name: 'John Doe6', + username: 'user6', + avatarUrl: + 'https://secure.gravatar.com/avatar/7ff9b8111da2e2109e7b66f37aa632cc?s=80&d=identicon', + webUrl: 'https://gdk.localhost:3443/user6', + }, + committers: { + __typename: 'Committers', + nodes: [], + }, + participants: { + __typename: 'Participants', + nodes: [ + { + __typename: 'User', + id: 50, + name: 'John Doe6', + username: 'user6', + avatarUrl: + 'https://secure.gravatar.com/avatar/7ff9b8111da2e2109e7b66f37aa632cc?s=80&d=identicon', + webUrl: 'https://gdk.localhost:3443/user6', + }, + ], + }, + approvedBy: { + __typename: 'ApprovedBy', + nodes: [ + { + __typename: 'User', + id: 49, + name: 'John Doe5', + username: 'user5', + avatarUrl: + 'https://secure.gravatar.com/avatar/eaafc9b0f704edaf23cd5cf7727df560?s=80&d=identicon', + webUrl: 'https://gdk.localhost:3443/user5', + }, + ], + }, + fullRef: 'gitlab-test!2', + reference: '!2', + sourceBranch: 'ut-171ad4e264', + sourceBranchExists: false, + targetBranch: 'master', + targetBranchExists: true, + }, + project: { + __typename: 'Project', + id: 2, + avatarUrl: null, + name: 'Gitlab Test', + webUrl: 'https://gdk.localhost:3443/gitlab-org/gitlab-test', + complianceFrameworks: { + __typename: 'ComplianceFrameworks', + nodes: [ + { + __typename: 'ComplianceFrameworks', + id: 2, name: 'SOX', description: 'A framework', color: '#00FF00', diff --git a/ee/app/assets/javascripts/compliance_dashboard/utils.js b/ee/app/assets/javascripts/compliance_dashboard/utils.js new file mode 100644 index 0000000000000000000000000000000000000000..7b2a803113236dd50884f69ca47d331a9e4e6adc --- /dev/null +++ b/ee/app/assets/javascripts/compliance_dashboard/utils.js @@ -0,0 +1,15 @@ +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; + +export const mapDashboardToDrawerData = (mergeRequest) => ({ + id: mergeRequest.id, + mergeRequest: { + ...convertObjectPropsToCamelCase(mergeRequest, { deep: true }), + webUrl: mergeRequest.path, + }, + project: { + ...convertObjectPropsToCamelCase(mergeRequest.project, { deep: true }), + complianceFramework: convertObjectPropsToCamelCase( + mergeRequest.compliance_management_framework, + ), + }, +}); diff --git a/ee/spec/frontend/compliance_dashboard/components/__snapshots__/dashboard_spec.js.snap b/ee/spec/frontend/compliance_dashboard/components/__snapshots__/dashboard_spec.js.snap index 20b287093a80378f39c883cc43b2632ec796f9e3..0553f65b014f2b8a1b68f2f12adf38edb420b829 100644 --- a/ee/spec/frontend/compliance_dashboard/components/__snapshots__/dashboard_spec.js.snap +++ b/ee/spec/frontend/compliance_dashboard/components/__snapshots__/dashboard_spec.js.snap @@ -46,6 +46,7 @@ exports[`ComplianceDashboard component when there are merge requests and the sho <merge-request-drawer-stub mergerequest="[object Object]" + project="[object Object]" z-index="252" /> </div> @@ -81,6 +82,7 @@ exports[`ComplianceDashboard component when there are merge requests matches the <merge-request-drawer-stub mergerequest="[object Object]" + project="[object Object]" z-index="252" /> </div> diff --git a/ee/spec/frontend/compliance_dashboard/components/dashboard_spec.js b/ee/spec/frontend/compliance_dashboard/components/dashboard_spec.js index fd1e01fc418b62f1eab15acd000cd0206e776281..b255c600799d25c9dbd255b2c4196d72e667a113 100644 --- a/ee/spec/frontend/compliance_dashboard/components/dashboard_spec.js +++ b/ee/spec/frontend/compliance_dashboard/components/dashboard_spec.js @@ -7,6 +7,7 @@ import MergeRequestDrawer from 'ee/compliance_dashboard/components/drawer.vue'; import MergeRequestGrid from 'ee/compliance_dashboard/components/merge_requests/grid.vue'; import MergeCommitsExportButton from 'ee/compliance_dashboard/components/merge_requests/merge_commits_export_button.vue'; import { COMPLIANCE_TAB_COOKIE_KEY } from 'ee/compliance_dashboard/constants'; +import { mapDashboardToDrawerData } from 'ee/compliance_dashboard/utils'; import { createMergeRequests } from '../mock_data'; describe('ComplianceDashboard component', () => { @@ -109,23 +110,29 @@ describe('ComplianceDashboard component', () => { }); }); - describe('with the merge requests drawer', () => { + describe('with the merge request drawer', () => { beforeEach(() => { wrapper = createComponent(); }); it('opens the drawer', async () => { + const drawerData = mapDashboardToDrawerData(mergeRequests[0]); + await findMergeRequestsGrid().vm.$emit('toggleDrawer', mergeRequests[0]); expect(findMergeRequestsDrawer().props('showDrawer')).toBe(true); - expect(findMergeRequestsDrawer().props('mergeRequest')).toStrictEqual(mergeRequests[0]); + expect(findMergeRequestsDrawer().props('mergeRequest')).toStrictEqual( + drawerData.mergeRequest, + ); + expect(findMergeRequestsDrawer().props('project')).toStrictEqual(drawerData.project); }); it('closes the drawer via the drawer close event', async () => { await findMergeRequestsDrawer().vm.$emit('close'); expect(findMergeRequestsDrawer().props('showDrawer')).toBe(false); - expect(findMergeRequestsDrawer().props('mergeRequest')).toEqual({}); + expect(findMergeRequestsDrawer().props('mergeRequest')).toStrictEqual({}); + expect(findMergeRequestsDrawer().props('project')).toStrictEqual({}); }); it('closes the drawer via the grid toggle event', async () => { @@ -133,15 +140,21 @@ describe('ComplianceDashboard component', () => { await findMergeRequestsGrid().vm.$emit('toggleDrawer', mergeRequests[0]); expect(findMergeRequestsDrawer().props('showDrawer')).toBe(false); - expect(findMergeRequestsDrawer().props('mergeRequest')).toEqual({}); + expect(findMergeRequestsDrawer().props('mergeRequest')).toStrictEqual({}); + expect(findMergeRequestsDrawer().props('project')).toStrictEqual({}); }); it('swaps the drawer when a new merge request is selected', async () => { + const drawerData = mapDashboardToDrawerData(mergeRequests[1]); + await findMergeRequestsGrid().vm.$emit('toggleDrawer', mergeRequests[0]); await findMergeRequestsGrid().vm.$emit('toggleDrawer', mergeRequests[1]); expect(findMergeRequestsDrawer().props('showDrawer')).toBe(true); - expect(findMergeRequestsDrawer().props('mergeRequest')).toStrictEqual(mergeRequests[1]); + expect(findMergeRequestsDrawer().props('mergeRequest')).toStrictEqual( + drawerData.mergeRequest, + ); + expect(findMergeRequestsDrawer().props('project')).toStrictEqual(drawerData.project); }); }); }); diff --git a/ee/spec/frontend/compliance_dashboard/components/drawer_sections/project_spec.js b/ee/spec/frontend/compliance_dashboard/components/drawer_sections/project_spec.js index 781d64c3438728f687a5b5dd06a7c95ba653cade..ab2f8e90b9a9c2f10938b9a6b03242f1c8972f50 100644 --- a/ee/spec/frontend/compliance_dashboard/components/drawer_sections/project_spec.js +++ b/ee/spec/frontend/compliance_dashboard/components/drawer_sections/project_spec.js @@ -21,6 +21,7 @@ describe('Project component', () => { propsData: { name: projectName, url, + complianceFramework: {}, ...props, }, }); diff --git a/ee/spec/frontend/compliance_dashboard/components/drawer_spec.js b/ee/spec/frontend/compliance_dashboard/components/drawer_spec.js index 9632e9c6af89edd1ba19ad55499b02af9497cbca..3d56899cc55b088de3b6f8c8a2ac4ba381e67551 100644 --- a/ee/spec/frontend/compliance_dashboard/components/drawer_spec.js +++ b/ee/spec/frontend/compliance_dashboard/components/drawer_spec.js @@ -1,5 +1,4 @@ import { GlDrawer } from '@gitlab/ui'; -import { convertArrayOfObjectsToCamelCase } from '~/lib/utils/common_utils'; import MergeRequestDrawer from 'ee/compliance_dashboard/components/drawer.vue'; import BranchPath from 'ee/compliance_dashboard/components/drawer_sections/branch_path.vue'; import Committers from 'ee/compliance_dashboard/components/drawer_sections/committers.vue'; @@ -7,10 +6,11 @@ import MergedBy from 'ee/compliance_dashboard/components/drawer_sections/merged_ import Project from 'ee/compliance_dashboard/components/drawer_sections/project.vue'; import Reference from 'ee/compliance_dashboard/components/drawer_sections/reference.vue'; import Reviewers from 'ee/compliance_dashboard/components/drawer_sections/reviewers.vue'; -import { complianceFramework } from 'ee_jest/vue_shared/components/compliance_framework_label/mock_data'; import { getContentWrapperHeight } from 'ee/threat_monitoring/utils'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; -import { createApprovers, createMergeRequests } from '../mock_data'; +import resolvers from 'ee/compliance_dashboard/graphql/resolvers'; +import { DRAWER_Z_INDEX } from '~/lib/utils/constants'; +import { mapViolations } from 'ee/compliance_dashboard/graphql/mappers'; jest.mock('ee/threat_monitoring/utils', () => ({ getContentWrapperHeight: jest.fn(), @@ -18,15 +18,18 @@ jest.mock('ee/threat_monitoring/utils', () => ({ describe('MergeRequestDrawer component', () => { let wrapper; - const mergeRequest = createMergeRequests({ - count: 1, - props: { - compliance_management_framework: complianceFramework, - committers: createApprovers(3), - approved_by_users: createApprovers(2), - participants: createApprovers(3), + const defaultData = mapViolations(resolvers.Query.group().mergeRequestViolations.nodes)[0]; + const data = { + id: defaultData.id, + mergeRequest: { + ...defaultData.mergeRequest, + sourceBranch: null, + sourceBranchUri: null, + targetBranch: null, + targetBranchUri: null, }, - })[0]; + project: defaultData.project, + }; const findTitle = () => wrapper.findByTestId('dashboard-drawer-title'); const findDrawer = () => wrapper.findComponent(GlDrawer); @@ -40,7 +43,8 @@ describe('MergeRequestDrawer component', () => { const createComponent = (props) => { return shallowMountExtended(MergeRequestDrawer, { propsData: { - mergeRequest, + mergeRequest: data.mergeRequest, + project: data.project, ...props, }, }); @@ -61,7 +65,7 @@ describe('MergeRequestDrawer component', () => { it('configures the drawer with header height and z-index', () => { expect(findDrawer().props()).toMatchObject({ headerHeight: mockHeaderHeight, - zIndex: 252, + zIndex: DRAWER_Z_INDEX, }); }); }); @@ -90,22 +94,22 @@ describe('MergeRequestDrawer component', () => { }); it('has the drawer title', () => { - expect(findTitle().text()).toEqual(mergeRequest.title); + expect(findTitle().text()).toEqual(data.mergeRequest.title); }); it('has the project section', () => { expect(findProject().props()).toStrictEqual({ - avatarUrl: mergeRequest.project.avatar_url, - complianceFramework, - name: mergeRequest.project.name, - url: mergeRequest.project.web_url, + avatarUrl: data.project.avatarUrl, + complianceFramework: data.project.complianceFramework, + name: data.project.name, + url: data.project.webUrl, }); }); it('has the reference section', () => { expect(findReference().props()).toStrictEqual({ - path: mergeRequest.path, - reference: mergeRequest.reference, + path: data.mergeRequest.webUrl, + reference: data.mergeRequest.reference, }); }); @@ -115,20 +119,20 @@ describe('MergeRequestDrawer component', () => { it('has the committers section with users array converted to camel case', () => { expect(findCommitters().props()).toStrictEqual({ - committers: convertArrayOfObjectsToCamelCase(mergeRequest.committers), + committers: data.mergeRequest.committers, }); }); it('has the reviewers section with users array converted to camel case', () => { expect(findReviewers().props()).toStrictEqual({ - approvers: convertArrayOfObjectsToCamelCase(mergeRequest.approved_by_users), - commenters: convertArrayOfObjectsToCamelCase(mergeRequest.participants), + approvers: data.mergeRequest.approvedByUsers, + commenters: data.mergeRequest.participants, }); }); it('has the merged by section', () => { expect(findMergedBy().props()).toStrictEqual({ - mergedBy: mergeRequest.merged_by, + mergedBy: data.mergeRequest.mergedBy, }); }); }); @@ -143,11 +147,11 @@ describe('MergeRequestDrawer component', () => { wrapper = createComponent({ showDrawer: true, mergeRequest: { - ...mergeRequest, - source_branch: sourceBranch, - source_branch_uri: sourceBranchUri, - target_branch: targetBranch, - target_branch_uri: targetBranchUri, + ...data.mergeRequest, + sourceBranch, + sourceBranchUri, + targetBranch, + targetBranchUri, }, }); }); diff --git a/ee/spec/frontend/compliance_dashboard/components/report_spec.js b/ee/spec/frontend/compliance_dashboard/components/report_spec.js index 214bf9d2182e5f4458b5633b83e6acb1d49b8872..3ebe45e0700ad8768d2ae0aec8d28c514ec9ae3b 100644 --- a/ee/spec/frontend/compliance_dashboard/components/report_spec.js +++ b/ee/spec/frontend/compliance_dashboard/components/report_spec.js @@ -1,13 +1,16 @@ import { GlAlert, GlLoadingIcon, GlTable } from '@gitlab/ui'; import { mount, shallowMount } from '@vue/test-utils'; import VueApollo from 'vue-apollo'; -import Vue from 'vue'; +import Vue, { nextTick } from 'vue'; import * as Sentry from '@sentry/browser'; import ComplianceReport from 'ee/compliance_dashboard/components/report.vue'; import EmptyState from 'ee/compliance_dashboard/components/empty_state.vue'; +import MergeRequestDrawer from 'ee/compliance_dashboard/components/drawer.vue'; import MergeCommitsExportButton from 'ee/compliance_dashboard/components/merge_requests/merge_commits_export_button.vue'; import ViolationReason from 'ee/compliance_dashboard/components/violations/reason.vue'; import resolvers from 'ee/compliance_dashboard/graphql/resolvers'; +import { mapViolations } from 'ee/compliance_dashboard/graphql/mappers'; +import { stripTypenames } from 'helpers/graphql_helpers'; import waitForPromises from 'helpers/wait_for_promises'; import createMockApollo from 'helpers/mock_apollo_helper'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; @@ -25,6 +28,7 @@ describe('ComplianceReport component', () => { const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); const findErrorMessage = () => wrapper.findComponent(GlAlert); const findViolationsTable = () => wrapper.findComponent(GlTable); + const findMergeRequestDrawer = () => wrapper.findComponent(MergeRequestDrawer); const findEmptyState = () => wrapper.findComponent(EmptyState); const findMergeCommitsExportButton = () => wrapper.findComponent(MergeCommitsExportButton); const findViolationReason = () => wrapper.findComponent(ViolationReason); @@ -33,6 +37,12 @@ describe('ComplianceReport component', () => { const findTableHeaders = () => findViolationsTable().findAll('th'); const findTablesFirstRowData = () => findViolationsTable().findAll('tbody > tr').at(0).findAll('td'); + const findSelectedRows = () => findViolationsTable().findAll('tr.b-table-row-selected'); + + const selectRow = async (idx) => { + await findViolationsTable().findAll('tbody > tr').at(idx).trigger('click'); + await nextTick(); + }; function createMockApolloProvider() { return createMockApollo([], { Query: { group: mockResolver } }); @@ -144,6 +154,61 @@ describe('ComplianceReport component', () => { expect(findTimeAgoTooltip().props('time')).toBe(mergedAt); }); + + describe('with the merge request drawer', () => { + it('opens the drawer', async () => { + const drawerData = mapViolations(mockResolver().mergeRequestViolations.nodes)[0]; + + await selectRow(0); + + expect(findMergeRequestDrawer().props('showDrawer')).toBe(true); + expect(findMergeRequestDrawer().props('mergeRequest')).toStrictEqual( + stripTypenames(drawerData.mergeRequest), + ); + expect(findMergeRequestDrawer().props('project')).toStrictEqual( + stripTypenames(drawerData.project), + ); + }); + + it('closes the drawer via the drawer close event', async () => { + await selectRow(0); + expect(findSelectedRows()).toHaveLength(1); + + await findMergeRequestDrawer().vm.$emit('close'); + + expect(findMergeRequestDrawer().props('showDrawer')).toBe(false); + expect(findSelectedRows()).toHaveLength(0); + expect(findMergeRequestDrawer().props('mergeRequest')).toStrictEqual({}); + expect(findMergeRequestDrawer().props('project')).toStrictEqual({}); + }); + + it('closes the drawer via the row-selected event', async () => { + await selectRow(0); + + expect(findSelectedRows()).toHaveLength(1); + + await selectRow(0); + + expect(findMergeRequestDrawer().props('showDrawer')).toBe(false); + expect(findMergeRequestDrawer().props('mergeRequest')).toStrictEqual({}); + expect(findMergeRequestDrawer().props('project')).toStrictEqual({}); + }); + + it('swaps the drawer when a new row is selected', async () => { + const drawerData = mapViolations(mockResolver().mergeRequestViolations.nodes)[1]; + + await selectRow(0); + await selectRow(1); + + expect(findMergeRequestDrawer().props('showDrawer')).toBe(true); + expect(findMergeRequestDrawer().props('mergeRequest')).toStrictEqual( + stripTypenames(drawerData.mergeRequest), + ); + expect(findMergeRequestDrawer().props('project')).toStrictEqual( + stripTypenames(drawerData.project), + ); + }); + }); }); describe('when there are no violations', () => {