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 = { ...@@ -4,6 +4,10 @@ export const LICENSE_APPROVAL_STATUS = {
BLACKLISTED: 'blacklisted', 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 = [ export const KNOWN_LICENSES = [
'AGPL-1.0', 'AGPL-1.0',
'AGPL-3.0', 'AGPL-3.0',
......
...@@ -56,9 +56,10 @@ const getLicenseStatusByName = (managedLicenses = [], licenseName) => ...@@ -56,9 +56,10 @@ const getLicenseStatusByName = (managedLicenses = [], licenseName) =>
managedLicenses.find(license => caseInsensitiveMatch(license.name, licenseName)) || {}; managedLicenses.find(license => caseInsensitiveMatch(license.name, licenseName)) || {};
const getDependenciesByLicenseName = (dependencies = [], licenseName) => const getDependenciesByLicenseName = (dependencies = [], licenseName) =>
dependencies.filter(dependencyItem => dependencies.filter(dependencyItem => {
caseInsensitiveMatch(dependencyItem.license.name, licenseName), const licenses = dependencyItem.licenses || [dependencyItem.license];
); return licenses.find(license => caseInsensitiveMatch(license.name, licenseName));
});
/** /**
* *
...@@ -104,6 +105,7 @@ export const parseLicenseReportMetrics = (headMetrics, baseMetrics, managedLicen ...@@ -104,6 +105,7 @@ export const parseLicenseReportMetrics = (headMetrics, baseMetrics, managedLicen
const { id, approvalStatus } = getLicenseStatusByName(managedLicenseList, name); const { id, approvalStatus } = getLicenseStatusByName(managedLicenseList, name);
const dependencies = getDependenciesByLicenseName(headDependencies, name); const dependencies = getDependenciesByLicenseName(headDependencies, name);
const url = const url =
license.url ||
(dependencies && dependencies[0] && dependencies[0].license && dependencies[0].license.url) || (dependencies && dependencies[0] && dependencies[0].license && dependencies[0].license.url) ||
''; '';
......
import { byLicenseNameComparator } from './store/utils'; import { byLicenseNameComparator } from './store/utils';
import { VERSION_1_1 } from './constants';
export default class V2Report { export default class V2Report {
constructor(report) { constructor(report) {
...@@ -9,6 +10,7 @@ export default class V2Report { ...@@ -9,6 +10,7 @@ export default class V2Report {
toV1Schema() { toV1Schema() {
return { return {
version: VERSION_1_1,
licenses: this.licenses, licenses: this.licenses,
dependencies: this.report.dependencies.map(v2Dependency => dependencies: this.report.dependencies.map(v2Dependency =>
this.mapFromDependency(v2Dependency), this.mapFromDependency(v2Dependency),
...@@ -33,12 +35,15 @@ export default class V2Report { ...@@ -33,12 +35,15 @@ export default class V2Report {
} }
mapFromDependency({ name, description, url, licenses }) { mapFromDependency({ name, description, url, licenses }) {
const convertedLicenses = [];
const combinedLicense = this.combine(licenses, license => { const combinedLicense = this.combine(licenses, license => {
this.incrementCountFor(license.name); this.incrementCountFor(license.name);
convertedLicenses.push(license);
}); });
return { return {
license: combinedLicense, license: combinedLicense,
licenses: convertedLicenses,
dependency: { name, url, description }, 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 = '/' }) => const urlFor = ({ scheme = 'https', host = 'www.example.org', path = '/' }) =>
`${scheme}://${host}${path}`; `${scheme}://${host}${path}`;
...@@ -7,6 +12,7 @@ const licenseUrlFor = name => ...@@ -7,6 +12,7 @@ const licenseUrlFor = name =>
const dependencyUrlFor = name => urlFor({ path: `/${name}` }); const dependencyUrlFor = name => urlFor({ path: `/${name}` });
const normalizeV1License = ({ name, url = licenseUrlFor(name) }) => ({ name, url }); const normalizeV1License = ({ name, url = licenseUrlFor(name) }) => ({ name, url });
const V1 = { const V1 = {
template: () => ({ licenses: [], dependencies: [] }),
normalizeLicenseSummary: ({ name, url = licenseUrlFor(name), count = 0 }) => ({ normalizeLicenseSummary: ({ name, url = licenseUrlFor(name), count = 0 }) => ({
name, name,
url, url,
...@@ -19,7 +25,22 @@ const V1 = { ...@@ -19,7 +25,22 @@ const V1 = {
license = {}, license = {},
}) => ({ dependency: { name, url, description }, license: normalizeV1License(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 = { const V2 = {
template: () => ({ version: VERSION_2_0, licenses: [], dependencies: [] }),
normalizeLicenseSummary: ({ id, name, url = licenseUrlFor(id) }) => ({ id, name, url }), normalizeLicenseSummary: ({ id, name, url = licenseUrlFor(id) }) => ({ id, name, url }),
normalizeDependency: ({ normalizeDependency: ({
name, name,
...@@ -30,20 +51,29 @@ const V2 = { ...@@ -30,20 +51,29 @@ const V2 = {
}; };
export class Builder { export class Builder {
static for(version, template = { licenses: [], dependencies: [] }) { static for(version) {
return new Builder(version, template); 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() { static forV1(minor = '0') {
return this.for(V1); return this.for(`1.${minor}`);
} }
static forV2() { static forV2(minor = '0') {
return this.for(V2, { version: '2.0', licenses: [], dependencies: [] }); return this.for(`2.${minor}`);
} }
constructor(version, template = { licenses: [], dependencies: [] }) { constructor(version) {
this.report = template; this.report = version.template();
this.version = version; this.version = version;
} }
......
...@@ -18,6 +18,25 @@ describe('build', () => { ...@@ -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', () => { it('creates a v2 report', () => {
const result = Builder.forV2() const result = Builder.forV2()
.addLicense({ id: 'MIT', name: 'MIT License' }) .addLicense({ id: 'MIT', name: 'MIT License' })
......
...@@ -8,7 +8,7 @@ describe('mapFrom', () => { ...@@ -8,7 +8,7 @@ describe('mapFrom', () => {
subject = new ReportMapper(true); 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() const report = Builder.forV2()
.addLicense({ id: 'MIT', name: 'MIT License' }) .addLicense({ id: 'MIT', name: 'MIT License' })
.addLicense({ id: 'BSD', name: 'BSD License' }) .addLicense({ id: 'BSD', name: 'BSD License' })
...@@ -19,12 +19,16 @@ describe('mapFrom', () => { ...@@ -19,12 +19,16 @@ describe('mapFrom', () => {
const result = subject.mapFrom(report); const result = subject.mapFrom(report);
expect(result).toMatchObject( expect(result).toMatchObject(
Builder.forV1() Builder.forV1('1')
.addLicense({ name: 'BSD License', count: 2 }) .addLicense({ name: 'BSD License', count: 2 })
.addLicense({ name: 'MIT License', count: 2 }) .addLicense({ name: 'MIT License', count: 2 })
.addDependency({ name: 'x', license: { name: 'MIT License' } }) .addDependency({ name: 'x', license: { name: 'MIT License' } })
.addDependency({ name: 'y', license: { name: 'BSD 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(), .build(),
); );
}); });
......
...@@ -37,7 +37,7 @@ describe('utils', () => { ...@@ -37,7 +37,7 @@ describe('utils', () => {
const result = parseLicenseReportMetrics(licenseHeadIssues, licenseBaseIssues); const result = parseLicenseReportMetrics(licenseHeadIssues, licenseBaseIssues);
expect(result[0].name).toBe(licenseHeadIssues.licenses[0].name); 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', () => { it('should omit issues from base report', () => {
...@@ -90,7 +90,10 @@ describe('utils', () => { ...@@ -90,7 +90,10 @@ describe('utils', () => {
status: 'failed', status: 'failed',
name: 'BSD License', name: 'BSD License',
url: 'https://opensource.org/licenses/BSD', 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', () => { ...@@ -108,14 +111,15 @@ describe('utils', () => {
const headReport = Builder.forV2() const headReport = Builder.forV2()
.addLicense({ id: 'MIT', name: 'MIT License' }) .addLicense({ id: 'MIT', name: 'MIT License' })
.addLicense({ id: 'BSD', name: 'BSD 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: 'x', licenses: ['MIT'] })
.addDependency({ name: 'y', licenses: ['BSD'] }) .addDependency({ name: 'y', licenses: ['BSD'] })
.addDependency({ name: 'z', licenses: ['BSD', 'MIT'] }) .addDependency({ name: 'z', licenses: ['BSD', 'MIT', 'MPL-1.1'] })
.build(); .build();
const result = parseLicenseReportMetrics(headReport, baseReport, policies); const result = parseLicenseReportMetrics(headReport, baseReport, policies);
expect(result.length).toBe(1); expect(result.length).toBe(2);
expect(result[0]).toEqual( expect(result[0]).toEqual(
jasmine.objectContaining({ jasmine.objectContaining({
id: 101, id: 101,
...@@ -124,7 +128,22 @@ describe('utils', () => { ...@@ -124,7 +128,22 @@ describe('utils', () => {
status: 'failed', status: 'failed',
name: 'BSD License', name: 'BSD License',
url: 'https://opensource.org/licenses/BSD', 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