add alerts to monitor step

This commit is contained in:
Simon Larsen 2024-10-02 23:00:47 +01:00
parent 11a4f3442d
commit 49a2ee18a1
No known key found for this signature in database
GPG Key ID: 96C5DCA24769DBCA
10 changed files with 395 additions and 0 deletions

View File

@ -0,0 +1,11 @@
import ObjectID from "../ObjectID";
export interface CriteriaAlert {
title: string;
description: string;
alertSeverityId?: ObjectID | undefined;
autoResolveAlert?: boolean | undefined;
remediationNotes?: string | undefined;
id: string;
onCallPolicyIds?: Array<ObjectID> | undefined;
}

View File

@ -4,6 +4,7 @@ import { JSONObject, ObjectType } from "../JSON";
import JSONFunctions from "../JSONFunctions";
import ObjectID from "../ObjectID";
import Typeof from "../Typeof";
import { CriteriaAlert } from "./CriteriaAlert";
import {
CheckOn,
CriteriaFilter,
@ -19,10 +20,12 @@ export interface MonitorCriteriaInstanceType {
filterCondition: FilterCondition;
filters: Array<CriteriaFilter>;
incidents: Array<CriteriaIncident>;
alerts: Array<CriteriaAlert>;
name: string;
description: string;
changeMonitorStatus?: boolean | undefined;
createIncidents?: boolean | undefined;
createAlerts?: boolean | undefined;
id: string;
}
@ -43,8 +46,10 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
},
],
createIncidents: false,
createAlerts: false,
changeMonitorStatus: false,
incidents: [],
alerts: [],
name: "",
description: "",
};
@ -71,6 +76,8 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
},
],
incidents: [],
alerts: [],
createAlerts: false,
changeMonitorStatus: true,
createIncidents: false,
name: `Check if ${arg.monitorName} is online`,
@ -96,8 +103,10 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
},
],
incidents: [],
alerts: [],
changeMonitorStatus: true,
createIncidents: false,
createAlerts: false,
name: `Check if ${arg.monitorName} is online`,
description: `This criteria checks if the ${arg.monitorName} is online`,
};
@ -121,6 +130,8 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
},
],
incidents: [],
alerts: [],
createAlerts: false,
changeMonitorStatus: true,
createIncidents: false,
name: `Check if ${arg.monitorName} is online`,
@ -146,6 +157,8 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
},
],
incidents: [],
alerts: [],
createAlerts: false,
changeMonitorStatus: true,
createIncidents: false,
name: `Check if ${arg.monitorName} is online`,
@ -174,6 +187,8 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
},
],
incidents: [],
alerts: [],
createAlerts: false,
changeMonitorStatus: true,
createIncidents: false,
name: `Check if ${arg.monitorName} is online`,
@ -199,6 +214,8 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
},
],
incidents: [],
alerts: [],
createAlerts: false,
changeMonitorStatus: true,
createIncidents: false,
name: `Check if ${arg.monitorName} is online`,
@ -230,6 +247,8 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
},
],
incidents: [],
alerts: [],
createAlerts: false,
changeMonitorStatus: true,
createIncidents: false,
name: `Check if ${arg.monitorName} is online`,
@ -291,6 +310,8 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
],
changeMonitorStatus: true,
createIncidents: true,
createAlerts: false,
alerts: [],
name: `Check if ${arg.monitorName} is offline`,
description: `This criteria checks if the ${arg.monitorName} is offline`,
};
@ -316,6 +337,8 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
value: 200,
},
],
alerts: [],
createAlerts: false,
incidents: [
{
title: `${arg.monitorName} is offline`,
@ -355,6 +378,8 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
onCallPolicyIds: [],
},
],
alerts: [],
createAlerts: false,
changeMonitorStatus: true,
createIncidents: true,
name: `Check if ${arg.monitorName} is offline`,
@ -374,6 +399,8 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
value: 0, // if there are no logs then the monitor is offline
},
],
alerts: [],
createAlerts: false,
incidents: [
{
title: `${arg.monitorName} is offline`,
@ -403,6 +430,8 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
value: 30, // if the request is not recieved in 30 minutes, then the monitor is offline
},
],
alerts: [],
createAlerts: false,
incidents: [
{
title: `${arg.monitorName} is offline`,
@ -435,6 +464,8 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
value: undefined,
},
],
alerts: [],
createAlerts: false,
incidents: [
{
title: `${arg.monitorName} is offline`,
@ -457,6 +488,8 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
id: ObjectID.generate().toString(),
monitorStatusId: arg.monitorStatusId,
filterCondition: FilterCondition.Any,
alerts: [],
createAlerts: false,
filters: [
{
checkOn: CheckOn.IsNotAValidCertificate,
@ -626,6 +659,17 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
return this;
}
public setAlerts(
alerts: Array<CriteriaAlert>,
): MonitorCriteriaInstance {
if (this.data) {
this.data.alerts = [...alerts];
}
return this;
}
public setChangeMonitorStatus(
changeMonitorStatus: boolean | undefined,
): MonitorCriteriaInstance {
@ -646,6 +690,16 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
return this;
}
public setCreateAlerts(
createAlerts: boolean | undefined,
): MonitorCriteriaInstance {
if (this.data) {
this.data.createAlerts = createAlerts;
}
return this;
}
public override toJSON(): JSONObject {
if (!this.data) {
return MonitorCriteriaInstance.getNewMonitorCriteriaInstanceAsJSON();

View File

@ -77,6 +77,7 @@ export default class MonitorStep extends DatabaseProperty {
onlineMonitorStatusId: ObjectID;
offlineMonitorStatusId: ObjectID;
defaultIncidentSeverityId: ObjectID;
defaultAlertSeverityId: ObjectID;
}): MonitorStep {
const monitorStep: MonitorStep = new MonitorStep();

View File

@ -40,6 +40,7 @@ export default class MonitorSteps extends DatabaseProperty {
onlineMonitorStatusId: ObjectID;
offlineMonitorStatusId: ObjectID;
defaultIncidentSeverityId: ObjectID;
defaultAlertSeverityId: ObjectID;
}): MonitorSteps {
const monitorSteps: MonitorSteps = new MonitorSteps();

View File

@ -16,6 +16,7 @@ export interface ComponentProps {
onChange?: undefined | ((value: MonitorCriteria) => void);
monitorStatusDropdownOptions: Array<DropdownOption>;
incidentSeverityDropdownOptions: Array<DropdownOption>;
alertSeverityDropdownOptions: Array<DropdownOption>;
onCallPolicyDropdownOptions: Array<DropdownOption>;
monitorType: MonitorType;
}
@ -50,6 +51,9 @@ const MonitorCriteriaElement: FunctionComponent<ComponentProps> = (
incidentSeverityDropdownOptions={
props.incidentSeverityDropdownOptions
}
alertSeverityDropdownOptions={
props.alertSeverityDropdownOptions
}
onCallPolicyDropdownOptions={props.onCallPolicyDropdownOptions}
initialValue={i}
onDelete={() => {

View File

@ -0,0 +1,143 @@
import { CriteriaAlert } from "Common/Types/Monitor/CriteriaAlert";
import Button, { ButtonStyleType } from "Common/UI/Components/Button/Button";
import { DropdownOption } from "Common/UI/Components/Dropdown/Dropdown";
import BasicForm from "Common/UI/Components/Forms/BasicForm";
import FormFieldSchemaType from "Common/UI/Components/Forms/Types/FormFieldSchemaType";
import FormValues from "Common/UI/Components/Forms/Types/FormValues";
import Alert from "Common/Models/DatabaseModels/Alert";
import React, { FunctionComponent, ReactElement, useEffect } from "react";
export interface ComponentProps {
initialValue?: undefined | CriteriaAlert;
onChange?: undefined | ((value: CriteriaAlert) => void);
alertSeverityDropdownOptions: Array<DropdownOption>;
onCallPolicyDropdownOptions: Array<DropdownOption>;
// onDelete?: undefined | (() => void);
}
const MonitorCriteriaAlertForm: FunctionComponent<ComponentProps> = (
props: ComponentProps,
): ReactElement => {
const [showAdvancedFields, setShowAdvancedFields] =
React.useState<boolean>(false);
useEffect(() => {
if (props.initialValue && props.initialValue.remediationNotes) {
setShowAdvancedFields(true);
}
}, [props.initialValue]);
return (
<div className="mt-4">
<BasicForm
modelType={Alert}
hideSubmitButton={true}
initialValues={props.initialValue}
onChange={(values: FormValues<CriteriaAlert>) => {
props.onChange && props.onChange(values as CriteriaAlert);
}}
disableAutofocus={true}
fields={[
{
field: {
title: true,
},
title: "Alert Title",
fieldType: FormFieldSchemaType.Text,
stepId: "alert-details",
required: true,
placeholder: "Alert Title",
validation: {
minLength: 2,
},
},
{
field: {
description: true,
},
title: "Alert Description",
stepId: "alert-details",
fieldType: FormFieldSchemaType.Markdown,
required: true,
placeholder: "Description",
},
{
field: {
alertSeverityId: true,
},
title: "Alert Severity",
stepId: "alert-details",
description: "What type of alert is this?",
fieldType: FormFieldSchemaType.Dropdown,
dropdownOptions: props.alertSeverityDropdownOptions,
required: true,
placeholder: "Alert Severity",
id: "alert-severity",
},
{
field: {
onCallPolicyIds: true,
},
title: "On-Call Policy",
stepId: "alert-details",
description:
"Execute these on-call policies when this alert is created.",
fieldType: FormFieldSchemaType.MultiSelectDropdown,
dropdownOptions: props.onCallPolicyDropdownOptions,
required: false,
placeholder: "Select On-Call Policies",
},
{
field: {
autoResolveAlert: true,
},
title: "Auto Resolve Alert",
stepId: "alert-details",
description:
"Automatically resolve this alert when this criteria is no longer met.",
fieldType: FormFieldSchemaType.Toggle,
required: false,
},
{
field: {
remediationNotes: true,
},
title: "Remediation Notes",
stepId: "alert-details",
description:
"Notes to help the on-call engineer resolve this alert.",
fieldType: FormFieldSchemaType.Markdown,
required: false,
showIf: () => {
return showAdvancedFields;
},
},
]}
/>
{!showAdvancedFields && (
<Button
title="Add Remediation Notes for an engineer to help resolve this alert."
onClick={() => {
return setShowAdvancedFields(true);
}}
className="-ml-3"
buttonStyle={ButtonStyleType.SECONDARY_LINK}
/>
)}
{/* <div className='mt-4'>
<Button
onClick={() => {
if (props.onDelete) {
props.onDelete();
}
}}
title="Delete"
/>
</div> */}
</div>
);
};
export default MonitorCriteriaAlertForm;

View File

@ -0,0 +1,82 @@
import MonitorCriteriaAlertForm from "./MonitorCriteriaAlertForm";
import { CriteriaAlert } from "Common/Types/Monitor/CriteriaAlert";
import ObjectID from "Common/Types/ObjectID";
import { DropdownOption } from "Common/UI/Components/Dropdown/Dropdown";
import React, { FunctionComponent, ReactElement, useEffect } from "react";
export interface ComponentProps {
initialValue: Array<CriteriaAlert> | undefined;
onChange?: undefined | ((value: Array<CriteriaAlert>) => void);
alertSeverityDropdownOptions: Array<DropdownOption>;
onCallPolicyDropdownOptions: Array<DropdownOption>;
}
const MonitorCriteriaAlertsForm: FunctionComponent<ComponentProps> = (
props: ComponentProps,
): ReactElement => {
const [alerts, setAlerts] = React.useState<Array<CriteriaAlert>>(
props.initialValue || [
{
title: "",
description: "",
alertSeverityId: undefined,
id: ObjectID.generate().toString(),
},
],
);
useEffect(() => {
if (alerts && props.onChange) {
props.onChange(alerts);
}
}, [alerts]);
return (
<div className="mt-4">
{alerts.map((i: CriteriaAlert, index: number) => {
return (
<MonitorCriteriaAlertForm
key={index}
alertSeverityDropdownOptions={
props.alertSeverityDropdownOptions
}
onCallPolicyDropdownOptions={props.onCallPolicyDropdownOptions}
initialValue={i}
// onDelete={() => {
// // remove the criteria filter
// const index: number = alerts.indexOf(i);
// const newAlerts: Array<CriteriaAlert> = [
// ...alerts,
// ];
// newAlerts.splice(index, 1);
// setAlerts(newAlerts);
// }}
onChange={(value: CriteriaAlert) => {
const index: number = alerts.indexOf(i);
const newAlerts: Array<CriteriaAlert> = [...alerts];
newAlerts[index] = value;
setAlerts(newAlerts);
}}
/>
);
})}
{/** Future Proofing */}
{/* <Button
title="Add Alert"
onClick={() => {
const newAlerts: Array<CriteriaAlert> = [
...alerts,
];
newAlerts.push({
title: '',
description: '',
alertSeverityId: undefined,
});
}}
/> */}
</div>
);
};
export default MonitorCriteriaAlertsForm;

View File

@ -31,10 +31,13 @@ import React, {
useEffect,
useState,
} from "react";
import MonitorCriteriaAlertsForm from "./MonitorCriteriaAlertsForm";
import { CriteriaAlert } from "Common/Types/Monitor/CriteriaAlert";
export interface ComponentProps {
monitorStatusDropdownOptions: Array<DropdownOption>;
incidentSeverityDropdownOptions: Array<DropdownOption>;
alertSeverityDropdownOptions: Array<DropdownOption>;
onCallPolicyDropdownOptions: Array<DropdownOption>;
monitorType: MonitorType;
initialValue?: undefined | MonitorCriteriaInstance;
@ -88,6 +91,11 @@ const MonitorCriteriaInstanceElement: FunctionComponent<ComponentProps> = (
(props.initialValue?.data?.incidents?.length || 0) > 0,
);
const [showAlertControl, setShowAlertControl] = useState<boolean>(
(props.initialValue?.data?.alerts?.length || 0) > 0,
);
return (
<div className="mt-4">
<div className="mt-5">
@ -285,6 +293,60 @@ const MonitorCriteriaInstanceElement: FunctionComponent<ComponentProps> = (
</div>
)}
<div className="mt-4">
<Toggle
value={showAlertControl}
title="When filters match, create an alert."
onChange={(value: boolean) => {
setShowAlertControl(value);
monitorCriteriaInstance.setCreateAlerts(value);
if (
(value && !monitorCriteriaInstance.data?.alerts) ||
monitorCriteriaInstance.data?.alerts?.length === 0
) {
monitorCriteriaInstance.setAlerts([
{
title: "",
description: "",
alertSeverityId: undefined,
id: ObjectID.generate().toString(),
},
]);
}
if (!value) {
monitorCriteriaInstance.setAlerts([]);
}
setMonitorCriteriaInstance(
MonitorCriteriaInstance.clone(monitorCriteriaInstance),
);
}}
/>
</div>
{showAlertControl && (
<div className="mt-4">
<FieldLabelElement title="Create Alert" />
<MonitorCriteriaAlertsForm
initialValue={monitorCriteriaInstance?.data?.alerts || []}
alertSeverityDropdownOptions={
props.alertSeverityDropdownOptions
}
onCallPolicyDropdownOptions={props.onCallPolicyDropdownOptions}
onChange={(value: Array<CriteriaAlert>) => {
monitorCriteriaInstance.setAlerts(value);
setMonitorCriteriaInstance(
MonitorCriteriaInstance.clone(monitorCriteriaInstance),
);
}}
/>
</div>
)}
<div className="mt-4">
<Toggle
value={showIncidentControl}

View File

@ -59,6 +59,7 @@ import MonitorStepTraceMonitor, {
export interface ComponentProps {
monitorStatusDropdownOptions: Array<DropdownOption>;
incidentSeverityDropdownOptions: Array<DropdownOption>;
alertSeverityDropdownOptions: Array<DropdownOption>;
onCallPolicyDropdownOptions: Array<DropdownOption>;
initialValue?: undefined | MonitorStep;
onChange?: undefined | ((value: MonitorStep) => void);
@ -688,6 +689,7 @@ const MonitorStepElement: FunctionComponent<ComponentProps> = (
incidentSeverityDropdownOptions={
props.incidentSeverityDropdownOptions
}
alertSeverityDropdownOptions={props.alertSeverityDropdownOptions}
onCallPolicyDropdownOptions={props.onCallPolicyDropdownOptions}
initialValue={monitorStep?.data?.monitorCriteria}
onChange={(value: MonitorCriteria) => {

View File

@ -20,6 +20,7 @@ import MonitorStatus from "Common/Models/DatabaseModels/MonitorStatus";
import OnCallDutyPolicy from "Common/Models/DatabaseModels/OnCallDutyPolicy";
import React, { FunctionComponent, ReactElement, useEffect } from "react";
import useAsyncEffect from "use-async-effect";
import AlertSeverity from "Common/Models/DatabaseModels/AlertSeverity";
export interface ComponentProps extends CustomElementProps {
error?: string | undefined;
@ -39,6 +40,10 @@ const MonitorStepsElement: FunctionComponent<ComponentProps> = (
const [incidentSeverityDropdownOptions, setIncidentSeverityDropdownOptions] =
React.useState<Array<DropdownOption>>([]);
const [alertSeverityDropdownOptions, setAlertSeverityDropdownOptions] =
React.useState<Array<DropdownOption>>([]);
const [onCallPolicyDropdownOptions, setOnCallPolicyDropdownOptions] =
React.useState<Array<DropdownOption>>([]);
@ -94,6 +99,22 @@ const MonitorStepsElement: FunctionComponent<ComponentProps> = (
},
});
const alertSeverityList: ListResult<AlertSeverity> =
await ModelAPI.getList({
modelType: AlertSeverity,
query: {},
limit: LIMIT_PER_PROJECT,
skip: 0,
select: {
name: true,
order: true,
},
sort: {
order: SortOrder.Ascending,
},
});
const onCallPolicyList: ListResult<OnCallDutyPolicy> =
await ModelAPI.getList({
modelType: OnCallDutyPolicy,
@ -117,6 +138,18 @@ const MonitorStepsElement: FunctionComponent<ComponentProps> = (
);
}
if (alertSeverityList.data) {
setAlertSeverityDropdownOptions(
alertSeverityList.data.map((i: AlertSeverity) => {
return {
value: i._id!,
label: i.name!,
};
}),
);
}
if (onCallPolicyList.data) {
setOnCallPolicyDropdownOptions(
onCallPolicyList.data.map((i: OnCallDutyPolicy) => {
@ -151,6 +184,7 @@ const MonitorStepsElement: FunctionComponent<ComponentProps> = (
},
)!.id!,
defaultIncidentSeverityId: incidentSeverityList.data[0]!.id!,
defaultAlertSeverityId: alertSeverityList.data[0]!.id!
}),
);
}
@ -192,6 +226,7 @@ const MonitorStepsElement: FunctionComponent<ComponentProps> = (
key={index}
monitorStatusDropdownOptions={monitorStatusDropdownOptions}
incidentSeverityDropdownOptions={incidentSeverityDropdownOptions}
alertSeverityDropdownOptions={alertSeverityDropdownOptions}
onCallPolicyDropdownOptions={onCallPolicyDropdownOptions}
initialValue={i}
// onDelete={() => {