From ee7a285d30547e539ef5ba908bf1e4f554a1ab0b Mon Sep 17 00:00:00 2001
From: AWSHurneyt
Date: Sat, 15 Mar 2025 04:57:22 -0700
Subject: [PATCH 1/3] Refactored vega-based visualizations to chart.js.
Signed-off-by: AWSHurneyt
---
package.json | 4 +-
.../pages/Alerts/containers/Alerts/Alerts.tsx | 67 +--
.../Findings/containers/Findings/Findings.tsx | 66 +--
.../Overview/components/Widgets/Summary.tsx | 64 +-
.../components/Widgets/TopRulesWidget.tsx | 13 +-
.../Overview/containers/Overview/Overview.tsx | 3 -
.../utils/__snapshots__/helper.test.ts.snap | 476 ---------------
public/pages/Overview/utils/helper.test.ts | 28 -
public/pages/Overview/utils/helpers.ts | 301 ----------
public/utils/chartUtils.tsx | 558 ++++++++++++++++++
public/utils/helpers.tsx | 78 ---
yarn.lock | 17 +
12 files changed, 665 insertions(+), 1010 deletions(-)
delete mode 100644 public/pages/Overview/utils/__snapshots__/helper.test.ts.snap
create mode 100644 public/utils/chartUtils.tsx
diff --git a/package.json b/package.json
index 2f2e25bd5..25512d41e 100644
--- a/package.json
+++ b/package.json
@@ -85,6 +85,8 @@
},
"dependencies": {
"formik": "^2.2.6",
- "react-graph-vis": "^1.0.7"
+ "react-graph-vis": "^1.0.7",
+ "chart.js": "^4.4.8",
+ "chartjs-adapter-moment": "^1.0.1"
}
}
diff --git a/public/pages/Alerts/containers/Alerts/Alerts.tsx b/public/pages/Alerts/containers/Alerts/Alerts.tsx
index ca1e26cc7..ce3f154e5 100644
--- a/public/pages/Alerts/containers/Alerts/Alerts.tsx
+++ b/public/pages/Alerts/containers/Alerts/Alerts.tsx
@@ -17,7 +17,6 @@ import {
EuiToolTip,
EuiEmptyPrompt,
EuiTableSelectionType,
- EuiIcon,
EuiTabbedContent,
EuiText,
} from '@elastic/eui';
@@ -25,12 +24,7 @@ import { FieldValueSelectionFilterConfigType } from '@elastic/eui/src/components
import dateMath from '@elastic/datemath';
import React, { Component } from 'react';
import { ContentPanel } from '../../../../components/ContentPanel';
-import {
- getAlertsVisualizationSpec,
- getChartTimeUnit,
- getDomainRange,
- TimeUnit,
-} from '../../../Overview/utils/helpers';
+import { getChartTimeUnit, TimeUnit } from '../../../Overview/utils/helpers';
import moment from 'moment';
import {
ALERT_STATE,
@@ -58,13 +52,11 @@ import {
errorNotificationToast,
getDuration,
renderTime,
- renderVisualization,
setBreadcrumbs,
successNotificationToast,
} from '../../../../utils/helpers';
import { NotificationsStart } from 'opensearch-dashboards/public';
import { match, RouteComponentProps, withRouter } from 'react-router-dom';
-import { ChartContainer } from '../../../../components/Charts/ChartContainer';
import {
AlertItem,
CorrelationAlertTableItem,
@@ -77,6 +69,7 @@ import { DurationRange } from '@elastic/eui/src/components/date_picker/types';
import { DataStore } from '../../../../store/DataStore';
import { ThreatIntelAlertsTable } from '../../components/ThreatIntelAlertsTable/ThreatIntelAlertsTable';
import { PageHeader } from '../../../../components/PageHeader/PageHeader';
+import { createBarChartWrapper } from '../../../../utils/chartUtils';
type FilterAlertParams =
| { alerts: AlertItem[]; timeField: 'last_notification_time' }
@@ -121,11 +114,13 @@ export interface AlertsState {
selectedTabId: AlertTabId;
}
-const groupByOptions = [
+export const alertsGroupByOptions = [
{ text: 'Alert status', value: 'status' },
{ text: 'Alert severity', value: 'severity' },
];
+const ALERTS_VIEW_CHART = 'alerts-view';
+
export class Alerts extends Component {
private abortControllers: AbortController[] = [];
@@ -248,7 +243,7 @@ export class Alerts extends Component {
/>
),
});
- renderVisualization(this.generateVisualizationSpec(filteredAlerts), 'alerts-view');
+ this.getChart(this.getVisData(filteredAlerts));
};
filterCorrelationAlerts = () => {
@@ -272,7 +267,7 @@ export class Alerts extends Component {
/>
),
});
- renderVisualization(this.generateVisualizationSpec(filteredCorrelationAlerts), 'alerts-view');
+ this.getChart(this.getVisData(filteredCorrelationAlerts));
};
filterThreatIntelAlerts = () => {
@@ -296,28 +291,19 @@ export class Alerts extends Component {
/>
),
});
- renderVisualization(this.generateVisualizationSpec(filteredAlerts), 'alerts-view');
+ this.getChart(this.getVisData(filteredAlerts));
};
private renderVisAsPerTab() {
switch (this.state.selectedTabId) {
case AlertTabId.DetectionRules:
- renderVisualization(
- this.generateVisualizationSpec(this.state.filteredAlerts),
- 'alerts-view'
- );
+ this.getChart(this.getVisData(this.state.filteredAlerts));
break;
case AlertTabId.Correlations:
- renderVisualization(
- this.generateVisualizationSpec(this.state.filteredCorrelationAlerts),
- 'alerts-view'
- );
+ this.getChart(this.getVisData(this.state.filteredCorrelationAlerts));
break;
case AlertTabId.ThreatIntel:
- renderVisualization(
- this.generateVisualizationSpec(this.state.filteredThreatIntelAlerts),
- 'alerts-view'
- );
+ this.getChart(this.getVisData(this.state.filteredThreatIntelAlerts));
break;
}
}
@@ -518,39 +504,28 @@ export class Alerts extends Component {
this.setState({ flyoutCorrelationData: alertItem ? { alertItem } : undefined });
}
- generateVisualizationSpec(alerts: (AlertItem | CorrelationAlertTableItem | ThreatIntelAlert)[]) {
- const visData = alerts.map((alert) => {
+ getChart(data: any) {
+ createBarChartWrapper(data, this.state.groupBy, ALERTS_VIEW_CHART, this.props.dateTimeFilter);
+ }
+
+ getVisData(alerts: (AlertItem | CorrelationAlertTableItem | ThreatIntelAlert)[]) {
+ return alerts.map((alert) => {
const time = new Date(alert.start_time);
time.setMilliseconds(0);
time.setSeconds(0);
return {
alert: 1,
- time,
+ time: time.getTime(),
status: alert.state,
severity: parseAlertSeverityToOption(alert.severity)?.label || alert.severity,
};
});
- const {
- dateTimeFilter = {
- startTime: DEFAULT_DATE_RANGE.start,
- endTime: DEFAULT_DATE_RANGE.end,
- },
- } = this.props;
- const chartTimeUnits = getChartTimeUnit(dateTimeFilter.startTime, dateTimeFilter.endTime);
- return getAlertsVisualizationSpec(visData, this.state.groupBy, {
- timeUnit: chartTimeUnits.timeUnit,
- dateFormat: chartTimeUnits.dateFormat,
- domain: getDomainRange(
- [dateTimeFilter.startTime, dateTimeFilter.endTime],
- chartTimeUnits.timeUnit.unit
- ),
- });
}
createGroupByControl(): React.ReactNode {
return createSelectComponent(
- groupByOptions,
+ alertsGroupByOptions,
this.state.groupBy,
'alert-vis-groupBy',
(event) => {
@@ -1181,7 +1156,9 @@ export class Alerts extends Component {
}
/>
) : (
-
+
+
+
)}
diff --git a/public/pages/Findings/containers/Findings/Findings.tsx b/public/pages/Findings/containers/Findings/Findings.tsx
index aeebd7d3e..777cde04c 100644
--- a/public/pages/Findings/containers/Findings/Findings.tsx
+++ b/public/pages/Findings/containers/Findings/Findings.tsx
@@ -32,13 +32,7 @@ import {
FindingTabId,
MAX_RECENTLY_USED_TIME_RANGES,
} from '../../../../utils/constants';
-import {
- getChartTimeUnit,
- getDomainRange,
- getFindingsVisualizationSpec,
- getThreatIntelFindingsVisualizationSpec,
- TimeUnit,
-} from '../../../Overview/utils/helpers';
+import { getChartTimeUnit, TimeUnit } from '../../../Overview/utils/helpers';
import {
getNotificationChannels,
parseNotificationChannelsToOptions,
@@ -46,7 +40,6 @@ import {
import {
createSelectComponent,
errorNotificationToast,
- renderVisualization,
getDuration,
getIsNotificationPluginInstalled,
setBreadcrumbs,
@@ -54,7 +47,6 @@ import {
} from '../../../../utils/helpers';
import { RuleSource } from '../../../../../server/models/interfaces';
import { NotificationsStart } from 'opensearch-dashboards/public';
-import { ChartContainer } from '../../../../components/Charts/ChartContainer';
import { DataStore } from '../../../../store/DataStore';
import { DurationRange } from '@elastic/eui/src/components/date_picker/types';
import {
@@ -69,6 +61,7 @@ import {
import { ThreatIntelFindingsTable } from '../../components/FindingsTable/ThreatIntelFindingsTable';
import { PageHeader } from '../../../../components/PageHeader/PageHeader';
import { RuleSeverityValue, RuleSeverityPriority } from '../../../Rules/utils/constants';
+import { createBarChartWrapper } from '../../../../utils/chartUtils';
interface FindingsProps extends RouteComponentProps, DataSourceProps {
detectorService: DetectorsService;
@@ -133,6 +126,8 @@ export const groupByOptionsByTabId = {
[FindingTabId.ThreatIntel]: [{ text: 'Indicator type', value: 'indicatorType' }],
};
+const FINDINGS_VIEW_CHART = 'findings-view';
+
class Findings extends Component {
private abortGetFindingsControllers: AbortController[] = [];
@@ -219,7 +214,8 @@ class Findings extends Component {
) {
this.onRefresh();
} else if (this.shouldUpdateVisualization(prevState)) {
- renderVisualization(this.generateVisualizationSpec(), 'findings-view');
+ const data = this.generateVisualizationData();
+ this.createStackedBarChart(data.visData, data.groupBy);
}
}
@@ -239,7 +235,8 @@ class Findings extends Component {
} else if (this.state.selectedTabId === FindingTabId.ThreatIntel) {
await this.getThreatIntelFindings();
}
- renderVisualization(this.generateVisualizationSpec(), 'findings-view');
+ const data = this.generateVisualizationData();
+ this.createStackedBarChart(data.visData, data.groupBy);
};
setStateForTab(
@@ -419,7 +416,7 @@ class Findings extends Component {
});
};
- generateVisualizationSpec() {
+ generateVisualizationData() {
const visData: (FindingVisualizationData | ThreatIntelFindingVisualizationData)[] = [];
const { selectedTabId, findingStateByTabId } = this.state;
@@ -428,7 +425,6 @@ class Findings extends Component {
? findingStateByTabId[FindingTabId.DetectionRules]
: findingStateByTabId[FindingTabId.ThreatIntel];
const groupBy = findingsState.groupBy;
- let specGetter;
if (selectedTabId === FindingTabId.DetectionRules) {
(findingsState.filteredFindings as FindingItemType[]).forEach((finding: FindingItemType) => {
@@ -449,7 +445,6 @@ class Findings extends Component {
ruleLevel === 'critical' ? ruleLevel : (finding as any)['ruleSeverity'] || ruleLevel,
});
});
- specGetter = getFindingsVisualizationSpec;
} else {
(findingsState.findings as ThreatIntelFinding[]).forEach((finding) => {
const findingTime = new Date(finding.timestamp);
@@ -462,24 +457,12 @@ class Findings extends Component {
indicatorType: finding.ioc_type,
});
});
- specGetter = getThreatIntelFindingsVisualizationSpec;
}
- const {
- dateTimeFilter = {
- startTime: DEFAULT_DATE_RANGE.start,
- endTime: DEFAULT_DATE_RANGE.end,
- },
- } = this.props;
- const chartTimeUnits = getChartTimeUnit(dateTimeFilter.startTime, dateTimeFilter.endTime);
-
- return specGetter(visData, groupBy, {
- timeUnit: chartTimeUnits.timeUnit,
- dateFormat: chartTimeUnits.dateFormat,
- domain: getDomainRange(
- [dateTimeFilter.startTime, dateTimeFilter.endTime],
- chartTimeUnits.timeUnit.unit
- ),
- });
+
+ return {
+ visData,
+ groupBy,
+ };
}
createGroupByControl(): React.ReactNode {
@@ -506,6 +489,21 @@ class Findings extends Component {
});
};
+ createStackedBarChart(
+ data: (FindingVisualizationData | ThreatIntelFindingVisualizationData)[],
+ groupBy: string
+ ) {
+ // Calculate the time difference in milliseconds
+ const {
+ dateTimeFilter = {
+ startTime: DEFAULT_DATE_RANGE.start,
+ endTime: DEFAULT_DATE_RANGE.end,
+ },
+ } = this.props;
+
+ createBarChartWrapper(data, groupBy, FINDINGS_VIEW_CHART, dateTimeFilter);
+ }
+
render() {
const {
loading,
@@ -541,7 +539,7 @@ class Findings extends Component {
finding['ruleName'] =
matchedRules[0]?.title ||
- (finding.queries.find(({ id }) => isThreatIntelQuery(id))
+ (finding.queries.find(({ id }: any) => isThreatIntelQuery(id))
? 'Threat intel'
: DEFAULT_EMPTY_DATA);
finding['ruleSeverity'] =
@@ -679,7 +677,9 @@ class Findings extends Component {
}
/>
) : (
-
+
+
+
)}
diff --git a/public/pages/Overview/components/Widgets/Summary.tsx b/public/pages/Overview/components/Widgets/Summary.tsx
index 910aa555b..23e90b8d2 100644
--- a/public/pages/Overview/components/Widgets/Summary.tsx
+++ b/public/pages/Overview/components/Widgets/Summary.tsx
@@ -16,19 +16,12 @@ import {
import React, { useCallback, useEffect, useState } from 'react';
import { WidgetContainer } from './WidgetContainer';
import { summaryGroupByOptions } from '../../utils/constants';
-import {
- getChartTimeUnit,
- getDomainRange,
- getOverviewVisualizationSpec,
- getTimeWithMinPrecision,
- TimeUnit,
-} from '../../utils/helpers';
-import { createSelectComponent, renderVisualization } from '../../../../utils/helpers';
+import { TimeUnit } from '../../utils/helpers';
+import { createSelectComponent } from '../../../../utils/helpers';
import { ROUTES } from '../../../../utils/constants';
-import { ChartContainer } from '../../../../components/Charts/ChartContainer';
-import { getLogTypeLabel } from '../../../LogTypes/utils/helpers';
import { OverviewAlertItem, OverviewFindingItem } from '../../../../../types';
import { getUseUpdatedUx } from '../../../../services/utils/constants';
+import { createBarAndLineChartWrapper } from '../../../../utils/chartUtils';
export interface SummaryProps {
findings: OverviewFindingItem[];
@@ -39,12 +32,7 @@ export interface SummaryProps {
timeUnit: TimeUnit;
}
-export interface SummaryData {
- time: number;
- alert: number;
- finding: number;
- logType?: string;
-}
+export const SUMMARY_VIEW_CHART = 'summary-view';
export const Summary: React.FC = ({
alerts,
@@ -54,7 +42,8 @@ export const Summary: React.FC = ({
loading = false,
}) => {
const [groupBy, setGroupBy] = useState('');
- const [summaryData, setSummaryData] = useState([]);
+ const [alertsVisData, setAlertsVisData] = useState([]);
+ const [findingsVisData, setFindingsVisData] = useState([]);
const [activeAlerts, setActiveAlerts] = useState(undefined);
const [totalFindings, setTotalFindings] = useState(undefined);
@@ -76,51 +65,44 @@ export const Summary: React.FC = ({
[onGroupByChange]
);
- const generateVisualizationSpec = useCallback(
- (summaryData, groupBy) => {
- const chartTimeUnits = getChartTimeUnit(startTime, endTime);
- return getOverviewVisualizationSpec(summaryData, groupBy, {
- timeUnit: chartTimeUnits.timeUnit,
- dateFormat: chartTimeUnits.dateFormat,
- domain: getDomainRange([startTime, endTime], chartTimeUnits.timeUnit.unit),
- });
- },
- [startTime, endTime]
- );
-
useEffect(() => {
- const summaryData: SummaryData[] = [];
+ const alertsVisData: any[] = [];
let activeAlerts = 0;
alerts.forEach((alert) => {
if (!alert.acknowledged) {
activeAlerts++;
}
- summaryData.push({
- time: getTimeWithMinPrecision(alert.time),
+ alertsVisData.push({
+ time: alert.time,
alert: 1,
finding: 0,
- logType: getLogTypeLabel(alert.logType),
+ logType: alert.logType,
});
});
+ const findingsVisData: any[] = [];
findings.forEach((finding) => {
- summaryData.push({
- time: getTimeWithMinPrecision(finding.time),
+ findingsVisData.push({
+ time: finding.time,
alert: 0,
finding: 1,
- logType: getLogTypeLabel(finding.logType),
+ logType: finding.logType,
});
});
setActiveAlerts(activeAlerts);
setTotalFindings(findings.length);
- setSummaryData(summaryData);
+ setAlertsVisData(alertsVisData);
+ setFindingsVisData(findingsVisData);
}, [alerts, findings]);
useEffect(() => {
- renderVisualization(generateVisualizationSpec(summaryData, groupBy), 'summary-view');
- }, [summaryData, groupBy]);
+ createBarAndLineChartWrapper(alertsVisData, findingsVisData, groupBy, SUMMARY_VIEW_CHART, {
+ startTime,
+ endTime,
+ });
+ }, [alertsVisData, findingsVisData, groupBy]);
const createStatComponent = useCallback(
(description: string, urlData: { url: string; color: EuiLinkColor }, stat?: number) => (
@@ -197,7 +179,9 @@ export const Summary: React.FC = ({
}
/>
) : (
-
+
+
+
)}
diff --git a/public/pages/Overview/components/Widgets/TopRulesWidget.tsx b/public/pages/Overview/components/Widgets/TopRulesWidget.tsx
index b6ef0a512..45de884e2 100644
--- a/public/pages/Overview/components/Widgets/TopRulesWidget.tsx
+++ b/public/pages/Overview/components/Widgets/TopRulesWidget.tsx
@@ -3,12 +3,11 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import { getEuiEmptyPrompt, renderVisualization } from '../../../../utils/helpers';
+import { getEuiEmptyPrompt } from '../../../../utils/helpers';
import React, { useEffect } from 'react';
import { WidgetContainer } from './WidgetContainer';
-import { getTopRulesVisualizationSpec } from '../../utils/helpers';
-import { ChartContainer } from '../../../../components/Charts/ChartContainer';
import { OverviewFindingItem } from '../../../../../types';
+import { createDoughnutChartWrapper } from '../../../../utils/chartUtils';
export interface TopRulesWidgetProps {
findings: OverviewFindingItem[];
@@ -17,6 +16,8 @@ export interface TopRulesWidgetProps {
type RulesCount = { [ruleName: string]: number };
+export const TOP_RULES_VIEW_CHART = 'top-rules-view';
+
export const TopRulesWidget: React.FC = ({ findings, loading = false }) => {
useEffect(() => {
const rulesCount: RulesCount = {};
@@ -29,7 +30,7 @@ export const TopRulesWidget: React.FC = ({ findings, loadin
ruleName,
count: rulesCount[ruleName],
}));
- renderVisualization(getTopRulesVisualizationSpec(visualizationData), 'top-rules-view');
+ createDoughnutChartWrapper(visualizationData, TOP_RULES_VIEW_CHART);
}
}, [findings]);
@@ -38,7 +39,9 @@ export const TopRulesWidget: React.FC = ({ findings, loadin
{findings.length === 0 ? (
getEuiEmptyPrompt('No findings with detection rules.')
) : (
-
+
+
+
)}
);
diff --git a/public/pages/Overview/containers/Overview/Overview.tsx b/public/pages/Overview/containers/Overview/Overview.tsx
index cd98ec273..0a4782303 100644
--- a/public/pages/Overview/containers/Overview/Overview.tsx
+++ b/public/pages/Overview/containers/Overview/Overview.tsx
@@ -9,9 +9,6 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiPopover,
- EuiSuperDatePicker,
- EuiTitle,
- EuiSpacer,
EuiSmallButton,
EuiCard,
EuiPanel,
diff --git a/public/pages/Overview/utils/__snapshots__/helper.test.ts.snap b/public/pages/Overview/utils/__snapshots__/helper.test.ts.snap
deleted file mode 100644
index 36e2531b8..000000000
--- a/public/pages/Overview/utils/__snapshots__/helper.test.ts.snap
+++ /dev/null
@@ -1,476 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`helper utilities spec tests getAlertsVisualizationSpec function - snapshot test: should match alerts spec 1`] = `
-Object {
- "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
- "config": Object {
- "legend": Object {
- "labelFont": "\\"Inter UI\\", -apple-system, BlinkMacSystemFont, \\"Segoe UI\\", Helvetica, Arial, sans-serif, \\"Apple Color Emoji\\", \\"Segoe UI Emoji\\", \\"Segoe UI Symbol\\"",
- "labelFontSize": 14,
- "rowPadding": 6,
- "titleFontSize": 14,
- "titleFontWeight": 600,
- "titleLineHeight": 21,
- "titlePadding": 10,
- },
- "view": Object {
- "stroke": "transparent",
- },
- },
- "data": Object {
- "values": Array [],
- },
- "description": "Alerts data overview",
- "layer": Array [
- Object {
- "encoding": Object {
- "color": Object {
- "field": "",
- "scale": Object {
- "domain": Array [
- "5 (Lowest)",
- "4 (Low)",
- "3 (Medium)",
- "2 (High)",
- "1 (Highest)",
- ],
- "range": Array [
- "#209280",
- "#54b399",
- "#d6bf57",
- "#e7664c",
- "#cc5642",
- ],
- },
- "title": "Alert severity",
- },
- "opacity": Object {
- "condition": Object {
- "selection": "series",
- "value": 1,
- },
- "value": 0.2,
- },
- "tooltip": Array [
- Object {
- "aggregate": "sum",
- "axis": Object {
- "grid": true,
- },
- "field": "alert",
- "title": "Alerts",
- "type": "quantitative",
- },
- Object {
- "field": "time",
- "format": "%Y-%m-%d %H:%M:%S",
- "timeUnit": Object {
- "step": 1,
- "unit": "yearmonthdatehoursminutes",
- },
- "title": "Time",
- "type": "temporal",
- },
- Object {
- "field": "",
- "title": "Alert severity",
- },
- ],
- "x": Object {
- "axis": Object {
- "format": "%Y-%m-%d %H:%M",
- "grid": false,
- },
- "field": "time",
- "scale": Object {
- "domain": undefined,
- },
- "timeUnit": Object {
- "step": 1,
- "unit": "yearmonthdatehoursminutes",
- },
- "title": "",
- "type": "temporal",
- },
- "y": Object {
- "aggregate": "sum",
- "axis": Object {
- "grid": true,
- },
- "field": "alert",
- "title": "Count",
- "type": "quantitative",
- },
- },
- "mark": Object {
- "clip": true,
- "type": "bar",
- },
- "selection": Object {
- "series": Object {
- "bind": "legend",
- "encodings": Array [
- "color",
- ],
- "on": "click",
- "type": "multi",
- },
- },
- },
- ],
-}
-`;
-
-exports[`helper utilities spec tests getFindingsVisualizationSpec function - snapshot test: should match findings spec 1`] = `
-Object {
- "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
- "config": Object {
- "legend": Object {
- "labelFont": "\\"Inter UI\\", -apple-system, BlinkMacSystemFont, \\"Segoe UI\\", Helvetica, Arial, sans-serif, \\"Apple Color Emoji\\", \\"Segoe UI Emoji\\", \\"Segoe UI Symbol\\"",
- "labelFontSize": 14,
- "rowPadding": 6,
- "titleFontSize": 14,
- "titleFontWeight": 600,
- "titleLineHeight": 21,
- "titlePadding": 10,
- },
- "view": Object {
- "stroke": "transparent",
- },
- },
- "data": Object {
- "values": Array [],
- },
- "description": "Findings data overview",
- "layer": Array [
- Object {
- "encoding": Object {
- "color": Object {
- "field": "",
- "scale": Object {
- "domain": Array [
- "info",
- "low",
- "medium",
- "high",
- "critical",
- ],
- "range": Array [
- "#209280",
- "#54b399",
- "#d6bf57",
- "#e7664c",
- "#cc5642",
- ],
- },
- "title": "Rule severity",
- },
- "opacity": Object {
- "condition": Object {
- "selection": "series",
- "value": 1,
- },
- "value": 0.2,
- },
- "tooltip": Array [
- Object {
- "aggregate": "sum",
- "axis": Object {
- "grid": true,
- },
- "field": "finding",
- "title": "Findings",
- "type": "quantitative",
- },
- Object {
- "field": "time",
- "format": "%Y-%m-%d %H:%M:%S",
- "timeUnit": Object {
- "step": 1,
- "unit": "yearmonthdatehoursminutes",
- },
- "title": "Time",
- "type": "temporal",
- },
- Object {
- "field": "",
- "title": "Rule severity",
- },
- ],
- "x": Object {
- "axis": Object {
- "format": "%Y-%m-%d %H:%M",
- "grid": false,
- },
- "field": "time",
- "scale": Object {
- "domain": undefined,
- },
- "timeUnit": Object {
- "step": 1,
- "unit": "yearmonthdatehoursminutes",
- },
- "title": "",
- "type": "temporal",
- },
- "y": Object {
- "aggregate": "sum",
- "axis": Object {
- "grid": true,
- },
- "field": "finding",
- "title": "Count",
- "type": "quantitative",
- },
- },
- "mark": Object {
- "clip": true,
- "type": "bar",
- },
- "selection": Object {
- "series": Object {
- "bind": "legend",
- "encodings": Array [
- "color",
- ],
- "on": "click",
- "type": "multi",
- },
- },
- },
- ],
-}
-`;
-
-exports[`helper utilities spec tests getOverviewVisualizationSpec function - snapshot test: should match overview spec 1`] = `
-Object {
- "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
- "config": Object {
- "legend": Object {
- "labelFont": "\\"Inter UI\\", -apple-system, BlinkMacSystemFont, \\"Segoe UI\\", Helvetica, Arial, sans-serif, \\"Apple Color Emoji\\", \\"Segoe UI Emoji\\", \\"Segoe UI Symbol\\"",
- "labelFontSize": 14,
- "rowPadding": 6,
- "titleFontSize": 14,
- "titleFontWeight": 600,
- "titleLineHeight": 21,
- "titlePadding": 10,
- },
- "view": Object {
- "stroke": "transparent",
- },
- },
- "data": Object {
- "values": Array [],
- },
- "description": "Plot showing average data with raw values in the background.",
- "layer": Array [
- Object {
- "encoding": Object {
- "color": Object {
- "field": "fieldType",
- "legend": Object {
- "values": Array [
- "Active alerts",
- "Findings",
- ],
- },
- "scale": Object {
- "domain": Array [
- "Active alerts",
- "Findings",
- ],
- "range": Array [
- "#BD271E",
- "#6092C0",
- ],
- },
- "title": "",
- },
- "tooltip": Array [
- Object {
- "aggregate": "sum",
- "axis": Object {
- "grid": true,
- },
- "field": "finding",
- "title": "Findings",
- "type": "quantitative",
- },
- Object {
- "field": "time",
- "format": "%Y-%m-%d %H:%M:%S",
- "timeUnit": Object {
- "step": 1,
- "unit": "yearmonthdatehoursminutes",
- },
- "title": "Time",
- "type": "temporal",
- },
- ],
- "x": Object {
- "axis": Object {
- "format": "%Y-%m-%d %H:%M",
- "grid": false,
- },
- "field": "time",
- "scale": Object {
- "domain": undefined,
- },
- "timeUnit": Object {
- "step": 1,
- "unit": "yearmonthdatehoursminutes",
- },
- "title": "",
- "type": "temporal",
- },
- "y": Object {
- "aggregate": "sum",
- "axis": Object {
- "grid": true,
- },
- "field": "finding",
- "scale": Object {
- "domain": Array [
- 0,
- 0.1,
- ],
- },
- "title": "Count",
- "type": "quantitative",
- },
- },
- "mark": Object {
- "clip": true,
- "type": "bar",
- },
- "transform": Array [
- Object {
- "as": "fieldType",
- "calculate": "datum.alert == 1 ? 'Active alerts' : 'Findings'",
- },
- ],
- },
- Object {
- "encoding": Object {
- "tooltip": Array [
- Object {
- "aggregate": "sum",
- "axis": Object {
- "grid": true,
- },
- "field": "alert",
- "title": "Alerts",
- "type": "quantitative",
- },
- Object {
- "field": "time",
- "format": "%Y-%m-%d %H:%M:%S",
- "timeUnit": Object {
- "step": 1,
- "unit": "yearmonthdatehoursminutes",
- },
- "title": "Time",
- "type": "temporal",
- },
- ],
- "x": Object {
- "axis": Object {
- "format": "%Y-%m-%d %H:%M",
- "grid": false,
- },
- "band": 0.5,
- "field": "time",
- "scale": Object {
- "domain": undefined,
- },
- "timeUnit": Object {
- "step": 1,
- "unit": "yearmonthdatehoursminutes",
- },
- "title": "",
- "type": "temporal",
- },
- "y": Object {
- "aggregate": "sum",
- "axis": Object {
- "grid": true,
- },
- "field": "alert",
- "title": "Count",
- "type": "quantitative",
- },
- },
- "mark": Object {
- "clip": true,
- "color": "#BD271E",
- "interpolate": "monotone",
- "point": Object {
- "color": "#BD271E",
- "fill": "white",
- "filled": false,
- "size": 50,
- },
- "type": "line",
- },
- },
- ],
-}
-`;
-
-exports[`helper utilities spec tests getVisualizationSpec function - snapshot test: should match visualization spec 1`] = `
-Object {
- "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
- "config": Object {
- "legend": Object {
- "labelFont": "\\"Inter UI\\", -apple-system, BlinkMacSystemFont, \\"Segoe UI\\", Helvetica, Arial, sans-serif, \\"Apple Color Emoji\\", \\"Segoe UI Emoji\\", \\"Segoe UI Symbol\\"",
- "labelFontSize": 14,
- "rowPadding": 6,
- "titleFontSize": 14,
- "titleFontWeight": 600,
- "titleLineHeight": 21,
- "titlePadding": 10,
- },
- "view": Object {
- "stroke": "transparent",
- },
- },
- "data": Object {
- "values": Array [
- Object {
- "xField": 1,
- "yField": 1,
- },
- ],
- },
- "description": "Visualization",
- "layer": Array [
- Object {
- "encoding": Object {
- "opacity": Object {
- "condition": Object {
- "selection": "series",
- "value": 1,
- },
- "value": 0.2,
- },
- },
- "selection": Object {
- "series": Object {
- "bind": "legend",
- "encodings": Array [
- "color",
- ],
- "on": "click",
- "type": "multi",
- },
- },
- "x": Object {
- "field": "xField",
- },
- "y": Object {
- "field": "yField",
- },
- },
- ],
-}
-`;
diff --git a/public/pages/Overview/utils/helper.test.ts b/public/pages/Overview/utils/helper.test.ts
index 88ec7e93a..b0077e5e8 100644
--- a/public/pages/Overview/utils/helper.test.ts
+++ b/public/pages/Overview/utils/helper.test.ts
@@ -181,34 +181,6 @@ describe('helper utilities spec', () => {
});
});
- describe('tests getVisualizationSpec function', () => {
- const result = getVisualizationSpec(description, data, [layer]);
- it(' - snapshot test', () => {
- expect(result).toMatchSnapshot('should match visualization spec');
- });
- });
-
- describe('tests getOverviewVisualizationSpec function', () => {
- const result = getOverviewVisualizationSpec([], '', dateOpts);
- it(' - snapshot test', () => {
- expect(result).toMatchSnapshot('should match overview spec');
- });
- });
-
- describe('tests getFindingsVisualizationSpec function', () => {
- const result = getFindingsVisualizationSpec([], '', dateOpts);
- it(' - snapshot test', () => {
- expect(result).toMatchSnapshot('should match findings spec');
- });
- });
-
- describe('tests getAlertsVisualizationSpec function', () => {
- const result = getAlertsVisualizationSpec([], '', dateOpts);
- it(' - snapshot test', () => {
- expect(result).toMatchSnapshot('should match alerts spec');
- });
- });
-
describe('tests getTimeWithMinPrecision function', () => {
const result = getTimeWithMinPrecision('2022/12/01 01:01:01');
it(' - test should be with ms and seconds eq to 0', () => {
diff --git a/public/pages/Overview/utils/helpers.ts b/public/pages/Overview/utils/helpers.ts
index 92caba7ac..7daa8efd8 100644
--- a/public/pages/Overview/utils/helpers.ts
+++ b/public/pages/Overview/utils/helpers.ts
@@ -3,13 +3,9 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import { euiPaletteColorBlind, euiPaletteForStatus } from '@elastic/eui';
-import { TopLevelSpec } from 'vega-lite';
-import { SummaryData } from '../components/Widgets/Summary';
import dateMath from '@elastic/datemath';
import _ from 'lodash';
import { DEFAULT_DATE_RANGE } from '../../../utils/constants';
-import { severityOptions } from '../../Alerts/utils/constants';
import moment from 'moment';
import { euiThemeVars } from '@osd/ui-shared-deps/theme';
@@ -98,30 +94,6 @@ export const getTimeTooltip = (dateOpts: DateOpts) => ({
format: '%Y-%m-%d %H:%M:%S',
});
-export function getVisualizationSpec(description: string, data: any, layers: any[]): TopLevelSpec {
- return {
- config: {
- view: { stroke: 'transparent' },
- legend: {
- labelFontSize: 14,
- titleFontWeight: 600,
- titleLineHeight: 21,
- titleFontSize: 14,
- titlePadding: 10,
- rowPadding: 6,
- labelFont:
- '"Inter UI", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"',
- },
- },
- $schema: 'https://vega.github.io/schema/vega-lite/v5.json',
- description: description,
- data: {
- values: data,
- },
- layer: layers,
- };
-}
-
/**
* Recalculates vertical domain range to add a bit of space
* so that the topmost items in the chart are not clipped
@@ -141,279 +113,6 @@ export const getYDomainRange = (data: any[], timeUnit: string): number[] => {
return [0, domainMax.length + 0.1];
};
-export function getOverviewVisualizationSpec(
- visualizationData: SummaryData[],
- groupBy: string,
- dateOpts: DateOpts = {
- timeUnit: defaultTimeUnit,
- dateFormat: defaultDateFormat,
- domain: defaultScaleDomain,
- }
-): TopLevelSpec {
- const findingsEncoding: { [x: string]: any } = {
- x: getXAxis(dateOpts),
- y: getYAxis('finding', 'Count', true, {
- scale: {
- domain: getYDomainRange(visualizationData, dateOpts.timeUnit.unit),
- },
- }),
- tooltip: [getYAxis('finding', 'Findings'), getTimeTooltip(dateOpts)],
- color: {
- field: 'fieldType',
- title: '',
- legend: {
- values: ['Active alerts', 'Findings'],
- },
- scale: {
- domain: ['Active alerts', 'Findings'],
- range: [alertsDefaultColor, euiPaletteColorBlind()[1]],
- },
- },
- };
-
- let barLayer = {
- mark: {
- type: 'bar',
- clip: true,
- },
- transform: [{ calculate: "datum.alert == 1 ? 'Active alerts' : 'Findings'", as: 'fieldType' }],
- encoding: findingsEncoding,
- };
-
- if (groupBy === 'logType') {
- findingsEncoding['color'] = {
- field: 'logType',
- type: 'nominal',
- title: 'Log type',
- scale: {
- range: euiPaletteColorBlind(),
- },
- };
-
- findingsEncoding['tooltip'].push({
- field: groupBy,
- title: groupBy === 'logType' ? 'Log type' : 'Rule severity',
- });
-
- barLayer = addInteractiveLegends(barLayer);
- }
-
- return getVisualizationSpec(
- 'Plot showing average data with raw values in the background.',
- visualizationData,
- [
- barLayer,
- {
- mark: {
- type: 'line',
- clip: true,
- interpolate: 'monotone',
- color: alertsDefaultColor,
- point: {
- filled: false,
- fill: 'white',
- color: alertsDefaultColor,
- size: 50,
- },
- },
- encoding: {
- x: getXAxis(dateOpts, {
- band: 0.5,
- }),
- y: getYAxis('alert', 'Count'),
- tooltip: [getYAxis('alert', 'Alerts'), getTimeTooltip(dateOpts)],
- },
- },
- ]
- );
-}
-
-export function getFindingsVisualizationSpec(
- visualizationData: any[],
- groupBy: string,
- dateOpts: DateOpts = {
- timeUnit: defaultTimeUnit,
- dateFormat: defaultDateFormat,
- domain: defaultScaleDomain,
- }
-) {
- const severities = ['info', 'low', 'medium', 'high', 'critical'];
- const isGroupedByLogType = groupBy === 'logType';
- const logTitle = 'Log type';
- const severityTitle = 'Rule severity';
- const title = isGroupedByLogType ? logTitle : severityTitle;
- return getVisualizationSpec('Findings data overview', visualizationData, [
- addInteractiveLegends({
- mark: {
- type: 'bar',
- clip: true,
- },
- encoding: {
- tooltip: [
- getYAxis('finding', 'Findings'),
- getTimeTooltip(dateOpts),
- {
- field: groupBy,
- title: title,
- },
- ],
- x: getXAxis(dateOpts),
- y: getYAxis('finding', 'Count'),
- color: {
- field: groupBy,
- title: title,
- scale: {
- domain: isGroupedByLogType ? undefined : severities,
- range: groupBy === 'logType' ? euiPaletteColorBlind() : euiPaletteForStatus(5),
- },
- },
- },
- }),
- ]);
-}
-
-export function getThreatIntelFindingsVisualizationSpec(
- visualizationData: any[],
- groupBy: string,
- dateOpts: DateOpts = {
- timeUnit: defaultTimeUnit,
- dateFormat: defaultDateFormat,
- domain: defaultScaleDomain,
- }
-) {
- const indicatorTypeTitle = 'Indicator type';
-
- return getVisualizationSpec('Findings data overview', visualizationData, [
- addInteractiveLegends({
- mark: {
- type: 'bar',
- clip: true,
- },
- encoding: {
- tooltip: [
- getYAxis('finding', 'Findings'),
- getTimeTooltip(dateOpts),
- {
- field: groupBy,
- title: indicatorTypeTitle,
- },
- ],
- x: getXAxis(dateOpts),
- y: getYAxis('finding', 'Count'),
- color: {
- field: groupBy,
- title: indicatorTypeTitle,
- scale: {
- range: euiPaletteColorBlind(),
- },
- },
- },
- }),
- ]);
-}
-
-export function getAlertsVisualizationSpec(
- visualizationData: any[],
- groupBy: string,
- dateOpts: DateOpts = {
- timeUnit: defaultTimeUnit,
- dateFormat: defaultDateFormat,
- domain: defaultScaleDomain,
- }
-) {
- const isGroupedByStatus = groupBy === 'status';
- let severities = severityOptions.map((severity) => severity.text);
- severities.reverse().pop();
-
- let states = ['ACTIVE', 'ACKNOWLEDGED', 'COMPLETED', 'ERROR'];
- const statusColors = {
- ACTIVE: '#E7664C',
- ACKNOWLEDGED: '#6092C0',
- COMPLETED: '#54B399',
- ERROR: '#B9A888',
- };
-
- const statusTitle = 'Alert status';
- const severityTitle = 'Alert severity';
- const title = isGroupedByStatus ? statusTitle : severityTitle;
- return getVisualizationSpec('Alerts data overview', visualizationData, [
- addInteractiveLegends({
- mark: {
- type: 'bar',
- clip: true,
- },
- encoding: {
- tooltip: [
- getYAxis('alert', 'Alerts'),
- getTimeTooltip(dateOpts),
- {
- field: groupBy,
- title: title,
- },
- ],
- x: getXAxis(dateOpts),
- y: getYAxis('alert', 'Count'),
- color: {
- field: groupBy,
- title: title,
- scale: {
- domain: isGroupedByStatus ? states : severities,
- range: isGroupedByStatus ? Object.values(statusColors) : euiPaletteForStatus(5),
- },
- },
- },
- }),
- ]);
-}
-
-export function getTopRulesVisualizationSpec(visualizationData: any[]) {
- return getVisualizationSpec('Most frequent detection rules', visualizationData, [
- {
- mark: { type: 'arc', innerRadius: 90 },
- transform: [
- {
- joinaggregate: [
- {
- op: 'sum',
- field: 'count',
- as: 'total',
- },
- ],
- },
- {
- calculate: 'datum.count/datum.total',
- as: 'percentage',
- },
- ],
- encoding: {
- tooltip: [
- {
- field: 'percentage',
- title: 'Percentage',
- type: 'quantitative',
- format: '2.0%',
- },
- {
- field: 'ruleName',
- type: 'nominal',
- title: 'Rule',
- },
- getYAxis('count', 'Count'),
- ],
- theta: getYAxis('count', ''),
- color: {
- field: 'ruleName',
- type: 'nominal',
- title: 'Rule name',
- scale: {
- range: euiPaletteColorBlind(),
- },
- },
- },
- },
- ]);
-}
-
export function getTimeWithMinPrecision(time: number | string) {
const date = new Date(time);
date.setSeconds(0);
diff --git a/public/utils/chartUtils.tsx b/public/utils/chartUtils.tsx
new file mode 100644
index 000000000..e59550284
--- /dev/null
+++ b/public/utils/chartUtils.tsx
@@ -0,0 +1,558 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React from 'react';
+import { euiPaletteColorBlind, euiPaletteForStatus } from '@elastic/eui';
+import {
+ BarController,
+ BarElement,
+ CategoryScale,
+ Chart,
+ Legend,
+ LinearScale,
+ TimeScale,
+ Title,
+ Tooltip,
+ LineElement,
+ PointElement,
+ LineController,
+ ArcElement,
+ DoughnutController,
+} from 'chart.js';
+import datemath from '@elastic/datemath';
+import moment from 'moment';
+import 'chartjs-adapter-moment';
+import { DEFAULT_DATE_RANGE, FindingTabId } from './constants';
+import { alertsGroupByOptions } from '../pages/Alerts/containers/Alerts/Alerts';
+import { groupByOptionsByTabId } from '../pages/Findings/containers/Findings/Findings';
+import { summaryGroupByOptions } from '../pages/Overview/utils/constants';
+
+export const createBarChartWrapper = (
+ data: any[],
+ groupBy: string,
+ container: string = 'myBarChart',
+ dateTimeFilter = {
+ startTime: DEFAULT_DATE_RANGE.start,
+ endTime: DEFAULT_DATE_RANGE.end,
+ }
+) => {
+ try {
+ createBarChart(data, groupBy, container, dateTimeFilter);
+ } catch (e) {
+ console.error(`Error while compiling bar chart ${container}`, e);
+ return <>{/*TODO: hurneyt Error panel of some kind*/}>;
+ }
+};
+
+const createBarChart = (
+ data: any[],
+ groupBy: string,
+ container: string = 'myBarChart',
+ dateTimeFilter = {
+ startTime: DEFAULT_DATE_RANGE.start,
+ endTime: DEFAULT_DATE_RANGE.end,
+ }
+) => {
+ // Register the required components
+ Chart.register(
+ CategoryScale,
+ LinearScale,
+ BarController,
+ BarElement,
+ Title,
+ Tooltip,
+ Legend,
+ TimeScale
+ );
+
+ const groupByOption = getGroupByText(groupBy);
+
+ const defaultColorPalette = euiPaletteColorBlind();
+ const severityColorPalette = euiPaletteForStatus(5);
+
+ // Calculate the time difference in milliseconds
+ const start = datemath.parse(dateTimeFilter.startTime)!;
+ const end = datemath.parse(dateTimeFilter.endTime)!;
+ const diffInHours = end?.diff(start, 'hour') || 0;
+
+ // Determine the appropriate time unit and format based on the range
+ let timeUnit: 'hour' | 'day' | 'week' | 'month';
+ let displayFormat: string;
+ let tickLimit: number;
+ let stepSize: number;
+
+ if (diffInHours <= 24) {
+ // Last 24 hours - show hourly ticks
+ timeUnit = 'hour';
+ displayFormat = 'HH:mm';
+ tickLimit = 24;
+ stepSize = 1; // Show every hour
+ } else if (diffInHours <= 72) {
+ // Last 3 days - show every 3 hours
+ timeUnit = 'hour';
+ displayFormat = 'MMM D HH:mm';
+ tickLimit = 24;
+ stepSize = 3; // Show every 3 hours
+ } else if (diffInHours <= 168) {
+ // Last week - show daily ticks
+ timeUnit = 'day';
+ displayFormat = 'MMM D';
+ tickLimit = 7;
+ stepSize = 6; // Show every 6 hours
+ } else {
+ // More than a week - show weekly ticks
+ timeUnit = 'week';
+ displayFormat = 'MMM D';
+ tickLimit = 10;
+ stepSize = 12; // Show every 12 hours
+ }
+ // Destroy existing chart if it exists
+ const existingChart = Chart.getChart(container);
+ if (existingChart) {
+ existingChart.destroy();
+ }
+
+ // Group data by the selected field
+ const uniqueGroups = [...new Set(data.map((item) => (item as any)[groupBy]))];
+
+ // Group data by time periods
+ const timeLabels = [...new Set(data.map((item) => item.time))]
+ .sort((a, b) => a - b)
+ .map((time) => new Date(time).toLocaleString());
+
+ // Create datasets for each group
+ const datasets = uniqueGroups.map((group, idx) => {
+ const color = groupBy.includes('severity')
+ ? severityColorPalette[idx % severityColorPalette.length]
+ : defaultColorPalette[idx % defaultColorPalette.length];
+
+ const counts: Record = {};
+ return {
+ label: group,
+ data: data
+ .filter((item: any) => item[groupBy] === group)
+ .map((item) => {
+ counts[item.time] = (counts[item.time] || 0) + 1;
+ return {
+ x: moment(item.time).toDate(), // Convert to JavaScript Date object
+ y: counts[item.time],
+ };
+ }),
+ backgroundColor: color,
+ borderColor: color,
+ borderWidth: 1,
+ };
+ });
+
+ const ctx = document.getElementById(container) as HTMLCanvasElement;
+ if (!ctx) {
+ return;
+ }
+
+ new Chart(ctx, {
+ type: 'bar',
+ data: {
+ labels: timeLabels,
+ datasets: datasets,
+ },
+ options: {
+ responsive: true,
+ scales: {
+ x: {
+ type: 'time',
+ time: {
+ unit: timeUnit, // or 'hour', 'day', etc.
+ displayFormats: {
+ minute: 'HH:mm',
+ hour: 'HH:mm',
+ day: 'MMM d',
+ week: 'MMM d',
+ month: 'MMM yyyy',
+ quarter: 'MMM yyyy',
+ year: 'yyyy',
+ },
+ },
+ bounds: 'ticks',
+ offset: true,
+ ticks: {
+ source: 'auto',
+ autoSkip: true,
+ maxRotation: 45,
+ maxTicksLimit: tickLimit, // Adjust this to show more/fewer labels
+ stepSize: stepSize,
+ major: {
+ enabled: true,
+ },
+ callback: function (value) {
+ // Format the tick label
+ return moment(value).format(displayFormat);
+ },
+ },
+ min: start.valueOf(), // Add start time
+ max: end.valueOf(), // Add end time
+ stacked: true,
+ title: {
+ display: true,
+ text: 'Time',
+ },
+ },
+ y: {
+ stacked: true,
+ beginAtZero: true,
+ title: {
+ display: true,
+ text: 'Count',
+ },
+ ticks: {
+ stepSize: 1,
+ precision: 0, // This prevents decimal places
+ },
+ },
+ },
+ plugins: {
+ tooltip: {
+ callbacks: {
+ label: function (context: any) {
+ const label = context.dataset.label || '';
+ const value = context.parsed.y;
+ const timeLabel = context.label;
+ return `${groupByOption.text}: ${label}
+ Count: ${value}
+ Time: ${timeLabel}`;
+ },
+ },
+ },
+ legend: {
+ position: 'top',
+ },
+ title: {
+ display: true,
+ text: `Grouped by ${groupByOption.text}`,
+ },
+ },
+ },
+ });
+};
+
+export const createBarAndLineChartWrapper = (
+ alertData: any[],
+ findingData: any[],
+ groupBy: string,
+ container: string = 'myBarAndLineChart',
+ dateTimeFilter = {
+ startTime: DEFAULT_DATE_RANGE.start,
+ endTime: DEFAULT_DATE_RANGE.end,
+ }
+) => {
+ try {
+ createBarAndLineChart(alertData, findingData, groupBy, container, dateTimeFilter);
+ } catch (e) {
+ console.error(`Error while compiling bar/line chart ${container}`, e);
+ return <>{/*TODO: hurneyt Error panel of some kind*/}>;
+ }
+};
+
+const createBarAndLineChart = (
+ alertData: any[],
+ findingData: any[],
+ groupBy: string,
+ container: string = 'myChart',
+ dateTimeFilter = {
+ startTime: DEFAULT_DATE_RANGE.start,
+ endTime: DEFAULT_DATE_RANGE.end,
+ }
+) => {
+ // Register the required components
+ Chart.register(
+ CategoryScale,
+ LinearScale,
+ BarController,
+ BarElement,
+ Title,
+ Tooltip,
+ Legend,
+ TimeScale,
+ LineElement,
+ PointElement,
+ LineController
+ );
+
+ const groupByOption = getGroupByText(groupBy);
+
+ const defaultColorPalette = euiPaletteColorBlind();
+ const severityColorPalette = euiPaletteForStatus(5);
+
+ // Calculate the time difference in milliseconds
+ const start = datemath.parse(dateTimeFilter.startTime)!;
+ const end = datemath.parse(dateTimeFilter.endTime)!;
+ const diffInHours = end?.diff(start, 'hour') || 0;
+
+ // Determine the appropriate time unit and format based on the range
+ let timeUnit: 'hour' | 'day' | 'week' | 'month';
+ let displayFormat: string;
+ let tickLimit: number;
+ let stepSize: number;
+
+ if (diffInHours <= 24) {
+ // Last 24 hours - show hourly ticks
+ timeUnit = 'hour';
+ displayFormat = 'HH:mm';
+ tickLimit = 24;
+ stepSize = 1; // Show every hour
+ } else if (diffInHours <= 72) {
+ // Last 3 days - show every 3 hours
+ timeUnit = 'hour';
+ displayFormat = 'MMM D HH:mm';
+ tickLimit = 24;
+ stepSize = 3; // Show every 3 hours
+ } else if (diffInHours <= 168) {
+ // Last week - show daily ticks
+ timeUnit = 'day';
+ displayFormat = 'MMM D';
+ tickLimit = 7;
+ stepSize = 6; // Show every 6 hours
+ } else {
+ // More than a week - show weekly ticks
+ timeUnit = 'week';
+ displayFormat = 'MMM D';
+ tickLimit = 10;
+ stepSize = 12; // Show every 12 hours
+ }
+ // Destroy existing chart if it exists
+ const existingChart = Chart.getChart(container);
+ if (existingChart) {
+ existingChart.destroy();
+ }
+
+ // Group data by the selected field
+ const uniqueGroups =
+ groupBy === 'logType'
+ ? [...new Set(findingData.map((item) => (item as any)[groupBy]))]
+ : ['All findings'];
+
+ // Group data by time periods
+ const timeLabels = [...new Set(findingData.map((item) => item.time))]
+ .sort((a, b) => a - b)
+ .map((time) => new Date(time).toLocaleString());
+
+ // Create datasets for each group
+ const datasets = uniqueGroups.map((group, idx) => {
+ const color = groupBy.includes('severity')
+ ? severityColorPalette[idx % severityColorPalette.length]
+ : defaultColorPalette[idx % defaultColorPalette.length];
+
+ const counts: Record = {};
+ return {
+ type: 'bar',
+ order: 2,
+ label: group,
+ data: findingData
+ // Overview page only supports grouping by logType, otherwise all findings should be mapped
+ .filter((item: any) => groupBy !== 'logType' || item[groupBy] === group)
+ .map((item) => {
+ counts[item.time] = (counts[item.time] || 0) + 1;
+ return {
+ x: moment(item.time).toDate(), // Convert to JavaScript Date object
+ y: counts[item.time],
+ };
+ }),
+ backgroundColor: color,
+ borderColor: color,
+ borderWidth: 1,
+ };
+ });
+
+ const alertCounts: Record = {};
+ alertData.forEach((item) => {
+ alertCounts[item.time] = (alertCounts[item.time] || 0) + 1;
+ });
+ const transData = Object.entries(alertCounts).map(([time, count]) => {
+ return {
+ x: moment(time).toDate(), // Convert to JavaScript Date object
+ y: count,
+ };
+ });
+ console.info(`hurneyt transData =`, transData);
+ const alertsDataset = {
+ type: 'line',
+ order: 1,
+ label: 'Alerts',
+ data: transData,
+ backgroundColor: 'warning',
+ borderColor: 'warning',
+ borderWidth: 1,
+ };
+
+ const ctx = document.getElementById(container) as HTMLCanvasElement;
+ if (!ctx) {
+ return;
+ }
+
+ new Chart(ctx, {
+ type: 'bar',
+ data: {
+ labels: timeLabels,
+ datasets: [...datasets, alertsDataset],
+ },
+ options: {
+ responsive: true,
+ scales: {
+ x: {
+ type: 'time',
+ time: {
+ unit: timeUnit, // or 'hour', 'day', etc.
+ displayFormats: {
+ minute: 'HH:mm',
+ hour: 'HH:mm',
+ day: 'MMM d',
+ week: 'MMM d',
+ month: 'MMM yyyy',
+ quarter: 'MMM yyyy',
+ year: 'yyyy',
+ },
+ },
+ bounds: 'ticks',
+ offset: true,
+ ticks: {
+ source: 'auto',
+ autoSkip: true,
+ maxRotation: 45,
+ maxTicksLimit: tickLimit, // Adjust this to show more/fewer labels
+ stepSize: stepSize,
+ major: {
+ enabled: true,
+ },
+ callback: function (value) {
+ // Format the tick label
+ return moment(value).format(displayFormat);
+ },
+ },
+ min: start.valueOf(), // Add start time
+ max: end.valueOf(), // Add end time
+ stacked: true,
+ title: {
+ display: true,
+ text: 'Time',
+ },
+ },
+ y: {
+ stacked: true,
+ beginAtZero: true,
+ title: {
+ display: true,
+ text: 'Count',
+ },
+ ticks: {
+ stepSize: 1,
+ precision: 0, // This prevents decimal places
+ },
+ },
+ },
+ plugins: {
+ tooltip: {
+ callbacks: {
+ label: function (context: any) {
+ const label = context.dataset.label || '';
+ const value = context.parsed.y;
+ const timeLabel = context.label;
+ return `${groupByOption.text}: ${label}
+ Count: ${value}
+ Time: ${timeLabel}`;
+ },
+ },
+ },
+ legend: {
+ position: 'top',
+ },
+ title: {
+ display: true,
+ text: `Grouped by ${groupByOption.text}`,
+ },
+ },
+ },
+ });
+};
+
+export const createDoughnutChartWrapper = (
+ visData: {
+ ruleName: string;
+ count: number;
+ }[] = [],
+ container: string = 'myDoughnutChart'
+) => {
+ try {
+ createDoughnutChart(visData, container);
+ } catch (e) {
+ console.error(`Error while compiling doughnut chart ${container}`, e);
+ return <>{/*TODO: hurneyt Error panel of some kind*/}>;
+ }
+};
+
+const createDoughnutChart = (
+ visData: {
+ ruleName: string;
+ count: number;
+ }[] = [],
+ container: string = 'myDoughnutChart'
+) => {
+ // Register the required components
+ Chart.register(
+ DoughnutController, // for doughnut charts
+ ArcElement, // for doughnut/pie charts
+ Tooltip,
+ Legend
+ );
+
+ const ctx = document.getElementById(container) as HTMLCanvasElement;
+ if (!ctx) {
+ return;
+ }
+
+ const labels: string[] = [];
+ const counts: number[] = [];
+ visData.forEach((item) => {
+ labels.push(item.ruleName);
+ counts.push(item.count);
+ });
+ new Chart(ctx, {
+ type: 'doughnut',
+ data: {
+ labels: labels,
+ datasets: [
+ {
+ data: counts,
+ backgroundColor: euiPaletteColorBlind(labels.length),
+ },
+ ],
+ },
+ options: {
+ responsive: true,
+ plugins: {
+ legend: {
+ position: 'top',
+ },
+ tooltip: {
+ callbacks: {
+ label: function (context) {
+ const label = context.label || '';
+ const value = context.parsed;
+ return `${label}: ${value}`;
+ },
+ },
+ },
+ },
+ },
+ });
+};
+
+const getGroupByText = (groupByValue: string) => {
+ const allOptions = [
+ ...alertsGroupByOptions,
+ ...groupByOptionsByTabId[FindingTabId.DetectionRules],
+ ...groupByOptionsByTabId[FindingTabId.ThreatIntel],
+ ...summaryGroupByOptions,
+ ];
+ return allOptions.filter((item) => item.value === groupByValue)[0] || { text: '-', value: '-' };
+};
diff --git a/public/utils/helpers.tsx b/public/utils/helpers.tsx
index eed5c5ad7..d4009bb3a 100644
--- a/public/utils/helpers.tsx
+++ b/public/utils/helpers.tsx
@@ -58,10 +58,6 @@ import { LogCategoryOptionView } from '../components/Utility/LogCategoryOption';
import { getLogTypeLabel } from '../pages/LogTypes/utils/helpers';
import { euiThemeVars } from '@osd/ui-shared-deps/theme';
import dateMath from '@elastic/datemath';
-import { parse, View } from 'vega/build-es5/vega.js';
-import { compile } from 'vega-lite';
-import { Handler } from 'vega-tooltip';
-import { expressionInterpreter as vegaExpressionInterpreter } from 'vega-interpreter/build/vega-interpreter';
import {
getBreadCrumbsSetter,
getBrowserServices,
@@ -221,80 +217,6 @@ export function getUpdatedEnabledRuleIds(
return newEnabledIds;
}
-export async function renderVisualization(spec: any, containerId: string) {
- let view;
-
- try {
- setDefaultColors(spec);
- renderVegaSpec(compile({ ...spec, width: 'container', height: 400 }).spec).catch((err: Error) =>
- console.error(err)
- );
- } catch (error) {
- console.error(error);
- }
-
- async function renderVegaSpec(spec: {}) {
- let chartColoredItems: any[] = [];
- const handler = new Handler({
- formatTooltip: (value, sanitize) => {
- let tooltipData = { ...value };
- let values = Object.entries(tooltipData);
- if (!values.length) return '';
- const tooltipItem = chartColoredItems.filter((groupItem: any) =>
- _.isEqual(groupItem.tooltip, tooltipData)
- );
- const color = tooltipItem.length
- ? tooltipItem[0].fill || tooltipItem[0].stroke
- : 'transparent';
-
- const firstItem = values.pop() || ['', ''];
-
- let rowData = '';
- values.forEach((item: any) => {
- rowData += `
-
- | ${sanitize(item[0])} |
- ${sanitize(item[1])} |
-
- `;
- });
-
- return `
-
- `;
- },
- });
- view = new View(parse(spec, undefined, { expr: vegaExpressionInterpreter } as any), {
- renderer: 'canvas', // renderer (canvas or svg)
- container: `#${containerId}`, // parent DOM container
- hover: true, // enable hover processing
- });
- view.tooltip(handler.call);
- return view.runAsync().then((view: any) => {
- const items = view.scenegraph().root.items[0].items || [];
- const groups = items.filter(
- (item: any) => item.name && item.name.match(/^(layer_).*(_marks)$/)
- );
- for (let item of groups) {
- chartColoredItems = chartColoredItems.concat(item.items);
- }
- });
- }
-}
-
export function createSelectComponent(
options: EuiSelectOption[],
value: string,
diff --git a/yarn.lock b/yarn.lock
index c142fab61..194d5a774 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -677,6 +677,11 @@
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
+"@kurkle/color@^0.3.0":
+ version "0.3.4"
+ resolved "https://registry.yarnpkg.com/@kurkle/color/-/color-0.3.4.tgz#4d4ff677e1609214fc71c580125ddddd86abcabf"
+ integrity sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==
+
"@napi-rs/wasm-runtime@^0.1.1":
version "0.1.1"
resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.1.1.tgz#ec090e2f46bee2ed0c8486dd9d97ddca836ae30e"
@@ -1767,6 +1772,18 @@ char-regex@^1.0.2:
resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf"
integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==
+chart.js@^4.4.8:
+ version "4.4.8"
+ resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-4.4.8.tgz#54645b638e9d585099bc16b892947b5e6cd2a552"
+ integrity sha512-IkGZlVpXP+83QpMm4uxEiGqSI7jFizwVtF3+n5Pc3k7sMO+tkd0qxh2OzLhenM0K80xtmAONWGBn082EiBQSDA==
+ dependencies:
+ "@kurkle/color" "^0.3.0"
+
+chartjs-adapter-moment@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/chartjs-adapter-moment/-/chartjs-adapter-moment-1.0.1.tgz#0f04c30d330b207c14bfb57dfaae9ce332f09102"
+ integrity sha512-Uz+nTX/GxocuqXpGylxK19YG4R3OSVf8326D+HwSTsNw1LgzyIGRo+Qujwro1wy6X+soNSnfj5t2vZ+r6EaDmA==
+
check-more-types@^2.24.0:
version "2.24.0"
resolved "https://registry.yarnpkg.com/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600"
From d3e350df043abc5767536dc85aed93fa795e6c55 Mon Sep 17 00:00:00 2001
From: AWSHurneyt
Date: Sat, 15 Mar 2025 18:42:43 -0700
Subject: [PATCH 2/3] Removed visualizations as chart.js dependency is also not
building into distribution.
Signed-off-by: AWSHurneyt
---
package.json | 3 +-
.../pages/Alerts/containers/Alerts/Alerts.tsx | 32 +-
.../Findings/containers/Findings/Findings.tsx | 49 +-
.../Overview/components/Widgets/Summary.tsx | 84 +-
.../components/Widgets/TopRulesWidget.tsx | 4 +-
.../Overview/containers/Overview/Overview.tsx | 2 +-
public/utils/chartUtils.tsx | 1130 +++++++++--------
yarn.lock | 9 +-
8 files changed, 687 insertions(+), 626 deletions(-)
diff --git a/package.json b/package.json
index 25512d41e..97362170d 100644
--- a/package.json
+++ b/package.json
@@ -78,7 +78,8 @@
"jest-environment-jsdom": "^27.5.1",
"lint-staged": "^10.2.0",
"string.prototype.replaceall": "1.0.7",
- "ts-loader": "^6.2.1"
+ "ts-loader": "^6.2.1",
+ "@types/chart.js": "^2.9.37"
},
"engines": {
"yarn": "^1.21.1"
diff --git a/public/pages/Alerts/containers/Alerts/Alerts.tsx b/public/pages/Alerts/containers/Alerts/Alerts.tsx
index ce3f154e5..1859e96c4 100644
--- a/public/pages/Alerts/containers/Alerts/Alerts.tsx
+++ b/public/pages/Alerts/containers/Alerts/Alerts.tsx
@@ -69,7 +69,7 @@ import { DurationRange } from '@elastic/eui/src/components/date_picker/types';
import { DataStore } from '../../../../store/DataStore';
import { ThreatIntelAlertsTable } from '../../components/ThreatIntelAlertsTable/ThreatIntelAlertsTable';
import { PageHeader } from '../../../../components/PageHeader/PageHeader';
-import { createBarChartWrapper } from '../../../../utils/chartUtils';
+// import { createBarChartWrapper } from '../../../../utils/chartUtils';
type FilterAlertParams =
| { alerts: AlertItem[]; timeField: 'last_notification_time' }
@@ -243,7 +243,7 @@ export class Alerts extends Component {
/>
),
});
- this.getChart(this.getVisData(filteredAlerts));
+ // this.getChart(this.getVisData(filteredAlerts));
};
filterCorrelationAlerts = () => {
@@ -267,7 +267,7 @@ export class Alerts extends Component {
/>
),
});
- this.getChart(this.getVisData(filteredCorrelationAlerts));
+ // this.getChart(this.getVisData(filteredCorrelationAlerts));
};
filterThreatIntelAlerts = () => {
@@ -291,19 +291,19 @@ export class Alerts extends Component {
/>
),
});
- this.getChart(this.getVisData(filteredAlerts));
+ // this.getChart(this.getVisData(filteredAlerts));
};
private renderVisAsPerTab() {
switch (this.state.selectedTabId) {
case AlertTabId.DetectionRules:
- this.getChart(this.getVisData(this.state.filteredAlerts));
+ // this.getChart(this.getVisData(this.state.filteredAlerts));
break;
case AlertTabId.Correlations:
- this.getChart(this.getVisData(this.state.filteredCorrelationAlerts));
+ // this.getChart(this.getVisData(this.state.filteredCorrelationAlerts));
break;
case AlertTabId.ThreatIntel:
- this.getChart(this.getVisData(this.state.filteredThreatIntelAlerts));
+ // this.getChart(this.getVisData(this.state.filteredThreatIntelAlerts));
break;
}
}
@@ -504,9 +504,9 @@ export class Alerts extends Component {
this.setState({ flyoutCorrelationData: alertItem ? { alertItem } : undefined });
}
- getChart(data: any) {
- createBarChartWrapper(data, this.state.groupBy, ALERTS_VIEW_CHART, this.props.dateTimeFilter);
- }
+ // getChart(data: any) {
+ // createBarChartWrapper(data, this.state.groupBy, ALERTS_VIEW_CHART, this.props.dateTimeFilter);
+ // }
getVisData(alerts: (AlertItem | CorrelationAlertTableItem | ThreatIntelAlert)[]) {
return alerts.map((alert) => {
@@ -999,8 +999,8 @@ export class Alerts extends Component {
content: (
<>
- {this.getAlertsGraph(alerts, loading)}
-
+ {/*{this.getAlertsGraph(alerts, loading)}*/}
+ {/**/}
{
content: (
<>
- {this.getAlertsGraph(alerts, loading)}
-
+ {/*{this.getAlertsGraph(alerts, loading)}*/}
+ {/**/}
{
content: (
<>
- {this.getAlertsGraph(alerts, loading)}
-
+ {/*{this.getAlertsGraph(alerts, loading)}*/}
+ {/**/}
{
this.state.selectedTabId !== prevState.selectedTabId
) {
this.onRefresh();
- } else if (this.shouldUpdateVisualization(prevState)) {
- const data = this.generateVisualizationData();
- this.createStackedBarChart(data.visData, data.groupBy);
}
+ // else if (this.shouldUpdateVisualization(prevState)) {
+ // const data = this.generateVisualizationData();
+ // this.createStackedBarChart(data.visData, data.groupBy);
+ // }
}
componentDidMount = async () => {
@@ -235,8 +236,8 @@ class Findings extends Component {
} else if (this.state.selectedTabId === FindingTabId.ThreatIntel) {
await this.getThreatIntelFindings();
}
- const data = this.generateVisualizationData();
- this.createStackedBarChart(data.visData, data.groupBy);
+ // const data = this.generateVisualizationData();
+ // this.createStackedBarChart(data.visData, data.groupBy);
};
setStateForTab(
@@ -489,20 +490,20 @@ class Findings extends Component {
});
};
- createStackedBarChart(
- data: (FindingVisualizationData | ThreatIntelFindingVisualizationData)[],
- groupBy: string
- ) {
- // Calculate the time difference in milliseconds
- const {
- dateTimeFilter = {
- startTime: DEFAULT_DATE_RANGE.start,
- endTime: DEFAULT_DATE_RANGE.end,
- },
- } = this.props;
-
- createBarChartWrapper(data, groupBy, FINDINGS_VIEW_CHART, dateTimeFilter);
- }
+ // createStackedBarChart(
+ // data: (FindingVisualizationData | ThreatIntelFindingVisualizationData)[],
+ // groupBy: string
+ // ) {
+ // // Calculate the time difference in milliseconds
+ // const {
+ // dateTimeFilter = {
+ // startTime: DEFAULT_DATE_RANGE.start,
+ // endTime: DEFAULT_DATE_RANGE.end,
+ // },
+ // } = this.props;
+ //
+ // createBarChartWrapper(data, groupBy, FINDINGS_VIEW_CHART, dateTimeFilter);
+ // }
render() {
const {
@@ -563,8 +564,8 @@ class Findings extends Component {
content: (
<>
- {this.getFindingsGraph(findings, loading)}
-
+ {/*{this.getFindingsGraph(findings, loading)}*/}
+ {/**/}
{
content: (
<>
- {this.getFindingsGraph(findings, loading)}
-
+ {/*{this.getFindingsGraph(findings, loading)}*/}
+ {/**/}
= ({
setFindingsVisData(findingsVisData);
}, [alerts, findings]);
- useEffect(() => {
- createBarAndLineChartWrapper(alertsVisData, findingsVisData, groupBy, SUMMARY_VIEW_CHART, {
- startTime,
- endTime,
- });
- }, [alertsVisData, findingsVisData, groupBy]);
+ // useEffect(() => {
+ // createBarAndLineChartWrapper(alertsVisData, findingsVisData, groupBy, SUMMARY_VIEW_CHART, {
+ // startTime,
+ // endTime,
+ // });
+ // }, [alertsVisData, findingsVisData, groupBy]);
const createStatComponent = useCallback(
(description: string, urlData: { url: string; color: EuiLinkColor }, stat?: number) => (
@@ -149,41 +149,41 @@ export const Summary: React.FC = ({
)}
)}
-
- {activeAlerts === 0 && totalFindings === 0 ? (
-
- No alerts and findings found
-
- }
- body={
- <>
-
-
- Adjust the time range to see more results or create a
- detector to generate findings.
-
-
-
- Create a detector
-
- >
- }
- />
- ) : (
-
-
-
- )}
-
+ {/**/}
+ {/* {activeAlerts === 0 && totalFindings === 0 ? (*/}
+ {/* */}
+ {/* No alerts and findings found
*/}
+ {/* */}
+ {/* }*/}
+ {/* body={*/}
+ {/* <>*/}
+ {/* */}
+ {/* */}
+ {/* Adjust the time range to see more results or create a
*/}
+ {/* detector to generate findings.*/}
+ {/* */}
+ {/*
*/}
+ {/* */}
+ {/* Create a detector*/}
+ {/* */}
+ {/* >*/}
+ {/* }*/}
+ {/* />*/}
+ {/* ) : (*/}
+ {/* */}
+ {/* */}
+ {/*
*/}
+ {/* )}*/}
+ {/**/}
);
diff --git a/public/pages/Overview/components/Widgets/TopRulesWidget.tsx b/public/pages/Overview/components/Widgets/TopRulesWidget.tsx
index 45de884e2..1b0407831 100644
--- a/public/pages/Overview/components/Widgets/TopRulesWidget.tsx
+++ b/public/pages/Overview/components/Widgets/TopRulesWidget.tsx
@@ -7,7 +7,7 @@ import { getEuiEmptyPrompt } from '../../../../utils/helpers';
import React, { useEffect } from 'react';
import { WidgetContainer } from './WidgetContainer';
import { OverviewFindingItem } from '../../../../../types';
-import { createDoughnutChartWrapper } from '../../../../utils/chartUtils';
+// import { createDoughnutChartWrapper } from '../../../../utils/chartUtils';
export interface TopRulesWidgetProps {
findings: OverviewFindingItem[];
@@ -30,7 +30,7 @@ export const TopRulesWidget: React.FC = ({ findings, loadin
ruleName,
count: rulesCount[ruleName],
}));
- createDoughnutChartWrapper(visualizationData, TOP_RULES_VIEW_CHART);
+ // createDoughnutChartWrapper(visualizationData, TOP_RULES_VIEW_CHART);
}
}, [findings]);
diff --git a/public/pages/Overview/containers/Overview/Overview.tsx b/public/pages/Overview/containers/Overview/Overview.tsx
index 0a4782303..ec1fc07e0 100644
--- a/public/pages/Overview/containers/Overview/Overview.tsx
+++ b/public/pages/Overview/containers/Overview/Overview.tsx
@@ -313,7 +313,7 @@ export const Overview: React.FC = (props) => {
-
+ {/**/}
{
- try {
- createBarChart(data, groupBy, container, dateTimeFilter);
- } catch (e) {
- console.error(`Error while compiling bar chart ${container}`, e);
- return <>{/*TODO: hurneyt Error panel of some kind*/}>;
- }
-};
-
-const createBarChart = (
- data: any[],
- groupBy: string,
- container: string = 'myBarChart',
- dateTimeFilter = {
- startTime: DEFAULT_DATE_RANGE.start,
- endTime: DEFAULT_DATE_RANGE.end,
- }
-) => {
- // Register the required components
- Chart.register(
- CategoryScale,
- LinearScale,
- BarController,
- BarElement,
- Title,
- Tooltip,
- Legend,
- TimeScale
- );
-
- const groupByOption = getGroupByText(groupBy);
-
- const defaultColorPalette = euiPaletteColorBlind();
- const severityColorPalette = euiPaletteForStatus(5);
-
- // Calculate the time difference in milliseconds
- const start = datemath.parse(dateTimeFilter.startTime)!;
- const end = datemath.parse(dateTimeFilter.endTime)!;
- const diffInHours = end?.diff(start, 'hour') || 0;
-
- // Determine the appropriate time unit and format based on the range
- let timeUnit: 'hour' | 'day' | 'week' | 'month';
- let displayFormat: string;
- let tickLimit: number;
- let stepSize: number;
-
- if (diffInHours <= 24) {
- // Last 24 hours - show hourly ticks
- timeUnit = 'hour';
- displayFormat = 'HH:mm';
- tickLimit = 24;
- stepSize = 1; // Show every hour
- } else if (diffInHours <= 72) {
- // Last 3 days - show every 3 hours
- timeUnit = 'hour';
- displayFormat = 'MMM D HH:mm';
- tickLimit = 24;
- stepSize = 3; // Show every 3 hours
- } else if (diffInHours <= 168) {
- // Last week - show daily ticks
- timeUnit = 'day';
- displayFormat = 'MMM D';
- tickLimit = 7;
- stepSize = 6; // Show every 6 hours
- } else {
- // More than a week - show weekly ticks
- timeUnit = 'week';
- displayFormat = 'MMM D';
- tickLimit = 10;
- stepSize = 12; // Show every 12 hours
- }
- // Destroy existing chart if it exists
- const existingChart = Chart.getChart(container);
- if (existingChart) {
- existingChart.destroy();
- }
-
- // Group data by the selected field
- const uniqueGroups = [...new Set(data.map((item) => (item as any)[groupBy]))];
-
- // Group data by time periods
- const timeLabels = [...new Set(data.map((item) => item.time))]
- .sort((a, b) => a - b)
- .map((time) => new Date(time).toLocaleString());
-
- // Create datasets for each group
- const datasets = uniqueGroups.map((group, idx) => {
- const color = groupBy.includes('severity')
- ? severityColorPalette[idx % severityColorPalette.length]
- : defaultColorPalette[idx % defaultColorPalette.length];
-
- const counts: Record = {};
- return {
- label: group,
- data: data
- .filter((item: any) => item[groupBy] === group)
- .map((item) => {
- counts[item.time] = (counts[item.time] || 0) + 1;
- return {
- x: moment(item.time).toDate(), // Convert to JavaScript Date object
- y: counts[item.time],
- };
- }),
- backgroundColor: color,
- borderColor: color,
- borderWidth: 1,
- };
- });
-
- const ctx = document.getElementById(container) as HTMLCanvasElement;
- if (!ctx) {
- return;
- }
-
- new Chart(ctx, {
- type: 'bar',
- data: {
- labels: timeLabels,
- datasets: datasets,
- },
- options: {
- responsive: true,
- scales: {
- x: {
- type: 'time',
- time: {
- unit: timeUnit, // or 'hour', 'day', etc.
- displayFormats: {
- minute: 'HH:mm',
- hour: 'HH:mm',
- day: 'MMM d',
- week: 'MMM d',
- month: 'MMM yyyy',
- quarter: 'MMM yyyy',
- year: 'yyyy',
- },
- },
- bounds: 'ticks',
- offset: true,
- ticks: {
- source: 'auto',
- autoSkip: true,
- maxRotation: 45,
- maxTicksLimit: tickLimit, // Adjust this to show more/fewer labels
- stepSize: stepSize,
- major: {
- enabled: true,
- },
- callback: function (value) {
- // Format the tick label
- return moment(value).format(displayFormat);
- },
- },
- min: start.valueOf(), // Add start time
- max: end.valueOf(), // Add end time
- stacked: true,
- title: {
- display: true,
- text: 'Time',
- },
- },
- y: {
- stacked: true,
- beginAtZero: true,
- title: {
- display: true,
- text: 'Count',
- },
- ticks: {
- stepSize: 1,
- precision: 0, // This prevents decimal places
- },
- },
- },
- plugins: {
- tooltip: {
- callbacks: {
- label: function (context: any) {
- const label = context.dataset.label || '';
- const value = context.parsed.y;
- const timeLabel = context.label;
- return `${groupByOption.text}: ${label}
- Count: ${value}
- Time: ${timeLabel}`;
- },
- },
- },
- legend: {
- position: 'top',
- },
- title: {
- display: true,
- text: `Grouped by ${groupByOption.text}`,
- },
- },
- },
- });
-};
-
-export const createBarAndLineChartWrapper = (
- alertData: any[],
- findingData: any[],
- groupBy: string,
- container: string = 'myBarAndLineChart',
- dateTimeFilter = {
- startTime: DEFAULT_DATE_RANGE.start,
- endTime: DEFAULT_DATE_RANGE.end,
- }
-) => {
- try {
- createBarAndLineChart(alertData, findingData, groupBy, container, dateTimeFilter);
- } catch (e) {
- console.error(`Error while compiling bar/line chart ${container}`, e);
- return <>{/*TODO: hurneyt Error panel of some kind*/}>;
- }
-};
-
-const createBarAndLineChart = (
- alertData: any[],
- findingData: any[],
- groupBy: string,
- container: string = 'myChart',
- dateTimeFilter = {
- startTime: DEFAULT_DATE_RANGE.start,
- endTime: DEFAULT_DATE_RANGE.end,
- }
-) => {
- // Register the required components
- Chart.register(
- CategoryScale,
- LinearScale,
- BarController,
- BarElement,
- Title,
- Tooltip,
- Legend,
- TimeScale,
- LineElement,
- PointElement,
- LineController
- );
-
- const groupByOption = getGroupByText(groupBy);
-
- const defaultColorPalette = euiPaletteColorBlind();
- const severityColorPalette = euiPaletteForStatus(5);
-
- // Calculate the time difference in milliseconds
- const start = datemath.parse(dateTimeFilter.startTime)!;
- const end = datemath.parse(dateTimeFilter.endTime)!;
- const diffInHours = end?.diff(start, 'hour') || 0;
-
- // Determine the appropriate time unit and format based on the range
- let timeUnit: 'hour' | 'day' | 'week' | 'month';
- let displayFormat: string;
- let tickLimit: number;
- let stepSize: number;
-
- if (diffInHours <= 24) {
- // Last 24 hours - show hourly ticks
- timeUnit = 'hour';
- displayFormat = 'HH:mm';
- tickLimit = 24;
- stepSize = 1; // Show every hour
- } else if (diffInHours <= 72) {
- // Last 3 days - show every 3 hours
- timeUnit = 'hour';
- displayFormat = 'MMM D HH:mm';
- tickLimit = 24;
- stepSize = 3; // Show every 3 hours
- } else if (diffInHours <= 168) {
- // Last week - show daily ticks
- timeUnit = 'day';
- displayFormat = 'MMM D';
- tickLimit = 7;
- stepSize = 6; // Show every 6 hours
- } else {
- // More than a week - show weekly ticks
- timeUnit = 'week';
- displayFormat = 'MMM D';
- tickLimit = 10;
- stepSize = 12; // Show every 12 hours
- }
- // Destroy existing chart if it exists
- const existingChart = Chart.getChart(container);
- if (existingChart) {
- existingChart.destroy();
- }
-
- // Group data by the selected field
- const uniqueGroups =
- groupBy === 'logType'
- ? [...new Set(findingData.map((item) => (item as any)[groupBy]))]
- : ['All findings'];
-
- // Group data by time periods
- const timeLabels = [...new Set(findingData.map((item) => item.time))]
- .sort((a, b) => a - b)
- .map((time) => new Date(time).toLocaleString());
-
- // Create datasets for each group
- const datasets = uniqueGroups.map((group, idx) => {
- const color = groupBy.includes('severity')
- ? severityColorPalette[idx % severityColorPalette.length]
- : defaultColorPalette[idx % defaultColorPalette.length];
-
- const counts: Record = {};
- return {
- type: 'bar',
- order: 2,
- label: group,
- data: findingData
- // Overview page only supports grouping by logType, otherwise all findings should be mapped
- .filter((item: any) => groupBy !== 'logType' || item[groupBy] === group)
- .map((item) => {
- counts[item.time] = (counts[item.time] || 0) + 1;
- return {
- x: moment(item.time).toDate(), // Convert to JavaScript Date object
- y: counts[item.time],
- };
- }),
- backgroundColor: color,
- borderColor: color,
- borderWidth: 1,
- };
- });
-
- const alertCounts: Record = {};
- alertData.forEach((item) => {
- alertCounts[item.time] = (alertCounts[item.time] || 0) + 1;
- });
- const transData = Object.entries(alertCounts).map(([time, count]) => {
- return {
- x: moment(time).toDate(), // Convert to JavaScript Date object
- y: count,
- };
- });
- console.info(`hurneyt transData =`, transData);
- const alertsDataset = {
- type: 'line',
- order: 1,
- label: 'Alerts',
- data: transData,
- backgroundColor: 'warning',
- borderColor: 'warning',
- borderWidth: 1,
- };
-
- const ctx = document.getElementById(container) as HTMLCanvasElement;
- if (!ctx) {
- return;
- }
-
- new Chart(ctx, {
- type: 'bar',
- data: {
- labels: timeLabels,
- datasets: [...datasets, alertsDataset],
- },
- options: {
- responsive: true,
- scales: {
- x: {
- type: 'time',
- time: {
- unit: timeUnit, // or 'hour', 'day', etc.
- displayFormats: {
- minute: 'HH:mm',
- hour: 'HH:mm',
- day: 'MMM d',
- week: 'MMM d',
- month: 'MMM yyyy',
- quarter: 'MMM yyyy',
- year: 'yyyy',
- },
- },
- bounds: 'ticks',
- offset: true,
- ticks: {
- source: 'auto',
- autoSkip: true,
- maxRotation: 45,
- maxTicksLimit: tickLimit, // Adjust this to show more/fewer labels
- stepSize: stepSize,
- major: {
- enabled: true,
- },
- callback: function (value) {
- // Format the tick label
- return moment(value).format(displayFormat);
- },
- },
- min: start.valueOf(), // Add start time
- max: end.valueOf(), // Add end time
- stacked: true,
- title: {
- display: true,
- text: 'Time',
- },
- },
- y: {
- stacked: true,
- beginAtZero: true,
- title: {
- display: true,
- text: 'Count',
- },
- ticks: {
- stepSize: 1,
- precision: 0, // This prevents decimal places
- },
- },
- },
- plugins: {
- tooltip: {
- callbacks: {
- label: function (context: any) {
- const label = context.dataset.label || '';
- const value = context.parsed.y;
- const timeLabel = context.label;
- return `${groupByOption.text}: ${label}
- Count: ${value}
- Time: ${timeLabel}`;
- },
- },
- },
- legend: {
- position: 'top',
- },
- title: {
- display: true,
- text: `Grouped by ${groupByOption.text}`,
- },
- },
- },
- });
-};
-
-export const createDoughnutChartWrapper = (
- visData: {
- ruleName: string;
- count: number;
- }[] = [],
- container: string = 'myDoughnutChart'
-) => {
- try {
- createDoughnutChart(visData, container);
- } catch (e) {
- console.error(`Error while compiling doughnut chart ${container}`, e);
- return <>{/*TODO: hurneyt Error panel of some kind*/}>;
- }
-};
-
-const createDoughnutChart = (
- visData: {
- ruleName: string;
- count: number;
- }[] = [],
- container: string = 'myDoughnutChart'
-) => {
- // Register the required components
- Chart.register(
- DoughnutController, // for doughnut charts
- ArcElement, // for doughnut/pie charts
- Tooltip,
- Legend
- );
-
- const ctx = document.getElementById(container) as HTMLCanvasElement;
- if (!ctx) {
- return;
- }
-
- const labels: string[] = [];
- const counts: number[] = [];
- visData.forEach((item) => {
- labels.push(item.ruleName);
- counts.push(item.count);
- });
- new Chart(ctx, {
- type: 'doughnut',
- data: {
- labels: labels,
- datasets: [
- {
- data: counts,
- backgroundColor: euiPaletteColorBlind(labels.length),
- },
- ],
- },
- options: {
- responsive: true,
- plugins: {
- legend: {
- position: 'top',
- },
- tooltip: {
- callbacks: {
- label: function (context) {
- const label = context.label || '';
- const value = context.parsed;
- return `${label}: ${value}`;
- },
- },
- },
- },
- },
- });
-};
+// import { getEuiEmptyPrompt } from "./helpers";
+// import {
+// BarController,
+// BarElement,
+// CategoryScale,
+// Chart,
+// Legend,
+// LinearScale,
+// TimeScale,
+// Title,
+// Tooltip,
+// LineElement,
+// PointElement,
+// LineController,
+// ArcElement,
+// DoughnutController,
+// } from 'chart.js';
+// import 'chartjs-adapter-moment';
+
+// /**
+// * This function is intended to be used to prevent the UI page failing to load when
+// * there is a webpack/build issues that makes chart.js unavailable.
+// *
+// * An empty UI panel will appear in place of the chart.
+// *
+// * @param callback
+// * @param container
+// */
+// const initializeChart = (container: string) => {
+// try {
+// // Try to access Chart.js - this will throw if exports is not defined
+// require('chart.js');
+// require('chartjs-adapter-moment');
+// } catch (e) {
+// console.error(`Chart initialization error for ${container}:`, e);
+// const chartContainer = document.getElementById(container)?.parentElement;
+// if (chartContainer) {
+// const errorMessage = getEuiEmptyPrompt('Unable to load chart visualization.');
+// chartContainer.innerHTML = `
+//
+// ${errorMessage}
+//
+// `;
+// }
+// }
+// };
+//
+// export const createBarChartWrapper = (
+// data: any[],
+// groupBy: string,
+// container: string = 'myBarChart',
+// dateTimeFilter = {
+// startTime: DEFAULT_DATE_RANGE.start,
+// endTime: DEFAULT_DATE_RANGE.end,
+// }
+// ) => {
+// initializeChart(container);
+// try {
+// createBarChart(data, groupBy, container, dateTimeFilter);
+// } catch (e) {
+// console.error(`Error while compiling bar chart ${container}`, e);
+// const chartContainer = document.getElementById(container)?.parentElement;
+// if (chartContainer) {
+// chartContainer.innerHTML = `
+//
+// Error while compiling bar chart ${container}
+//
+// `;
+// }
+// }
+// };
+//
+// const createBarChart = (
+// data: any[],
+// groupBy: string,
+// container: string = 'myBarChart',
+// dateTimeFilter = {
+// startTime: DEFAULT_DATE_RANGE.start,
+// endTime: DEFAULT_DATE_RANGE.end,
+// }
+// ) => {
+// // Register the required components
+// Chart.register(
+// CategoryScale,
+// LinearScale,
+// BarController,
+// BarElement,
+// Title,
+// Tooltip,
+// Legend,
+// TimeScale
+// );
+//
+// const groupByOption = getGroupByText(groupBy);
+//
+// const defaultColorPalette = euiPaletteColorBlind();
+// const severityColorPalette = euiPaletteForStatus(5);
+//
+// // Calculate the time difference in milliseconds
+// const start = datemath.parse(dateTimeFilter.startTime)!;
+// const end = datemath.parse(dateTimeFilter.endTime)!;
+// const diffInHours = end?.diff(start, 'hour') || 0;
+//
+// // Determine the appropriate time unit and format based on the range
+// let timeUnit: 'hour' | 'day' | 'week' | 'month';
+// let displayFormat: string;
+// let tickLimit: number;
+// let stepSize: number;
+//
+// if (diffInHours <= 24) {
+// // Last 24 hours - show hourly ticks
+// timeUnit = 'hour';
+// displayFormat = 'HH:mm';
+// tickLimit = 24;
+// stepSize = 1; // Show every hour
+// } else if (diffInHours <= 72) {
+// // Last 3 days - show every 3 hours
+// timeUnit = 'hour';
+// displayFormat = 'MMM D HH:mm';
+// tickLimit = 24;
+// stepSize = 3; // Show every 3 hours
+// } else if (diffInHours <= 168) {
+// // Last week - show daily ticks
+// timeUnit = 'day';
+// displayFormat = 'MMM D';
+// tickLimit = 7;
+// stepSize = 6; // Show every 6 hours
+// } else {
+// // More than a week - show weekly ticks
+// timeUnit = 'week';
+// displayFormat = 'MMM D';
+// tickLimit = 10;
+// stepSize = 12; // Show every 12 hours
+// }
+// // Destroy existing chart if it exists
+// const existingChart = Chart.getChart(container);
+// if (existingChart) {
+// existingChart.destroy();
+// }
+//
+// // Group data by the selected field
+// const uniqueGroups = [...new Set(data.map((item) => (item as any)[groupBy]))];
+//
+// // Group data by time periods
+// const timeLabels = [...new Set(data.map((item) => item.time))]
+// .sort((a, b) => a - b)
+// .map((time) => new Date(time).toLocaleString());
+//
+// // Create datasets for each group
+// const datasets = uniqueGroups.map((group, idx) => {
+// const color = groupBy.includes('severity')
+// ? severityColorPalette[idx % severityColorPalette.length]
+// : defaultColorPalette[idx % defaultColorPalette.length];
+//
+// const counts: Record = {};
+// return {
+// label: group,
+// data: data
+// .filter((item: any) => item[groupBy] === group)
+// .map((item) => {
+// counts[item.time] = (counts[item.time] || 0) + 1;
+// return {
+// x: moment(item.time).toDate(), // Convert to JavaScript Date object
+// y: counts[item.time],
+// };
+// }),
+// backgroundColor: color,
+// borderColor: color,
+// borderWidth: 1,
+// };
+// });
+//
+// const ctx = document.getElementById(container) as HTMLCanvasElement;
+// if (!ctx) {
+// return;
+// }
+//
+// new Chart(ctx, {
+// type: 'bar',
+// data: {
+// labels: timeLabels,
+// datasets: datasets,
+// },
+// options: {
+// responsive: true,
+// scales: {
+// x: {
+// type: 'time',
+// time: {
+// unit: timeUnit, // or 'hour', 'day', etc.
+// displayFormats: {
+// minute: 'HH:mm',
+// hour: 'HH:mm',
+// day: 'MMM d',
+// week: 'MMM d',
+// month: 'MMM yyyy',
+// quarter: 'MMM yyyy',
+// year: 'yyyy',
+// },
+// },
+// bounds: 'ticks',
+// offset: true,
+// ticks: {
+// source: 'auto',
+// autoSkip: true,
+// maxRotation: 45,
+// maxTicksLimit: tickLimit, // Adjust this to show more/fewer labels
+// stepSize: stepSize,
+// major: {
+// enabled: true,
+// },
+// callback: function (value) {
+// // Format the tick label
+// return moment(value).format(displayFormat);
+// },
+// },
+// min: start.valueOf(), // Add start time
+// max: end.valueOf(), // Add end time
+// stacked: true,
+// title: {
+// display: true,
+// text: 'Time',
+// },
+// },
+// y: {
+// stacked: true,
+// beginAtZero: true,
+// title: {
+// display: true,
+// text: 'Count',
+// },
+// ticks: {
+// stepSize: 1,
+// precision: 0, // This prevents decimal places
+// },
+// },
+// },
+// plugins: {
+// tooltip: {
+// callbacks: {
+// label: function (context: any) {
+// const label = context.dataset.label || '';
+// const value = context.parsed.y;
+// const timeLabel = context.label;
+// return `${groupByOption.text}: ${label}
+// Count: ${value}
+// Time: ${timeLabel}`;
+// },
+// },
+// },
+// legend: {
+// position: 'top',
+// },
+// title: {
+// display: true,
+// text: `Grouped by ${groupByOption.text}`,
+// },
+// },
+// },
+// });
+// };
+//
+// export const createBarAndLineChartWrapper = (
+// alertData: any[],
+// findingData: any[],
+// groupBy: string,
+// container: string = 'myBarAndLineChart',
+// dateTimeFilter = {
+// startTime: DEFAULT_DATE_RANGE.start,
+// endTime: DEFAULT_DATE_RANGE.end,
+// }
+// ) => {
+// initializeChart(container);
+// try {
+// createBarAndLineChart(alertData, findingData, groupBy, container, dateTimeFilter);
+// } catch (e) {
+// console.error(`Error while compiling bar/line chart ${container}`, e);
+// const chartContainer = document.getElementById(container)?.parentElement;
+// if (chartContainer) {
+// chartContainer.innerHTML = `
+//
+// Error while compiling bar/line chart ${container}
+//
+// `;
+// }
+// }
+// };
+//
+// const createBarAndLineChart = (
+// alertData: any[],
+// findingData: any[],
+// groupBy: string,
+// container: string = 'myChart',
+// dateTimeFilter = {
+// startTime: DEFAULT_DATE_RANGE.start,
+// endTime: DEFAULT_DATE_RANGE.end,
+// }
+// ) => {
+// // Register the required components
+// Chart.register(
+// CategoryScale,
+// LinearScale,
+// BarController,
+// BarElement,
+// Title,
+// Tooltip,
+// Legend,
+// TimeScale,
+// LineElement,
+// PointElement,
+// LineController
+// );
+//
+// const groupByOption = getGroupByText(groupBy);
+//
+// const defaultColorPalette = euiPaletteColorBlind();
+// const severityColorPalette = euiPaletteForStatus(5);
+//
+// // Calculate the time difference in milliseconds
+// const start = datemath.parse(dateTimeFilter.startTime)!;
+// const end = datemath.parse(dateTimeFilter.endTime)!;
+// const diffInHours = end?.diff(start, 'hour') || 0;
+//
+// // Determine the appropriate time unit and format based on the range
+// let timeUnit: 'hour' | 'day' | 'week' | 'month';
+// let displayFormat: string;
+// let tickLimit: number;
+// let stepSize: number;
+//
+// if (diffInHours <= 24) {
+// // Last 24 hours - show hourly ticks
+// timeUnit = 'hour';
+// displayFormat = 'HH:mm';
+// tickLimit = 24;
+// stepSize = 1; // Show every hour
+// } else if (diffInHours <= 72) {
+// // Last 3 days - show every 3 hours
+// timeUnit = 'hour';
+// displayFormat = 'MMM D HH:mm';
+// tickLimit = 24;
+// stepSize = 3; // Show every 3 hours
+// } else if (diffInHours <= 168) {
+// // Last week - show daily ticks
+// timeUnit = 'day';
+// displayFormat = 'MMM D';
+// tickLimit = 7;
+// stepSize = 6; // Show every 6 hours
+// } else {
+// // More than a week - show weekly ticks
+// timeUnit = 'week';
+// displayFormat = 'MMM D';
+// tickLimit = 10;
+// stepSize = 12; // Show every 12 hours
+// }
+// // Destroy existing chart if it exists
+// const existingChart = Chart.getChart(container);
+// if (existingChart) {
+// existingChart.destroy();
+// }
+//
+// // Group data by the selected field
+// const uniqueGroups =
+// groupBy === 'logType'
+// ? [...new Set(findingData.map((item) => (item as any)[groupBy]))]
+// : ['All findings'];
+//
+// // Group data by time periods
+// const timeLabels = [...new Set(findingData.map((item) => item.time))]
+// .sort((a, b) => a - b)
+// .map((time) => new Date(time).toLocaleString());
+//
+// // Create datasets for each group
+// const datasets = uniqueGroups.map((group, idx) => {
+// const color = groupBy.includes('severity')
+// ? severityColorPalette[idx % severityColorPalette.length]
+// : defaultColorPalette[idx % defaultColorPalette.length];
+//
+// const counts: Record = {};
+// return {
+// type: 'bar',
+// order: 2,
+// label: group,
+// data: findingData
+// // Overview page only supports grouping by logType, otherwise all findings should be mapped
+// .filter((item: any) => groupBy !== 'logType' || item[groupBy] === group)
+// .map((item) => {
+// counts[item.time] = (counts[item.time] || 0) + 1;
+// return {
+// x: moment(item.time).toDate(), // Convert to JavaScript Date object
+// y: counts[item.time],
+// };
+// }),
+// backgroundColor: color,
+// borderColor: color,
+// borderWidth: 1,
+// };
+// });
+//
+// const alertCounts: Record = {};
+// alertData.forEach((item) => {
+// alertCounts[item.time] = (alertCounts[item.time] || 0) + 1;
+// });
+// const transData = Object.entries(alertCounts).map(([time, count]) => {
+// return {
+// x: moment(time).toDate(), // Convert to JavaScript Date object
+// y: count,
+// };
+// });
+// console.info(`hurneyt transData =`, transData);
+// const alertsDataset = {
+// type: 'line',
+// order: 1,
+// label: 'Alerts',
+// data: transData,
+// backgroundColor: 'warning',
+// borderColor: 'warning',
+// borderWidth: 1,
+// };
+//
+// const ctx = document.getElementById(container) as HTMLCanvasElement;
+// if (!ctx) {
+// return;
+// }
+//
+// new Chart(ctx, {
+// type: 'bar',
+// data: {
+// labels: timeLabels,
+// datasets: [...datasets, alertsDataset],
+// },
+// options: {
+// responsive: true,
+// scales: {
+// x: {
+// type: 'time',
+// time: {
+// unit: timeUnit, // or 'hour', 'day', etc.
+// displayFormats: {
+// minute: 'HH:mm',
+// hour: 'HH:mm',
+// day: 'MMM d',
+// week: 'MMM d',
+// month: 'MMM yyyy',
+// quarter: 'MMM yyyy',
+// year: 'yyyy',
+// },
+// },
+// bounds: 'ticks',
+// offset: true,
+// ticks: {
+// source: 'auto',
+// autoSkip: true,
+// maxRotation: 45,
+// maxTicksLimit: tickLimit, // Adjust this to show more/fewer labels
+// stepSize: stepSize,
+// major: {
+// enabled: true,
+// },
+// callback: function (value) {
+// // Format the tick label
+// return moment(value).format(displayFormat);
+// },
+// },
+// min: start.valueOf(), // Add start time
+// max: end.valueOf(), // Add end time
+// stacked: true,
+// title: {
+// display: true,
+// text: 'Time',
+// },
+// },
+// y: {
+// stacked: true,
+// beginAtZero: true,
+// title: {
+// display: true,
+// text: 'Count',
+// },
+// ticks: {
+// stepSize: 1,
+// precision: 0, // This prevents decimal places
+// },
+// },
+// },
+// plugins: {
+// tooltip: {
+// callbacks: {
+// label: function (context: any) {
+// const label = context.dataset.label || '';
+// const value = context.parsed.y;
+// const timeLabel = context.label;
+// return `${groupByOption.text}: ${label}
+// Count: ${value}
+// Time: ${timeLabel}`;
+// },
+// },
+// },
+// legend: {
+// position: 'top',
+// },
+// title: {
+// display: true,
+// text: `Grouped by ${groupByOption.text}`,
+// },
+// },
+// },
+// });
+// };
+//
+// export const createDoughnutChartWrapper = (
+// visData: {
+// ruleName: string;
+// count: number;
+// }[] = [],
+// container: string = 'myDoughnutChart'
+// ) => {
+// initializeChart(container);
+// try {
+// createDoughnutChart(visData, container);
+// } catch (e) {
+// console.error(`Error while compiling doughnut chart ${container}`, e);
+// const chartContainer = document.getElementById(container)?.parentElement;
+// if (chartContainer) {
+// chartContainer.innerHTML = `
+//
+// Error while compiling doughnut chart ${container}
+//
+// `;
+// }
+// }
+// };
+//
+// const createDoughnutChart = (
+// visData: {
+// ruleName: string;
+// count: number;
+// }[] = [],
+// container: string = 'myDoughnutChart'
+// ) => {
+// // Register the required components
+// Chart.register(
+// DoughnutController, // for doughnut charts
+// ArcElement, // for doughnut/pie charts
+// Tooltip,
+// Legend
+// );
+//
+// const ctx = document.getElementById(container) as HTMLCanvasElement;
+// if (!ctx) {
+// return;
+// }
+//
+// const labels: string[] = [];
+// const counts: number[] = [];
+// visData.forEach((item) => {
+// labels.push(item.ruleName);
+// counts.push(item.count);
+// });
+// new Chart(ctx, {
+// type: 'doughnut',
+// data: {
+// labels: labels,
+// datasets: [
+// {
+// data: counts,
+// backgroundColor: euiPaletteColorBlind(labels.length),
+// },
+// ],
+// },
+// options: {
+// responsive: true,
+// plugins: {
+// legend: {
+// position: 'top',
+// },
+// tooltip: {
+// callbacks: {
+// label: function (context) {
+// const label = context.label || '';
+// const value = context.parsed;
+// return `${label}: ${value}`;
+// },
+// },
+// },
+// },
+// },
+// });
+// };
const getGroupByText = (groupByValue: string) => {
const allOptions = [
diff --git a/yarn.lock b/yarn.lock
index 194d5a774..0c01e34d6 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -876,6 +876,13 @@
dependencies:
"@babel/types" "^7.20.7"
+"@types/chart.js@^2.9.37":
+ version "2.9.41"
+ resolved "https://registry.yarnpkg.com/@types/chart.js/-/chart.js-2.9.41.tgz#4148cdc87d4f98fad44b2883271cd0fa57f05e0d"
+ integrity sha512-3dvkDvueckY83UyUXtJMalYoH6faOLkWQoaTlJgB4Djde3oORmNP0Jw85HtzTuXyliUHcdp704s0mZFQKio/KQ==
+ dependencies:
+ moment "^2.10.2"
+
"@types/graceful-fs@^4.1.2":
version "4.1.9"
resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4"
@@ -4450,7 +4457,7 @@ mkdirp@^0.5.1, mkdirp@^0.5.3:
dependencies:
minimist "^1.2.6"
-moment@>=2.13.0, moment@^2.29.2:
+moment@>=2.13.0, moment@^2.10.2, moment@^2.29.2:
version "2.30.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae"
integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==
From 64695b4966007f83ba782ee7ababad68d3b3ad8d Mon Sep 17 00:00:00 2001
From: AWSHurneyt
Date: Sat, 15 Mar 2025 18:50:09 -0700
Subject: [PATCH 3/3] Updated snapshots.
Signed-off-by: AWSHurneyt
---
.../Alerts/__snapshots__/Alerts.test.tsx.snap | 572 ------------------
1 file changed, 572 deletions(-)
diff --git a/public/pages/Alerts/containers/Alerts/__snapshots__/Alerts.test.tsx.snap b/public/pages/Alerts/containers/Alerts/__snapshots__/Alerts.test.tsx.snap
index 8c07a5059..b4651f427 100644
--- a/public/pages/Alerts/containers/Alerts/__snapshots__/Alerts.test.tsx.snap
+++ b/public/pages/Alerts/containers/Alerts/__snapshots__/Alerts.test.tsx.snap
@@ -835,79 +835,6 @@ exports[` spec renders the component 1`] = `
initialSelectedTab={
Object {
"content":
-
-
-
-
-
-
-
-
-
-
-
-
-
- Adjust the time range to see more results or create alert triggers in your
-
-
- detectors
-
- to generate alerts.
-
-
- }
- title={
-
-
- No alerts
-
-
- }
- />
-
-
-
@@ -1039,79 +966,6 @@ exports[` spec renders the component 1`] = `
Array [
Object {
"content":
-
-
-
-
-
-
-
-
-
-
-
-
-
- Adjust the time range to see more results or create alert triggers in your
-
-
- detectors
-
- to generate alerts.
-
-
- }
- title={
-
-
- No alerts
-
-
- }
- />
-
-
-
@@ -1238,79 +1092,6 @@ exports[` spec renders the component 1`] = `
},
Object {
"content":
-
-
-
-
-
-
-
-
-
-
-
-
-
- Adjust the time range to see more results or create alert triggers in your
-
-
- detectors
-
- to generate alerts.
-
-
- }
- title={
-
-
- No alerts
-
-
- }
- />
-
-
-
@@ -1340,79 +1121,6 @@ exports[` spec renders the component 1`] = `
},
Object {
"content":
-
-
-
-
-
-
-
-
-
-
-
-
-
- Adjust the time range to see more results or create alert triggers in your
-
-
- detectors
-
- to generate alerts.
-
-
- }
- title={
-
-
- No alerts
-
-
- }
- />
-
-
-
@@ -1670,286 +1378,6 @@ exports[` spec renders the component 1`] = `
id="some_html_id"
role="tabpanel"
>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- EuiIconMock
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Adjust the time range to see more results or create alert triggers in your
-
-
- detectors
-
- to generate alerts.
-
-
- }
- title={
-
-
- No alerts
-
-
- }
- >
-
-
-
-
-
- No alerts
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Adjust the time range to see more results or create alert triggers in your
-
-
-
- detectors
-
-
- to generate alerts.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-