Commit c94c11cf authored by Alexander Turinske's avatar Alexander Turinske

Create empty states for Policies page

- create no policies created empty state
- create no policies found from filter empty
  state
- remove empty state for no environment
- update tests
parent 7ecb8fd2
......@@ -10,6 +10,7 @@ export const EMPTY_STATE_DESCRIPTION = s__(
`ThreatMonitoring|To view this data, ensure you have configured an environment
for this project and that at least one threat monitoring feature is enabled. %{linkStart}More information%{linkEnd}`,
);
export const NEW_POLICY_BUTTON_TEXT = s__('SecurityOrchestration|New policy');
export const COLORS = {
nominal: gray700,
......
<script>
import { GlEmptyState } from '@gitlab/ui';
import { s__ } from '~/locale';
import { NEW_POLICY_BUTTON_TEXT } from '../constants';
export default {
components: {
GlEmptyState,
},
i18n: {
emptyFilterTitle: s__(`SecurityOrchestration|Sorry, your filter produced no results`),
emptyFilterDescription: s__(
`SecurityOrchestration|To widen your search, change filters above or select a different security policy project.`,
),
emptyStateDescription: s__(
`SecurityOrchestration|This project does not contain any security policies`,
),
newPolicyButtonText: NEW_POLICY_BUTTON_TEXT,
},
inject: ['emptyFilterSvgPath', 'emptyListSvgPath', 'newPolicyPath'],
props: {
hasExistingPolicies: {
type: Boolean,
required: false,
default: false,
},
},
};
</script>
<template>
<div>
<gl-empty-state
v-if="hasExistingPolicies"
data-testid="empty-filter-state"
:svg-path="emptyFilterSvgPath"
:title="$options.i18n.emptyFilterTitle"
>
<template #description>
{{ $options.i18n.emptyFilterDescription }}
</template>
</gl-empty-state>
<gl-empty-state
v-else
data-testid="empty-list-state"
:primary-button-link="newPolicyPath"
:primary-button-text="$options.i18n.newPolicyButtonText"
:svg-path="emptyListSvgPath"
title=""
>
<template #description>
<p class="gl-font-weight-bold">
{{ $options.i18n.emptyStateDescription }}
</p>
</template>
</gl-empty-state>
</div>
</template>
<script>
import { mapActions } from 'vuex';
import NoEnvironmentEmptyState from '../no_environment_empty_state.vue';
import PoliciesHeader from './policies_header.vue';
import PoliciesList from './policies_list.vue';
......@@ -8,7 +7,6 @@ export default {
components: {
PoliciesHeader,
PoliciesList,
NoEnvironmentEmptyState,
},
inject: ['defaultEnvironmentId'],
data() {
......@@ -42,9 +40,8 @@ export default {
<template>
<div>
<policies-header @update-policy-list="handleUpdatePolicyList" />
<no-environment-empty-state v-if="!shouldFetchEnvironment" />
<policies-list
v-else
:has-environment="shouldFetchEnvironment"
:should-update-policy-list="shouldUpdatePolicyList"
@update-policy-list="handleUpdatePolicyList"
/>
......
<script>
import { GlAlert, GlSprintf, GlButton } from '@gitlab/ui';
import { s__ } from '~/locale';
import { NEW_POLICY_BUTTON_TEXT } from '../constants';
import ScanNewPolicyModal from './scan_new_policy_modal.vue';
export default {
......@@ -21,7 +22,7 @@ export default {
subtitle: s__(
'SecurityOrchestration|Enforce security for this project. %{linkStart}More information.%{linkEnd}',
),
newPolicyButtonText: s__('SecurityOrchestration|New policy'),
newPolicyButtonText: NEW_POLICY_BUTTON_TEXT,
editPolicyProjectButtonText: s__('SecurityOrchestration|Edit policy project'),
},
data() {
......
<script>
import {
GlTable,
GlEmptyState,
GlAlert,
GlSprintf,
GlLink,
GlIcon,
GlTooltipDirective,
} from '@gitlab/ui';
import { GlTable, GlAlert, GlSprintf, GlLink, GlIcon, GlTooltipDirective } from '@gitlab/ui';
import { mapState, mapGetters } from 'vuex';
import { PREDEFINED_NETWORK_POLICIES } from 'ee/threat_monitoring/constants';
import createFlash from '~/flash';
......@@ -22,6 +14,7 @@ import EnvironmentPicker from '../environment_picker.vue';
import PolicyDrawer from '../policy_drawer/policy_drawer.vue';
import PolicyEnvironments from '../policy_environments.vue';
import PolicyTypeFilter from '../policy_type_filter.vue';
import NoPoliciesEmptyState from './no_policies_empty_state.vue';
const createPolicyFetchError = ({ gqlError, networkError }) => {
const error =
......@@ -42,12 +35,12 @@ const getPoliciesWithType = (policies, policyType) =>
export default {
components: {
GlTable,
GlEmptyState,
GlAlert,
GlSprintf,
GlLink,
GlIcon,
EnvironmentPicker,
NoPoliciesEmptyState,
PolicyTypeFilter,
PolicyDrawer,
PolicyEnvironments,
......@@ -55,8 +48,13 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
inject: ['projectPath', 'documentationPath', 'newPolicyPath'],
inject: ['documentationPath', 'projectPath', 'newPolicyPath'],
props: {
hasEnvironment: {
type: Boolean,
required: false,
default: false,
},
shouldUpdatePolicyList: {
type: Boolean,
required: false,
......@@ -74,9 +72,11 @@ export default {
},
update(data) {
const policies = data?.project?.networkPolicies?.nodes ?? [];
const predefined = PREDEFINED_NETWORK_POLICIES.filter(
({ name }) => !policies.some((policy) => name === policy.name),
);
const predefined = this.hasEnvironment
? PREDEFINED_NETWORK_POLICIES.filter(
({ name }) => !policies.some((policy) => name === policy.name),
)
: [];
return [...policies, ...predefined];
},
error: createPolicyFetchError,
......@@ -165,6 +165,9 @@ export default {
policyType() {
return this.selectedPolicy ? getPolicyType(this.selectedPolicy.yaml) : 'container';
},
hasExistingPolicies() {
return !(this.selectedPolicyType === POLICY_TYPE_OPTIONS.ALL.value && !this.policies.length);
},
fields() {
const environments = {
key: 'environments',
......@@ -228,14 +231,9 @@ export default {
},
},
i18n: {
emptyStateDescription: s__(
`NetworkPolicies|Policies are a specification of how groups of pods are allowed to communicate with each other's network endpoints.`,
),
autodevopsNoticeDescription: s__(
`NetworkPolicies|If you are using Auto DevOps, your %{monospacedStart}auto-deploy-values.yaml%{monospacedEnd} file will not be updated if you change a policy in this section. Auto DevOps users should make changes by following the %{linkStart}Container Network Policy documentation%{linkEnd}.`,
`SecurityOrchestration|If you are using Auto DevOps, your %{monospacedStart}auto-deploy-values.yaml%{monospacedEnd} file will not be updated if you change a policy in this section. Auto DevOps users should make changes by following the %{linkStart}Container Network Policy documentation%{linkEnd}.`,
),
emptyStateButton: __('Learn more'),
emptyStateTitle: s__('NetworkPolicies|No policies detected'),
statusEnabled: __('Enabled'),
statusDisabled: __('Disabled'),
},
......@@ -314,13 +312,7 @@ export default {
<template #empty>
<slot name="empty-state">
<gl-empty-state
ref="tableEmptyState"
:title="$options.i18n.emptyStateTitle"
:description="$options.i18n.emptyStateDescription"
:primary-button-link="documentationFullPath"
:primary-button-text="$options.i18n.emptyStateButton"
/>
<no-policies-empty-state :has-existing-policies="hasExistingPolicies" />
</slot>
</template>
</gl-table>
......
......@@ -18,7 +18,8 @@ export default () => {
disableSecurityPolicyProject,
defaultEnvironmentId,
environmentsEndpoint,
emptyStateSvgPath,
emptyFilterSvgPath,
emptyListSvgPath,
documentationPath,
newPolicyPath,
projectPath,
......@@ -37,7 +38,8 @@ export default () => {
documentationPath,
newPolicyPath,
projectPath,
emptyStateSvgPath,
emptyFilterSvgPath,
emptyListSvgPath,
defaultEnvironmentId: parseInt(defaultEnvironmentId, 10),
},
render(createElement) {
......
......@@ -7,7 +7,8 @@
default_environment_id: default_environment_id,
disable_security_policy_project: disable_security_policy_project.to_s,
documentation_path: help_page_path('user/application_security/policies/index.md'),
empty_state_svg_path: image_path('illustrations/monitoring/unable_to_connect.svg'),
empty_filter_svg_path: image_path('illustrations/issues.svg'),
empty_list_svg_path: image_path('illustrations/security-dashboard_empty.svg'),
new_policy_path: new_project_security_policy_path(project),
environments_endpoint: project_environments_path(project),
project_path: project.full_path } }
import NoPoliciesEmptyState from 'ee/threat_monitoring/components/policies/no_policies_empty_state.vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
describe('NoPoliciesEmptyState component', () => {
let wrapper;
const findEmptyFilterState = () => wrapper.findByTestId('empty-filter-state');
const findEmptyListState = () => wrapper.findByTestId('empty-list-state');
const factory = (hasExistingPolicies = false) => {
wrapper = shallowMountExtended(NoPoliciesEmptyState, {
propsData: {
hasExistingPolicies,
},
provide: {
emptyFilterSvgPath: 'path/to/filter/svg',
emptyListSvgPath: 'path/to/list/svg',
newPolicyPath: 'path/to/new/policy',
},
});
};
afterEach(() => {
wrapper.destroy();
});
it.each`
title | findComponent | state | factoryFn
${'does display the empty filter state'} | ${findEmptyFilterState} | ${false} | ${factory}
${'does not display the empty list state'} | ${findEmptyListState} | ${true} | ${factory}
${'does not display the empty filter state'} | ${findEmptyFilterState} | ${true} | ${() => factory(true)}
${'does display the empty list state'} | ${findEmptyListState} | ${false} | ${() => factory(true)}
`('$title', async ({ factoryFn, findComponent, state }) => {
factoryFn();
await wrapper.vm.$nextTick();
expect(findComponent().exists()).toBe(state);
});
});
import NoEnvironmentEmptyState from 'ee/threat_monitoring/components/no_environment_empty_state.vue';
import PoliciesApp from 'ee/threat_monitoring/components/policies/policies_app.vue';
import PoliciesHeader from 'ee/threat_monitoring/components/policies/policies_header.vue';
import PoliciesList from 'ee/threat_monitoring/components/policies/policies_list.vue';
......@@ -13,7 +12,6 @@ describe('Policies App', () => {
const findPoliciesHeader = () => wrapper.findComponent(PoliciesHeader);
const findPoliciesList = () => wrapper.findComponent(PoliciesList);
const findEmptyState = () => wrapper.findComponent(NoEnvironmentEmptyState);
const createWrapper = ({ provide } = {}) => {
store = createStore();
......@@ -49,11 +47,11 @@ describe('Policies App', () => {
});
it('mounts the policies list component', () => {
expect(findPoliciesList().exists()).toBe(true);
});
it('does not mount the empty state', () => {
expect(findEmptyState().exists()).toBe(false);
const policiesList = findPoliciesList();
expect(policiesList.exists()).toBe(true);
expect(policiesList.props()).toMatchObject({
hasEnvironment: true,
});
});
it('fetches the environments when created', async () => {
......@@ -81,16 +79,12 @@ describe('Policies App', () => {
createWrapper();
});
it('mounts the policies header component', () => {
expect(findPoliciesHeader().exists()).toBe(true);
});
it('does not mount the policies list component', () => {
expect(findPoliciesList().exists()).toBe(false);
});
it('mounts the empty state', () => {
expect(findEmptyState().exists()).toBe(true);
it('mounts the policies list component', () => {
const policiesList = findPoliciesList();
expect(policiesList.exists()).toBe(true);
expect(policiesList.props()).toMatchObject({
hasEnvironment: false,
});
});
it('does not fetch the environments when created', () => {
......
import { GlAlert, GlButton, GlSprintf } from '@gitlab/ui';
import { NEW_POLICY_BUTTON_TEXT } from 'ee/threat_monitoring/components/constants';
import PoliciesHeader from 'ee/threat_monitoring/components/policies/policies_header.vue';
import ScanNewPolicyModal from 'ee/threat_monitoring/components/policies/scan_new_policy_modal.vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
......@@ -52,7 +53,7 @@ describe('Policies Header Component', () => {
});
it('displays New policy button with correct text and link', () => {
expect(findNewPolicyButton().text()).toBe('New policy');
expect(findNewPolicyButton().text()).toBe(NEW_POLICY_BUTTON_TEXT);
expect(findNewPolicyButton().attributes('href')).toBe(newPolicyPath);
});
......
......@@ -60,6 +60,7 @@ describe('PoliciesList component', () => {
{
propsData: {
documentationPath: 'documentation_path',
hasEnvironment: true,
newPolicyPath: '/policies/new',
},
store,
......@@ -79,6 +80,7 @@ describe('PoliciesList component', () => {
...GlDrawer.props,
},
}),
NoPoliciesEmptyState: true,
},
localVue,
},
......@@ -146,6 +148,10 @@ describe('PoliciesList component', () => {
rows = wrapper.findAll('tr');
});
it('does render default network policies', () => {
expect(findPolicyStatusCells().length).toBe(5);
});
it('fetches network policies on environment change', async () => {
store.dispatch.mockReset();
await store.commit('threatMonitoring/SET_CURRENT_ENVIRONMENT_ID', 2);
......@@ -297,4 +303,18 @@ describe('PoliciesList component', () => {
expect(findAutodevopsAlert().exists()).toBe(true);
});
});
describe('given no environement', () => {
beforeEach(() => {
mountWrapper({
propsData: {
hasEnvironment: false,
},
});
});
it('does not render default network policies', () => {
expect(findPolicyStatusCells().length).toBe(3);
});
});
});
......@@ -29628,6 +29628,9 @@ msgstr ""
msgid "SecurityOrchestration|Enforce security for this project. %{linkStart}More information.%{linkEnd}"
msgstr ""
msgid "SecurityOrchestration|If you are using Auto DevOps, your %{monospacedStart}auto-deploy-values.yaml%{monospacedEnd} file will not be updated if you change a policy in this section. Auto DevOps users should make changes by following the %{linkStart}Container Network Policy documentation%{linkEnd}."
msgstr ""
msgid "SecurityOrchestration|Network"
msgstr ""
......@@ -29658,9 +29661,18 @@ msgstr ""
msgid "SecurityOrchestration|Select security project"
msgstr ""
msgid "SecurityOrchestration|Sorry, your filter produced no results"
msgstr ""
msgid "SecurityOrchestration|There was a problem creating the new security policy"
msgstr ""
msgid "SecurityOrchestration|This project does not contain any security policies"
msgstr ""
msgid "SecurityOrchestration|To widen your search, change filters above or select a different security policy project."
msgstr ""
msgid "SecurityOrchestration|Update scan execution policies"
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