Commit 0027c7dc authored by mo khan's avatar mo khan Committed by Fatih Acet

Display packages with multiple licenses correctly

* v1.0 had a defect where multiple licenses were squashed together.
* v1.1 include support for listing multiple licenses for each package.
* v2.0 is the latest version but we are moving processing to the backend

This change includes the minimum changes necessary to keep the existing
js code working while we port the processing to the backend.
parent 309b1b10
......@@ -4,6 +4,10 @@ export const LICENSE_APPROVAL_STATUS = {
BLACKLISTED: 'blacklisted',
};
export const VERSION_1_0 = '1.0';
export const VERSION_1_1 = '1.1';
export const VERSION_2_0 = '2.0';
export const KNOWN_LICENSES = [
'AGPL-1.0',
'AGPL-3.0',
......
......@@ -56,9 +56,10 @@ const getLicenseStatusByName = (managedLicenses = [], licenseName) =>
managedLicenses.find(license => caseInsensitiveMatch(license.name, licenseName)) || {};
const getDependenciesByLicenseName = (dependencies = [], licenseName) =>
dependencies.filter(dependencyItem =>
caseInsensitiveMatch(dependencyItem.license.name, licenseName),
);
dependencies.filter(dependencyItem => {
const licenses = dependencyItem.licenses || [dependencyItem.license];
return licenses.find(license => caseInsensitiveMatch(license.name, licenseName));
});
/**
*
......@@ -104,6 +105,7 @@ export const parseLicenseReportMetrics = (headMetrics, baseMetrics, managedLicen
const { id, approvalStatus } = getLicenseStatusByName(managedLicenseList, name);
const dependencies = getDependenciesByLicenseName(headDependencies, name);
const url =
license.url ||
(dependencies && dependencies[0] && dependencies[0].license && dependencies[0].license.url) ||
'';
......
import { byLicenseNameComparator } from './store/utils';
import { VERSION_1_1 } from './constants';
export default class V2Report {
constructor(report) {
......@@ -9,6 +10,7 @@ export default class V2Report {
toV1Schema() {
return {
version: VERSION_1_1,
licenses: this.licenses,
dependencies: this.report.dependencies.map(v2Dependency =>
this.mapFromDependency(v2Dependency),
......@@ -33,12 +35,15 @@ export default class V2Report {
}
mapFromDependency({ name, description, url, licenses }) {
const convertedLicenses = [];
const combinedLicense = this.combine(licenses, license => {
this.incrementCountFor(license.name);
convertedLicenses.push(license);
});
return {
license: combinedLicense,
licenses: convertedLicenses,
dependency: { name, url, description },
};
}
......
---
title: Display packages with multiple licenses
merge_request: 19333
author:
type: fixed
import { LICENSE_APPROVAL_STATUS } from 'ee/vue_shared/license_management/constants';
import {
LICENSE_APPROVAL_STATUS,
VERSION_1_0,
VERSION_1_1,
VERSION_2_0,
} from 'ee/vue_shared/license_management/constants';
const urlFor = ({ scheme = 'https', host = 'www.example.org', path = '/' }) =>
`${scheme}://${host}${path}`;
......@@ -7,6 +12,7 @@ const licenseUrlFor = name =>
const dependencyUrlFor = name => urlFor({ path: `/${name}` });
const normalizeV1License = ({ name, url = licenseUrlFor(name) }) => ({ name, url });
const V1 = {
template: () => ({ licenses: [], dependencies: [] }),
normalizeLicenseSummary: ({ name, url = licenseUrlFor(name), count = 0 }) => ({
name,
url,
......@@ -19,7 +25,22 @@ const V1 = {
license = {},
}) => ({ dependency: { name, url, description }, license: normalizeV1License(license) }),
};
const V1_1 = Object.assign(V1, {
template: () => ({ version: VERSION_1_1, licenses: [], dependencies: [] }),
normalizeDependency: ({
name,
url = dependencyUrlFor(name),
description = name.toUpperCase(),
license = {},
licenses = [normalizeV1License(license)],
}) => ({
dependency: { name, url, description },
license: normalizeV1License(license),
licenses,
}),
});
const V2 = {
template: () => ({ version: VERSION_2_0, licenses: [], dependencies: [] }),
normalizeLicenseSummary: ({ id, name, url = licenseUrlFor(id) }) => ({ id, name, url }),
normalizeDependency: ({
name,
......@@ -30,20 +51,29 @@ const V2 = {
};
export class Builder {
static for(version, template = { licenses: [], dependencies: [] }) {
return new Builder(version, template);
static for(version) {
switch (version) {
case VERSION_1_0:
return new Builder(V1);
case VERSION_1_1:
return new Builder(V1_1);
case VERSION_2_0:
return new Builder(V2);
default:
return new Builder(V1);
}
}
static forV1() {
return this.for(V1);
static forV1(minor = '0') {
return this.for(`1.${minor}`);
}
static forV2() {
return this.for(V2, { version: '2.0', licenses: [], dependencies: [] });
static forV2(minor = '0') {
return this.for(`2.${minor}`);
}
constructor(version, template = { licenses: [], dependencies: [] }) {
this.report = template;
constructor(version) {
this.report = version.template();
this.version = version;
}
......
......@@ -18,6 +18,25 @@ describe('build', () => {
});
});
it('creates a v1.1 report', () => {
const result = Builder.forV1('1')
.addLicense({ name: 'MIT License' })
.addDependency({ name: 'rails', license: { name: 'MIT License' } })
.build();
expect(result).toMatchObject({
version: '1.1',
licenses: [{ name: 'MIT License', url: 'https://opensource.org/licenses/MIT', count: 0 }],
dependencies: [
{
license: { name: 'MIT License', url: 'https://opensource.org/licenses/MIT' },
licenses: [{ name: 'MIT License', url: 'https://opensource.org/licenses/MIT' }],
dependency: { name: 'rails', description: 'RAILS', url: 'https://www.example.org/rails' },
},
],
});
});
it('creates a v2 report', () => {
const result = Builder.forV2()
.addLicense({ id: 'MIT', name: 'MIT License' })
......
......@@ -8,7 +8,7 @@ describe('mapFrom', () => {
subject = new ReportMapper(true);
});
it('converts a v2 schema report to v1', () => {
it('converts a v2 schema report to v1.1', () => {
const report = Builder.forV2()
.addLicense({ id: 'MIT', name: 'MIT License' })
.addLicense({ id: 'BSD', name: 'BSD License' })
......@@ -19,12 +19,16 @@ describe('mapFrom', () => {
const result = subject.mapFrom(report);
expect(result).toMatchObject(
Builder.forV1()
Builder.forV1('1')
.addLicense({ name: 'BSD License', count: 2 })
.addLicense({ name: 'MIT License', count: 2 })
.addDependency({ name: 'x', license: { name: 'MIT License' } })
.addDependency({ name: 'y', license: { name: 'BSD License' } })
.addDependency({ name: 'z', license: { name: 'BSD License, MIT License', url: '' } })
.addDependency({
name: 'z',
license: { name: 'BSD License, MIT License', url: '' },
licenses: [{ name: 'BSD License' }, { name: 'MIT License' }],
})
.build(),
);
});
......
......@@ -37,7 +37,7 @@ describe('utils', () => {
const result = parseLicenseReportMetrics(licenseHeadIssues, licenseBaseIssues);
expect(result[0].name).toBe(licenseHeadIssues.licenses[0].name);
expect(result[0].url).toBe(licenseHeadIssues.dependencies[0].license.url);
expect(result[0].url).toBe(licenseHeadIssues.licenses[0].url);
});
it('should omit issues from base report', () => {
......@@ -90,7 +90,10 @@ describe('utils', () => {
status: 'failed',
name: 'BSD License',
url: 'https://opensource.org/licenses/BSD',
packages: [{ name: 'y', url: 'https://www.example.org/y', description: 'Y' }],
packages: [
{ name: 'y', url: 'https://www.example.org/y', description: 'Y' },
{ name: 'z', url: 'https://www.example.org/z', description: 'Z' },
],
}),
);
});
......@@ -108,14 +111,15 @@ describe('utils', () => {
const headReport = Builder.forV2()
.addLicense({ id: 'MIT', name: 'MIT License' })
.addLicense({ id: 'BSD', name: 'BSD License' })
.addLicense({ id: 'MPL-1.1', name: 'Mozilla Public License 1.1' })
.addDependency({ name: 'x', licenses: ['MIT'] })
.addDependency({ name: 'y', licenses: ['BSD'] })
.addDependency({ name: 'z', licenses: ['BSD', 'MIT'] })
.addDependency({ name: 'z', licenses: ['BSD', 'MIT', 'MPL-1.1'] })
.build();
const result = parseLicenseReportMetrics(headReport, baseReport, policies);
expect(result.length).toBe(1);
expect(result.length).toBe(2);
expect(result[0]).toEqual(
jasmine.objectContaining({
id: 101,
......@@ -124,7 +128,22 @@ describe('utils', () => {
status: 'failed',
name: 'BSD License',
url: 'https://opensource.org/licenses/BSD',
packages: [{ name: 'y', url: 'https://www.example.org/y', description: 'Y' }],
packages: [
{ name: 'y', url: 'https://www.example.org/y', description: 'Y' },
{ name: 'z', url: 'https://www.example.org/z', description: 'Z' },
],
}),
);
expect(result[1]).toEqual(
jasmine.objectContaining({
id: undefined,
approvalStatus: undefined,
count: 1,
status: 'neutral',
name: 'Mozilla Public License 1.1',
url: 'https://opensource.org/licenses/MPL-1.1',
packages: [{ name: 'z', url: 'https://www.example.org/z', description: 'Z' }],
}),
);
});
......
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