diff --git a/ee/app/assets/javascripts/security_dashboard/components/shared/group_vulnerabilities_over_time_panel.vue b/ee/app/assets/javascripts/security_dashboard/components/shared/group_vulnerabilities_over_time_panel.vue index ab7f614b15a39d456117c51d0d1f56cb509d65f9..d879186cb5cb915d00a95334e8d045e4c8176f11 100644 --- a/ee/app/assets/javascripts/security_dashboard/components/shared/group_vulnerabilities_over_time_panel.vue +++ b/ee/app/assets/javascripts/security_dashboard/components/shared/group_vulnerabilities_over_time_panel.vue @@ -1,14 +1,13 @@ + + diff --git a/ee/app/assets/javascripts/security_dashboard/components/shared/over_time_severity_filter.vue b/ee/app/assets/javascripts/security_dashboard/components/shared/over_time_severity_filter.vue index 004905178f9ade430dfc9fed4524fc5170a87bec..a47ebcc977433a9faa6b9996b46069b6f80c2c01 100644 --- a/ee/app/assets/javascripts/security_dashboard/components/shared/over_time_severity_filter.vue +++ b/ee/app/assets/javascripts/security_dashboard/components/shared/over_time_severity_filter.vue @@ -58,7 +58,7 @@ export default { :selected="selectedSeverities" block multiple - class="gl-mr-2 gl-w-15" + class="gl-w-15 gl-flex-shrink-0" size="small" :toggle-text="severityFilterToggleText" @select="updateSelected" diff --git a/ee/app/assets/javascripts/security_dashboard/components/shared/project_vulnerabilities_over_time_panel.vue b/ee/app/assets/javascripts/security_dashboard/components/shared/project_vulnerabilities_over_time_panel.vue index 5a9d5bebd7574bcf3cf4786d9b9467ddacc40da8..22e6edfeea5fe78f5bb229cb8692517abdb837e0 100644 --- a/ee/app/assets/javascripts/security_dashboard/components/shared/project_vulnerabilities_over_time_panel.vue +++ b/ee/app/assets/javascripts/security_dashboard/components/shared/project_vulnerabilities_over_time_panel.vue @@ -2,13 +2,12 @@ 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 { fetchPolicies } from '~/lib/graphql'; 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 { formatVulnerabilitiesOverTimeData } from 'ee/security_dashboard/utils/chart_utils'; -import { DASHBOARD_LOOKBACK_DAYS } from 'ee/security_dashboard/constants'; import OverTimeSeverityFilter from './over_time_severity_filter.vue'; import OverTimeGroupBy from './over_time_group_by.vue'; +import OverTimePeriodSelector from './over_time_period_selector.vue'; export default { name: 'ProjectVulnerabilitiesOverTimePanel', @@ -17,6 +16,7 @@ export default { VulnerabilitiesOverTimeChart, OverTimeGroupBy, OverTimeSeverityFilter, + OverTimePeriodSelector, }, inject: ['projectFullPath'], props: { @@ -25,47 +25,101 @@ export default { required: true, }, }, - apollo: { - vulnerabilitiesOverTime: { - fetchPolicy: fetchPolicies.NETWORK_ONLY, - query: projectVulnerabilitiesOverTime, - variables() { - const lookbackDate = getDateInPast(new Date(), DASHBOARD_LOOKBACK_DAYS); - const startDate = formatDate(lookbackDate, 'isoDate'); - const endDate = formatDate(new Date(), 'isoDate'); - - return { - fullPath: this.projectFullPath, - startDate, - endDate, - reportType: this.filters.reportType, - severity: this.panelLevelFilters.severity, - includeBySeverity: this.groupedBy === 'severity', - includeByReportType: this.groupedBy === 'reportType', - }; - }, - update(data) { - const rawData = data.project?.securityMetrics?.vulnerabilitiesOverTime?.nodes || []; - return formatVulnerabilitiesOverTimeData(rawData, this.groupedBy); - }, - error() { - this.fetchError = true; - }, - }, - }, data() { return { - vulnerabilitiesOverTime: [], fetchError: false, groupedBy: 'severity', + selectedTimePeriod: 30, + isLoading: false, + chartData: { + thirtyDays: [], + sixtyDays: [], + ninetyDays: [], + }, panelLevelFilters: { severity: [], }, }; }, computed: { + combinedFilters() { + return { + ...this.filters, + ...this.panelLevelFilters, + }; + }, hasChartData() { - return this.vulnerabilitiesOverTime.length > 0; + return this.selectedChartData.length > 0; + }, + selectedChartData() { + const selectedChartData = [ + ...(this.selectedTimePeriod >= 90 ? this.chartData.ninetyDays : []), + ...(this.selectedTimePeriod >= 60 ? this.chartData.sixtyDays : []), + ...this.chartData.thirtyDays, + ]; + + return formatVulnerabilitiesOverTimeData(selectedChartData, this.groupedBy); + }, + baseQueryVariables() { + return { + reportType: this.filters.reportType, + severity: this.panelLevelFilters.severity, + includeBySeverity: this.groupedBy === 'severity', + includeByReportType: this.groupedBy === 'reportType', + fullPath: this.projectFullPath, + }; + }, + lookBackDatesToLoad() { + return [ + { key: 'thirtyDays', startDays: 30, endDays: 0 }, + { key: 'sixtyDays', startDays: 60, endDays: 31 }, + { key: 'ninetyDays', startDays: 90, endDays: 61 }, + ].filter(({ startDays }) => startDays <= this.selectedTimePeriod); + }, + }, + watch: { + baseQueryVariables: { + handler() { + this.fetchChartData(); + }, + deep: true, + immediate: true, + }, + selectedTimePeriod() { + this.fetchChartData(); + }, + }, + methods: { + async fetchChartData() { + this.isLoading = true; + this.fetchError = false; + + try { + // Note: we want to load each chunk sequentially for BE-performance reasons + for await (const lookBackDate of this.lookBackDatesToLoad) { + await this.loadLookBackWindow(lookBackDate); + } + } catch (error) { + this.fetchError = true; + } finally { + this.isLoading = false; + } + }, + async loadLookBackWindow({ key, startDays, endDays }) { + const startDate = formatDate(getDateInPast(new Date(), startDays), 'isoDate'); + const endDate = formatDate(getDateInPast(new Date(), endDays), 'isoDate'); + + const result = await this.$apollo.query({ + query: projectVulnerabilitiesOverTime, + variables: { + ...this.baseQueryVariables, + startDate, + endDate, + }, + }); + + this.chartData[key] = + result.data.project?.securityMetrics?.vulnerabilitiesOverTime?.nodes || []; }, }, tooltip: { @@ -77,20 +131,23 @@ export default {