diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue b/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue index bbe15f659f5f6d4774418557180c65f367cc57f5..0bc17de638b73e1235584a9a9f7b48023c63c79a 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue @@ -254,7 +254,7 @@ export default { class="media-body gl-display-flex gl-flex-direction-row! gl-align-self-center" data-testid="widget-extension-top-level" > - <div class="gl-flex-grow-1"> + <div class="gl-flex-grow-1" data-testid="widget-extension-top-level-summary"> <template v-if="isLoadingSummary">{{ widgetLoadingText }}</template> <template v-else-if="hasFetchError">{{ widgetErrorText }}</template> <div v-else> diff --git a/ee/app/assets/javascripts/vue_merge_request_widget/extensions/license_compliance/index.js b/ee/app/assets/javascripts/vue_merge_request_widget/extensions/license_compliance/index.js index 396cdbfcef1e86567f65334df41a159a8b60e538..6046f503adbbd458f5e8abb46269d8ea3657ed7a 100644 --- a/ee/app/assets/javascripts/vue_merge_request_widget/extensions/license_compliance/index.js +++ b/ee/app/assets/javascripts/vue_merge_request_widget/extensions/license_compliance/index.js @@ -43,6 +43,20 @@ export default { hasDeniedLicense() { return this.deniedLicenses() > 0; }, + tertiaryButtons() { + return [ + { + text: s__('ciReport|Manage Licenses'), + href: this.licenseCompliance.license_scanning.settings_path, + target: '_blank', + }, + { + text: s__('ciReport|Full Report'), + href: this.licenseCompliance.license_scanning.full_report_path, + target: '_blank', + }, + ]; + }, summary() { if (this.hasReportItems()) { if (this.hasBaseReportLicenses()) { diff --git a/ee/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/ee/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js index a859b4d48a654a7e53463ba4e04aa9aa4baa5ff0..71f04099c00ae40924cbe238f44a6ecbfd7d3347 100644 --- a/ee/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js +++ b/ee/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js @@ -125,11 +125,13 @@ export default class MergeRequestStore extends CEMergeRequestStore { license_scanning_comparison_path, license_scanning_comparison_collapsed_path, api_approvals_path, + license_scanning, }) { this.licenseCompliance = { license_scanning_comparison_path, license_scanning_comparison_collapsed_path, api_approvals_path, + license_scanning, }; } diff --git a/ee/spec/frontend/vue_mr_widget/extensions/license_compliance/index_spec.js b/ee/spec/frontend/vue_mr_widget/extensions/license_compliance/index_spec.js index a4af0c9685d75043f86c239f6e0941700bc5d2b3..0264bd62260546d5510cde751c92a2b9100a1ee9 100644 --- a/ee/spec/frontend/vue_mr_widget/extensions/license_compliance/index_spec.js +++ b/ee/spec/frontend/vue_mr_widget/extensions/license_compliance/index_spec.js @@ -4,6 +4,7 @@ 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 actions from '~/vue_merge_request_widget/components/extensions/actions.vue'; import licenseComplianceExtension from 'ee/vue_merge_request_widget/extensions/license_compliance'; import httpStatusCodes from '~/lib/utils/http_status'; import { @@ -20,23 +21,38 @@ describe('License Compliance extension', () => { registerExtension(licenseComplianceExtension); - const endpoint = '/group-name/project-name/-/merge_requests/78/license_scanning_reports'; + const licenseComparisonPath = + '/group-name/project-name/-/merge_requests/78/license_scanning_reports'; + const licenseComparisonPathCollapsed = + '/group-name/project-name/-/merge_requests/78/license_scanning_reports_collapsed'; + const fullReportPath = '/group-name/project-name/-/merge_requests/78/full_report'; + const settingsPath = '/group-name/project-name/-/licenses#licenses'; + const apiApprovalsPath = '/group-name/project-name/-/licenses#policies'; - const mockApi = (statusCode, data) => { + const mockApi = (endpoint, statusCode, data) => { mock.onGet(endpoint).reply(statusCode, data); }; const findToggleCollapsedButton = () => wrapper.findByTestId('toggle-button'); const findAllExtensionListItems = () => wrapper.findAllByTestId('extension-list-item'); + const findActionButtons = () => wrapper.findComponent(actions); + const findByHrefAttribute = (href) => wrapper.find(`[href="${href}"]`); + const findLicenseScanningReport = () => findByHrefAttribute(settingsPath); + const findFullReport = () => findByHrefAttribute(fullReportPath); + const findSummary = () => wrapper.findByTestId('widget-extension-top-level-summary'); const createComponent = () => { wrapper = mountExtended(extensionsContainer, { propsData: { mr: { licenseCompliance: { - license_scanning_comparison_path: endpoint, - license_scanning_comparison_collapsed_path: endpoint, - api_approvals_path: endpoint, + license_scanning_comparison_path: licenseComparisonPath, + license_scanning_comparison_collapsed_path: licenseComparisonPathCollapsed, + api_approvals_path: apiApprovalsPath, + license_scanning: { + settings_path: settingsPath, + full_report_path: fullReportPath, + }, }, }, }, @@ -54,75 +70,98 @@ describe('License Compliance extension', () => { describe('summary', () => { it('displays loading text', () => { - mockApi(httpStatusCodes.OK, licenseComplianceSuccess); + mockApi(licenseComparisonPathCollapsed, httpStatusCodes.OK, licenseComplianceSuccess); createComponent(); - expect(wrapper.text()).toBe('License Compliance test metrics results are being parsed'); + expect(findSummary().text()).toBe('License Compliance test metrics results are being parsed'); }); it('displays failed loading text', async () => { - mockApi(httpStatusCodes.INTERNAL_SERVER_ERROR); + mockApi(licenseComparisonPathCollapsed, httpStatusCodes.INTERNAL_SERVER_ERROR); createComponent(); await waitForPromises(); - expect(wrapper.text()).toBe('License Compliance failed loading results'); + expect(findSummary().text()).toBe('License Compliance failed loading results'); }); it('displays no licenses', async () => { - mockApi(httpStatusCodes.OK, licenseComplianceEmpty); + mockApi(licenseComparisonPathCollapsed, httpStatusCodes.OK, licenseComplianceEmpty); createComponent(); await waitForPromises(); - expect(wrapper.text()).toBe( + expect(findSummary().text()).toBe( 'License Compliance detected no licenses for the source branch only', ); }); it('displays new licenses count', async () => { - mockApi(httpStatusCodes.OK, licenseComplianceSuccess); + mockApi(licenseComparisonPathCollapsed, httpStatusCodes.OK, licenseComplianceSuccess); createComponent(); await waitForPromises(); - expect(wrapper.text()).toBe( + expect(findSummary().text()).toBe( 'License Compliance detected 3 licenses for the source branch only', ); }); it('displays removed licenses count', async () => { - mockApi(httpStatusCodes.OK, licenseComplianceRemovedLicenses); + mockApi(licenseComparisonPathCollapsed, httpStatusCodes.OK, licenseComplianceRemovedLicenses); createComponent(); await waitForPromises(); - expect(wrapper.text()).toBe( + expect(findSummary().text()).toBe( 'License Compliance detected no licenses for the source branch only', ); }); it('displays new and removed licenses count', async () => { - mockApi(httpStatusCodes.OK, licenseComplianceNewAndRemovedLicenses); + mockApi( + licenseComparisonPathCollapsed, + httpStatusCodes.OK, + licenseComplianceNewAndRemovedLicenses, + ); createComponent(); await waitForPromises(); - expect(wrapper.text()).toBe( + expect(findSummary().text()).toBe( 'License Compliance detected 3 licenses for the source branch only', ); }); }); + describe('actions buttons', () => { + it('displays manage licenses and full report links', async () => { + mockApi(licenseComparisonPathCollapsed, httpStatusCodes.OK, licenseComplianceSuccess); + + createComponent(); + + await waitForPromises(); + + expect(findLicenseScanningReport().exists()).toBe(true); + expect(findLicenseScanningReport().text()).toBe('Manage Licenses'); + + expect(findFullReport().exists()).toBe(true); + expect(findFullReport().text()).toBe('Full Report'); + + expect(findActionButtons().exists()).toBe(true); + }); + }); + describe('expanded data', () => { describe('with new licenses', () => { beforeEach(async () => { - mockApi(httpStatusCodes.OK, licenseComplianceSuccessExpanded); + mockApi(licenseComparisonPathCollapsed, httpStatusCodes.OK, licenseComplianceSuccess); + mockApi(licenseComparisonPath, httpStatusCodes.OK, licenseComplianceSuccessExpanded); createComponent(); diff --git a/locale/gitlab.pot b/locale/gitlab.pot index c3f74309bc67b7dc6cff4d805e9a0cf76a16674f..ac085946e3634638690f61608c02f1952aa7061d 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -44417,6 +44417,9 @@ msgstr "" msgid "ciReport|Found %{issuesWithCount}" msgstr "" +msgid "ciReport|Full Report" +msgstr "" + msgid "ciReport|IaC Scanning" msgstr "" @@ -44455,6 +44458,9 @@ msgstr "" msgid "ciReport|Loading Code Quality report" msgstr "" +msgid "ciReport|Manage Licenses" +msgstr "" + msgid "ciReport|Manage licenses" msgstr ""