diff --git a/ee/app/assets/javascripts/threat_monitoring/components/app.vue b/ee/app/assets/javascripts/threat_monitoring/components/app.vue index 521f5057fd2a34ca3e2df49baee24c2dd29a9c43..3d1fb5802e31064944780baf216941963b2c55af 100644 --- a/ee/app/assets/javascripts/threat_monitoring/components/app.vue +++ b/ee/app/assets/javascripts/threat_monitoring/components/app.vue @@ -1,15 +1,6 @@ <script> import { mapActions } from 'vuex'; -import { - GlAlert, - GlEmptyState, - GlIcon, - GlLink, - GlPopover, - GlSprintf, - GlTabs, - GlTab, -} from '@gitlab/ui'; +import { GlAlert, GlIcon, GlLink, GlPopover, GlTabs, GlTab } from '@gitlab/ui'; import { s__ } from '~/locale'; import axios from '~/lib/utils/axios_utils'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; @@ -17,22 +8,22 @@ import Alerts from './alerts/alerts.vue'; import ThreatMonitoringFilters from './threat_monitoring_filters.vue'; import ThreatMonitoringSection from './threat_monitoring_section.vue'; import NetworkPolicyList from './network_policy_list.vue'; +import NoEnvironmentEmptyState from './no_environment_empty_state.vue'; export default { name: 'ThreatMonitoring', components: { GlAlert, - GlEmptyState, GlIcon, GlLink, GlPopover, - GlSprintf, GlTabs, GlTab, Alerts, ThreatMonitoringFilters, ThreatMonitoringSection, NetworkPolicyList, + NoEnvironmentEmptyState, }, mixins: [glFeatureFlagsMixin()], inject: ['documentationPath'], @@ -45,10 +36,6 @@ export default { type: String, required: true, }, - emptyStateSvgPath: { - type: String, - required: true, - }, wafNoDataSvgPath: { type: String, required: true, @@ -122,10 +109,6 @@ export default { `ThreatMonitoring|Container Network Policies are not installed or have been disabled. To view this data, ensure your Network Policies are installed and enabled for your cluster.`, ), - emptyStateDescription: s__( - `ThreatMonitoring|To view this data, ensure you have configured an environment - for this project and that at least one threat monitoring feature is enabled. %{linkStart}More information%{linkEnd}`, - ), alertText: s__( `ThreatMonitoring|The graph below is an overview of traffic coming to your application as tracked by the Web Application Firewall (WAF). View the docs @@ -138,22 +121,7 @@ export default { </script> <template> - <gl-empty-state - v-if="!isSetUpMaybe" - ref="emptyState" - :title="s__('ThreatMonitoring|No environments detected')" - :svg-path="emptyStateSvgPath" - > - <template #description> - <gl-sprintf :message="$options.emptyStateDescription"> - <template #link="{ content }"> - <gl-link :href="documentationPath" target="_blank">{{ content }}</gl-link> - </template> - </gl-sprintf> - </template> - </gl-empty-state> - - <section v-else> + <section> <gl-alert v-if="showAlert" class="my-3" @@ -190,45 +158,55 @@ export default { <alerts /> </gl-tab> <gl-tab ref="networkPolicyTab" :title="s__('ThreatMonitoring|Policies')"> + <no-environment-empty-state v-if="!isSetUpMaybe" /> <network-policy-list + v-else :documentation-path="documentationPath" :new-policy-path="newPolicyPath" /> </gl-tab> - <gl-tab :title="s__('ThreatMonitoring|Statistics')"> - <threat-monitoring-filters /> + <gl-tab + :title="s__('ThreatMonitoring|Statistics')" + data-testid="threat-monitoring-statistics-tab" + > + <no-environment-empty-state v-if="!isSetUpMaybe" /> + <template v-else> + <threat-monitoring-filters /> - <threat-monitoring-section - ref="wafSection" - store-namespace="threatMonitoringWaf" - :title="s__('ThreatMonitoring|Web Application Firewall')" - :subtitle="s__('ThreatMonitoring|Requests')" - :anomalous-title="s__('ThreatMonitoring|Anomalous Requests')" - :nominal-title="s__('ThreatMonitoring|Total Requests')" - :y-legend="s__('ThreatMonitoring|Requests')" - :chart-empty-state-title="s__('ThreatMonitoring|Application firewall not detected')" - :chart-empty-state-text="$options.wafChartEmptyStateDescription" - :chart-empty-state-svg-path="wafNoDataSvgPath" - :documentation-path="documentationPath" - documentation-anchor="web-application-firewall" - /> + <threat-monitoring-section + ref="wafSection" + store-namespace="threatMonitoringWaf" + :title="s__('ThreatMonitoring|Web Application Firewall')" + :subtitle="s__('ThreatMonitoring|Requests')" + :anomalous-title="s__('ThreatMonitoring|Anomalous Requests')" + :nominal-title="s__('ThreatMonitoring|Total Requests')" + :y-legend="s__('ThreatMonitoring|Requests')" + :chart-empty-state-title="s__('ThreatMonitoring|Application firewall not detected')" + :chart-empty-state-text="$options.wafChartEmptyStateDescription" + :chart-empty-state-svg-path="wafNoDataSvgPath" + :documentation-path="documentationPath" + documentation-anchor="web-application-firewall" + /> - <hr /> + <hr /> - <threat-monitoring-section - ref="networkPolicySection" - store-namespace="threatMonitoringNetworkPolicy" - :title="s__('ThreatMonitoring|Container Network Policy')" - :subtitle="s__('ThreatMonitoring|Packet Activity')" - :anomalous-title="s__('ThreatMonitoring|Dropped Packets')" - :nominal-title="s__('ThreatMonitoring|Total Packets')" - :y-legend="s__('ThreatMonitoring|Operations Per Second')" - :chart-empty-state-title="s__('ThreatMonitoring|Container NetworkPolicies not detected')" - :chart-empty-state-text="$options.networkPolicyChartEmptyStateDescription" - :chart-empty-state-svg-path="networkPolicyNoDataSvgPath" - :documentation-path="documentationPath" - documentation-anchor="container-network-policy" - /> + <threat-monitoring-section + ref="networkPolicySection" + store-namespace="threatMonitoringNetworkPolicy" + :title="s__('ThreatMonitoring|Container Network Policy')" + :subtitle="s__('ThreatMonitoring|Packet Activity')" + :anomalous-title="s__('ThreatMonitoring|Dropped Packets')" + :nominal-title="s__('ThreatMonitoring|Total Packets')" + :y-legend="s__('ThreatMonitoring|Operations Per Second')" + :chart-empty-state-title=" + s__('ThreatMonitoring|Container NetworkPolicies not detected') + " + :chart-empty-state-text="$options.networkPolicyChartEmptyStateDescription" + :chart-empty-state-svg-path="networkPolicyNoDataSvgPath" + :documentation-path="documentationPath" + documentation-anchor="container-network-policy" + /> + </template> </gl-tab> </gl-tabs> </section> diff --git a/ee/app/assets/javascripts/threat_monitoring/components/constants.js b/ee/app/assets/javascripts/threat_monitoring/components/constants.js index a5175234fc090e40d46ed7f17119ef99f91d9b8a..1a0ddcf1547458321afff57985d7b52a334c86a2 100644 --- a/ee/app/assets/javascripts/threat_monitoring/components/constants.js +++ b/ee/app/assets/javascripts/threat_monitoring/components/constants.js @@ -5,6 +5,11 @@ export const TOTAL_REQUESTS = s__('ThreatMonitoring|Total Requests'); export const ANOMALOUS_REQUESTS = s__('ThreatMonitoring|Anomalous Requests'); export const TIME = s__('ThreatMonitoring|Time'); export const REQUESTS = s__('ThreatMonitoring|Requests'); +export const NO_ENVIRONMENT_TITLE = s__('ThreatMonitoring|No environments detected'); +export const EMPTY_STATE_DESCRIPTION = s__( + `ThreatMonitoring|To view this data, ensure you have configured an environment + for this project and that at least one threat monitoring feature is enabled. %{linkStart}More information%{linkEnd}`, +); export const COLORS = { nominal: gray700, diff --git a/ee/app/assets/javascripts/threat_monitoring/components/no_environment_empty_state.vue b/ee/app/assets/javascripts/threat_monitoring/components/no_environment_empty_state.vue new file mode 100644 index 0000000000000000000000000000000000000000..485cec83f3dde5dc0bf9efc5ea0fd9e7d78d9f9f --- /dev/null +++ b/ee/app/assets/javascripts/threat_monitoring/components/no_environment_empty_state.vue @@ -0,0 +1,30 @@ +<script> +import { GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui'; +import { EMPTY_STATE_DESCRIPTION, NO_ENVIRONMENT_TITLE } from './constants'; + +export default { + i18n: { EMPTY_STATE_DESCRIPTION, NO_ENVIRONMENT_TITLE }, + name: 'NoEnvironmentEmptyState', + components: { + GlEmptyState, + GlLink, + GlSprintf, + }, + inject: { + documentationPath: { type: String, default: '' }, + emptyStateSvgPath: { type: String, default: '' }, + }, +}; +</script> + +<template> + <gl-empty-state :title="$options.i18n.NO_ENVIRONMENT_TITLE" :svg-path="emptyStateSvgPath"> + <template #description> + <gl-sprintf :message="$options.i18n.EMPTY_STATE_DESCRIPTION"> + <template #link="{ content }"> + <gl-link :href="documentationPath" target="_blank">{{ content }}</gl-link> + </template> + </gl-sprintf> + </template> + </gl-empty-state> +</template> diff --git a/ee/app/assets/javascripts/threat_monitoring/index.js b/ee/app/assets/javascripts/threat_monitoring/index.js index e9279f317b8c4d51e1dcddc3c25daf4cc82abb15..e3ec94c8fa73014bd9496faa7904d93bbfd014de 100644 --- a/ee/app/assets/javascripts/threat_monitoring/index.js +++ b/ee/app/assets/javascripts/threat_monitoring/index.js @@ -46,6 +46,7 @@ export default () => { el, provide: { documentationPath, + emptyStateSvgPath, projectPath, }, store, @@ -53,7 +54,6 @@ export default () => { return createElement(ThreatMonitoringApp, { props: { chartEmptyStateSvgPath, - emptyStateSvgPath, wafNoDataSvgPath, networkPolicyNoDataSvgPath, defaultEnvironmentId: parseInt(defaultEnvironmentId, 10), diff --git a/ee/changelogs/unreleased/296179-change-empty-state-location.yml b/ee/changelogs/unreleased/296179-change-empty-state-location.yml new file mode 100644 index 0000000000000000000000000000000000000000..70325397e5be93ae8de21cd2064dd2e011f929b5 --- /dev/null +++ b/ee/changelogs/unreleased/296179-change-empty-state-location.yml @@ -0,0 +1,5 @@ +--- +title: Move empty state into the threat monitoring tabs +merge_request: 51748 +author: +type: changed diff --git a/ee/spec/frontend/threat_monitoring/components/__snapshots__/no_environment_empty_state_spec.js.snap b/ee/spec/frontend/threat_monitoring/components/__snapshots__/no_environment_empty_state_spec.js.snap new file mode 100644 index 0000000000000000000000000000000000000000..76f6792b632d94ea1228064e1173b8ae00368b6e --- /dev/null +++ b/ee/spec/frontend/threat_monitoring/components/__snapshots__/no_environment_empty_state_spec.js.snap @@ -0,0 +1,12 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`NoEnvironmentEmptyState component default state matches the snapshot 1`] = ` +<gl-empty-state-stub + svgpath="/svgs" + title="No environments detected" +> + <gl-sprintf-stub + message="To view this data, ensure you have configured an environment for this project and that at least one threat monitoring feature is enabled. %{linkStart}More information%{linkEnd}" + /> +</gl-empty-state-stub> +`; diff --git a/ee/spec/frontend/threat_monitoring/components/app_spec.js b/ee/spec/frontend/threat_monitoring/components/app_spec.js index f7b59b2e77988b5dd74e893a6438a0f94c1bdd61..c5445236cb34314dae706bb395b227b5d8f1e2e7 100644 --- a/ee/spec/frontend/threat_monitoring/components/app_spec.js +++ b/ee/spec/frontend/threat_monitoring/components/app_spec.js @@ -1,9 +1,12 @@ -import { GlAlert, GlSprintf } from '@gitlab/ui'; +import { GlAlert } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import ThreatMonitoringAlerts from 'ee/threat_monitoring/components/alerts/alerts.vue'; import ThreatMonitoringApp from 'ee/threat_monitoring/components/app.vue'; import ThreatMonitoringFilters from 'ee/threat_monitoring/components/threat_monitoring_filters.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 createStore from 'ee/threat_monitoring/store'; import { TEST_HOST } from 'helpers/test_constants'; import axios from '~/lib/utils/axios_utils'; @@ -25,7 +28,7 @@ describe('ThreatMonitoringApp component', () => { let store; let wrapper; - const factory = ({ propsData, provide = {}, state, options } = {}) => { + const factory = ({ propsData, provide = {}, state, stubs = {} } = {}) => { store = createStore(); Object.assign(store.state.threatMonitoring, { environmentsEndpoint, @@ -36,38 +39,41 @@ describe('ThreatMonitoringApp component', () => { jest.spyOn(store, 'dispatch').mockImplementation(); - wrapper = shallowMount(ThreatMonitoringApp, { - propsData: { - defaultEnvironmentId, - chartEmptyStateSvgPath, - emptyStateSvgPath, - wafNoDataSvgPath, - networkPolicyNoDataSvgPath, - newPolicyPath, - showUserCallout: true, - userCalloutId, - userCalloutsPath, - ...propsData, - }, - provide: { - documentationPath, - glFeatures: { threatMonitoringAlerts: false }, - ...provide, - }, - store, - ...options, - }); + wrapper = extendedWrapper( + shallowMount(ThreatMonitoringApp, { + propsData: { + defaultEnvironmentId, + chartEmptyStateSvgPath, + emptyStateSvgPath, + wafNoDataSvgPath, + networkPolicyNoDataSvgPath, + newPolicyPath, + showUserCallout: true, + userCalloutId, + userCalloutsPath, + ...propsData, + }, + provide: { + documentationPath, + glFeatures: { threatMonitoringAlerts: false }, + ...provide, + }, + store, + stubs, + }), + ); }; const findAlert = () => wrapper.find(GlAlert); const findAlertsView = () => wrapper.find(ThreatMonitoringAlerts); + const findNetworkPolicyList = () => wrapper.find(NetworkPolicyList); const findFilters = () => wrapper.find(ThreatMonitoringFilters); const findWafSection = () => wrapper.find({ ref: 'wafSection' }); const findNetworkPolicySection = () => wrapper.find({ ref: 'networkPolicySection' }); - const findEmptyState = () => wrapper.find({ ref: 'emptyState' }); - const findEmptyStateMessage = () => wrapper.find(GlSprintf); + const findNoEnvironmentEmptyStates = () => wrapper.findAll(NoEnvironmentEmptyState); const findNetworkPolicyTab = () => wrapper.find({ ref: 'networkPolicyTab' }); - const findAlertTab = () => wrapper.find('[data-testid="threat-monitoring-alerts-tab"]'); + const findAlertTab = () => wrapper.findByTestId('threat-monitoring-alerts-tab'); + const findStatisticsTab = () => wrapper.findByTestId('threat-monitoring-statistics-tab'); afterEach(() => { wrapper.destroy(); @@ -82,6 +88,7 @@ describe('ThreatMonitoringApp component', () => { propsData: { defaultEnvironmentId: invalidEnvironmentId, }, + stubs: { GlTabs: false }, }); }); @@ -89,13 +96,21 @@ describe('ThreatMonitoringApp component', () => { expect(store.dispatch).not.toHaveBeenCalled(); }); - it('shows only the empty state', () => { - const emptyState = findEmptyState(); - expect(wrapper.element).toBe(emptyState.element); - expect(findEmptyStateMessage().exists()).toBe(true); - expect(emptyState.props()).toMatchObject({ - svgPath: emptyStateSvgPath, - }); + it('shows the "no environment" empty state', () => { + expect(findNoEnvironmentEmptyStates().length).toBe(2); + }); + + it('shows the tabs', () => { + expect(findNetworkPolicyTab().exists()).toBe(true); + expect(findStatisticsTab().exists()).toBe(true); + }); + + it('does not show the network policy list', () => { + expect(findNetworkPolicyList().exists()).toBe(false); + }); + + it('does not show the threat monitoring section', () => { + expect(findNetworkPolicySection().exists()).toBe(false); }); }, ); diff --git a/ee/spec/frontend/threat_monitoring/components/no_environment_empty_state_spec.js b/ee/spec/frontend/threat_monitoring/components/no_environment_empty_state_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..e62d7156f3afdf102175b56e3cc880de4bb18104 --- /dev/null +++ b/ee/spec/frontend/threat_monitoring/components/no_environment_empty_state_spec.js @@ -0,0 +1,54 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlEmptyState, GlSprintf } from '@gitlab/ui'; +import { + EMPTY_STATE_DESCRIPTION, + NO_ENVIRONMENT_TITLE, +} from 'ee/threat_monitoring/components/constants'; +import NoEnvironmentEmptyState from 'ee/threat_monitoring/components/no_environment_empty_state.vue'; + +const documentationPath = '/docs'; +const emptyStateSvgPath = '/svgs'; + +describe('NoEnvironmentEmptyState component', () => { + let wrapper; + + const findGlEmptyState = () => wrapper.find(GlEmptyState); + const findGlSprintf = () => wrapper.find(GlSprintf); + + const factory = () => { + wrapper = shallowMount(NoEnvironmentEmptyState, { + provide: { + documentationPath, + emptyStateSvgPath, + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + describe('default state', () => { + beforeEach(() => { + factory(); + }); + + it('matches the snapshot', () => { + expect(wrapper.element).toMatchSnapshot(); + }); + + it('shows the GlEmptyState component', () => { + expect(findGlEmptyState().exists()).toBe(true); + expect(findGlEmptyState().attributes()).toMatchObject({ + title: NO_ENVIRONMENT_TITLE, + svgpath: emptyStateSvgPath, + }); + }); + + it('shows the message', () => { + expect(findGlSprintf().exists()).toBe(true); + expect(findGlSprintf().attributes('message')).toBe(EMPTY_STATE_DESCRIPTION); + }); + }); +});