diff --git a/ee/app/assets/javascripts/license_compliance/components/app.vue b/ee/app/assets/javascripts/license_compliance/components/app.vue index 348dd39eba7a59abf7d16f6f2a5704491abc90ee..0a2357fbe48de74817f8ad46f490abd6b628989e 100644 --- a/ee/app/assets/javascripts/license_compliance/components/app.vue +++ b/ee/app/assets/javascripts/license_compliance/components/app.vue @@ -57,9 +57,6 @@ export default { hasEmptyState() { return Boolean(!this.isJobSetUp || this.isJobFailed); }, - hasLicensePolicyList() { - return Boolean(this.glFeatures.licensePolicyList); - }, licenseCount() { return this.pageInfo.total; }, @@ -134,31 +131,24 @@ export default { <template v-else>{{ s__('Licenses|Specified policies in this project') }}</template> </header> - <!-- TODO: Remove feature flag --> - <template v-if="hasLicensePolicyList"> - <gl-tabs v-model="tabIndex" content-class="pt-0"> - <gl-tab data-testid="licensesTab"> - <template #title> - <span data-testid="licensesTabTitle">{{ s__('Licenses|Detected in Project') }}</span> - <gl-badge pill>{{ licenseCount }}</gl-badge> - </template> - - <detected-licenses-table /> - </gl-tab> + <gl-tabs v-model="tabIndex" content-class="pt-0"> + <gl-tab data-testid="licensesTab"> + <template #title> + <span data-testid="licensesTabTitle">{{ s__('Licenses|Detected in Project') }}</span> + <gl-badge pill>{{ licenseCount }}</gl-badge> + </template> - <gl-tab data-testid="policiesTab"> - <template #title> - <span data-testid="policiesTabTitle">{{ s__('Licenses|Policies') }}</span> - <gl-badge pill>{{ policyCount }}</gl-badge> - </template> + <detected-licenses-table /> + </gl-tab> - <license-management /> - </gl-tab> - </gl-tabs> - </template> + <gl-tab data-testid="policiesTab"> + <template #title> + <span data-testid="policiesTabTitle">{{ s__('Licenses|Policies') }}</span> + <gl-badge pill>{{ policyCount }}</gl-badge> + </template> - <template v-else> - <detected-licenses-table class="mt-3" /> - </template> + <license-management /> + </gl-tab> + </gl-tabs> </div> </template> diff --git a/ee/app/assets/javascripts/vue_shared/license_compliance/license_management.vue b/ee/app/assets/javascripts/vue_shared/license_compliance/license_management.vue index a2f9881fa1056b79c791d8e9186e813531e3bae9..545ec65f943dc245e8c0821811a12b2bc1ad034b 100644 --- a/ee/app/assets/javascripts/vue_shared/license_compliance/license_management.vue +++ b/ee/app/assets/javascripts/vue_shared/license_compliance/license_management.vue @@ -1,8 +1,8 @@ <script> +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { mapState, mapGetters, mapActions } from 'vuex'; -import { GlButton, GlLoadingIcon } from '@gitlab/ui'; +import { GlButton, GlLoadingIcon, GlIcon, GlPopover } from '@gitlab/ui'; import { s__ } from '~/locale'; -import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { LICENSE_MANAGEMENT } from 'ee/vue_shared/license_compliance/store/constants'; import AddLicenseForm from './components/add_license_form.vue'; import AdminLicenseManagementRow from './components/admin_license_management_row.vue'; @@ -20,6 +20,8 @@ export default { LicenseManagementRow, GlButton, GlLoadingIcon, + GlIcon, + GlPopover, PaginatedList, LicenseApprovals, }, @@ -27,10 +29,6 @@ export default { data() { return { formIsOpen: false, - tableHeaders: [ - { className: 'section-70', label: s__('Licenses|Policy') }, - { className: 'section-30', label: s__('Licenses|Name') }, - ], }; }, computed: { @@ -46,6 +44,9 @@ export default { showLoadingSpinner() { return this.isLoadingManagedLicenses && !this.hasPendingLicenses; }, + isTooltipEnabled() { + return Boolean(this.glFeatures.licenseComplianceDeniesMr); + }, }, watch: { isAddingNewLicense(isAddingNewLicense) { @@ -103,14 +104,40 @@ export default { </div> <template v-else> - <div - v-for="header in tableHeaders" - :key="header.label" - class="table-section" - :class="header.className" - role="rowheader" - > - {{ header.label }} + <div class="table-section gl-d-flex gl-pl-2 section-70" role="rowheader"> + {{ s__('Licenses|Policy') }} + <template v-if="isTooltipEnabled"> + <gl-icon + ref="reportInfo" + name="question" + class="text-info gl-ml-1 gl-cursor-pointer" + :aria-label="__('help')" + :size="14" + /> + <gl-popover + :target="() => $refs.reportInfo.$el" + placement="bottom" + triggers="click blur" + :css-classes="['gl-mt-3']" + > + <div class="h5">{{ __('Allowed') }}</div> + <span class="text-secondary"> + {{ s__('Licenses|Acceptable license to be used in the project') }}</span + > + <div class="h5">{{ __('Denied') }}</div> + <span class="text-secondary"> + {{ + s__( + 'Licenses|Disallow Merge request if detected and will instruct the developer to remove', + ) + }}</span + > + </gl-popover> + </template> + </div> + + <div class="table-section section-30" role="rowheader"> + {{ s__('Licenses|Name') }} </div> </template> </template> diff --git a/ee/app/controllers/projects/licenses_controller.rb b/ee/app/controllers/projects/licenses_controller.rb index 86abed90e15b1ef9c67f7e19bb15cd92aae53563..9caf37be53e7dbba407e1fdd68292cc7f049b8b1 100644 --- a/ee/app/controllers/projects/licenses_controller.rb +++ b/ee/app/controllers/projects/licenses_controller.rb @@ -5,8 +5,8 @@ class LicensesController < Projects::ApplicationController before_action :authorize_read_licenses!, only: [:index] before_action :authorize_admin_software_license_policy!, only: [:create, :update] before_action do - push_frontend_feature_flag(:license_policy_list, default_enabled: true) push_frontend_feature_flag(:license_approvals, default_enabled: false) + push_frontend_feature_flag(:license_compliance_denies_mr, default_enabled: false) end def index diff --git a/ee/spec/frontend/license_compliance/components/app_spec.js b/ee/spec/frontend/license_compliance/components/app_spec.js index cf9f8c20f37e835fb860e4d14b8499b463ae82ef..566ab10344d1d9dd973c453e5542d5d929560383 100644 --- a/ee/spec/frontend/license_compliance/components/app_spec.js +++ b/ee/spec/frontend/license_compliance/components/app_spec.js @@ -164,7 +164,7 @@ describe('Project Licenses', () => { }); }); - describe('when licensePolicyList feature flag is enabled', () => { + describe('when page is shown', () => { beforeEach(() => { createComponent({ state: { @@ -175,11 +175,6 @@ describe('Project Licenses', () => { status: REPORT_STATUS.ok, }, }, - options: { - provide: { - glFeatures: { licensePolicyList: true }, - }, - }, }); }); @@ -231,9 +226,6 @@ describe('Project Licenses', () => { pageInfo: 1, }, options: { - provide: { - glFeatures: { licensePolicyList: true }, - }, mount: true, }, }); @@ -274,9 +266,6 @@ describe('Project Licenses', () => { pageInfo, }, options: { - provide: { - glFeatures: { licensePolicyList: true }, - }, mount: true, }, }); @@ -334,43 +323,4 @@ describe('Project Licenses', () => { }); }); }); - - describe('when licensePolicyList feature flag is disabled', () => { - beforeEach(() => { - createComponent({ - state: { - initialized: true, - reportInfo: { - jobPath: '/', - generatedAt: '', - status: REPORT_STATUS.ok, - }, - }, - options: { - provide: { - glFeatures: { licensePolicyList: false }, - }, - }, - }); - }); - - it('only renders the "Detected in project" table', () => { - expect(wrapper.find(DetectedLicensesTable).exists()).toBe(true); - expect(wrapper.find(LicenseManagement).exists()).toBe(false); - }); - - it('renders no "Policies" table', () => { - expect(wrapper.find(GlTabs).exists()).toBe(false); - expect(wrapper.find(GlTab).exists()).toBe(false); - }); - - it('renders the pipeline info', () => { - expect(wrapper.find(PipelineInfo).exists()).toBe(true); - }); - - it('renders no tabs', () => { - expect(wrapper.find(GlTabs).exists()).toBe(false); - expect(wrapper.find(GlTab).exists()).toBe(false); - }); - }); }); diff --git a/ee/spec/frontend/vue_shared/license_compliance/license_management_spec.js b/ee/spec/frontend/vue_shared/license_compliance/license_management_spec.js index d1640b86c3cc0fa332f45b15282d29c0ddbe01d4..b04af8af97cada69490f6dc1afe56cb1780bd095 100644 --- a/ee/spec/frontend/vue_shared/license_compliance/license_management_spec.js +++ b/ee/spec/frontend/vue_shared/license_compliance/license_management_spec.js @@ -1,5 +1,5 @@ import { shallowMount } from '@vue/test-utils'; -import { GlButton, GlLoadingIcon } from '@gitlab/ui'; +import { GlButton, GlLoadingIcon, GlIcon, GlPopover } from '@gitlab/ui'; import Vue from 'vue'; import Vuex from 'vuex'; import LicenseManagement from 'ee/vue_shared/license_compliance/license_management.vue'; @@ -29,8 +29,10 @@ const PaginatedListMock = { }; const noop = () => {}; +const findIcon = () => wrapper.find(GlIcon); +const findPopover = () => wrapper.find(GlPopover); -const createComponent = ({ state, getters, props, actionMocks, isAdmin, options }) => { +const createComponent = ({ state, getters, props, actionMocks, isAdmin, options, provide }) => { const fakeStore = new Vuex.Store({ modules: { licenseManagement: { @@ -63,6 +65,10 @@ const createComponent = ({ state, getters, props, actionMocks, isAdmin, options stubs: { PaginatedList: PaginatedListMock, }, + provide: { + glFeatures: { licenseComplianceDeniesMr: false }, + ...provide, + }, store: fakeStore, ...options, }); @@ -190,6 +196,24 @@ describe('License Management', () => { expect(wrapper.find(AdminLicenseManagementRow).exists()).toBe(true); }); }); + + describe.each([true, false])( + 'when licenseComplianceDeniesMr feature flag is %p', + licenseComplianceDeniesMr => { + it('should not show the developer only tooltip', () => { + createComponent({ + state: { isLoadingManagedLicenses: false }, + isAdmin: true, + provide: { + glFeatures: { licenseComplianceDeniesMr }, + }, + }); + + expect(findIcon().exists()).toBe(false); + expect(findPopover().exists()).toBe(false); + }); + }, + ); }); describe('when developer', () => { @@ -228,6 +252,28 @@ describe('License Management', () => { expect(wrapper.find(AdminLicenseManagementRow).exists()).toBe(false); }); }); + + describe.each` + licenseComplianceDeniesMr | should + ${true} | ${'should'} + ${false} | ${'should not'} + `( + 'when licenseComplianceDeniesMr feature flag is $licenseComplianceDeniesMr', + ({ licenseComplianceDeniesMr, should }) => { + it(`${should} show the developer only tooltip`, () => { + createComponent({ + state: { isLoadingManagedLicenses: false }, + isAdmin: false, + provide: { + glFeatures: { licenseComplianceDeniesMr }, + }, + }); + + expect(findIcon().exists()).toBe(licenseComplianceDeniesMr); + expect(findPopover().exists()).toBe(licenseComplianceDeniesMr); + }); + }, + ); }); }); }); diff --git a/locale/gitlab.pot b/locale/gitlab.pot index e2891e57cc5831a12a59602e54c708dcd3daef48..bf4071427012b977f0e9bc6e0174d3037bd98aae 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -2238,6 +2238,9 @@ msgstr "" msgid "Allow users to request access (if visibility is public or internal)" msgstr "" +msgid "Allowed" +msgstr "" + msgid "Allowed Geo IP" msgstr "" @@ -7441,6 +7444,9 @@ msgstr "" msgid "Deletion pending. This project will be removed on %{date}. Repository and other project resources are read-only." msgstr "" +msgid "Denied" +msgstr "" + msgid "Denied authorization of chat nickname %{user_name}." msgstr "" @@ -13466,6 +13472,9 @@ msgstr "" msgid "Licenses|%{remainingComponentsCount} more" msgstr "" +msgid "Licenses|Acceptable license to be used in the project" +msgstr "" + msgid "Licenses|Component" msgstr "" @@ -13478,6 +13487,9 @@ msgstr "" msgid "Licenses|Detected licenses that are out-of-compliance with the project's assigned policies" msgstr "" +msgid "Licenses|Disallow Merge request if detected and will instruct the developer to remove" +msgstr "" + msgid "Licenses|Displays licenses detected in the project, based on the %{linkStart}latest successful%{linkEnd} scan" msgstr ""