Commit ab09a438 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch 'network-policy-management-ui' into 'master'

Add Policies tab to the Threat Management page

See merge request gitlab-org/gitlab!31289
parents 909308c1 537bcb16
<script> <script>
import { mapActions } from 'vuex'; import { mapActions } from 'vuex';
import { GlAlert, GlEmptyState, GlIcon, GlLink, GlPopover } from '@gitlab/ui'; import { GlAlert, GlEmptyState, GlIcon, GlLink, GlPopover, GlTabs, GlTab } from '@gitlab/ui';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import ThreatMonitoringFilters from './threat_monitoring_filters.vue'; import ThreatMonitoringFilters from './threat_monitoring_filters.vue';
import ThreatMonitoringSection from './threat_monitoring_section.vue'; import ThreatMonitoringSection from './threat_monitoring_section.vue';
import NetworkPolicyList from './network_policy_list.vue';
export default { export default {
name: 'ThreatMonitoring', name: 'ThreatMonitoring',
...@@ -15,8 +16,11 @@ export default { ...@@ -15,8 +16,11 @@ export default {
GlIcon, GlIcon,
GlLink, GlLink,
GlPopover, GlPopover,
GlTabs,
GlTab,
ThreatMonitoringFilters, ThreatMonitoringFilters,
ThreatMonitoringSection, ThreatMonitoringSection,
NetworkPolicyList,
}, },
mixins: [glFeatureFlagsMixin()], mixins: [glFeatureFlagsMixin()],
props: { props: {
...@@ -154,38 +158,49 @@ export default { ...@@ -154,38 +158,49 @@ export default {
</h2> </h2>
</header> </header>
<threat-monitoring-filters /> <gl-tabs>
<gl-tab :title="s__('ThreatMonitoring|Overview')">
<threat-monitoring-filters />
<threat-monitoring-section <threat-monitoring-section
ref="wafSection" ref="wafSection"
store-namespace="threatMonitoringWaf" store-namespace="threatMonitoringWaf"
:title="s__('ThreatMonitoring|Web Application Firewall')" :title="s__('ThreatMonitoring|Web Application Firewall')"
:subtitle="s__('ThreatMonitoring|Requests')" :subtitle="s__('ThreatMonitoring|Requests')"
:anomalous-title="s__('ThreatMonitoring|Anomalous Requests')" :anomalous-title="s__('ThreatMonitoring|Anomalous Requests')"
:nominal-title="s__('ThreatMonitoring|Total Requests')" :nominal-title="s__('ThreatMonitoring|Total Requests')"
:y-legend="s__('ThreatMonitoring|Requests')" :y-legend="s__('ThreatMonitoring|Requests')"
:chart-empty-state-title="s__('ThreatMonitoring|Application firewall not detected')" :chart-empty-state-title="s__('ThreatMonitoring|Application firewall not detected')"
:chart-empty-state-text="$options.wafChartEmptyStateDescription" :chart-empty-state-text="$options.wafChartEmptyStateDescription"
:chart-empty-state-svg-path="wafNoDataSvgPath" :chart-empty-state-svg-path="wafNoDataSvgPath"
:documentation-path="documentationPath" :documentation-path="documentationPath"
documentation-anchor="web-application-firewall" documentation-anchor="web-application-firewall"
/> />
<hr /> <hr />
<threat-monitoring-section <threat-monitoring-section
ref="networkPolicySection" ref="networkPolicySection"
store-namespace="threatMonitoringNetworkPolicy" store-namespace="threatMonitoringNetworkPolicy"
:title="s__('ThreatMonitoring|Container Network Policy')" :title="s__('ThreatMonitoring|Container Network Policy')"
:subtitle="s__('ThreatMonitoring|Packet Activity')" :subtitle="s__('ThreatMonitoring|Packet Activity')"
:anomalous-title="s__('ThreatMonitoring|Dropped Packets')" :anomalous-title="s__('ThreatMonitoring|Dropped Packets')"
:nominal-title="s__('ThreatMonitoring|Total Packets')" :nominal-title="s__('ThreatMonitoring|Total Packets')"
:y-legend="s__('ThreatMonitoring|Operations Per Second')" :y-legend="s__('ThreatMonitoring|Operations Per Second')"
:chart-empty-state-title="s__('ThreatMonitoring|Container NetworkPolicies not detected')" :chart-empty-state-title="s__('ThreatMonitoring|Container NetworkPolicies not detected')"
:chart-empty-state-text="$options.networkPolicyChartEmptyStateDescription" :chart-empty-state-text="$options.networkPolicyChartEmptyStateDescription"
:chart-empty-state-svg-path="networkPolicyNoDataSvgPath" :chart-empty-state-svg-path="networkPolicyNoDataSvgPath"
:documentation-path="documentationPath" :documentation-path="documentationPath"
documentation-anchor="container-network-policy" documentation-anchor="container-network-policy"
/> />
</gl-tab>
<gl-tab
v-if="glFeatures.networkPolicyManagement"
ref="networkPolicyTab"
:title="s__('ThreatMonitoring|Policies')"
>
<network-policy-list :documentation-path="documentationPath" />
</gl-tab>
</gl-tabs>
</section> </section>
</template> </template>
<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>
......
...@@ -9,6 +9,7 @@ export default () => { ...@@ -9,6 +9,7 @@ export default () => {
wafStatisticsEndpoint, wafStatisticsEndpoint,
networkPolicyStatisticsEndpoint, networkPolicyStatisticsEndpoint,
environmentsEndpoint, environmentsEndpoint,
networkPoliciesEndpoint,
chartEmptyStateSvgPath, chartEmptyStateSvgPath,
emptyStateSvgPath, emptyStateSvgPath,
wafNoDataSvgPath, wafNoDataSvgPath,
...@@ -26,6 +27,9 @@ export default () => { ...@@ -26,6 +27,9 @@ export default () => {
networkPolicyStatisticsEndpoint, networkPolicyStatisticsEndpoint,
environmentsEndpoint, environmentsEndpoint,
}); });
store.dispatch('networkPolicies/setEndpoints', {
networkPoliciesEndpoint,
});
return new Vue({ return new Vue({
el, el,
......
...@@ -58,6 +58,9 @@ export const setCurrentEnvironmentId = ({ commit, dispatch }, environmentId) => ...@@ -58,6 +58,9 @@ export const setCurrentEnvironmentId = ({ commit, dispatch }, environmentId) =>
commit(types.SET_CURRENT_ENVIRONMENT_ID, environmentId); commit(types.SET_CURRENT_ENVIRONMENT_ID, environmentId);
dispatch(`threatMonitoringWaf/fetchStatistics`, null, { root: true }); dispatch(`threatMonitoringWaf/fetchStatistics`, null, { root: true });
dispatch(`threatMonitoringNetworkPolicy/fetchStatistics`, null, { root: true }); dispatch(`threatMonitoringNetworkPolicy/fetchStatistics`, null, { root: true });
if (window.gon.features?.networkPolicyManagement) {
dispatch(`networkPolicies/fetchPolicies`, environmentId, { root: true });
}
}; };
export const setCurrentTimeWindow = ({ commit, dispatch }, timeWindow) => { export const setCurrentTimeWindow = ({ commit, dispatch }, timeWindow) => {
......
/* 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;
...@@ -3,5 +3,8 @@ ...@@ -3,5 +3,8 @@
module Projects module Projects
class ThreatMonitoringController < Projects::ApplicationController class ThreatMonitoringController < Projects::ApplicationController
before_action :authorize_read_threat_monitoring! before_action :authorize_read_threat_monitoring!
before_action only: [:show] do
push_frontend_feature_flag(:network_policy_management)
end
end end
end end
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
waf_statistics_endpoint: summary_project_security_waf_anomalies_path(@project, format: :json), waf_statistics_endpoint: summary_project_security_waf_anomalies_path(@project, format: :json),
network_policy_statistics_endpoint: summary_project_security_network_policies_path(@project, format: :json), network_policy_statistics_endpoint: summary_project_security_network_policies_path(@project, format: :json),
environments_endpoint: project_environments_path(@project), environments_endpoint: project_environments_path(@project),
network_policies_endpoint: project_security_network_policies_path(@project),
default_environment_id: default_environment_id, default_environment_id: default_environment_id,
user_callouts_path: user_callouts_path, user_callouts_path: user_callouts_path,
user_callout_id: UserCalloutsHelper::THREAT_MONITORING_INFO, user_callout_id: UserCalloutsHelper::THREAT_MONITORING_INFO,
......
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ThreatMonitoringApp component given there is a default environment with data given the networkPolicyManagement feature flag is enabled renders the network policy tab 1`] = `
<gl-tab-stub
title="Policies"
>
<network-policy-list-stub
documentationpath="/docs"
/>
</gl-tab-stub>
`;
exports[`ThreatMonitoringApp component given there is a default environment with data renders the network policy section 1`] = ` exports[`ThreatMonitoringApp component given there is a default environment with data renders the network policy section 1`] = `
<threat-monitoring-section-stub <threat-monitoring-section-stub
anomaloustitle="Dropped Packets" anomaloustitle="Dropped Packets"
......
// 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>
`;
...@@ -57,9 +57,11 @@ describe('ThreatMonitoringApp component', () => { ...@@ -57,9 +57,11 @@ describe('ThreatMonitoringApp component', () => {
const findWafSection = () => wrapper.find({ ref: 'wafSection' }); const findWafSection = () => wrapper.find({ ref: 'wafSection' });
const findNetworkPolicySection = () => wrapper.find({ ref: 'networkPolicySection' }); const findNetworkPolicySection = () => wrapper.find({ ref: 'networkPolicySection' });
const findEmptyState = () => wrapper.find({ ref: 'emptyState' }); const findEmptyState = () => wrapper.find({ ref: 'emptyState' });
const findNetworkPolicyTab = () => wrapper.find({ ref: 'networkPolicyTab' });
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
wrapper = null;
}); });
describe.each([-1, NaN, Math.PI])( describe.each([-1, NaN, Math.PI])(
...@@ -116,6 +118,28 @@ describe('ThreatMonitoringApp component', () => { ...@@ -116,6 +118,28 @@ describe('ThreatMonitoringApp component', () => {
expect(findNetworkPolicySection().element).toMatchSnapshot(); expect(findNetworkPolicySection().element).toMatchSnapshot();
}); });
it('does not render the network policy tab', () => {
expect(findNetworkPolicyTab().exists()).toBe(false);
});
describe('given the networkPolicyManagement feature flag is enabled', () => {
beforeEach(() => {
factory({
options: {
provide: {
glFeatures: {
networkPolicyManagement: true,
},
},
},
});
});
it('renders the network policy tab', () => {
expect(findNetworkPolicyTab().element).toMatchSnapshot();
});
});
describe('dismissing the alert', () => { describe('dismissing the alert', () => {
let mockAxios; let mockAxios;
......
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();
wrapper = null;
});
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();
wrapper = null;
});
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');
}); });
}); },
);
}); });
...@@ -16,6 +16,16 @@ const environmentsEndpoint = 'environmentsEndpoint'; ...@@ -16,6 +16,16 @@ const environmentsEndpoint = 'environmentsEndpoint';
const wafStatisticsEndpoint = 'wafStatisticsEndpoint'; const wafStatisticsEndpoint = 'wafStatisticsEndpoint';
const networkPolicyStatisticsEndpoint = 'networkPolicyStatisticsEndpoint'; const networkPolicyStatisticsEndpoint = 'networkPolicyStatisticsEndpoint';
const stubFeatureFlags = features => {
beforeEach(() => {
window.gon.features = features;
});
afterEach(() => {
delete window.gon.features;
});
};
describe('Threat Monitoring actions', () => { describe('Threat Monitoring actions', () => {
let state; let state;
...@@ -209,6 +219,23 @@ describe('Threat Monitoring actions', () => { ...@@ -209,6 +219,23 @@ describe('Threat Monitoring actions', () => {
{ type: 'threatMonitoringNetworkPolicy/fetchStatistics', payload: null }, { type: 'threatMonitoringNetworkPolicy/fetchStatistics', payload: null },
], ],
)); ));
describe('given the networkPolicyManagement feature flag is enabled', () => {
stubFeatureFlags({ networkPolicyManagement: true });
it('commits the SET_CURRENT_ENVIRONMENT_ID mutation and dispatches WAF, Network Policy statistics fetch actions and policy fetch action', () =>
testAction(
actions.setCurrentEnvironmentId,
environmentId,
state,
[{ type: types.SET_CURRENT_ENVIRONMENT_ID, payload: environmentId }],
[
{ type: 'threatMonitoringWaf/fetchStatistics', payload: null },
{ type: 'threatMonitoringNetworkPolicy/fetchStatistics', payload: null },
{ type: 'networkPolicies/fetchPolicies', payload: environmentId },
],
));
});
}); });
describe('setCurrentTimeWindow', () => { describe('setCurrentTimeWindow', () => {
......
...@@ -14080,6 +14080,9 @@ msgstr "" ...@@ -14080,6 +14080,9 @@ msgstr ""
msgid "Network" msgid "Network"
msgstr "" msgstr ""
msgid "NetworkPolicies|Enabled"
msgstr ""
msgid "NetworkPolicies|Environment does not have deployment platform" msgid "NetworkPolicies|Environment does not have deployment platform"
msgstr "" msgstr ""
...@@ -14089,6 +14092,18 @@ msgstr "" ...@@ -14089,6 +14092,18 @@ msgstr ""
msgid "NetworkPolicies|Kubernetes error: %{error}" msgid "NetworkPolicies|Kubernetes error: %{error}"
msgstr "" msgstr ""
msgid "NetworkPolicies|Last modified"
msgstr ""
msgid "NetworkPolicies|Name"
msgstr ""
msgid "NetworkPolicies|No policies detected"
msgstr ""
msgid "NetworkPolicies|Policies are a specification of how groups of pods are allowed to communicate with each other network endpoints."
msgstr ""
msgid "NetworkPolicies|Policy %{policyName} was successfully changed" msgid "NetworkPolicies|Policy %{policyName} was successfully changed"
msgstr "" msgstr ""
...@@ -14098,6 +14113,9 @@ msgstr "" ...@@ -14098,6 +14113,9 @@ msgstr ""
msgid "NetworkPolicies|Something went wrong, unable to fetch policies" msgid "NetworkPolicies|Something went wrong, unable to fetch policies"
msgstr "" msgstr ""
msgid "NetworkPolicies|Status"
msgstr ""
msgid "Never" msgid "Never"
msgstr "" msgstr ""
...@@ -22347,9 +22365,15 @@ msgstr "" ...@@ -22347,9 +22365,15 @@ msgstr ""
msgid "ThreatMonitoring|Operations Per Second" msgid "ThreatMonitoring|Operations Per Second"
msgstr "" msgstr ""
msgid "ThreatMonitoring|Overview"
msgstr ""
msgid "ThreatMonitoring|Packet Activity" msgid "ThreatMonitoring|Packet Activity"
msgstr "" msgstr ""
msgid "ThreatMonitoring|Policies"
msgstr ""
msgid "ThreatMonitoring|Requests" msgid "ThreatMonitoring|Requests"
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