Skip to content
Snippets Groups Projects
Commit 958a54cb authored by Mark Florian's avatar Mark Florian Committed by Marcel van Remmerden
Browse files

Persist Threat Monitoring alert dismissal

This ensures that once a user dismisses the info alert on the Threat
Monitoring page, it doesn't appear again on subsequent visits.

Part of [WAF statistics reporting][1].

[1]: #14707
parent b68fceb2
No related branches found
No related tags found
1 merge request!21257Updated no commit verbiage
......@@ -2,6 +2,7 @@
import { mapActions, mapState } from 'vuex';
import { GlAlert, GlEmptyState, GlIcon, GlLink, GlPopover } from '@gitlab/ui';
import { s__ } from '~/locale';
import axios from '~/lib/utils/axios_utils';
import ThreatMonitoringFilters from './threat_monitoring_filters.vue';
import WafLoadingSkeleton from './waf_loading_skeleton.vue';
import WafStatisticsSummary from './waf_statistics_summary.vue';
......@@ -33,10 +34,22 @@ export default {
type: String,
required: true,
},
showUserCallout: {
type: Boolean,
required: true,
},
userCalloutId: {
type: String,
required: true,
},
userCalloutsPath: {
type: String,
required: true,
},
},
data() {
return {
showAlert: true,
showAlert: this.showUserCallout,
// WAF requires the project to have at least one available environment.
// An invalid default environment id means there there are no available
......@@ -61,6 +74,10 @@ export default {
},
dismissAlert() {
this.showAlert = false;
axios.post(this.userCalloutsPath, {
feature_name: this.userCalloutId,
});
},
},
emptyStateDescription: s__(
......
import Vue from 'vue';
import { parseBoolean } from '~/lib/utils/common_utils';
import ThreatMonitoringApp from './components/app.vue';
import createStore from './store';
......@@ -10,6 +11,9 @@ export default () => {
emptyStateSvgPath,
documentationPath,
defaultEnvironmentId,
showUserCallout,
userCalloutId,
userCalloutsPath,
} = el.dataset;
const store = createStore();
......@@ -27,6 +31,9 @@ export default () => {
emptyStateSvgPath,
documentationPath,
defaultEnvironmentId: parseInt(defaultEnvironmentId, 10),
showUserCallout: parseBoolean(showUserCallout),
userCalloutId,
userCalloutsPath,
},
});
},
......
......@@ -9,6 +9,7 @@ module UserCalloutsHelper
CANARY_DEPLOYMENT = 'canary_deployment'
GOLD_TRIAL = 'gold_trial'
GOLD_TRIAL_BILLINGS = 'gold_trial_billings'
THREAT_MONITORING_INFO = 'threat_monitoring_info'
def show_canary_deployment_callout?(project)
!user_dismissed?(CANARY_DEPLOYMENT) &&
......@@ -64,6 +65,10 @@ def render_billings_gold_trial(user, namespace)
render 'shared/gold_trial_callout_content', is_dismissable: !namespace.free_plan?, callout: GOLD_TRIAL_BILLINGS
end
def show_threat_monitoring_info?
!user_dismissed?(THREAT_MONITORING_INFO)
end
private
def hashed_storage_enabled?
......
......@@ -14,7 +14,8 @@ def feature_names
geo_enable_hashed_storage: 5,
geo_migrate_hashed_storage: 6,
canary_deployment: 7,
gold_trial_billings: 8
gold_trial_billings: 8,
threat_monitoring_info: 11
)
end
end
......
......@@ -8,4 +8,7 @@
waf_statistics_endpoint: 'dummy',
environments_endpoint: project_environments_path(@project),
default_environment_id: default_environment_id,
user_callouts_path: user_callouts_path,
user_callout_id: UserCalloutsHelper::THREAT_MONITORING_INFO,
show_user_callout: show_threat_monitoring_info?.to_s,
} }
import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import { GlAlert, GlEmptyState } from '@gitlab/ui';
import { TEST_HOST } from 'helpers/test_constants';
import createStore from 'ee/threat_monitoring/store';
......@@ -13,6 +15,8 @@ const documentationPath = '/docs';
const emptyStateSvgPath = '/svgs';
const environmentsEndpoint = `${TEST_HOST}/environments`;
const wafStatisticsEndpoint = `${TEST_HOST}/waf`;
const userCalloutId = 'threat_monitoring_info';
const userCalloutsPath = `${TEST_HOST}/user_callouts`;
describe('ThreatMonitoringApp component', () => {
let store;
......@@ -28,7 +32,15 @@ describe('ThreatMonitoringApp component', () => {
jest.spyOn(store, 'dispatch').mockImplementation();
wrapper = shallowMount(ThreatMonitoringApp, {
propsData,
propsData: {
defaultEnvironmentId,
emptyStateSvgPath,
documentationPath,
showUserCallout: true,
userCalloutId,
userCalloutsPath,
...propsData,
},
store,
sync: false,
});
......@@ -49,8 +61,6 @@ describe('ThreatMonitoringApp component', () => {
beforeEach(() => {
factory({
defaultEnvironmentId: invalidEnvironmentId,
emptyStateSvgPath,
documentationPath,
});
});
......@@ -71,11 +81,7 @@ describe('ThreatMonitoringApp component', () => {
describe('given there is a default environment', () => {
beforeEach(() => {
factory({
defaultEnvironmentId,
emptyStateSvgPath,
documentationPath,
});
factory();
});
it('dispatches the setCurrentEnvironmentId and fetchEnvironments actions', () => {
......@@ -103,14 +109,39 @@ describe('ThreatMonitoringApp component', () => {
});
describe('dismissing the alert', () => {
let mockAxios;
beforeEach(() => {
mockAxios = new MockAdapter(axios);
mockAxios.onPost(userCalloutsPath, { feature_name: userCalloutId }).reply(200);
findAlert().vm.$emit('dismiss');
return wrapper.vm.$nextTick();
});
afterEach(() => {
mockAxios.restore();
});
it('hides the alert', () => {
expect(findAlert().exists()).toBe(false);
});
it('posts the dismissal to the user callouts endpoint', () => {
expect(mockAxios.history.post).toHaveLength(1);
});
});
});
describe('given showUserCallout is false', () => {
beforeEach(() => {
factory({
showUserCallout: false,
});
});
it('does not render the alert', () => {
expect(findAlert().exists()).toBe(false);
});
describe('given the statistics are loading', () => {
......
......@@ -268,4 +268,26 @@
end
end
end
describe '.show_threat_monitoring_info?' do
subject { helper.show_threat_monitoring_info? }
let(:user) { create(:user) }
before do
expect(helper).to receive(:current_user).and_return(user)
end
context 'when the threat monitoring info has not been dismissed' do
it { is_expected.to be_truthy }
end
context 'when the threat monitoring info was dismissed' do
before do
create(:user_callout, user: user, feature_name: described_class::THREAT_MONITORING_INFO)
end
it { is_expected.to be_falsy }
end
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment