Commit 3987e09a authored by Mark Florian's avatar Mark Florian

Merge branch '333727-fetch-scan-execution-policies' into 'master'

Add scan execution policies to the policy list

See merge request gitlab-org/gitlab!64783
parents 67139e53 5673ea8e
......@@ -3,8 +3,8 @@ import { GlIcon, GlLink, GlPopover, GlTabs, GlTab } from '@gitlab/ui';
import { mapActions } from 'vuex';
import { s__ } from '~/locale';
import Alerts from './alerts/alerts.vue';
import NetworkPolicyList from './network_policy_list.vue';
import NoEnvironmentEmptyState from './no_environment_empty_state.vue';
import PolicyList from './policy_list.vue';
import ThreatMonitoringFilters from './threat_monitoring_filters.vue';
import ThreatMonitoringSection from './threat_monitoring_section.vue';
......@@ -19,7 +19,7 @@ export default {
Alerts,
ThreatMonitoringFilters,
ThreatMonitoringSection,
NetworkPolicyList,
PolicyList,
NoEnvironmentEmptyState,
},
inject: ['documentationPath'],
......@@ -94,9 +94,9 @@ export default {
>
<alerts />
</gl-tab>
<gl-tab ref="networkPolicyTab" :title="s__('ThreatMonitoring|Policies')">
<gl-tab ref="policyTab" :title="s__('ThreatMonitoring|Policies')">
<no-environment-empty-state v-if="!isSetUpMaybe" />
<network-policy-list
<policy-list
v-else
:documentation-path="documentationPath"
:new-policy-path="newPolicyPath"
......@@ -111,7 +111,7 @@ export default {
<threat-monitoring-filters />
<threat-monitoring-section
ref="networkPolicySection"
ref="policySection"
store-namespace="threatMonitoringNetworkPolicy"
:title="s__('ThreatMonitoring|Container Network Policy')"
:subtitle="s__('ThreatMonitoring|Packet Activity')"
......
......@@ -7,9 +7,20 @@ import { getTimeago } from '~/lib/utils/datetime_utility';
import { setUrlFragment, mergeUrlParams } from '~/lib/utils/url_utility';
import { s__ } from '~/locale';
import networkPoliciesQuery from '../graphql/queries/network_policies.query.graphql';
import scanExecutionPoliciesQuery from '../graphql/queries/scan_execution_policies.query.graphql';
import EnvironmentPicker from './environment_picker.vue';
import PolicyDrawer from './policy_drawer/policy_drawer.vue';
const createPolicyFetchError = ({ gqlError, networkError }) => {
const error =
gqlError?.message ||
networkError?.message ||
s__('NetworkPolicies|Something went wrong, unable to fetch policies');
createFlash({
message: error,
});
};
export default {
components: {
GlTable,
......@@ -48,22 +59,32 @@ export default {
);
return [...policies, ...predefined];
},
error({ gqlError, networkError }) {
const error =
gqlError?.message ||
networkError?.message ||
s__('NetworkPolicies|Something went wrong, unable to fetch policies');
createFlash({
message: error,
});
},
error: createPolicyFetchError,
skip() {
return this.isLoadingEnvironments;
},
},
scanExecutionPolicies: {
query: scanExecutionPoliciesQuery,
variables() {
return {
fullPath: this.projectPath,
};
},
update(data) {
return data?.project?.scanExecutionPolicies?.nodes ?? [];
},
error: createPolicyFetchError,
},
},
data() {
return { selectedPolicyName: null, initialManifest: null, initialEnforcementStatus: null };
return {
selectedPolicyName: null,
initialManifest: null,
initialEnforcementStatus: null,
networkPolicies: [],
scanExecutionPolicies: [],
};
},
computed: {
...mapState('threatMonitoring', [
......@@ -75,8 +96,15 @@ export default {
documentationFullPath() {
return setUrlFragment(this.documentationPath, 'container-network-policy');
},
policies() {
return [...this.networkPolicies, ...this.scanExecutionPolicies];
},
isLoadingPolicies() {
return this.isLoadingEnvironments || this.$apollo.queries.networkPolicies.loading;
return (
this.isLoadingEnvironments ||
this.$apollo.queries.networkPolicies.loading ||
this.$apollo.queries.scanExecutionPolicies.loading
);
},
hasSelectedPolicy() {
return Boolean(this.selectedPolicyName);
......@@ -188,7 +216,7 @@ export default {
<gl-table
ref="policiesTable"
:busy="isLoadingPolicies"
:items="networkPolicies"
:items="policies"
:fields="fields"
head-variant="white"
stacked="md"
......
query scanExecutionPolicies($fullPath: ID!) {
project(fullPath: $fullPath) {
scanExecutionPolicies {
nodes {
name
yaml
enabled
updatedAt
}
}
}
}
......@@ -30,7 +30,6 @@ export default () => {
const {
networkPolicyStatisticsEndpoint,
environmentsEndpoint,
networkPoliciesEndpoint,
emptyStateSvgPath,
networkPolicyNoDataSvgPath,
newPolicyPath,
......@@ -44,9 +43,6 @@ export default () => {
networkPolicyStatisticsEndpoint,
environmentsEndpoint,
});
store.dispatch('networkPolicies/setEndpoints', {
networkPoliciesEndpoint,
});
return new Vue({
apolloProvider,
......
......@@ -11,7 +11,6 @@
network_policy_no_data_svg_path: image_path('illustrations/network-policies-not-detected-sm.svg'),
network_policy_statistics_endpoint: summary_project_security_network_policies_path(@project, format: :json),
environments_endpoint: project_environments_path(@project),
network_policies_endpoint: project_security_network_policies_path(@project),
new_policy_path: new_project_threat_monitoring_policy_path(@project),
default_environment_id: default_environment_id,
project_path: @project.full_path,
......
......@@ -21,7 +21,7 @@ exports[`ThreatMonitoringApp component given there is a default environment with
title="Policies"
titlelinkclass=""
>
<network-policy-list-stub
<policy-list-stub
documentationpath="/docs"
newpolicypath="/policy/new"
/>
......
// 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`] = `undefined`;
exports[`NetworkPolicyList component renders policies table 1`] = `
<table
aria-busy="false"
aria-colcount="3"
aria-describedby="__BVID__243__caption_"
aria-multiselectable="false"
class="table b-table gl-table table-hover b-table-stacked-md b-table-selectable b-table-select-single"
id="__BVID__243"
role="table"
>
<!---->
<!---->
<thead
class="thead-white gl-text-gray-900 border-bottom"
role="rowgroup"
>
<!---->
<tr
class=""
role="row"
>
<th
aria-colindex="1"
class="gl-w-half"
role="columnheader"
scope="col"
>
<div>
Name
</div>
</th>
<th
aria-colindex="2"
class=""
role="columnheader"
scope="col"
>
<div>
Status
</div>
</th>
<th
aria-colindex="3"
class=""
role="columnheader"
scope="col"
>
<div>
Last modified
</div>
</th>
</tr>
</thead>
<tbody
class="gl-text-gray-900"
role="rowgroup"
>
<!---->
<tr
aria-selected="false"
class=""
role="row"
tabindex="0"
>
<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>
2 months ago
</div>
</td>
</tr>
<tr
aria-selected="false"
class=""
role="row"
tabindex="0"
>
<td
aria-colindex="1"
class=""
data-label="Name"
role="cell"
>
<div>
drop-outbound
</div>
</td>
<td
aria-colindex="2"
class=""
data-label="Status"
role="cell"
>
<div>
Disabled
</div>
</td>
<td
aria-colindex="3"
class=""
data-label="Last modified"
role="cell"
>
<div>
</div>
</td>
</tr>
<tr
aria-selected="false"
class=""
role="row"
tabindex="0"
>
<td
aria-colindex="1"
class=""
data-label="Name"
role="cell"
>
<div>
allow-inbound-http
</div>
</td>
<td
aria-colindex="2"
class=""
data-label="Status"
role="cell"
>
<div>
Disabled
</div>
</td>
<td
aria-colindex="3"
class=""
data-label="Last modified"
role="cell"
>
<div>
</div>
</td>
</tr>
<!---->
<!---->
</tbody>
<!---->
</table>
`;
import { shallowMount } from '@vue/test-utils';
import ThreatMonitoringAlerts from 'ee/threat_monitoring/components/alerts/alerts.vue';
import ThreatMonitoringApp from 'ee/threat_monitoring/components/app.vue';
import NetworkPolicyList from 'ee/threat_monitoring/components/network_policy_list.vue';
import NoEnvironmentEmptyState from 'ee/threat_monitoring/components/no_environment_empty_state.vue';
import PolicyList from 'ee/threat_monitoring/components/policy_list.vue';
import ThreatMonitoringFilters from 'ee/threat_monitoring/components/threat_monitoring_filters.vue';
import createStore from 'ee/threat_monitoring/store';
import { TEST_HOST } from 'helpers/test_constants';
......@@ -50,11 +50,11 @@ describe('ThreatMonitoringApp component', () => {
};
const findAlertsView = () => wrapper.find(ThreatMonitoringAlerts);
const findNetworkPolicyList = () => wrapper.find(NetworkPolicyList);
const findPolicyList = () => wrapper.find(PolicyList);
const findFilters = () => wrapper.find(ThreatMonitoringFilters);
const findNetworkPolicySection = () => wrapper.find({ ref: 'networkPolicySection' });
const findPolicySection = () => wrapper.find({ ref: 'policySection' });
const findNoEnvironmentEmptyStates = () => wrapper.findAll(NoEnvironmentEmptyState);
const findNetworkPolicyTab = () => wrapper.find({ ref: 'networkPolicyTab' });
const findPolicyTab = () => wrapper.find({ ref: 'policyTab' });
const findAlertTab = () => wrapper.findByTestId('threat-monitoring-alerts-tab');
const findStatisticsTab = () => wrapper.findByTestId('threat-monitoring-statistics-tab');
......@@ -84,16 +84,16 @@ describe('ThreatMonitoringApp component', () => {
});
it('shows the tabs', () => {
expect(findNetworkPolicyTab().exists()).toBe(true);
expect(findPolicyTab().exists()).toBe(true);
expect(findStatisticsTab().exists()).toBe(true);
});
it('does not show the network policy list', () => {
expect(findNetworkPolicyList().exists()).toBe(false);
expect(findPolicyList().exists()).toBe(false);
});
it('does not show the threat monitoring section', () => {
expect(findNetworkPolicySection().exists()).toBe(false);
expect(findPolicySection().exists()).toBe(false);
});
},
);
......@@ -115,11 +115,11 @@ describe('ThreatMonitoringApp component', () => {
});
it('renders the network policy section', () => {
expect(findNetworkPolicySection().element).toMatchSnapshot();
expect(findPolicySection().element).toMatchSnapshot();
});
it('renders the network policy tab', () => {
expect(findNetworkPolicyTab().element).toMatchSnapshot();
expect(findPolicyTab().element).toMatchSnapshot();
});
});
......
......@@ -3,12 +3,12 @@ import PolicyDrawer from 'ee/threat_monitoring/components/policy_drawer/policy_d
import ScanExecutionPolicy from 'ee/threat_monitoring/components/policy_drawer/scan_execution_policy.vue';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import {
mockPoliciesResponse,
mockNetworkPoliciesResponse,
mockCiliumPolicy,
mockScanExecutionPolicy,
} from '../../mocks/mock_data';
const [mockGenericPolicy] = mockPoliciesResponse;
const [mockGenericPolicy] = mockNetworkPoliciesResponse;
describe('PolicyDrawer component', () => {
let wrapper;
......
import { GlTable, GlDrawer } from '@gitlab/ui';
import { createLocalVue } from '@vue/test-utils';
import { merge } from 'lodash';
import VueApollo from 'vue-apollo';
import NetworkPolicyList from 'ee/threat_monitoring/components/network_policy_list.vue';
import PolicyList from 'ee/threat_monitoring/components/policy_list.vue';
import networkPoliciesQuery from 'ee/threat_monitoring/graphql/queries/network_policies.query.graphql';
import scanExecutionPoliciesQuery from 'ee/threat_monitoring/graphql/queries/scan_execution_policies.query.graphql';
import createStore from 'ee/threat_monitoring/store';
import createMockApolloProvider from 'helpers/mock_apollo_helper';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { networkPolicies } from '../mocks/mock_apollo';
import { mockPoliciesResponse, mockCiliumPolicy } from '../mocks/mock_data';
import waitForPromises from 'helpers/wait_for_promises';
import { networkPolicies, scanExecutionPolicies } from '../mocks/mock_apollo';
import { mockNetworkPoliciesResponse, mockScanExecutionPoliciesResponse } from '../mocks/mock_data';
const localVue = createLocalVue();
localVue.use(VueApollo);
......@@ -20,17 +23,19 @@ const environments = [
},
];
const defaultRequestHandlers = {
networkPolicies: networkPolicies(mockPoliciesResponse),
networkPolicies: networkPolicies(mockNetworkPoliciesResponse),
scanExecutionPolicies: scanExecutionPolicies(mockScanExecutionPoliciesResponse),
};
const pendingHandler = jest.fn(() => new Promise(() => {}));
describe('NetworkPolicyList component', () => {
describe('PolicyList component', () => {
let store;
let wrapper;
let requestHandlers;
const factory = ({ mountFn = mountExtended, propsData, state, data, handlers } = {}) => {
const factory = (mountFn = mountExtended) => (options = {}) => {
store = createStore();
const { state, handlers, ...wrapperOptions } = options;
Object.assign(store.state.networkPolicies, {
...state,
});
......@@ -42,96 +47,121 @@ describe('NetworkPolicyList component', () => {
jest.spyOn(store, 'dispatch').mockImplementation(() => Promise.resolve());
wrapper = mountFn(NetworkPolicyList, {
propsData: {
documentationPath: 'documentation_path',
newPolicyPath: '/policies/new',
...propsData,
},
data,
store,
provide: {
projectPath: fullPath,
},
apolloProvider: createMockApolloProvider([
[networkPoliciesQuery, requestHandlers.networkPolicies],
]),
stubs: { PolicyDrawer: GlDrawer },
localVue,
});
wrapper = mountFn(
PolicyList,
merge(
{
propsData: {
documentationPath: 'documentation_path',
newPolicyPath: '/policies/new',
},
store,
provide: {
projectPath: fullPath,
},
apolloProvider: createMockApolloProvider([
[networkPoliciesQuery, requestHandlers.networkPolicies],
[scanExecutionPoliciesQuery, requestHandlers.scanExecutionPolicies],
]),
stubs: {
PolicyDrawer: GlDrawer,
},
localVue,
},
wrapperOptions,
),
);
};
const mountShallowWrapper = factory(shallowMountExtended);
const mountWrapper = factory();
const findEnvironmentsPicker = () => wrapper.find({ ref: 'environmentsPicker' });
const findPoliciesTable = () => wrapper.find(GlTable);
const findTableEmptyState = () => wrapper.find({ ref: 'tableEmptyState' });
const findPoliciesTable = () => wrapper.findComponent(GlTable);
const findPolicyDrawer = () => wrapper.findByTestId('policyDrawer');
const findAutodevopsAlert = () => wrapper.findByTestId('autodevopsAlert');
beforeEach(() => {
factory({});
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
it('renders EnvironmentPicker', () => {
expect(findEnvironmentsPicker().exists()).toBe(true);
});
it('renders the new policy button', () => {
const button = wrapper.find('[data-testid="new-policy"]');
expect(button.exists()).toBe(true);
});
describe('initial state', () => {
beforeEach(() => {
factory({
mountFn: shallowMountExtended,
mountShallowWrapper({
handlers: {
networkPolicies: pendingHandler,
},
});
});
it('renders EnvironmentPicker', () => {
expect(findEnvironmentsPicker().exists()).toBe(true);
});
it('renders the new policy button', () => {
const button = wrapper.findByTestId('new-policy');
expect(button.exists()).toBe(true);
});
it('renders closed editor drawer', () => {
const editorDrawer = findPolicyDrawer();
expect(editorDrawer.exists()).toBe(true);
expect(editorDrawer.props('open')).toBe(false);
});
it('does not render autodevops alert', () => {
expect(findAutodevopsAlert().exists()).toBe(false);
});
it('fetches policies', () => {
expect(requestHandlers.networkPolicies).toHaveBeenCalledWith({
fullPath,
});
expect(requestHandlers.scanExecutionPolicies).toHaveBeenCalledWith({
fullPath,
});
});
it("sets table's loading state", () => {
expect(findPoliciesTable().attributes('busy')).toBe('true');
});
});
it('fetches policies on environment change', async () => {
store.dispatch.mockReset();
await store.commit('threatMonitoring/SET_CURRENT_ENVIRONMENT_ID', 2);
expect(requestHandlers.networkPolicies).toHaveBeenCalledTimes(2);
expect(requestHandlers.networkPolicies.mock.calls[1][0]).toEqual({
fullPath: 'project/path',
environmentId: environments[0].global_id,
});
});
describe('given selected policy is a cilium policy', () => {
beforeEach(() => {
findPoliciesTable().vm.$emit('row-selected', [mockCiliumPolicy]);
});
it('renders the new policy drawer', () => {
expect(findPolicyDrawer().exists()).toBe(true);
it('fetches network policies on environment change', async () => {
store.dispatch.mockReset();
await store.commit('threatMonitoring/SET_CURRENT_ENVIRONMENT_ID', 2);
expect(requestHandlers.networkPolicies).toHaveBeenCalledTimes(2);
expect(requestHandlers.networkPolicies.mock.calls[1][0]).toEqual({
fullPath: 'project/path',
environmentId: environments[0].global_id,
});
});
});
it('renders policies table', () => {
expect(findPoliciesTable().element).toMatchSnapshot();
describe('given policies have been fetched', () => {
let rows;
beforeEach(async () => {
mountWrapper();
await waitForPromises();
rows = wrapper.findAll('tr');
});
it.each`
rowIndex | expectedPolicyName
${1} | ${mockNetworkPoliciesResponse[0].name}
${2} | ${'drop-outbound'}
${3} | ${'allow-inbound-http'}
${4} | ${mockScanExecutionPoliciesResponse[0].name}
`(
'renders "$expectedPolicyName" policy in row #$rowIndex',
({ expectedPolicyName, rowIndex }) => {
expect(rows.at(rowIndex).text()).toContain(expectedPolicyName);
},
);
});
describe('with allEnvironments enabled', () => {
beforeEach(() => {
mountWrapper();
wrapper.vm.$store.state.threatMonitoring.allEnvironments = true;
});
......@@ -141,29 +171,10 @@ describe('NetworkPolicyList component', () => {
});
});
it('renders closed editor drawer', () => {
const editorDrawer = findPolicyDrawer();
expect(editorDrawer.exists()).toBe(true);
expect(editorDrawer.props('open')).toBe(false);
});
it('renders opened editor drawer on row selection', () => {
findPoliciesTable().find('td').trigger('click');
return wrapper.vm.$nextTick().then(() => {
const editorDrawer = findPolicyDrawer();
expect(editorDrawer.exists()).toBe(true);
expect(editorDrawer.props('open')).toBe(true);
});
});
it('does not render autodevops alert', () => {
expect(findAutodevopsAlert().exists()).toBe(false);
});
describe('given there is a selected policy', () => {
beforeEach(() => {
findPoliciesTable().vm.$emit('row-selected', [mockPoliciesResponse[0]]);
mountShallowWrapper();
findPoliciesTable().vm.$emit('row-selected', [mockNetworkPoliciesResponse[0]]);
});
it('renders opened editor drawer', () => {
......@@ -173,28 +184,14 @@ describe('NetworkPolicyList component', () => {
});
});
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();
});
});
describe('given autodevops selected policy', () => {
describe('given an autodevops policy', () => {
beforeEach(() => {
const autoDevOpsPolicy = {
...mockPoliciesResponse[0],
...mockNetworkPoliciesResponse[0],
name: 'auto-devops',
fromAutoDevops: true,
};
factory({
mountShallowWrapper({
handlers: {
networkPolicies: networkPolicies([autoDevOpsPolicy]),
},
......
......@@ -38,3 +38,14 @@ export const networkPolicies = (nodes) =>
},
},
});
export const scanExecutionPolicies = (nodes) =>
jest.fn().mockResolvedValue({
data: {
project: {
scanExecutionPolicies: {
nodes,
},
},
},
});
......@@ -15,7 +15,7 @@ export const mockEnvironmentsResponse = {
stopped_count: 5,
};
export const mockPoliciesResponse = [
export const mockNetworkPoliciesResponse = [
{
name: 'policy',
yaml: `---
......@@ -68,8 +68,11 @@ actions:
scanner_profile: Scanner Profile
site_profile: Site Profile
`,
enabled: true,
};
export const mockScanExecutionPoliciesResponse = [mockScanExecutionPolicy];
export const mockNominalHistory = [
['2019-12-04T00:00:00.000Z', 56],
['2019-12-05T00:00:00.000Z', 2647],
......
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