From 88607e9737569f31bce19a9eef3dba14b3f8466b Mon Sep 17 00:00:00 2001
From: Alexander Turinske <aturinske@gitlab.com>
Date: Thu, 18 Apr 2024 00:48:09 +0000
Subject: [PATCH] Add file path component to pipeline policy

- add file path component
- add override selector for pipeline policy
- conditionally show other message
---
 .../action/action_section.vue                 |  6 ++-
 .../pipeline_execution/constants.js           |  1 -
 .../pipeline_execution/editor_component.vue   | 10 +----
 .../action/code_block_file_path.vue           | 43 ++++++++++++++++++-
 .../action/code_block_override_selector.vue   | 40 +++++++++++++++++
 .../policy_editor/scan_execution/constants.js | 13 ++++++
 .../scan_execution/editor_component.vue       |  6 ++-
 .../scan_execution/lib/actions.js             |  4 +-
 .../action/action_section_spec.js             |  5 ++-
 .../editor_component_spec.js                  | 33 ++++++++------
 .../action/code_block_file_path_spec.js       | 39 ++++++++++++++++-
 .../code_block_override_selector_spec.js      | 40 +++++++++++++++++
 ....js => code_block_source_selector_spec.js} |  2 +-
 .../scan_execution/editor_component_spec.js   | 31 +++++++++----
 .../scan_execution/lib/actions_spec.js        | 13 ++++++
 locale/gitlab.pot                             | 12 ++++++
 16 files changed, 259 insertions(+), 39 deletions(-)
 create mode 100644 ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_execution/action/code_block_override_selector.vue
 create mode 100644 ee/spec/frontend/security_orchestration/components/policy_editor/scan_execution/action/code_block_override_selector_spec.js
 rename ee/spec/frontend/security_orchestration/components/policy_editor/scan_execution/action/{action_type_selector_spec.js => code_block_source_selector_spec.js} (96%)

diff --git a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/pipeline_execution/action/action_section.vue b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/pipeline_execution/action/action_section.vue
index 3a2318f89ab04..e117c1035fca3 100644
--- a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/pipeline_execution/action/action_section.vue
+++ b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/pipeline_execution/action/action_section.vue
@@ -1,13 +1,17 @@
 <script>
 import { ACTIONS_LABEL } from '../../constants';
+import CodeBlockFilePath from '../../scan_execution/action/code_block_file_path.vue';
 
 export default {
   i18n: {
     ACTIONS_LABEL,
   },
+  components: {
+    CodeBlockFilePath,
+  },
 };
 </script>
 
 <template>
-  <div>{{ $options.i18n.ACTIONS_LABEL }}</div>
+  <code-block-file-path />
 </template>
diff --git a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/pipeline_execution/constants.js b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/pipeline_execution/constants.js
index dbebcb5f43b34..5e9327e397890 100644
--- a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/pipeline_execution/constants.js
+++ b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/pipeline_execution/constants.js
@@ -8,5 +8,4 @@ content:
   include: ''
 `;
 
-export const ADD_CONDITION_LABEL = s__('ScanExecutionPolicy|Add condition');
 export const CONDITIONS_LABEL = s__('ScanExecutionPolicy|Conditions');
diff --git a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/pipeline_execution/editor_component.vue b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/pipeline_execution/editor_component.vue
index f25bf3f7877a2..d98784c2bb001 100644
--- a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/pipeline_execution/editor_component.vue
+++ b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/pipeline_execution/editor_component.vue
@@ -11,8 +11,8 @@ import {
 } from '../constants';
 import EditorLayout from '../editor_layout.vue';
 import DimDisableContainer from '../dim_disable_container.vue';
-import RuleSection from './rule/rule_section.vue';
 import ActionSection from './action/action_section.vue';
+import RuleSection from './rule/rule_section.vue';
 import { createPolicyObject, policyToYaml } from './utils';
 import { CONDITIONS_LABEL, DEFAULT_PIPELINE_EXECUTION_POLICY } from './constants';
 
@@ -143,13 +143,7 @@ export default {
           <div class="gl-bg-gray-10 gl-rounded-base gl-p-6"></div>
         </template>
 
-        <action-section
-          v-for="(action, index) in policy.actions"
-          :key="action.id"
-          :data-testid="`action-${index}`"
-          :action-index="index"
-          :init-action="action"
-        />
+        <action-section class="gl-mb-4 security-policies-bg-gray-10 gl-rounded-base gl-p-5" />
       </dim-disable-container>
     </template>
   </editor-layout>
diff --git a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_execution/action/code_block_file_path.vue b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_execution/action/code_block_file_path.vue
index 3e508b91251cb..b6fbd7a3a4382 100644
--- a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_execution/action/code_block_file_path.vue
+++ b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_execution/action/code_block_file_path.vue
@@ -2,6 +2,7 @@
 import {
   GlFormGroup,
   GlFormInputGroup,
+  GlIcon,
   GlInputGroupText,
   GlSprintf,
   GlFormInput,
@@ -11,10 +12,12 @@ import {
 import { s__, __ } from '~/locale';
 import { BV_SHOW_TOOLTIP, BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
 import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
 import RefSelector from '~/ref/components/ref_selector.vue';
-import CodeBlockSourceSelector from 'ee/security_orchestration/components/policy_editor/scan_execution/action/code_block_source_selector.vue';
 import GroupProjectsDropdown from 'ee/security_orchestration/components/group_projects_dropdown.vue';
 import { isGroup } from 'ee/security_orchestration/components/utils';
+import CodeBlockSourceSelector from './code_block_source_selector.vue';
+import CodeBlockOverrideSelector from './code_block_override_selector.vue';
 
 export default {
   i18n: {
@@ -22,6 +25,9 @@ export default {
     filePathCopy: s__(
       'ScanExecutionPolicy|%{boldStart}Run%{boldEnd} %{typeSelector} from the project %{projectSelector} with ref %{refSelector}',
     ),
+    pipelineFilePathCopy: s__(
+      'ScanExecutionPolicy|%{overrideSelector}into the %{boldStart}.gitlab-ci.yml%{boldEnd} with the following %{boldStart}pipeline execution file%{boldEnd} from %{projectSelector} And run with reference (Optional) %{refSelector}',
+    ),
     filePathPrependLabel: __('No project selected'),
     fileRefLabel: s__('ScanExecutionPolicy|Select ref'),
     filePathInputPlaceholder: s__('ScanExecutionPolicy|Link existing CI file'),
@@ -30,6 +36,9 @@ export default {
       "ScanExecutionPolicy|The file at that project, ref, and path doesn't exist",
     ),
     formGroupLabel: s__('ScanExecutionPolicy|file path group'),
+    selectedProjectInformation: s__(
+      'ScanExecutionPolicy|The content of this pipeline execution YAML file is included in the .gitlab-ci.yml file of the target project. All GitLab CI/CD features are supported.',
+    ),
     tooltipText: s__('ScanExecutionPolicy|Select project first, and then insert a file path'),
   },
   refSelectorTranslations: {
@@ -38,7 +47,9 @@ export default {
   SELECTED_PROJECT_TOOLTIP: 'selected-project-tooltip',
   name: 'CodeBlockFilePath',
   components: {
+    CodeBlockOverrideSelector,
     CodeBlockSourceSelector,
+    GlIcon,
     GlFormGroup,
     GlFormInputGroup,
     GlFormInput,
@@ -49,8 +60,14 @@ export default {
     RefSelector,
   },
   directives: { GlTooltip: GlTooltipDirective },
+  mixins: [glFeatureFlagMixin()],
   inject: ['namespacePath', 'rootNamespacePath', 'namespaceType'],
   props: {
+    overrideType: {
+      type: String,
+      required: false,
+      default: undefined,
+    },
     selectedType: {
       type: String,
       required: false,
@@ -78,6 +95,14 @@ export default {
     },
   },
   computed: {
+    isPipelineExecution() {
+      return this.glFeatures.pipelineExecutionPolicyType;
+    },
+    fileBlockMessage() {
+      return this.isPipelineExecution
+        ? this.$options.i18n.pipelineFilePathCopy
+        : this.$options.i18n.filePathCopy;
+    },
     isValidFilePath() {
       if (this.filePath === null) {
         return null;
@@ -123,6 +148,10 @@ export default {
     updatedFilePath(value) {
       this.$emit('update-file-path', value);
     },
+    setOverride(override) {
+      this.$emit('select-override', override);
+    },
+
     setSelectedProject(project) {
       this.$emit('select-project', project);
     },
@@ -143,7 +172,11 @@ export default {
 <template>
   <div class="gl-display-flex gl-w-full gl-flex-direction-column gl-gap-3">
     <div class="gl-display-flex gl-gap-3 gl-align-items-center gl-flex-wrap">
-      <gl-sprintf :message="$options.i18n.filePathCopy">
+      <gl-sprintf :message="fileBlockMessage">
+        <template #overrideSelector>
+          <code-block-override-selector :override-type="overrideType" @select="setOverride" />
+        </template>
+
         <template #bold="{ content }">
           <b>{{ content }}</b>
         </template>
@@ -161,6 +194,12 @@ export default {
             :state="projectAndRefState"
             @select="setSelectedProject"
           />
+          <gl-icon
+            v-if="isPipelineExecution"
+            v-gl-tooltip
+            name="question-o"
+            :title="$options.i18n.selectedProjectInformation"
+          />
         </template>
 
         <template #refSelector>
diff --git a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_execution/action/code_block_override_selector.vue b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_execution/action/code_block_override_selector.vue
new file mode 100644
index 0000000000000..68330231e8d6d
--- /dev/null
+++ b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_execution/action/code_block_override_selector.vue
@@ -0,0 +1,40 @@
+<script>
+import { GlCollapsibleListbox } from '@gitlab/ui';
+import {
+  CUSTOM_OVERRIDE_OPTIONS,
+  CUSTOM_OVERRIDE_OPTIONS_LISTBOX_ITEMS,
+  INJECT,
+} from 'ee/security_orchestration/components/policy_editor/scan_execution/constants';
+import { validateOverrideValues } from 'ee/security_orchestration/components/policy_editor/scan_execution//lib';
+
+export default {
+  CUSTOM_OVERRIDE_OPTIONS_LISTBOX_ITEMS,
+  name: 'CodeBlockOverrideSelector',
+  components: {
+    GlCollapsibleListbox,
+  },
+  props: {
+    overrideType: {
+      type: String,
+      required: false,
+      default: INJECT,
+      validator: validateOverrideValues,
+    },
+  },
+  computed: {
+    toggleText() {
+      return CUSTOM_OVERRIDE_OPTIONS[this.overrideType];
+    },
+  },
+};
+</script>
+
+<template>
+  <gl-collapsible-listbox
+    label-for="file-path"
+    :items="$options.CUSTOM_OVERRIDE_OPTIONS_LISTBOX_ITEMS"
+    :toggle-text="toggleText"
+    :selected="overrideType"
+    @select="$emit('select', $event)"
+  />
+</template>
diff --git a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_execution/constants.js b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_execution/constants.js
index 23514ee723e64..cec76f4646833 100644
--- a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_execution/constants.js
+++ b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_execution/constants.js
@@ -93,3 +93,16 @@ export const CUSTOM_ACTION_OPTIONS_KEYS = Object.keys(CUSTOM_ACTION_OPTIONS);
 export const CUSTOM_ACTION_OPTIONS_LISTBOX_ITEMS = Object.entries(
   CUSTOM_ACTION_OPTIONS,
 ).map(([value, text]) => ({ value, text }));
+
+export const INJECT = 'inject';
+export const OVERRIDE = 'override';
+
+export const CUSTOM_OVERRIDE_OPTIONS = {
+  [INJECT]: s__('ScanExecutionPolicy|Inject'),
+  [OVERRIDE]: s__('ScanExecutionPolicy|Override'),
+};
+
+export const CUSTOM_OVERRIDE_OPTIONS_KEYS = Object.keys(CUSTOM_OVERRIDE_OPTIONS);
+export const CUSTOM_OVERRIDE_OPTIONS_LISTBOX_ITEMS = Object.entries(
+  CUSTOM_OVERRIDE_OPTIONS,
+).map(([value, text]) => ({ value, text }));
diff --git a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_execution/editor_component.vue b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_execution/editor_component.vue
index a4336cd4e55d9..5537a8c847158 100644
--- a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_execution/editor_component.vue
+++ b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_execution/editor_component.vue
@@ -126,7 +126,11 @@ export default {
       return this.existingPolicy?.name;
     },
     showActionSection() {
-      return this.glFeatures.compliancePipelineInPolicies && this.customCiToggleEnabled;
+      return (
+        this.glFeatures.compliancePipelineInPolicies &&
+        this.customCiToggleEnabled &&
+        !this.glFeatures.pipelineExecutionPolicyType
+      );
     },
   },
   methods: {
diff --git a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_execution/lib/actions.js b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_execution/lib/actions.js
index 6aa89eea0632a..346d53a63cd02 100644
--- a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_execution/lib/actions.js
+++ b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_execution/lib/actions.js
@@ -1,6 +1,6 @@
 import { uniqueId } from 'lodash';
 import { REPORT_TYPE_DAST } from '~/vue_shared/security_reports/constants';
-import { CUSTOM_ACTION_KEY } from '../constants';
+import { CUSTOM_ACTION_KEY, CUSTOM_OVERRIDE_OPTIONS_KEYS } from '../constants';
 
 export const buildScannerAction = ({ scanner, siteProfile = '', scannerProfile = '', id }) => {
   const action = { scan: scanner, id: id ?? uniqueId('action_') };
@@ -16,3 +16,5 @@ export const buildScannerAction = ({ scanner, siteProfile = '', scannerProfile =
 export const buildCustomCodeAction = (id) => {
   return { scan: CUSTOM_ACTION_KEY, id: id || uniqueId('action_') };
 };
+
+export const validateOverrideValues = (value) => CUSTOM_OVERRIDE_OPTIONS_KEYS.includes(value);
diff --git a/ee/spec/frontend/security_orchestration/components/policy_editor/pipeline_execution/action/action_section_spec.js b/ee/spec/frontend/security_orchestration/components/policy_editor/pipeline_execution/action/action_section_spec.js
index 919c81255863d..2ab7115d6a8a0 100644
--- a/ee/spec/frontend/security_orchestration/components/policy_editor/pipeline_execution/action/action_section_spec.js
+++ b/ee/spec/frontend/security_orchestration/components/policy_editor/pipeline_execution/action/action_section_spec.js
@@ -1,5 +1,6 @@
 import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
 import ActionSection from 'ee/security_orchestration/components/policy_editor/pipeline_execution/action/action_section.vue';
+import CodeBlockFilePath from 'ee/security_orchestration/components/policy_editor/scan_execution/action/code_block_file_path.vue';
 
 describe('ActionSection', () => {
   let wrapper;
@@ -15,8 +16,10 @@ describe('ActionSection', () => {
     });
   };
 
+  const findCodeBlockFilePath = () => wrapper.findComponent(CodeBlockFilePath);
+
   it('renders', () => {
     factory();
-    expect(wrapper.find('div').exists()).toBe(true);
+    expect(findCodeBlockFilePath().exists()).toBe(true);
   });
 });
diff --git a/ee/spec/frontend/security_orchestration/components/policy_editor/pipeline_execution/editor_component_spec.js b/ee/spec/frontend/security_orchestration/components/policy_editor/pipeline_execution/editor_component_spec.js
index 1f649b3f31c94..239b142f2b8ea 100644
--- a/ee/spec/frontend/security_orchestration/components/policy_editor/pipeline_execution/editor_component_spec.js
+++ b/ee/spec/frontend/security_orchestration/components/policy_editor/pipeline_execution/editor_component_spec.js
@@ -2,9 +2,11 @@ import { GlEmptyState } from '@gitlab/ui';
 import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
 import { DEFAULT_ASSIGNED_POLICY_PROJECT } from 'ee/security_orchestration/constants';
 import EditorComponent from 'ee/security_orchestration/components/policy_editor/pipeline_execution/editor_component.vue';
+import ActionSection from 'ee/security_orchestration/components/policy_editor/pipeline_execution/action/action_section.vue';
 import RuleSection from 'ee/security_orchestration/components/policy_editor/pipeline_execution/rule/rule_section.vue';
 import EditorLayout from 'ee/security_orchestration/components/policy_editor/editor_layout.vue';
 import { DEFAULT_PIPELINE_EXECUTION_POLICY } from 'ee/security_orchestration/components/policy_editor/pipeline_execution/constants';
+
 import { configFileManifest, configFileObject } from './mock_data';
 
 describe('EditorComponent', () => {
@@ -29,17 +31,16 @@ describe('EditorComponent', () => {
 
   const findEmptyState = () => wrapper.findComponent(GlEmptyState);
   const findPolicyEditorLayout = () => wrapper.findComponent(EditorLayout);
+  const findActionSection = () => wrapper.findComponent(ActionSection);
   const findRuleSection = () => wrapper.findComponent(RuleSection);
 
   describe('rule mode', () => {
     it('renders the editor', () => {
       factory();
-      expect(findEmptyState().exists()).toBe(false);
-    });
-
-    it('renders the rule section', () => {
-      factory();
+      expect(findPolicyEditorLayout().exists()).toBe(true);
+      expect(findActionSection().exists()).toBe(true);
       expect(findRuleSection().exists()).toBe(true);
+      expect(findEmptyState().exists()).toBe(false);
     });
 
     it('renders the default policy editor layout', () => {
@@ -53,7 +54,7 @@ describe('EditorComponent', () => {
       );
     });
 
-    it('updates the policy', async () => {
+    it('updates the general policy properties', async () => {
       const name = 'New name';
       factory();
       expect(findPolicyEditorLayout().props('policy').name).toBe('');
@@ -73,14 +74,18 @@ describe('EditorComponent', () => {
     });
   });
 
-  it('renders the empty page', () => {
-    factory({ provide: { disableScanPolicyUpdate: true } });
-    expect(findPolicyEditorLayout().exists()).toBe(false);
+  describe('empty page', () => {
+    it('renders', () => {
+      factory({ provide: { disableScanPolicyUpdate: true } });
+      expect(findPolicyEditorLayout().exists()).toBe(false);
+      expect(findActionSection().exists()).toBe(false);
+      expect(findRuleSection().exists()).toBe(false);
 
-    const emptyState = findEmptyState();
-    expect(emptyState.exists()).toBe(true);
-    expect(emptyState.props('primaryButtonLink')).toMatch(scanPolicyDocumentationPath);
-    expect(emptyState.props('primaryButtonLink')).toMatch('pipeline-execution-policy-editor');
-    expect(emptyState.props('svgPath')).toBe(policyEditorEmptyStateSvgPath);
+      const emptyState = findEmptyState();
+      expect(emptyState.exists()).toBe(true);
+      expect(emptyState.props('primaryButtonLink')).toMatch(scanPolicyDocumentationPath);
+      expect(emptyState.props('primaryButtonLink')).toMatch('pipeline-execution-policy-editor');
+      expect(emptyState.props('svgPath')).toBe(policyEditorEmptyStateSvgPath);
+    });
   });
 });
diff --git a/ee/spec/frontend/security_orchestration/components/policy_editor/scan_execution/action/code_block_file_path_spec.js b/ee/spec/frontend/security_orchestration/components/policy_editor/scan_execution/action/code_block_file_path_spec.js
index b227654b88153..48b10707f3889 100644
--- a/ee/spec/frontend/security_orchestration/components/policy_editor/scan_execution/action/code_block_file_path_spec.js
+++ b/ee/spec/frontend/security_orchestration/components/policy_editor/scan_execution/action/code_block_file_path_spec.js
@@ -1,5 +1,6 @@
 import { GlFormInput, GlSprintf, GlFormGroup, GlFormInputGroup, GlTruncate } from '@gitlab/ui';
 import { shallowMount } from '@vue/test-utils';
+import CodeBlockOverrideSelector from 'ee/security_orchestration/components/policy_editor/scan_execution/action/code_block_override_selector.vue';
 import CodeBlockSourceSelector from 'ee/security_orchestration/components/policy_editor/scan_execution/action/code_block_source_selector.vue';
 import CodeBlockFilePath from 'ee/security_orchestration/components/policy_editor/scan_execution/action/code_block_file_path.vue';
 import GroupProjectsDropdown from 'ee/security_orchestration/components/group_projects_dropdown.vue';
@@ -12,7 +13,7 @@ describe('CodeBlockFilePath', () => {
 
   const PROJECT_ID = 'gid://gitlab/Project/29';
 
-  const createComponent = ({ propsData = {}, provide = {} } = {}) => {
+  const createComponent = ({ propsData = {}, provide = {}, stubs = {} } = {}) => {
     wrapper = shallowMount(CodeBlockFilePath, {
       propsData: {
         selectedType: INSERTED_CODE_BLOCK,
@@ -20,6 +21,7 @@ describe('CodeBlockFilePath', () => {
       },
       stubs: {
         GlSprintf,
+        ...stubs,
       },
       provide: {
         namespacePath: 'gitlab-org',
@@ -34,7 +36,9 @@ describe('CodeBlockFilePath', () => {
   const findFormInput = () => wrapper.findComponent(GlFormInput);
   const findFormInputGroup = () => wrapper.findComponent(GlFormInputGroup);
   const findFormGroup = () => wrapper.findComponent(GlFormGroup);
+  const findGlSprintf = () => wrapper.findComponent(GlSprintf);
   const findGroupProjectsDropdown = () => wrapper.findComponent(GroupProjectsDropdown);
+  const findOverrideSelector = () => wrapper.findComponent(CodeBlockOverrideSelector);
   const findRefSelector = () => wrapper.findComponent(RefSelector);
   const findTruncate = () => wrapper.findComponent(GlTruncate);
 
@@ -65,6 +69,25 @@ describe('CodeBlockFilePath', () => {
     });
   });
 
+  describe('pipeline execution policy', () => {
+    it('renders message for scan execution policy', () => {
+      createComponent({ stubs: { GlSprintf: false } });
+      expect(findGlSprintf().attributes('message')).toBe(
+        '%{boldStart}Run%{boldEnd} %{typeSelector} from the project %{projectSelector} with ref %{refSelector}',
+      );
+    });
+
+    it('renders message for pipeline execution policy', () => {
+      createComponent({
+        provide: { glFeatures: { pipelineExecutionPolicyType: true } },
+        stubs: { GlSprintf: false },
+      });
+      expect(findGlSprintf().attributes('message')).toBe(
+        '%{overrideSelector}into the %{boldStart}.gitlab-ci.yml%{boldEnd} with the following %{boldStart}pipeline execution file%{boldEnd} from %{projectSelector} And run with reference (Optional) %{refSelector}',
+      );
+    });
+  });
+
   describe('selected state', () => {
     it('render selected ref input', () => {
       createComponent({
@@ -128,6 +151,14 @@ describe('CodeBlockFilePath', () => {
       expect(findFormInput().exists()).toBe(true);
       expect(findGroupProjectsDropdown().props('selected')).toEqual([]);
     });
+
+    it('renders selected override', () => {
+      createComponent({
+        propsData: { overrideType: 'override' },
+        provide: { glFeatures: { pipelineExecutionPolicyType: true } },
+      });
+      expect(findOverrideSelector().props('overrideType')).toBe('override');
+    });
   });
 
   describe('actions', () => {
@@ -168,6 +199,12 @@ describe('CodeBlockFilePath', () => {
 
       expect(wrapper.emitted('update-file-path')).toEqual([['file-path']]);
     });
+
+    it('can select override for pipeline execution policy', () => {
+      createComponent({ provide: { glFeatures: { pipelineExecutionPolicyType: true } } });
+      findOverrideSelector().vm.$emit('select', 'override');
+      expect(wrapper.emitted('select-override')).toEqual([['override']]);
+    });
   });
 
   describe('group projects dropdown', () => {
diff --git a/ee/spec/frontend/security_orchestration/components/policy_editor/scan_execution/action/code_block_override_selector_spec.js b/ee/spec/frontend/security_orchestration/components/policy_editor/scan_execution/action/code_block_override_selector_spec.js
new file mode 100644
index 0000000000000..60f8bb05e95c4
--- /dev/null
+++ b/ee/spec/frontend/security_orchestration/components/policy_editor/scan_execution/action/code_block_override_selector_spec.js
@@ -0,0 +1,40 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlCollapsibleListbox } from '@gitlab/ui';
+import CodeBlockOverrideSelector from 'ee/security_orchestration/components/policy_editor/scan_execution/action/code_block_override_selector.vue';
+import {
+  INJECT,
+  OVERRIDE,
+  CUSTOM_OVERRIDE_OPTIONS,
+} from 'ee/security_orchestration/components/policy_editor/scan_execution/constants';
+
+describe('CodeBlockOverrideSelector', () => {
+  let wrapper;
+
+  const createComponent = ({ propsData = {} } = {}) => {
+    wrapper = shallowMount(CodeBlockOverrideSelector, {
+      propsData,
+    });
+  };
+
+  const findListBox = () => wrapper.findComponent(GlCollapsibleListbox);
+
+  it('selects action type', () => {
+    createComponent();
+    expect(findListBox().props('selected')).toBe('inject');
+    findListBox().vm.$emit('select', INJECT);
+    expect(wrapper.emitted('select')).toEqual([[INJECT]]);
+    findListBox().vm.$emit('select', OVERRIDE);
+    expect(wrapper.emitted('select')[1]).toEqual([OVERRIDE]);
+  });
+
+  it.each([INJECT, OVERRIDE])('renders override type', (overrideType) => {
+    createComponent({
+      propsData: {
+        overrideType,
+      },
+    });
+
+    expect(findListBox().props('selected')).toBe(overrideType);
+    expect(findListBox().props('toggleText')).toBe(CUSTOM_OVERRIDE_OPTIONS[overrideType]);
+  });
+});
diff --git a/ee/spec/frontend/security_orchestration/components/policy_editor/scan_execution/action/action_type_selector_spec.js b/ee/spec/frontend/security_orchestration/components/policy_editor/scan_execution/action/code_block_source_selector_spec.js
similarity index 96%
rename from ee/spec/frontend/security_orchestration/components/policy_editor/scan_execution/action/action_type_selector_spec.js
rename to ee/spec/frontend/security_orchestration/components/policy_editor/scan_execution/action/code_block_source_selector_spec.js
index 69e0fc3e57227..604b4fdd1ddd5 100644
--- a/ee/spec/frontend/security_orchestration/components/policy_editor/scan_execution/action/action_type_selector_spec.js
+++ b/ee/spec/frontend/security_orchestration/components/policy_editor/scan_execution/action/code_block_source_selector_spec.js
@@ -7,7 +7,7 @@ import {
   CUSTOM_ACTION_OPTIONS,
 } from 'ee/security_orchestration/components/policy_editor/scan_execution/constants';
 
-describe('ActionTypeSelector', () => {
+describe('CodeBlockSourceSelector', () => {
   let wrapper;
 
   const createComponent = ({ propsData = {} } = {}) => {
diff --git a/ee/spec/frontend/security_orchestration/components/policy_editor/scan_execution/editor_component_spec.js b/ee/spec/frontend/security_orchestration/components/policy_editor/scan_execution/editor_component_spec.js
index 2919785e81e81..3c45ea3922532 100644
--- a/ee/spec/frontend/security_orchestration/components/policy_editor/scan_execution/editor_component_spec.js
+++ b/ee/spec/frontend/security_orchestration/components/policy_editor/scan_execution/editor_component_spec.js
@@ -396,19 +396,34 @@ enabled: true`;
   });
 
   describe('execute yaml block section', () => {
-    it.each([true, false])(
-      'should render action builder when feature flag is enabled',
-      (flagEnabled) => {
+    it.each`
+      compliancePipelineInPolicies | customCiToggleEnabled | pipelineExecutionPolicyType | output
+      ${true}                      | ${true}               | ${true}                     | ${false}
+      ${true}                      | ${true}               | ${false}                    | ${true}
+      ${true}                      | ${false}              | ${true}                     | ${false}
+      ${true}                      | ${false}              | ${false}                    | ${false}
+      ${false}                     | ${true}               | ${true}                     | ${false}
+      ${false}                     | ${true}               | ${false}                    | ${false}
+      ${false}                     | ${false}              | ${true}                     | ${false}
+      ${false}                     | ${false}              | ${false}                    | ${false}
+    `(
+      'should render the correct action builder when compliancePipelineInPolicies is $compliancePipelineInPolicies, customCiToggleEnabled is $customCiToggleEnabled, and pipelineExecutionPolicyType is $pipelineExecutionPolicyType',
+      ({
+        compliancePipelineInPolicies,
+        customCiToggleEnabled,
+        pipelineExecutionPolicyType,
+        output,
+      }) => {
         factory({
           provide: {
-            glFeatures: { compliancePipelineInPolicies: flagEnabled },
-            customCiToggleEnabled: flagEnabled,
+            glFeatures: { compliancePipelineInPolicies, pipelineExecutionPolicyType },
+            customCiToggleEnabled,
           },
         });
 
-        expect(findActionSection().exists()).toBe(flagEnabled);
-        expect(findScanFilterSelector().exists()).toBe(flagEnabled);
-        expect(findAddActionButton().exists()).toBe(!flagEnabled);
+        expect(findActionSection().exists()).toBe(output);
+        expect(findScanFilterSelector().exists()).toBe(output);
+        expect(findAddActionButton().exists()).toBe(!output);
       },
     );
 
diff --git a/ee/spec/frontend/security_orchestration/components/policy_editor/scan_execution/lib/actions_spec.js b/ee/spec/frontend/security_orchestration/components/policy_editor/scan_execution/lib/actions_spec.js
index c69dc270a6eaa..6eff6e046d5ad 100644
--- a/ee/spec/frontend/security_orchestration/components/policy_editor/scan_execution/lib/actions_spec.js
+++ b/ee/spec/frontend/security_orchestration/components/policy_editor/scan_execution/lib/actions_spec.js
@@ -1,6 +1,8 @@
+import { CUSTOM_OVERRIDE_OPTIONS_KEYS } from 'ee/security_orchestration/components/policy_editor/scan_execution/constants';
 import {
   buildCustomCodeAction,
   buildScannerAction,
+  validateOverrideValues,
 } from 'ee/security_orchestration/components/policy_editor/scan_execution/lib/actions';
 import { REPORT_TYPE_DAST } from '~/vue_shared/security_reports/constants';
 
@@ -49,3 +51,14 @@ describe('buildScannerAction', () => {
     });
   });
 });
+
+describe('validateOverrideValues', () => {
+  it.each`
+    input                              | expected
+    ${CUSTOM_OVERRIDE_OPTIONS_KEYS[0]} | ${true}
+    ${CUSTOM_OVERRIDE_OPTIONS_KEYS[1]} | ${true}
+    ${'other string'}                  | ${false}
+  `('validates correctly for $input', ({ input, expected }) => {
+    expect(validateOverrideValues(input)).toBe(expected);
+  });
+});
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index e1a369f76e765..02acb1ed495a5 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -44932,6 +44932,9 @@ msgstr ""
 msgid "ScanExecutionPolicy|%{labelStart}File path:%{labelEnd} %{filePath}"
 msgstr ""
 
+msgid "ScanExecutionPolicy|%{overrideSelector}into the %{boldStart}.gitlab-ci.yml%{boldEnd} with the following %{boldStart}pipeline execution file%{boldEnd} from %{projectSelector} And run with reference (Optional) %{refSelector}"
+msgstr ""
+
 msgid "ScanExecutionPolicy|%{period} %{days} at %{time} %{timezoneLabel} %{timezone}"
 msgstr ""
 
@@ -44977,6 +44980,9 @@ msgstr ""
 msgid "ScanExecutionPolicy|If there are any conflicting variables with the local pipeline configuration (Ex, gitlab-ci.yml) then variables defined here will take precedence. %{linkStart}Learn more%{linkEnd}."
 msgstr ""
 
+msgid "ScanExecutionPolicy|Inject"
+msgstr ""
+
 msgid "ScanExecutionPolicy|Inserted CI code block"
 msgstr ""
 
@@ -44998,6 +45004,9 @@ msgstr ""
 msgid "ScanExecutionPolicy|Only one variable can be added at a time."
 msgstr ""
 
+msgid "ScanExecutionPolicy|Override"
+msgstr ""
+
 msgid "ScanExecutionPolicy|Run CI/CD code"
 msgstr ""
 
@@ -45046,6 +45055,9 @@ msgstr ""
 msgid "ScanExecutionPolicy|Select timezone"
 msgstr ""
 
+msgid "ScanExecutionPolicy|The content of this pipeline execution YAML file is included in the .gitlab-ci.yml file of the target project. All GitLab CI/CD features are supported."
+msgstr ""
+
 msgid "ScanExecutionPolicy|The file at that project, ref, and path doesn't exist"
 msgstr ""
 
-- 
GitLab