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