diff --git a/package.json b/package.json index 2f2e25bd5..97362170d 100644 --- a/package.json +++ b/package.json @@ -78,13 +78,16 @@ "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" }, "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..1859e96c4 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) => { @@ -1024,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)}*/} + {/**/} { } /> ) : ( - +
+ +
)} 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" > - -
- - -
- -
- -
- -
- -
- - - -
- - - -
- - - - -
- - - - - -
-
-
-
-
-
-
-
-
-
-
-
-
- -
- - - 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. -
- -

-
-
- - -
- -
- -
- -
- diff --git a/public/pages/Findings/containers/Findings/Findings.tsx b/public/pages/Findings/containers/Findings/Findings.tsx index aeebd7d3e..9a4937b3a 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[] = []; @@ -218,9 +213,11 @@ class Findings extends Component { this.state.selectedTabId !== prevState.selectedTabId ) { this.onRefresh(); - } else if (this.shouldUpdateVisualization(prevState)) { - renderVisualization(this.generateVisualizationSpec(), 'findings-view'); } + // else if (this.shouldUpdateVisualization(prevState)) { + // const data = this.generateVisualizationData(); + // this.createStackedBarChart(data.visData, data.groupBy); + // } } componentDidMount = async () => { @@ -239,7 +236,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 +417,7 @@ class Findings extends Component { }); }; - generateVisualizationSpec() { + generateVisualizationData() { const visData: (FindingVisualizationData | ThreatIntelFindingVisualizationData)[] = []; const { selectedTabId, findingStateByTabId } = this.state; @@ -428,7 +426,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 +446,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 +458,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 +490,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 +540,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'] = @@ -565,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)}*/} + {/**/} { } /> ) : ( - +
+ +
)}
diff --git a/public/pages/Overview/components/Widgets/Summary.tsx b/public/pages/Overview/components/Widgets/Summary.tsx index 910aa555b..226570dbf 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]); + // 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) => ( @@ -167,39 +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 b6ef0a512..1b0407831 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..ec1fc07e0 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, @@ -316,7 +313,7 @@ export const Overview: React.FC = (props) => { - + {/**/} { }); }); - 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..5cd213d99 --- /dev/null +++ b/public/utils/chartUtils.tsx @@ -0,0 +1,610 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +// import { euiPaletteColorBlind, euiPaletteForStatus } from '@elastic/eui'; +// import datemath from '@elastic/datemath'; +// import moment from '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'; +// 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 = [ + ...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 ` -
-
- - - - - - -
${sanitize(firstItem[0])}${sanitize(firstItem[1])}
-
-
- ${rowData}
-
-
- `; - }, - }); - 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..0c01e34d6 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" @@ -871,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" @@ -1767,6 +1779,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" @@ -4433,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==