diff --git a/ee/app/assets/javascripts/security_orchestration/components/policies/list_header.vue b/ee/app/assets/javascripts/security_orchestration/components/policies/list_header.vue index 8c1c67bff26863ef964c8646bbae2e0c03a5701a..5f7a0ce44af38f553d7638370c766bd4592cdbb9 100644 --- a/ee/app/assets/javascripts/security_orchestration/components/policies/list_header.vue +++ b/ee/app/assets/javascripts/security_orchestration/components/policies/list_header.vue @@ -1,6 +1,7 @@ <script> import { GlAlert, GlButton, GlIcon, GlSprintf } from '@gitlab/ui'; import { joinPaths } from '~/lib/utils/url_utility'; +import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; import { s__ } from '~/locale'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { NEW_POLICY_BUTTON_TEXT } from '../constants'; @@ -8,12 +9,14 @@ import ExperimentFeaturesBanner from './experiment_features_banner.vue'; import ProjectModal from './project_modal.vue'; export default { + BANNER_STORAGE_KEY: 'security_policies_scan_result_name_change', components: { ExperimentFeaturesBanner, GlAlert, GlButton, GlIcon, GlSprintf, + LocalStorageSync, ProjectModal, }, mixins: [glFeatureFlagMixin()], @@ -32,11 +35,16 @@ export default { newPolicyButtonText: NEW_POLICY_BUTTON_TEXT, editPolicyProjectButtonText: s__('SecurityOrchestration|Edit policy project'), viewPolicyProjectButtonText: s__('SecurityOrchestration|View policy project'), + migrationTitle: s__('SecurityOrchestration|Updated policy name'), + migrationDescription: s__( + 'SecurityOrchestration|The %{oldNameStart}Scan result policy%{oldNameEnd} is now called the %{newNameStart}Merge request approval policy%{newNameEnd} to better align with its purpose. For more details, see the release notes.', + ), }, data() { return { projectIsBeingLinked: false, showAlert: false, + migrationAlertDismissed: false, alertVariant: '', alertText: '', modalVisible: false, @@ -75,6 +83,9 @@ export default { dismissAlert() { this.showAlert = false; }, + dismissMigrationAlert() { + this.migrationAlertDismissed = true; + }, showNewPolicyModal() { this.modalVisible = true; }, @@ -88,6 +99,7 @@ export default { class="gl-mt-3" :dismissible="true" :variant="alertVariant" + data-testid="error-alert" @dismiss="dismissAlert" > {{ alertText }} @@ -151,5 +163,27 @@ export default { @updating-project="isUpdatingProject" /> </header> + <local-storage-sync + v-model="migrationAlertDismissed" + :storage-key="$options.BANNER_STORAGE_KEY" + > + <gl-alert + v-if="!migrationAlertDismissed" + class="gl-mt-3 gl-mb-6" + :dismissible="true" + :title="$options.i18n.migrationTitle" + data-testid="migration-alert" + @dismiss="dismissMigrationAlert()" + > + <gl-sprintf :message="$options.i18n.migrationDescription"> + <template #oldName="{ content }"> + <b>{{ content }}</b> + </template> + <template #newName="{ content }"> + <b>{{ content }}</b> + </template> + </gl-sprintf> + </gl-alert> + </local-storage-sync> </div> </template> diff --git a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/policy_type_selector.vue b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/policy_type_selector.vue index d8978e0a629688866bea71e7cdf6ad364a9d2aea..2a5a5758dec3e856bc3a7d6ab66b543cdcd6a12e 100644 --- a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/policy_type_selector.vue +++ b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/policy_type_selector.vue @@ -1,7 +1,7 @@ <script> import shieldCheckIllustrationUrl from '@gitlab/svgs/dist/illustrations/secure-sm.svg?url'; import magnifyingGlassIllustrationUrl from '@gitlab/svgs/dist/illustrations/search-sm.svg?url'; -import { GlButton, GlCard, GlIcon, GlSprintf } from '@gitlab/ui'; +import { GlButton, GlCard, GlIcon, GlPopover, GlSprintf } from '@gitlab/ui'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import SafeHtml from '~/vue_shared/directives/safe_html'; import { s__, __ } from '~/locale'; @@ -12,6 +12,11 @@ const i18n = { examples: __('Examples'), selectPolicy: s__('SecurityOrchestration|Select policy'), scanResultPolicyTitle: s__('SecurityOrchestration|Merge request approval policy'), + scanResultPolicyTitlePopoverTitle: s__('SecurityOrchestration|Updated policy name'), + scanResultPolicyTitlePopoverDesc: s__( + 'SecurityOrchestration|The Scan result policy is now called the Merge request approval policy to better align with its purpose. For more details, see the release notes.', + ), + scanResultPolicySubTitle: s__('SecurityOrchestraation|(Formerly Scan result policy)'), scanResultPolicyDesc: s__( 'SecurityOrchestration|Use a merge request approval policy to create rules that check for security vulnerabilities and license compliance before merging a merge request.', ), @@ -35,6 +40,7 @@ export default { GlButton, GlCard, GlIcon, + GlPopover, GlSprintf, }, directives: { @@ -55,11 +61,14 @@ export default { text: POLICY_TYPE_COMPONENT_OPTIONS.scanResult.text.toLowerCase(), urlParameter: POLICY_TYPE_COMPONENT_OPTIONS.approval.urlParameter, title: i18n.scanResultPolicyTitle, + titlePopover: i18n.scanResultPolicyTitlePopoverTitle, + titlePopoverDesc: i18n.scanResultPolicyTitlePopoverDesc, description: i18n.scanResultPolicyDesc, example: i18n.scanResultPolicyExample, imageSrc: shieldCheckIllustrationUrl, hasMax: this.maxActiveScanResultPoliciesReached, maxPoliciesAllowed: this.maxScanResultPoliciesAllowed, + subtitle: i18n.scanResultPolicySubTitle, }, { text: POLICY_TYPE_COMPONENT_OPTIONS.scanExecution.text.toLowerCase(), @@ -93,7 +102,27 @@ export default { <img :alt="option.title" aria-hidden="true" :src="option.imageSrc" /> </div> <div class="gl-display-flex gl-flex-direction-column"> - <h4 class="gl-mt-0">{{ option.title }}</h4> + <div> + <h4 class="gl-display-inline-block gl-my-0">{{ option.title }}</h4> + <gl-icon + v-if="option.titlePopover" + id="change-icon" + name="status-active" + :size="12" + class="gl-display-inline-block gl-text-blue-500" + /> + <gl-popover + v-if="option.titlePopover" + triggers="hover focus" + :title="option.titlePopover" + :show-close-button="false" + placement="top" + target="change-icon" + :content="option.titlePopoverDesc" + :show="false" + /> + </div> + <p v-if="option.subtitle" class="gl-text-gray-400">{{ option.subtitle }}</p> <p>{{ option.description }}</p> <h5>{{ $options.i18n.examples }}</h5> <p class="gl-flex-grow-1">{{ option.example }}</p> diff --git a/ee/spec/frontend/security_orchestration/components/policies/list_header_spec.js b/ee/spec/frontend/security_orchestration/components/policies/list_header_spec.js index 32bdd8af1ae8a518b788143c52aff68f3485eda5..c054372123e87c0ce6f001638343607f74bc6458 100644 --- a/ee/spec/frontend/security_orchestration/components/policies/list_header_spec.js +++ b/ee/spec/frontend/security_orchestration/components/policies/list_header_spec.js @@ -1,6 +1,7 @@ -import { GlAlert, GlButton, GlSprintf } from '@gitlab/ui'; +import { GlButton, GlSprintf } from '@gitlab/ui'; import { nextTick } from 'vue'; import ExperimentFeaturesBanner from 'ee/security_orchestration/components/policies/experiment_features_banner.vue'; +import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; import ListHeader from 'ee/security_orchestration/components/policies/list_header.vue'; import ProjectModal from 'ee/security_orchestration/components/policies/project_modal.vue'; import { NEW_POLICY_BUTTON_TEXT } from 'ee/security_orchestration/components/constants'; @@ -13,7 +14,8 @@ describe('List Header Component', () => { const newPolicyPath = '/path/to/new/policy/page'; const projectLinkSuccessText = 'Project was linked successfully.'; - const findAlert = () => wrapper.findComponent(GlAlert); + const findErrorAlert = () => wrapper.findByTestId('error-alert'); + const findMigrationAlert = () => wrapper.findByTestId('migration-alert'); const findScanNewPolicyModal = () => wrapper.findComponent(ProjectModal); const findHeader = () => wrapper.findByRole('heading'); const findMoreInformationLink = () => wrapper.findComponent(GlButton); @@ -22,6 +24,7 @@ describe('List Header Component', () => { const findNewPolicyButton = () => wrapper.findByTestId('new-policy-button'); const findSubheader = () => wrapper.findByTestId('policies-subheader'); const findExperimentFeaturesBanner = () => wrapper.findComponent(ExperimentFeaturesBanner); + const findLocalStorageSync = () => wrapper.findComponent(LocalStorageSync); const linkSecurityPoliciesProject = async () => { findScanNewPolicyModal().vm.$emit('project-updated', { @@ -42,8 +45,8 @@ describe('List Header Component', () => { ...provide, }, stubs: { - GlSprintf, GlButton, + GlSprintf, }, }); }; @@ -64,7 +67,7 @@ describe('List Header Component', () => { status | component | findFn | exists ${'does'} | ${'edit policy project button'} | ${findEditPolicyProjectButton} | ${true} ${'does not'} | ${'view policy project button'} | ${findViewPolicyProjectButton} | ${false} - ${'does not'} | ${'alert component'} | ${findAlert} | ${false} + ${'does not'} | ${'alert component'} | ${findErrorAlert} | ${false} ${'does'} | ${'header'} | ${findHeader} | ${true} `('$status display the $component', ({ findFn, exists }) => { expect(findFn().exists()).toBe(exists); @@ -93,7 +96,7 @@ describe('List Header Component', () => { }); it('displays the alert component when scan new modal policy emits event', () => { - expect(findAlert().text()).toBe(projectLinkSuccessText); + expect(findErrorAlert().text()).toBe(projectLinkSuccessText); expect(wrapper.emitted('update-policy-list')).toStrictEqual([ [ { @@ -107,7 +110,20 @@ describe('List Header Component', () => { it('hides the previous alert when scan new modal policy is processing a new link', async () => { findScanNewPolicyModal().vm.$emit('updating-project'); await nextTick(); - expect(findAlert().exists()).toBe(false); + expect(findErrorAlert().exists()).toBe(false); + }); + }); + + describe('migration alert', () => { + it('displays the migration alert', () => { + expect(findLocalStorageSync().exists()).toBe(true); + expect(findMigrationAlert().exists()).toBe(true); + }); + + it('dismisses the alert when the dismiss button is clicked', async () => { + await findMigrationAlert().vm.$emit('dismiss'); + expect(findMigrationAlert().exists()).toBe(false); + expect(findLocalStorageSync().props().value).toBe(true); }); }); }); diff --git a/ee/spec/frontend/security_orchestration/components/policy_editor/scan_result/settings/setting_popover_spec.js b/ee/spec/frontend/security_orchestration/components/policy_editor/scan_result/settings/setting_popover_spec.js index 40bc440810fbfddea0bdf3834bd877d550a36b10..290766d4e203e012fda9b12e1a3f0acd79a98af2 100644 --- a/ee/spec/frontend/security_orchestration/components/policy_editor/scan_result/settings/setting_popover_spec.js +++ b/ee/spec/frontend/security_orchestration/components/policy_editor/scan_result/settings/setting_popover_spec.js @@ -61,7 +61,7 @@ describe('SettingPopover', () => { }); it('permanently dismisses the popover when the popover button is clicked', async () => { - createComponent({ propsData: { propsData: { showPopover: true } } }); + createComponent({ propsData: { showPopover: true } }); await findButton().vm.$emit('click'); expect(userCalloutDismissSpy).toHaveBeenCalled(); }); diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 0a57c2d8a9dac5efd8a5706793ae6a095cc9978b..cb8e3891a69695155ca46f5226d5e2c7ac062869 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -44373,6 +44373,9 @@ msgstr "" msgid "SecurityConfiguration|Vulnerability details and statistics in the merge request" msgstr "" +msgid "SecurityOrchestraation|(Formerly Scan result policy)" +msgstr "" + msgid "SecurityOrchestration| and " msgstr "" @@ -44894,6 +44897,12 @@ msgstr "" msgid "SecurityOrchestration|Summary" msgstr "" +msgid "SecurityOrchestration|The %{oldNameStart}Scan result policy%{oldNameEnd} is now called the %{newNameStart}Merge request approval policy%{newNameEnd} to better align with its purpose. For more details, see the release notes." +msgstr "" + +msgid "SecurityOrchestration|The Scan result policy is now called the Merge request approval policy to better align with its purpose. For more details, see the release notes." +msgstr "" + msgid "SecurityOrchestration|The following branches do not exist on this development project: %{branches}. Please review all protected branches to ensure the values are accurate before updating this policy." msgstr "" @@ -44954,6 +44963,9 @@ msgstr "" msgid "SecurityOrchestration|Update scan policies" msgstr "" +msgid "SecurityOrchestration|Updated policy name" +msgstr "" + msgid "SecurityOrchestration|Use a merge request approval policy to create rules that check for security vulnerabilities and license compliance before merging a merge request." msgstr ""