mirror of
https://github.com/OneUptime/oneuptime
synced 2024-11-22 23:30:10 +00:00
441 lines
15 KiB
TypeScript
441 lines
15 KiB
TypeScript
import PostgresDatabase from '../Infrastructure/PostgresDatabase';
|
|
import Model from 'Model/Models/ScheduledMaintenance';
|
|
import DatabaseService from './DatabaseService';
|
|
import { OnCreate, OnDelete, OnUpdate } from '../Types/Database/Hooks';
|
|
import ObjectID from 'Common/Types/ObjectID';
|
|
import Monitor from 'Model/Models/Monitor';
|
|
import MonitorService from './MonitorService';
|
|
import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps';
|
|
import ScheduledMaintenanceStateTimeline from 'Model/Models/ScheduledMaintenanceStateTimeline';
|
|
import ScheduledMaintenanceStateTimelineService from './ScheduledMaintenanceStateTimelineService';
|
|
import CreateBy from '../Types/Database/CreateBy';
|
|
import BadDataException from 'Common/Types/Exception/BadDataException';
|
|
import ScheduledMaintenanceState from 'Model/Models/ScheduledMaintenanceState';
|
|
import ScheduledMaintenanceStateService from './ScheduledMaintenanceStateService';
|
|
import LIMIT_MAX, { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax';
|
|
import ScheduledMaintenanceOwnerUserService from './ScheduledMaintenanceOwnerUserService';
|
|
import ScheduledMaintenanceOwnerUser from 'Model/Models/ScheduledMaintenanceOwnerUser';
|
|
import Typeof from 'Common/Types/Typeof';
|
|
import ScheduledMaintenanceOwnerTeamService from './ScheduledMaintenanceOwnerTeamService';
|
|
import ScheduledMaintenanceOwnerTeam from 'Model/Models/ScheduledMaintenanceOwnerTeam';
|
|
import TeamMemberService from './TeamMemberService';
|
|
import User from 'Model/Models/User';
|
|
import URL from 'Common/Types/API/URL';
|
|
import SortOrder from 'Common/Types/BaseDatabase/SortOrder';
|
|
import DatabaseConfig from '../DatabaseConfig';
|
|
import DeleteBy from '../Types/Database/DeleteBy';
|
|
|
|
export class Service extends DatabaseService<Model> {
|
|
public constructor(postgresDatabase?: PostgresDatabase) {
|
|
super(Model, postgresDatabase);
|
|
this.hardDeleteItemsOlderThanInDays('createdAt', 120);
|
|
}
|
|
|
|
protected override async onBeforeDelete(
|
|
deleteBy: DeleteBy<Model>
|
|
): Promise<OnDelete<Model>> {
|
|
const scheduledMaintenanceEvents: Array<Model> = await this.findBy({
|
|
query: deleteBy.query,
|
|
limit: LIMIT_MAX,
|
|
skip: 0,
|
|
select: {
|
|
_id: true,
|
|
projectId: true,
|
|
monitors: {
|
|
_id: true,
|
|
},
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
return {
|
|
carryForward: {
|
|
scheduledMaintenanceEvents: scheduledMaintenanceEvents,
|
|
},
|
|
deleteBy: deleteBy,
|
|
};
|
|
}
|
|
|
|
protected override async onDeleteSuccess(
|
|
onDelete: OnDelete<Model>,
|
|
_deletedItemIds: ObjectID[]
|
|
): Promise<OnDelete<Model>> {
|
|
if (onDelete.carryForward?.scheduledMaintenanceEvents) {
|
|
for (const scheduledMaintenanceEvent of onDelete?.carryForward
|
|
?.scheduledMaintenanceEvents || []) {
|
|
await ScheduledMaintenanceStateTimelineService.enableActiveMonitoringForMonitors(
|
|
scheduledMaintenanceEvent
|
|
);
|
|
}
|
|
}
|
|
|
|
return onDelete;
|
|
}
|
|
|
|
protected override async onBeforeCreate(
|
|
createBy: CreateBy<Model>
|
|
): Promise<OnCreate<Model>> {
|
|
if (!createBy.props.tenantId) {
|
|
throw new BadDataException('ProjectId required to create monitor.');
|
|
}
|
|
|
|
const scheduledMaintenanceState: ScheduledMaintenanceState | null =
|
|
await ScheduledMaintenanceStateService.findOneBy({
|
|
query: {
|
|
projectId: createBy.props.tenantId,
|
|
isScheduledState: true,
|
|
},
|
|
select: {
|
|
_id: true,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
if (!scheduledMaintenanceState || !scheduledMaintenanceState.id) {
|
|
throw new BadDataException(
|
|
'Scheduled state not found for this project. Please add an scheduled event state from settings.'
|
|
);
|
|
}
|
|
|
|
createBy.data.currentScheduledMaintenanceStateId =
|
|
scheduledMaintenanceState.id;
|
|
|
|
return { createBy, carryForward: null };
|
|
}
|
|
|
|
protected override async onCreateSuccess(
|
|
onCreate: OnCreate<Model>,
|
|
createdItem: Model
|
|
): Promise<Model> {
|
|
// create new scheduled maintenance state timeline.
|
|
|
|
const timeline: ScheduledMaintenanceStateTimeline =
|
|
new ScheduledMaintenanceStateTimeline();
|
|
timeline.projectId = createdItem.projectId!;
|
|
timeline.scheduledMaintenanceId = createdItem.id!;
|
|
timeline.isOwnerNotified = true; // ignore notifying owners because you already notify for Scheduled Event, you don't have to notify them for timeline event.
|
|
timeline.shouldStatusPageSubscribersBeNotified = Boolean(
|
|
createdItem.shouldStatusPageSubscribersBeNotifiedOnEventCreated
|
|
);
|
|
timeline.isStatusPageSubscribersNotified = Boolean(
|
|
createdItem.shouldStatusPageSubscribersBeNotifiedOnEventCreated
|
|
); // ignore notifying subscribers because you already notify for Scheduled Event, you don't have to notify them for timeline event.
|
|
timeline.scheduledMaintenanceStateId =
|
|
createdItem.currentScheduledMaintenanceStateId!;
|
|
|
|
await ScheduledMaintenanceStateTimelineService.create({
|
|
data: timeline,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
if (
|
|
createdItem.projectId &&
|
|
createdItem.id &&
|
|
onCreate.createBy.miscDataProps &&
|
|
(onCreate.createBy.miscDataProps['ownerTeams'] ||
|
|
onCreate.createBy.miscDataProps['ownerUsers'])
|
|
) {
|
|
await this.addOwners(
|
|
createdItem.projectId!,
|
|
createdItem.id!,
|
|
(onCreate.createBy.miscDataProps[
|
|
'ownerUsers'
|
|
] as Array<ObjectID>) || [],
|
|
(onCreate.createBy.miscDataProps[
|
|
'ownerTeams'
|
|
] as Array<ObjectID>) || [],
|
|
false,
|
|
onCreate.createBy.props
|
|
);
|
|
}
|
|
|
|
return createdItem;
|
|
}
|
|
|
|
public async addOwners(
|
|
projectId: ObjectID,
|
|
scheduledMaintenanceId: ObjectID,
|
|
userIds: Array<ObjectID>,
|
|
teamIds: Array<ObjectID>,
|
|
notifyOwners: boolean,
|
|
props: DatabaseCommonInteractionProps
|
|
): Promise<void> {
|
|
for (let teamId of teamIds) {
|
|
if (typeof teamId === Typeof.String) {
|
|
teamId = new ObjectID(teamId.toString());
|
|
}
|
|
|
|
const teamOwner: ScheduledMaintenanceOwnerTeam =
|
|
new ScheduledMaintenanceOwnerTeam();
|
|
teamOwner.scheduledMaintenanceId = scheduledMaintenanceId;
|
|
teamOwner.projectId = projectId;
|
|
teamOwner.teamId = teamId;
|
|
teamOwner.isOwnerNotified = !notifyOwners;
|
|
|
|
await ScheduledMaintenanceOwnerTeamService.create({
|
|
data: teamOwner,
|
|
props: props,
|
|
});
|
|
}
|
|
|
|
for (let userId of userIds) {
|
|
if (typeof userId === Typeof.String) {
|
|
userId = new ObjectID(userId.toString());
|
|
}
|
|
const teamOwner: ScheduledMaintenanceOwnerUser =
|
|
new ScheduledMaintenanceOwnerUser();
|
|
teamOwner.scheduledMaintenanceId = scheduledMaintenanceId;
|
|
teamOwner.projectId = projectId;
|
|
teamOwner.isOwnerNotified = !notifyOwners;
|
|
teamOwner.userId = userId;
|
|
await ScheduledMaintenanceOwnerUserService.create({
|
|
data: teamOwner,
|
|
props: props,
|
|
});
|
|
}
|
|
}
|
|
|
|
public async getScheduledMaintenanceLinkInDashboard(
|
|
projectId: ObjectID,
|
|
scheduledMaintenanceId: ObjectID
|
|
): Promise<URL> {
|
|
const dashboardUrl: URL = await DatabaseConfig.getDashboardUrl();
|
|
|
|
return URL.fromString(dashboardUrl.toString()).addRoute(
|
|
`/${projectId.toString()}/scheduled-maintenance-events/${scheduledMaintenanceId.toString()}`
|
|
);
|
|
}
|
|
|
|
public async findOwners(
|
|
scheduledMaintenanceId: ObjectID
|
|
): Promise<Array<User>> {
|
|
if (!scheduledMaintenanceId) {
|
|
throw new BadDataException('scheduledMaintenanceId is required');
|
|
}
|
|
|
|
const ownerUsers: Array<ScheduledMaintenanceOwnerUser> =
|
|
await ScheduledMaintenanceOwnerUserService.findBy({
|
|
query: {
|
|
scheduledMaintenanceId: scheduledMaintenanceId,
|
|
},
|
|
select: {
|
|
_id: true,
|
|
user: {
|
|
_id: true,
|
|
email: true,
|
|
name: true,
|
|
},
|
|
},
|
|
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
limit: LIMIT_PER_PROJECT,
|
|
skip: 0,
|
|
});
|
|
|
|
const ownerTeams: Array<ScheduledMaintenanceOwnerTeam> =
|
|
await ScheduledMaintenanceOwnerTeamService.findBy({
|
|
query: {
|
|
scheduledMaintenanceId: scheduledMaintenanceId,
|
|
},
|
|
select: {
|
|
_id: true,
|
|
teamId: true,
|
|
},
|
|
skip: 0,
|
|
limit: LIMIT_PER_PROJECT,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
const users: Array<User> =
|
|
ownerUsers.map((ownerUser: ScheduledMaintenanceOwnerUser) => {
|
|
return ownerUser.user!;
|
|
}) || [];
|
|
|
|
if (ownerTeams.length > 0) {
|
|
const teamIds: Array<ObjectID> =
|
|
ownerTeams.map((ownerTeam: ScheduledMaintenanceOwnerTeam) => {
|
|
return ownerTeam.teamId!;
|
|
}) || [];
|
|
|
|
const teamUsers: Array<User> =
|
|
await TeamMemberService.getUsersInTeams(teamIds);
|
|
|
|
for (const teamUser of teamUsers) {
|
|
//check if the user is already added.
|
|
const isUserAlreadyAdded: User | undefined = users.find(
|
|
(user: User) => {
|
|
return user.id!.toString() === teamUser.id!.toString();
|
|
}
|
|
);
|
|
|
|
if (!isUserAlreadyAdded) {
|
|
users.push(teamUser);
|
|
}
|
|
}
|
|
}
|
|
|
|
return users;
|
|
}
|
|
|
|
public async changeAttachedMonitorStates(
|
|
item: Model,
|
|
props: DatabaseCommonInteractionProps
|
|
): Promise<void> {
|
|
if (!item.projectId) {
|
|
throw new BadDataException('projectId is required');
|
|
}
|
|
|
|
if (!item.id) {
|
|
throw new BadDataException('id is required');
|
|
}
|
|
|
|
if (item.changeMonitorStatusToId && item.projectId) {
|
|
// change status of all the monitors.
|
|
await MonitorService.changeMonitorStatus(
|
|
item.projectId,
|
|
item.monitors?.map((monitor: Monitor) => {
|
|
return new ObjectID(monitor._id || '');
|
|
}) || [],
|
|
item.changeMonitorStatusToId,
|
|
true, // notify owners
|
|
'Changed because of scheduled maintenance event: ' +
|
|
item.id.toString(),
|
|
undefined,
|
|
props
|
|
);
|
|
}
|
|
}
|
|
|
|
protected override async onUpdateSuccess(
|
|
onUpdate: OnUpdate<Model>,
|
|
updatedItemIds: ObjectID[]
|
|
): Promise<OnUpdate<Model>> {
|
|
if (
|
|
onUpdate.updateBy.data.currentScheduledMaintenanceStateId &&
|
|
onUpdate.updateBy.props.tenantId
|
|
) {
|
|
for (const itemId of updatedItemIds) {
|
|
await this.changeScheduledMaintenanceState({
|
|
projectId: onUpdate.updateBy.props.tenantId as ObjectID,
|
|
scheduledMaintenanceId: itemId,
|
|
scheduledMaintenanceStateId: onUpdate.updateBy.data
|
|
.currentScheduledMaintenanceStateId as ObjectID,
|
|
shouldNotifyStatusPageSubscribers: true,
|
|
isSubscribersNotified: false,
|
|
notifyOwners: true, // notifyOwners = true
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
return onUpdate;
|
|
}
|
|
|
|
public async changeScheduledMaintenanceState(data: {
|
|
projectId: ObjectID;
|
|
scheduledMaintenanceId: ObjectID;
|
|
scheduledMaintenanceStateId: ObjectID;
|
|
shouldNotifyStatusPageSubscribers: boolean;
|
|
isSubscribersNotified: boolean;
|
|
notifyOwners: boolean;
|
|
props: DatabaseCommonInteractionProps;
|
|
}): Promise<void> {
|
|
const {
|
|
projectId,
|
|
scheduledMaintenanceId,
|
|
scheduledMaintenanceStateId,
|
|
notifyOwners,
|
|
shouldNotifyStatusPageSubscribers,
|
|
isSubscribersNotified,
|
|
props,
|
|
} = data;
|
|
|
|
if (!projectId) {
|
|
throw new BadDataException('projectId is required');
|
|
}
|
|
|
|
if (!scheduledMaintenanceId) {
|
|
throw new BadDataException('scheduledMaintenanceId is required');
|
|
}
|
|
|
|
if (!scheduledMaintenanceStateId) {
|
|
throw new BadDataException(
|
|
'scheduledMaintenanceStateId is required'
|
|
);
|
|
}
|
|
|
|
// get last scheduled status timeline.
|
|
const lastState: ScheduledMaintenanceStateTimeline | null =
|
|
await ScheduledMaintenanceStateTimelineService.findOneBy({
|
|
query: {
|
|
scheduledMaintenanceId: scheduledMaintenanceId,
|
|
projectId: projectId,
|
|
},
|
|
select: {
|
|
_id: true,
|
|
scheduledMaintenanceStateId: true,
|
|
},
|
|
sort: {
|
|
createdAt: SortOrder.Descending,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
if (
|
|
lastState &&
|
|
lastState.scheduledMaintenanceStateId &&
|
|
lastState.scheduledMaintenanceStateId.toString() ===
|
|
scheduledMaintenanceStateId.toString()
|
|
) {
|
|
return;
|
|
}
|
|
|
|
const statusTimeline: ScheduledMaintenanceStateTimeline =
|
|
new ScheduledMaintenanceStateTimeline();
|
|
|
|
statusTimeline.scheduledMaintenanceId = scheduledMaintenanceId;
|
|
statusTimeline.scheduledMaintenanceStateId =
|
|
scheduledMaintenanceStateId;
|
|
statusTimeline.projectId = projectId;
|
|
statusTimeline.isOwnerNotified = !notifyOwners;
|
|
statusTimeline.isStatusPageSubscribersNotified = isSubscribersNotified;
|
|
statusTimeline.shouldStatusPageSubscribersBeNotified =
|
|
shouldNotifyStatusPageSubscribers;
|
|
|
|
await ScheduledMaintenanceStateTimelineService.create({
|
|
data: statusTimeline,
|
|
props: props,
|
|
});
|
|
|
|
await this.updateBy({
|
|
data: {
|
|
currentScheduledMaintenanceStateId:
|
|
scheduledMaintenanceStateId.id,
|
|
},
|
|
skip: 0,
|
|
limit: LIMIT_PER_PROJECT,
|
|
query: {
|
|
_id: scheduledMaintenanceId.toString()!,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
export default new Service();
|