Commit edaaf79c authored by David Pisek's avatar David Pisek Committed by Mark Florian

Add status groups to license-compliance MR widget

This commit groups licenses within the MR widget by their status
and adds a header and subscription to each group.

It also refactors related vue-specs to use vue-test-utils.

WIP - group licenses by status
parent 5b1879b1
...@@ -63,15 +63,6 @@ ...@@ -63,15 +63,6 @@
list-style: none; list-style: none;
padding: 0 1px; padding: 0 1px;
margin: 0; margin: 0;
.license-item {
line-height: $gl-padding-32;
.license-packages {
font-size: $label-font-size;
}
}
} }
.report-block-list-icon { .report-block-list-icon {
......
...@@ -38,7 +38,7 @@ export default { ...@@ -38,7 +38,7 @@ export default {
}; };
</script> </script>
<template> <template>
<div class="license-packages d-inline"> <div class="license-packages d-inline gl-font-size-12">
<div class="js-license-dependencies d-inline">{{ packageString }}</div> <div class="js-license-dependencies d-inline">{{ packageString }}</div>
<button <button
v-if="!showAllPackages && remainingPackages" v-if="!showAllPackages && remainingPackages"
......
/* eslint-disable @gitlab/require-i18n-strings */ import { __, s__ } from '~/locale';
import { STATUS_FAILED, STATUS_NEUTRAL, STATUS_SUCCESS } from '~/reports/constants';
/* /*
* Endpoint still returns 'approved' & 'blacklisted' * Endpoint still returns 'approved' & 'blacklisted'
...@@ -14,6 +16,7 @@ export const LICENSE_APPROVAL_ACTION = { ...@@ -14,6 +16,7 @@ export const LICENSE_APPROVAL_ACTION = {
DENY: 'deny', DENY: 'deny',
}; };
/* eslint-disable @gitlab/require-i18n-strings */
export const KNOWN_LICENSES = [ export const KNOWN_LICENSES = [
'AGPL-1.0', 'AGPL-1.0',
'AGPL-3.0', 'AGPL-3.0',
...@@ -41,3 +44,22 @@ export const KNOWN_LICENSES = [ ...@@ -41,3 +44,22 @@ export const KNOWN_LICENSES = [
'WTFPL', 'WTFPL',
'Zlib', 'Zlib',
]; ];
/* eslint-enable @gitlab/require-i18n-strings */
export const REPORT_GROUPS = [
{
name: s__('LicenseManagement|Denied'),
description: __("Out-of-compliance with this project's policies and should be removed"),
status: STATUS_FAILED,
},
{
name: s__('LicenseManagement|Uncategorized'),
description: __('No policy matches this license'),
status: STATUS_NEUTRAL,
},
{
name: s__('LicenseManagement|Allowed'),
description: __('Acceptable for use in this project'),
status: STATUS_SUCCESS,
},
];
...@@ -2,13 +2,13 @@ ...@@ -2,13 +2,13 @@
import { mapState, mapGetters, mapActions } from 'vuex'; import { mapState, mapGetters, mapActions } from 'vuex';
import { GlLink } from '@gitlab/ui'; import { GlLink } from '@gitlab/ui';
import reportsMixin from 'ee/vue_shared/security_reports/mixins/reports_mixin'; import reportsMixin from 'ee/vue_shared/security_reports/mixins/reports_mixin';
import ReportItem from '~/reports/components/report_item.vue';
import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue';
import SetLicenseApprovalModal from 'ee/vue_shared/license_compliance/components/set_approval_status_modal.vue'; import SetLicenseApprovalModal from 'ee/vue_shared/license_compliance/components/set_approval_status_modal.vue';
import { componentNames } from 'ee/reports/components/issue_body'; import { componentNames } from 'ee/reports/components/issue_body';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import ReportSection from '~/reports/components/report_section.vue'; import ReportSection from '~/reports/components/report_section.vue';
import { LICENSE_MANAGEMENT } from 'ee/vue_shared/license_compliance/store/constants'; import { LICENSE_MANAGEMENT } from 'ee/vue_shared/license_compliance/store/constants';
import createStore from './store'; import createStore from './store';
const store = createStore(); const store = createStore();
...@@ -19,8 +19,10 @@ export default { ...@@ -19,8 +19,10 @@ export default {
store, store,
components: { components: {
GlLink, GlLink,
ReportItem,
ReportSection, ReportSection,
SetLicenseApprovalModal, SetLicenseApprovalModal,
SmartVirtualList,
Icon, Icon,
}, },
mixins: [reportsMixin], mixins: [reportsMixin],
...@@ -64,6 +66,8 @@ export default { ...@@ -64,6 +66,8 @@ export default {
default: '', default: '',
}, },
}, },
typicalReportItemHeight: 26,
maxShownReportItems: 20,
computed: { computed: {
...mapState(LICENSE_MANAGEMENT, ['loadLicenseReportError']), ...mapState(LICENSE_MANAGEMENT, ['loadLicenseReportError']),
...mapGetters(LICENSE_MANAGEMENT, [ ...mapGetters(LICENSE_MANAGEMENT, [
...@@ -71,6 +75,7 @@ export default { ...@@ -71,6 +75,7 @@ export default {
'isLoading', 'isLoading',
'licenseSummaryText', 'licenseSummaryText',
'reportContainsBlacklistedLicense', 'reportContainsBlacklistedLicense',
'licenseReportGroups',
]), ]),
hasLicenseReportIssues() { hasLicenseReportIssues() {
const { licenseReport } = this; const { licenseReport } = this;
...@@ -119,6 +124,38 @@ export default { ...@@ -119,6 +124,38 @@ export default {
class="license-report-widget mr-report" class="license-report-widget mr-report"
data-qa-selector="license_report_widget" data-qa-selector="license_report_widget"
> >
<template #body>
<smart-virtual-list
ref="reportSectionBody"
:size="$options.typicalReportItemHeight"
:length="licenseReport.length"
:remain="$options.maxShownReportItems"
class="report-block-container"
wtag="ul"
wclass="report-block-list my-1"
>
<template v-for="(licenseReportGroup, index) in licenseReportGroups">
<li
ref="reportHeading"
:key="licenseReportGroup.name"
:class="{ 'mt-3': index > 0 }"
class="mx-1 mb-1"
>
<h2 class="h5 m-0">{{ licenseReportGroup.name }}</h2>
<p class="m-0">{{ licenseReportGroup.description }}</p>
</li>
<report-item
v-for="license in licenseReportGroup.licenses"
:key="license.name"
:issue="license"
:status="license.status"
:component="$options.componentNames.LicenseIssueBody"
:show-report-section-status-icon="true"
class="my-1"
/>
</template>
</smart-virtual-list>
</template>
<template #success> <template #success>
<div class="pr-3"> <div class="pr-3">
{{ licenseSummaryText }} {{ licenseSummaryText }}
......
import { n__, s__, sprintf } from '~/locale'; import { n__, s__, sprintf } from '~/locale';
import { LICENSE_APPROVAL_STATUS } from '../constants'; import { addLicensesMatchingReportGroupStatus, reportGroupHasAtLeastOneLicense } from './utils';
import { LICENSE_APPROVAL_STATUS, REPORT_GROUPS } from '../constants';
export const isLoading = state => state.isLoadingManagedLicenses || state.isLoadingLicenseReport; export const isLoading = state => state.isLoadingManagedLicenses || state.isLoadingLicenseReport;
...@@ -11,6 +12,11 @@ export const hasPendingLicenses = state => state.pendingLicenses.length > 0; ...@@ -11,6 +12,11 @@ export const hasPendingLicenses = state => state.pendingLicenses.length > 0;
export const licenseReport = state => state.newLicenses; export const licenseReport = state => state.newLicenses;
export const licenseReportGroups = state =>
REPORT_GROUPS.map(addLicensesMatchingReportGroupStatus(state.newLicenses)).filter(
reportGroupHasAtLeastOneLicense,
);
export const licenseSummaryText = (state, getters) => { export const licenseSummaryText = (state, getters) => {
const hasReportItems = getters.licenseReport && getters.licenseReport.length; const hasReportItems = getters.licenseReport && getters.licenseReport.length;
const baseReportHasLicenses = state.existingLicenses.length; const baseReportHasLicenses = state.existingLicenses.length;
...@@ -66,7 +72,7 @@ export const licenseSummaryText = (state, getters) => { ...@@ -66,7 +72,7 @@ export const licenseSummaryText = (state, getters) => {
return s__('LicenseCompliance|License Compliance detected no new licenses'); return s__('LicenseCompliance|License Compliance detected no new licenses');
}; };
export const reportContainsBlacklistedLicense = (_state, getters) => export const reportContainsBlacklistedLicense = (_, getters) =>
(getters.licenseReport || []).some( (getters.licenseReport || []).some(
license => license.approvalStatus === LICENSE_APPROVAL_STATUS.DENIED, license => license.approvalStatus === LICENSE_APPROVAL_STATUS.DENIED,
); );
......
import { groupBy } from 'lodash';
import { LICENSE_APPROVAL_STATUS } from 'ee/vue_shared/license_compliance/constants'; import { LICENSE_APPROVAL_STATUS } from 'ee/vue_shared/license_compliance/constants';
import { s__, n__, sprintf } from '~/locale'; import { s__, n__, sprintf } from '~/locale';
import { STATUS_FAILED, STATUS_NEUTRAL, STATUS_SUCCESS } from '~/reports/constants'; import { STATUS_FAILED, STATUS_NEUTRAL, STATUS_SUCCESS } from '~/reports/constants';
...@@ -93,3 +94,30 @@ export const convertToOldReportFormat = license => { ...@@ -93,3 +94,30 @@ export const convertToOldReportFormat = license => {
status: getIssueStatusFromLicenseStatus(approvalStatus), status: getIssueStatusFromLicenseStatus(approvalStatus),
}; };
}; };
/**
* Takes an array of licenses and returns a function that takes an report-group objects
*
* It returns a fresh object, containing all properties of the original report-group and added "license" property,
* containing an array of licenses, matching the report-group's status
*
* @param {Array} licenses
* @returns {function(*): {licenses: (*|*[])}}
*/
export const addLicensesMatchingReportGroupStatus = licenses => {
const licensesGroupedByStatus = groupBy(licenses, 'status');
return reportGroup => ({
...reportGroup,
licenses: licensesGroupedByStatus[reportGroup.status] || [],
});
};
/**
* Returns true of the given object has a "license" property, containing an array with at least licenses. Otherwise false.
*
*
* @param {Object}
* @returns {boolean}
*/
export const reportGroupHasAtLeastOneLicense = ({ licenses }) => licenses?.length > 0;
---
title: 'Clarify detected license results in merge request: Group licenses by status'
merge_request: 28631
author:
type: changed
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`License Report MR Widget report section report body should render correctly 1`] = `
<smart-virtual-list-stub
class="report-block-container"
length="1"
remain="20"
rtag="div"
size="26"
wclass="report-block-list my-1"
wtag="ul"
>
<li
class="mx-1 mb-1"
>
<h2
class="h5 m-0"
>
some-status group-name
</h2>
<p
class="m-0"
>
some-status group-description
</p>
</li>
</smart-virtual-list-stub>
`;
exports[`License Report MR Widget report section should render correctly 1`] = `
<report-section-stub
class="license-report-widget mr-report"
component="LicenseIssueBody"
data-qa-selector="license_report_widget"
errortext="FOO"
hasissues="true"
loadingtext="FOO"
neutralissues="[object Object]"
popoveroptions="[object Object]"
resolvedissues=""
showreportsectionstatusicon="true"
status="SUCCESS"
successtext=""
unresolvedissues=""
>
<div
class="append-right-default"
>
<a
class="btn btn-default btn-sm js-manage-licenses append-right-8"
href="http://test.host/lm_settings"
>
Manage licenses
</a>
<a
class="btn btn-default btn-sm js-full-report"
href="http://test.host/path/to/the/full/report"
target="_blank"
>
View full report
<icon-stub
name="external-link"
size="16"
/>
</a>
</div>
</report-section-stub>
`;
import { range } from 'lodash';
import { LICENSE_APPROVAL_STATUS } from 'ee/vue_shared/license_compliance/constants'; import { LICENSE_APPROVAL_STATUS } from 'ee/vue_shared/license_compliance/constants';
export const approvedLicense = { export const approvedLicense = {
...@@ -57,4 +58,14 @@ export const licenseReport = [ ...@@ -57,4 +58,14 @@ export const licenseReport = [
}, },
]; ];
export const generateReportGroup = ({ status = 'some-status', numberOfLicenses = 0 } = {}) => ({
status,
name: `${status} group-name`,
description: `${status} group-description`,
licenses: range(numberOfLicenses).map(i => ({
name: `${status} license-name-${i}`,
status,
})),
});
export default () => {}; export default () => {};
import Vue from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import LicenseManagement from 'ee/vue_shared/license_compliance/mr_widget_license_report.vue'; import LicenseManagement from 'ee/vue_shared/license_compliance/mr_widget_license_report.vue';
import ReportSection from '~/reports/components/report_section.vue';
import ReportItem from '~/reports/components/report_item.vue';
import { LOADING, ERROR, SUCCESS } from 'ee/vue_shared/security_reports/store/constants'; import { LOADING, ERROR, SUCCESS } from 'ee/vue_shared/security_reports/store/constants';
import { mountComponentWithStore } from 'helpers/vue_mount_component_helper'; import { shallowMount, createLocalVue } from '@vue/test-utils';
import { TEST_HOST } from 'spec/test_constants'; import { TEST_HOST } from 'spec/test_constants';
import { import {
approvedLicense, approvedLicense,
blacklistedLicense, blacklistedLicense,
licenseReport as licenseReportMock, licenseReport as licenseReportMock,
generateReportGroup,
} from './mock_data'; } from './mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('License Report MR Widget', () => { describe('License Report MR Widget', () => {
const Component = Vue.extend(LicenseManagement);
const apiUrl = `${TEST_HOST}/license_management`; const apiUrl = `${TEST_HOST}/license_management`;
const securityApprovalsHelpPagePath = `${TEST_HOST}/path/to/security/approvals/help`; const securityApprovalsHelpPagePath = `${TEST_HOST}/path/to/security/approvals/help`;
let vm; let wrapper;
const defaultState = { const defaultState = {
managedLicenses: [approvedLicense, blacklistedLicense], managedLicenses: [approvedLicense, blacklistedLicense],
...@@ -36,6 +39,9 @@ describe('License Report MR Widget', () => { ...@@ -36,6 +39,9 @@ describe('License Report MR Widget', () => {
reportContainsBlacklistedLicense() { reportContainsBlacklistedLicense() {
return false; return false;
}, },
licenseReportGroups() {
return [];
},
}; };
const defaultProps = { const defaultProps = {
...@@ -60,6 +66,7 @@ describe('License Report MR Widget', () => { ...@@ -60,6 +66,7 @@ describe('License Report MR Widget', () => {
getters = defaultGetters, getters = defaultGetters,
state = defaultState, state = defaultState,
actions = defaultActions, actions = defaultActions,
stubs = {},
} = {}) => { } = {}) => {
const store = new Vuex.Store({ const store = new Vuex.Store({
modules: { modules: {
...@@ -71,15 +78,19 @@ describe('License Report MR Widget', () => { ...@@ -71,15 +78,19 @@ describe('License Report MR Widget', () => {
}, },
}, },
}); });
return mountComponentWithStore(Component, { props, store }); wrapper = shallowMount(LicenseManagement, {
localVue,
propsData: props,
store,
stubs,
});
}; };
beforeEach(() => { const findAllReportItems = () => wrapper.findAll(ReportItem);
vm = mountComponent();
});
afterEach(() => { afterEach(() => {
vm.$destroy(); wrapper.destroy();
wrapper = null;
}); });
describe('computed', () => { describe('computed', () => {
...@@ -91,13 +102,15 @@ describe('License Report MR Widget', () => { ...@@ -91,13 +102,15 @@ describe('License Report MR Widget', () => {
return []; return [];
}, },
}; };
vm = mountComponent({ getters }); mountComponent({ getters });
expect(vm.hasLicenseReportIssues).toBe(false); expect(wrapper.vm.hasLicenseReportIssues).toBe(false);
}); });
it('should be true, if the report is not empty', () => { it('should be true, if the report is not empty', () => {
expect(vm.hasLicenseReportIssues).toBe(true); mountComponent();
expect(wrapper.vm.hasLicenseReportIssues).toBe(true);
}); });
}); });
...@@ -109,20 +122,22 @@ describe('License Report MR Widget', () => { ...@@ -109,20 +122,22 @@ describe('License Report MR Widget', () => {
return true; return true;
}, },
}; };
vm = mountComponent({ getters }); mountComponent({ getters });
expect(vm.licenseReportStatus).toBe(LOADING); expect(wrapper.vm.licenseReportStatus).toBe(LOADING);
}); });
it('should be `ERROR`, if the report is has an error', () => { it('should be `ERROR`, if the report is has an error', () => {
const state = { ...defaultState, loadLicenseReportError: new Error('test') }; const state = { ...defaultState, loadLicenseReportError: new Error('test') };
vm = mountComponent({ state }); mountComponent({ state });
expect(vm.licenseReportStatus).toBe(ERROR); expect(wrapper.vm.licenseReportStatus).toBe(ERROR);
}); });
it('should be `SUCCESS`, if the report is successful', () => { it('should be `SUCCESS`, if the report is successful', () => {
expect(vm.licenseReportStatus).toBe(SUCCESS); mountComponent();
expect(wrapper.vm.licenseReportStatus).toBe(SUCCESS);
}); });
}); });
...@@ -131,16 +146,16 @@ describe('License Report MR Widget', () => { ...@@ -131,16 +146,16 @@ describe('License Report MR Widget', () => {
it('should be true if fullReportPath AND licenseManagementSettingsPath prop are provided', () => { it('should be true if fullReportPath AND licenseManagementSettingsPath prop are provided', () => {
const props = { ...otherProps, fullReportPath, licenseManagementSettingsPath }; const props = { ...otherProps, fullReportPath, licenseManagementSettingsPath };
vm = mountComponent({ props }); mountComponent({ props });
expect(vm.showActionButtons).toBe(true); expect(wrapper.vm.showActionButtons).toBe(true);
}); });
it('should be true if only licenseManagementSettingsPath is provided', () => { it('should be true if only licenseManagementSettingsPath is provided', () => {
const props = { ...otherProps, fullReportPath: null, licenseManagementSettingsPath }; const props = { ...otherProps, fullReportPath: null, licenseManagementSettingsPath };
vm = mountComponent({ props }); mountComponent({ props });
expect(vm.showActionButtons).toBe(true); expect(wrapper.vm.showActionButtons).toBe(true);
}); });
it('should be true if only fullReportPath is provided', () => { it('should be true if only fullReportPath is provided', () => {
...@@ -149,9 +164,9 @@ describe('License Report MR Widget', () => { ...@@ -149,9 +164,9 @@ describe('License Report MR Widget', () => {
fullReportPath, fullReportPath,
licenseManagementSettingsPath: null, licenseManagementSettingsPath: null,
}; };
vm = mountComponent({ props }); mountComponent({ props });
expect(vm.showActionButtons).toBe(true); expect(wrapper.vm.showActionButtons).toBe(true);
}); });
it('should be false if fullReportPath and licenseManagementSettingsPath prop are not provided', () => { it('should be false if fullReportPath and licenseManagementSettingsPath prop are not provided', () => {
...@@ -160,39 +175,138 @@ describe('License Report MR Widget', () => { ...@@ -160,39 +175,138 @@ describe('License Report MR Widget', () => {
fullReportPath: null, fullReportPath: null,
licenseManagementSettingsPath: null, licenseManagementSettingsPath: null,
}; };
vm = mountComponent({ props }); mountComponent({ props });
expect(vm.showActionButtons).toBe(false); expect(wrapper.vm.showActionButtons).toBe(false);
}); });
}); });
}); });
it('should render report section wrapper', () => { describe('report section', () => {
expect(vm.$el.querySelector('.license-report-widget')).not.toBeNull(); it('should render correctly', () => {
const mockReportGroups = [generateReportGroup()];
mountComponent({
getters: {
...defaultGetters,
licenseReportGroups() {
return mockReportGroups;
},
},
});
expect(wrapper.find(ReportSection).element).toMatchSnapshot();
});
describe('report body', () => {
it('should render correctly', () => {
const mockReportGroups = [generateReportGroup()];
mountComponent({
getters: {
...defaultGetters,
licenseReportGroups() {
return mockReportGroups;
},
},
stubs: { ReportSection },
});
expect(wrapper.find({ ref: 'reportSectionBody' }).element).toMatchSnapshot();
});
it.each`
givenStatuses | expectedNumberOfReportHeadings
${[]} | ${0}
${['failed', 'neutral']} | ${2}
${['failed', 'neutral', 'success']} | ${3}
`(
'given reports for: $givenStatuses it has $expectedNumberOfReportHeadings report headings',
({ givenStatuses, expectedNumberOfReportHeadings }) => {
const mockReportGroups = givenStatuses.map(status => generateReportGroup({ status }));
mountComponent({
getters: {
...defaultGetters,
licenseReportGroups() {
return mockReportGroups;
},
},
stubs: { ReportSection },
});
expect(wrapper.findAll({ ref: 'reportHeading' }).length).toBe(
expectedNumberOfReportHeadings,
);
},
);
it.each([0, 1, 2])(
'should include %d report items when section has that many licenses',
numberOfLicenses => {
const mockReportGroups = [
generateReportGroup({
numberOfLicenses,
}),
];
mountComponent({
getters: {
...defaultGetters,
licenseReportGroups() {
return mockReportGroups;
},
},
stubs: { ReportSection },
});
expect(findAllReportItems().length).toBe(numberOfLicenses);
},
);
it('renders the report items in the correct order', () => {
const mockReportGroups = [
generateReportGroup({ status: 'failed', numberOfLicenses: 1 }),
generateReportGroup({ status: 'neutral', numberOfLicenses: 1 }),
generateReportGroup({ status: 'success', numberOfLicenses: 1 }),
];
mountComponent({
getters: {
...defaultGetters,
licenseReportGroups() {
return mockReportGroups;
},
},
stubs: { ReportSection },
}); });
it('should render report widget section', () => { const allReportItems = findAllReportItems();
expect(vm.$el.querySelector('.report-block-container')).not.toBeNull(); mockReportGroups.forEach((group, index) => {
expect(allReportItems.at(index).props('status')).toBe(group.status);
});
});
});
}); });
describe('`View full report` button', () => { describe('`View full report` button', () => {
const selector = '.js-full-report'; const selector = '.js-full-report';
it('should be rendered when fullReportPath prop is provided', () => { it('should be rendered when fullReportPath prop is provided', () => {
const linkEl = vm.$el.querySelector(selector); mountComponent();
expect(linkEl).not.toBeNull(); const linkEl = wrapper.find(selector);
expect(linkEl.getAttribute('href')).toEqual(defaultProps.fullReportPath);
expect(linkEl.textContent.trim()).toEqual('View full report'); expect(linkEl.exists()).toBe(true);
expect(linkEl.attributes('href')).toEqual(defaultProps.fullReportPath);
expect(linkEl.text()).toBe('View full report');
}); });
it('should not be rendered when fullReportPath prop is not provided', () => { it('should not be rendered when fullReportPath prop is not provided', () => {
const props = { ...defaultProps, fullReportPath: null }; const props = { ...defaultProps, fullReportPath: null };
vm = mountComponent({ props }); mountComponent({ props });
const linkEl = vm.$el.querySelector(selector);
expect(linkEl).toBeNull(); expect(wrapper.contains(selector)).toBe(false);
}); });
}); });
...@@ -200,25 +314,27 @@ describe('License Report MR Widget', () => { ...@@ -200,25 +314,27 @@ describe('License Report MR Widget', () => {
const selector = '.js-manage-licenses'; const selector = '.js-manage-licenses';
it('should be rendered when licenseManagementSettingsPath prop is provided', () => { it('should be rendered when licenseManagementSettingsPath prop is provided', () => {
const linkEl = vm.$el.querySelector(selector); mountComponent();
const linkEl = wrapper.find(selector);
expect(linkEl).not.toBeNull(); expect(linkEl.exists()).toBe(true);
expect(linkEl.getAttribute('href')).toEqual(defaultProps.licenseManagementSettingsPath); expect(linkEl.attributes('href')).toEqual(defaultProps.licenseManagementSettingsPath);
expect(linkEl.textContent.trim()).toEqual('Manage licenses'); expect(linkEl.text()).toBe('Manage licenses');
}); });
it('should not be rendered when licenseManagementSettingsPath prop is not provided', () => { it('should not be rendered when licenseManagementSettingsPath prop is not provided', () => {
const props = { ...defaultProps, licenseManagementSettingsPath: null }; const props = { ...defaultProps, licenseManagementSettingsPath: null };
vm = mountComponent({ props }); mountComponent({ props });
const linkEl = vm.$el.querySelector(selector); expect(wrapper.contains(selector)).toBe(false);
expect(linkEl).toBeNull();
}); });
}); });
it('should render set approval modal', () => { it('should render set approval modal', () => {
expect(vm.$el.querySelector('#modal-set-license-approval')).not.toBeNull(); mountComponent();
expect(wrapper.find('#modal-set-license-approval')).not.toBeNull();
}); });
it('should init store after mount', () => { it('should init store after mount', () => {
...@@ -226,7 +342,7 @@ describe('License Report MR Widget', () => { ...@@ -226,7 +342,7 @@ describe('License Report MR Widget', () => {
setAPISettings: jest.fn(() => {}), setAPISettings: jest.fn(() => {}),
fetchParsedLicenseReport: jest.fn(() => {}), fetchParsedLicenseReport: jest.fn(() => {}),
}; };
vm = mountComponent({ actions }); mountComponent({ actions });
expect(actions.setAPISettings).toHaveBeenCalledWith( expect(actions.setAPISettings).toHaveBeenCalledWith(
expect.any(Object), expect.any(Object),
...@@ -246,11 +362,14 @@ describe('License Report MR Widget', () => { ...@@ -246,11 +362,14 @@ describe('License Report MR Widget', () => {
}); });
describe('approval status', () => { describe('approval status', () => {
const findSecurityApprovalHelpLink = () => const findSecurityApprovalHelpLink = () => wrapper.find('.js-security-approval-help-link');
vm.$el.querySelector('.js-security-approval-help-link');
it('does not show a link to security approval help page if report does not contain blacklisted licenses', () => { it('does not show a link to security approval help page if report does not contain blacklisted licenses', () => {
expect(findSecurityApprovalHelpLink()).toBeNull(); mountComponent({
stubs: { ReportSection },
});
expect(findSecurityApprovalHelpLink().exists()).toBe(false);
}); });
it('shows a link to security approval help page if report contains blacklisted licenses', () => { it('shows a link to security approval help page if report contains blacklisted licenses', () => {
...@@ -260,11 +379,15 @@ describe('License Report MR Widget', () => { ...@@ -260,11 +379,15 @@ describe('License Report MR Widget', () => {
return true; return true;
}, },
}; };
vm = mountComponent({ getters }); mountComponent({
getters,
stubs: { ReportSection },
});
const securityApprovalHelpLink = findSecurityApprovalHelpLink(); const securityApprovalHelpLink = findSecurityApprovalHelpLink();
expect(findSecurityApprovalHelpLink()).not.toBeNull(); expect(findSecurityApprovalHelpLink().exists()).toBe(true);
expect(securityApprovalHelpLink.getAttribute('href')).toEqual(securityApprovalsHelpPagePath); expect(securityApprovalHelpLink.attributes('href')).toBe(securityApprovalsHelpPagePath);
}); });
}); });
}); });
...@@ -91,6 +91,59 @@ describe('getters', () => { ...@@ -91,6 +91,59 @@ describe('getters', () => {
}); });
}); });
describe('licenseReportGroups', () => {
it('returns an array of objects containing information about the group and licenses', () => {
const licensesSuccess = [
{ status: 'success', value: 'foo' },
{ status: 'success', value: 'bar' },
];
const licensesNeutral = [
{ status: 'neutral', value: 'foo' },
{ status: 'neutral', value: 'bar' },
];
const licensesFailed = [
{ status: 'failed', value: 'foo' },
{ status: 'failed', value: 'bar' },
];
const newLicenses = [...licensesSuccess, ...licensesNeutral, ...licensesFailed];
expect(getters.licenseReportGroups({ newLicenses })).toEqual([
{
name: 'Denied',
description: `Out-of-compliance with this project's policies and should be removed`,
status: 'failed',
licenses: licensesFailed,
},
{
name: 'Uncategorized',
description: 'No policy matches this license',
status: 'neutral',
licenses: licensesNeutral,
},
{
name: 'Allowed',
description: 'Acceptable for use in this project',
status: 'success',
licenses: licensesSuccess,
},
]);
});
it.each(['failed', 'neutral', 'success'])(
`it filters report-groups that don't have the given status: %s`,
status => {
const newLicenses = [{ status }];
expect(getters.licenseReportGroups({ newLicenses })).toEqual([
expect.objectContaining({
status,
licenses: newLicenses,
}),
]);
},
);
});
describe('licenseSummaryText', () => { describe('licenseSummaryText', () => {
describe('when licenses exist on both the HEAD and the BASE', () => { describe('when licenses exist on both the HEAD and the BASE', () => {
beforeEach(() => { beforeEach(() => {
......
...@@ -4,6 +4,8 @@ import { ...@@ -4,6 +4,8 @@ import {
getStatusTranslationsFromLicenseStatus, getStatusTranslationsFromLicenseStatus,
getIssueStatusFromLicenseStatus, getIssueStatusFromLicenseStatus,
convertToOldReportFormat, convertToOldReportFormat,
addLicensesMatchingReportGroupStatus,
reportGroupHasAtLeastOneLicense,
} from 'ee/vue_shared/license_compliance/store/utils'; } from 'ee/vue_shared/license_compliance/store/utils';
import { LICENSE_APPROVAL_STATUS } from 'ee/vue_shared/license_compliance/constants'; import { LICENSE_APPROVAL_STATUS } from 'ee/vue_shared/license_compliance/constants';
import { licenseReport } from '../mock_data'; import { licenseReport } from '../mock_data';
...@@ -111,4 +113,55 @@ describe('utils', () => { ...@@ -111,4 +113,55 @@ describe('utils', () => {
expect(parsedLicense.name).toEqual(rawLicense.name); expect(parsedLicense.name).toEqual(rawLicense.name);
}); });
}); });
describe('addLicensesMatchingReportGroupStatus', () => {
describe('with matching licenses', () => {
it(`adds a "licenses" property containing an array of licenses matching the report's status to the report object`, () => {
const licenses = [
{ status: 'match' },
{ status: 'no-match' },
{ status: 'match' },
{ status: 'no-match' },
];
const reportGroup = { description: 'description', status: 'match' };
expect(addLicensesMatchingReportGroupStatus(licenses)(reportGroup)).toEqual({
...reportGroup,
licenses: [licenses[0], licenses[2]],
});
});
});
describe('without matching licenses', () => {
it('adds a "licenses" property containing an empty array to the report object', () => {
const licenses = [
{ status: 'no-match' },
{ status: 'no-match' },
{ status: 'no-match' },
{ status: 'no-match' },
];
const reportGroup = { description: 'description', status: 'match' };
expect(addLicensesMatchingReportGroupStatus(licenses)(reportGroup)).toEqual({
...reportGroup,
licenses: [],
});
});
});
});
describe('reportGroupHasAtLeastOneLicense', () => {
it.each`
givenReportGroup | expected
${{ licenses: [{ foo: 'foo ' }] }} | ${true}
${{ licenses: [] }} | ${false}
${{ licenses: null }} | ${false}
${{ licenses: undefined }} | ${false}
`(
'returns "$expected" if the given report-group contains $licenses.length licenses',
({ givenReportGroup, expected }) => {
expect(reportGroupHasAtLeastOneLicense(givenReportGroup)).toBe(expected);
},
);
});
}); });
...@@ -931,6 +931,9 @@ msgstr "" ...@@ -931,6 +931,9 @@ msgstr ""
msgid "Accept terms" msgid "Accept terms"
msgstr "" msgstr ""
msgid "Acceptable for use in this project"
msgstr ""
msgid "Accepted MR" msgid "Accepted MR"
msgstr "" msgstr ""
...@@ -12000,6 +12003,15 @@ msgstr "" ...@@ -12000,6 +12003,15 @@ msgstr ""
msgid "LicenseCompliance|You are about to remove the license, %{name}, from this project." msgid "LicenseCompliance|You are about to remove the license, %{name}, from this project."
msgstr "" msgstr ""
msgid "LicenseManagement|Allowed"
msgstr ""
msgid "LicenseManagement|Denied"
msgstr ""
msgid "LicenseManagement|Uncategorized"
msgstr ""
msgid "Licensed Features" msgid "Licensed Features"
msgstr "" msgstr ""
...@@ -13556,6 +13568,9 @@ msgstr "" ...@@ -13556,6 +13568,9 @@ msgstr ""
msgid "No pods available" msgid "No pods available"
msgstr "" msgstr ""
msgid "No policy matches this license"
msgstr ""
msgid "No preview for this file type" msgid "No preview for this file type"
msgstr "" msgstr ""
...@@ -14086,6 +14101,9 @@ msgstr "" ...@@ -14086,6 +14101,9 @@ msgstr ""
msgid "Other visibility settings have been disabled by the administrator." msgid "Other visibility settings have been disabled by the administrator."
msgstr "" msgstr ""
msgid "Out-of-compliance with this project's policies and should be removed"
msgstr ""
msgid "Outbound requests" msgid "Outbound requests"
msgstr "" 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