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
133 changes: 133 additions & 0 deletions static/app/views/alerts/changeAlerts/comparisonMarklines.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import MarkLine from 'app/components/charts/components/markLine';
import {LineChartSeries} from 'app/components/charts/lineChart';
import {t} from 'app/locale';
import {Series} from 'app/types/echarts';
import {MINUTE} from 'app/utils/formatters';
import theme from 'app/utils/theme';
import {AlertRuleThresholdType, Trigger} from 'app/views/alerts/incidentRules/types';

const checkChangeStatus = (
value: number,
thresholdType: AlertRuleThresholdType,
triggers: Trigger[]
): string => {
const criticalTrigger = triggers?.find(trig => trig.label === 'critical');
const warningTrigger = triggers?.find(trig => trig.label === 'warning');
const criticalTriggerAlertThreshold =
typeof criticalTrigger?.alertThreshold === 'number'
? criticalTrigger.alertThreshold
: undefined;
const warningTriggerAlertThreshold =
typeof warningTrigger?.alertThreshold === 'number'
? warningTrigger.alertThreshold
: undefined;

// Need to catch the critical threshold cases before warning threshold cases
if (
thresholdType === AlertRuleThresholdType.ABOVE &&
criticalTriggerAlertThreshold &&
value >= criticalTriggerAlertThreshold
) {
return 'critical';
}
if (
thresholdType === AlertRuleThresholdType.ABOVE &&
warningTriggerAlertThreshold &&
value >= warningTriggerAlertThreshold
) {
return 'warning';
}
// When threshold is below(lower than in comparison alerts) the % diff value is negative
// It crosses the threshold if its abs value is greater than threshold
// -80% change crosses below 60% threshold -1 * (-80) > 60
if (
thresholdType === AlertRuleThresholdType.BELOW &&
criticalTriggerAlertThreshold &&
-1 * value >= criticalTriggerAlertThreshold
) {
return 'critical';
}
if (
thresholdType === AlertRuleThresholdType.BELOW &&
warningTriggerAlertThreshold &&
-1 * value >= warningTriggerAlertThreshold
) {
return 'warning';
}

return '';
};

export const getComparisonMarkLines = (
timeseriesData: Series[] = [],
comparisonTimeseriesData: Series[] = [],
timeWindow: number,
triggers: Trigger[],
thresholdType: AlertRuleThresholdType
): LineChartSeries[] => {
const changeStatuses: {name: number | string; status: string}[] = [];

if (
timeseriesData?.[0]?.data !== undefined &&
timeseriesData[0].data.length > 1 &&
comparisonTimeseriesData?.[0]?.data !== undefined &&
comparisonTimeseriesData[0].data.length > 1
) {
const changeData = comparisonTimeseriesData[0].data;
const baseData = timeseriesData[0].data;

if (triggers.some(({alertThreshold}) => typeof alertThreshold === 'number')) {
const lastPointLimit =
(baseData[changeData.length - 1].name as number) - timeWindow * MINUTE;
changeData.forEach(({name, value: comparisonValue}, idx) => {
const baseValue = baseData[idx].value;
const comparisonPercentage =
comparisonValue === 0
? baseValue === 0
? 0
: Infinity
: ((baseValue - comparisonValue) / comparisonValue) * 100;
const status = checkChangeStatus(comparisonPercentage, thresholdType, triggers);
if (
idx === 0 ||
idx === changeData.length - 1 ||
status !== changeStatuses[changeStatuses.length - 1].status
) {
changeStatuses.push({name, status});
}
});

return changeStatuses.slice(0, -1).map(({name, status}, idx) => ({
seriesName: t('status'),
type: 'line',
markLine: MarkLine({
silent: true,
lineStyle: {
color:
status === 'critical'
? theme.red300
: status === 'warning'
? theme.yellow300
: theme.green300,
type: 'solid',
width: 4,
},
data: [
[
{coord: [name, 0]},
{
coord: [
Math.min(changeStatuses[idx + 1].name as number, lastPointLimit),
0,
],
},
] as any,
],
}),
data: [],
}));
}
}

return [];
};
142 changes: 11 additions & 131 deletions static/app/views/alerts/incidentRules/triggers/chart/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import minBy from 'lodash/minBy';
import {fetchTotalCount} from 'app/actionCreators/events';
import {Client} from 'app/api';
import Feature from 'app/components/acl/feature';
import MarkLine from 'app/components/charts/components/markLine';
import EventsRequest from 'app/components/charts/eventsRequest';
import {LineChartSeries} from 'app/components/charts/lineChart';
import OptionSelector from 'app/components/charts/optionSelector';
Expand All @@ -25,21 +24,19 @@ import {t} from 'app/locale';
import space from 'app/styles/space';
import {Organization, Project} from 'app/types';
import {Series, SeriesDataUnit} from 'app/types/echarts';
import {MINUTE} from 'app/utils/formatters';
import {
getCrashFreeRateSeries,
MINUTES_THRESHOLD_TO_DISPLAY_SECONDS,
} from 'app/utils/sessions';
import theme from 'app/utils/theme';
import withApi from 'app/utils/withApi';
import {getComparisonMarkLines} from 'app/views/alerts/changeAlerts/comparisonMarklines';
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';

import {
AlertRuleComparisonType,
AlertRuleThresholdType,
Dataset,
IncidentRule,
SessionsAggregate,
Expand Down Expand Up @@ -227,80 +224,6 @@ class TriggersChart extends React.PureComponent<Props, State> {
);
}

getComparisonMarkLines(
timeseriesData: Series[] = [],
comparisonTimeseriesData: Series[] = []
): LineChartSeries[] {
const {timeWindow} = this.props;
const changeStatuses: {name: number | string; status: string}[] = [];

if (
timeseriesData?.[0]?.data !== undefined &&
timeseriesData[0].data.length > 1 &&
comparisonTimeseriesData?.[0]?.data !== undefined &&
comparisonTimeseriesData[0].data.length > 1
) {
const changeData = comparisonTimeseriesData[0].data;
const baseData = timeseriesData[0].data;

if (
this.props.triggers.some(({alertThreshold}) => typeof alertThreshold === 'number')
) {
const lastPointLimit =
(baseData[changeData.length - 1].name as number) - timeWindow * MINUTE;
changeData.forEach(({name, value: comparisonValue}, idx) => {
const baseValue = baseData[idx].value;
const comparisonPercentage =
comparisonValue === 0
? baseValue === 0
? 0
: Infinity
: ((baseValue - comparisonValue) / comparisonValue) * 100;
const status = this.checkChangeStatus(comparisonPercentage);
if (
idx === 0 ||
idx === changeData.length - 1 ||
status !== changeStatuses[changeStatuses.length - 1].status
) {
changeStatuses.push({name, status});
}
});

return changeStatuses.slice(0, -1).map(({name, status}, idx) => ({
seriesName: t('status'),
type: 'line',
markLine: MarkLine({
silent: true,
lineStyle: {
color:
status === 'critical'
? theme.red300
: status === 'warning'
? theme.yellow300
: theme.green300,
type: 'solid',
width: 4,
},
data: [
[
{coord: [name, 0]},
{
coord: [
Math.min(changeStatuses[idx + 1].name as number, lastPointLimit),
0,
],
},
] as any,
],
}),
data: [],
}));
}
}

return [];
}

async fetchTotalCount() {
const {api, organization, environment, projects, query} = this.props;
const statsPeriod = this.getStatsPeriod();
Expand All @@ -318,55 +241,6 @@ class TriggersChart extends React.PureComponent<Props, State> {
}
}

checkChangeStatus(value: number): string {
const {thresholdType, triggers} = this.props;
const criticalTrigger = triggers?.find(trig => trig.label === 'critical');
const warningTrigger = triggers?.find(trig => trig.label === 'warning');
const criticalTriggerAlertThreshold =
typeof criticalTrigger?.alertThreshold === 'number'
? criticalTrigger.alertThreshold
: undefined;
const warningTriggerAlertThreshold =
typeof warningTrigger?.alertThreshold === 'number'
? warningTrigger.alertThreshold
: undefined;

// Need to catch the critical threshold cases before warning threshold cases
if (
thresholdType === AlertRuleThresholdType.ABOVE &&
criticalTriggerAlertThreshold &&
value >= criticalTriggerAlertThreshold
) {
return 'critical';
}
if (
thresholdType === AlertRuleThresholdType.ABOVE &&
warningTriggerAlertThreshold &&
value >= warningTriggerAlertThreshold
) {
return 'warning';
}
// When threshold is below(lower than in comparison alerts) the % diff value is negative
// It crosses the threshold if its abs value is greater than threshold
// -80% change crosses below 60% threshold -1 * (-80) > 60
if (
thresholdType === AlertRuleThresholdType.BELOW &&
criticalTriggerAlertThreshold &&
-1 * value >= criticalTriggerAlertThreshold
) {
return 'critical';
}
if (
thresholdType === AlertRuleThresholdType.BELOW &&
warningTriggerAlertThreshold &&
-1 * value >= warningTriggerAlertThreshold
) {
return 'warning';
}

return '';
}

renderChart(
timeseriesData: Series[] = [],
isLoading: boolean,
Expand Down Expand Up @@ -448,11 +322,14 @@ class TriggersChart extends React.PureComponent<Props, State> {
aggregate,
environment,
comparisonDelta,
triggers,
thresholdType,
} = this.props;

const period = this.getStatsPeriod();
const renderComparisonStats =
organization.features.includes('change-alerts') && comparisonDelta;
const renderComparisonStats = Boolean(
organization.features.includes('change-alerts') && comparisonDelta
);

return isSessionAggregate(aggregate) ? (
<SessionsRequest
Expand Down Expand Up @@ -513,9 +390,12 @@ class TriggersChart extends React.PureComponent<Props, State> {
{({loading, reloading, timeseriesData, comparisonTimeseriesData}) => {
let comparisonMarkLines: LineChartSeries[] = [];
if (renderComparisonStats && comparisonTimeseriesData) {
comparisonMarkLines = this.getComparisonMarkLines(
comparisonMarkLines = getComparisonMarkLines(
timeseriesData,
comparisonTimeseriesData
comparisonTimeseriesData,
timeWindow,
triggers,
thresholdType
);
}

Expand Down
2 changes: 1 addition & 1 deletion static/app/views/alerts/issueRuleEditor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ import withTeams from 'app/utils/withTeams';
import {
CHANGE_ALERT_CONDITION_IDS,
CHANGE_ALERT_PLACEHOLDERS_LABELS,
} from 'app/views/alerts/issueRuleEditor/constants/changeAlerts';
} from 'app/views/alerts/changeAlerts/constants';
import AsyncView from 'app/views/asyncView';
import Input from 'app/views/settings/components/forms/controls/input';
import Field from 'app/views/settings/components/forms/field';
Expand Down
6 changes: 3 additions & 3 deletions static/app/views/alerts/issueRuleEditor/ruleNodeList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ import {
IssueAlertRuleCondition,
IssueAlertRuleConditionTemplate,
} from 'app/types/alerts';
import {EVENT_FREQUENCY_PERCENT_CONDITION} from 'app/views/projectInstall/issueAlertOptions';

import {
CHANGE_ALERT_CONDITION_IDS,
COMPARISON_INTERVAL_CHOICES,
COMPARISON_TYPE_CHOICE_VALUES,
COMPARISON_TYPE_CHOICES,
} from './constants/changeAlerts';
} from 'app/views/alerts/changeAlerts/constants';
import {EVENT_FREQUENCY_PERCENT_CONDITION} from 'app/views/projectInstall/issueAlertOptions';

import RuleNode from './ruleNode';

type Props = {
Expand Down
Loading