Commit 5f6b6c8f authored by mo khan's avatar mo khan

Convert V2 report to v1 schema

This change ensures the current client side diffing code continues to
function as expected while the backend is able to advance the schema of
the report artifact. This change adds mapping classes that allows
mapping from v2 schema to v1 schema of the license scan report.
parent 3b0a63ca
export default class {
static isEnabled(featureName) {
return gon && gon.features && gon.features[featureName];
}
}
/* eslint-disable class-methods-use-this */
export default class {
mapFrom(report) {
return report;
}
}
import { byLicenseNameComparator } from '../store/utils';
export default class V2 {
mapFrom(report) {
this.licenseMap = V2.createLicenseMap(report.licenses);
this.licenses = report.licenses.sort(byLicenseNameComparator).map(V2.mapFromLicense);
return {
licenses: this.licenses,
dependencies: report.dependencies.map(x => this.mapFromDependency(x)),
};
}
combine(licenses) {
return licenses.reduce(
(memo, licenseId) => {
const license = this.licenseMap[licenseId];
this.incrementCountFor(license.name);
if (memo.name === null) {
return {
name: license.name,
url: license.url,
};
}
return { name: `${memo.name}, ${license.name}`, url: '' };
},
{ name: null, url: null },
);
}
incrementCountFor(licenseName) {
const legacyLicense = this.licenses.find(license => license.name === licenseName);
if (legacyLicense) legacyLicense.count += 1;
}
mapFromDependency(dependency) {
return {
license: this.combine(dependency.licenses),
dependency: {
name: dependency.name,
url: dependency.url,
description: dependency.description,
},
};
}
static mapFromLicense(license) {
return { name: license.name, count: 0 };
}
static createLicenseMap(licenses) {
return licenses.reduce((memo, item) => {
memo[item.id] = { name: item.name, url: item.url }; // eslint-disable-line no-param-reassign
return memo;
}, {});
}
}
import V1 from './mappers/v1';
import V2 from './mappers/v2';
import FeatureFlag from '~/lib/feature_flag';
const MAPPERS = { '1': V1, '2': V2 };
export default class ReportMapper {
constructor(featureEnabled = FeatureFlag.isEnabled('licenseScanV2')) {
this.featureEnabled = featureEnabled;
}
mapFrom(reportArtifact) {
const majorVersion = ReportMapper.majorVersion(reportArtifact);
return this.mapperFor(majorVersion).mapFrom(reportArtifact);
}
mapperFor(majorVersion) {
if (this.featureEnabled || majorVersion === '2') {
return new MAPPERS[majorVersion]();
}
return new V1();
}
static majorVersion(report) {
return report && report.version ? report.version.split('.')[0] : '1';
}
}
import { n__, sprintf } from '~/locale'; import { n__, sprintf } from '~/locale';
import { STATUS_FAILED, STATUS_NEUTRAL, STATUS_SUCCESS } from '~/reports/constants'; import { STATUS_FAILED, STATUS_NEUTRAL, STATUS_SUCCESS } from '~/reports/constants';
import { LICENSE_APPROVAL_STATUS } from 'ee/vue_shared/license_management/constants'; import { LICENSE_APPROVAL_STATUS } from 'ee/vue_shared/license_management/constants';
import ReportMapper from 'ee/vue_shared/license_management/report_mapper';
const toLowerCase = name => name.toLowerCase(); const toLowerCase = name => name.toLowerCase();
/** /**
...@@ -85,10 +86,13 @@ export const parseLicenseReportMetrics = (headMetrics, baseMetrics, managedLicen ...@@ -85,10 +86,13 @@ export const parseLicenseReportMetrics = (headMetrics, baseMetrics, managedLicen
if (!headMetrics && !baseMetrics) { if (!headMetrics && !baseMetrics) {
return []; return [];
} }
const reportMapper = new ReportMapper();
const headReport = reportMapper.mapFrom(headMetrics);
const baseReport = reportMapper.mapFrom(baseMetrics);
const headLicenses = headMetrics.licenses || []; const headLicenses = headReport.licenses || [];
const headDependencies = headMetrics.dependencies || []; const headDependencies = headReport.dependencies || [];
const baseLicenses = baseMetrics.licenses || []; const baseLicenses = baseReport.licenses || [];
const managedLicenseList = managedLicenses || []; const managedLicenseList = managedLicenses || [];
if (!headLicenses.length && !headDependencies.length) return []; if (!headLicenses.length && !headDependencies.length) return [];
......
---
title: Update the frontend diffing code to support v2 license scan reports
merge_request: 18105
author:
type: changed
import ReportMapper from 'ee/vue_shared/license_management/report_mapper';
describe('mapFrom', () => {
let subject = null;
beforeEach(() => {
subject = new ReportMapper(true);
});
it('converts a v2 schema report to v1', () => {
const report = {
version: '2.0',
licenses: [
{ id: 'MIT', name: 'MIT License', url: 'https://opensource.org/licenses/MIT' },
{ id: 'BSD', name: 'BSD License', url: 'https://opensource.org/licenses/BSD' },
],
dependencies: [
{ name: 'x', url: 'https://www.example.com/x', licenses: ['MIT'], description: 'X' },
{ name: 'y', url: 'https://www.example.com/y', licenses: ['BSD'], description: 'Y' },
{
name: 'z',
url: 'https://www.example.com/z',
licenses: ['BSD', 'MIT'],
description: 'Z',
},
],
};
const result = subject.mapFrom(report);
expect(result).toMatchObject({
licenses: [{ name: 'BSD License', count: 2 }, { name: 'MIT License', count: 2 }],
dependencies: [
{
license: {
name: 'MIT License',
url: 'https://opensource.org/licenses/MIT',
},
dependency: {
name: 'x',
url: 'https://www.example.com/x',
description: 'X',
},
},
{
license: {
name: 'BSD License',
url: 'https://opensource.org/licenses/BSD',
},
dependency: {
name: 'y',
url: 'https://www.example.com/y',
description: 'Y',
},
},
{
license: {
name: 'BSD License, MIT License',
url: '',
},
dependency: {
name: 'z',
url: 'https://www.example.com/z',
description: 'Z',
},
},
],
});
});
it('returns a v1 schema report', () => {
const report = {
licenses: [],
dependencies: [],
};
expect(subject.mapFrom(report)).toBe(report);
});
it('returns a v1.1 schema report', () => {
const report = {
version: '1.1',
licenses: [],
dependencies: [],
};
expect(subject.mapFrom(report)).toBe(report);
});
it('ignores undefined versions', () => {
const report = {};
expect(subject.mapFrom(report)).toBe(report);
});
it('ignores undefined reports', () => {
const report = undefined;
expect(subject.mapFrom(report)).toBe(report);
});
it('ignores null reports', () => {
const report = null;
expect(subject.mapFrom(report)).toBe(report);
});
});
...@@ -17,6 +17,11 @@ import { ...@@ -17,6 +17,11 @@ import {
} from 'ee_spec/license_management/mock_data'; } from 'ee_spec/license_management/mock_data';
describe('utils', () => { describe('utils', () => {
beforeEach(() => {
gon.features = gon.features || {};
gon.features.licenseScanV2 = true;
});
describe('parseLicenseReportMetrics', () => { describe('parseLicenseReportMetrics', () => {
it('should return empty result, if no parameters are given', () => { it('should return empty result, if no parameters are given', () => {
const result = parseLicenseReportMetrics(); const result = parseLicenseReportMetrics();
...@@ -63,6 +68,97 @@ describe('utils', () => { ...@@ -63,6 +68,97 @@ describe('utils', () => {
expect(result[1].id).toBe(blacklistedLicense.id); expect(result[1].id).toBe(blacklistedLicense.id);
}); });
it('compares a v2 report with a v2 report', () => {
const policies = [{ id: 100, name: 'BSD License', approvalStatus: 'blacklisted' }];
const baseReport = {
version: '2.0',
licenses: [{ id: 'MIT', name: 'MIT License', url: 'https://opensource.org/licenses/MIT' }],
dependencies: [
{ name: 'x', url: 'https://www.example.com/x', licenses: ['MIT'], description: 'X' },
],
};
const headReport = {
version: '2.0',
licenses: [
{ id: 'MIT', name: 'MIT License', url: 'https://opensource.org/licenses/MIT' },
{ id: 'BSD', name: 'BSD License', url: 'https://opensource.org/licenses/BSD' },
],
dependencies: [
{ name: 'x', url: 'https://www.example.com/x', licenses: ['MIT'], description: 'X' },
{ name: 'y', url: 'https://www.example.com/y', licenses: ['BSD'], description: 'Y' },
{
name: 'z',
url: 'https://www.example.com/z',
licenses: ['BSD', 'MIT'],
description: 'Z',
},
],
};
const result = parseLicenseReportMetrics(headReport, baseReport, policies);
expect(result.length).toBe(1);
expect(result[0]).toEqual(
jasmine.objectContaining({
id: 100,
approvalStatus: 'blacklisted',
count: 2,
status: 'failed',
name: 'BSD License',
packages: [{ name: 'y', url: 'https://www.example.com/y', description: 'Y' }],
}),
);
});
it('compares a v1 report with a v2 report', () => {
const policies = [{ id: 101, name: 'BSD License', approvalStatus: 'blacklisted' }];
const baseReport = {
licenses: [{ name: 'MIT License', url: 'https://opensource.org/licenses/MIT' }],
dependencies: [
{
license: {
name: 'MIT License',
url: 'https://opensource.org/licenses/MIT',
},
dependency: { name: 'x', url: 'https://www.example.com/x', description: 'X' },
},
],
};
const headReport = {
version: '2.0',
licenses: [
{ id: 'MIT', name: 'MIT License', url: 'https://opensource.org/licenses/MIT' },
{ id: 'BSD', name: 'BSD License', url: 'https://opensource.org/licenses/BSD' },
],
dependencies: [
{ name: 'x', url: 'https://www.example.com/x', licenses: ['MIT'], description: 'X' },
{ name: 'y', url: 'https://www.example.com/y', licenses: ['BSD'], description: 'Y' },
{
name: 'z',
url: 'https://www.example.com/z',
licenses: ['BSD', 'MIT'],
description: 'Z',
},
],
};
const result = parseLicenseReportMetrics(headReport, baseReport, policies);
expect(result.length).toBe(1);
expect(result[0]).toEqual(
jasmine.objectContaining({
id: 101,
approvalStatus: 'blacklisted',
count: 2,
status: 'failed',
name: 'BSD License',
packages: [{ name: 'y', url: 'https://www.example.com/y', description: 'Y' }],
}),
);
});
it('matches using a case insensitive match on license name', () => { it('matches using a case insensitive match on license name', () => {
const headReport = { licenses: [{ count: 1, name: 'BSD' }], dependencies: [] }; const headReport = { licenses: [{ count: 1, name: 'BSD' }], dependencies: [] };
const baseReport = { licenses: [{ count: 1, name: 'bsd' }], dependencies: [] }; const baseReport = { licenses: [{ count: 1, name: 'bsd' }], dependencies: [] };
......
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