mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 00:32:12 +02:00
feat: enhance workspace notification handling in various services
This commit is contained in:
@@ -1,10 +1,199 @@
|
||||
import ObjectID from "../../Types/ObjectID";
|
||||
import { OnCreate, OnDelete } from "../Types/Database/Hooks";
|
||||
import DatabaseService from "./DatabaseService";
|
||||
import Model from "../../Models/DatabaseModels/AlertEpisodeOwnerTeam";
|
||||
import AlertEpisodeFeedService from "./AlertEpisodeFeedService";
|
||||
import { AlertEpisodeFeedEventType } from "../../Models/DatabaseModels/AlertEpisodeFeed";
|
||||
import { Gray500, Red500 } from "../../Types/BrandColors";
|
||||
import TeamService from "./TeamService";
|
||||
import Team from "../../Models/DatabaseModels/Team";
|
||||
import DeleteBy from "../Types/Database/DeleteBy";
|
||||
import AlertEpisodeService from "./AlertEpisodeService";
|
||||
import AlertEpisode from "../../Models/DatabaseModels/AlertEpisode";
|
||||
import WorkspaceNotificationRuleService from "./WorkspaceNotificationRuleService";
|
||||
import NotificationRuleEventType from "../../Types/Workspace/NotificationRules/EventType";
|
||||
import WorkspaceNotificationRule from "../../Models/DatabaseModels/WorkspaceNotificationRule";
|
||||
import logger from "../Utils/Logger";
|
||||
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
||||
|
||||
export class Service extends DatabaseService<Model> {
|
||||
public constructor() {
|
||||
super(Model);
|
||||
}
|
||||
|
||||
@CaptureSpan()
|
||||
protected override async onBeforeDelete(
|
||||
deleteBy: DeleteBy<Model>,
|
||||
): Promise<OnDelete<Model>> {
|
||||
const itemsToDelete: Model[] = await this.findBy({
|
||||
query: deleteBy.query,
|
||||
limit: deleteBy.limit,
|
||||
skip: deleteBy.skip,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
select: {
|
||||
alertEpisodeId: true,
|
||||
projectId: true,
|
||||
teamId: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
carryForward: {
|
||||
itemsToDelete: itemsToDelete,
|
||||
},
|
||||
deleteBy: deleteBy,
|
||||
};
|
||||
}
|
||||
|
||||
@CaptureSpan()
|
||||
protected override async onDeleteSuccess(
|
||||
onDelete: OnDelete<Model>,
|
||||
_itemIdsBeforeDelete: Array<ObjectID>,
|
||||
): Promise<OnDelete<Model>> {
|
||||
const deleteByUserId: ObjectID | undefined =
|
||||
onDelete.deleteBy.deletedByUser?.id || onDelete.deleteBy.props.userId;
|
||||
|
||||
const itemsToDelete: Model[] = onDelete.carryForward.itemsToDelete;
|
||||
|
||||
for (const item of itemsToDelete) {
|
||||
const alertEpisodeId: ObjectID | undefined = item.alertEpisodeId;
|
||||
const projectId: ObjectID | undefined = item.projectId;
|
||||
const teamId: ObjectID | undefined = item.teamId;
|
||||
|
||||
if (alertEpisodeId && teamId && projectId) {
|
||||
const team: Team | null = await TeamService.findOneById({
|
||||
id: teamId,
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
const episodeNumberResult: {
|
||||
number: number | null;
|
||||
numberWithPrefix: string | null;
|
||||
} = await AlertEpisodeService.getEpisodeNumber({
|
||||
episodeId: alertEpisodeId,
|
||||
});
|
||||
const episodeNumberDisplay: string =
|
||||
episodeNumberResult.numberWithPrefix ||
|
||||
"#" + episodeNumberResult.number;
|
||||
|
||||
if (team && team.name) {
|
||||
await AlertEpisodeFeedService.createAlertEpisodeFeedItem({
|
||||
alertEpisodeId: alertEpisodeId,
|
||||
projectId: projectId,
|
||||
alertEpisodeFeedEventType:
|
||||
AlertEpisodeFeedEventType.OwnerTeamRemoved,
|
||||
displayColor: Red500,
|
||||
feedInfoInMarkdown: `👨🏻👩🏻👦🏻 Removed team **${team.name}** from the [Episode ${episodeNumberDisplay}](${(await AlertEpisodeService.getEpisodeLinkInDashboard(projectId!, alertEpisodeId!)).toString()}) as the owner.`,
|
||||
userId: deleteByUserId || undefined,
|
||||
workspaceNotification: {
|
||||
sendWorkspaceNotification: true,
|
||||
notifyUserId: deleteByUserId || undefined,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return onDelete;
|
||||
}
|
||||
|
||||
@CaptureSpan()
|
||||
public override async onCreateSuccess(
|
||||
onCreate: OnCreate<Model>,
|
||||
createdItem: Model,
|
||||
): Promise<Model> {
|
||||
const alertEpisodeId: ObjectID | undefined = createdItem.alertEpisodeId;
|
||||
const projectId: ObjectID | undefined = createdItem.projectId;
|
||||
const teamId: ObjectID | undefined = createdItem.teamId;
|
||||
const createdByUserId: ObjectID | undefined =
|
||||
createdItem.createdByUserId || onCreate.createBy.props.userId;
|
||||
|
||||
if (alertEpisodeId && teamId && projectId) {
|
||||
const team: Team | null = await TeamService.findOneById({
|
||||
id: teamId,
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (team && team.name) {
|
||||
const episodeNumberResult: {
|
||||
number: number | null;
|
||||
numberWithPrefix: string | null;
|
||||
} = await AlertEpisodeService.getEpisodeNumber({
|
||||
episodeId: alertEpisodeId,
|
||||
});
|
||||
const episodeNumberDisplay: string =
|
||||
episodeNumberResult.numberWithPrefix ||
|
||||
"#" + episodeNumberResult.number;
|
||||
|
||||
await AlertEpisodeFeedService.createAlertEpisodeFeedItem({
|
||||
alertEpisodeId: alertEpisodeId,
|
||||
projectId: projectId,
|
||||
alertEpisodeFeedEventType:
|
||||
AlertEpisodeFeedEventType.OwnerTeamAdded,
|
||||
displayColor: Gray500,
|
||||
feedInfoInMarkdown: `👨🏻👩🏻👦🏻 Added team **${team.name}** to the [Episode ${episodeNumberDisplay}](${(await AlertEpisodeService.getEpisodeLinkInDashboard(projectId!, alertEpisodeId!)).toString()}) as the owner.`,
|
||||
userId: createdByUserId || undefined,
|
||||
workspaceNotification: {
|
||||
sendWorkspaceNotification: true,
|
||||
notifyUserId: createdByUserId || undefined,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// get notification rule where inviteOwners is true.
|
||||
const notificationRules: Array<WorkspaceNotificationRule> =
|
||||
await WorkspaceNotificationRuleService.getNotificationRulesWhereInviteOwnersIsTrue(
|
||||
{
|
||||
projectId: projectId,
|
||||
notificationFor: {
|
||||
alertEpisodeId: alertEpisodeId,
|
||||
},
|
||||
notificationRuleEventType:
|
||||
NotificationRuleEventType.AlertEpisode,
|
||||
},
|
||||
);
|
||||
|
||||
// Fetch episode to get workspace channels
|
||||
const episode: AlertEpisode | null =
|
||||
await AlertEpisodeService.findOneById({
|
||||
id: alertEpisodeId,
|
||||
select: {
|
||||
postUpdatesToWorkspaceChannels: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (episode) {
|
||||
WorkspaceNotificationRuleService.inviteTeamsBasedOnRulesAndWorkspaceChannels(
|
||||
{
|
||||
notificationRules: notificationRules,
|
||||
projectId: projectId,
|
||||
workspaceChannels:
|
||||
episode.postUpdatesToWorkspaceChannels || [],
|
||||
teamIds: [teamId],
|
||||
},
|
||||
).catch((error: Error) => {
|
||||
logger.error(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return createdItem;
|
||||
}
|
||||
}
|
||||
|
||||
export default new Service();
|
||||
|
||||
@@ -1,10 +1,192 @@
|
||||
import ObjectID from "../../Types/ObjectID";
|
||||
import DatabaseService from "./DatabaseService";
|
||||
import Model from "../../Models/DatabaseModels/AlertEpisodeOwnerUser";
|
||||
import AlertEpisodeFeedService from "./AlertEpisodeFeedService";
|
||||
import { AlertEpisodeFeedEventType } from "../../Models/DatabaseModels/AlertEpisodeFeed";
|
||||
import { Gray500, Red500 } from "../../Types/BrandColors";
|
||||
import User from "../../Models/DatabaseModels/User";
|
||||
import UserService from "./UserService";
|
||||
import { OnCreate, OnDelete } from "../Types/Database/Hooks";
|
||||
import DeleteBy from "../Types/Database/DeleteBy";
|
||||
import AlertEpisodeService from "./AlertEpisodeService";
|
||||
import AlertEpisode from "../../Models/DatabaseModels/AlertEpisode";
|
||||
import WorkspaceNotificationRuleService from "./WorkspaceNotificationRuleService";
|
||||
import NotificationRuleEventType from "../../Types/Workspace/NotificationRules/EventType";
|
||||
import WorkspaceNotificationRule from "../../Models/DatabaseModels/WorkspaceNotificationRule";
|
||||
import logger from "../Utils/Logger";
|
||||
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
||||
|
||||
export class Service extends DatabaseService<Model> {
|
||||
public constructor() {
|
||||
super(Model);
|
||||
}
|
||||
|
||||
@CaptureSpan()
|
||||
protected override async onBeforeDelete(
|
||||
deleteBy: DeleteBy<Model>,
|
||||
): Promise<OnDelete<Model>> {
|
||||
const itemsToDelete: Model[] = await this.findBy({
|
||||
query: deleteBy.query,
|
||||
limit: deleteBy.limit,
|
||||
skip: deleteBy.skip,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
select: {
|
||||
alertEpisodeId: true,
|
||||
projectId: true,
|
||||
userId: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
carryForward: {
|
||||
itemsToDelete: itemsToDelete,
|
||||
},
|
||||
deleteBy: deleteBy,
|
||||
};
|
||||
}
|
||||
|
||||
@CaptureSpan()
|
||||
protected override async onDeleteSuccess(
|
||||
onDelete: OnDelete<Model>,
|
||||
_itemIdsBeforeDelete: Array<ObjectID>,
|
||||
): Promise<OnDelete<Model>> {
|
||||
const deleteByUserId: ObjectID | undefined =
|
||||
onDelete.deleteBy.deletedByUser?.id || onDelete.deleteBy.props.userId;
|
||||
|
||||
const itemsToDelete: Model[] = onDelete.carryForward.itemsToDelete;
|
||||
|
||||
for (const item of itemsToDelete) {
|
||||
const alertEpisodeId: ObjectID | undefined = item.alertEpisodeId;
|
||||
const projectId: ObjectID | undefined = item.projectId;
|
||||
const userId: ObjectID | undefined = item.userId;
|
||||
|
||||
if (alertEpisodeId && userId && projectId) {
|
||||
const user: User | null = await UserService.findOneById({
|
||||
id: userId,
|
||||
select: {
|
||||
name: true,
|
||||
email: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
const episodeNumberResult: {
|
||||
number: number | null;
|
||||
numberWithPrefix: string | null;
|
||||
} = await AlertEpisodeService.getEpisodeNumber({
|
||||
episodeId: alertEpisodeId,
|
||||
});
|
||||
const episodeNumberDisplay: string =
|
||||
episodeNumberResult.numberWithPrefix ||
|
||||
"#" + episodeNumberResult.number;
|
||||
|
||||
if (user && user.name) {
|
||||
await AlertEpisodeFeedService.createAlertEpisodeFeedItem({
|
||||
alertEpisodeId: alertEpisodeId,
|
||||
projectId: projectId,
|
||||
alertEpisodeFeedEventType:
|
||||
AlertEpisodeFeedEventType.OwnerUserRemoved,
|
||||
displayColor: Red500,
|
||||
feedInfoInMarkdown: `👨🏻💻 Removed **${user.name.toString()}** (${user.email?.toString()}) from the [Episode ${episodeNumberDisplay}](${(await AlertEpisodeService.getEpisodeLinkInDashboard(projectId!, alertEpisodeId!)).toString()}) as the owner.`,
|
||||
userId: deleteByUserId || undefined,
|
||||
workspaceNotification: {
|
||||
sendWorkspaceNotification: true,
|
||||
notifyUserId: userId || undefined,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return onDelete;
|
||||
}
|
||||
|
||||
@CaptureSpan()
|
||||
public override async onCreateSuccess(
|
||||
onCreate: OnCreate<Model>,
|
||||
createdItem: Model,
|
||||
): Promise<Model> {
|
||||
const alertEpisodeId: ObjectID | undefined = createdItem.alertEpisodeId;
|
||||
const projectId: ObjectID | undefined = createdItem.projectId;
|
||||
const userId: ObjectID | undefined = createdItem.userId;
|
||||
const createdByUserId: ObjectID | undefined =
|
||||
createdItem.createdByUserId || onCreate.createBy.props.userId;
|
||||
|
||||
if (alertEpisodeId && userId && projectId) {
|
||||
const episodeNumberResult: {
|
||||
number: number | null;
|
||||
numberWithPrefix: string | null;
|
||||
} = await AlertEpisodeService.getEpisodeNumber({
|
||||
episodeId: alertEpisodeId,
|
||||
});
|
||||
const episodeNumberDisplay: string =
|
||||
episodeNumberResult.numberWithPrefix ||
|
||||
"#" + episodeNumberResult.number;
|
||||
|
||||
await AlertEpisodeFeedService.createAlertEpisodeFeedItem({
|
||||
alertEpisodeId: alertEpisodeId,
|
||||
projectId: projectId,
|
||||
alertEpisodeFeedEventType: AlertEpisodeFeedEventType.OwnerUserAdded,
|
||||
displayColor: Gray500,
|
||||
feedInfoInMarkdown: `👨🏻💻 Added **${await UserService.getUserMarkdownString(
|
||||
{
|
||||
userId: userId,
|
||||
projectId: projectId,
|
||||
},
|
||||
)}** to the [Episode ${episodeNumberDisplay}](${(await AlertEpisodeService.getEpisodeLinkInDashboard(projectId!, alertEpisodeId!)).toString()}) as the owner.`,
|
||||
userId: createdByUserId || undefined,
|
||||
workspaceNotification: {
|
||||
sendWorkspaceNotification: true,
|
||||
notifyUserId: userId || undefined,
|
||||
},
|
||||
});
|
||||
|
||||
// get notification rule where inviteOwners is true.
|
||||
const notificationRules: Array<WorkspaceNotificationRule> =
|
||||
await WorkspaceNotificationRuleService.getNotificationRulesWhereInviteOwnersIsTrue(
|
||||
{
|
||||
projectId: projectId,
|
||||
notificationFor: {
|
||||
alertEpisodeId: alertEpisodeId,
|
||||
},
|
||||
notificationRuleEventType:
|
||||
NotificationRuleEventType.AlertEpisode,
|
||||
},
|
||||
);
|
||||
|
||||
// Fetch episode to get workspace channels
|
||||
const episode: AlertEpisode | null =
|
||||
await AlertEpisodeService.findOneById({
|
||||
id: alertEpisodeId,
|
||||
select: {
|
||||
postUpdatesToWorkspaceChannels: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (episode) {
|
||||
WorkspaceNotificationRuleService.inviteUsersBasedOnRulesAndWorkspaceChannels(
|
||||
{
|
||||
notificationRules: notificationRules,
|
||||
projectId: projectId,
|
||||
workspaceChannels:
|
||||
episode.postUpdatesToWorkspaceChannels || [],
|
||||
userIds: [userId],
|
||||
},
|
||||
).catch((error: Error) => {
|
||||
logger.error(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return createdItem;
|
||||
}
|
||||
}
|
||||
|
||||
export default new Service();
|
||||
|
||||
@@ -374,6 +374,11 @@ export class Service extends DatabaseService<AlertEpisodeStateTimeline> {
|
||||
? `**Cause:** \n${createdItem.rootCause}`
|
||||
: undefined,
|
||||
userId: createdItem.createdByUserId || onCreate.createBy.props.userId,
|
||||
workspaceNotification: {
|
||||
sendWorkspaceNotification: true,
|
||||
notifyUserId:
|
||||
createdItem.createdByUserId || onCreate.createBy.props.userId,
|
||||
},
|
||||
});
|
||||
|
||||
return createdItem;
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
import ObjectID from "../../Types/ObjectID";
|
||||
import DatabaseService from "./DatabaseService";
|
||||
import Model from "../../Models/DatabaseModels/IncidentEpisodeInternalNote";
|
||||
import { OnCreate, OnUpdate } from "../Types/Database/Hooks";
|
||||
import IncidentEpisodeFeedService from "./IncidentEpisodeFeedService";
|
||||
import { IncidentEpisodeFeedEventType } from "../../Models/DatabaseModels/IncidentEpisodeFeed";
|
||||
import { Blue500 } from "../../Types/BrandColors";
|
||||
import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
|
||||
import IncidentEpisodeService from "./IncidentEpisodeService";
|
||||
import IncidentEpisode from "../../Models/DatabaseModels/IncidentEpisode";
|
||||
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
||||
import File from "../../Models/DatabaseModels/File";
|
||||
import FileAttachmentMarkdownUtil from "../Utils/FileAttachmentMarkdownUtil";
|
||||
|
||||
export class Service extends DatabaseService<Model> {
|
||||
public constructor() {
|
||||
@@ -66,6 +74,161 @@ export class Service extends DatabaseService<Model> {
|
||||
|
||||
return existingNote !== null;
|
||||
}
|
||||
|
||||
@CaptureSpan()
|
||||
public override async onCreateSuccess(
|
||||
_onCreate: OnCreate<Model>,
|
||||
createdItem: Model,
|
||||
): Promise<Model> {
|
||||
const userId: ObjectID | null | undefined =
|
||||
createdItem.createdByUserId || createdItem.createdByUser?.id;
|
||||
|
||||
const incidentEpisodeId: ObjectID = createdItem.incidentEpisodeId!;
|
||||
|
||||
const episodeNumberResult: {
|
||||
number: number | null;
|
||||
numberWithPrefix: string | null;
|
||||
} = await IncidentEpisodeService.getEpisodeNumber({
|
||||
episodeId: incidentEpisodeId,
|
||||
});
|
||||
const episodeNumberDisplay: string =
|
||||
episodeNumberResult.numberWithPrefix ||
|
||||
"#" + episodeNumberResult.number;
|
||||
|
||||
const attachmentsMarkdown: string = await this.getAttachmentsMarkdown(
|
||||
createdItem.id!,
|
||||
"/incident-episode-internal-note/attachment",
|
||||
);
|
||||
|
||||
await IncidentEpisodeFeedService.createIncidentEpisodeFeedItem({
|
||||
incidentEpisodeId: createdItem.incidentEpisodeId!,
|
||||
projectId: createdItem.projectId!,
|
||||
incidentEpisodeFeedEventType: IncidentEpisodeFeedEventType.PrivateNote,
|
||||
displayColor: Blue500,
|
||||
userId: userId || undefined,
|
||||
feedInfoInMarkdown: `📄 posted **private note** for this [Episode ${episodeNumberDisplay}](${(await IncidentEpisodeService.getEpisodeLinkInDashboard(createdItem.projectId!, incidentEpisodeId)).toString()}):
|
||||
|
||||
${(createdItem.note || "") + attachmentsMarkdown}
|
||||
`,
|
||||
workspaceNotification: {
|
||||
sendWorkspaceNotification: true,
|
||||
notifyUserId: userId || undefined,
|
||||
},
|
||||
});
|
||||
|
||||
return createdItem;
|
||||
}
|
||||
|
||||
@CaptureSpan()
|
||||
public override async onUpdateSuccess(
|
||||
onUpdate: OnUpdate<Model>,
|
||||
_updatedItemIds: Array<ObjectID>,
|
||||
): Promise<OnUpdate<Model>> {
|
||||
if (onUpdate.updateBy.data.note) {
|
||||
const updatedItems: Array<Model> = await this.findBy({
|
||||
query: onUpdate.updateBy.query,
|
||||
limit: LIMIT_PER_PROJECT,
|
||||
skip: 0,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
select: {
|
||||
incidentEpisodeId: true,
|
||||
incidentEpisode: {
|
||||
projectId: true,
|
||||
episodeNumber: true,
|
||||
episodeNumberWithPrefix: true,
|
||||
},
|
||||
projectId: true,
|
||||
note: true,
|
||||
createdByUserId: true,
|
||||
createdByUser: {
|
||||
_id: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const userId: ObjectID | null | undefined =
|
||||
onUpdate.updateBy.props.userId;
|
||||
|
||||
for (const updatedItem of updatedItems) {
|
||||
const episode: IncidentEpisode = updatedItem.incidentEpisode!;
|
||||
|
||||
const attachmentsMarkdown: string = await this.getAttachmentsMarkdown(
|
||||
updatedItem.id!,
|
||||
"/incident-episode-internal-note/attachment",
|
||||
);
|
||||
|
||||
await IncidentEpisodeFeedService.createIncidentEpisodeFeedItem({
|
||||
incidentEpisodeId: updatedItem.incidentEpisodeId!,
|
||||
projectId: updatedItem.projectId!,
|
||||
incidentEpisodeFeedEventType: IncidentEpisodeFeedEventType.PrivateNote,
|
||||
displayColor: Blue500,
|
||||
userId: userId || undefined,
|
||||
feedInfoInMarkdown: `📄 updated **Private Note** for this [Episode ${episode.episodeNumberWithPrefix || "#" + episode.episodeNumber}](${(await IncidentEpisodeService.getEpisodeLinkInDashboard(episode.projectId!, episode.id!)).toString()})
|
||||
|
||||
${(updatedItem.note || "") + attachmentsMarkdown}
|
||||
`,
|
||||
workspaceNotification: {
|
||||
sendWorkspaceNotification: true,
|
||||
notifyUserId: userId || undefined,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
return onUpdate;
|
||||
}
|
||||
|
||||
private async getAttachmentsMarkdown(
|
||||
modelId: ObjectID,
|
||||
attachmentApiPath: string,
|
||||
): Promise<string> {
|
||||
if (!modelId) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const noteWithAttachments: Model | null = await this.findOneById({
|
||||
id: modelId,
|
||||
select: {
|
||||
attachments: {
|
||||
_id: true,
|
||||
},
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!noteWithAttachments || !noteWithAttachments.attachments) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const attachmentIds: Array<ObjectID> = noteWithAttachments.attachments
|
||||
.map((file: File) => {
|
||||
if (file.id) {
|
||||
return file.id;
|
||||
}
|
||||
|
||||
if (file._id) {
|
||||
return new ObjectID(file._id);
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
.filter((id: ObjectID | null): id is ObjectID => {
|
||||
return Boolean(id);
|
||||
});
|
||||
|
||||
if (!attachmentIds.length) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return await FileAttachmentMarkdownUtil.buildAttachmentMarkdown({
|
||||
modelId,
|
||||
attachmentIds,
|
||||
attachmentApiPath,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new Service();
|
||||
|
||||
@@ -1,10 +1,200 @@
|
||||
import ObjectID from "../../Types/ObjectID";
|
||||
import { OnCreate, OnDelete } from "../Types/Database/Hooks";
|
||||
import DatabaseService from "./DatabaseService";
|
||||
import Model from "../../Models/DatabaseModels/IncidentEpisodeOwnerTeam";
|
||||
import IncidentEpisodeFeedService from "./IncidentEpisodeFeedService";
|
||||
import { IncidentEpisodeFeedEventType } from "../../Models/DatabaseModels/IncidentEpisodeFeed";
|
||||
import { Gray500, Red500 } from "../../Types/BrandColors";
|
||||
import TeamService from "./TeamService";
|
||||
import Team from "../../Models/DatabaseModels/Team";
|
||||
import DeleteBy from "../Types/Database/DeleteBy";
|
||||
import IncidentEpisodeService from "./IncidentEpisodeService";
|
||||
import IncidentEpisode from "../../Models/DatabaseModels/IncidentEpisode";
|
||||
import WorkspaceNotificationRuleService from "./WorkspaceNotificationRuleService";
|
||||
import NotificationRuleEventType from "../../Types/Workspace/NotificationRules/EventType";
|
||||
import WorkspaceNotificationRule from "../../Models/DatabaseModels/WorkspaceNotificationRule";
|
||||
import logger from "../Utils/Logger";
|
||||
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
||||
|
||||
export class Service extends DatabaseService<Model> {
|
||||
public constructor() {
|
||||
super(Model);
|
||||
}
|
||||
|
||||
@CaptureSpan()
|
||||
protected override async onBeforeDelete(
|
||||
deleteBy: DeleteBy<Model>,
|
||||
): Promise<OnDelete<Model>> {
|
||||
const itemsToDelete: Model[] = await this.findBy({
|
||||
query: deleteBy.query,
|
||||
limit: deleteBy.limit,
|
||||
skip: deleteBy.skip,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
select: {
|
||||
incidentEpisodeId: true,
|
||||
projectId: true,
|
||||
teamId: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
carryForward: {
|
||||
itemsToDelete: itemsToDelete,
|
||||
},
|
||||
deleteBy: deleteBy,
|
||||
};
|
||||
}
|
||||
|
||||
@CaptureSpan()
|
||||
protected override async onDeleteSuccess(
|
||||
onDelete: OnDelete<Model>,
|
||||
_itemIdsBeforeDelete: Array<ObjectID>,
|
||||
): Promise<OnDelete<Model>> {
|
||||
const deleteByUserId: ObjectID | undefined =
|
||||
onDelete.deleteBy.deletedByUser?.id || onDelete.deleteBy.props.userId;
|
||||
|
||||
const itemsToDelete: Model[] = onDelete.carryForward.itemsToDelete;
|
||||
|
||||
for (const item of itemsToDelete) {
|
||||
const incidentEpisodeId: ObjectID | undefined = item.incidentEpisodeId;
|
||||
const projectId: ObjectID | undefined = item.projectId;
|
||||
const teamId: ObjectID | undefined = item.teamId;
|
||||
|
||||
if (incidentEpisodeId && teamId && projectId) {
|
||||
const team: Team | null = await TeamService.findOneById({
|
||||
id: teamId,
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
const episodeNumberResult: {
|
||||
number: number | null;
|
||||
numberWithPrefix: string | null;
|
||||
} = await IncidentEpisodeService.getEpisodeNumber({
|
||||
episodeId: incidentEpisodeId,
|
||||
});
|
||||
const episodeNumberDisplay: string =
|
||||
episodeNumberResult.numberWithPrefix ||
|
||||
"#" + episodeNumberResult.number;
|
||||
|
||||
if (team && team.name) {
|
||||
await IncidentEpisodeFeedService.createIncidentEpisodeFeedItem({
|
||||
incidentEpisodeId: incidentEpisodeId,
|
||||
projectId: projectId,
|
||||
incidentEpisodeFeedEventType:
|
||||
IncidentEpisodeFeedEventType.OwnerTeamRemoved,
|
||||
displayColor: Red500,
|
||||
feedInfoInMarkdown: `👨🏻👩🏻👦🏻 Removed team **${team.name}** from the [Episode ${episodeNumberDisplay}](${(await IncidentEpisodeService.getEpisodeLinkInDashboard(projectId!, incidentEpisodeId!)).toString()}) as the owner.`,
|
||||
userId: deleteByUserId || undefined,
|
||||
workspaceNotification: {
|
||||
sendWorkspaceNotification: true,
|
||||
notifyUserId: deleteByUserId || undefined,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return onDelete;
|
||||
}
|
||||
|
||||
@CaptureSpan()
|
||||
public override async onCreateSuccess(
|
||||
onCreate: OnCreate<Model>,
|
||||
createdItem: Model,
|
||||
): Promise<Model> {
|
||||
const incidentEpisodeId: ObjectID | undefined =
|
||||
createdItem.incidentEpisodeId;
|
||||
const projectId: ObjectID | undefined = createdItem.projectId;
|
||||
const teamId: ObjectID | undefined = createdItem.teamId;
|
||||
const createdByUserId: ObjectID | undefined =
|
||||
createdItem.createdByUserId || onCreate.createBy.props.userId;
|
||||
|
||||
if (incidentEpisodeId && teamId && projectId) {
|
||||
const team: Team | null = await TeamService.findOneById({
|
||||
id: teamId,
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (team && team.name) {
|
||||
const episodeNumberResult: {
|
||||
number: number | null;
|
||||
numberWithPrefix: string | null;
|
||||
} = await IncidentEpisodeService.getEpisodeNumber({
|
||||
episodeId: incidentEpisodeId,
|
||||
});
|
||||
const episodeNumberDisplay: string =
|
||||
episodeNumberResult.numberWithPrefix ||
|
||||
"#" + episodeNumberResult.number;
|
||||
|
||||
await IncidentEpisodeFeedService.createIncidentEpisodeFeedItem({
|
||||
incidentEpisodeId: incidentEpisodeId,
|
||||
projectId: projectId,
|
||||
incidentEpisodeFeedEventType:
|
||||
IncidentEpisodeFeedEventType.OwnerTeamAdded,
|
||||
displayColor: Gray500,
|
||||
feedInfoInMarkdown: `👨🏻👩🏻👦🏻 Added team **${team.name}** to the [Episode ${episodeNumberDisplay}](${(await IncidentEpisodeService.getEpisodeLinkInDashboard(projectId!, incidentEpisodeId!)).toString()}) as the owner.`,
|
||||
userId: createdByUserId || undefined,
|
||||
workspaceNotification: {
|
||||
sendWorkspaceNotification: true,
|
||||
notifyUserId: createdByUserId || undefined,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// get notification rule where inviteOwners is true.
|
||||
const notificationRules: Array<WorkspaceNotificationRule> =
|
||||
await WorkspaceNotificationRuleService.getNotificationRulesWhereInviteOwnersIsTrue(
|
||||
{
|
||||
projectId: projectId,
|
||||
notificationFor: {
|
||||
incidentEpisodeId: incidentEpisodeId,
|
||||
},
|
||||
notificationRuleEventType:
|
||||
NotificationRuleEventType.IncidentEpisode,
|
||||
},
|
||||
);
|
||||
|
||||
// Fetch episode to get workspace channels
|
||||
const episode: IncidentEpisode | null =
|
||||
await IncidentEpisodeService.findOneById({
|
||||
id: incidentEpisodeId,
|
||||
select: {
|
||||
postUpdatesToWorkspaceChannels: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (episode) {
|
||||
WorkspaceNotificationRuleService.inviteTeamsBasedOnRulesAndWorkspaceChannels(
|
||||
{
|
||||
notificationRules: notificationRules,
|
||||
projectId: projectId,
|
||||
workspaceChannels:
|
||||
episode.postUpdatesToWorkspaceChannels || [],
|
||||
teamIds: [teamId],
|
||||
},
|
||||
).catch((error: Error) => {
|
||||
logger.error(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return createdItem;
|
||||
}
|
||||
}
|
||||
|
||||
export default new Service();
|
||||
|
||||
@@ -1,10 +1,194 @@
|
||||
import ObjectID from "../../Types/ObjectID";
|
||||
import DatabaseService from "./DatabaseService";
|
||||
import Model from "../../Models/DatabaseModels/IncidentEpisodeOwnerUser";
|
||||
import IncidentEpisodeFeedService from "./IncidentEpisodeFeedService";
|
||||
import { IncidentEpisodeFeedEventType } from "../../Models/DatabaseModels/IncidentEpisodeFeed";
|
||||
import { Gray500, Red500 } from "../../Types/BrandColors";
|
||||
import User from "../../Models/DatabaseModels/User";
|
||||
import UserService from "./UserService";
|
||||
import { OnCreate, OnDelete } from "../Types/Database/Hooks";
|
||||
import DeleteBy from "../Types/Database/DeleteBy";
|
||||
import IncidentEpisodeService from "./IncidentEpisodeService";
|
||||
import IncidentEpisode from "../../Models/DatabaseModels/IncidentEpisode";
|
||||
import WorkspaceNotificationRuleService from "./WorkspaceNotificationRuleService";
|
||||
import NotificationRuleEventType from "../../Types/Workspace/NotificationRules/EventType";
|
||||
import WorkspaceNotificationRule from "../../Models/DatabaseModels/WorkspaceNotificationRule";
|
||||
import logger from "../Utils/Logger";
|
||||
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
||||
|
||||
export class Service extends DatabaseService<Model> {
|
||||
public constructor() {
|
||||
super(Model);
|
||||
}
|
||||
|
||||
@CaptureSpan()
|
||||
protected override async onBeforeDelete(
|
||||
deleteBy: DeleteBy<Model>,
|
||||
): Promise<OnDelete<Model>> {
|
||||
const itemsToDelete: Model[] = await this.findBy({
|
||||
query: deleteBy.query,
|
||||
limit: deleteBy.limit,
|
||||
skip: deleteBy.skip,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
select: {
|
||||
incidentEpisodeId: true,
|
||||
projectId: true,
|
||||
userId: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
carryForward: {
|
||||
itemsToDelete: itemsToDelete,
|
||||
},
|
||||
deleteBy: deleteBy,
|
||||
};
|
||||
}
|
||||
|
||||
@CaptureSpan()
|
||||
protected override async onDeleteSuccess(
|
||||
onDelete: OnDelete<Model>,
|
||||
_itemIdsBeforeDelete: Array<ObjectID>,
|
||||
): Promise<OnDelete<Model>> {
|
||||
const deleteByUserId: ObjectID | undefined =
|
||||
onDelete.deleteBy.deletedByUser?.id || onDelete.deleteBy.props.userId;
|
||||
|
||||
const itemsToDelete: Model[] = onDelete.carryForward.itemsToDelete;
|
||||
|
||||
for (const item of itemsToDelete) {
|
||||
const incidentEpisodeId: ObjectID | undefined = item.incidentEpisodeId;
|
||||
const projectId: ObjectID | undefined = item.projectId;
|
||||
const userId: ObjectID | undefined = item.userId;
|
||||
|
||||
if (incidentEpisodeId && userId && projectId) {
|
||||
const user: User | null = await UserService.findOneById({
|
||||
id: userId,
|
||||
select: {
|
||||
name: true,
|
||||
email: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
const episodeNumberResult: {
|
||||
number: number | null;
|
||||
numberWithPrefix: string | null;
|
||||
} = await IncidentEpisodeService.getEpisodeNumber({
|
||||
episodeId: incidentEpisodeId,
|
||||
});
|
||||
const episodeNumberDisplay: string =
|
||||
episodeNumberResult.numberWithPrefix ||
|
||||
"#" + episodeNumberResult.number;
|
||||
|
||||
if (user && user.name) {
|
||||
await IncidentEpisodeFeedService.createIncidentEpisodeFeedItem({
|
||||
incidentEpisodeId: incidentEpisodeId,
|
||||
projectId: projectId,
|
||||
incidentEpisodeFeedEventType:
|
||||
IncidentEpisodeFeedEventType.OwnerUserRemoved,
|
||||
displayColor: Red500,
|
||||
feedInfoInMarkdown: `👨🏻💻 Removed **${user.name.toString()}** (${user.email?.toString()}) from the [Episode ${episodeNumberDisplay}](${(await IncidentEpisodeService.getEpisodeLinkInDashboard(projectId!, incidentEpisodeId!)).toString()}) as the owner.`,
|
||||
userId: deleteByUserId || undefined,
|
||||
workspaceNotification: {
|
||||
sendWorkspaceNotification: true,
|
||||
notifyUserId: userId || undefined,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return onDelete;
|
||||
}
|
||||
|
||||
@CaptureSpan()
|
||||
public override async onCreateSuccess(
|
||||
onCreate: OnCreate<Model>,
|
||||
createdItem: Model,
|
||||
): Promise<Model> {
|
||||
const incidentEpisodeId: ObjectID | undefined =
|
||||
createdItem.incidentEpisodeId;
|
||||
const projectId: ObjectID | undefined = createdItem.projectId;
|
||||
const userId: ObjectID | undefined = createdItem.userId;
|
||||
const createdByUserId: ObjectID | undefined =
|
||||
createdItem.createdByUserId || onCreate.createBy.props.userId;
|
||||
|
||||
if (incidentEpisodeId && userId && projectId) {
|
||||
const episodeNumberResult: {
|
||||
number: number | null;
|
||||
numberWithPrefix: string | null;
|
||||
} = await IncidentEpisodeService.getEpisodeNumber({
|
||||
episodeId: incidentEpisodeId,
|
||||
});
|
||||
const episodeNumberDisplay: string =
|
||||
episodeNumberResult.numberWithPrefix ||
|
||||
"#" + episodeNumberResult.number;
|
||||
|
||||
await IncidentEpisodeFeedService.createIncidentEpisodeFeedItem({
|
||||
incidentEpisodeId: incidentEpisodeId,
|
||||
projectId: projectId,
|
||||
incidentEpisodeFeedEventType:
|
||||
IncidentEpisodeFeedEventType.OwnerUserAdded,
|
||||
displayColor: Gray500,
|
||||
feedInfoInMarkdown: `👨🏻💻 Added **${await UserService.getUserMarkdownString(
|
||||
{
|
||||
userId: userId,
|
||||
projectId: projectId,
|
||||
},
|
||||
)}** to the [Episode ${episodeNumberDisplay}](${(await IncidentEpisodeService.getEpisodeLinkInDashboard(projectId!, incidentEpisodeId!)).toString()}) as the owner.`,
|
||||
userId: createdByUserId || undefined,
|
||||
workspaceNotification: {
|
||||
sendWorkspaceNotification: true,
|
||||
notifyUserId: userId || undefined,
|
||||
},
|
||||
});
|
||||
|
||||
// get notification rule where inviteOwners is true.
|
||||
const notificationRules: Array<WorkspaceNotificationRule> =
|
||||
await WorkspaceNotificationRuleService.getNotificationRulesWhereInviteOwnersIsTrue(
|
||||
{
|
||||
projectId: projectId,
|
||||
notificationFor: {
|
||||
incidentEpisodeId: incidentEpisodeId,
|
||||
},
|
||||
notificationRuleEventType:
|
||||
NotificationRuleEventType.IncidentEpisode,
|
||||
},
|
||||
);
|
||||
|
||||
// Fetch episode to get workspace channels
|
||||
const episode: IncidentEpisode | null =
|
||||
await IncidentEpisodeService.findOneById({
|
||||
id: incidentEpisodeId,
|
||||
select: {
|
||||
postUpdatesToWorkspaceChannels: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (episode) {
|
||||
WorkspaceNotificationRuleService.inviteUsersBasedOnRulesAndWorkspaceChannels(
|
||||
{
|
||||
notificationRules: notificationRules,
|
||||
projectId: projectId,
|
||||
workspaceChannels:
|
||||
episode.postUpdatesToWorkspaceChannels || [],
|
||||
userIds: [userId],
|
||||
},
|
||||
).catch((error: Error) => {
|
||||
logger.error(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return createdItem;
|
||||
}
|
||||
}
|
||||
|
||||
export default new Service();
|
||||
|
||||
@@ -140,6 +140,10 @@ export class Service extends DatabaseService<Model> {
|
||||
|
||||
${(createdItem.note || "") + attachmentsMarkdown}
|
||||
`,
|
||||
workspaceNotification: {
|
||||
sendWorkspaceNotification: true,
|
||||
notifyUserId: userId || undefined,
|
||||
},
|
||||
});
|
||||
|
||||
return createdItem;
|
||||
@@ -196,6 +200,10 @@ ${(createdItem.note || "") + attachmentsMarkdown}
|
||||
|
||||
${(updatedItem.note || "") + attachmentsMarkdown}
|
||||
`,
|
||||
workspaceNotification: {
|
||||
sendWorkspaceNotification: true,
|
||||
notifyUserId: userId || undefined,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,6 +383,11 @@ export class Service extends DatabaseService<IncidentEpisodeStateTimeline> {
|
||||
? `**Cause:** \n${createdItem.rootCause}`
|
||||
: undefined,
|
||||
userId: createdItem.createdByUserId || onCreate.createBy.props.userId,
|
||||
workspaceNotification: {
|
||||
sendWorkspaceNotification: true,
|
||||
notifyUserId:
|
||||
createdItem.createdByUserId || onCreate.createBy.props.userId,
|
||||
},
|
||||
});
|
||||
|
||||
return createdItem;
|
||||
|
||||
Reference in New Issue
Block a user