mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 00:32:12 +02:00
316 lines
9.2 KiB
TypeScript
316 lines
9.2 KiB
TypeScript
import CreateBy from "../Types/Database/CreateBy";
|
|
import DeleteBy from "../Types/Database/DeleteBy";
|
|
import { OnCreate, OnDelete } from "../Types/Database/Hooks";
|
|
import DatabaseService from "./DatabaseService";
|
|
import BadDataException from "../../Types/Exception/BadDataException";
|
|
import ObjectID from "../../Types/ObjectID";
|
|
import Model from "../../Models/DatabaseModels/AlertEpisodeMember";
|
|
import Alert from "../../Models/DatabaseModels/Alert";
|
|
import AlertEpisode from "../../Models/DatabaseModels/AlertEpisode";
|
|
import { IsBillingEnabled } from "../EnvironmentConfig";
|
|
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
import logger from "../Utils/Logger";
|
|
import AlertEpisodeFeedService from "./AlertEpisodeFeedService";
|
|
import AlertFeedService from "./AlertFeedService";
|
|
import { AlertEpisodeFeedEventType } from "../../Models/DatabaseModels/AlertEpisodeFeed";
|
|
import { AlertFeedEventType } from "../../Models/DatabaseModels/AlertFeed";
|
|
import { Yellow500, Green500 } from "../../Types/BrandColors";
|
|
import OneUptimeDate from "../../Types/Date";
|
|
import AlertService from "./AlertService";
|
|
import AlertEpisodeService from "./AlertEpisodeService";
|
|
|
|
export class Service extends DatabaseService<Model> {
|
|
public constructor() {
|
|
super(Model);
|
|
if (IsBillingEnabled) {
|
|
this.hardDeleteItemsOlderThanInDays("createdAt", 3 * 365); // 3 years
|
|
}
|
|
}
|
|
|
|
@CaptureSpan()
|
|
protected override async onBeforeCreate(
|
|
createBy: CreateBy<Model>,
|
|
): Promise<OnCreate<Model>> {
|
|
if (!createBy.data.alertEpisodeId) {
|
|
throw new BadDataException("alertEpisodeId is required");
|
|
}
|
|
|
|
if (!createBy.data.alertId) {
|
|
throw new BadDataException("alertId is required");
|
|
}
|
|
|
|
// Check if this alert is already in the episode
|
|
const existingMember: Model | null = await this.findOneBy({
|
|
query: {
|
|
alertEpisodeId: createBy.data.alertEpisodeId,
|
|
alertId: createBy.data.alertId,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
select: {
|
|
_id: true,
|
|
},
|
|
});
|
|
|
|
if (existingMember) {
|
|
throw new BadDataException("Alert is already a member of this episode");
|
|
}
|
|
|
|
// Set addedAt if not provided
|
|
if (!createBy.data.addedAt) {
|
|
createBy.data.addedAt = OneUptimeDate.getCurrentDate();
|
|
}
|
|
|
|
return { createBy, carryForward: null };
|
|
}
|
|
|
|
@CaptureSpan()
|
|
protected override async onCreateSuccess(
|
|
_onCreate: OnCreate<Model>,
|
|
createdItem: Model,
|
|
): Promise<Model> {
|
|
if (!createdItem.alertEpisodeId) {
|
|
throw new BadDataException("alertEpisodeId is required");
|
|
}
|
|
|
|
if (!createdItem.alertId) {
|
|
throw new BadDataException("alertId is required");
|
|
}
|
|
|
|
if (!createdItem.projectId) {
|
|
throw new BadDataException("projectId is required");
|
|
}
|
|
|
|
// Update alert's episode reference
|
|
await AlertService.updateOneById({
|
|
id: createdItem.alertId,
|
|
data: {
|
|
alertEpisodeId: createdItem.alertEpisodeId,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
// Update episode's alertCount and lastAlertAddedAt
|
|
try {
|
|
await AlertEpisodeService.updateAlertCount(createdItem.alertEpisodeId!);
|
|
await AlertEpisodeService.updateLastAlertAddedAt(
|
|
createdItem.alertEpisodeId!,
|
|
);
|
|
} catch (error) {
|
|
logger.error(
|
|
`Error updating episode counts in AlertEpisodeMemberService.onCreateSuccess: ${error}`,
|
|
);
|
|
}
|
|
|
|
// Get alert details for feed
|
|
const alert: Alert | null = await AlertService.findOneById({
|
|
id: createdItem.alertId,
|
|
select: {
|
|
alertNumber: true,
|
|
alertNumberWithPrefix: true,
|
|
title: true,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
// Get episode details for feed
|
|
const episode: AlertEpisode | null = await AlertEpisodeService.findOneById({
|
|
id: createdItem.alertEpisodeId,
|
|
select: {
|
|
episodeNumber: true,
|
|
episodeNumberWithPrefix: true,
|
|
title: true,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
// Create feed item on episode
|
|
await AlertEpisodeFeedService.createAlertEpisodeFeedItem({
|
|
alertEpisodeId: createdItem.alertEpisodeId,
|
|
projectId: createdItem.projectId,
|
|
alertEpisodeFeedEventType: AlertEpisodeFeedEventType.AlertAdded,
|
|
displayColor: Yellow500,
|
|
feedInfoInMarkdown: `**Alert ${alert?.alertNumberWithPrefix || "#" + (alert?.alertNumber || "N/A")}** added to episode: ${alert?.title || "No title"}`,
|
|
userId: createdItem.addedByUserId || undefined,
|
|
workspaceNotification: {
|
|
sendWorkspaceNotification: true,
|
|
notifyUserId: createdItem.addedByUserId || undefined,
|
|
},
|
|
});
|
|
|
|
// Create feed item on alert
|
|
await AlertFeedService.createAlertFeedItem({
|
|
alertId: createdItem.alertId,
|
|
projectId: createdItem.projectId,
|
|
alertFeedEventType: AlertFeedEventType.AddedToEpisode,
|
|
displayColor: Yellow500,
|
|
feedInfoInMarkdown: `Added to **Episode ${episode?.episodeNumberWithPrefix || "#" + (episode?.episodeNumber || "N/A")}**: ${episode?.title || "No title"}`,
|
|
userId: createdItem.addedByUserId || undefined,
|
|
});
|
|
|
|
return createdItem;
|
|
}
|
|
|
|
@CaptureSpan()
|
|
protected override async onBeforeDelete(
|
|
deleteBy: DeleteBy<Model>,
|
|
): Promise<OnDelete<Model>> {
|
|
// Get the member records before deletion
|
|
const membersToDelete: Model[] = await this.findBy({
|
|
query: deleteBy.query,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
select: {
|
|
alertEpisodeId: true,
|
|
alertId: true,
|
|
projectId: true,
|
|
},
|
|
limit: 100,
|
|
skip: 0,
|
|
});
|
|
|
|
return {
|
|
deleteBy,
|
|
carryForward: membersToDelete,
|
|
};
|
|
}
|
|
|
|
@CaptureSpan()
|
|
protected override async onDeleteSuccess(
|
|
onDelete: OnDelete<Model>,
|
|
_itemIdsBeforeDelete: ObjectID[],
|
|
): Promise<OnDelete<Model>> {
|
|
const membersDeleted: Model[] = onDelete.carryForward as Model[];
|
|
|
|
if (membersDeleted && membersDeleted.length > 0) {
|
|
for (const member of membersDeleted) {
|
|
if (member.alertId) {
|
|
// Clear the episode reference from the alert
|
|
await AlertService.updateOneById({
|
|
id: member.alertId,
|
|
data: {
|
|
alertEpisodeId: undefined as any,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
// Get alert details for feed
|
|
const alert: Alert | null = await AlertService.findOneById({
|
|
id: member.alertId,
|
|
select: {
|
|
alertNumber: true,
|
|
title: true,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
// Create feed item for removal
|
|
if (member.alertEpisodeId && member.projectId) {
|
|
// Get episode details for feed
|
|
const episode: AlertEpisode | null =
|
|
await AlertEpisodeService.findOneById({
|
|
id: member.alertEpisodeId,
|
|
select: {
|
|
episodeNumber: true,
|
|
episodeNumberWithPrefix: true,
|
|
title: true,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
// Create feed item on episode
|
|
await AlertEpisodeFeedService.createAlertEpisodeFeedItem({
|
|
alertEpisodeId: member.alertEpisodeId,
|
|
projectId: member.projectId,
|
|
alertEpisodeFeedEventType: AlertEpisodeFeedEventType.AlertRemoved,
|
|
displayColor: Green500,
|
|
feedInfoInMarkdown: `**Alert #${alert?.alertNumber || "N/A"}** removed from episode: ${alert?.title || "No title"}`,
|
|
workspaceNotification: {
|
|
sendWorkspaceNotification: true,
|
|
},
|
|
});
|
|
|
|
// Create feed item on alert
|
|
await AlertFeedService.createAlertFeedItem({
|
|
alertId: member.alertId,
|
|
projectId: member.projectId,
|
|
alertFeedEventType: AlertFeedEventType.RemovedFromEpisode,
|
|
displayColor: Green500,
|
|
feedInfoInMarkdown: `Removed from **Episode ${episode?.episodeNumberWithPrefix || "#" + (episode?.episodeNumber || "N/A")}**: ${episode?.title || "No title"}`,
|
|
});
|
|
}
|
|
}
|
|
|
|
if (member.alertEpisodeId) {
|
|
// Update episode's alertCount
|
|
await AlertEpisodeService.updateAlertCount(member.alertEpisodeId);
|
|
}
|
|
}
|
|
}
|
|
|
|
return onDelete;
|
|
}
|
|
|
|
@CaptureSpan()
|
|
public async getAlertsInEpisode(episodeId: ObjectID): Promise<ObjectID[]> {
|
|
const members: Model[] = await this.findBy({
|
|
query: {
|
|
alertEpisodeId: episodeId,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
select: {
|
|
alertId: true,
|
|
},
|
|
limit: 1000,
|
|
skip: 0,
|
|
});
|
|
|
|
return members
|
|
.filter((m: Model) => {
|
|
return m.alertId;
|
|
})
|
|
.map((m: Model) => {
|
|
return m.alertId!;
|
|
});
|
|
}
|
|
|
|
@CaptureSpan()
|
|
public async isAlertInEpisode(
|
|
alertId: ObjectID,
|
|
episodeId: ObjectID,
|
|
): Promise<boolean> {
|
|
const member: Model | null = await this.findOneBy({
|
|
query: {
|
|
alertId: alertId,
|
|
alertEpisodeId: episodeId,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
select: {
|
|
_id: true,
|
|
},
|
|
});
|
|
|
|
return member !== null;
|
|
}
|
|
}
|
|
|
|
export default new Service();
|