Skip to content

Commit

Permalink
Merge pull request #1693 from OneUptime/stop-execution-on-incident-ack
Browse files Browse the repository at this point in the history
Stop execution on incident ack
  • Loading branch information
simlarsen authored Sep 11, 2024
2 parents 6b9a46b + 25755c4 commit a5bd73a
Show file tree
Hide file tree
Showing 23 changed files with 1,055 additions and 266 deletions.
3 changes: 3 additions & 0 deletions App/FeatureSet/Workers/Index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,13 @@ import "./Jobs/ScheduledMaintenanceOwners/SendCreatedResourceNotification";
import "./Jobs/ScheduledMaintenanceOwners/SendNotePostedNotification";
import "./Jobs/ScheduledMaintenanceOwners/SendOwnerAddedNotification";
import "./Jobs/ScheduledMaintenanceOwners/SendStateChangeNotification";

// Scheduled Event Notes
import "./Jobs/ScheduledMaintenancePublicNote/SendNotificationToSubscribers";
import "./Jobs/ScheduledMaintenanceStateTimeline/SendNotificationToSubscribers";
import "./Jobs/ServerMonitor/CheckOnlineStatus";
import "./Jobs/ScheduledMaintenance/SendSubscriberRemindersOnEventScheduled";

// Certs Routers
import "./Jobs/StatusPageCerts/StatusPageCerts";
import "./Jobs/StatusPageOwners/SendAnnouncementCreatedNotification";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import OnCallDutyPolicyExecutionLogService from "Common/Server/Services/OnCallDu
import logger from "Common/Server/Utils/Logger";
import OnCallDutyPolicyEscalationRule from "Common/Models/DatabaseModels/OnCallDutyPolicyEscalationRule";
import OnCallDutyPolicyExecutionLog from "Common/Models/DatabaseModels/OnCallDutyPolicyExecutionLog";
import IncidentService from "Common/Server/Services/IncidentService";

RunCron(
"OnCallDutyPolicyExecutionLog:ExecutePendingExecutions",
Expand Down Expand Up @@ -64,6 +65,30 @@ const executeOnCallPolicy: ExecuteOnCallPolicyFunction = async (
executionLog: OnCallDutyPolicyExecutionLog,
): Promise<void> => {
try {
// get trigger by incident
if (executionLog.triggeredByIncidentId) {
// check if this incident is ack.
const isAcknowledged: boolean =
await IncidentService.isIncidentAcknowledged({
incidentId: executionLog.triggeredByIncidentId,
});

if (isAcknowledged) {
// then mark this policy as executed.
await OnCallDutyPolicyExecutionLogService.updateOneById({
id: executionLog.id!,
data: {
status: OnCallDutyPolicyStatus.Completed,
},
props: {
isRoot: true,
},
});

return;
}
}

// check if this execution needs to be executed.

const currentDate: Date = OneUptimeDate.getCurrentDate();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ RunCron(
labels: true,
isRecurringEvent: true,
recurringInterval: true,
sendSubscriberNotificationsOnBeforeTheEvent: true,
},
});

Expand Down Expand Up @@ -132,6 +133,8 @@ RunCron(
scheduledMaintenanceEvent.title = recurringTemplate.title!;
scheduledMaintenanceEvent.description = recurringTemplate.description!;
scheduledMaintenanceEvent.labels = recurringTemplate.labels!;
scheduledMaintenanceEvent.sendSubscriberNotificationsOnBeforeTheEvent =
recurringTemplate.sendSubscriberNotificationsOnBeforeTheEvent!;

const eventscheduledTime: Date = recurringTemplate.scheduleNextEventAt!;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,40 +1,14 @@
import RunCron from "../../Utils/Cron";
import { FileRoute } from "Common/ServiceRoute";
import Hostname from "Common/Types/API/Hostname";
import Protocol from "Common/Types/API/Protocol";
import URL from "Common/Types/API/URL";
import LIMIT_MAX, { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax";
import LIMIT_MAX from "Common/Types/Database/LimitMax";
import OneUptimeDate from "Common/Types/Date";
import Dictionary from "Common/Types/Dictionary";
import EmailTemplateType from "Common/Types/Email/EmailTemplateType";
import ObjectID from "Common/Types/ObjectID";
import SMS from "Common/Types/SMS/SMS";
import { EVERY_MINUTE } from "Common/Utils/CronTime";
import DatabaseConfig from "Common/Server/DatabaseConfig";
import MailService from "Common/Server/Services/MailService";
import ProjectCallSMSConfigService from "Common/Server/Services/ProjectCallSMSConfigService";
import ProjectSmtpConfigService from "Common/Server/Services/ProjectSmtpConfigService";
import ScheduledMaintenanceService from "Common/Server/Services/ScheduledMaintenanceService";
import SmsService from "Common/Server/Services/SmsService";
import StatusPageResourceService from "Common/Server/Services/StatusPageResourceService";
import StatusPageService from "Common/Server/Services/StatusPageService";
import StatusPageSubscriberService from "Common/Server/Services/StatusPageSubscriberService";
import QueryHelper from "Common/Server/Types/Database/QueryHelper";
import Markdown, { MarkdownContentType } from "Common/Server/Types/Markdown";
import logger from "Common/Server/Utils/Logger";
import Monitor from "Common/Models/DatabaseModels/Monitor";
import ScheduledMaintenance from "Common/Models/DatabaseModels/ScheduledMaintenance";
import StatusPage from "Common/Models/DatabaseModels/StatusPage";
import StatusPageResource from "Common/Models/DatabaseModels/StatusPageResource";
import StatusPageSubscriber from "Common/Models/DatabaseModels/StatusPageSubscriber";

import ScheduledMaintenanceService from "Common/Server/Services/ScheduledMaintenanceService";
RunCron(
"ScheduledMaintenance:SendNotificationToSubscribers",
{ schedule: EVERY_MINUTE, runOnStartup: false },
async () => {
const host: Hostname = await DatabaseConfig.getHost();
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();

// get all scheduled events of all the projects.
const scheduledEvents: Array<ScheduledMaintenance> =
await ScheduledMaintenanceService.findBy({
Expand Down Expand Up @@ -63,8 +37,6 @@ RunCron(
});

for (const event of scheduledEvents) {
// update the flag.

await ScheduledMaintenanceService.updateOneById({
id: event.id!,
data: {
Expand All @@ -75,191 +47,10 @@ RunCron(
ignoreHooks: true,
},
});

// get status page resources from monitors.

let statusPageResources: Array<StatusPageResource> = [];

if (event.monitors && event.monitors.length > 0) {
statusPageResources = await StatusPageResourceService.findBy({
query: {
monitorId: QueryHelper.any(
event.monitors
.filter((m: Monitor) => {
return m._id;
})
.map((m: Monitor) => {
return new ObjectID(m._id!);
}),
),
},
props: {
isRoot: true,
ignoreHooks: true,
},
skip: 0,
limit: LIMIT_PER_PROJECT,
select: {
_id: true,
displayName: true,
statusPageId: true,
},
});
}

const statusPageToResources: Dictionary<Array<StatusPageResource>> = {};

for (const resource of statusPageResources) {
if (!resource.statusPageId) {
continue;
}

if (!statusPageToResources[resource.statusPageId?.toString()]) {
statusPageToResources[resource.statusPageId?.toString()] = [];
}

statusPageToResources[resource.statusPageId?.toString()]?.push(
resource,
);
}

const statusPages: Array<StatusPage> =
await StatusPageSubscriberService.getStatusPagesToSendNotification(
event.statusPages?.map((i: StatusPage) => {
return i.id!;
}) || [],
);

for (const statuspage of statusPages) {
if (!statuspage.id) {
continue;
}

const subscribers: Array<StatusPageSubscriber> =
await StatusPageSubscriberService.getSubscribersByStatusPage(
statuspage.id!,
{
isRoot: true,
ignoreHooks: true,
},
);

const statusPageURL: string = await StatusPageService.getStatusPageURL(
statuspage.id,
);

const statusPageName: string =
statuspage.pageTitle || statuspage.name || "Status Page";

// Send email to Email subscribers.

const resourcesAffected: string =
statusPageToResources[statuspage._id!]
?.map((r: StatusPageResource) => {
return r.displayName;
})
.join(", ") || "";

for (const subscriber of subscribers) {
if (!subscriber._id) {
continue;
}

const shouldNotifySubscriber: boolean =
StatusPageSubscriberService.shouldSendNotification({
subscriber: subscriber,
statusPageResources: statusPageToResources[statuspage._id!] || [],
statusPage: statuspage,
});

if (!shouldNotifySubscriber) {
continue;
}

const unsubscribeUrl: string =
StatusPageSubscriberService.getUnsubscribeLink(
URL.fromString(statusPageURL),
subscriber.id!,
).toString();

if (subscriber.subscriberPhone) {
const sms: SMS = {
message: `
Scheduled Maintenance - ${statusPageName}
${event.title || ""}
${
resourcesAffected
? "Resources Affected: " + resourcesAffected
: ""
}
To view this event, visit ${statusPageURL}
To update notification preferences or unsubscribe, visit ${unsubscribeUrl}
`,
to: subscriber.subscriberPhone,
};

// send sms here.
SmsService.sendSms(sms, {
projectId: statuspage.projectId,
customTwilioConfig: ProjectCallSMSConfigService.toTwilioConfig(
statuspage.callSmsConfig,
),
}).catch((err: Error) => {
logger.error(err);
});
}

if (subscriber.subscriberEmail) {
// send email here.

MailService.sendMail(
{
toEmail: subscriber.subscriberEmail,
templateType:
EmailTemplateType.SubscriberScheduledMaintenanceEventCreated,
vars: {
statusPageName: statusPageName,
statusPageUrl: statusPageURL,
logoUrl: statuspage.logoFileId
? new URL(httpProtocol, host)
.addRoute(FileRoute)
.addRoute("/image/" + statuspage.logoFileId)
.toString()
: "",
isPublicStatusPage: statuspage.isPublicStatusPage
? "true"
: "false",
resourcesAffected: resourcesAffected,
scheduledAt:
OneUptimeDate.getDateAsFormattedHTMLInMultipleTimezones({
date: event.startsAt!,
timezones: statuspage.subscriberTimezones || [],
}),
eventTitle: event.title || "",
eventDescription: await Markdown.convertToHTML(
event.description || "",
MarkdownContentType.Email,
),
unsubscribeUrl: unsubscribeUrl,
},
subject: "[Scheduled Maintenance] " + statusPageName,
},
{
mailServer: ProjectSmtpConfigService.toEmailServer(
statuspage.smtpConfig,
),
projectId: statuspage.projectId!,
},
).catch((err: Error) => {
logger.error(err);
});
}
}
}
}

await ScheduledMaintenanceService.notififySubscribersOnEventScheduled(
scheduledEvents,
);
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import RunCron from "../../Utils/Cron";
import LIMIT_MAX from "Common/Types/Database/LimitMax";
import OneUptimeDate from "Common/Types/Date";
import { EVERY_MINUTE } from "Common/Utils/CronTime";
import QueryHelper from "Common/Server/Types/Database/QueryHelper";
import ScheduledMaintenance from "Common/Models/DatabaseModels/ScheduledMaintenance";
import ScheduledMaintenanceService from "Common/Server/Services/ScheduledMaintenanceService";
RunCron(
"ScheduledMaintenance:SendSubscriberRemindersOnEventScheduled",
{ schedule: EVERY_MINUTE, runOnStartup: false },
async () => {
// get all scheduled events of all the projects.
const scheduledEvents: Array<ScheduledMaintenance> =
await ScheduledMaintenanceService.findBy({
query: {
nextSubscriberNotificationBeforeTheEventAt: QueryHelper.lessThan(
OneUptimeDate.getCurrentDate(),
),
createdAt: QueryHelper.lessThan(OneUptimeDate.getCurrentDate()),
},
props: {
isRoot: true,
},
limit: LIMIT_MAX,
skip: 0,
select: {
_id: true,
title: true,
description: true,
startsAt: true,
monitors: {
_id: true,
},
statusPages: {
_id: true,
},
sendSubscriberNotificationsOnBeforeTheEvent: true,
nextSubscriberNotificationBeforeTheEventAt: true,
},
});

for (const event of scheduledEvents) {
const nextSubscriberNotificationAt: Date | null =
ScheduledMaintenanceService.getNextTimeToNotify({
eventScheduledDate: event.startsAt!,
sendSubscriberNotifiationsOn:
event.sendSubscriberNotificationsOnBeforeTheEvent!,
});

await ScheduledMaintenanceService.updateOneById({
id: event.id!,
data: {
nextSubscriberNotificationBeforeTheEventAt:
nextSubscriberNotificationAt,
},
props: {
isRoot: true,
},
});
}

await ScheduledMaintenanceService.notififySubscribersOnEventScheduled(
scheduledEvents,
);
},
);
Loading

0 comments on commit a5bd73a

Please sign in to comment.