Commit 8054ddac authored by Paul Gascou-Vaillancourt's avatar Paul Gascou-Vaillancourt Committed by Miguel Rincon

Create environment column in policies list

This replaces the obsolete "Namespace" column with a newer
"Environments" column that summarizes the environments a policy is
associated with.

Changelog: changed
EE: true
parent d183f9ab
...@@ -11,7 +11,7 @@ export default { ...@@ -11,7 +11,7 @@ export default {
}, },
computed: { computed: {
enforcementStatusLabel() { enforcementStatusLabel() {
return this.policy?.isEnabled ? __('Enabled') : __('Disabled'); return this.policy?.enabled ? __('Enabled') : __('Disabled');
}, },
}, },
}; };
......
<script> <script>
import { GlIntersperse } from '@gitlab/ui';
import { removeUnnecessaryDashes } from '../../utils'; import { removeUnnecessaryDashes } from '../../utils';
import { fromYaml, humanizeNetworkPolicy } from '../policy_editor/network_policy/lib'; import { fromYaml, humanizeNetworkPolicy } from '../policy_editor/network_policy/lib';
import PolicyPreview from '../policy_editor/policy_preview.vue'; import PolicyPreview from '../policy_editor/policy_preview.vue';
...@@ -7,29 +8,37 @@ import PolicyInfoRow from './policy_info_row.vue'; ...@@ -7,29 +8,37 @@ import PolicyInfoRow from './policy_info_row.vue';
export default { export default {
components: { components: {
GlIntersperse,
BasePolicy, BasePolicy,
PolicyPreview, PolicyPreview,
PolicyInfoRow, PolicyInfoRow,
}, },
props: { props: {
value: { policy: {
type: String, type: Object,
required: true, required: true,
}, },
}, },
computed: { computed: {
initialTab() { parsedYaml() {
return this.policy ? 0 : 1; try {
const parsedYaml = fromYaml(this.policy.yaml);
return parsedYaml.error ? null : parsedYaml;
} catch (e) {
return null;
}
}, },
policy() { initialTab() {
const policy = fromYaml(this.value); return this.parsedYaml ? 0 : 1;
return policy.error ? null : policy;
}, },
humanizedPolicy() { humanizedPolicy() {
return this.policy ? humanizeNetworkPolicy(this.policy) : this.policy; return this.parsedYaml ? humanizeNetworkPolicy(this.parsedYaml) : this.parsedYaml;
}, },
policyYaml() { policyYaml() {
return removeUnnecessaryDashes(this.value); return removeUnnecessaryDashes(this.policy.yaml);
},
environments() {
return this.policy.environments?.nodes ?? [];
}, },
}, },
}; };
...@@ -40,17 +49,29 @@ export default { ...@@ -40,17 +49,29 @@ export default {
<template #type>{{ s__('NetworkPolicies|Network') }}</template> <template #type>{{ s__('NetworkPolicies|Network') }}</template>
<template #default="{ enforcementStatusLabel }"> <template #default="{ enforcementStatusLabel }">
<div v-if="policy"> <div v-if="parsedYaml">
<policy-info-row <policy-info-row
v-if="policy.description" v-if="parsedYaml.description"
data-testid="description" data-testid="description"
:label="s__('NetworkPolicies|Description')" :label="__('Description')"
>{{ policy.description }}</policy-info-row >{{ parsedYaml.description }}</policy-info-row
> >
<policy-info-row :label="s__('NetworkPolicies|Enforcement status')">{{ <policy-info-row :label="s__('NetworkPolicies|Enforcement status')">{{
enforcementStatusLabel enforcementStatusLabel
}}</policy-info-row> }}</policy-info-row>
<policy-info-row
v-if="environments.length"
data-testid="environments"
:label="s__('SecurityPolicies|Environment(s)')"
>
<gl-intersperse>
<span v-for="environment in environments" :key="environment.name">{{
environment.name
}}</span>
</gl-intersperse>
</policy-info-row>
</div> </div>
<policy-preview <policy-preview
......
...@@ -71,7 +71,7 @@ export default { ...@@ -71,7 +71,7 @@ export default {
> >
</template> </template>
<div v-if="policy"> <div v-if="policy">
<component :is="policyComponent" v-if="policyComponent" :value="policy.yaml" /> <component :is="policyComponent" v-if="policyComponent" :policy="policy" />
<div v-else> <div v-else>
<h5>{{ s__('NetworkPolicies|Policy definition') }}</h5> <h5>{{ s__('NetworkPolicies|Policy definition') }}</h5>
<p> <p>
......
...@@ -11,14 +11,18 @@ export default { ...@@ -11,14 +11,18 @@ export default {
PolicyInfoRow, PolicyInfoRow,
}, },
props: { props: {
value: { policy: {
type: String, type: Object,
required: true, required: true,
}, },
}, },
computed: { computed: {
policy() { parsedYaml() {
return fromYaml(this.value); try {
return fromYaml(this.policy.yaml);
} catch (e) {
return null;
}
}, },
}, },
}; };
...@@ -29,12 +33,12 @@ export default { ...@@ -29,12 +33,12 @@ export default {
<template #type>{{ s__('SecurityPolicies|Scan execution') }}</template> <template #type>{{ s__('SecurityPolicies|Scan execution') }}</template>
<template #default="{ enforcementStatusLabel }"> <template #default="{ enforcementStatusLabel }">
<div v-if="policy"> <div v-if="parsedYaml">
<policy-info-row <policy-info-row
v-if="policy.description" v-if="parsedYaml.description"
data-testid="description" data-testid="description"
:label="s__('SecurityPolicies|Description')" :label="s__('SecurityPolicies|Description')"
>{{ policy.description }}</policy-info-row >{{ parsedYaml.description }}</policy-info-row
> >
<!-- TODO: humanize policy rules --> <!-- TODO: humanize policy rules -->
...@@ -58,11 +62,11 @@ export default { ...@@ -58,11 +62,11 @@ export default {
}}</policy-info-row> }}</policy-info-row>
<policy-info-row <policy-info-row
v-if="policy.latestScan" v-if="parsedYaml.latestScan"
data-testid="latest-scan" data-testid="latest-scan"
:label="s__('SecurityPolicies|Latest scan')" :label="s__('SecurityPolicies|Latest scan')"
>{{ policy.latestScan.date }} >{{ parsedYaml.latestScan.date }}
<gl-link :href="policy.latestScan.pipelineUrl">{{ <gl-link :href="parsedYaml.latestScan.pipelineUrl">{{
s__('SecurityPolicies|view results') s__('SecurityPolicies|view results')
}}</gl-link></policy-info-row }}</gl-link></policy-info-row
> >
......
<script>
export default {
name: 'PolicyEnvironments',
props: {
environments: {
type: Object,
required: false,
default: null,
},
},
computed: {
nodes() {
return this.environments?.nodes ?? [];
},
},
};
</script>
<template>
<span>
<template v-if="nodes.length > 0">
{{ nodes[0].name }}
<span v-if="nodes.length > 1" class="gl-text-gray-600">
{{ sprintf(s__('SecurityPolicies|+%{count} more'), { count: nodes.length - 1 }) }}
</span>
</template>
<template v-else>-</template>
</span>
</template>
...@@ -21,6 +21,7 @@ import scanExecutionPoliciesQuery from '../graphql/queries/scan_execution_polici ...@@ -21,6 +21,7 @@ import scanExecutionPoliciesQuery from '../graphql/queries/scan_execution_polici
import { POLICY_TYPE_OPTIONS } from './constants'; import { POLICY_TYPE_OPTIONS } from './constants';
import EnvironmentPicker from './environment_picker.vue'; import EnvironmentPicker from './environment_picker.vue';
import PolicyDrawer from './policy_drawer/policy_drawer.vue'; import PolicyDrawer from './policy_drawer/policy_drawer.vue';
import PolicyEnvironments from './policy_environments.vue';
import PolicyTypeFilter from './policy_type_filter.vue'; import PolicyTypeFilter from './policy_type_filter.vue';
const createPolicyFetchError = ({ gqlError, networkError }) => { const createPolicyFetchError = ({ gqlError, networkError }) => {
...@@ -51,6 +52,7 @@ export default { ...@@ -51,6 +52,7 @@ export default {
EnvironmentPicker, EnvironmentPicker,
PolicyTypeFilter, PolicyTypeFilter,
PolicyDrawer, PolicyDrawer,
PolicyEnvironments,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
...@@ -165,9 +167,9 @@ export default { ...@@ -165,9 +167,9 @@ export default {
: ''; : '';
}, },
fields() { fields() {
const namespace = { const environments = {
key: 'namespace', key: 'environments',
label: s__('NetworkPolicies|Namespace'), label: s__('SecurityPolicies|Environment(s)'),
}; };
const fields = [ const fields = [
{ {
...@@ -180,7 +182,7 @@ export default { ...@@ -180,7 +182,7 @@ export default {
}, },
{ {
key: 'name', key: 'name',
label: s__('NetworkPolicies|Name'), label: __('Name'),
thClass: 'gl-w-half', thClass: 'gl-w-half',
}, },
{ {
...@@ -190,12 +192,12 @@ export default { ...@@ -190,12 +192,12 @@ export default {
}, },
{ {
key: 'updatedAt', key: 'updatedAt',
label: s__('NetworkPolicies|Last modified'), label: __('Last modified'),
sortable: true, sortable: true,
}, },
]; ];
// Adds column 'namespace' only while 'all environments' option is selected // Adds column 'environments' only while 'all environments' option is selected
if (this.allEnvironments) fields.splice(2, 0, namespace); if (this.allEnvironments) fields.splice(2, 0, environments);
return fields; return fields;
}, },
...@@ -302,6 +304,10 @@ export default { ...@@ -302,6 +304,10 @@ export default {
<span v-else class="gl-sr-only">{{ $options.i18n.statusDisabled }}</span> <span v-else class="gl-sr-only">{{ $options.i18n.statusDisabled }}</span>
</template> </template>
<template #cell(environments)="value">
<policy-environments :environments="value.item.environments" />
</template>
<template #cell(updatedAt)="value"> <template #cell(updatedAt)="value">
{{ getTimeAgoString(value.item.updatedAt) }} {{ getTimeAgoString(value.item.updatedAt) }}
</template> </template>
......
fragment Environments on NetworkPolicy {
environments {
nodes {
name
}
}
}
#import "./environments.fragment.graphql"
query networkPolicies($fullPath: ID!, $environmentId: EnvironmentID) { query networkPolicies($fullPath: ID!, $environmentId: EnvironmentID) {
project(fullPath: $fullPath) { project(fullPath: $fullPath) {
networkPolicies(environmentId: $environmentId) { networkPolicies(environmentId: $environmentId) {
...@@ -8,6 +10,7 @@ query networkPolicies($fullPath: ID!, $environmentId: EnvironmentID) { ...@@ -8,6 +10,7 @@ query networkPolicies($fullPath: ID!, $environmentId: EnvironmentID) {
enabled enabled
fromAutoDevops fromAutoDevops
updatedAt updatedAt
...Environments
} }
} }
} }
......
...@@ -27,6 +27,8 @@ exports[`CiliumNetworkPolicy component supported YAML renders policy preview tab ...@@ -27,6 +27,8 @@ exports[`CiliumNetworkPolicy component supported YAML renders policy preview tab
> >
Disabled Disabled
</policy-info-row-stub> </policy-info-row-stub>
<!---->
</div> </div>
<policy-preview-stub <policy-preview-stub
......
...@@ -31,15 +31,15 @@ describe('BasePolicy component', () => { ...@@ -31,15 +31,15 @@ describe('BasePolicy component', () => {
}); });
it.each` it.each`
description | isEnabled | expectedLabel description | enabled | expectedLabel
${'enabled'} | ${true} | ${'Enabled'} ${'enabled'} | ${true} | ${'Enabled'}
${'disabled'} | ${false} | ${'Disabled'} ${'disabled'} | ${false} | ${'Disabled'}
`( `(
'renders the enforcement status label when policy is $description', 'renders the enforcement status label when policy is $description',
({ isEnabled, expectedLabel }) => { ({ enabled, expectedLabel }) => {
factory({ factory({
policy: { policy: {
isEnabled, enabled,
}, },
}); });
......
import { GlIntersperse } from '@gitlab/ui';
import BasePolicy from 'ee/threat_monitoring/components/policy_drawer/base_policy.vue'; import BasePolicy from 'ee/threat_monitoring/components/policy_drawer/base_policy.vue';
import CiliumNetworkPolicy from 'ee/threat_monitoring/components/policy_drawer/cilium_network_policy.vue'; import CiliumNetworkPolicy from 'ee/threat_monitoring/components/policy_drawer/cilium_network_policy.vue';
import { toYaml } from 'ee/threat_monitoring/components/policy_editor/network_policy/lib'; import { toYaml } from 'ee/threat_monitoring/components/policy_editor/network_policy/lib';
...@@ -6,16 +7,17 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; ...@@ -6,16 +7,17 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
describe('CiliumNetworkPolicy component', () => { describe('CiliumNetworkPolicy component', () => {
let wrapper; let wrapper;
const policy = { const supportedYaml = toYaml({
name: 'test-policy', name: 'test-policy',
description: 'test description', description: 'test description',
endpointLabels: '', endpointLabels: '',
rules: [], rules: [],
}; });
const unsupportedYaml = 'unsupportedPrimaryKey: test'; const unsupportedYaml = 'unsupportedPrimaryKey: test';
const findPolicyPreview = () => wrapper.findComponent(PolicyPreview); const findPolicyPreview = () => wrapper.findComponent(PolicyPreview);
const findDescription = () => wrapper.findByTestId('description'); const findDescription = () => wrapper.findByTestId('description');
const findEnvironments = () => wrapper.findByTestId('environments');
const factory = ({ propsData } = {}) => { const factory = ({ propsData } = {}) => {
wrapper = shallowMountExtended(CiliumNetworkPolicy, { wrapper = shallowMountExtended(CiliumNetworkPolicy, {
...@@ -24,6 +26,7 @@ describe('CiliumNetworkPolicy component', () => { ...@@ -24,6 +26,7 @@ describe('CiliumNetworkPolicy component', () => {
}, },
stubs: { stubs: {
BasePolicy, BasePolicy,
GlIntersperse,
}, },
}); });
}; };
...@@ -34,7 +37,7 @@ describe('CiliumNetworkPolicy component', () => { ...@@ -34,7 +37,7 @@ describe('CiliumNetworkPolicy component', () => {
describe('supported YAML', () => { describe('supported YAML', () => {
beforeEach(() => { beforeEach(() => {
factory({ propsData: { value: toYaml(policy) } }); factory({ propsData: { policy: { yaml: supportedYaml } } });
}); });
it('renders policy preview tabs', () => { it('renders policy preview tabs', () => {
...@@ -51,14 +54,14 @@ describe('CiliumNetworkPolicy component', () => { ...@@ -51,14 +54,14 @@ describe('CiliumNetworkPolicy component', () => {
expect(findPolicyPreview().props()).toStrictEqual({ expect(findPolicyPreview().props()).toStrictEqual({
initialTab: 0, initialTab: 0,
policyDescription: 'Deny all traffic', policyDescription: 'Deny all traffic',
policyYaml: toYaml(policy), policyYaml: supportedYaml,
}); });
}); });
}); });
describe('unsupported YAML', () => { describe('unsupported YAML', () => {
beforeEach(() => { beforeEach(() => {
factory({ propsData: { value: unsupportedYaml } }); factory({ propsData: { policy: { yaml: unsupportedYaml } } });
}); });
it('renders policy preview tabs', () => { it('renders policy preview tabs', () => {
...@@ -78,4 +81,35 @@ describe('CiliumNetworkPolicy component', () => { ...@@ -78,4 +81,35 @@ describe('CiliumNetworkPolicy component', () => {
}); });
}); });
}); });
describe('environments', () => {
it('renders environments if any', () => {
factory({
propsData: {
policy: {
environments: {
nodes: [{ name: 'production' }, { name: 'local' }],
},
yaml: supportedYaml,
},
},
});
expect(findEnvironments().exists()).toBe(true);
expect(findEnvironments().text()).toBe('production, local');
});
it("does not render environments row if there aren't any", () => {
factory({
propsData: {
policy: {
environments: {
nodes: [],
},
yaml: supportedYaml,
},
},
});
expect(findEnvironments().exists()).toBe(false);
});
});
}); });
...@@ -23,7 +23,7 @@ describe('ScanExecutionPolicy component', () => { ...@@ -23,7 +23,7 @@ describe('ScanExecutionPolicy component', () => {
describe('supported YAML', () => { describe('supported YAML', () => {
beforeEach(() => { beforeEach(() => {
factory({ propsData: { value: mockScanExecutionPolicy.yaml } }); factory({ propsData: { policy: mockScanExecutionPolicy } });
}); });
it('does render the policy description', () => { it('does render the policy description', () => {
......
import { shallowMount } from '@vue/test-utils';
import PolicyEnvironments from 'ee/threat_monitoring/components/policy_environments.vue';
describe('PolicyEnvironments component', () => {
let wrapper;
const createComponent = (nodes = []) => {
wrapper = shallowMount(PolicyEnvironments, {
propsData: {
environments: { nodes },
},
});
};
afterEach(() => {
wrapper.destroy();
});
it("renders the environement's name if there is only one", () => {
const name = 'My environment';
createComponent([{ name }]);
expect(wrapper.text()).toBe(name);
});
it("renders the first environement's name and a count of the remaining ones", () => {
const firstEnvironmentName = 'Primary environment';
createComponent([
{ name: firstEnvironmentName },
{
name: 'Secondary environment',
},
{
name: 'Tertiary environment',
},
]);
expect(wrapper.text()).toMatchInterpolatedText(`${firstEnvironmentName} +2 more`);
});
it('renders a "-" when there is no environment', () => {
createComponent();
expect(wrapper.text()).toBe('-');
});
});
...@@ -231,9 +231,9 @@ describe('PolicyList component', () => { ...@@ -231,9 +231,9 @@ describe('PolicyList component', () => {
wrapper.vm.$store.state.threatMonitoring.allEnvironments = true; wrapper.vm.$store.state.threatMonitoring.allEnvironments = true;
}); });
it('renders namespace column', () => { it('renders environments column', () => {
const namespaceHeader = findPoliciesTable().findAll('[role="columnheader"]').at(2); const environmentsHeader = findPoliciesTable().findAll('[role="columnheader"]').at(2);
expect(namespaceHeader.text()).toContain('Namespace'); expect(environmentsHeader.text()).toContain('Environment(s)');
}); });
}); });
......
...@@ -39,6 +39,9 @@ spec: ...@@ -39,6 +39,9 @@ spec:
updatedAt: '2020-04-14T00:08:30Z', updatedAt: '2020-04-14T00:08:30Z',
enabled: true, enabled: true,
fromAutoDevops: false, fromAutoDevops: false,
environments: {
nodes: [],
},
}, },
]; ];
......
...@@ -19185,6 +19185,9 @@ msgstr "" ...@@ -19185,6 +19185,9 @@ msgstr ""
msgid "Last item before this page loaded in your browser:" msgid "Last item before this page loaded in your browser:"
msgstr "" msgstr ""
msgid "Last modified"
msgstr ""
msgid "Last month" msgid "Last month"
msgstr "" msgstr ""
...@@ -21770,15 +21773,9 @@ msgstr "" ...@@ -21770,15 +21773,9 @@ msgstr ""
msgid "NetworkPolicies|Kubernetes error: %{error}" msgid "NetworkPolicies|Kubernetes error: %{error}"
msgstr "" msgstr ""
msgid "NetworkPolicies|Last modified"
msgstr ""
msgid "NetworkPolicies|Name" msgid "NetworkPolicies|Name"
msgstr "" msgstr ""
msgid "NetworkPolicies|Namespace"
msgstr ""
msgid "NetworkPolicies|Network" msgid "NetworkPolicies|Network"
msgstr "" msgstr ""
...@@ -29099,6 +29096,9 @@ msgstr "" ...@@ -29099,6 +29096,9 @@ msgstr ""
msgid "SecurityOrchestration|Security policy project" msgid "SecurityOrchestration|Security policy project"
msgstr "" msgstr ""
msgid "SecurityPolicies|+%{count} more"
msgstr ""
msgid "SecurityPolicies|All policies" msgid "SecurityPolicies|All policies"
msgstr "" msgstr ""
...@@ -29108,6 +29108,9 @@ msgstr "" ...@@ -29108,6 +29108,9 @@ msgstr ""
msgid "SecurityPolicies|Enforcement status" msgid "SecurityPolicies|Enforcement status"
msgstr "" msgstr ""
msgid "SecurityPolicies|Environment(s)"
msgstr ""
msgid "SecurityPolicies|Latest scan" msgid "SecurityPolicies|Latest scan"
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