Commit 7dc2df6d authored by Andrew Fontaine's avatar Andrew Fontaine

Merge branch '338839-update-network-policy-empty-state' into 'master'

Add network policy editor empty state

See merge request gitlab-org/gitlab!68907
parents 1b86edce 9c3d4a5c
<script> <script>
import { GlFormGroup, GlFormInput, GlFormTextarea, GlToggle, GlButton, GlAlert } from '@gitlab/ui'; import {
GlEmptyState,
GlFormGroup,
GlFormInput,
GlFormTextarea,
GlLoadingIcon,
GlToggle,
GlButton,
GlAlert,
} from '@gitlab/ui';
import { mapState, mapActions } from 'vuex'; import { mapState, mapActions } from 'vuex';
import { removeUnnecessaryDashes } from 'ee/threat_monitoring/utils'; import { removeUnnecessaryDashes } from 'ee/threat_monitoring/utils';
import { redirectTo } from '~/lib/utils/url_utility'; import { redirectTo } from '~/lib/utils/url_utility';
import { s__ } from '~/locale'; import { __, s__ } from '~/locale';
import { EDITOR_MODES, EDITOR_MODE_YAML, PARSING_ERROR_MESSAGE } from '../constants'; import { EDITOR_MODES, EDITOR_MODE_YAML, PARSING_ERROR_MESSAGE } from '../constants';
import DimDisableContainer from '../dim_disable_container.vue'; import DimDisableContainer from '../dim_disable_container.vue';
import PolicyActionPicker from '../policy_action_picker.vue'; import PolicyActionPicker from '../policy_action_picker.vue';
...@@ -24,13 +33,19 @@ import PolicyRuleBuilder from './policy_rule_builder.vue'; ...@@ -24,13 +33,19 @@ import PolicyRuleBuilder from './policy_rule_builder.vue';
export default { export default {
EDITOR_MODES, EDITOR_MODES,
i18n: { i18n: {
toggleLabel: s__('NetworkPolicies|Policy status'), toggleLabel: s__('SecurityOrchestration|Policy status'),
PARSING_ERROR_MESSAGE, PARSING_ERROR_MESSAGE,
noEnvironmentDescription: s__(
'SecurityOrchestration|Network Policies can be used to limit which network traffic is allowed between containers inside the cluster.',
),
noEnvironmentButton: __('Learn more'),
}, },
components: { components: {
GlEmptyState,
GlFormGroup, GlFormGroup,
GlFormInput, GlFormInput,
GlFormTextarea, GlFormTextarea,
GlLoadingIcon,
GlToggle, GlToggle,
GlButton, GlButton,
GlAlert, GlAlert,
...@@ -41,7 +56,7 @@ export default { ...@@ -41,7 +56,7 @@ export default {
PolicyEditorLayout, PolicyEditorLayout,
DimDisableContainer, DimDisableContainer,
}, },
inject: ['threatMonitoringPath', 'projectId'], inject: ['networkDocumentationPath', 'noEnvironmentSvgPath', 'projectId', 'threatMonitoringPath'],
props: { props: {
existingPolicy: { existingPolicy: {
type: Object, type: Object,
...@@ -71,6 +86,9 @@ export default { ...@@ -71,6 +86,9 @@ export default {
}; };
}, },
computed: { computed: {
hasEnvironment() {
return Boolean(this.environments.length);
},
humanizedPolicy() { humanizedPolicy() {
return this.policy.error ? null : humanizeNetworkPolicy(this.policy); return this.policy.error ? null : humanizeNetworkPolicy(this.policy);
}, },
...@@ -80,7 +98,11 @@ export default { ...@@ -80,7 +98,11 @@ export default {
policyYaml() { policyYaml() {
return this.hasParsingError ? '' : toYaml(this.policy); return this.hasParsingError ? '' : toYaml(this.policy);
}, },
...mapState('threatMonitoring', ['currentEnvironmentId']), ...mapState('threatMonitoring', [
'currentEnvironmentId',
'environments',
'isLoadingEnvironments',
]),
...mapState('networkPolicies', [ ...mapState('networkPolicies', [
'isUpdatingPolicy', 'isUpdatingPolicy',
'isRemovingPolicy', 'isRemovingPolicy',
...@@ -159,7 +181,9 @@ export default { ...@@ -159,7 +181,9 @@ export default {
</script> </script>
<template> <template>
<gl-loading-icon v-if="isLoadingEnvironments" size="lg" />
<policy-editor-layout <policy-editor-layout
v-else-if="hasEnvironment"
:is-editing="isEditing" :is-editing="isEditing"
:is-removing-policy="isRemovingPolicy" :is-removing-policy="isRemovingPolicy"
:is-updating-policy="isUpdatingPolicy" :is-updating-policy="isUpdatingPolicy"
...@@ -255,4 +279,12 @@ export default { ...@@ -255,4 +279,12 @@ export default {
</dim-disable-container> </dim-disable-container>
</template> </template>
</policy-editor-layout> </policy-editor-layout>
<gl-empty-state
v-else
:description="$options.i18n.noEnvironmentDescription"
:primary-button-link="networkDocumentationPath"
:primary-button-text="$options.i18n.noEnvironmentButton"
:svg-path="noEnvironmentSvgPath"
title=""
/>
</template> </template>
...@@ -79,10 +79,10 @@ export default { ...@@ -79,10 +79,10 @@ export default {
{{ error }} {{ error }}
</gl-alert> </gl-alert>
<header class="gl-pb-5"> <header class="gl-pb-5">
<h3>{{ s__('NetworkPolicies|Policy description') }}</h3> <h3>{{ s__('SecurityOrchestration|Policy description') }}</h3>
</header> </header>
<div class="gl-display-flex"> <div class="gl-display-flex">
<gl-form-group :label="s__('NetworkPolicies|Policy type')" label-for="policyType"> <gl-form-group :label="s__('SecurityOrchestration|Policy type')" label-for="policyType">
<gl-form-select <gl-form-select
id="policyType" id="policyType"
:value="policyOptions.value" :value="policyOptions.value"
......
...@@ -20,7 +20,9 @@ export default () => { ...@@ -20,7 +20,9 @@ export default () => {
environmentsEndpoint, environmentsEndpoint,
configureAgentHelpPath, configureAgentHelpPath,
createAgentHelpPath, createAgentHelpPath,
networkDocumentationPath,
networkPoliciesEndpoint, networkPoliciesEndpoint,
noEnvironmentSvgPath,
threatMonitoringPath, threatMonitoringPath,
policy, policy,
policyType, policyType,
...@@ -58,6 +60,8 @@ export default () => { ...@@ -58,6 +60,8 @@ export default () => {
createAgentHelpPath, createAgentHelpPath,
disableScanExecutionUpdate: parseBoolean(disableScanExecutionUpdate), disableScanExecutionUpdate: parseBoolean(disableScanExecutionUpdate),
policyType, policyType,
networkDocumentationPath,
noEnvironmentSvgPath,
projectId, projectId,
projectPath, projectPath,
threatMonitoringPath, threatMonitoringPath,
......
...@@ -28,6 +28,8 @@ module Projects::Security::PoliciesHelper ...@@ -28,6 +28,8 @@ 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/threat_monitoring/index'),
no_environment_svg_path: image_path('illustrations/monitoring/unable_to_connect.svg'),
policy: policy&.to_json, policy: policy&.to_json,
policy_type: policy_type, policy_type: policy_type,
project_path: project.full_path, project_path: project.full_path,
......
import { GlToggle } from '@gitlab/ui'; import { GlEmptyState, GlLoadingIcon, GlToggle } from '@gitlab/ui';
import { EDITOR_MODE_YAML } from 'ee/threat_monitoring/components/policy_editor/constants'; import { EDITOR_MODE_YAML } from 'ee/threat_monitoring/components/policy_editor/constants';
import { import {
RuleDirectionInbound, RuleDirectionInbound,
...@@ -25,22 +25,33 @@ describe('NetworkPolicyEditor component', () => { ...@@ -25,22 +25,33 @@ describe('NetworkPolicyEditor component', () => {
let store; let store;
let wrapper; let wrapper;
const factory = ({ propsData, provide = {}, state, data } = {}) => { const defaultStore = { threatMonitoring: { environments: [{ id: 1 }], currentEnvironmentId: 1 } };
const factory = ({ propsData, provide = {}, updatedStore = defaultStore, data } = {}) => {
store = createStore(); store = createStore();
Object.assign(store.state.threatMonitoring, {
...state, store.replaceState({
}); ...store.state,
Object.assign(store.state.networkPolicies, { networkPolicies: {
...state, ...store.state.networkPolicies,
...updatedStore.networkPolicies,
},
threatMonitoring: {
...store.state.threatMonitoring,
...updatedStore.threatMonitoring,
},
}); });
jest.spyOn(store, 'dispatch').mockImplementation(() => Promise.resolve()); jest.spyOn(store, 'dispatch').mockImplementation(() => Promise.resolve());
wrapper = shallowMountExtended(NetworkPolicyEditor, { wrapper = shallowMountExtended(NetworkPolicyEditor, {
propsData: { propsData: {
hasEnvironment: true,
...propsData, ...propsData,
}, },
provide: { provide: {
networkDocumentationPath: 'path/to/docs',
noEnvironmentSvgPath: 'path/to/svg',
threatMonitoringPath: '/threat-monitoring', threatMonitoringPath: '/threat-monitoring',
projectId: '21', projectId: '21',
...provide, ...provide,
...@@ -51,6 +62,8 @@ describe('NetworkPolicyEditor component', () => { ...@@ -51,6 +62,8 @@ describe('NetworkPolicyEditor component', () => {
}); });
}; };
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findPreview = () => wrapper.findComponent(PolicyPreview); const findPreview = () => wrapper.findComponent(PolicyPreview);
const findAddRuleButton = () => wrapper.findByTestId('add-rule'); const findAddRuleButton = () => wrapper.findByTestId('add-rule');
const findYAMLParsingAlert = () => wrapper.findByTestId('parsing-alert'); const findYAMLParsingAlert = () => wrapper.findByTestId('parsing-alert');
...@@ -92,12 +105,14 @@ describe('NetworkPolicyEditor component', () => { ...@@ -92,12 +105,14 @@ describe('NetworkPolicyEditor component', () => {
}); });
it.each` it.each`
component | status | findComponent | state component | status | findComponent | state
${'policy alert picker'} | ${'does display'} | ${findPolicyAlertPicker} | ${true} ${'policy alert picker'} | ${'does display'} | ${findPolicyAlertPicker} | ${true}
${'policy name input'} | ${'does display'} | ${findPolicyName} | ${true} ${'policy name input'} | ${'does display'} | ${findPolicyName} | ${true}
${'add rule button'} | ${'does display'} | ${findAddRuleButton} | ${true} ${'add rule button'} | ${'does display'} | ${findAddRuleButton} | ${true}
${'policy preview'} | ${'does display'} | ${findPreview} | ${true} ${'policy preview'} | ${'does display'} | ${findPreview} | ${true}
${'parsing error alert'} | ${'does not display'} | ${findYAMLParsingAlert} | ${false} ${'parsing error alert'} | ${'does not display'} | ${findYAMLParsingAlert} | ${false}
${'loading icon'} | ${'does not display'} | ${findLoadingIcon} | ${false}
${'no environment empty state'} | ${'does not display'} | ${findEmptyState} | ${false}
`('$status the $component', async ({ findComponent, state }) => { `('$status the $component', async ({ findComponent, state }) => {
expect(findComponent().exists()).toBe(state); expect(findComponent().exists()).toBe(state);
}); });
...@@ -140,7 +155,7 @@ describe('NetworkPolicyEditor component', () => { ...@@ -140,7 +155,7 @@ describe('NetworkPolicyEditor component', () => {
await findPolicyEditorLayout().vm.$emit('save-policy', EDITOR_MODE_YAML); await findPolicyEditorLayout().vm.$emit('save-policy', EDITOR_MODE_YAML);
expect(store.dispatch).toHaveBeenCalledWith('networkPolicies/createPolicy', { expect(store.dispatch).toHaveBeenCalledWith('networkPolicies/createPolicy', {
environmentId: -1, environmentId: 1,
policy: { manifest: mockL7Manifest }, policy: { manifest: mockL7Manifest },
}); });
expect(redirectTo).toHaveBeenCalledWith('/threat-monitoring'); expect(redirectTo).toHaveBeenCalledWith('/threat-monitoring');
...@@ -244,7 +259,7 @@ describe('NetworkPolicyEditor component', () => { ...@@ -244,7 +259,7 @@ describe('NetworkPolicyEditor component', () => {
it('creates policy and redirects to a threat monitoring path', async () => { it('creates policy and redirects to a threat monitoring path', async () => {
await findPolicyEditorLayout().vm.$emit('save-policy'); await findPolicyEditorLayout().vm.$emit('save-policy');
expect(store.dispatch).toHaveBeenCalledWith('networkPolicies/createPolicy', { expect(store.dispatch).toHaveBeenCalledWith('networkPolicies/createPolicy', {
environmentId: -1, environmentId: 1,
policy: { manifest: toYaml(wrapper.vm.policy) }, policy: { manifest: toYaml(wrapper.vm.policy) },
}); });
expect(redirectTo).toHaveBeenCalledWith('/threat-monitoring'); expect(redirectTo).toHaveBeenCalledWith('/threat-monitoring');
...@@ -253,9 +268,7 @@ describe('NetworkPolicyEditor component', () => { ...@@ -253,9 +268,7 @@ describe('NetworkPolicyEditor component', () => {
describe('given there is a createPolicy error', () => { describe('given there is a createPolicy error', () => {
beforeEach(() => { beforeEach(() => {
factory({ factory({
state: { updatedStore: { networkPolicies: { errorUpdatingPolicy: true }, ...defaultStore },
errorUpdatingPolicy: true,
},
}); });
}); });
...@@ -289,7 +302,7 @@ describe('NetworkPolicyEditor component', () => { ...@@ -289,7 +302,7 @@ describe('NetworkPolicyEditor component', () => {
it('updates existing policy and redirects to a threat monitoring path', async () => { it('updates existing policy and redirects to a threat monitoring path', async () => {
await findPolicyEditorLayout().vm.$emit('save-policy'); await findPolicyEditorLayout().vm.$emit('save-policy');
expect(store.dispatch).toHaveBeenCalledWith('networkPolicies/updatePolicy', { expect(store.dispatch).toHaveBeenCalledWith('networkPolicies/updatePolicy', {
environmentId: -1, environmentId: 1,
policy: { name: 'policy', manifest: toYaml(wrapper.vm.policy) }, policy: { name: 'policy', manifest: toYaml(wrapper.vm.policy) },
}); });
expect(redirectTo).toHaveBeenCalledWith('/threat-monitoring'); expect(redirectTo).toHaveBeenCalledWith('/threat-monitoring');
...@@ -298,12 +311,8 @@ describe('NetworkPolicyEditor component', () => { ...@@ -298,12 +311,8 @@ describe('NetworkPolicyEditor component', () => {
describe('given there is a updatePolicy error', () => { describe('given there is a updatePolicy error', () => {
beforeEach(() => { beforeEach(() => {
factory({ factory({
propsData: { propsData: { existingPolicy: { name: 'policy', manifest } },
existingPolicy: { name: 'policy', manifest }, updatedStore: { networkPolicies: { errorUpdatingPolicy: true }, ...defaultStore },
},
state: {
errorUpdatingPolicy: true,
},
}); });
}); });
...@@ -318,7 +327,7 @@ describe('NetworkPolicyEditor component', () => { ...@@ -318,7 +327,7 @@ describe('NetworkPolicyEditor component', () => {
await findPolicyEditorLayout().vm.$emit('remove-policy'); await findPolicyEditorLayout().vm.$emit('remove-policy');
expect(store.dispatch).toHaveBeenCalledWith('networkPolicies/deletePolicy', { expect(store.dispatch).toHaveBeenCalledWith('networkPolicies/deletePolicy', {
environmentId: -1, environmentId: 1,
policy: { name: 'policy', manifest: toYaml(wrapper.vm.policy) }, policy: { name: 'policy', manifest: toYaml(wrapper.vm.policy) },
}); });
expect(redirectTo).toHaveBeenCalledWith('/threat-monitoring'); expect(redirectTo).toHaveBeenCalledWith('/threat-monitoring');
...@@ -329,7 +338,7 @@ describe('NetworkPolicyEditor component', () => { ...@@ -329,7 +338,7 @@ describe('NetworkPolicyEditor component', () => {
it('adds a policy annotation on alert addition', async () => { it('adds a policy annotation on alert addition', async () => {
await modifyPolicyAlert({ isAlertEnabled: true }); await modifyPolicyAlert({ isAlertEnabled: true });
expect(store.dispatch).toHaveBeenLastCalledWith('networkPolicies/createPolicy', { expect(store.dispatch).toHaveBeenLastCalledWith('networkPolicies/createPolicy', {
environmentId: -1, environmentId: 1,
policy: { policy: {
manifest: expect.stringContaining("app.gitlab.com/alert: 'true'"), manifest: expect.stringContaining("app.gitlab.com/alert: 'true'"),
}, },
...@@ -339,11 +348,42 @@ describe('NetworkPolicyEditor component', () => { ...@@ -339,11 +348,42 @@ describe('NetworkPolicyEditor component', () => {
it('removes a policy annotation on alert removal', async () => { it('removes a policy annotation on alert removal', async () => {
await modifyPolicyAlert({ isAlertEnabled: false }); await modifyPolicyAlert({ isAlertEnabled: false });
expect(store.dispatch).toHaveBeenLastCalledWith('networkPolicies/createPolicy', { expect(store.dispatch).toHaveBeenLastCalledWith('networkPolicies/createPolicy', {
environmentId: -1, environmentId: 1,
policy: { policy: {
manifest: expect.not.stringContaining("app.gitlab.com/alert: 'true'"), manifest: expect.not.stringContaining("app.gitlab.com/alert: 'true'"),
}, },
}); });
}); });
}); });
describe('when loading environments', () => {
beforeEach(() => {
factory({
updatedStore: { threatMonitoring: { environments: [], isLoadingEnvironments: true } },
});
});
it.each`
component | status | findComponent | state
${'loading icon'} | ${'does display'} | ${findLoadingIcon} | ${true}
${'policy editor layout'} | ${'does not display'} | ${findPolicyEditorLayout} | ${false}
${'no environment empty state'} | ${'does not display'} | ${findEmptyState} | ${false}
`('$status the $component', ({ findComponent, state }) => {
expect(findComponent().exists()).toBe(state);
});
});
describe('when no environments are configured', () => {
beforeEach(() => {
factory({ updatedStore: { threatMonitoring: { environments: [] } } });
});
it.each`
component | status | findComponent | state
${'loading icon'} | ${'does display'} | ${findLoadingIcon} | ${false}
${'policy editor layout'} | ${'does not display'} | ${findPolicyEditorLayout} | ${false}
${'no environment empty state'} | ${'does not display'} | ${findEmptyState} | ${true}
`('$status the $component', ({ findComponent, state }) => {
expect(findComponent().exists()).toBe(state);
});
});
}); });
...@@ -46,14 +46,17 @@ describe('PolicyEditor component', () => { ...@@ -46,14 +46,17 @@ describe('PolicyEditor component', () => {
beforeEach(factory); beforeEach(factory);
it.each` it.each`
component | status | findComponent | state component | status | findComponent | state
${'environment picker'} | ${'does display'} | ${findEnvironmentPicker} | ${true} ${'environment picker'} | ${'does display'} | ${findEnvironmentPicker} | ${true}
${'NetworkPolicyEditor component'} | ${'does display'} | ${findNeworkPolicyEditor} | ${true} ${'alert'} | ${'does not display'} | ${findAlert} | ${false}
${'alert'} | ${'does not display'} | ${findAlert} | ${false}
`('$status the $component', ({ findComponent, state }) => { `('$status the $component', ({ findComponent, state }) => {
expect(findComponent().exists()).toBe(state); expect(findComponent().exists()).toBe(state);
}); });
it('renders the network policy editor component', () => {
expect(findNeworkPolicyEditor().props('existingPolicy')).toBe(null);
});
it('renders the disabled form select', () => { it('renders the disabled form select', () => {
const formSelect = findFormSelect(); const formSelect = findFormSelect();
expect(formSelect.exists()).toBe(true); expect(formSelect.exists()).toBe(true);
......
...@@ -44,6 +44,8 @@ RSpec.describe Projects::Security::PoliciesHelper do ...@@ -44,6 +44,8 @@ RSpec.describe Projects::Security::PoliciesHelper do
configure_agent_help_path: kind_of(String), configure_agent_help_path: kind_of(String),
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),
no_environment_svg_path: kind_of(String),
project_path: project.full_path, project_path: project.full_path,
project_id: project.id, project_id: project.id,
threat_monitoring_path: kind_of(String), threat_monitoring_path: kind_of(String),
......
...@@ -22203,21 +22203,12 @@ msgstr "" ...@@ -22203,21 +22203,12 @@ msgstr ""
msgid "NetworkPolicies|Policy definition" msgid "NetworkPolicies|Policy definition"
msgstr "" msgstr ""
msgid "NetworkPolicies|Policy description"
msgstr ""
msgid "NetworkPolicies|Policy editor" msgid "NetworkPolicies|Policy editor"
msgstr "" msgstr ""
msgid "NetworkPolicies|Policy preview" msgid "NetworkPolicies|Policy preview"
msgstr "" msgstr ""
msgid "NetworkPolicies|Policy status"
msgstr ""
msgid "NetworkPolicies|Policy type"
msgstr ""
msgid "NetworkPolicies|Rule" msgid "NetworkPolicies|Rule"
msgstr "" msgstr ""
...@@ -29682,6 +29673,9 @@ msgstr "" ...@@ -29682,6 +29673,9 @@ msgstr ""
msgid "SecurityOrchestration|Network" msgid "SecurityOrchestration|Network"
msgstr "" msgstr ""
msgid "SecurityOrchestration|Network Policies can be used to limit which network traffic is allowed between containers inside the cluster."
msgstr ""
msgid "SecurityOrchestration|New policy" msgid "SecurityOrchestration|New policy"
msgstr "" msgstr ""
...@@ -29691,9 +29685,18 @@ msgstr "" ...@@ -29691,9 +29685,18 @@ msgstr ""
msgid "SecurityOrchestration|Policies" msgid "SecurityOrchestration|Policies"
msgstr "" msgstr ""
msgid "SecurityOrchestration|Policy description"
msgstr ""
msgid "SecurityOrchestration|Policy editor" msgid "SecurityOrchestration|Policy editor"
msgstr "" msgstr ""
msgid "SecurityOrchestration|Policy status"
msgstr ""
msgid "SecurityOrchestration|Policy type"
msgstr ""
msgid "SecurityOrchestration|Scan Execution" msgid "SecurityOrchestration|Scan Execution"
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