Skip to content

Commit

Permalink
[8.x] [RCA] Events timeline !! (#193265) (#195280)
Browse files Browse the repository at this point in the history
# Backport

This will backport the following commits from `main` to `8.x`:
- [[RCA] Events timeline !!
(#193265)](#193265)

<!--- Backport version: 8.9.8 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT
[{"author":{"name":"Shahzad","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-10-07T13:41:24Z","message":"[RCA]
Events timeline !! (#193265)\n\n## Summary\r\n\r\nEvents timeline
!!\r\n\r\n<img width=\"1728\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/c00c2368-5f7e-4e5e-a6a1-cbcfacb859cd\">\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<[email protected]>","sha":"89f28025052da4309087014330ed649468fc78b5","branchLabelMapping":{"^v9.0.0$":"main","^v8.16.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","backport:prev-minor","ci:project-deploy-observability"],"number":193265,"url":"https://github.com/elastic/kibana/pull/193265","mergeCommit":{"message":"[RCA]
Events timeline !! (#193265)\n\n## Summary\r\n\r\nEvents timeline
!!\r\n\r\n<img width=\"1728\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/c00c2368-5f7e-4e5e-a6a1-cbcfacb859cd\">\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<[email protected]>","sha":"89f28025052da4309087014330ed649468fc78b5"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","labelRegex":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/193265","number":193265,"mergeCommit":{"message":"[RCA]
Events timeline !! (#193265)\n\n## Summary\r\n\r\nEvents timeline
!!\r\n\r\n<img width=\"1728\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/c00c2368-5f7e-4e5e-a6a1-cbcfacb859cd\">\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<[email protected]>","sha":"89f28025052da4309087014330ed649468fc78b5"}}]}]
BACKPORT-->
  • Loading branch information
shahzad31 authored Oct 8, 2024
1 parent ba2ecd5 commit c033f0d
Show file tree
Hide file tree
Showing 15 changed files with 351 additions and 13 deletions.
3 changes: 2 additions & 1 deletion packages/kbn-investigation-shared/src/rest_specs/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { eventSchema } from '../schema';
const eventResponseSchema = eventSchema;

type EventResponse = z.output<typeof eventResponseSchema>;
type EventSchema = z.output<typeof eventSchema>;

export { eventResponseSchema };
export type { EventResponse };
export type { EventResponse, EventSchema };
16 changes: 16 additions & 0 deletions x-pack/packages/observability/observability_utils/chart/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { BrushEvent } from '@elastic/charts';
import moment from 'moment';

export function getBrushData(e: BrushEvent) {
const [from, to] = [Number(e.x?.[0]), Number(e.x?.[1])];
const [fromUtc, toUtc] = [moment(from).format(), moment(to).format()];

return { from: fromUtc, to: toUtc };
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export function getMockInvestigateAppContext(): DeeplyMockedKeys<InvestigateAppK
});
}),
},
charts: {} as any,
};

const core = coreMock.createStart();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"investigate",
"observabilityShared",
"lens",
"charts",
"dataViews",
"data",
"embeddable",
Expand All @@ -25,7 +26,7 @@
"requiredBundles": [
"esql",
"kibanaReact",
"kibanaUtils",
"kibanaUtils"
],
"optionalPlugins": ["observabilityAIAssistant"],
"extraPublicDirs": []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export const investigationKeys = {
userProfiles: (profileIds: Set<string>) =>
[...investigationKeys.all, 'userProfiles', ...profileIds] as const,
tags: () => [...investigationKeys.all, 'tags'] as const,
events: (rangeFrom?: string, rangeTo?: string) =>
[...investigationKeys.all, 'events', rangeFrom, rangeTo] as const,
stats: () => [...investigationKeys.all, 'stats'] as const,
lists: () => [...investigationKeys.all, 'list'] as const,
list: (params: { page: number; perPage: number; search?: string; filter?: string }) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { i18n } from '@kbn/i18n';
import { useQuery } from '@tanstack/react-query';
import { GetEventsResponse } from '@kbn/investigation-shared';
import { investigationKeys } from './query_key_factory';
import { useKibana } from './use_kibana';

export interface Response {
isInitialLoading: boolean;
isLoading: boolean;
isRefetching: boolean;
isSuccess: boolean;
isError: boolean;
data?: GetEventsResponse;
}

export function useFetchEvents({
rangeFrom,
rangeTo,
}: {
rangeFrom?: string;
rangeTo?: string;
}): Response {
const {
core: {
http,
notifications: { toasts },
},
} = useKibana();

const { isInitialLoading, isLoading, isError, isSuccess, isRefetching, data } = useQuery({
queryKey: investigationKeys.events(rangeFrom, rangeTo),
queryFn: async ({ signal }) => {
return await http.get<GetEventsResponse>(`/api/observability/events`, {
query: {
rangeFrom,
rangeTo,
},
version: '2023-10-31',
signal,
});
},
cacheTime: 600 * 1000, // 10_minutes
staleTime: 0,
refetchOnWindowFocus: false,
retry: false,
onError: (error: Error) => {
toasts.addError(error, {
title: i18n.translate('xpack.investigateApp.events.fetch.error', {
defaultMessage: 'Something went wrong while fetching the events',
}),
});
},
});

return {
data,
isInitialLoading,
isLoading,
isRefetching,
isSuccess,
isError,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { LineAnnotation, AnnotationDomainType } from '@elastic/charts';
import { EuiIcon } from '@elastic/eui';
import React from 'react';
import moment from 'moment';
import { EventSchema } from '@kbn/investigation-shared';

export const AlertEvent = ({ event }: { event: EventSchema }) => {
return (
<LineAnnotation
id={event.id}
domainType={AnnotationDomainType.XDomain}
marker={
<span>
<EuiIcon style={{ marginTop: -16 }} type="dot" size="l" color="danger" />
</span>
}
markerPosition="bottom"
dataValues={[
{
dataValue: moment(event.timestamp).valueOf(),
header: moment(event.timestamp).format('lll'),
details: event.description,
},
]}
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React from 'react';
import moment from 'moment';
import { AnnotationDomainType, LineAnnotation } from '@elastic/charts';
import { EuiIcon } from '@elastic/eui';
import { EventSchema } from '@kbn/investigation-shared';

export function AnnotationEvent({ event }: { event: EventSchema }) {
const timestamp = event.timestamp;

return (
<LineAnnotation
id={event.id}
domainType={AnnotationDomainType.XDomain}
dataValues={[
{
dataValue: moment(timestamp).valueOf(),
details: event.description,
header: moment(event.timestamp).format('lll'),
},
]}
marker={
<span>
<EuiIcon style={{ marginTop: -16 }} type="dot" size="l" />
</span>
}
markerPosition="bottom"
/>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { useMemo, useRef } from 'react';
import moment from 'moment';

import { Chart, Axis, AreaSeries, Position, ScaleType, Settings } from '@elastic/charts';
import { useActiveCursor } from '@kbn/charts-plugin/public';
import { EuiSkeletonText } from '@elastic/eui';
import { getBrushData } from '@kbn/observability-utils/chart/utils';
import { AnnotationEvent } from './annotation_event';
import { TIME_LINE_THEME } from './timeline_theme';
import { useFetchEvents } from '../../../../hooks/use_fetch_events';
import { useInvestigation } from '../../contexts/investigation_context';
import { useKibana } from '../../../../hooks/use_kibana';
import { AlertEvent } from './alert_event';

export const EventsTimeLine = () => {
const { dependencies } = useKibana();

const baseTheme = dependencies.start.charts.theme.useChartsBaseTheme();

const { globalParams, updateInvestigationParams } = useInvestigation();

const { data: events, isLoading } = useFetchEvents({
rangeFrom: globalParams.timeRange.from,
rangeTo: globalParams.timeRange.to,
});

const chartRef = useRef(null);
const handleCursorUpdate = useActiveCursor(dependencies.start.charts.activeCursor, chartRef, {
isDateHistogram: true,
});

const data = useMemo(() => {
const points = [
{ x: moment(globalParams.timeRange.from).valueOf(), y: 0 },
{ x: moment(globalParams.timeRange.to).valueOf(), y: 0 },
];

// adding 100 fake points to the chart so the chart shows cursor on hover
for (let i = 0; i < 100; i++) {
const diff =
moment(globalParams.timeRange.to).valueOf() - moment(globalParams.timeRange.from).valueOf();
points.push({ x: moment(globalParams.timeRange.from).valueOf() + (diff / 100) * i, y: 0 });
}
return points;
}, [globalParams.timeRange.from, globalParams.timeRange.to]);

if (isLoading) {
return <EuiSkeletonText />;
}

const alertEvents = events?.filter((evt) => evt.eventType === 'alert');
const annotations = events?.filter((evt) => evt.eventType === 'annotation');

return (
<>
<Chart size={['100%', 100]} ref={chartRef}>
<Settings
xDomain={{
min: moment(globalParams.timeRange.from).valueOf(),
max: moment(globalParams.timeRange.to).valueOf(),
}}
theme={TIME_LINE_THEME}
baseTheme={baseTheme}
onPointerUpdate={handleCursorUpdate}
externalPointerEvents={{
tooltip: { visible: true },
}}
onBrushEnd={(brush) => {
const { from, to } = getBrushData(brush);
updateInvestigationParams({
timeRange: { from, to },
});
}}
/>
<Axis id="y" position={Position.Left} hide />
<Axis
id="x"
position={Position.Bottom}
tickFormat={(d) => moment(d).format('LTS')}
style={{
tickLine: {
visible: true,
strokeWidth: 1,
stroke: '#98A2B3',
},
}}
/>

{alertEvents?.map((event) => (
<AlertEvent key={event.id} event={event} />
))}

{annotations?.map((annotation) => (
<AnnotationEvent key={annotation.id} event={annotation} />
))}

<AreaSeries
id="Time"
xScaleType={ScaleType.Time}
xAccessor="x"
yAccessors={['y']}
data={data}
filterSeriesInTooltip={() => false}
/>
</Chart>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { PartialTheme } from '@elastic/charts';

export const TIME_LINE_THEME: PartialTheme = {
highlighter: {
point: {
opacity: 0,
},
},
axes: {
gridLine: {
horizontal: {
visible: false,
},
vertical: {
visible: false,
},
},
axisLine: {
strokeWidth: 1,
stroke: '#98A2B3',
},
},
chartMargins: {
bottom: 10,
top: 10,
},
areaSeriesStyle: {
area: {
visible: false,
},
line: {
visible: false,
},
},
lineAnnotation: {
line: {
opacity: 0,
},
},
};
Loading

0 comments on commit c033f0d

Please sign in to comment.