Skip to content

Commit

Permalink
feat: add alerting period as a form prop
Browse files Browse the repository at this point in the history
- Use it to calculate the approx. number for test executions along with frequency
- Send it in the POST request
  • Loading branch information
VikaCep committed Feb 26, 2025
1 parent a6e34f7 commit 4f61703
Show file tree
Hide file tree
Showing 8 changed files with 107 additions and 47 deletions.
6 changes: 6 additions & 0 deletions src/checkUsageCalc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ export const getTotalChecksPerMonth = (probeCount: number, frequencySeconds: num
return checksPerMonth * probeCount;
};

export const getTotalChecksPerPeriod = (probeCount: number, frequencySeconds: number, periodInSeconds: number) => {
const checksPerMinute = 60 / frequencySeconds;
const checksPerPeriod = checksPerMinute * (periodInSeconds / 60) * probeCount;
return Math.round(checksPerPeriod);
};

const getLogsGbPerMonth = (probeCount: number, frequencySeconds: number) => {
const gbPerCheck = 0.0008;
const checksPerMonth = getChecksPerMonth(frequencySeconds);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export function getAlertsPayload(formValues?: CheckAlertFormRecord, checkId?: nu
alerts.push({
name: alertType as CheckAlertType,
threshold: alert.threshold!!,
period: alert.period,
});
}
return alerts;
Expand Down
17 changes: 10 additions & 7 deletions src/components/CheckForm/AlertsPerCheck/AlertItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,14 @@ export const AlertItem = ({
const job = getValues('job');
const instance = getValues('target');
const threshold = getValues(`alerts.${alert.type}.threshold`);
const period = getValues(`alerts.${alert.type}.period`);

const query = alert.query
.replace(/\$instance/g, instance)
.replace(/\$job/g, job)
.replace(/\$threshold/g, threshold);
.replace(/\$threshold/g, threshold)
.replace(/\$period/g, period);

const exploreLink = ds && getValues('id') && threshold && createExploreLink(ds.name, query);
const tooltipContent = (
<div>
Expand All @@ -68,12 +71,12 @@ export const AlertItem = ({
return (
<div key={alert.type} className={styles.item}>
{alert.type === CheckAlertType.ProbeFailedExecutionsTooHigh && (
<Stack alignItems="center">
<FailedExecutionsAlert alert={alert} selected={selected} onSelectionChange={handleToggleAlert} />
<Tooltip content={tooltipContent} placement="bottom" interactive={true}>
<Icon name="info-circle" />
</Tooltip>
</Stack>
<FailedExecutionsAlert
alert={alert}
selected={selected}
onSelectionChange={handleToggleAlert}
tooltipContent={tooltipContent}
/>
)}

{alert.type !== CheckAlertType.ProbeFailedExecutionsTooHigh && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface PredefinedAlertInterface {
query: string;
default?: number;
hide: boolean;
supportsPeriod: boolean;
}

export const ALERT_PENDING_PERIODS = [
Expand All @@ -26,18 +27,19 @@ export const GLOBAL_PREDEFINED_ALERTS: PredefinedAlertInterface[] = [
name: 'Probe Failed Executions Too High',
description: '',
hide: false,
supportsPeriod: true,
unit: 'no.',
category: CheckAlertCategory.FailedChecks,
default: 10,
query: `
(
(1 - (
sum by (instance, job) (
increase(probe_all_success_sum{instance="$instance", job="$job"}[5m])
increase(probe_all_success_sum{instance="$instance", job="$job"}[$period])
)
/
sum by (instance, job) (
increase(probe_all_success_count{instance="$instance", job="$job"}[5m])
increase(probe_all_success_count{instance="$instance", job="$job"}[$period])
)
)) * 100
>
Expand All @@ -53,8 +55,9 @@ export const HTTP_PREDEFINED_ALERTS: PredefinedAlertInterface[] = [
{
type: CheckAlertType.HTTPRequestDurationTooHighP50,
name: 'HTTP Request Duration Too High (P50)',
description: 'Trigger an alert if the p50 for the HTTP request is higher than $thresholdms for the last 5 minutes',
description: 'Trigger an alert if the p50 for the HTTP request is higher than $thresholdms',
hide: true,
supportsPeriod: false,
unit: 'ms',
category: CheckAlertCategory.RequestDuration,
default: 300,
Expand All @@ -76,8 +79,9 @@ export const HTTP_PREDEFINED_ALERTS: PredefinedAlertInterface[] = [
{
type: CheckAlertType.HTTPRequestDurationTooHighP90,
name: 'HTTP Request Duration Too High (P90)',
description: 'Trigger an alert if the p90 for the HTTP request is higher than $thresholdms for the last 5 minutes',
description: 'Trigger an alert if the p90 for the HTTP request is higher than $thresholdms',
hide: true,
supportsPeriod: false,
unit: 'ms',
category: CheckAlertCategory.RequestDuration,
default: 500,
Expand All @@ -98,8 +102,9 @@ export const HTTP_PREDEFINED_ALERTS: PredefinedAlertInterface[] = [
{
type: CheckAlertType.HTTPRequestDurationTooHighP95,
name: 'HTTP Request Duration Too High (P95)',
description: 'Trigger an alert if the p95 for the HTTP request is higher than $thresholdms for the last 5 minutes',
description: 'Trigger an alert if the p95 for the HTTP request is higher than $thresholdms',
hide: true,
supportsPeriod: false,
unit: 'ms',
category: CheckAlertCategory.RequestDuration,
default: 800,
Expand All @@ -120,8 +125,9 @@ export const HTTP_PREDEFINED_ALERTS: PredefinedAlertInterface[] = [
{
type: CheckAlertType.HTTPRequestDurationTooHighP99,
name: 'HTTP Request Duration Too High (P99)',
description: 'Trigger an alert if the p99 for the HTTP request is higher than $thresholdms for the last 5 minutes',
description: 'Trigger an alert if the p99 for the HTTP request is higher than $thresholdms',
hide: true,
supportsPeriod: false,
unit: 'ms',
category: CheckAlertCategory.RequestDuration,
default: 1500,
Expand All @@ -144,6 +150,7 @@ export const HTTP_PREDEFINED_ALERTS: PredefinedAlertInterface[] = [
name: 'HTTP Target Certificate Close To Expiring',
description: 'Trigger an alert if the target certificate will expire in less than $threshold days',
hide: false,
supportsPeriod: false,
unit: 'd',
category: CheckAlertCategory.SystemHealth,
default: 60,
Expand All @@ -167,8 +174,9 @@ export const PING_PREDEFINED_ALERTS: PredefinedAlertInterface[] = [
{
type: CheckAlertType.PingICMPDurationTooHighP50,
name: 'Ping ICMP Duration Too High (P50)',
description: 'Trigger an alert if the p50 for the ICMP request is higher than $thresholdms for the last 5 minutes',
description: 'Trigger an alert if the p50 for the ICMP request is higher than $thresholdms',
hide: true,
supportsPeriod: false,
unit: 'ms',
category: CheckAlertCategory.RequestDuration,
default: 50,
Expand All @@ -189,8 +197,9 @@ export const PING_PREDEFINED_ALERTS: PredefinedAlertInterface[] = [
{
type: CheckAlertType.PingICMPDurationTooHighP90,
name: 'Ping ICMP Duration Too High (P90)',
description: 'Trigger an alert if the p90 for the ICMP request is higher than $thresholdms for the last 5 minutes',
description: 'Trigger an alert if the p90 for the ICMP request is higher than $thresholdms',
hide: true,
supportsPeriod: false,
unit: 'ms',
category: CheckAlertCategory.RequestDuration,
default: 100,
Expand All @@ -211,8 +220,9 @@ export const PING_PREDEFINED_ALERTS: PredefinedAlertInterface[] = [
{
type: CheckAlertType.PingICMPDurationTooHighP95,
name: 'Ping ICMP Duration Too High (P95)',
description: 'Trigger an alert if the p95 for the ICMP request is higher than $thresholdms for the last 5 minutes',
description: 'Trigger an alert if the p95 for the ICMP request is higher than $thresholdms',
hide: true,
supportsPeriod: false,
unit: 'ms',
category: CheckAlertCategory.RequestDuration,
default: 200,
Expand All @@ -233,8 +243,9 @@ export const PING_PREDEFINED_ALERTS: PredefinedAlertInterface[] = [
{
type: CheckAlertType.PingICMPDurationTooHighP99,
name: 'Ping ICMP Duration Too High (P99)',
description: 'Trigger an alert if the p99 for the ICMP request is higher than $thresholdms for the last 5 minutes',
description: 'Trigger an alert if the p99 for the ICMP request is higher than $thresholdms',
hide: true,
supportsPeriod: false,
unit: 'ms',
category: CheckAlertCategory.RequestDuration,
default: 400,
Expand Down
1 change: 1 addition & 0 deletions src/components/CheckForm/AlertsPerCheck/AlertsPerCheck.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export const AlertsPerCheck = ({ onInitAlerts }: AlertsPerCheckProps) => {
...acc,
[alert.name]: {
threshold: alert.threshold,
period: alert.period,
isSelected: true,
},
};
Expand Down
86 changes: 64 additions & 22 deletions src/components/CheckForm/AlertsPerCheck/FailedExecutionsAlert.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React, { useMemo } from 'react';
import { useFormContext } from 'react-hook-form';
import React, { useCallback, useMemo } from 'react';
import { Controller, useFormContext } from 'react-hook-form';
import { durationToMilliseconds, parseDuration } from '@grafana/data';
import { Checkbox, Select, Stack } from '@grafana/ui';
import { Checkbox, Icon, PopoverContent, Select, Stack, Tooltip } from '@grafana/ui';
import { getTotalChecksPerPeriod } from 'checkUsageCalc';

import { CheckAlertType, CheckFormValues } from 'types';

Expand All @@ -13,48 +14,89 @@ export const FailedExecutionsAlert = ({
alert,
selected,
onSelectionChange,
tooltipContent,
}: {
alert: PredefinedAlertInterface;
selected: boolean;
onSelectionChange: (type: CheckAlertType) => void;
tooltipContent: PopoverContent;
}) => {
const { isFormDisabled } = useCheckFormContext();
const { getValues } = useFormContext<CheckFormValues>();
const { getValues, control } = useFormContext<CheckFormValues>();

const handleToggleAlert = (type: CheckAlertType) => {
onSelectionChange(type);
};

const checkFrequency = getValues('frequency');
const probes = getValues('probes');

//min time range >= 2.5 * check frequency
const convertPeriodToSeconds = (period: { label: string; value: string }) =>
durationToMilliseconds(parseDuration(period.value)) / 1000;
const convertPeriodToSeconds = useCallback(
(period: string) => durationToMilliseconds(parseDuration(period)) / 1000,
[]
);

const validPendingPeriods = useMemo(
() => ALERT_PENDING_PERIODS.filter((period) => convertPeriodToSeconds(period) >= checkFrequency * 2.5),
[checkFrequency]
() => ALERT_PENDING_PERIODS.filter((period) => convertPeriodToSeconds(period.value) >= checkFrequency * 2.5),
[checkFrequency, convertPeriodToSeconds]
);

const defaultPeriod = validPendingPeriods[0];
const period = getValues(`alerts.${alert.type}.period`) || defaultPeriod.value;

const testExecutionsPerPeriod = useMemo(() => {
if (!period) {
return '';
}
return getTotalChecksPerPeriod(probes.length, checkFrequency, convertPeriodToSeconds(period));
}, [checkFrequency, period, probes, convertPeriodToSeconds]);

const customTooltipContent: PopoverContent = (
<div>
The number of test executions is an approximation based on the check&apos;s frequency and the pending period
chosen for this alert.
{tooltipContent as React.ReactNode}
</div>
);

return (
<Stack alignItems="center">
<Checkbox
id={`alert-${alert.type}`}
data-testid={`checkbox-alert-${alert.type}`}
onClick={() => handleToggleAlert(alert.type)}
checked={selected}
/>
<Stack alignItems="center">
Trigger an alert if more than <ThresholdSelector alert={alert} selected={selected} />
of tests fail for
<Select
disabled={!selected || isFormDisabled}
data-testid="alertPendingPeriod"
options={validPendingPeriods}
defaultValue={validPendingPeriods[0]}
onChange={(e) => {}}
<Checkbox
id={`alert-${alert.type}`}
data-testid={`checkbox-alert-${alert.type}`}
onClick={() => handleToggleAlert(alert.type)}
checked={selected}
/>
<Stack alignItems="center">
Trigger an alert if more than <ThresholdSelector alert={alert} selected={selected} />
of {testExecutionsPerPeriod} tests fail for
<Controller
name={`alerts.${alert.type}.period`}
control={control}
render={({ field }) => (
<Select
{...field}
disabled={!selected || isFormDisabled}
data-testid="alertPendingPeriod"
options={validPendingPeriods}
defaultValue={defaultPeriod.value}
value={field.value || defaultPeriod.value}
onChange={(value) => {
if (value === null) {
return field.onChange(null);
}
field.onChange(value?.value);
}}
/>
)}
/>
</Stack>
</Stack>
<Tooltip content={customTooltipContent} placement="bottom" interactive={true}>
<Icon name="info-circle" />
</Tooltip>
</Stack>
);
};
4 changes: 2 additions & 2 deletions src/schemas/general/CheckAlerts.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { z, ZodType } from 'zod';

import { CheckAlertFormRecord, ThresholdSelectorType } from 'types';
import { CheckAlertFormRecord } from 'types';

const isScientificNotation = (val: number) => {
return /e|E/.test(val.toString());
Expand All @@ -10,7 +10,7 @@ const CheckAlertSchema = z
.object({
id: z.number().optional(),
isSelected: z.boolean().optional(),
thresholdUnit: z.nativeEnum(ThresholdSelectorType).optional(),
period: z.string().optional(),
threshold: z
.number()
.optional()
Expand Down
8 changes: 2 additions & 6 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,15 +321,10 @@ export interface AlertFormValues {
}
export interface CheckAlertFormValues {
threshold?: number;
thresholdUnit?: ThresholdSelectorType;
period?: string;
isSelected?: boolean;
}

export enum ThresholdSelectorType {
Number = 'number',
Percentage = '%',
}

export type CheckAlertFormRecord = Partial<Record<CheckAlertType, CheckAlertFormValues>>;

export type CheckFormValuesBase = Omit<Check, 'settings' | 'basicMetricsOnly'> & {
Expand Down Expand Up @@ -678,6 +673,7 @@ export enum CheckAlertCategory {
export type CheckAlertDraft = {
name: CheckAlertType;
threshold: number;
period?: string;
};

export type CheckAlertPublished = CheckAlertDraft & {
Expand Down

0 comments on commit 4f61703

Please sign in to comment.