Skip to content

Commit

Permalink
feat(alerts): Display all metric alerts bucket as lines (#20871)
Browse files Browse the repository at this point in the history
  • Loading branch information
scttcper committed Sep 25, 2020
1 parent b11150e commit d8bdb42
Showing 1 changed file with 62 additions and 58 deletions.
Expand Up @@ -10,6 +10,7 @@ import {SeriesDataUnit} from 'app/types/echarts';
import {Panel, PanelBody} from 'app/components/panels';
import Feature from 'app/components/acl/feature';
import EventsRequest from 'app/components/charts/eventsRequest';
import Checkbox from 'app/components/checkbox';
import LoadingMask from 'app/components/loadingMask';
import Placeholder from 'app/components/placeholder';
import SelectControl from 'app/components/forms/selectControl';
Expand Down Expand Up @@ -85,7 +86,6 @@ const AVAILABLE_TIME_PERIODS: Record<TimeWindow, TimePeriod[]> = {
};

const AGGREGATE_FUNCTIONS = {
none: (seriesChunk: SeriesDataUnit[]) => seriesChunk,
avg: (seriesChunk: SeriesDataUnit[]) =>
AGGREGATE_FUNCTIONS.sum(seriesChunk) / seriesChunk.length,
sum: (seriesChunk: SeriesDataUnit[]) =>
Expand All @@ -96,17 +96,24 @@ const AGGREGATE_FUNCTIONS = {
Math.min(...seriesChunk.map(series => series.value)),
};

const AGGREGATE_FUNCTION_MAP: Record<keyof typeof AGGREGATE_FUNCTIONS, string> = {
none: t('None'),
avg: t('Average'),
sum: t('Sum'),
max: t('Maximum'),
min: t('Minimum'),
/**
* Determines the number of datapoints to roll up
*/
const getBucketSize = (timeWindow: TimeWindow, dataPoints: number): number => {
const MAX_DPS = 720;
for (const bucketSize of [5, 10, 15, 30, 60, 120, 240]) {
const chunkSize = bucketSize / timeWindow;
if (dataPoints / chunkSize <= MAX_DPS) {
return bucketSize / timeWindow;
}
}

return 2;
};

type State = {
statsPeriod: TimePeriod;
aggregateFn: keyof typeof AGGREGATE_FUNCTIONS;
rollupGraph: boolean;
};

/**
Expand All @@ -116,18 +123,15 @@ type State = {
class TriggersChart extends React.PureComponent<Props, State> {
state: State = {
statsPeriod: TimePeriod.ONE_DAY,
aggregateFn: 'none',
rollupGraph: false,
};

handleStatsPeriodChange = (statsPeriod: {value: TimePeriod; label: string}) => {
this.setState({statsPeriod: statsPeriod.value});
};

handleAggregateFunctionChange = (aggregateFn: {
value: keyof typeof AGGREGATE_FUNCTIONS;
label: string;
}) => {
this.setState({aggregateFn: aggregateFn.value});
handleRollupChange = (event: React.ChangeEvent<HTMLInputElement>) => {
this.setState({rollupGraph: event.target.checked});
};

render() {
Expand All @@ -143,7 +147,7 @@ class TriggersChart extends React.PureComponent<Props, State> {
thresholdType,
environment,
} = this.props;
const {statsPeriod, aggregateFn} = this.state;
const {statsPeriod, rollupGraph} = this.state;

const statsPeriodOptions = AVAILABLE_TIME_PERIODS[timeWindow];
const period = statsPeriodOptions.includes(statsPeriod)
Expand All @@ -169,23 +173,31 @@ class TriggersChart extends React.PureComponent<Props, State> {
if (timeseriesData?.[0]?.data !== undefined) {
maxValue = maxBy(timeseriesData[0].data, ({value}) => value);
timeseriesLength = timeseriesData[0].data.length;
if (aggregateFn !== 'none' && timeseriesLength > 600) {
let chunkSize = 2;
if (timeseriesData[0].data.length > 8000) {
chunkSize = 20;
} else if (timeseriesData[0].data.length > 4000) {
chunkSize = 10;
} else if (timeseriesData[0].data.length > 2000) {
chunkSize = 5;
}
timeseriesData[0].data = chunk(timeseriesData[0].data, chunkSize).map(
seriesChunk => {
return {
name: seriesChunk[0].name,
value: AGGREGATE_FUNCTIONS[aggregateFn](seriesChunk),
};
}
);
if (rollupGraph && timeseriesLength > 600) {
const avgData: SeriesDataUnit[] = [];
const minData: SeriesDataUnit[] = [];
const maxData: SeriesDataUnit[] = [];
const chunkSize = getBucketSize(timeWindow, timeseriesData[0].data.length);
chunk(timeseriesData[0].data, chunkSize).forEach(seriesChunk => {
avgData.push({
name: seriesChunk[0].name,
value: AGGREGATE_FUNCTIONS.avg(seriesChunk),
});
minData.push({
name: seriesChunk[0].name,
value: AGGREGATE_FUNCTIONS.min(seriesChunk),
});
maxData.push({
name: seriesChunk[0].name,
value: AGGREGATE_FUNCTIONS.max(seriesChunk),
});
});
timeseriesData = [
timeseriesData[0],
{seriesName: t('Minimum'), data: minData},
{seriesName: t('Average'), data: avgData},
{seriesName: t('Maximum'), data: maxData},
];
}
}

Expand All @@ -197,26 +209,17 @@ class TriggersChart extends React.PureComponent<Props, State> {
<Feature features={['internal-catchall']} organization={organization}>
{/* TODO(scttcper): Remove internal aggregate experiment */}
{timeseriesLength && timeseriesLength > 600 && (
<AggregationSelectControl
inline={false}
styles={{
control: provided => ({
...provided,
minHeight: '25px',
height: '25px',
}),
}}
isSearchable={false}
isClearable={false}
disabled={loading || reloading}
name="aggregateFn"
value={aggregateFn}
choices={Object.keys(AGGREGATE_FUNCTIONS).map(fnName => [
fnName,
AGGREGATE_FUNCTION_MAP[fnName],
])}
onChange={this.handleAggregateFunctionChange}
/>
<AggregateContainer>
<Checkbox
id="rollupGraph"
checked={rollupGraph}
onChange={this.handleRollupChange}
/>
<AggregateLabel htmlFor="rollupGraph">
{/* TODO(scttcper): remove (sentry only) */}
{t('Rollup')} (Sentry only)
</AggregateLabel>
</AggregateContainer>
)}
</Feature>
<PeriodSelectControl
Expand Down Expand Up @@ -297,13 +300,14 @@ const ControlsContainer = styled('div')`
margin-bottom: ${space(0.5)};
`;

const AggregationSelectControl = styled(SelectControl)`
display: inline-block;
width: 120px;
margin-right: ${space(1)};
const AggregateContainer = styled('div')`
display: flex;
margin-right: ${space(1.5)};
`;

const AggregateLabel = styled('label')`
margin-left: ${space(1)};
font-weight: normal;
text-transform: none;
border: 0;
`;

const PeriodSelectControl = styled(SelectControl)`
Expand Down

0 comments on commit d8bdb42

Please sign in to comment.