Commit b148b97b authored by Simon Knox's avatar Simon Knox

Merge branch '339372-scan-execution-user' into 'master'

Update scan execution empty state view

See merge request gitlab-org/gitlab!72995
parents f00c32e3 b501c17b
...@@ -66,7 +66,12 @@ export default { ...@@ -66,7 +66,12 @@ export default {
PolicyEditorLayout, PolicyEditorLayout,
DimDisableContainer, DimDisableContainer,
}, },
inject: ['networkDocumentationPath', 'noEnvironmentSvgPath', 'projectId', 'policiesPath'], inject: [
'networkDocumentationPath',
'policyEditorEmptyStateSvgPath',
'projectId',
'policiesPath',
],
props: { props: {
existingPolicy: { existingPolicy: {
type: Object, type: Object,
...@@ -303,7 +308,7 @@ export default { ...@@ -303,7 +308,7 @@ export default {
:description="$options.i18n.noEnvironmentDescription" :description="$options.i18n.noEnvironmentDescription"
:primary-button-link="networkDocumentationPath" :primary-button-link="networkDocumentationPath"
:primary-button-text="$options.i18n.noEnvironmentButton" :primary-button-text="$options.i18n.noEnvironmentButton"
:svg-path="noEnvironmentSvgPath" :svg-path="policyEditorEmptyStateSvgPath"
title="" title=""
/> />
</template> </template>
<script> <script>
import { GlEmptyState } from '@gitlab/ui';
import { joinPaths, visitUrl } from '~/lib/utils/url_utility'; import { joinPaths, visitUrl } from '~/lib/utils/url_utility';
import { __ } from '~/locale'; import { __, s__ } from '~/locale';
import { EDITOR_MODES, EDITOR_MODE_YAML } from '../constants'; import { EDITOR_MODES, EDITOR_MODE_YAML } from '../constants';
import PolicyEditorLayout from '../policy_editor_layout.vue'; import PolicyEditorLayout from '../policy_editor_layout.vue';
import { import {
...@@ -18,11 +19,22 @@ export default { ...@@ -18,11 +19,22 @@ export default {
EDITOR_MODES: [EDITOR_MODES[1]], EDITOR_MODES: [EDITOR_MODES[1]],
i18n: { i18n: {
createMergeRequest: __('Create merge request'), createMergeRequest: __('Create merge request'),
notOwnerButtonText: __('Learn more'),
notOwnerDescription: s__(
'SecurityOrchestration|Scan execution policies can only be created by project owners.',
),
}, },
components: { components: {
GlEmptyState,
PolicyEditorLayout, PolicyEditorLayout,
}, },
inject: ['disableScanExecutionUpdate', 'projectId', 'projectPath'], inject: [
'disableScanExecutionUpdate',
'policyEditorEmptyStateSvgPath',
'projectId',
'projectPath',
'scanExecutionDocumentationPath',
],
props: { props: {
assignedPolicyProject: { assignedPolicyProject: {
type: Object, type: Object,
...@@ -110,9 +122,9 @@ export default { ...@@ -110,9 +122,9 @@ export default {
<template> <template>
<policy-editor-layout <policy-editor-layout
v-if="!disableScanExecutionUpdate"
:custom-save-button-text="$options.i18n.createMergeRequest" :custom-save-button-text="$options.i18n.createMergeRequest"
:default-editor-mode="$options.DEFAULT_EDITOR_MODE" :default-editor-mode="$options.DEFAULT_EDITOR_MODE"
:disable-update="disableScanExecutionUpdate"
:editor-modes="$options.EDITOR_MODES" :editor-modes="$options.EDITOR_MODES"
:is-editing="isEditing" :is-editing="isEditing"
:is-removing-policy="isRemovingPolicy" :is-removing-policy="isRemovingPolicy"
...@@ -123,4 +135,12 @@ export default { ...@@ -123,4 +135,12 @@ export default {
@save-policy="handleModifyPolicy()" @save-policy="handleModifyPolicy()"
@update-yaml="updateYaml" @update-yaml="updateYaml"
/> />
<gl-empty-state
v-else
:description="$options.i18n.notOwnerDescription"
:primary-button-link="scanExecutionDocumentationPath"
:primary-button-text="$options.i18n.notOwnerButtonText"
:svg-path="policyEditorEmptyStateSvgPath"
title=""
/>
</template> </template>
...@@ -21,15 +21,16 @@ export default () => { ...@@ -21,15 +21,16 @@ export default () => {
environmentsEndpoint, environmentsEndpoint,
configureAgentHelpPath, configureAgentHelpPath,
createAgentHelpPath, createAgentHelpPath,
networkDocumentationPath,
networkPoliciesEndpoint, networkPoliciesEndpoint,
noEnvironmentSvgPath, networkDocumentationPath,
policiesPath, policiesPath,
policy, policy,
policyEditorEmptyStateSvgPath,
policyType, policyType,
projectPath, projectPath,
projectId, projectId,
environmentId, environmentId,
scanExecutionDocumentationPath,
} = el.dataset; } = el.dataset;
// We require the project to have at least one available environment. // We require the project to have at least one available environment.
...@@ -66,12 +67,13 @@ export default () => { ...@@ -66,12 +67,13 @@ export default () => {
configureAgentHelpPath, configureAgentHelpPath,
createAgentHelpPath, createAgentHelpPath,
disableScanExecutionUpdate: parseBoolean(disableScanExecutionUpdate), disableScanExecutionUpdate: parseBoolean(disableScanExecutionUpdate),
policyType,
networkDocumentationPath, networkDocumentationPath,
noEnvironmentSvgPath, policyEditorEmptyStateSvgPath,
policyType,
projectId, projectId,
projectPath, projectPath,
policiesPath, policiesPath,
scanExecutionDocumentationPath,
}, },
store, store,
render(createElement) { render(createElement) {
......
...@@ -29,13 +29,14 @@ module Projects::Security::PoliciesHelper ...@@ -29,13 +29,14 @@ module Projects::Security::PoliciesHelper
create_agent_help_path: help_page_url('user/clusters/agent/index.md', anchor: 'create-an-agent-record-in-gitlab'), create_agent_help_path: help_page_url('user/clusters/agent/index.md', anchor: 'create-an-agent-record-in-gitlab'),
environments_endpoint: project_environments_path(project), environments_endpoint: project_environments_path(project),
environment_id: environment&.id, environment_id: environment&.id,
network_documentation_path: help_page_path('user/application_security/policies/index'), network_documentation_path: help_page_path('user/application_security/policies/index', anchor: 'container-network-policy'),
no_environment_svg_path: image_path('illustrations/monitoring/unable_to_connect.svg'),
policy: policy&.to_json, policy: policy&.to_json,
policy_editor_empty_state_svg_path: image_path('illustrations/monitoring/unable_to_connect.svg'),
policy_type: policy_type, policy_type: policy_type,
project_path: project.full_path, project_path: project.full_path,
project_id: project.id, project_id: project.id,
policies_path: project_security_policies_path(project) policies_path: project_security_policies_path(project),
scan_execution_documentation_path: help_page_path('user/application_security/policies/index', anchor: 'scan-execution-policy-editor')
} }
end end
end end
...@@ -52,7 +52,7 @@ describe('NetworkPolicyEditor component', () => { ...@@ -52,7 +52,7 @@ describe('NetworkPolicyEditor component', () => {
}, },
provide: { provide: {
networkDocumentationPath: 'path/to/docs', networkDocumentationPath: 'path/to/docs',
noEnvironmentSvgPath: 'path/to/svg', policyEditorEmptyStateSvgPath: 'path/to/svg',
policiesPath: '/threat-monitoring', policiesPath: '/threat-monitoring',
projectId: '21', projectId: '21',
...provide, ...provide,
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { GlEmptyState } from '@gitlab/ui';
import PolicyEditorLayout from 'ee/threat_monitoring/components/policy_editor/policy_editor_layout.vue'; import PolicyEditorLayout from 'ee/threat_monitoring/components/policy_editor/policy_editor_layout.vue';
import { import {
DEFAULT_SCAN_EXECUTION_POLICY, DEFAULT_SCAN_EXECUTION_POLICY,
...@@ -40,8 +41,10 @@ jest.mock('ee/threat_monitoring/components/policy_editor/scan_execution_policy/l ...@@ -40,8 +41,10 @@ jest.mock('ee/threat_monitoring/components/policy_editor/scan_execution_policy/l
describe('ScanExecutionPolicyEditor', () => { describe('ScanExecutionPolicyEditor', () => {
let wrapper; let wrapper;
const defaultProjectPath = 'path/to/project'; const defaultProjectPath = 'path/to/project';
const policyEditorEmptyStateSvgPath = 'path/to/svg';
const scanExecutionDocumentationPath = 'path/to/docs';
const factory = ({ propsData = {} } = {}) => { const factory = ({ propsData = {}, provide = {} } = {}) => {
wrapper = shallowMount(ScanExecutionPolicyEditor, { wrapper = shallowMount(ScanExecutionPolicyEditor, {
propsData: { propsData: {
assignedPolicyProject: DEFAULT_ASSIGNED_POLICY_PROJECT, assignedPolicyProject: DEFAULT_ASSIGNED_POLICY_PROJECT,
...@@ -49,8 +52,11 @@ describe('ScanExecutionPolicyEditor', () => { ...@@ -49,8 +52,11 @@ describe('ScanExecutionPolicyEditor', () => {
}, },
provide: { provide: {
disableScanExecutionUpdate: false, disableScanExecutionUpdate: false,
policyEditorEmptyStateSvgPath,
projectId: 1, projectId: 1,
projectPath: defaultProjectPath, projectPath: defaultProjectPath,
scanExecutionDocumentationPath,
...provide,
}, },
}); });
}; };
...@@ -59,45 +65,59 @@ describe('ScanExecutionPolicyEditor', () => { ...@@ -59,45 +65,59 @@ describe('ScanExecutionPolicyEditor', () => {
return factory({ propsData: { existingPolicy: mockDastScanExecutionObject, isEditing: true } }); return factory({ propsData: { existingPolicy: mockDastScanExecutionObject, isEditing: true } });
}; };
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
const findPolicyEditorLayout = () => wrapper.findComponent(PolicyEditorLayout); const findPolicyEditorLayout = () => wrapper.findComponent(PolicyEditorLayout);
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
}); });
it('updates the policy yaml when "update-yaml" is emitted', async () => { describe('default', () => {
factory(); it('updates the policy yaml when "update-yaml" is emitted', async () => {
await wrapper.vm.$nextTick(); factory();
const newManifest = 'new yaml!'; await wrapper.vm.$nextTick();
expect(findPolicyEditorLayout().attributes('yamleditorvalue')).toBe( const newManifest = 'new yaml!';
DEFAULT_SCAN_EXECUTION_POLICY, expect(findPolicyEditorLayout().attributes('yamleditorvalue')).toBe(
DEFAULT_SCAN_EXECUTION_POLICY,
);
await findPolicyEditorLayout().vm.$emit('update-yaml', newManifest);
expect(findPolicyEditorLayout().attributes('yamleditorvalue')).toBe(newManifest);
});
it.each`
status | action | event | factoryFn | yamlEditorValue
${'to save a new policy'} | ${SECURITY_POLICY_ACTIONS.APPEND} | ${'save-policy'} | ${factory} | ${DEFAULT_SCAN_EXECUTION_POLICY}
${'to update an existing policy'} | ${SECURITY_POLICY_ACTIONS.REPLACE} | ${'save-policy'} | ${factoryWithExistingPolicy} | ${mockDastScanExecutionManifest}
${'to delete an existing policy'} | ${SECURITY_POLICY_ACTIONS.REMOVE} | ${'remove-policy'} | ${factoryWithExistingPolicy} | ${mockDastScanExecutionManifest}
`(
'navigates to the new merge request when "modifyPolicy" is emitted $status',
async ({ action, event, factoryFn, yamlEditorValue }) => {
factoryFn();
await wrapper.vm.$nextTick();
findPolicyEditorLayout().vm.$emit(event);
await wrapper.vm.$nextTick();
expect(modifyPolicy).toHaveBeenCalledTimes(1);
expect(modifyPolicy).toHaveBeenCalledWith({
action,
assignedPolicyProject: DEFAULT_ASSIGNED_POLICY_PROJECT,
projectPath: defaultProjectPath,
yamlEditorValue,
});
await wrapper.vm.$nextTick();
expect(visitUrl).toHaveBeenCalled();
expect(visitUrl).toHaveBeenCalledWith('/tests/-/merge_requests/2');
},
); );
await findPolicyEditorLayout().vm.$emit('update-yaml', newManifest);
expect(findPolicyEditorLayout().attributes('yamleditorvalue')).toBe(newManifest);
}); });
it.each` describe('when a user is not an owner of the project', () => {
status | action | event | factoryFn | yamlEditorValue it('displays the empty state with the appropriate properties', async () => {
${'to save a new policy'} | ${SECURITY_POLICY_ACTIONS.APPEND} | ${'save-policy'} | ${factory} | ${DEFAULT_SCAN_EXECUTION_POLICY} factory({ provide: { disableScanExecutionUpdate: true } });
${'to update an existing policy'} | ${SECURITY_POLICY_ACTIONS.REPLACE} | ${'save-policy'} | ${factoryWithExistingPolicy} | ${mockDastScanExecutionManifest}
${'to delete an existing policy'} | ${SECURITY_POLICY_ACTIONS.REMOVE} | ${'remove-policy'} | ${factoryWithExistingPolicy} | ${mockDastScanExecutionManifest}
`(
'navigates to the new merge request when "modifyPolicy" is emitted $status',
async ({ action, event, factoryFn, yamlEditorValue }) => {
factoryFn();
await wrapper.vm.$nextTick();
findPolicyEditorLayout().vm.$emit(event);
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
expect(modifyPolicy).toHaveBeenCalledTimes(1); expect(findEmptyState().props()).toMatchObject({
expect(modifyPolicy).toHaveBeenCalledWith({ primaryButtonLink: scanExecutionDocumentationPath,
action, svgPath: policyEditorEmptyStateSvgPath,
assignedPolicyProject: DEFAULT_ASSIGNED_POLICY_PROJECT,
projectPath: defaultProjectPath,
yamlEditorValue,
}); });
await wrapper.vm.$nextTick(); });
expect(visitUrl).toHaveBeenCalled(); });
expect(visitUrl).toHaveBeenCalledWith('/tests/-/merge_requests/2');
},
);
}); });
...@@ -46,13 +46,14 @@ RSpec.describe Projects::Security::PoliciesHelper do ...@@ -46,13 +46,14 @@ RSpec.describe Projects::Security::PoliciesHelper do
create_agent_help_path: kind_of(String), create_agent_help_path: kind_of(String),
environments_endpoint: kind_of(String), environments_endpoint: kind_of(String),
network_documentation_path: kind_of(String), network_documentation_path: kind_of(String),
no_environment_svg_path: kind_of(String), policy_editor_empty_state_svg_path: kind_of(String),
project_path: project.full_path, project_path: project.full_path,
project_id: project.id, project_id: project.id,
policies_path: kind_of(String), policies_path: kind_of(String),
environment_id: environment&.id, environment_id: environment&.id,
policy: policy&.to_json, policy: policy&.to_json,
policy_type: policy_type policy_type: policy_type,
scan_execution_documentation_path: kind_of(String)
} }
end end
......
...@@ -30430,6 +30430,9 @@ msgstr "" ...@@ -30430,6 +30430,9 @@ msgstr ""
msgid "SecurityOrchestration|Scan execution" msgid "SecurityOrchestration|Scan execution"
msgstr "" msgstr ""
msgid "SecurityOrchestration|Scan execution policies can only be created by project owners."
msgstr ""
msgid "SecurityOrchestration|Scan to be performed every %{cadence} on the %{branches}" msgid "SecurityOrchestration|Scan to be performed every %{cadence} on the %{branches}"
msgstr "" msgstr ""
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment