123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222 |
- import moment from 'moment';
- import MarkLine from 'app/components/charts/components/markLine';
- import MarkPoint from 'app/components/charts/components/markPoint';
- import LineChart, {LineChartSeries} from 'app/components/charts/lineChart';
- import {t} from 'app/locale';
- import space from 'app/styles/space';
- import theme from 'app/utils/theme';
- import {Trigger} from 'app/views/settings/incidentRules/types';
- import closedSymbol from './closedSymbol';
- import startedSymbol from './startedSymbol';
- type Truthy<T> = T extends false | '' | 0 | null | undefined ? never : T;
- function truthy<T>(value: T): value is Truthy<T> {
- return !!value;
- }
- type Data = [number, {count: number}[]];
- /**
- * So we'll have to see how this looks with real data, but echarts requires
- * an explicit (x,y) value to draw a symbol (incident started/closed bubble).
- *
- * This uses the closest date *without* going over.
- *
- * AFAICT we can't give it an x-axis value and have it draw on the line,
- * so we probably need to calculate the y-axis value ourselves if we want it placed
- * at the exact time.
- *
- * @param data Data array
- * @param needle the target timestamp
- */
- function getNearbyIndex(data: Data[], needle: number) {
- // `data` is sorted, return the first index whose value (timestamp) is > `needle`
- const index = data.findIndex(([ts]) => ts > needle);
- // this shouldn't happen, as we try to buffer dates before start/end dates
- if (index === 0) {
- return 0;
- }
- return index !== -1 ? index - 1 : data.length - 1;
- }
- type Props = {
- data: Data[];
- aggregate: string;
- started: string;
- closed?: string;
- triggers?: Trigger[];
- resolveThreshold?: number | '' | null;
- };
- const Chart = (props: Props) => {
- const {aggregate, data, started, closed, triggers, resolveThreshold} = props;
- const startedTs = started && moment.utc(started).unix();
- const closedTs = closed && moment.utc(closed).unix();
- const chartData = data.map(([ts, val]) => [
- ts * 1000,
- val.length ? val.reduce((acc, {count} = {count: 0}) => acc + count, 0) : 0,
- ]);
- const startedCoordinate = startedTs
- ? chartData[getNearbyIndex(data, startedTs)]
- : undefined;
- const showClosedMarker =
- data && closedTs && data[data.length - 1] && data[data.length - 1][0] >= closedTs
- ? true
- : false;
- const closedCoordinate =
- closedTs && showClosedMarker ? chartData[getNearbyIndex(data, closedTs)] : undefined;
- const seriesName = aggregate;
- const warningTrigger = triggers?.find(trig => trig.label === 'warning');
- const criticalTrigger = triggers?.find(trig => trig.label === 'critical');
- const warningTriggerAlertThreshold =
- typeof warningTrigger?.alertThreshold === 'number'
- ? warningTrigger?.alertThreshold
- : undefined;
- const criticalTriggerAlertThreshold =
- typeof criticalTrigger?.alertThreshold === 'number'
- ? criticalTrigger?.alertThreshold
- : undefined;
- const alertResolveThreshold =
- typeof resolveThreshold === 'number' ? resolveThreshold : undefined;
- const marklinePrecision = Math.max(
- ...[
- warningTriggerAlertThreshold,
- criticalTriggerAlertThreshold,
- alertResolveThreshold,
- ].map(decimal => {
- if (!decimal || !isFinite(decimal)) return 0;
- let e = 1;
- let p = 0;
- while (Math.round(decimal * e) / e !== decimal) {
- e *= 10;
- p += 1;
- }
- return p;
- })
- );
- const lineSeries: LineChartSeries[] = [
- {
- // e.g. Events or Users
- seriesName,
- dataArray: chartData,
- data: [],
- markPoint: MarkPoint({
- data: [
- {
- labelForValue: seriesName,
- seriesName,
- symbol: `image://${startedSymbol}`,
- name: t('Alert Triggered'),
- coord: startedCoordinate,
- },
- ...(closedTs
- ? [
- {
- labelForValue: seriesName,
- seriesName,
- symbol: `image://${closedSymbol}`,
- symbolSize: 24,
- name: t('Alert Resolved'),
- coord: closedCoordinate,
- },
- ]
- : []),
- ] as any, // TODO(ts): data on this type is likely incomplete (needs @types/echarts@4.6.2)
- }),
- },
- warningTrigger &&
- warningTriggerAlertThreshold && {
- seriesName: 'Warning Alert',
- type: 'line',
- markLine: MarkLine({
- silent: true,
- lineStyle: {color: theme.yellow300},
- data: [
- {
- yAxis: warningTriggerAlertThreshold,
- } as any, // TODO(ts): data on this type is likely incomplete (needs @types/echarts@4.6.2)
- ],
- precision: marklinePrecision,
- label: {
- show: true,
- position: 'insideEndTop',
- formatter: 'WARNING',
- color: theme.yellow300,
- fontSize: 10,
- } as any, // TODO(ts): Color is not an exposed option for label,
- }),
- data: [],
- },
- criticalTrigger &&
- criticalTriggerAlertThreshold && {
- seriesName: 'Critical Alert',
- type: 'line',
- markLine: MarkLine({
- silent: true,
- lineStyle: {color: theme.red200},
- data: [
- {
- yAxis: criticalTriggerAlertThreshold,
- } as any, // TODO(ts): data on this type is likely incomplete (needs @types/echarts@4.6.2)
- ],
- precision: marklinePrecision,
- label: {
- show: true,
- position: 'insideEndTop',
- formatter: 'CRITICAL',
- color: theme.red300,
- fontSize: 10,
- } as any, // TODO(ts): Color is not an exposed option for label,
- }),
- data: [],
- },
- criticalTrigger &&
- alertResolveThreshold && {
- seriesName: 'Critical Resolve',
- type: 'line',
- markLine: MarkLine({
- silent: true,
- lineStyle: {color: theme.gray200},
- data: [
- {
- yAxis: alertResolveThreshold,
- } as any, // TODO(ts): data on this type is likely incomplete (needs @types/echarts@4.6.2)
- ],
- precision: marklinePrecision,
- label: {
- show: true,
- position: 'insideEndBottom',
- formatter: 'CRITICAL RESOLUTION',
- color: theme.gray200,
- fontSize: 10,
- } as any, // TODO(ts): Color is not an option for label,
- }),
- data: [],
- },
- ].filter(truthy);
- return (
- <LineChart
- isGroupedByDate
- showTimeInTooltip
- grid={{
- left: 0,
- right: 0,
- top: space(2),
- bottom: 0,
- }}
- series={lineSeries}
- />
- );
- };
- export default Chart;
|