Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions static/app/components/charts/baseChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ type Props = {
* be an array of ECharts "Series" components.
*/
series?: EChartOption.Series[];
/**
* Additional Chart Series
* This is to pass series to BaseChart bypassing the wrappers like LineChart, AreaChart etc.
*/
additionalSeries?: EChartOption.SeriesLine[];
/**
* Array of color codes to use in charts. May also take a function which is
* provided with the current theme
Expand Down Expand Up @@ -112,6 +117,7 @@ type Props = {
seriesParams?: EChartOption.Tooltip.Format
) => string | number;
nameFormatter?: (name: string) => string;
markerFormatter?: (marker: string, label?: string) => string;
/**
* Array containing seriesNames that need to be indented
*/
Expand Down Expand Up @@ -283,6 +289,7 @@ function BaseChartUnwrapped({

options = {},
series = [],
additionalSeries = [],
yAxis = {},
xAxis = {},

Expand Down Expand Up @@ -337,8 +344,8 @@ function BaseChartUnwrapped({
) ?? [];

const resolvedSeries = !previousPeriod
? transformedSeries
: [...transformedSeries, ...transformedPreviousPeriod];
? [...transformedSeries, ...additionalSeries]
: [...transformedSeries, ...transformedPreviousPeriod, ...additionalSeries];

const defaultAxesProps = {theme};

Expand Down
14 changes: 12 additions & 2 deletions static/app/components/charts/components/tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ function defaultNameFormatter(value: string) {
return value;
}

function defaultMarkerFormatter(value: string) {
return value;
}

function getSeriesValue(series: EChartOption.Tooltip.Format, offset: number) {
if (!series.data) {
return undefined;
Expand All @@ -81,7 +85,8 @@ type TooltipFormatters =
| 'filter'
| 'formatAxisLabel'
| 'valueFormatter'
| 'nameFormatter';
| 'nameFormatter'
| 'markerFormatter';

type FormatterOptions = Pick<NonNullable<ChartProps['tooltip']>, TooltipFormatters> &
Pick<ChartProps, NeededChartProps> & {
Expand All @@ -105,6 +110,7 @@ function getFormatter({
bucketSize,
valueFormatter = defaultValueFormatter,
nameFormatter = defaultNameFormatter,
markerFormatter = defaultMarkerFormatter,
indentLabels = [],
addSecondsToTimeFormat = false,
}: FormatterOptions) {
Expand Down Expand Up @@ -203,11 +209,13 @@ function getFormatter({
);
const value = valueFormatter(getSeriesValue(s, 1), s.seriesName, s);

const marker = markerFormatter(s.marker ?? '', s.seriesName);

const className = indentLabels.includes(formattedLabel)
? 'tooltip-label tooltip-label-indent'
: 'tooltip-label';

return `<div><span class="${className}">${s.marker} <strong>${formattedLabel}</strong></span> ${value}</div>`;
return `<div><span class="${className}">${marker} <strong>${formattedLabel}</strong></span> ${value}</div>`;
})
.join(''),
'</div>',
Expand Down Expand Up @@ -235,6 +243,7 @@ export default function Tooltip({
formatAxisLabel,
valueFormatter,
nameFormatter,
markerFormatter,
hideDelay,
indentLabels,
...props
Expand All @@ -252,6 +261,7 @@ export default function Tooltip({
formatAxisLabel,
valueFormatter,
nameFormatter,
markerFormatter,
indentLabels,
});

Expand Down
2 changes: 1 addition & 1 deletion static/app/components/charts/eventsRequest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ type DefaultProps = {
*/
interval: string;
/**
* Time delta for comparing intervals alert metrics, value in minutes
* Time delta for comparing intervals of alert metrics, in seconds
*/
comparisonDelta?: number;
/**
Expand Down
16 changes: 15 additions & 1 deletion static/app/views/alerts/incidentRules/triggers/chart/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as React from 'react';
import styled from '@emotion/styled';
import capitalize from 'lodash/capitalize';
import chunk from 'lodash/chunk';
import maxBy from 'lodash/maxBy';
import minBy from 'lodash/minBy';
Expand Down Expand Up @@ -31,6 +32,7 @@ import {
} from 'app/utils/sessions';
import theme from 'app/utils/theme';
import withApi from 'app/utils/withApi';
import {COMPARISON_DELTA_OPTIONS} from 'app/views/alerts/incidentRules/constants';
import {isSessionAggregate, SESSION_AGGREGATE_TO_FIELD} from 'app/views/alerts/utils';
import {AlertWizardAlertNames} from 'app/views/alerts/wizard/options';
import {getAlertTypeFromAggregateDataset} from 'app/views/alerts/wizard/utils';
Expand Down Expand Up @@ -218,6 +220,13 @@ class TriggersChart extends React.PureComponent<Props, State> {
return period;
};

get comparisonSeriesName() {
return capitalize(
COMPARISON_DELTA_OPTIONS.find(({value}) => value === this.props.comparisonDelta)
?.label || ''
);
}

getComparisonMarkLines(
timeseriesData: Series[] = [],
comparisonTimeseriesData: Series[] = []
Expand Down Expand Up @@ -362,6 +371,7 @@ class TriggersChart extends React.PureComponent<Props, State> {
timeseriesData: Series[] = [],
isLoading: boolean,
isReloading: boolean,
comparisonData?: Series[],
comparisonMarkLines?: LineChartSeries[],
minutesThresholdToDisplaySeconds?: number
) {
Expand Down Expand Up @@ -389,6 +399,8 @@ class TriggersChart extends React.PureComponent<Props, State> {
minValue={minBy(timeseriesData[0]?.data, ({value}) => value)?.value}
maxValue={maxBy(timeseriesData[0]?.data, ({value}) => value)?.value}
data={timeseriesData}
comparisonData={comparisonData ?? []}
comparisonSeriesName={this.comparisonSeriesName}
comparisonMarkLines={comparisonMarkLines ?? []}
hideThresholdLines={comparisonType === AlertRuleComparisonType.CHANGE}
triggers={triggers}
Expand Down Expand Up @@ -475,6 +487,7 @@ class TriggersChart extends React.PureComponent<Props, State> {
loading,
reloading,
undefined,
undefined,
MINUTES_THRESHOLD_TO_DISPLAY_SECONDS
);
}}
Expand All @@ -490,7 +503,7 @@ class TriggersChart extends React.PureComponent<Props, State> {
environment={environment ? [environment] : undefined}
project={projects.map(({id}) => Number(id))}
interval={`${timeWindow}m`}
comparisonDelta={comparisonDelta}
comparisonDelta={comparisonDelta && comparisonDelta * 60}
period={period}
yAxis={aggregate}
includePrevious={false}
Expand Down Expand Up @@ -544,6 +557,7 @@ class TriggersChart extends React.PureComponent<Props, State> {
timeseriesData,
loading,
reloading,
comparisonTimeseriesData,
comparisonMarkLines
);
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import flatten from 'lodash/flatten';

import AreaChart, {AreaChartSeries} from 'app/components/charts/areaChart';
import Graphic from 'app/components/charts/components/graphic';
import {LineChartSeries} from 'app/components/charts/lineChart';
import LineSeries from 'app/components/charts/series/lineSeries';
import space from 'app/styles/space';
import {GlobalSelection} from 'app/types';
import {ReactEchartsRef, Series} from 'app/types/echarts';
Expand All @@ -21,7 +23,8 @@ import {AlertRuleThresholdType, IncidentRule, Trigger} from '../../types';

type DefaultProps = {
data: Series[];
comparisonMarkLines: AreaChartSeries[];
comparisonData: Series[];
comparisonMarkLines: LineChartSeries[];
};

type Props = DefaultProps & {
Expand All @@ -33,6 +36,7 @@ type Props = DefaultProps & {
minutesThresholdToDisplaySeconds?: number;
maxValue?: number;
minValue?: number;
comparisonSeriesName?: string;
} & Partial<GlobalSelection['datetime']>;

type State = {
Expand Down Expand Up @@ -63,6 +67,7 @@ const COLOR = {
export default class ThresholdsChart extends PureComponent<Props, State> {
static defaultProps: DefaultProps = {
data: [],
comparisonData: [],
comparisonMarkLines: [],
};

Expand All @@ -81,6 +86,7 @@ export default class ThresholdsChart extends PureComponent<Props, State> {
if (
this.props.triggers !== prevProps.triggers ||
this.props.data !== prevProps.data ||
this.props.comparisonData !== prevProps.comparisonData ||
this.props.comparisonMarkLines !== prevProps.comparisonMarkLines
) {
this.handleUpdateChartAxis();
Expand Down Expand Up @@ -295,6 +301,8 @@ export default class ThresholdsChart extends PureComponent<Props, State> {
triggers,
period,
aggregate,
comparisonData,
comparisonSeriesName,
comparisonMarkLines,
minutesThresholdToDisplaySeconds,
} = this.props;
Expand All @@ -306,6 +314,13 @@ export default class ThresholdsChart extends PureComponent<Props, State> {
})
);

const comparisonDataWithoutRecentBucket = comparisonData?.map(
({data: eventData, ...restOfData}) => ({
...restOfData,
data: eventData.slice(0, -1),
})
);

// Disable all lines by default but the 1st one
const selected: Record<string, boolean> = dataWithoutRecentBucket.reduce(
(acc, {seriesName}, index) => {
Expand All @@ -323,8 +338,17 @@ export default class ThresholdsChart extends PureComponent<Props, State> {

const chartOptions = {
tooltip: {
valueFormatter: (value: number, seriesName?: string) =>
alertTooltipValueFormatter(value, seriesName ?? '', aggregate),
// use the main aggregate for all series (main, min, max, avg, comparison)
// to format all values similarly
valueFormatter: (value: number) =>
alertTooltipValueFormatter(value, aggregate, aggregate),

markerFormatter: (marker: string, seriesName?: string) => {
if (seriesName === comparisonSeriesName) {
return '<span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:transparent;"></span>';
}
return marker;
},
},
yAxis: {
min: this.state.yAxisMin ?? undefined,
Expand Down Expand Up @@ -355,6 +379,19 @@ export default class ThresholdsChart extends PureComponent<Props, State> {
),
})}
series={[...dataWithoutRecentBucket, ...comparisonMarkLines]}
additionalSeries={[
...comparisonDataWithoutRecentBucket.map(({data: _data, ...otherSeriesProps}) =>
LineSeries({
name: comparisonSeriesName,
data: _data.map(({name, value}) => [name, value]),
lineStyle: {color: theme.gray200, type: 'dashed', width: 1},
animation: false,
animationThreshold: 1,
animationDuration: 0,
...otherSeriesProps,
})
),
]}
onFinished={() => {
// We want to do this whenever the chart finishes re-rendering so that we can update the dimensions of
// any graphics related to the triggers (e.g. the threshold areas + boundaries)
Expand Down