Commit 3b7e20f7 authored by Jose Ivan Vargas's avatar Jose Ivan Vargas

Merge branch '342897-update-merge-request-violations-to-consume-api' into 'master'

Update compliance violations app to fetch results from GraphQL API

See merge request gitlab-org/gitlab!82397
parents 28ee1ea6 c9ef3f77
......@@ -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