Commit 5de26dfc authored by Kushal Pandya's avatar Kushal Pandya

Merge branch '340339-infinite-scroll-envs' into 'master'

Add "Load more" button to environment dropdown

See merge request gitlab-org/gitlab!72731
parents d88007b9 3a3c7df1
......@@ -17,18 +17,11 @@ import getAlertsQuery from '~/graphql_shared/queries/get_alerts.query.graphql';
import { convertToSnakeCase } from '~/lib/utils/text_utility';
import { joinPaths } from '~/lib/utils/url_utility';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import { PAGE_SIZE } from 'ee/threat_monitoring/constants';
import AlertDrawer from './alert_drawer.vue';
import AlertFilters from './alert_filters.vue';
import AlertStatus from './alert_status.vue';
import {
DEFAULT_FILTERS,
FIELDS,
MESSAGES,
PAGE_SIZE,
STATUSES,
DOMAIN,
CLOSED,
} from './constants';
import { DEFAULT_FILTERS, FIELDS, MESSAGES, STATUSES, DOMAIN, CLOSED } from './constants';
export default {
PAGE_SIZE,
......
......@@ -62,8 +62,6 @@ export const FIELDS = [
},
];
export const PAGE_SIZE = 20;
export const DEFAULT_FILTERS = { statuses: ['TRIGGERED', 'ACKNOWLEDGED'] };
export const DOMAIN = 'threat_monitoring';
......
<script>
import { GlIcon, GlLink, GlPopover, GlTabs, GlTab } from '@gitlab/ui';
import { mapActions } from 'vuex';
import { mapState } from 'vuex';
import { s__ } from '~/locale';
import Alerts from './alerts/alerts.vue';
import NoEnvironmentEmptyState from './no_environment_empty_state.vue';
......@@ -22,10 +22,6 @@ export default {
},
inject: ['documentationPath'],
props: {
defaultEnvironmentId: {
type: Number,
required: true,
},
networkPolicyNoDataSvgPath: {
type: String,
required: true,
......@@ -35,26 +31,8 @@ export default {
required: true,
},
},
data() {
return {
// We require the project to have at least one available environment.
// An invalid default environment id means there there are no available
// environments, therefore infrastructure cannot be set up. A valid default
// environment id only means that infrastructure *might* be set up.
isSetUpMaybe: this.isValidEnvironmentId(this.defaultEnvironmentId),
};
},
created() {
if (this.isSetUpMaybe) {
this.setCurrentEnvironmentId(this.defaultEnvironmentId);
this.fetchEnvironments();
}
},
methods: {
...mapActions('threatMonitoring', ['fetchEnvironments', 'setCurrentEnvironmentId']),
isValidEnvironmentId(id) {
return Number.isInteger(id) && id >= 0;
},
computed: {
...mapState('threatMonitoring', ['hasEnvironment']),
},
networkPolicyChartEmptyStateDescription: s__(
`ThreatMonitoring|Container Network Policies are not installed or have been disabled. To view
......@@ -96,7 +74,7 @@ export default {
:title="s__('ThreatMonitoring|Statistics')"
data-testid="threat-monitoring-statistics-tab"
>
<no-environment-empty-state v-if="!isSetUpMaybe" />
<no-environment-empty-state v-if="!hasEnvironment" />
<template v-else>
<threat-monitoring-filters />
......
<script>
import { GlFormGroup, GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { GlButton, GlFormGroup, GlDropdown, GlDropdownItem, GlDropdownDivider } from '@gitlab/ui';
import { mapActions, mapGetters, mapState } from 'vuex';
import { __ } from '~/locale';
import { ALL_ENVIRONMENT_NAME, LOADING_TEXT } from '../constants';
export default {
components: {
GlButton,
GlFormGroup,
GlDropdown,
GlDropdownDivider,
GlDropdownItem,
},
props: {
......@@ -16,25 +19,45 @@ export default {
default: false,
},
},
i18n: {
loadMore: __('Load more'),
},
computed: {
...mapState('threatMonitoring', [
'environments',
'currentEnvironmentId',
'allEnvironments',
'currentEnvironmentId',
'environments',
'isLoadingEnvironments',
'hasEnvironment',
'nextPage',
]),
...mapGetters('threatMonitoring', ['currentEnvironmentName', 'canChangeEnvironment']),
environmentName() {
if (this.isLoadingEnvironments) {
if (this.isDropdownInitiallyLoading) {
return LOADING_TEXT;
} else if (this.allEnvironments && this.includeAll) {
return ALL_ENVIRONMENT_NAME;
}
return this.currentEnvironmentName;
},
isDropdownInitiallyLoading() {
return this.isLoadingEnvironments && !this.environments.length;
},
},
created() {
if (this.hasEnvironment) {
this.fetchEnvironments();
}
},
methods: {
...mapActions('threatMonitoring', ['setCurrentEnvironmentId', 'setAllEnvironments']),
...mapActions('threatMonitoring', [
'fetchEnvironments',
'setCurrentEnvironmentId',
'setAllEnvironments',
]),
isEnvironmentChecked(currentEnvironmentName) {
return currentEnvironmentName === this.environmentName;
},
},
environmentFilterId: 'threat-monitoring-environment-filter',
ALL_ENVIRONMENT_NAME,
......@@ -44,7 +67,6 @@ export default {
<template>
<gl-form-group
:label="s__('ThreatMonitoring|Environment')"
label-size="sm"
:label-for="$options.environmentFilterId"
>
<gl-dropdown
......@@ -53,18 +75,35 @@ export default {
toggle-class="gl-truncate"
:text="environmentName"
:disabled="!canChangeEnvironment"
:loading="isLoadingEnvironments"
:loading="isDropdownInitiallyLoading"
>
<gl-dropdown-item
v-if="includeAll"
:is-check-item="true"
:is-checked="allEnvironments"
@click="setAllEnvironments"
>
{{ $options.ALL_ENVIRONMENT_NAME }}
</gl-dropdown-item>
<gl-dropdown-divider v-if="includeAll" />
<gl-dropdown-item
v-for="environment in environments"
:key="environment.id"
:is-check-item="true"
:is-checked="isEnvironmentChecked(environment.name)"
@click="setCurrentEnvironmentId(environment.id)"
>
{{ environment.name }}
</gl-dropdown-item>
<gl-dropdown-item v-if="includeAll" @click="setAllEnvironments">
{{ $options.ALL_ENVIRONMENT_NAME }}
</gl-dropdown-item>
<gl-button
v-if="Boolean(nextPage)"
variant="link"
class="gl-w-full"
:loading="isLoadingEnvironments"
@click="fetchEnvironments"
>
{{ this.$options.i18n.loadMore }}
</gl-button>
</gl-dropdown>
</gl-form-group>
</template>
......@@ -2,12 +2,12 @@
import produce from 'immer';
import getUsersProjects from '~/graphql_shared/queries/get_users_projects.query.graphql';
import ProjectSelector from '~/vue_shared/components/project_selector/project_selector.vue';
import { PAGE_SIZE } from 'ee/threat_monitoring/constants';
const defaultPageInfo = { endCursor: '', hasNextPage: false };
export default {
MINIMUM_QUERY_LENGTH: 3,
PROJECTS_PER_PAGE: 20,
SEARCH_ERROR: 'SEARCH_ERROR',
QUERY_TOO_SHORT_ERROR: 'QUERY_TOO_SHORT_ERROR',
NO_RESULTS_ERROR: 'NO_RESULTS_ERROR',
......@@ -17,7 +17,7 @@ export default {
variables() {
return {
search: this.searchQuery,
first: this.$options.PROJECTS_PER_PAGE,
first: PAGE_SIZE,
searchNamespaces: true,
sort: 'similarity',
};
......
<script>
import { mapActions } from 'vuex';
import { isValidEnvironmentId } from '../../utils';
import PoliciesHeader from './policies_header.vue';
import PoliciesList from './policies_list.vue';
......@@ -9,25 +7,12 @@ export default {
PoliciesHeader,
PoliciesList,
},
inject: ['defaultEnvironmentId'],
data() {
return {
// We require the project to have at least one available environment.
// An invalid default environment id means there there are no available
// environments, therefore infrastructure cannot be set up. A valid default
// environment id only means that infrastructure *might* be set up.
shouldFetchEnvironment: isValidEnvironmentId(this.defaultEnvironmentId),
shouldUpdatePolicyList: false,
};
},
created() {
if (this.shouldFetchEnvironment) {
this.setCurrentEnvironmentId(this.defaultEnvironmentId);
this.fetchEnvironments();
}
},
methods: {
...mapActions('threatMonitoring', ['fetchEnvironments', 'setCurrentEnvironmentId']),
handleUpdatePolicyList(val) {
this.shouldUpdatePolicyList = val;
},
......@@ -38,7 +23,6 @@ export default {
<div>
<policies-header @update-policy-list="handleUpdatePolicyList" />
<policies-list
:has-environment="shouldFetchEnvironment"
:should-update-policy-list="shouldUpdatePolicyList"
@update-policy-list="handleUpdatePolicyList"
/>
......
......@@ -59,11 +59,6 @@ export default {
},
inject: ['documentationPath', 'projectPath', 'newPolicyPath'],
props: {
hasEnvironment: {
type: Boolean,
required: false,
default: false,
},
shouldUpdatePolicyList: {
type: Boolean,
required: false,
......@@ -115,7 +110,7 @@ export default {
};
},
computed: {
...mapState('threatMonitoring', ['currentEnvironmentId', 'allEnvironments']),
...mapState('threatMonitoring', ['allEnvironments', 'currentEnvironmentId', 'hasEnvironment']),
...mapGetters('threatMonitoring', ['currentEnvironmentGid']),
allPolicyTypes() {
return {
......
......@@ -66,13 +66,7 @@ export default {
PolicyEditorLayout,
DimDisableContainer,
},
inject: [
'hasEnvironment',
'networkDocumentationPath',
'noEnvironmentSvgPath',
'projectId',
'policiesPath',
],
inject: ['networkDocumentationPath', 'noEnvironmentSvgPath', 'projectId', 'policiesPath'],
props: {
existingPolicy: {
type: Object,
......@@ -124,6 +118,7 @@ export default {
'currentEnvironmentId',
'environments',
'isLoadingEnvironments',
'hasEnvironment',
]),
...mapState('networkPolicies', [
'isUpdatingPolicy',
......
<script>
import { GlAlert, GlFormGroup, GlFormSelect } from '@gitlab/ui';
import { mapActions } from 'vuex';
import { POLICY_TYPE_COMPONENT_OPTIONS } from '../constants';
import EnvironmentPicker from '../environment_picker.vue';
import NetworkPolicyEditor from './network_policy/network_policy_editor.vue';
......@@ -59,11 +58,7 @@ export default {
return !this.existingPolicy;
},
},
created() {
this.fetchEnvironments();
},
methods: {
...mapActions('threatMonitoring', ['fetchEnvironments']),
setError(error) {
this.error = error;
},
......
......@@ -43,3 +43,5 @@ spec:
];
export const ALL_ENVIRONMENT_NAME = s__('ThreatMonitoring|All Environments');
export const PAGE_SIZE = 20;
......@@ -3,6 +3,7 @@ import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import ThreatMonitoringApp from './components/app.vue';
import { isValidEnvironmentId } from './utils';
import createStore from './store';
Vue.use(VueApollo);
......@@ -38,9 +39,20 @@ export default () => {
projectPath,
} = el.dataset;
const environmentId = parseInt(defaultEnvironmentId, 10);
// We require the project to have at least one available environment.
// An invalid default environment id means there there are no available
// environments, therefore infrastructure cannot be set up. A valid default
// environment id only means that infrastructure *might* be set up.
const hasEnvironment = isValidEnvironmentId(environmentId);
const store = createStore();
store.dispatch('threatMonitoring/setStatisticsEndpoint', networkPolicyStatisticsEndpoint);
store.dispatch('threatMonitoring/setEnvironmentEndpoint', environmentsEndpoint);
store.dispatch('threatMonitoring/setHasEnvironment', hasEnvironment);
if (hasEnvironment) {
store.dispatch('threatMonitoring/setCurrentEnvironmentId', environmentId);
}
return new Vue({
apolloProvider,
......@@ -55,7 +67,6 @@ export default () => {
return createElement(ThreatMonitoringApp, {
props: {
networkPolicyNoDataSvgPath,
defaultEnvironmentId: parseInt(defaultEnvironmentId, 10),
newPolicyPath,
},
});
......
......@@ -32,13 +32,19 @@ export default () => {
environmentId,
} = el.dataset;
// We require the project to have at least one available environment.
// An invalid default environment id means there there are no available
// environments, therefore infrastructure cannot be set up. A valid default
// environment id only means that infrastructure *might* be set up.
const hasEnvironment = isValidEnvironmentId(parseInt(defaultEnvironmentId, 10));
const store = createStore();
store.dispatch('threatMonitoring/setEnvironmentEndpoint', environmentsEndpoint);
store.dispatch('networkPolicies/setEndpoints', {
networkPoliciesEndpoint,
});
if (environmentId !== undefined) {
store.dispatch('threatMonitoring/setHasEnvironment', hasEnvironment);
if (hasEnvironment && environmentId !== undefined) {
store.dispatch('threatMonitoring/setCurrentEnvironmentId', parseInt(environmentId, 10));
}
......@@ -65,7 +71,6 @@ export default () => {
noEnvironmentSvgPath,
projectId,
projectPath,
hasEnvironment: isValidEnvironmentId(parseInt(defaultEnvironmentId, 10)),
policiesPath,
},
store,
......
......@@ -2,6 +2,7 @@ import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import { parseBoolean } from '~/lib/utils/common_utils';
import { isValidEnvironmentId } from './utils';
import SecurityPoliciesApp from './components/policies/policies_app.vue';
import createStore from './store';
......@@ -25,8 +26,19 @@ export default () => {
projectPath,
} = el.dataset;
const environmentId = parseInt(defaultEnvironmentId, 10);
// We require the project to have at least one available environment.
// An invalid default environment id means there there are no available
// environments, therefore infrastructure cannot be set up. A valid default
// environment id only means that infrastructure *might* be set up.
const hasEnvironment = isValidEnvironmentId(environmentId);
const store = createStore();
store.dispatch('threatMonitoring/setEnvironmentEndpoint', environmentsEndpoint);
store.dispatch('threatMonitoring/setHasEnvironment', hasEnvironment);
if (hasEnvironment) {
store.dispatch('threatMonitoring/setCurrentEnvironmentId', environmentId);
}
return new Vue({
apolloProvider,
......@@ -40,7 +52,6 @@ export default () => {
projectPath,
emptyFilterSvgPath,
emptyListSvgPath,
defaultEnvironmentId: parseInt(defaultEnvironmentId, 10),
},
render(createElement) {
return createElement(SecurityPoliciesApp);
......
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils';
import { s__ } from '~/locale';
import { PAGE_SIZE } from 'ee/threat_monitoring/constants';
import * as types from './mutation_types';
export const setEnvironmentEndpoint = ({ commit }, endpoint) => {
commit(types.SET_ENDPOINT, endpoint);
};
export const setHasEnvironment = ({ commit }, data) => {
commit(types.SET_HAS_ENVIRONMENT, data);
};
export const setStatisticsEndpoint = ({ commit }, endpoint) => {
commit(`threatMonitoringNetworkPolicy/${types.SET_ENDPOINT}`, endpoint, { root: true });
};
export const requestEnvironments = ({ commit }) => commit(types.REQUEST_ENVIRONMENTS);
export const receiveEnvironmentsSuccess = ({ commit }, environments) =>
commit(types.RECEIVE_ENVIRONMENTS_SUCCESS, environments);
export const receiveEnvironmentsSuccess = ({ commit }, data) =>
commit(types.RECEIVE_ENVIRONMENTS_SUCCESS, data);
export const receiveEnvironmentsError = ({ commit }) => {
commit(types.RECEIVE_ENVIRONMENTS_ERROR);
createFlash({
......@@ -21,35 +27,39 @@ export const receiveEnvironmentsError = ({ commit }) => {
});
};
const getAllEnvironments = (url, page = 1) =>
axios
.get(url, {
const getEnvironments = async (url, page = 1) => {
try {
const { data, headers } = await axios.get(url, {
params: {
per_page: 100,
per_page: PAGE_SIZE,
page,
},
})
.then(({ headers, data }) => {
const nextPage = headers && headers['x-next-page'];
return nextPage
? // eslint-disable-next-line promise/no-nesting
getAllEnvironments(url, nextPage).then((environments) => [
...data.environments,
...environments,
])
: data.environments;
});
export const fetchEnvironments = ({ state, dispatch }) => {
const { nextPage } = parseIntPagination(normalizeHeaders(headers));
return { environments: data.environments, nextPage };
} catch {
throw new Error();
}
};
export const fetchEnvironments = async ({ state, dispatch }) => {
if (!state.environmentsEndpoint) {
return dispatch('receiveEnvironmentsError');
}
dispatch('requestEnvironments');
return getAllEnvironments(state.environmentsEndpoint)
.then((environments) => dispatch('receiveEnvironmentsSuccess', environments))
.catch(() => dispatch('receiveEnvironmentsError'));
try {
const data = await getEnvironments(state.environmentsEndpoint, state.nextPage);
return dispatch('receiveEnvironmentsSuccess', {
environments: [...state.environments, ...data.environments],
nextPage: data.nextPage,
});
} catch {
return dispatch('receiveEnvironmentsError');
}
};
export const setCurrentEnvironmentId = ({ commit }, environmentId) => {
......
......@@ -8,8 +8,4 @@ export const currentEnvironmentName = (state, getters) =>
export const currentEnvironmentGid = (state, getters) => getters.currentEnvironment?.global_id;
export const canChangeEnvironment = ({
isLoadingEnvironments,
isLoadingNetworkPolicyStatistics,
environments,
}) => !isLoadingEnvironments && !isLoadingNetworkPolicyStatistics && environments.length > 0;
export const canChangeEnvironment = ({ environments }) => Boolean(environments.length);
export const SET_ENDPOINT = 'SET_ENDPOINT';
export const REQUEST_ENVIRONMENTS = 'REQUEST_ENVIRONMENTS';
export const RECEIVE_ENVIRONMENTS_SUCCESS = 'RECEIVE_ENVIRONMENTS_SUCCESS';
export const RECEIVE_ENVIRONMENTS_ERROR = 'RECEIVE_ENVIRONMENTS_ERROR';
export const SET_CURRENT_ENVIRONMENT_ID = 'SET_CURRENT_ENVIRONMENT_ID';
export const SET_ALL_ENVIRONMENTS = 'SET_ALL_ENVIRONMENTS';
export const SET_CURRENT_TIME_WINDOW = 'SET_CURRENT_TIME_WINDOW';
export const SET_HAS_ENVIRONMENT = 'SET_HAS_ENVIRONMENT';
......@@ -8,12 +8,13 @@ export default {
state.isLoadingEnvironments = true;
state.errorLoadingEnvironments = false;
},
[types.RECEIVE_ENVIRONMENTS_SUCCESS](state, payload) {
state.environments = payload;
[types.RECEIVE_ENVIRONMENTS_SUCCESS](state, { environments, nextPage }) {
state.environments = environments;
state.nextPage = nextPage;
state.isLoadingEnvironments = false;
state.errorLoadingEnvironments = false;
if (payload.length > 0 && state.currentEnvironmentId === -1)
state.currentEnvironmentId = payload[0].id;
if (environments.length > 0 && state.currentEnvironmentId === -1)
state.currentEnvironmentId = environments[0].id;
},
[types.RECEIVE_ENVIRONMENTS_ERROR](state) {
state.isLoadingEnvironments = false;
......@@ -23,10 +24,13 @@ export default {
state.currentEnvironmentId = payload;
state.allEnvironments = false;
},
[types.SET_ALL_ENVIRONMENTS](state) {
state.allEnvironments = true;
},
[types.SET_CURRENT_TIME_WINDOW](state, payload) {
state.currentTimeWindow = payload;
},
[types.SET_ALL_ENVIRONMENTS](state) {
state.allEnvironments = true;
[types.SET_HAS_ENVIRONMENT](state, payload) {
state.hasEnvironment = payload;
},
};
......@@ -4,8 +4,10 @@ export default () => ({
environmentsEndpoint: '',
environments: [],
isLoadingEnvironments: false,
hasEnvironment: false,
errorLoadingEnvironments: false,
currentEnvironmentId: -1,
currentTimeWindow: defaultTimeRange.name,
allEnvironments: false,
nextPage: false,
});
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ThreatMonitoringApp component given there is a default environment with data renders the statistics section 1`] = `
exports[`ThreatMonitoringApp component given there are environments present passes the statistics section the correct information 1`] = `
<threat-monitoring-section-stub
anomaloustitle="Dropped Packets"
chartemptystatesvgpath="/network-policy-no-data-svg"
......
......@@ -7,22 +7,22 @@ import createStore from 'ee/threat_monitoring/store';
import { TEST_HOST } from 'helpers/test_constants';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
const defaultEnvironmentId = 3;
const documentationPath = '/docs';
const newPolicyPath = '/policy/new';
const emptyStateSvgPath = '/svgs';
const networkPolicyNoDataSvgPath = '/network-policy-no-data-svg';
const environmentsEndpoint = `${TEST_HOST}/environments`;
const hasEnvironment = true;
const networkPolicyStatisticsEndpoint = `${TEST_HOST}/network_policy`;
describe('ThreatMonitoringApp component', () => {
let store;
let wrapper;
const factory = ({ propsData, provide = {}, state, stubs = {} } = {}) => {
const factory = ({ propsData, state, stubs = {} } = {}) => {
store = createStore();
Object.assign(store.state.threatMonitoring, {
environmentsEndpoint,
hasEnvironment,
networkPolicyStatisticsEndpoint,
...state,
});
......@@ -32,15 +32,12 @@ describe('ThreatMonitoringApp component', () => {
wrapper = extendedWrapper(
shallowMount(ThreatMonitoringApp, {
propsData: {
defaultEnvironmentId,
emptyStateSvgPath,
networkPolicyNoDataSvgPath,
newPolicyPath,
...propsData,
},
provide: {
documentationPath,
...provide,
},
store,
stubs,
......@@ -60,55 +57,39 @@ describe('ThreatMonitoringApp component', () => {
wrapper = null;
});
describe.each([-1, NaN, Math.PI])(
'given an invalid default environment id of %p',
(invalidEnvironmentId) => {
beforeEach(() => {
factory({
propsData: {
defaultEnvironmentId: invalidEnvironmentId,
},
stubs: { GlTabs: false },
});
});
it('dispatches no actions', () => {
expect(store.dispatch).not.toHaveBeenCalled();
});
it('shows the "no environment" empty state', () => {
expect(findNoEnvironmentEmptyState().exists()).toBe(true);
});
it('shows the tabs', () => {
expect(findAlertTab().exists()).toBe(true);
expect(findStatisticsTab().exists()).toBe(true);
});
it('does not show the threat monitoring section', () => {
expect(findStatisticsSection().exists()).toBe(false);
});
},
);
describe('given there is a default environment with data', () => {
describe('given there are environments present', () => {
beforeEach(() => {
factory();
});
it('dispatches the setCurrentEnvironmentId and fetchEnvironments actions', () => {
expect(store.dispatch.mock.calls).toEqual([
['threatMonitoring/setCurrentEnvironmentId', defaultEnvironmentId],
['threatMonitoring/fetchEnvironments', undefined],
]);
it.each`
component | status | findComponent | state
${'"no environment" empty state'} | ${'does not display'} | ${findNoEnvironmentEmptyState} | ${false}
${'alert tab'} | ${'does display'} | ${findAlertTab} | ${true}
${'statistics tab'} | ${'does display'} | ${findStatisticsTab} | ${true}
${'statistics filter section'} | ${'does display'} | ${findFilters} | ${true}
${'statistics section'} | ${'does display'} | ${findStatisticsSection} | ${true}
`('$status the $component', async ({ findComponent, state }) => {
expect(findComponent().exists()).toBe(state);
});
it('shows the filter bar', () => {
expect(findFilters().exists()).toBe(true);
it('passes the statistics section the correct information', () => {
expect(findStatisticsSection().element).toMatchSnapshot();
});
});
it('renders the statistics section', () => {
expect(findStatisticsSection().element).toMatchSnapshot();
describe('given there are no environments present', () => {
beforeEach(() => {
factory({ state: { hasEnvironment: false }, stubs: { GlTabs: false } });
});
it.each`
component | status | findComponent | state
${'"no environment" empty state'} | ${'does display'} | ${findNoEnvironmentEmptyState} | ${true}
${'statistics filter section'} | ${'does not display'} | ${findFilters} | ${false}
${'statistics section'} | ${'does not display'} | ${findStatisticsSection} | ${false}
`('$status the $component', async ({ findComponent, state }) => {
expect(findComponent().exists()).toBe(state);
});
});
......
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { GlButton, GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import EnvironmentPicker from 'ee/threat_monitoring/components/environment_picker.vue';
import {
......@@ -15,115 +15,129 @@ const currentEnvironment = mockEnvironments[1];
describe('EnvironmentPicker component', () => {
let store;
let wrapper;
let fetchEnvironmentsSpy;
const factory = (state) => {
const factory = (state = {}, propsData = {}) => {
store = createStore();
Object.assign(store.state.threatMonitoring, state);
store.replaceState({
...store.state,
threatMonitoring: {
...store.state.threatMonitoring,
currentEnvironmentId: currentEnvironment.id,
environments: mockEnvironments,
hasEnvironment: true,
nextPage: 'someHash',
...state,
},
});
fetchEnvironmentsSpy = jest
.spyOn(EnvironmentPicker.methods, 'fetchEnvironments')
.mockImplementation(() => {});
jest.spyOn(store, 'dispatch').mockImplementation();
wrapper = shallowMount(EnvironmentPicker, {
propsData,
store,
});
};
const findEnvironmentsDropdown = () => wrapper.findComponent(GlDropdown);
const findEnvironmentsDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
const findDropdown = () => wrapper.findComponent(GlDropdown);
const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
const findLoadMoreButton = () => wrapper.findComponent(GlButton);
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('the environments dropdown', () => {
describe('given there are no environments', () => {
beforeEach(() => {
factory();
});
describe('when there are environments', () => {
beforeEach(() => {
factory();
});
it('has no dropdown items', () => {
expect(findEnvironmentsDropdownItems()).toHaveLength(0);
});
it('fetches the environments when created', async () => {
expect(fetchEnvironmentsSpy).toHaveBeenCalled();
});
describe('given there are environments', () => {
beforeEach(() => {
factory({
environments: mockEnvironments,
currentEnvironmentId: currentEnvironment.id,
});
});
it('is not disabled', () => {
expect(findEnvironmentsDropdown().attributes().disabled).toBe(undefined);
});
it('is not disabled', () => {
expect(findDropdown().attributes().disabled).toBe(undefined);
});
it('has text set to the current environment', () => {
expect(findEnvironmentsDropdown().attributes().text).toBe(currentEnvironment.name);
});
it('has text set to the current environment', () => {
expect(findDropdown().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,
);
});
it('has dropdown items for each environment', () => {
const dropdownItems = findDropdownItems();
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('with includeAll enabled', () => {
beforeEach(() => {
factory({
environments: mockEnvironments,
currentEnvironmentId: currentEnvironment.id,
allEnvironments: true,
});
wrapper = shallowMount(EnvironmentPicker, {
propsData: {
includeAll: true,
},
store,
});
it('shows the "Load more" button when there are more environments to fetch', () => {
expect(findLoadMoreButton().exists()).toBe(true);
});
});
describe('when there are no environments', () => {
beforeEach(() => {
factory({
environments: [],
hasEnvironment: false,
isLoadingEnvironments: false,
nextPage: '',
});
});
it('has text set to the all environment option', () => {
expect(findEnvironmentsDropdown().attributes().text).toBe(ALL_ENVIRONMENT_NAME);
it('disables the environments dropdown', () => {
expect(findDropdown().attributes()).toMatchObject({
disabled: 'true',
text: INVALID_CURRENT_ENVIRONMENT_NAME,
});
});
it('has no dropdown items', () => {
expect(findDropdownItems()).toHaveLength(0);
});
it('does not fetch the environments when created', () => {
expect(fetchEnvironmentsSpy).not.toHaveBeenCalled();
});
it('does not show the "Load more" button', () => {
expect(findLoadMoreButton().exists()).toBe(false);
});
});
describe.each`
context | isLoadingEnvironments | isLoadingNetworkPolicyStatistics | environments | text | loadingState
${'environments are loading'} | ${true} | ${false} | ${mockEnvironments} | ${LOADING_TEXT} | ${'true'}
${'NetPol statistics are loading'} | ${false} | ${true} | ${mockEnvironments} | ${INVALID_CURRENT_ENVIRONMENT_NAME} | ${undefined}
${'there are no environments'} | ${false} | ${false} | ${[]} | ${INVALID_CURRENT_ENVIRONMENT_NAME} | ${undefined}
`(
'given $context',
({
isLoadingEnvironments,
isLoadingNetworkPolicyStatistics,
environments,
text,
loadingState,
}) => {
beforeEach(() => {
factory({
environments,
isLoadingEnvironments,
isLoadingNetworkPolicyStatistics,
});
return wrapper.vm.$nextTick();
});
describe('when includeAll is enabled', () => {
beforeEach(() => {
factory({ allEnvironments: true }, { includeAll: true });
});
it('disables the environments dropdown', () => {
expect(findEnvironmentsDropdown().attributes('disabled')).toBe('true');
expect(findEnvironmentsDropdown().attributes('text')).toBe(text);
expect(findEnvironmentsDropdown().attributes('loading')).toBe(loadingState);
it('has text set to the all environment option', () => {
expect(findDropdown().attributes().text).toBe(ALL_ENVIRONMENT_NAME);
});
});
describe('when environments are loading', () => {
beforeEach(() => {
factory({ environments: [], isLoadingEnvironments: true });
});
it('disables the environments dropdown', () => {
expect(findDropdown().attributes()).toMatchObject({
disabled: 'true',
text: LOADING_TEXT,
loading: 'true',
});
},
);
});
});
});
import PoliciesApp from 'ee/threat_monitoring/components/policies/policies_app.vue';
import PoliciesHeader from 'ee/threat_monitoring/components/policies/policies_header.vue';
import PoliciesList from 'ee/threat_monitoring/components/policies/policies_list.vue';
import createStore from 'ee/threat_monitoring/store';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
describe('Policies App', () => {
let wrapper;
let store;
let setCurrentEnvironmentIdSpy;
let fetchEnvironmentsSpy;
const findPoliciesHeader = () => wrapper.findComponent(PoliciesHeader);
const findPoliciesList = () => wrapper.findComponent(PoliciesList);
const createWrapper = ({ provide } = {}) => {
store = createStore();
setCurrentEnvironmentIdSpy = jest
.spyOn(PoliciesApp.methods, 'setCurrentEnvironmentId')
.mockImplementation(() => {});
fetchEnvironmentsSpy = jest
.spyOn(PoliciesApp.methods, 'fetchEnvironments')
.mockImplementation(() => {});
wrapper = shallowMountExtended(PoliciesApp, {
store,
provide: {
defaultEnvironmentId: -1,
...provide,
},
});
const createWrapper = () => {
wrapper = shallowMountExtended(PoliciesApp, {});
};
afterEach(() => {
......@@ -39,23 +19,13 @@ describe('Policies App', () => {
describe('when does have an environment enabled', () => {
beforeEach(() => {
createWrapper({ provide: { defaultEnvironmentId: 22 } });
createWrapper();
});
it('mounts the policies header component', () => {
expect(findPoliciesHeader().exists()).toBe(true);
});
it('mounts the policies list component', () => {
const policiesList = findPoliciesList();
expect(policiesList.props('hasEnvironment')).toBe(true);
});
it('fetches the environments when created', async () => {
expect(setCurrentEnvironmentIdSpy).toHaveBeenCalled();
expect(fetchEnvironmentsSpy).toHaveBeenCalled();
});
it.each`
component | findFn
${'PolicyHeader'} | ${findPoliciesHeader}
......@@ -70,20 +40,4 @@ describe('Policies App', () => {
},
);
});
describe('when does not have an environment enabled', () => {
beforeEach(() => {
createWrapper();
});
it('mounts the policies list component', () => {
const policiesList = findPoliciesList();
expect(policiesList.props('hasEnvironment')).toBe(false);
});
it('does not fetch the environments when created', () => {
expect(setCurrentEnvironmentIdSpy).not.toHaveBeenCalled();
expect(fetchEnvironmentsSpy).not.toHaveBeenCalled();
});
});
});
......@@ -55,6 +55,7 @@ describe('PoliciesList component', () => {
threatMonitoring: {
...store.state.threatMonitoring,
environments,
hasEnvironment: true,
currentEnvironmentId: environments[0].id,
...state.threatMonitoring,
},
......@@ -73,7 +74,6 @@ describe('PoliciesList component', () => {
{
propsData: {
documentationPath: 'documentation_path',
hasEnvironment: true,
newPolicyPath: '/policies/new',
},
store,
......@@ -323,11 +323,7 @@ describe('PoliciesList component', () => {
describe('given no environments', () => {
beforeEach(() => {
mountWrapper({
propsData: {
hasEnvironment: false,
},
});
mountWrapper({ state: { threatMonitoring: { hasEnvironment: false } } });
});
it('does not make a request for network policies', () => {
......
......@@ -25,7 +25,9 @@ describe('NetworkPolicyEditor component', () => {
let store;
let wrapper;
const defaultStore = { threatMonitoring: { environments: [{ id: 1 }], currentEnvironmentId: 1 } };
const defaultStore = {
threatMonitoring: { environments: [{ id: 1 }], currentEnvironmentId: 1, hasEnvironment: true },
};
const factory = ({ propsData, provide = {}, updatedStore = defaultStore } = {}) => {
store = createStore();
......@@ -49,7 +51,6 @@ describe('NetworkPolicyEditor component', () => {
...propsData,
},
provide: {
hasEnvironment: true,
networkDocumentationPath: 'path/to/docs',
noEnvironmentSvgPath: 'path/to/svg',
policiesPath: '/threat-monitoring',
......@@ -358,7 +359,9 @@ describe('NetworkPolicyEditor component', () => {
describe('when loading environments', () => {
beforeEach(() => {
factory({
updatedStore: { threatMonitoring: { environments: [], isLoadingEnvironments: true } },
updatedStore: {
threatMonitoring: { environments: [], hasEnvironment: true, isLoadingEnvironments: true },
},
});
});
......@@ -376,10 +379,7 @@ describe('NetworkPolicyEditor component', () => {
describe('when no environments are configured', () => {
beforeEach(() => {
factory({
provide: { hasEnvironment: false },
updatedStore: { threatMonitoring: { environments: [] } },
});
factory({ updatedStore: { threatMonitoring: { environments: [], hasEnvironment: false } } });
});
it.each`
......
......@@ -6,11 +6,9 @@ import NetworkPolicyEditor from 'ee/threat_monitoring/components/policy_editor/n
import PolicyEditor from 'ee/threat_monitoring/components/policy_editor/policy_editor.vue';
import ScanExecutionPolicyEditor from 'ee/threat_monitoring/components/policy_editor/scan_execution_policy/scan_execution_policy_editor.vue';
import { DEFAULT_ASSIGNED_POLICY_PROJECT } from 'ee/threat_monitoring/constants';
import createStore from 'ee/threat_monitoring/store';
import { mockDastScanExecutionObject, mockL3Manifest } from '../../mocks/mock_data';
describe('PolicyEditor component', () => {
let store;
let wrapper;
const findAlert = () => wrapper.findComponent(GlAlert);
......@@ -20,10 +18,6 @@ describe('PolicyEditor component', () => {
const findScanExecutionPolicyEditor = () => wrapper.findComponent(ScanExecutionPolicyEditor);
const factory = ({ propsData = {}, provide = {} } = {}) => {
store = createStore();
jest.spyOn(store, 'dispatch').mockImplementation(() => Promise.resolve());
wrapper = shallowMount(PolicyEditor, {
propsData: {
assignedPolicyProject: DEFAULT_ASSIGNED_POLICY_PROJECT,
......@@ -33,7 +27,6 @@ describe('PolicyEditor component', () => {
policyType: undefined,
...provide,
},
store,
stubs: { GlFormSelect },
});
};
......
......@@ -7,6 +7,7 @@ import { timeRanges, defaultTimeRange } from '~/vue_shared/constants';
import { mockEnvironmentsResponse } from '../mocks/mock_data';
const mockEnvironments = mockEnvironmentsResponse.environments;
const currentEnvironment = mockEnvironments[1];
describe('ThreatMonitoringFilters component', () => {
let store;
......@@ -14,7 +15,16 @@ describe('ThreatMonitoringFilters component', () => {
const factory = (state) => {
store = createStore();
Object.assign(store.state.threatMonitoring, state);
store.replaceState({
...store.state,
threatMonitoring: {
...store.state.threatMonitoring,
currentEnvironmentId: currentEnvironment.id,
environments: mockEnvironments,
hasEnvironment: true,
...state,
},
});
jest.spyOn(store, 'dispatch').mockImplementation();
......@@ -30,7 +40,7 @@ describe('ThreatMonitoringFilters component', () => {
wrapper.destroy();
});
describe('the environments picker', () => {
describe('has environments', () => {
beforeEach(() => {
factory();
});
......@@ -38,20 +48,9 @@ describe('ThreatMonitoringFilters component', () => {
it('renders EnvironmentPicker', () => {
expect(findEnvironmentsPicker().exists()).toBe(true);
});
});
describe('the "show last" dropdown', () => {
beforeEach(() => {
factory({
environments: mockEnvironments,
});
});
it('is not disabled', () => {
it('renders the "Show last" dropdown correctly', () => {
expect(findShowLastDropdown().attributes().disabled).toBe(undefined);
});
it('has text set to the current time window name', () => {
expect(findShowLastDropdown().vm.value.label).toBe(defaultTimeRange.label);
});
......@@ -66,26 +65,17 @@ describe('ThreatMonitoringFilters component', () => {
});
describe.each`
context | isLoadingEnvironments | isLoadingNetworkPolicyStatistics | environments
${'environments are loading'} | ${true} | ${false} | ${mockEnvironments}
${'NetPol statistics are loading'} | ${false} | ${true} | ${mockEnvironments}
${'there are no environments'} | ${false} | ${false} | ${[]}
`(
'given $context',
({ isLoadingEnvironments, isLoadingNetworkPolicyStatistics, environments }) => {
beforeEach(() => {
factory({
environments,
isLoadingEnvironments,
isLoadingNetworkPolicyStatistics,
});
return wrapper.vm.$nextTick();
});
context | status | isLoadingEnvironments | environments | disabled
${'environments are initially loading'} | ${'does'} | ${true} | ${[]} | ${'true'}
${'more environments are loading'} | ${'does not'} | ${true} | ${mockEnvironments} | ${undefined}
${'there are no environments'} | ${'does'} | ${false} | ${[]} | ${'true'}
`('when $context', ({ isLoadingEnvironments, environments, disabled, status }) => {
beforeEach(() => {
factory({ environments, isLoadingEnvironments });
});
it('disables the "show last" dropdown', () => {
expect(findShowLastDropdown().attributes('disabled')).toBe('true');
});
},
);
it(`${status} disable the "Show last" dropdown`, () => {
expect(findShowLastDropdown().attributes('disabled')).toBe(disabled);
});
});
});
......@@ -13,6 +13,7 @@ jest.mock('~/flash');
const environmentsEndpoint = 'environmentsEndpoint';
const networkPolicyStatisticsEndpoint = 'networkPolicyStatisticsEndpoint';
const nextPage = 2;
describe('Threat Monitoring actions', () => {
let state;
......@@ -121,7 +122,9 @@ describe('Threat Monitoring actions', () => {
describe('on success', () => {
beforeEach(() => {
mock.onGet(environmentsEndpoint).replyOnce(httpStatus.OK, mockEnvironmentsResponse);
mock
.onGet(environmentsEndpoint)
.replyOnce(httpStatus.OK, mockEnvironmentsResponse, { 'x-next-page': nextPage });
});
it('should dispatch the request and success actions', () =>
......@@ -134,37 +137,7 @@ describe('Threat Monitoring actions', () => {
{ type: 'requestEnvironments' },
{
type: 'receiveEnvironmentsSuccess',
payload: mockEnvironmentsResponse.environments,
},
],
));
});
describe('given more than one page of environments', () => {
beforeEach(() => {
const oneEnvironmentPerPage = ({ totalPages }) => (config) => {
const { page } = config.params;
const response = [httpStatus.OK, { environments: [{ id: page }] }];
if (page < totalPages) {
response.push({ 'x-next-page': page + 1 });
}
return response;
};
mock.onGet(environmentsEndpoint).reply(oneEnvironmentPerPage({ totalPages: 3 }));
});
it('should fetch all pages and dispatch the request and success actions', () =>
testAction(
actions.fetchEnvironments,
undefined,
state,
[],
[
{ type: 'requestEnvironments' },
{
type: 'receiveEnvironmentsSuccess',
payload: [{ id: 1 }, { id: 2 }, { id: 3 }],
payload: { environments: mockEnvironmentsResponse.environments, nextPage },
},
],
));
......
......@@ -36,10 +36,10 @@ describe('Threat Monitoring mutations', () => {
beforeEach(() => {
environments = [{ id: 1, name: 'production' }];
mutations[types.RECEIVE_ENVIRONMENTS_SUCCESS](state, environments);
mutations[types.RECEIVE_ENVIRONMENTS_SUCCESS](state, { environments, nextPage: '' });
});
it('sets environments to the payload', () => {
it('sets environments', () => {
expect(state.environments).toBe(environments);
});
......@@ -55,10 +55,10 @@ describe('Threat Monitoring mutations', () => {
expect(state.currentEnvironmentId).toEqual(1);
});
describe('without payload', () => {
describe('without environments', () => {
beforeEach(() => {
state.currentEnvironmentId = 1;
mutations[types.RECEIVE_ENVIRONMENTS_SUCCESS](state, []);
mutations[types.RECEIVE_ENVIRONMENTS_SUCCESS](state, { environments: [], nextPage: '' });
});
it('does not update currentEnvironmentId', () => {
......@@ -70,7 +70,7 @@ describe('Threat Monitoring mutations', () => {
beforeEach(() => {
state.currentEnvironmentId = 1;
environments = [{ id: 2, name: 'production' }];
mutations[types.RECEIVE_ENVIRONMENTS_SUCCESS](state, environments);
mutations[types.RECEIVE_ENVIRONMENTS_SUCCESS](state, { environments, nextPage: '' });
});
it('does not update currentEnvironmentId', () => {
......
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