Commit 84da9103 authored by Fatih Acet's avatar Fatih Acet

Merge branch '13079-parse-v1-1-report' into 'master'

Display software packages with multiple software licenses correctly

See merge request gitlab-org/gitlab!19333
parents 309b1b10 0027c7dc
...@@ -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