Commit c9ef3f77 authored by Jiaan Louw's avatar Jiaan Louw Committed by Jose Ivan Vargas

Update compliance report to fetch violations from GraphQL

Remove the local resolver and fetch the merge request compliance
violations from our GraphQL API.
parent 40b9f8bc
......@@ -2,10 +2,9 @@ import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import { parseBoolean } from '~/lib/utils/common_utils';
import { queryToObject } from '~/lib/utils/url_utility';
import resolvers from './graphql/resolvers';
import ComplianceDashboard from './components/dashboard.vue';
import ComplianceReport from './components/report.vue';
import { buildDefaultFilterParams } from './utils';
export default () => {
const el = document.getElementById('js-compliance-report');
......@@ -22,10 +21,10 @@ export default () => {
Vue.use(VueApollo);
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(resolvers),
defaultClient: createDefaultClient(),
});
const defaultQuery = queryToObject(window.location.search, { gatherArrays: true });
const defaultFilterParams = buildDefaultFilterParams(window.location.search);
return new Vue({
el,
......@@ -35,7 +34,7 @@ export default () => {
props: {
mergeCommitsCsvExportPath,
groupPath,
defaultQuery,
defaultFilterParams,
},
}),
});
......
......@@ -63,7 +63,7 @@ export default {
<template>
<div>
<gl-dropdown split>
<gl-dropdown data-testid="merge-commit-dropdown" split>
<template #button-content>
<gl-button
ref="listMergeCommitsButton"
......
......@@ -8,7 +8,7 @@ import { DRAWER_Z_INDEX } from '~/lib/utils/constants';
import UrlSync from '~/vue_shared/components/url_sync.vue';
import { helpPagePath } from '~/helpers/help_page_helper';
import SeverityBadge from 'ee/vue_shared/security_reports/components/severity_badge.vue';
import complianceViolationsQuery from '../graphql/compliance_violations.query.graphql';
import getComplianceViolationsQuery from '../graphql/compliance_violations.query.graphql';
import { mapViolations } from '../graphql/mappers';
import { DEFAULT_SORT, GRAPHQL_PAGE_SIZE } from '../constants';
import { parseViolationsQueryFilter } from '../utils';
......@@ -44,17 +44,17 @@ export default {
type: String,
required: true,
},
defaultQuery: {
defaultFilterParams: {
type: Object,
required: true,
},
},
data() {
const sortParam = this.defaultQuery.sort || DEFAULT_SORT;
const sortParam = this.defaultFilterParams.sort || DEFAULT_SORT;
const { sortBy, sortDesc } = sortStringToObject(sortParam);
return {
urlQuery: { ...this.defaultQuery },
urlQuery: { ...this.defaultFilterParams },
queryError: false,
violations: {
list: [],
......@@ -74,11 +74,11 @@ export default {
},
apollo: {
violations: {
query: complianceViolationsQuery,
query: getComplianceViolationsQuery,
variables() {
return {
fullPath: this.groupPath,
filter: parseViolationsQueryFilter(this.urlQuery),
filters: parseViolationsQueryFilter(this.urlQuery),
sort: this.sortParam,
first: GRAPHQL_PAGE_SIZE,
...this.paginationCursors,
......@@ -140,12 +140,19 @@ export default {
this.drawerProject = {};
},
updateUrlQuery({ projectIds = [], ...rest }) {
this.resetPagination();
this.urlQuery = {
// Clear the URL param when the id array is empty
projectIds: projectIds?.length > 0 ? projectIds : null,
...rest,
};
},
resetPagination() {
this.paginationCursors = {
before: null,
after: null,
};
},
loadPrevPage(startCursor) {
this.paginationCursors = {
before: startCursor,
......@@ -234,7 +241,7 @@ export default {
</header>
<violation-filter
:group-path="groupPath"
:default-query="defaultQuery"
:default-query="defaultFilterParams"
@filters-changed="updateUrlQuery"
/>
<gl-table
......
......@@ -2,7 +2,7 @@
import { GlDaterangePicker } from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import ProjectsDropdownFilter from '~/analytics/shared/components/projects_dropdown_filter.vue';
import { getDateInPast, pikadayToString, parsePikadayDate } from '~/lib/utils/datetime_utility';
import { pikadayToString, parsePikadayDate } from '~/lib/utils/datetime_utility';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { __ } from '~/locale';
import getGroupProjects from '../../graphql/violation_group_projects.query.graphql';
......@@ -38,12 +38,10 @@ export default {
},
computed: {
defaultStartDate() {
const startDate = this.defaultQuery.mergedAfter;
return startDate ? parsePikadayDate(startDate) : getDateInPast(CURRENT_DATE, 30);
return parsePikadayDate(this.defaultQuery.mergedAfter);
},
defaultEndDate() {
const endDate = this.defaultQuery.mergedBefore;
return endDate ? parsePikadayDate(endDate) : CURRENT_DATE;
return parsePikadayDate(this.defaultQuery.mergedBefore);
},
},
async created() {
......@@ -103,6 +101,7 @@ export default {
}}</label>
<projects-dropdown-filter
v-if="showProjectFilter"
data-testid="violations-project-dropdown"
class="gl-mb-2 gl-lg-mb-0 compliance-filter-dropdown-input"
:group-namespace="groupPath"
:query-params="$options.projectsFilterParams"
......@@ -115,6 +114,7 @@ export default {
<gl-daterange-picker
class="gl-display-flex gl-w-full gl-mb-5"
data-testid="violations-date-range-picker"
:default-start-date="defaultStartDate"
:default-end-date="defaultEndDate"
:default-max-date="$options.defaultMaxDate"
......
#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
# TODO: Add the correct filter type once it has been added in https://gitlab.com/gitlab-org/gitlab/-/issues/347325
query getComplianceViolations(
$fullPath: ID!
$filter: Object
$sort: String
$filters: ComplianceViolationInput
$sort: ComplianceViolationSort
$after: String
$before: String
$first: Int
) {
group(
fullPath: $fullPath
filter: $filter
sort: $sort
after: $after
before: $before
first: $first
) @client {
group(fullPath: $fullPath) {
id
mergeRequestViolations {
mergeRequestViolations(
filters: $filters
sort: $sort
after: $after
before: $before
first: $first
) {
nodes {
id
severityLevel
......@@ -76,7 +74,7 @@ query getComplianceViolations(
webUrl
}
}
ref
ref: reference
fullRef: reference(full: true)
sourceBranch
sourceBranchExists
......
// Note: This is mocking the server response until https://gitlab.com/gitlab-org/gitlab/-/issues/342897 is complete
// These values do not need to be translatable as it will remain behind a development feature flag
// until that issue is merged
/* eslint-disable @gitlab/require-i18n-strings */
export default {
Query: {
group() {
return {
__typename: 'Group',
id: 1,
mergeRequestViolations: {
__typename: 'MergeRequestViolations',
nodes: [
{
__typename: 'MergeRequestViolation',
id: 1,
severityLevel: 'HIGH',
reason: 'APPROVED_BY_COMMITTER',
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: 24,
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/1',
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',
},
mergeUser: {
__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',
},
{
__typename: 'ApprovedBy',
id: 48,
name: 'John Doe4',
username: 'user4',
avatarUrl:
'https://secure.gravatar.com/avatar/5c8881fc63652c86cd4b23101268cf84?s=80&d=identicon',
webUrl: 'https://gdk.localhost:3443/user4',
},
],
},
fullRef: 'gitlab-shell!1',
ref: '!1',
sourceBranch: 'ut-171ad4e263',
sourceBranchExists: false,
targetBranch: 'master',
targetBranchExists: true,
project: {
__typename: 'Project',
id: 1,
avatarUrl: null,
name: 'Gitlab Shell',
webUrl: 'https://gdk.localhost:3443/gitlab-org/gitlab-shell',
complianceFrameworks: {
__typename: 'ComplianceFrameworks',
nodes: [
{
__typename: 'ComplianceFrameworks',
id: 1,
name: 'GDPR',
description: 'General Data Protection Regulation',
color: '#009966',
},
],
},
},
},
},
{
__typename: 'MergeRequestViolation',
id: 2,
severityLevel: 'HIGH',
reason: 'APPROVED_BY_INSUFFICIENT_USERS',
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',
},
mergeUser: {
__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',
ref: '!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',
},
],
},
},
},
},
],
pageInfo: {
__typename: 'PageInfo',
hasNextPage: true,
hasPreviousPage: false,
startCursor: 'eyJpZCI6IjMzMjkwNjMzIn0',
endCursor: 'eyJpZCI6IjMzMjkwNjI5In0',
},
},
};
},
},
};
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { convertToGraphQLIds } from '~/graphql_shared/utils';
import { TYPE_PROJECT } from '~/graphql_shared/constants';
import { formatDate } from '~/lib/utils/datetime_utility';
import { formatDate, getDateInPast, pikadayToString } from '~/lib/utils/datetime_utility';
import { ISO_SHORT_FORMAT } from '~/vue_shared/constants';
import { queryToObject } from '~/lib/utils/url_utility';
import { CURRENT_DATE } from '../audit_events/constants';
export const mapDashboardToDrawerData = (mergeRequest) => ({
id: mergeRequest.id,
......@@ -24,8 +26,14 @@ export const convertProjectIdsToGraphQl = (projectIds) =>
projectIds.filter((id) => Boolean(id)),
);
export const parseViolationsQueryFilter = ({ createdBefore, createdAfter, projectIds }) => ({
export const parseViolationsQueryFilter = ({ mergedBefore, mergedAfter, projectIds }) => ({
projectIds: projectIds ? convertProjectIdsToGraphQl(projectIds) : [],
createdBefore: formatDate(createdBefore, ISO_SHORT_FORMAT),
createdAfter: formatDate(createdAfter, ISO_SHORT_FORMAT),
mergedBefore: formatDate(mergedBefore, ISO_SHORT_FORMAT),
mergedAfter: formatDate(mergedAfter, ISO_SHORT_FORMAT),
});
export const buildDefaultFilterParams = (queryString) => ({
mergedAfter: pikadayToString(getDateInPast(CURRENT_DATE, 30)),
mergedBefore: pikadayToString(CURRENT_DATE),
...queryToObject(queryString, { gatherArrays: true }),
});
......@@ -9,14 +9,35 @@ RSpec.describe 'Compliance Dashboard', :js do
let_it_be(:project) { create(:project, :repository, :public, namespace: group) }
let_it_be(:project_2) { create(:project, :repository, :public, namespace: group) }
shared_examples 'exports a merge commit-specific CSV' do
it 'downloads a commit chain of custory report', :aggregate_failures do
page.within('[data-testid="merge-commit-dropdown"]') do
find('.dropdown-toggle').click
requests = inspect_requests do
page.within('.dropdown-menu') do
find('input[name="commit_sha"]').set(merge_request.merge_commit_sha)
find('button[type="submit"]').click
end
end
csv_request = requests.find { |req| req.url.match(%r{.csv}) }
expect(csv_request.response_headers['Content-Disposition']).to match(%r{.csv})
expect(csv_request.response_headers['Content-Type']).to eq("text/csv; charset=utf-8")
expect(csv_request.response_headers['Content-Transfer-Encoding']).to eq("binary")
expect(csv_request.body).to match(%r{#{merge_request.merge_commit_sha}})
expect(csv_request.body).not_to match(%r{#{merge_request_2.merge_commit_sha}})
end
end
end
before do
stub_licensed_features(group_level_compliance_dashboard: true)
group.add_owner(user)
sign_in(user)
end
# TODO: This should be updated to fully test both with and without the feature flag once closer to feature completion
# https://gitlab.com/gitlab-org/gitlab/-/issues/347302
context 'when compliance_violations_report feature is disabled' do
before do
stub_feature_flags(compliance_violations_report: false)
......@@ -45,24 +66,7 @@ RSpec.describe 'Compliance Dashboard', :js do
end
context 'chain of custody report' do
it 'exports a merge commit-specific CSV' do
find('.dropdown-toggle').click
requests = inspect_requests do
page.within('.dropdown-menu') do
find('input[name="commit_sha"]').set(merge_request.merge_commit_sha)
find('button[type="submit"]').click
end
end
csv_request = requests.find { |req| req.url.match(%r{.csv}) }
expect(csv_request.response_headers['Content-Disposition']).to match(%r{.csv})
expect(csv_request.response_headers['Content-Type']).to eq("text/csv; charset=utf-8")
expect(csv_request.response_headers['Content-Transfer-Encoding']).to eq("binary")
expect(csv_request.body).to match(%r{#{merge_request.merge_commit_sha}})
expect(csv_request.body).not_to match(%r{#{merge_request_2.merge_commit_sha}})
end
it_behaves_like 'exports a merge commit-specific CSV'
end
end
end
......@@ -81,5 +85,95 @@ RSpec.describe 'Compliance Dashboard', :js do
expect(page).to have_content 'Date merged'
end
end
context 'when there are no compliance violations' do
it 'shows an empty state' do
expect(page).to have_content('No violations found')
end
end
context 'when there are merge requests' do
let_it_be(:merge_request) { create(:merge_request, source_project: project, state: :merged, merge_commit_sha: 'b71a6483b96dc303b66fdcaa212d9db6b10591ce') }
let_it_be(:merge_request_2) { create(:merge_request, source_project: project_2, state: :merged, merge_commit_sha: '24327319d067f4101cd3edd36d023ab5e49a8579') }
context 'chain of custody report' do
it_behaves_like 'exports a merge commit-specific CSV'
end
context 'and there is a compliance violation' do
let_it_be(:violation) { create(:compliance_violation, :approved_by_committer, severity_level: :high, merge_request: merge_request, violating_user: user) }
let_it_be(:violation_2) { create(:compliance_violation, :approved_by_merge_request_author, severity_level: :medium, merge_request: merge_request_2, violating_user: user) }
before do
merge_request.metrics.update!(merged_at: 1.day.ago)
merge_request_2.metrics.update!(merged_at: 7.days.ago)
wait_for_requests
end
it 'shows the compliance violations with details', :aggregate_failures do
expect(all('tbody > tr').count).to eq(2)
expect(first_row).to have_content('High')
expect(first_row).to have_content('Approved by committer')
expect(first_row).to have_content(merge_request.title)
expect(first_row).to have_content('1 day ago')
end
it 'can sort the violations by clicking on a column header' do
click_column_header 'Severity'
expect(first_row).to have_content(merge_request_2.title)
end
context 'violations filter' do
it 'can filter by date range' do
set_date_range(7.days.ago.to_date, 6.days.ago.to_date)
expect(page).to have_content(merge_request_2.title)
expect(page).not_to have_content(merge_request.title)
end
it 'can filter by project id' do
filter_by_project(merge_request_2.project)
expect(page).to have_content(merge_request_2.title)
expect(page).not_to have_content(merge_request.title)
end
end
end
end
end
def first_row
find('tbody tr', match: :first)
end
def set_date_range(start_date, end_date)
page.within('[data-testid="violations-date-range-picker"]') do
all('input')[0].set(start_date)
all('input')[0].native.send_keys(:return)
all('input')[1].set(end_date)
all('input')[1].native.send_keys(:return)
end
end
def filter_by_project(project)
page.within('[data-testid="violations-project-dropdown"]') do
find('.dropdown-toggle').click
find('input[aria-label="Search"]').set(project.name)
wait_for_requests
find('.dropdown-item').click
end
page.find('body').click
end
def click_column_header(name)
page.within('thead') do
find('div', text: name).click
wait_for_requests
end
end
end
......@@ -8,9 +8,9 @@ import Reference from 'ee/compliance_dashboard/components/drawer_sections/refere
import Reviewers from 'ee/compliance_dashboard/components/drawer_sections/reviewers.vue';
import { getContentWrapperHeight } from 'ee/threat_monitoring/utils';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import resolvers from 'ee/compliance_dashboard/graphql/resolvers';
import { DRAWER_Z_INDEX } from '~/lib/utils/constants';
import { mapViolations } from 'ee/compliance_dashboard/graphql/mappers';
import { createComplianceViolation } from '../mock_data';
jest.mock('ee/threat_monitoring/utils', () => ({
getContentWrapperHeight: jest.fn(),
......@@ -18,7 +18,7 @@ jest.mock('ee/threat_monitoring/utils', () => ({
describe('MergeRequestDrawer component', () => {
let wrapper;
const defaultData = mapViolations(resolvers.Query.group().mergeRequestViolations.nodes)[0];
const defaultData = mapViolations([createComplianceViolation()])[0];
const data = {
id: defaultData.id,
mergeRequest: {
......
......@@ -3,7 +3,10 @@ import Vue from 'vue';
import { GlDaterangePicker } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import ViolationFilter from 'ee/compliance_dashboard/components/violations/filter.vue';
import { convertProjectIdsToGraphQl } from 'ee/compliance_dashboard/utils';
import {
convertProjectIdsToGraphQl,
buildDefaultFilterParams,
} from 'ee/compliance_dashboard/utils';
import ProjectsDropdownFilter from '~/analytics/shared/components/projects_dropdown_filter.vue';
import { getDateInPast, pikadayToString } from '~/lib/utils/datetime_utility';
import { CURRENT_DATE } from 'ee/audit_events/constants';
......@@ -16,6 +19,7 @@ Vue.use(VueApollo);
describe('ViolationFilter component', () => {
let wrapper;
const defaultQuery = buildDefaultFilterParams('');
const groupPath = 'group-path';
const projectIds = ['1', '2'];
const startDate = getDateInPast(CURRENT_DATE, 20);
......@@ -42,7 +46,7 @@ describe('ViolationFilter component', () => {
apolloProvider: mockApollo(mockResponse),
propsData: {
groupPath,
defaultQuery: {},
defaultQuery,
...propsData,
},
});
......@@ -108,7 +112,9 @@ describe('ViolationFilter component', () => {
await findProjectsFilter().vm.$emit('selected', defaultProjects);
expect(wrapper.emitted('filters-changed')).toHaveLength(1);
expect(wrapper.emitted('filters-changed')[0]).toStrictEqual([{ projectIds: expectedIds }]);
expect(wrapper.emitted('filters-changed')[0]).toStrictEqual([
{ ...defaultQuery, projectIds: expectedIds },
]);
});
it('emits a query with a start and end date when a date range has been inputted', async () => {
......@@ -118,37 +124,29 @@ describe('ViolationFilter component', () => {
expect(wrapper.emitted('filters-changed')[0]).toStrictEqual([{ ...dateRangeQuery }]);
});
describe('with a default query', () => {
const defaultQuery = { projectIds, mergedAfter: '2022-01-01', mergedBefore: '2022-01-31' };
it('emits the existing filter query with mutations on each update', async () => {
await findProjectsFilter().vm.$emit('selected', []);
beforeEach(() => {
createComponent({ defaultQuery });
});
it('emits the existing filter query with mutations on each update', async () => {
await findProjectsFilter().vm.$emit('selected', []);
expect(wrapper.emitted('filters-changed')).toHaveLength(1);
expect(wrapper.emitted('filters-changed')[0]).toStrictEqual([
{ ...defaultQuery, projectIds: [] },
]);
expect(wrapper.emitted('filters-changed')).toHaveLength(1);
expect(wrapper.emitted('filters-changed')[0]).toStrictEqual([
{ ...defaultQuery, projectIds: [] },
]);
await findDatePicker().vm.$emit('input', { startDate, endDate });
await findDatePicker().vm.$emit('input', { startDate, endDate });
expect(wrapper.emitted('filters-changed')).toHaveLength(2);
expect(wrapper.emitted('filters-changed')[1]).toStrictEqual([
{
projectIds: [],
...dateRangeQuery,
},
]);
});
expect(wrapper.emitted('filters-changed')).toHaveLength(2);
expect(wrapper.emitted('filters-changed')[1]).toStrictEqual([
{
projectIds: [],
...dateRangeQuery,
},
]);
});
});
describe('projects filter', () => {
it('fetches the project details when the default query contains projectIds', () => {
createComponent({ defaultQuery: { projectIds } });
createComponent({ defaultQuery: { ...defaultQuery, projectIds } });
expect(groupProjectsSuccess).toHaveBeenCalledWith({
groupPath,
......@@ -158,7 +156,7 @@ describe('ViolationFilter component', () => {
describe('when the defaultProjects are being fetched', () => {
beforeEach(async () => {
createComponent({ defaultQuery: { projectIds } }, groupProjectsLoading);
createComponent({ defaultQuery: { ...defaultQuery, projectIds } }, groupProjectsLoading);
await waitForPromises();
});
......@@ -173,7 +171,7 @@ describe('ViolationFilter component', () => {
describe('when the defaultProjects have been fetched', () => {
beforeEach(async () => {
createComponent({ defaultQuery: { projectIds } });
createComponent({ defaultQuery: { ...defaultQuery, projectIds } });
await waitForPromises();
});
......
import { mapViolations } from 'ee/compliance_dashboard/graphql/mappers';
import resolvers from 'ee/compliance_dashboard/graphql/resolvers';
import { createComplianceViolation } from '../mock_data';
describe('mapViolations', () => {
const mockViolations = resolvers.Query.group().mergeRequestViolations.nodes;
it('returns the expected result', () => {
const { mergeRequest } = mapViolations([{ ...mockViolations[0] }])[0];
const { mergeRequest } = mapViolations([createComplianceViolation()])[0];
expect(mergeRequest).toMatchObject({
reference: mergeRequest.ref,
......
......@@ -93,3 +93,105 @@ export const createDefaultProjectsResponse = (projects) => ({
},
},
});
export const createComplianceViolation = (id) => ({
id: `gid://gitlab/MergeRequests::ComplianceViolation/${id}`,
severityLevel: 'HIGH',
reason: 'APPROVED_BY_COMMITTER',
violatingUser: {
id: 'gid://gitlab/User/21',
name: 'Miranda Friesen',
username: 'karren.medhurst',
avatarUrl: 'https://www.gravatar.com/avatar/9102aef461ba77d0fa0f37daffb834ac?s=80&d=identicon',
webUrl: 'http://gdk.test:3000/karren.medhurst',
__typename: 'UserCore',
},
mergeRequest: {
id: `gid://gitlab/MergeRequest/${id}`,
title: `Merge request ${id}`,
mergedAt: '2022-03-06T16:39:12Z',
webUrl: 'http://gdk.test:3000/gitlab-org/gitlab-shell/-/merge_requests/56',
author: {
id: 'gid://gitlab/User/1',
name: 'Administrator',
username: 'root',
avatarUrl:
'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
webUrl: 'http://gdk.test:3000/root',
__typename: 'UserCore',
},
mergeUser: null,
committers: {
nodes: [],
__typename: 'UserCoreConnection',
},
participants: {
nodes: [
{
id: 'gid://gitlab/User/1',
name: 'Administrator',
username: 'root',
avatarUrl:
'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
webUrl: 'http://gdk.test:3000/root',
__typename: 'UserCore',
},
],
__typename: 'UserCoreConnection',
},
approvedBy: {
nodes: [],
__typename: 'UserCoreConnection',
},
ref: '!56',
fullRef: 'gitlab-org/gitlab-shell!56',
sourceBranch: 'master',
sourceBranchExists: false,
targetBranch: 'feature',
targetBranchExists: false,
project: {
id: 'gid://gitlab/Project/2',
avatarUrl: null,
name: 'Gitlab Shell',
webUrl: 'http://gdk.test:3000/gitlab-org/gitlab-shell',
complianceFrameworks: {
nodes: [
{
id: 'gid://gitlab/ComplianceManagement::Framework/1',
name: 'GDPR',
description: 'asds',
color: '#0000ff',
__typename: 'ComplianceFramework',
},
],
__typename: 'ComplianceFrameworkConnection',
},
__typename: 'Project',
},
__typename: 'MergeRequest',
},
__typename: 'ComplianceViolation',
});
export const createComplianceViolationsResponse = ({ count = 1, pageInfo = {} } = {}) => ({
data: {
group: {
id: 'gid://gitlab/Group/1',
__typename: 'Group',
mergeRequestViolations: {
__typename: 'ComplianceViolationConnection',
nodes: Array(count)
.fill(null)
.map((_, id) => createComplianceViolation(id)),
pageInfo: {
endCursor: 'abc',
hasNextPage: true,
hasPreviousPage: false,
startCursor: 'abc',
__typename: 'PageInfo',
...pageInfo,
},
},
},
},
});
import * as utils from 'ee/compliance_dashboard/utils';
import { queryToObject } from '~/lib/utils/url_utility';
jest.mock('ee/audit_events/constants', () => ({
CURRENT_DATE: new Date('2022 2 28'),
}));
describe('compliance report utils', () => {
const projectIds = ['1', '2'];
......@@ -8,14 +13,14 @@ describe('compliance report utils', () => {
it('returns the expected result', () => {
const query = {
projectIds,
createdAfter: '2021-12-06',
createdBefore: '2022-01-06',
mergedAfter: '2021-12-06',
mergedBefore: '2022-01-06',
};
expect(utils.parseViolationsQueryFilter(query)).toStrictEqual({
projectIds: projectGraphQlIds,
createdAfter: query.createdAfter,
createdBefore: query.createdBefore,
mergedAfter: query.mergedAfter,
mergedBefore: query.mergedBefore,
});
});
});
......@@ -25,4 +30,24 @@ describe('compliance report utils', () => {
expect(utils.convertProjectIdsToGraphQl(projectIds)).toStrictEqual(projectGraphQlIds);
});
});
describe('buildDefaultFilterParams', () => {
it('returns the expected result with the default date range of 30 days', () => {
const queryString = 'projectIds[]=20';
expect(utils.buildDefaultFilterParams(queryString)).toStrictEqual({
mergedAfter: '2022-01-29',
mergedBefore: '2022-02-28',
projectIds: ['20'],
});
});
it('return the expected result when the query contains dates', () => {
const queryString = 'mergedAfter=2022-02-09&mergedBefore=2022-03-11&projectIds[]=20';
expect(utils.buildDefaultFilterParams(queryString)).toStrictEqual(
queryToObject(queryString, { gatherArrays: true }),
);
});
});
});
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