From d9ca1cb16965fa89fc11c97de7d8e944ee38656e Mon Sep 17 00:00:00 2001 From: Lorenz van Herwaarden Date: Fri, 17 Oct 2025 11:44:25 +0200 Subject: [PATCH 1/3] Add panel filters URL syncing to the new security dashboard This adds 3 utils to read and write panel specific filters to the URL parameters. It also sync the panel filters of total risk score and vulnerabilities over time panels. --- .../shared/group_risk_score_panel.vue | 28 ++- .../filtered_search.vue | 15 +- .../vulnerabilities_over_time_panel.vue | 55 +++++- .../utils/panel_state_url_sync.js | 70 ++++++++ .../shared/group_risk_score_panel_spec.js | 27 +++ .../filtered_search_spec.js | 17 +- .../vulnerabilities_over_time_panel_spec.js | 94 +++++++++- .../utils/panel_state_url_sync_spec.js | 170 ++++++++++++++++++ 8 files changed, 446 insertions(+), 30 deletions(-) create mode 100644 ee/app/assets/javascripts/security_dashboard/utils/panel_state_url_sync.js create mode 100644 ee/spec/frontend/security_dashboard/utils/panel_state_url_sync_spec.js diff --git a/ee/app/assets/javascripts/security_dashboard/components/shared/group_risk_score_panel.vue b/ee/app/assets/javascripts/security_dashboard/components/shared/group_risk_score_panel.vue index fa3f06cd0792d9..ebf298f8d810e2 100644 --- a/ee/app/assets/javascripts/security_dashboard/components/shared/group_risk_score_panel.vue +++ b/ee/app/assets/javascripts/security_dashboard/components/shared/group_risk_score_panel.vue @@ -1,12 +1,18 @@ diff --git a/ee/app/assets/javascripts/security_dashboard/components/shared/security_dashboard_filtered_search/filtered_search.vue b/ee/app/assets/javascripts/security_dashboard/components/shared/security_dashboard_filtered_search/filtered_search.vue index 596b334c0819d2..21fc3878fd1afd 100644 --- a/ee/app/assets/javascripts/security_dashboard/components/shared/security_dashboard_filtered_search/filtered_search.vue +++ b/ee/app/assets/javascripts/security_dashboard/components/shared/security_dashboard_filtered_search/filtered_search.vue @@ -2,6 +2,7 @@ import { nextTick } from 'vue'; import { GlFilteredSearch } from '@gitlab/ui'; import { isEqual } from 'lodash'; +import { setUrlParams, updateHistory } from '~/lib/utils/url_utility'; import { ALL_ID } from 'ee/security_dashboard/components/shared/filters/constants'; export default { @@ -26,7 +27,7 @@ export default { const { initialValue, newFilters } = this.tokens.reduce( (acc, token) => { const paramValue = params.get(token.type); - const data = paramValue?.split(',').filter(Boolean); + const data = paramValue?.split(',').filter((i) => i !== ''); if (data?.length > 0) { acc.newFilters[token.type] = data; @@ -62,17 +63,15 @@ export default { const params = this.tokens.reduce((acc, { type }) => { const filterValue = this.filters[type]; if (filterValue?.length > 0) { - acc.set(type, filterValue.join(',')); + acc[type] = filterValue.join(','); } else { - acc.delete(type); + acc[type] = undefined; } return acc; - }, new URLSearchParams(window.location.search)); + }, {}); + const url = setUrlParams(params, window.location.href, false, false, true); - const newUrl = params.toString() - ? `${window.location.pathname}?${params.toString()}` - : window.location.pathname; - window.history.pushState({}, '', newUrl); + updateHistory({ url }); }, async handleTokenComplete({ type }) { // Need to wait for `this.value` to have been updated diff --git a/ee/app/assets/javascripts/security_dashboard/components/shared/vulnerabilities_over_time_panel.vue b/ee/app/assets/javascripts/security_dashboard/components/shared/vulnerabilities_over_time_panel.vue index 1ae8c4496f2dc2..3155b9c135a682 100644 --- a/ee/app/assets/javascripts/security_dashboard/components/shared/vulnerabilities_over_time_panel.vue +++ b/ee/app/assets/javascripts/security_dashboard/components/shared/vulnerabilities_over_time_panel.vue @@ -2,6 +2,7 @@ import ExtendedDashboardPanel from '~/vue_shared/components/customizable_dashboard/extended_dashboard_panel.vue'; import { s__ } from '~/locale'; import { formatDate, getDateInPast } from '~/lib/utils/datetime_utility'; +import { readFromUrl, writeToUrl } from 'ee/security_dashboard/utils/panel_state_url_sync'; import VulnerabilitiesOverTimeChart from 'ee/security_dashboard/components/shared/charts/open_vulnerabilities_over_time.vue'; import projectVulnerabilitiesOverTime from 'ee/security_dashboard/graphql/queries/project_vulnerabilities_over_time.query.graphql'; import groupVulnerabilitiesOverTime from 'ee/security_dashboard/graphql/queries/group_vulnerabilities_over_time.query.graphql'; @@ -16,6 +17,10 @@ const TIME_PERIODS = { NINETY_DAYS: { key: 'ninetyDays', startDays: 90, endDays: 61 }, }; +const PANEL_ID = 'vulnerabilitiesOverTime'; +const GROUP_BY_DEFAULT = 'severity'; +const TIME_PERIOD_DEFAULT = 30; + const SCOPE_CONFIG = { project: { query: projectVulnerabilitiesOverTime, @@ -56,17 +61,27 @@ export default { data() { return { fetchError: false, - groupedBy: 'severity', - selectedTimePeriod: 30, + groupedBy: readFromUrl({ + panelId: PANEL_ID, + paramName: 'groupBy', + defaultValue: GROUP_BY_DEFAULT, + }), + selectedTimePeriod: readFromUrl({ + panelId: PANEL_ID, + paramName: 'timePeriod', + defaultValue: TIME_PERIOD_DEFAULT, + }), + severity: readFromUrl({ + panelId: PANEL_ID, + paramName: 'severity', + defaultValue: [], + }), isLoading: false, chartData: { thirtyDays: [], sixtyDays: [], ninetyDays: [], }, - panelLevelFilters: { - severity: [], - }, }; }, computed: { @@ -76,7 +91,7 @@ export default { combinedFilters() { return { ...this.filters, - ...this.panelLevelFilters, + severity: this.severity, }; }, hasChartData() { @@ -93,7 +108,7 @@ export default { }, baseQueryVariables() { const baseVariables = { - severity: this.panelLevelFilters.severity, + severity: this.severity, includeBySeverity: this.groupedBy === 'severity', includeByReportType: this.groupedBy === 'reportType', fullPath: this.fullPath, @@ -121,9 +136,31 @@ export default { deep: true, immediate: true, }, - selectedTimePeriod() { + selectedTimePeriod(value) { + writeToUrl({ + panelId: PANEL_ID, + paramName: 'timePeriod', + value, + defaultValue: TIME_PERIOD_DEFAULT, + }); this.fetchChartData(); }, + groupedBy(value) { + writeToUrl({ + panelId: PANEL_ID, + paramName: 'groupBy', + value, + defaultValue: GROUP_BY_DEFAULT, + }); + }, + severity(value) { + writeToUrl({ + panelId: PANEL_ID, + paramName: 'severity', + value, + defaultValue: [], + }); + }, }, methods: { async fetchChartData() { @@ -173,7 +210,7 @@ export default { >