Commit ea9ff9a4 authored by ap4y's avatar ap4y

Add NetworkPolicyList component

This commit introduces a new component that will be used on the new
policy management tab. Environment dropdown was extracted from the
filters into a separate component and re-used in the new list
component.
parent d1b31894
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
import { GlFormGroup, GlDropdown, GlDropdownItem } from '@gitlab/ui';
export default {
components: {
GlFormGroup,
GlDropdown,
GlDropdownItem,
},
computed: {
...mapState('threatMonitoring', ['environments', 'currentEnvironmentId']),
...mapGetters('threatMonitoring', ['currentEnvironmentName', 'canChangeEnvironment']),
},
methods: {
...mapActions('threatMonitoring', ['setCurrentEnvironmentId']),
},
environmentFilterId: 'threat-monitoring-environment-filter',
};
</script>
<template>
<gl-form-group
:label="s__('ThreatMonitoring|Environment')"
label-size="sm"
:label-for="$options.environmentFilterId"
class="col-sm-6 col-md-4 col-lg-3 col-xl-2"
>
<gl-dropdown
:id="$options.environmentFilterId"
ref="environmentsDropdown"
class="mb-0 d-flex"
toggle-class="d-flex justify-content-between text-truncate"
:text="currentEnvironmentName"
:disabled="!canChangeEnvironment"
>
<gl-dropdown-item
v-for="environment in environments"
:key="environment.id"
ref="environmentsDropdownItem"
@click="setCurrentEnvironmentId(environment.id)"
>{{ environment.name }}</gl-dropdown-item
>
</gl-dropdown>
</gl-form-group>
</template>
<script>
import { mapState } from 'vuex';
import { GlTable, GlEmptyState } from '@gitlab/ui';
import { s__ } from '~/locale';
import { getTimeago } from '~/lib/utils/datetime_utility';
import { setUrlFragment } from '~/lib/utils/url_utility';
import EnvironmentPicker from './environment_picker.vue';
export default {
components: {
GlTable,
GlEmptyState,
EnvironmentPicker,
},
props: {
documentationPath: {
type: String,
required: true,
},
},
computed: {
...mapState('networkPolicies', ['policies', 'isLoadingPolicies']),
documentationFullPath() {
return setUrlFragment(this.documentationPath, 'container-network-policy');
},
},
methods: {
getTimeAgoString(creationTimestamp) {
return getTimeago().format(creationTimestamp);
},
},
fields: [
{ key: 'name', label: s__('NetworkPolicies|Name'), thClass: 'w-75 font-weight-bold' },
{ key: 'status', label: s__('NetworkPolicies|Status'), thClass: 'font-weight-bold' },
{
key: 'creationTimestamp',
label: s__('NetworkPolicies|Last modified'),
thClass: 'font-weight-bold',
},
],
emptyStateDescription: s__(
`NetworkPolicies|Policies are a specification of how groups of pods are allowed to communicate with each other network endpoints.`,
),
};
</script>
<template>
<div>
<div class="pt-3 px-3 bg-gray-light">
<div class="row">
<environment-picker ref="environmentsPicker" />
</div>
</div>
<gl-table
ref="policiesTable"
:busy="isLoadingPolicies"
:items="policies"
:fields="$options.fields"
head-variant="white"
stacked="md"
thead-class="gl-text-gray-900 border-bottom"
tbody-class="gl-text-gray-900"
show-empty
>
<template #cell(status)>
{{ s__('NetworkPolicies|Enabled') }}
</template>
<template #cell(creationTimestamp)="value">
{{ getTimeAgoString(value.item.creationTimestamp) }}
</template>
<template #empty>
<slot name="emptyState">
<gl-empty-state
ref="tableEmptyState"
:title="s__('NetworkPolicies|No policies detected')"
:description="$options.emptyStateDescription"
:primary-button-link="documentationFullPath"
:primary-button-text="__('Learn More')"
/>
</slot>
</template>
</gl-table>
</div>
</template>
<script> <script>
import { mapActions, mapGetters, mapState } from 'vuex'; import { mapActions, mapGetters } from 'vuex';
import { GlFormGroup, GlDropdown, GlDropdownItem } from '@gitlab/ui'; import { GlFormGroup } from '@gitlab/ui';
import { timeRanges, defaultTimeRange } from '~/vue_shared/constants'; import { timeRanges, defaultTimeRange } from '~/vue_shared/constants';
import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue'; import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue';
import EnvironmentPicker from './environment_picker.vue';
export default { export default {
name: 'ThreatMonitoringFilters', name: 'ThreatMonitoringFilters',
components: { components: {
GlFormGroup, GlFormGroup,
GlDropdown,
GlDropdownItem,
DateTimePicker, DateTimePicker,
EnvironmentPicker,
}, },
data() { data() {
return { return {
...@@ -19,56 +19,22 @@ export default { ...@@ -19,56 +19,22 @@ export default {
}; };
}, },
computed: { computed: {
...mapState('threatMonitoring', [ ...mapGetters('threatMonitoring', ['canChangeEnvironment']),
'environments',
'currentEnvironmentId',
'isLoadingEnvironments',
'isLoadingWafStatistics',
]),
...mapGetters('threatMonitoring', ['currentEnvironmentName']),
isDisabled() {
return (
this.isLoadingEnvironments || this.isLoadingWafStatistics || this.environments.length === 0
);
},
}, },
methods: { methods: {
...mapActions('threatMonitoring', ['setCurrentEnvironmentId', 'setCurrentTimeWindow']), ...mapActions('threatMonitoring', ['setCurrentTimeWindow']),
onDateTimePickerInput(timeRange) { onDateTimePickerInput(timeRange) {
this.selectedTimeRange = timeRange; this.selectedTimeRange = timeRange;
this.setCurrentTimeWindow(timeRange); this.setCurrentTimeWindow(timeRange);
}, },
}, },
environmentFilterId: 'threat-monitoring-environment-filter',
}; };
</script> </script>
<template> <template>
<div class="pt-3 px-3 bg-gray-light"> <div class="pt-3 px-3 bg-gray-light">
<div class="row"> <div class="row">
<gl-form-group <environment-picker />
:label="s__('ThreatMonitoring|Environment')"
label-size="sm"
:label-for="$options.environmentFilterId"
class="col-sm-6 col-md-4 col-lg-3 col-xl-2"
>
<gl-dropdown
:id="$options.environmentFilterId"
ref="environmentsDropdown"
class="mb-0 d-flex"
toggle-class="d-flex justify-content-between text-truncate"
:text="currentEnvironmentName"
:disabled="isDisabled"
>
<gl-dropdown-item
v-for="environment in environments"
:key="environment.id"
ref="environmentsDropdownItem"
@click="setCurrentEnvironmentId(environment.id)"
>{{ environment.name }}</gl-dropdown-item
>
</gl-dropdown>
</gl-form-group>
<gl-form-group <gl-form-group
:label="s__('ThreatMonitoring|Show last')" :label="s__('ThreatMonitoring|Show last')"
...@@ -81,7 +47,7 @@ export default { ...@@ -81,7 +47,7 @@ export default {
:custom-enabled="false" :custom-enabled="false"
:value="selectedTimeRange" :value="selectedTimeRange"
:options="timeRanges" :options="timeRanges"
:disabled="isDisabled" :disabled="!canChangeEnvironment"
@input="onDateTimePickerInput" @input="onDateTimePickerInput"
/> />
</gl-form-group> </gl-form-group>
......
/* eslint-disable import/prefer-default-export */
import { INVALID_CURRENT_ENVIRONMENT_NAME } from '../../../constants'; import { INVALID_CURRENT_ENVIRONMENT_NAME } from '../../../constants';
export const currentEnvironmentName = ({ currentEnvironmentId, environments }) => { export const currentEnvironmentName = ({ currentEnvironmentId, environments }) => {
const environment = environments.find(({ id }) => id === currentEnvironmentId); const environment = environments.find(({ id }) => id === currentEnvironmentId);
return environment ? environment.name : INVALID_CURRENT_ENVIRONMENT_NAME; return environment ? environment.name : INVALID_CURRENT_ENVIRONMENT_NAME;
}; };
export const canChangeEnvironment = ({
isLoadingEnvironments,
isLoadingWafStatistics,
isLoadingNetworkPolicyStatistics,
environments,
}) =>
!isLoadingEnvironments &&
!isLoadingWafStatistics &&
!isLoadingNetworkPolicyStatistics &&
environments.length > 0;
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`NetworkPolicyList component given there is a default environment with no data to display shows the table empty state 1`] = `
<section
class="row empty-state text-center"
>
<div
class="col-12"
>
<!---->
</div>
<div
class="col-12"
>
<div
class="text-content gl-mx-auto gl-my-0 gl-p-5"
>
<h1
class="h4"
>
No policies detected
</h1>
<p>
Policies are a specification of how groups of pods are allowed to communicate with each other network endpoints.
</p>
<div>
<a
class="btn btn-success btn-md gl-button"
href="documentation_path#container-network-policy"
>
<!---->
<!---->
<span
class="gl-button-text"
>
Learn More
</span>
</a>
<!---->
</div>
</div>
</div>
</section>
`;
exports[`NetworkPolicyList component renders policies table 1`] = `
<table
aria-busy="false"
aria-colcount="3"
aria-describedby="__BVID__39__caption_"
class="table b-table gl-table b-table-stacked-md"
id="__BVID__39"
role="table"
>
<!---->
<!---->
<thead
class="thead-white gl-text-gray-900 border-bottom"
role="rowgroup"
>
<!---->
<tr
class=""
role="row"
>
<th
aria-colindex="1"
class="w-75 font-weight-bold"
role="columnheader"
scope="col"
>
Name
</th>
<th
aria-colindex="2"
class="font-weight-bold"
role="columnheader"
scope="col"
>
Status
</th>
<th
aria-colindex="3"
class="font-weight-bold"
role="columnheader"
scope="col"
>
Last modified
</th>
</tr>
</thead>
<tbody
class="gl-text-gray-900"
role="rowgroup"
>
<!---->
<tr
class=""
role="row"
>
<td
aria-colindex="1"
class=""
data-label="Name"
role="cell"
>
<div>
policy
</div>
</td>
<td
aria-colindex="2"
class=""
data-label="Status"
role="cell"
>
<div>
Enabled
</div>
</td>
<td
aria-colindex="3"
class=""
data-label="Last modified"
role="cell"
>
<div>
just now
</div>
</td>
</tr>
<!---->
<!---->
</tbody>
<!---->
</table>
`;
import { shallowMount } from '@vue/test-utils';
import createStore from 'ee/threat_monitoring/store';
import EnvironmentPicker from 'ee/threat_monitoring/components/environment_picker.vue';
import { INVALID_CURRENT_ENVIRONMENT_NAME } from 'ee/threat_monitoring/constants';
import { mockEnvironmentsResponse } from '../mock_data';
const mockEnvironments = mockEnvironmentsResponse.environments;
describe('EnvironmentPicker component', () => {
let store;
let wrapper;
const factory = state => {
store = createStore();
Object.assign(store.state.threatMonitoring, state);
jest.spyOn(store, 'dispatch').mockImplementation();
wrapper = shallowMount(EnvironmentPicker, {
store,
});
};
const findEnvironmentsDropdown = () => wrapper.find({ ref: 'environmentsDropdown' });
const findEnvironmentsDropdownItems = () => wrapper.findAll({ ref: 'environmentsDropdownItem' });
afterEach(() => {
wrapper.destroy();
});
describe('the environments dropdown', () => {
describe('given there are no environments', () => {
beforeEach(() => {
factory();
});
it('has text set to the INVALID_CURRENT_ENVIRONMENT_NAME', () => {
expect(findEnvironmentsDropdown().attributes().text).toBe(INVALID_CURRENT_ENVIRONMENT_NAME);
});
it('has no dropdown items', () => {
expect(findEnvironmentsDropdownItems()).toHaveLength(0);
});
});
describe('given there are environments', () => {
const currentEnvironment = mockEnvironments[1];
beforeEach(() => {
factory({
environments: mockEnvironments,
currentEnvironmentId: currentEnvironment.id,
});
});
it('is not disabled', () => {
expect(findEnvironmentsDropdown().attributes().disabled).toBe(undefined);
});
it('has text set to the current environment', () => {
expect(findEnvironmentsDropdown().attributes().text).toBe(currentEnvironment.name);
});
it('has dropdown items for each environment', () => {
const dropdownItems = findEnvironmentsDropdownItems();
mockEnvironments.forEach((environment, i) => {
const dropdownItem = dropdownItems.at(i);
expect(dropdownItem.text()).toBe(environment.name);
dropdownItem.vm.$emit('click');
expect(store.dispatch).toHaveBeenCalledWith(
'threatMonitoring/setCurrentEnvironmentId',
environment.id,
);
});
});
});
});
describe.each`
context | isLoadingEnvironments | isLoadingWafStatistics | isLoadingNetworkPolicyStatistics | environments
${'environments are loading'} | ${true} | ${false} | ${false} | ${mockEnvironments}
${'WAF statistics are loading'} | ${false} | ${true} | ${false} | ${mockEnvironments}
${'NetPol statistics are loading'} | ${false} | ${false} | ${true} | ${mockEnvironments}
${'there are no environments'} | ${false} | ${false} | ${false} | ${[]}
`(
'given $context',
({
isLoadingEnvironments,
isLoadingWafStatistics,
isLoadingNetworkPolicyStatistics,
environments,
}) => {
beforeEach(() => {
factory({
environments,
isLoadingEnvironments,
isLoadingWafStatistics,
isLoadingNetworkPolicyStatistics,
});
return wrapper.vm.$nextTick();
});
it('disables the environments dropdown', () => {
expect(findEnvironmentsDropdown().attributes('disabled')).toBe('true');
});
},
);
});
import { mount } from '@vue/test-utils';
import createStore from 'ee/threat_monitoring/store';
import NetworkPolicyList from 'ee/threat_monitoring/components/network_policy_list.vue';
import { GlTable } from '@gitlab/ui';
import { mockPoliciesResponse } from '../mock_data';
describe('NetworkPolicyList component', () => {
let store;
let wrapper;
const factory = ({ propsData, state } = {}) => {
store = createStore();
Object.assign(store.state.networkPolicies, {
isLoadingPolicies: false,
policies: mockPoliciesResponse,
...state,
});
wrapper = mount(NetworkPolicyList, {
propsData: {
documentationPath: 'documentation_path',
...propsData,
},
store,
});
};
const findEnvironmentsPicker = () => wrapper.find({ ref: 'environmentsPicker' });
const findPoliciesTable = () => wrapper.find(GlTable);
const findTableEmptyState = () => wrapper.find({ ref: 'tableEmptyState' });
beforeEach(() => {
factory({});
});
afterEach(() => {
wrapper.destroy();
});
it('renders EnvironmentPicker', () => {
expect(findEnvironmentsPicker().exists()).toBe(true);
});
it('renders policies table', () => {
expect(findPoliciesTable().element).toMatchSnapshot();
});
describe('given there is a default environment with no data to display', () => {
beforeEach(() => {
factory({
state: {
policies: [],
},
});
});
it('shows the table empty state', () => {
expect(findTableEmptyState().element).toMatchSnapshot();
});
});
});
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import createStore from 'ee/threat_monitoring/store'; import createStore from 'ee/threat_monitoring/store';
import ThreatMonitoringFilters from 'ee/threat_monitoring/components/threat_monitoring_filters.vue'; import ThreatMonitoringFilters from 'ee/threat_monitoring/components/threat_monitoring_filters.vue';
import { INVALID_CURRENT_ENVIRONMENT_NAME } from 'ee/threat_monitoring/constants'; import EnvironmentPicker from 'ee/threat_monitoring/components/environment_picker.vue';
import { mockEnvironmentsResponse } from '../mock_data'; import { mockEnvironmentsResponse } from '../mock_data';
import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue'; import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue';
import { timeRanges, defaultTimeRange } from '~/vue_shared/constants'; import { timeRanges, defaultTimeRange } from '~/vue_shared/constants';
...@@ -23,61 +23,20 @@ describe('ThreatMonitoringFilters component', () => { ...@@ -23,61 +23,20 @@ describe('ThreatMonitoringFilters component', () => {
}); });
}; };
const findEnvironmentsDropdown = () => wrapper.find({ ref: 'environmentsDropdown' }); const findEnvironmentsPicker = () => wrapper.find(EnvironmentPicker);
const findEnvironmentsDropdownItems = () => wrapper.findAll({ ref: 'environmentsDropdownItem' });
const findShowLastDropdown = () => wrapper.find(DateTimePicker); const findShowLastDropdown = () => wrapper.find(DateTimePicker);
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
}); });
describe('the environments dropdown', () => { describe('the environments picker', () => {
describe('given there are no environments', () => { beforeEach(() => {
beforeEach(() => { factory();
factory();
});
it('has text set to the INVALID_CURRENT_ENVIRONMENT_NAME', () => {
expect(findEnvironmentsDropdown().attributes().text).toBe(INVALID_CURRENT_ENVIRONMENT_NAME);
});
it('has no dropdown items', () => {
expect(findEnvironmentsDropdownItems()).toHaveLength(0);
});
}); });
describe('given there are environments', () => { it('renders EnvironmentPicker', () => {
const currentEnvironment = mockEnvironments[1]; expect(findEnvironmentsPicker().exists()).toBe(true);
beforeEach(() => {
factory({
environments: mockEnvironments,
currentEnvironmentId: currentEnvironment.id,
});
});
it('is not disabled', () => {
expect(findEnvironmentsDropdown().attributes().disabled).toBe(undefined);
});
it('has text set to the current environment', () => {
expect(findEnvironmentsDropdown().attributes().text).toBe(currentEnvironment.name);
});
it('has dropdown items for each environment', () => {
const dropdownItems = findEnvironmentsDropdownItems();
mockEnvironments.forEach((environment, i) => {
const dropdownItem = dropdownItems.at(i);
expect(dropdownItem.text()).toBe(environment.name);
dropdownItem.vm.$emit('click');
expect(store.dispatch).toHaveBeenCalledWith(
'threatMonitoring/setCurrentEnvironmentId',
environment.id,
);
});
});
}); });
}); });
...@@ -107,27 +66,33 @@ describe('ThreatMonitoringFilters component', () => { ...@@ -107,27 +66,33 @@ describe('ThreatMonitoringFilters component', () => {
}); });
describe.each` describe.each`
context | isLoadingEnvironments | isLoadingWafStatistics | environments context | isLoadingEnvironments | isLoadingWafStatistics | isLoadingNetworkPolicyStatistics | environments
${'environments are loading'} | ${true} | ${false} | ${mockEnvironments} ${'environments are loading'} | ${true} | ${false} | ${false} | ${mockEnvironments}
${'WAF statistics are loading'} | ${false} | ${true} | ${mockEnvironments} ${'WAF statistics are loading'} | ${false} | ${true} | ${false} | ${mockEnvironments}
${'there are no environments'} | ${false} | ${false} | ${[]} ${'NetPol statistics are loading'} | ${false} | ${false} | ${true} | ${mockEnvironments}
`('given $context', ({ isLoadingEnvironments, isLoadingWafStatistics, environments }) => { ${'there are no environments'} | ${false} | ${false} | ${false} | ${[]}
beforeEach(() => { `(
factory({ 'given $context',
environments, ({
isLoadingEnvironments, isLoadingEnvironments,
isLoadingWafStatistics, isLoadingWafStatistics,
}); isLoadingNetworkPolicyStatistics,
environments,
return wrapper.vm.$nextTick(); }) => {
}); beforeEach(() => {
factory({
environments,
isLoadingEnvironments,
isLoadingWafStatistics,
isLoadingNetworkPolicyStatistics,
});
it('disables the environments dropdown', () => { return wrapper.vm.$nextTick();
expect(findEnvironmentsDropdown().attributes('disabled')).toBe('true'); });
});
it('disables the "show last" dropdown', () => { it('disables the "show last" dropdown', () => {
expect(findShowLastDropdown().attributes('disabled')).toBe('true'); expect(findShowLastDropdown().attributes('disabled')).toBe('true');
}); });
}); },
);
}); });
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