Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
6d29ff33
Commit
6d29ff33
authored
Mar 24, 2022
by
Jannik Lehmann
Committed by
Phil Hughes
Mar 24, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
License compliance MR widget extension
* Add component and unit tests
parent
4bfa8a0e
Changes
9
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
891 additions
and
3 deletions
+891
-3
ee/app/assets/javascripts/vue_merge_request_widget/extensions/license_compliance/index.js
...rge_request_widget/extensions/license_compliance/index.js
+143
-0
ee/app/assets/javascripts/vue_merge_request_widget/extensions/license_compliance/utils.js
...rge_request_widget/extensions/license_compliance/utils.js
+7
-0
ee/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
...avascripts/vue_merge_request_widget/mr_widget_options.vue
+13
-2
ee/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
...cripts/vue_merge_request_widget/stores/mr_widget_store.js
+10
-1
ee/spec/frontend/vue_mr_widget/extensions/license_compliance/__snapshots__/index_spec.js.snap
...sions/license_compliance/__snapshots__/index_spec.js.snap
+430
-0
ee/spec/frontend/vue_mr_widget/extensions/license_compliance/index_spec.js
...vue_mr_widget/extensions/license_compliance/index_spec.js
+141
-0
ee/spec/frontend/vue_mr_widget/extensions/license_compliance/mock_data.js
.../vue_mr_widget/extensions/license_compliance/mock_data.js
+98
-0
ee/spec/frontend/vue_mr_widget/extensions/license_compliance/utils_spec.js
...vue_mr_widget/extensions/license_compliance/utils_spec.js
+10
-0
locale/gitlab.pot
locale/gitlab.pot
+39
-0
No files found.
ee/app/assets/javascripts/vue_merge_request_widget/extensions/license_compliance/index.js
0 → 100644
View file @
6d29ff33
import
{
s__
,
n__
,
__
,
sprintf
}
from
'
~/locale
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
{
EXTENSION_ICONS
}
from
'
~/vue_merge_request_widget/constants
'
;
import
{
parseDependencies
}
from
'
./utils
'
;
// TODO: Clean up both status versions as part of https://gitlab.com/gitlab-org/gitlab/-/issues/356206
const
APPROVAL_STATUS_TO_ICON
=
{
allowed
:
EXTENSION_ICONS
.
success
,
approved
:
EXTENSION_ICONS
.
success
,
denied
:
EXTENSION_ICONS
.
failed
,
blacklisted
:
EXTENSION_ICONS
.
failed
,
unclassified
:
EXTENSION_ICONS
.
notice
,
};
export
default
{
name
:
'
WidgetLicenseCompliance
'
,
i18n
:
{
label
:
s__
(
'
ciReport|License Compliance
'
),
loading
:
s__
(
'
ciReport|License Compliance test metrics results are being parsed
'
),
error
:
s__
(
'
ciReport|License Compliance failed loading results
'
),
},
expandEvent
:
'
i_testing_license_compliance_widget_total
'
,
props
:
[
'
licenseCompliance
'
],
computed
:
{
summary
()
{
if
(
this
.
collapsedData
[
0
].
new_licenses
.
length
>
0
&&
this
.
collapsedData
[
0
].
removed_licenses
.
length
>
0
)
{
const
newLicenses
=
n__
(
'
%d new license
'
,
'
%d new licenses
'
,
this
.
collapsedData
[
0
].
new_licenses
.
length
,
);
const
removedLicenses
=
n__
(
'
%d removed license
'
,
'
%d removed licenses
'
,
this
.
collapsedData
[
0
].
removed_licenses
.
length
,
);
return
sprintf
(
__
(
`License Compliance detected
${
newLicenses
}
and
${
removedLicenses
}
`
));
}
else
if
(
this
.
collapsedData
[
0
].
new_licenses
.
length
>
0
)
{
return
n__
(
'
LicenseCompliance|License Compliance detected %d new license
'
,
'
LicenseCompliance|License Compliance detected %d new licenses
'
,
this
.
collapsedData
[
0
].
new_licenses
.
length
,
);
}
else
if
(
this
.
collapsedData
[
0
].
removed_licenses
.
length
>
0
)
{
return
n__
(
'
LicenseCompliance|License Compliance detected %d removed license
'
,
'
LicenseCompliance|License Compliance detected %d removed licenses
'
,
this
.
collapsedData
[
0
].
removed_licenses
.
length
,
);
}
return
s__
(
'
LicenseCompliance|License Compliance detected no new licenses
'
);
},
statusIcon
()
{
if
(
this
.
collapsedData
[
0
].
new_licenses
.
length
===
0
)
{
return
EXTENSION_ICONS
.
success
;
}
return
EXTENSION_ICONS
.
warning
;
},
},
methods
:
{
fetchCollapsedData
()
{
const
{
license_scanning_comparison_path
}
=
this
.
licenseCompliance
;
return
Promise
.
all
([
this
.
fetchReport
(
license_scanning_comparison_path
)]).
then
(
(
values
)
=>
values
,
);
},
fetchFullData
()
{
const
{
license_scanning_comparison_path
}
=
this
.
licenseCompliance
;
return
Promise
.
all
([
this
.
fetchReport
(
license_scanning_comparison_path
)]).
then
((
values
)
=>
{
let
newLicenses
=
values
[
0
].
new_licenses
;
newLicenses
=
newLicenses
.
map
((
e
)
=>
({
status
:
e
.
classification
.
approval_status
,
icon
:
{
name
:
APPROVAL_STATUS_TO_ICON
[
e
.
classification
.
approval_status
],
},
link
:
{
href
:
e
.
url
,
text
:
e
.
name
,
},
supportingText
:
`
${
s__
(
'
License Compliance| Used by
'
)}
${
parseDependencies
(
e
.
dependencies
,
)}
`
,
}));
const
groupedLicenses
=
newLicenses
.
reduce
(
(
licenses
,
license
)
=>
({
...
licenses
,
[
license
.
status
]:
[...(
licenses
[
license
.
status
]
||
[]),
license
],
}),
{},
);
// TODO: Clean up both status versions as part of https://gitlab.com/gitlab-org/gitlab/-/issues/356206
const
licenseSections
=
[
...(
groupedLicenses
.
denied
?.
length
>
0
||
groupedLicenses
.
blacklisted
?.
length
>
0
?
[
{
header
:
s__
(
'
LicenseCompliance|Denied
'
),
text
:
s__
(
"
LicenseCompliance|Out-of-compliance with the project's policies and should be removed
"
,
),
children
:
groupedLicenses
.
denied
||
groupedLicenses
.
blacklisted
,
},
]
:
[]),
...(
groupedLicenses
.
unclassified
?.
length
>
0
?
[
{
header
:
s__
(
'
LicenseCompliance|Uncategorized
'
),
text
:
s__
(
'
LicenseCompliance|No policy matches this license
'
),
children
:
groupedLicenses
.
unclassified
,
},
]
:
[]),
...(
groupedLicenses
.
allowed
?.
length
>
0
||
groupedLicenses
.
approved
?.
length
>
0
?
[
{
header
:
s__
(
'
LicenseCompliance|Allowed
'
),
text
:
s__
(
'
LicenseCompliance|Acceptable for use in this project
'
),
children
:
groupedLicenses
.
allowed
||
groupedLicenses
.
approved
,
},
]
:
[]),
];
return
licenseSections
;
});
},
fetchReport
(
endpoint
)
{
return
axios
.
get
(
endpoint
).
then
((
res
)
=>
res
.
data
);
},
},
};
ee/app/assets/javascripts/vue_merge_request_widget/extensions/license_compliance/utils.js
0 → 100644
View file @
6d29ff33
export
const
parseDependencies
=
(
dependencies
)
=>
{
return
dependencies
.
map
((
dependency
)
=>
{
return
dependency
.
name
;
})
.
join
(
'
,
'
);
};
ee/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
View file @
6d29ff33
...
...
@@ -13,6 +13,7 @@ import loadPerformanceExtension from './extensions/load_performance';
import
browserPerformanceExtension
from
'
./extensions/browser_performance
'
;
import
statusChecksExtension
from
'
./extensions/status_checks
'
;
import
metricsExtension
from
'
./extensions/metrics
'
;
import
licenseComplianceExtension
from
'
./extensions/license_compliance
'
;
export
default
{
components
:
{
...
...
@@ -53,7 +54,7 @@ export default {
},
computed
:
{
shouldRenderLicenseReport
()
{
return
this
.
mr
.
enabledReports
?.
licenseScanning
;
return
this
.
mr
?
.
enabledReports
?.
licenseScanning
;
},
hasBrowserPerformanceMetrics
()
{
return
(
...
...
@@ -211,8 +212,18 @@ export default {
this
.
registerMetrics
();
}
},
shouldRenderLicenseReport
(
newVal
)
{
if
(
newVal
)
{
this
.
registerLicenseCompliance
();
}
},
},
methods
:
{
registerLicenseCompliance
()
{
if
(
this
.
shouldShowExtension
)
{
registerExtension
(
licenseComplianceExtension
);
}
},
registerLoadPerformance
()
{
if
(
this
.
shouldShowExtension
)
{
registerExtension
(
loadPerformanceExtension
);
...
...
@@ -464,7 +475,7 @@ export default {
</mr-widget-enable-feature-prompt>
<mr-widget-licenses
v-if=
"shouldRenderLicenseReport"
v-if=
"shouldRenderLicenseReport
&& !shouldShowExtension
"
:api-url=
"mr.licenseScanning.managed_licenses_path"
:approvals-api-path=
"mr.apiApprovalsPath"
:licenses-api-path=
"licensesApiPath"
...
...
ee/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
View file @
6d29ff33
...
...
@@ -26,10 +26,12 @@ export default class MergeRequestStore extends CEMergeRequestStore {
data
.
create_vulnerability_feedback_dismissal_path
;
this
.
visualReviewAppAvailable
=
Boolean
(
data
.
visual_review_app_available
);
this
.
appUrl
=
gon
&&
gon
.
gitlab_url
;
this
.
licenseScanning
=
data
.
license_scanning
;
this
.
initBrowserPerformanceReport
(
data
);
this
.
initLoadPerformanceReport
(
data
);
this
.
licenseScanning
=
data
.
license_scanning
;
this
.
initLicenseComplianceReport
(
data
);
this
.
metricsReportsPath
=
data
.
metrics_reports_path
;
this
.
enabledReports
=
convertObjectPropsToCamelCase
(
data
.
enabled_reports
);
...
...
@@ -119,6 +121,13 @@ export default class MergeRequestStore extends CEMergeRequestStore {
};
}
initLicenseComplianceReport
({
license_scanning_comparison_path
,
api_approvals_path
})
{
this
.
licenseCompliance
=
{
license_scanning_comparison_path
,
api_approvals_path
,
};
}
compareBrowserPerformanceMetrics
(
headMetrics
,
baseMetrics
)
{
const
headMetricsIndexed
=
MergeRequestStore
.
normalizeBrowserPerformanceMetrics
(
headMetrics
);
const
baseMetricsIndexed
=
MergeRequestStore
.
normalizeBrowserPerformanceMetrics
(
baseMetrics
);
...
...
ee/spec/frontend/vue_mr_widget/extensions/license_compliance/__snapshots__/index_spec.js.snap
0 → 100644
View file @
6d29ff33
This diff is collapsed.
Click to expand it.
ee/spec/frontend/vue_mr_widget/extensions/license_compliance/index_spec.js
0 → 100644
View file @
6d29ff33
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
{
mountExtended
}
from
'
helpers/vue_test_utils_helper
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
extensionsContainer
from
'
~/vue_merge_request_widget/components/extensions/container
'
;
import
{
registerExtension
}
from
'
~/vue_merge_request_widget/components/extensions
'
;
import
licenseComplianceExtension
from
'
ee/vue_merge_request_widget/extensions/license_compliance
'
;
import
httpStatusCodes
from
'
~/lib/utils/http_status
'
;
import
{
licenseComplianceSuccess
,
licenseComplianceRemovedLicenses
,
licenseComplianceNewAndRemovedLicenses
,
licenseComplianceEmpty
,
}
from
'
./mock_data
'
;
describe
(
'
License Compliance extension
'
,
()
=>
{
let
wrapper
;
let
mock
;
registerExtension
(
licenseComplianceExtension
);
const
endpoint
=
'
/group-name/project-name/-/merge_requests/78/license_scanning_reports
'
;
const
mockApi
=
(
statusCode
,
data
)
=>
{
mock
.
onGet
(
endpoint
).
reply
(
statusCode
,
data
);
};
const
findToggleCollapsedButton
=
()
=>
wrapper
.
findByTestId
(
'
toggle-button
'
);
const
findAllExtensionListItems
=
()
=>
wrapper
.
findAllByTestId
(
'
extension-list-item
'
);
const
createComponent
=
()
=>
{
wrapper
=
mountExtended
(
extensionsContainer
,
{
propsData
:
{
mr
:
{
licenseCompliance
:
{
license_scanning_comparison_path
:
endpoint
,
api_approvals_path
:
endpoint
,
},
},
},
});
};
beforeEach
(()
=>
{
mock
=
new
MockAdapter
(
axios
);
});
afterEach
(()
=>
{
wrapper
.
destroy
();
mock
.
restore
();
});
describe
(
'
summary
'
,
()
=>
{
it
(
'
displays loading text
'
,
()
=>
{
mockApi
(
httpStatusCodes
.
OK
,
licenseComplianceSuccess
);
createComponent
();
expect
(
wrapper
.
text
()).
toBe
(
'
License Compliance test metrics results are being parsed
'
);
});
it
(
'
displays failed loading text
'
,
async
()
=>
{
mockApi
(
httpStatusCodes
.
INTERNAL_SERVER_ERROR
);
createComponent
();
await
waitForPromises
();
expect
(
wrapper
.
text
()).
toBe
(
'
License Compliance failed loading results
'
);
});
it
(
'
displays no licenses
'
,
async
()
=>
{
mockApi
(
httpStatusCodes
.
OK
,
licenseComplianceEmpty
);
createComponent
();
await
waitForPromises
();
expect
(
wrapper
.
text
()).
toBe
(
'
License Compliance detected no new licenses
'
);
});
it
(
'
displays new licenses count
'
,
async
()
=>
{
mockApi
(
httpStatusCodes
.
OK
,
licenseComplianceSuccess
);
createComponent
();
await
waitForPromises
();
expect
(
wrapper
.
text
()).
toBe
(
'
License Compliance detected 3 new licenses
'
);
});
it
(
'
displays removed licenses count
'
,
async
()
=>
{
mockApi
(
httpStatusCodes
.
OK
,
licenseComplianceRemovedLicenses
);
createComponent
();
await
waitForPromises
();
expect
(
wrapper
.
text
()).
toBe
(
'
License Compliance detected 3 removed licenses
'
);
});
it
(
'
displays new and removed licenses count
'
,
async
()
=>
{
mockApi
(
httpStatusCodes
.
OK
,
licenseComplianceNewAndRemovedLicenses
);
createComponent
();
await
waitForPromises
();
expect
(
wrapper
.
text
()).
toBe
(
'
License Compliance detected 3 new licenses and 3 removed licenses
'
,
);
});
});
describe
(
'
expanded data
'
,
()
=>
{
describe
(
'
with new licesnes
'
,
()
=>
{
beforeEach
(
async
()
=>
{
mockApi
(
httpStatusCodes
.
OK
,
licenseComplianceSuccess
);
createComponent
();
await
waitForPromises
();
findToggleCollapsedButton
().
trigger
(
'
click
'
);
await
waitForPromises
();
});
it
(
'
displays denied licenses
'
,
async
()
=>
{
expect
(
findAllExtensionListItems
().
at
(
0
).
element
).
toMatchSnapshot
();
});
it
(
'
displays uncategorized licenses
'
,
async
()
=>
{
expect
(
findAllExtensionListItems
().
at
(
1
).
element
).
toMatchSnapshot
();
});
it
(
'
displays allowed licenses
'
,
async
()
=>
{
expect
(
findAllExtensionListItems
().
at
(
2
).
element
).
toMatchSnapshot
();
});
});
});
});
ee/spec/frontend/vue_mr_widget/extensions/license_compliance/mock_data.js
0 → 100644
View file @
6d29ff33
export
const
licenses
=
[
{
name
:
'
Academic Free License v2.1
'
,
dependencies
:
[
{
name
:
'
json-schema
'
,
version
:
'
0.4.0
'
,
package_manager
:
'
npm
'
,
blob_path
:
'
package.json
'
,
},
],
url
:
'
http://opensource.linux-mirror.org/licenses/afl-2.1.txt
'
,
classification
:
{
id
:
null
,
name
:
'
Academic Free License v2.1
'
,
approval_status
:
'
unclassified
'
,
},
count
:
1
,
},
{
name
:
'
Apache License 2.0
'
,
dependencies
:
[
{
name
:
'
websocket-driver
'
,
version
:
'
0.7.4
'
,
package_manager
:
'
npm
'
,
blob_path
:
'
package.json
'
,
},
{
name
:
'
websocket-extensions
'
,
version
:
'
0.1.4
'
,
package_manager
:
'
npm
'
,
blob_path
:
'
package.json
'
,
},
{
name
:
'
xml-name-validator
'
,
version
:
'
3.0.0
'
,
package_manager
:
'
npm
'
,
blob_path
:
'
package.json
'
,
},
],
url
:
'
https://opensource.org/licenses/Apache-2.0
'
,
classification
:
{
id
:
null
,
name
:
'
Apache License 2.0
'
,
approval_status
:
'
blacklisted
'
,
},
count
:
3
,
},
{
name
:
'
ISC License
'
,
dependencies
:
[
{
name
:
'
abbrev
'
,
version
:
'
1.1.1
'
,
package_manager
:
'
npm
'
,
blob_path
:
'
package.json
'
,
},
{
name
:
'
anymatch
'
,
version
:
'
2.0.0
'
,
package_manager
:
'
npm
'
,
blob_path
:
'
package.json
'
,
},
],
url
:
'
https://opensource.org/licenses/ISC
'
,
classification
:
{
id
:
4
,
name
:
'
ISC License
'
,
approval_status
:
'
approved
'
,
},
count
:
2
,
},
];
export
const
licenseComplianceSuccess
=
{
new_licenses
:
licenses
,
existing_licenses
:
[],
removed_licenses
:
[],
};
export
const
licenseComplianceNewAndRemovedLicenses
=
{
new_licenses
:
licenses
,
existing_licenses
:
[],
removed_licenses
:
licenses
,
};
export
const
licenseComplianceRemovedLicenses
=
{
new_licenses
:
[],
existing_licenses
:
[],
removed_licenses
:
licenses
,
};
export
const
licenseComplianceEmpty
=
{
new_licenses
:
[],
existing_licenses
:
[],
removed_licenses
:
[],
};
ee/spec/frontend/vue_mr_widget/extensions/license_compliance/utils_spec.js
0 → 100644
View file @
6d29ff33
import
{
parseDependencies
}
from
'
ee/vue_merge_request_widget/extensions/license_compliance/utils
'
;
import
{
licenses
}
from
'
./mock_data
'
;
describe
(
'
parseDependencies
'
,
()
=>
{
it
(
'
generates a string
'
,
()
=>
{
expect
(
parseDependencies
(
licenses
[
1
].
dependencies
)).
toBe
(
'
websocket-driver, websocket-extensions, xml-name-validator
'
,
);
});
});
locale/gitlab.pot
View file @
6d29ff33
...
...
@@ -335,6 +335,11 @@ msgid_plural "%d more comments"
msgstr[0] ""
msgstr[1] ""
msgid "%d new license"
msgid_plural "%d new licenses"
msgstr[0] ""
msgstr[1] ""
msgid "%d open issue"
msgid_plural "%d open issues"
msgstr[0] ""
...
...
@@ -370,6 +375,11 @@ msgid_plural "%d projects selected"
msgstr[0] ""
msgstr[1] ""
msgid "%d removed license"
msgid_plural "%d removed licenses"
msgstr[0] ""
msgstr[1] ""
msgid "%d second"
msgid_plural "%d seconds"
msgstr[0] ""
...
...
@@ -22163,6 +22173,9 @@ msgstr ""
msgid "License Compliance"
msgstr ""
msgid "License Compliance| Used by"
msgstr ""
msgid "License compliance"
msgstr ""
...
...
@@ -22181,6 +22194,9 @@ msgstr ""
msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are inactive"
msgstr ""
msgid "LicenseCompliance|Acceptable for use in this project"
msgstr ""
msgid "LicenseCompliance|Acceptable license to be used in the project"
msgstr ""
...
...
@@ -22241,6 +22257,11 @@ msgid_plural "LicenseCompliance|License Compliance detected %d new licenses and
msgstr[0] ""
msgstr[1] ""
msgid "LicenseCompliance|License Compliance detected %d removed license"
msgid_plural "LicenseCompliance|License Compliance detected %d removed licenses"
msgstr[0] ""
msgstr[1] ""
msgid "LicenseCompliance|License Compliance detected no licenses for the source branch only"
msgstr ""
...
...
@@ -22250,6 +22271,12 @@ msgstr ""
msgid "LicenseCompliance|License name"
msgstr ""
msgid "LicenseCompliance|No policy matches this license"
msgstr ""
msgid "LicenseCompliance|Out-of-compliance with the project's policies and should be removed"
msgstr ""
msgid "LicenseCompliance|Remove license"
msgstr ""
...
...
@@ -22265,6 +22292,9 @@ msgstr ""
msgid "LicenseCompliance|This license already exists in this project."
msgstr ""
msgid "LicenseCompliance|Uncategorized"
msgstr ""
msgid "LicenseCompliance|You are about to remove the license, %{name}, from this project."
msgstr ""
...
...
@@ -43843,6 +43873,15 @@ msgstr ""
msgid "ciReport|Investigate this vulnerability by creating an issue"
msgstr ""
msgid "ciReport|License Compliance"
msgstr ""
msgid "ciReport|License Compliance failed loading results"
msgstr ""
msgid "ciReport|License Compliance test metrics results are being parsed"
msgstr ""
msgid "ciReport|Load Performance"
msgstr ""
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment