mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 00:32:12 +02:00
391 lines
13 KiB
TypeScript
391 lines
13 KiB
TypeScript
import CreateBy from "../Types/Database/CreateBy";
|
|
import { OnCreate, OnUpdate } from "../Types/Database/Hooks";
|
|
import DatabaseService from "./DatabaseService";
|
|
import IncidentService from "./IncidentService";
|
|
import OnCallDutyPolicyExecutionLogTimelineService from "./OnCallDutyPolicyExecutionLogTimelineService";
|
|
import UserNotificationRuleService from "./UserNotificationRuleService";
|
|
import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
|
|
import BadDataException from "../../Types/Exception/BadDataException";
|
|
import NotificationRuleType from "../../Types/NotificationRule/NotificationRuleType";
|
|
import ObjectID from "../../Types/ObjectID";
|
|
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
import OnCallDutyExecutionLogTimelineStatus from "../../Types/OnCallDutyPolicy/OnCalDutyExecutionLogTimelineStatus";
|
|
import PositiveNumber from "../../Types/PositiveNumber";
|
|
import UserNotificationEventType from "../../Types/UserNotification/UserNotificationEventType";
|
|
import UserNotificationExecutionStatus from "../../Types/UserNotification/UserNotificationExecutionStatus";
|
|
import Incident from "../../Models/DatabaseModels/Incident";
|
|
import UserNotificationRule from "../../Models/DatabaseModels/UserNotificationRule";
|
|
import Model from "../../Models/DatabaseModels/UserOnCallLog";
|
|
import { IsBillingEnabled } from "../EnvironmentConfig";
|
|
import Alert from "../../Models/DatabaseModels/Alert";
|
|
import AlertService from "./AlertService";
|
|
import AlertEpisode from "../../Models/DatabaseModels/AlertEpisode";
|
|
import AlertEpisodeService from "./AlertEpisodeService";
|
|
import IncidentEpisode from "../../Models/DatabaseModels/IncidentEpisode";
|
|
import IncidentEpisodeService from "./IncidentEpisodeService";
|
|
|
|
export class Service extends DatabaseService<Model> {
|
|
public constructor() {
|
|
super(Model);
|
|
if (IsBillingEnabled) {
|
|
this.hardDeleteItemsOlderThanInDays("createdAt", 30);
|
|
}
|
|
}
|
|
|
|
@CaptureSpan()
|
|
protected override async onBeforeCreate(
|
|
createBy: CreateBy<Model>,
|
|
): Promise<OnCreate<Model>> {
|
|
createBy.data.status = UserNotificationExecutionStatus.Scheduled;
|
|
|
|
return {
|
|
createBy,
|
|
carryForward: null,
|
|
};
|
|
}
|
|
|
|
@CaptureSpan()
|
|
protected override async onUpdateSuccess(
|
|
onUpdate: OnUpdate<Model>,
|
|
_updatedItemIds: ObjectID[],
|
|
): Promise<OnUpdate<Model>> {
|
|
if (onUpdate.updateBy.data.status) {
|
|
//update the corresponding oncallTimeline.
|
|
const items: Array<Model> = await this.findBy({
|
|
query: onUpdate.updateBy.query,
|
|
select: {
|
|
onCallDutyPolicyExecutionLogTimelineId: true,
|
|
},
|
|
skip: 0,
|
|
limit: LIMIT_PER_PROJECT,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
let status: OnCallDutyExecutionLogTimelineStatus | undefined = undefined;
|
|
|
|
switch (onUpdate.updateBy.data.status) {
|
|
case UserNotificationExecutionStatus.Completed:
|
|
status = OnCallDutyExecutionLogTimelineStatus.NotificationSent;
|
|
break;
|
|
case UserNotificationExecutionStatus.Error:
|
|
status = OnCallDutyExecutionLogTimelineStatus.Error;
|
|
break;
|
|
case UserNotificationExecutionStatus.Executing:
|
|
status = OnCallDutyExecutionLogTimelineStatus.Executing;
|
|
break;
|
|
case UserNotificationExecutionStatus.Scheduled:
|
|
status = OnCallDutyExecutionLogTimelineStatus.Started;
|
|
break;
|
|
case UserNotificationExecutionStatus.Started:
|
|
status = OnCallDutyExecutionLogTimelineStatus.Started;
|
|
break;
|
|
default:
|
|
throw new BadDataException("Invalid status");
|
|
}
|
|
|
|
for (const item of items) {
|
|
await OnCallDutyPolicyExecutionLogTimelineService.updateOneById({
|
|
id: item.onCallDutyPolicyExecutionLogTimelineId!,
|
|
data: {
|
|
status: status!,
|
|
statusMessage: onUpdate.updateBy.data.statusMessage!,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
return onUpdate;
|
|
}
|
|
|
|
@CaptureSpan()
|
|
protected override async onCreateSuccess(
|
|
_onCreate: OnCreate<Model>,
|
|
createdItem: Model,
|
|
): Promise<Model> {
|
|
// update this item to be processed.
|
|
await this.updateOneById({
|
|
id: createdItem.id!,
|
|
data: {
|
|
status: UserNotificationExecutionStatus.Started,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
const notificationRuleType: NotificationRuleType =
|
|
this.getNotificationRuleType(createdItem.userNotificationEventType!);
|
|
|
|
let ruleCount: PositiveNumber = new PositiveNumber(0);
|
|
|
|
let incident: Incident | null = null;
|
|
let alert: Alert | null = null;
|
|
let alertEpisode: AlertEpisode | null = null;
|
|
|
|
if (createdItem.triggeredByIncidentId) {
|
|
incident = await IncidentService.findOneById({
|
|
id: createdItem.triggeredByIncidentId,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
select: {
|
|
incidentSeverityId: true,
|
|
},
|
|
});
|
|
|
|
// Check if there are any rules .
|
|
ruleCount = await UserNotificationRuleService.countBy({
|
|
query: {
|
|
userId: createdItem.userId!,
|
|
projectId: createdItem.projectId!,
|
|
ruleType: notificationRuleType,
|
|
incidentSeverityId: incident?.incidentSeverityId as ObjectID,
|
|
},
|
|
skip: 0,
|
|
limit: LIMIT_PER_PROJECT,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
}
|
|
|
|
// get rule count for alerts.
|
|
if (createdItem.triggeredByAlertId) {
|
|
alert = await AlertService.findOneById({
|
|
id: createdItem.triggeredByAlertId,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
select: {
|
|
alertSeverityId: true,
|
|
},
|
|
});
|
|
|
|
ruleCount = await UserNotificationRuleService.countBy({
|
|
query: {
|
|
userId: createdItem.userId!,
|
|
projectId: createdItem.projectId!,
|
|
ruleType: notificationRuleType,
|
|
alertSeverityId: alert?.alertSeverityId as ObjectID,
|
|
},
|
|
skip: 0,
|
|
limit: LIMIT_PER_PROJECT,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
}
|
|
|
|
// get rule count for alert episodes.
|
|
if (createdItem.triggeredByAlertEpisodeId) {
|
|
alertEpisode = await AlertEpisodeService.findOneById({
|
|
id: createdItem.triggeredByAlertEpisodeId,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
select: {
|
|
alertSeverityId: true,
|
|
},
|
|
});
|
|
|
|
ruleCount = await UserNotificationRuleService.countBy({
|
|
query: {
|
|
userId: createdItem.userId!,
|
|
projectId: createdItem.projectId!,
|
|
ruleType: notificationRuleType,
|
|
alertSeverityId: alertEpisode?.alertSeverityId as ObjectID,
|
|
},
|
|
skip: 0,
|
|
limit: LIMIT_PER_PROJECT,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
}
|
|
|
|
// get rule count for incident episodes.
|
|
let incidentEpisode: IncidentEpisode | null = null;
|
|
if (createdItem.triggeredByIncidentEpisodeId) {
|
|
incidentEpisode = await IncidentEpisodeService.findOneById({
|
|
id: createdItem.triggeredByIncidentEpisodeId,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
select: {
|
|
incidentSeverityId: true,
|
|
},
|
|
});
|
|
|
|
ruleCount = await UserNotificationRuleService.countBy({
|
|
query: {
|
|
userId: createdItem.userId!,
|
|
projectId: createdItem.projectId!,
|
|
ruleType: notificationRuleType,
|
|
incidentSeverityId: incidentEpisode?.incidentSeverityId as ObjectID,
|
|
},
|
|
skip: 0,
|
|
limit: LIMIT_PER_PROJECT,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
}
|
|
|
|
if (ruleCount.toNumber() === 0) {
|
|
// update this item to be processed.
|
|
await this.updateOneById({
|
|
id: createdItem.id!,
|
|
data: {
|
|
status: UserNotificationExecutionStatus.Error, // now the worker will pick this up and complete this or mark this as failed.
|
|
statusMessage:
|
|
"No notification rules found for this user. User should add the rules in User Settings > On-Call Rules.",
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
// update oncall timeline item as well.
|
|
await OnCallDutyPolicyExecutionLogTimelineService.updateOneById({
|
|
id: createdItem.onCallDutyPolicyExecutionLogTimelineId!,
|
|
data: {
|
|
status: OnCallDutyExecutionLogTimelineStatus.Error,
|
|
statusMessage:
|
|
"No notification rules found for this user. User should add the rules in User Settings > On-Call Rules.",
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
return createdItem;
|
|
}
|
|
|
|
/*
|
|
* find immediate notification rule and alert the user.
|
|
* Determine the alertSeverityId - can come from alert or alertEpisode
|
|
* Determine the incidentSeverityId - can come from incident or incidentEpisode
|
|
*/
|
|
const alertSeverityIdForQuery: ObjectID | undefined =
|
|
alert && alert.alertSeverityId
|
|
? (alert.alertSeverityId as ObjectID)
|
|
: alertEpisode && alertEpisode.alertSeverityId
|
|
? (alertEpisode.alertSeverityId as ObjectID)
|
|
: undefined;
|
|
|
|
const incidentSeverityIdForQuery: ObjectID | undefined =
|
|
incident && incident.incidentSeverityId
|
|
? (incident.incidentSeverityId as ObjectID)
|
|
: incidentEpisode && incidentEpisode.incidentSeverityId
|
|
? (incidentEpisode.incidentSeverityId as ObjectID)
|
|
: undefined;
|
|
|
|
const immediateNotificationRule: Array<UserNotificationRule> =
|
|
await UserNotificationRuleService.findBy({
|
|
query: {
|
|
userId: createdItem.userId!,
|
|
projectId: createdItem.projectId!,
|
|
notifyAfterMinutes: 0,
|
|
ruleType: notificationRuleType,
|
|
incidentSeverityId: incidentSeverityIdForQuery,
|
|
alertSeverityId: alertSeverityIdForQuery,
|
|
},
|
|
select: {
|
|
_id: true,
|
|
},
|
|
skip: 0,
|
|
limit: LIMIT_PER_PROJECT,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
for (const immediateNotificationRuleItem of immediateNotificationRule) {
|
|
await UserNotificationRuleService.executeNotificationRuleItem(
|
|
immediateNotificationRuleItem.id!,
|
|
{
|
|
userNotificationLogId: createdItem.id!,
|
|
projectId: createdItem.projectId!,
|
|
triggeredByIncidentId: createdItem.triggeredByIncidentId,
|
|
triggeredByAlertId: createdItem.triggeredByAlertId,
|
|
triggeredByAlertEpisodeId: createdItem.triggeredByAlertEpisodeId,
|
|
triggeredByIncidentEpisodeId:
|
|
createdItem.triggeredByIncidentEpisodeId,
|
|
userNotificationEventType: createdItem.userNotificationEventType!,
|
|
onCallPolicyExecutionLogId:
|
|
createdItem.onCallDutyPolicyExecutionLogId,
|
|
onCallPolicyId: createdItem.onCallDutyPolicyId,
|
|
onCallPolicyEscalationRuleId:
|
|
createdItem.onCallDutyPolicyEscalationRuleId,
|
|
userBelongsToTeamId: createdItem.userBelongsToTeamId,
|
|
onCallDutyPolicyExecutionLogTimelineId:
|
|
createdItem.onCallDutyPolicyExecutionLogTimelineId,
|
|
onCallScheduleId: createdItem.onCallDutyScheduleId,
|
|
},
|
|
);
|
|
}
|
|
|
|
// update this item to be processed.
|
|
await this.updateOneById({
|
|
id: createdItem.id!,
|
|
data: {
|
|
status: UserNotificationExecutionStatus.Executing, // now the worker will pick this up and complete this or mark this as failed.
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
// update oncall timeline item as well.
|
|
await OnCallDutyPolicyExecutionLogTimelineService.updateOneById({
|
|
id: createdItem.onCallDutyPolicyExecutionLogTimelineId!,
|
|
data: {
|
|
status: OnCallDutyExecutionLogTimelineStatus.NotificationSent,
|
|
statusMessage: "Alert Sent",
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
return createdItem;
|
|
}
|
|
|
|
public getNotificationRuleType(
|
|
userNotificationEventType: UserNotificationEventType,
|
|
): NotificationRuleType {
|
|
if (
|
|
userNotificationEventType === UserNotificationEventType.IncidentCreated
|
|
) {
|
|
return NotificationRuleType.ON_CALL_EXECUTED_INCIDENT;
|
|
}
|
|
|
|
if (userNotificationEventType === UserNotificationEventType.AlertCreated) {
|
|
return NotificationRuleType.ON_CALL_EXECUTED_ALERT;
|
|
}
|
|
|
|
if (
|
|
userNotificationEventType ===
|
|
UserNotificationEventType.AlertEpisodeCreated
|
|
) {
|
|
return NotificationRuleType.ON_CALL_EXECUTED_ALERT_EPISODE;
|
|
}
|
|
|
|
if (
|
|
userNotificationEventType ===
|
|
UserNotificationEventType.IncidentEpisodeCreated
|
|
) {
|
|
return NotificationRuleType.ON_CALL_EXECUTED_INCIDENT_EPISODE;
|
|
}
|
|
|
|
// Invalid user notification event type.
|
|
throw new BadDataException("Invalid user notification event type.");
|
|
}
|
|
}
|
|
export default new Service();
|