@oneuptime/common 7.0.3038 → 7.0.3040
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/DatabaseModels/ScheduledMaintenance.ts +70 -0
- package/Models/DatabaseModels/ScheduledMaintenanceTemplate.ts +35 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1725975175669-MigrationName.ts +29 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1725976810107-MigrationName.ts +17 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Services/IncidentService.ts +45 -0
- package/Server/Services/IncidentStateService.ts +47 -6
- package/Server/Services/ScheduledMaintenanceService.ts +307 -0
- package/Types/Database/DatabaseProperty.ts +6 -3
- package/Types/Events/Recurring.ts +86 -41
- package/Types/SerializableObjectDictionary.ts +2 -0
- package/UI/Components/Events/RecurringArrayFieldElement.tsx +104 -0
- package/UI/Components/Events/RecurringArrayViewElement.tsx +37 -0
- package/UI/Components/Events/RecurringViewElement.tsx +2 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +73 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplate.js +36 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplate.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1725975175669-MigrationName.js +16 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1725975175669-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1725976810107-MigrationName.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1725976810107-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +34 -0
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/IncidentStateService.js +25 -2
- package/build/dist/Server/Services/IncidentStateService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceService.js +208 -0
- package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
- package/build/dist/Types/Database/DatabaseProperty.js.map +1 -1
- package/build/dist/Types/Events/Recurring.js +47 -20
- package/build/dist/Types/Events/Recurring.js.map +1 -1
- package/build/dist/Types/SerializableObjectDictionary.js +2 -0
- package/build/dist/Types/SerializableObjectDictionary.js.map +1 -1
- package/build/dist/UI/Components/Events/RecurringArrayFieldElement.js +49 -0
- package/build/dist/UI/Components/Events/RecurringArrayFieldElement.js.map +1 -0
- package/build/dist/UI/Components/Events/RecurringArrayViewElement.js +18 -0
- package/build/dist/UI/Components/Events/RecurringArrayViewElement.js.map +1 -0
- package/build/dist/UI/Components/Events/RecurringViewElement.js +2 -1
- package/build/dist/UI/Components/Events/RecurringViewElement.js.map +1 -1
- package/package.json +2 -2
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
ManyToMany,
|
|
35
35
|
ManyToOne,
|
|
36
36
|
} from "typeorm";
|
|
37
|
+
import Recurring from "../../Types/Events/Recurring";
|
|
37
38
|
|
|
38
39
|
@EnableDocumentation()
|
|
39
40
|
@AccessControlColumn("labels")
|
|
@@ -880,4 +881,73 @@ export default class ScheduledMaintenance extends BaseModel {
|
|
|
880
881
|
default: false,
|
|
881
882
|
})
|
|
882
883
|
public isOwnerNotifiedOfResourceCreation?: boolean = undefined;
|
|
884
|
+
|
|
885
|
+
@ColumnAccessControl({
|
|
886
|
+
create: [
|
|
887
|
+
Permission.ProjectOwner,
|
|
888
|
+
Permission.ProjectAdmin,
|
|
889
|
+
Permission.ProjectMember,
|
|
890
|
+
Permission.CreateProjectScheduledMaintenance,
|
|
891
|
+
],
|
|
892
|
+
read: [
|
|
893
|
+
Permission.ProjectOwner,
|
|
894
|
+
Permission.ProjectAdmin,
|
|
895
|
+
Permission.ProjectMember,
|
|
896
|
+
Permission.ReadProjectScheduledMaintenance,
|
|
897
|
+
],
|
|
898
|
+
update: [
|
|
899
|
+
Permission.ProjectOwner,
|
|
900
|
+
Permission.ProjectAdmin,
|
|
901
|
+
Permission.ProjectMember,
|
|
902
|
+
Permission.EditProjectScheduledMaintenance,
|
|
903
|
+
],
|
|
904
|
+
})
|
|
905
|
+
@TableColumn({
|
|
906
|
+
type: TableColumnType.JSON,
|
|
907
|
+
required: false,
|
|
908
|
+
isDefaultValueColumn: false,
|
|
909
|
+
title: "Subscriber notifications before the event",
|
|
910
|
+
description: "Should subscribers be notified before the event?",
|
|
911
|
+
})
|
|
912
|
+
@Column({
|
|
913
|
+
type: ColumnType.JSON,
|
|
914
|
+
nullable: true,
|
|
915
|
+
transformer: Recurring.getDatabaseTransformer(),
|
|
916
|
+
})
|
|
917
|
+
public sendSubscriberNotificationsOnBeforeTheEvent?: Array<Recurring> =
|
|
918
|
+
undefined;
|
|
919
|
+
|
|
920
|
+
@ColumnAccessControl({
|
|
921
|
+
create: [
|
|
922
|
+
Permission.ProjectOwner,
|
|
923
|
+
Permission.ProjectAdmin,
|
|
924
|
+
Permission.ProjectMember,
|
|
925
|
+
Permission.CreateProjectScheduledMaintenance,
|
|
926
|
+
],
|
|
927
|
+
read: [
|
|
928
|
+
Permission.ProjectOwner,
|
|
929
|
+
Permission.ProjectAdmin,
|
|
930
|
+
Permission.ProjectMember,
|
|
931
|
+
Permission.ReadProjectScheduledMaintenance,
|
|
932
|
+
],
|
|
933
|
+
update: [
|
|
934
|
+
Permission.ProjectOwner,
|
|
935
|
+
Permission.ProjectAdmin,
|
|
936
|
+
Permission.ProjectMember,
|
|
937
|
+
Permission.EditProjectScheduledMaintenance,
|
|
938
|
+
],
|
|
939
|
+
})
|
|
940
|
+
@TableColumn({
|
|
941
|
+
type: TableColumnType.Date,
|
|
942
|
+
required: false,
|
|
943
|
+
isDefaultValueColumn: false,
|
|
944
|
+
title: "Next subscriber notification before the event at?",
|
|
945
|
+
description: "When will the next notification to subscribers be sent out?",
|
|
946
|
+
})
|
|
947
|
+
@Index()
|
|
948
|
+
@Column({
|
|
949
|
+
type: ColumnType.Date,
|
|
950
|
+
nullable: true,
|
|
951
|
+
})
|
|
952
|
+
public nextSubscriberNotificationBeforeTheEventAt?: Date = undefined;
|
|
883
953
|
}
|
|
@@ -950,4 +950,39 @@ export default class ScheduledMaintenanceTemplate extends BaseModel {
|
|
|
950
950
|
nullable: true,
|
|
951
951
|
})
|
|
952
952
|
public customFields?: JSONObject = undefined;
|
|
953
|
+
|
|
954
|
+
@ColumnAccessControl({
|
|
955
|
+
create: [
|
|
956
|
+
Permission.ProjectOwner,
|
|
957
|
+
Permission.ProjectAdmin,
|
|
958
|
+
Permission.ProjectMember,
|
|
959
|
+
Permission.CreateScheduledMaintenanceTemplate,
|
|
960
|
+
],
|
|
961
|
+
read: [
|
|
962
|
+
Permission.ProjectOwner,
|
|
963
|
+
Permission.ProjectAdmin,
|
|
964
|
+
Permission.ProjectMember,
|
|
965
|
+
Permission.ReadScheduledMaintenanceTemplate,
|
|
966
|
+
],
|
|
967
|
+
update: [
|
|
968
|
+
Permission.ProjectOwner,
|
|
969
|
+
Permission.ProjectAdmin,
|
|
970
|
+
Permission.ProjectMember,
|
|
971
|
+
Permission.EditScheduledMaintenanceTemplate,
|
|
972
|
+
],
|
|
973
|
+
})
|
|
974
|
+
@TableColumn({
|
|
975
|
+
type: TableColumnType.JSON,
|
|
976
|
+
required: false,
|
|
977
|
+
isDefaultValueColumn: false,
|
|
978
|
+
title: "Subscriber notifications before the event",
|
|
979
|
+
description: "Should subscribers be notified before the event?",
|
|
980
|
+
})
|
|
981
|
+
@Column({
|
|
982
|
+
type: ColumnType.JSON,
|
|
983
|
+
nullable: true,
|
|
984
|
+
transformer: Recurring.getDatabaseTransformer(),
|
|
985
|
+
})
|
|
986
|
+
public sendSubscriberNotificationsOnBeforeTheEvent?: Array<Recurring> =
|
|
987
|
+
undefined;
|
|
953
988
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
2
|
+
|
|
3
|
+
export class MigrationName1725975175669 implements MigrationInterface {
|
|
4
|
+
public name = "MigrationName1725975175669";
|
|
5
|
+
|
|
6
|
+
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
7
|
+
await queryRunner.query(
|
|
8
|
+
`ALTER TABLE "ScheduledMaintenance" ADD "sendSubscriberNotificationsOnBeforeTheEvent" jsonb`,
|
|
9
|
+
);
|
|
10
|
+
await queryRunner.query(
|
|
11
|
+
`ALTER TABLE "ScheduledMaintenance" ADD "nextSubscriberNotificationBeforeTheEventAt" TIMESTAMP WITH TIME ZONE`,
|
|
12
|
+
);
|
|
13
|
+
await queryRunner.query(
|
|
14
|
+
`CREATE INDEX "IDX_37b2094ce25cc62b4766a7d3b1" ON "ScheduledMaintenance" ("nextSubscriberNotificationBeforeTheEventAt") `,
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
19
|
+
await queryRunner.query(
|
|
20
|
+
`DROP INDEX "public"."IDX_37b2094ce25cc62b4766a7d3b1"`,
|
|
21
|
+
);
|
|
22
|
+
await queryRunner.query(
|
|
23
|
+
`ALTER TABLE "ScheduledMaintenance" DROP COLUMN "nextSubscriberNotificationBeforeTheEventAt"`,
|
|
24
|
+
);
|
|
25
|
+
await queryRunner.query(
|
|
26
|
+
`ALTER TABLE "ScheduledMaintenance" DROP COLUMN "sendSubscriberNotificationsOnBeforeTheEvent"`,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
2
|
+
|
|
3
|
+
export class MigrationName1725976810107 implements MigrationInterface {
|
|
4
|
+
public name = "MigrationName1725976810107";
|
|
5
|
+
|
|
6
|
+
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
7
|
+
await queryRunner.query(
|
|
8
|
+
`ALTER TABLE "ScheduledMaintenanceTemplate" ADD "sendSubscriberNotificationsOnBeforeTheEvent" jsonb`,
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
13
|
+
await queryRunner.query(
|
|
14
|
+
`ALTER TABLE "ScheduledMaintenanceTemplate" DROP COLUMN "sendSubscriberNotificationsOnBeforeTheEvent"`,
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -58,6 +58,8 @@ import { MigrationName1725884177663 } from "./1725884177663-MigrationName";
|
|
|
58
58
|
import { MigrationName1725898621366 } from "./1725898621366-MigrationName";
|
|
59
59
|
import { MigrationName1725900315712 } from "./1725900315712-MigrationName";
|
|
60
60
|
import { MigrationName1725901024444 } from "./1725901024444-MigrationName";
|
|
61
|
+
import { MigrationName1725975175669 } from "./1725975175669-MigrationName";
|
|
62
|
+
import { MigrationName1725976810107 } from "./1725976810107-MigrationName";
|
|
61
63
|
|
|
62
64
|
export default [
|
|
63
65
|
InitialMigration,
|
|
@@ -120,4 +122,6 @@ export default [
|
|
|
120
122
|
MigrationName1725898621366,
|
|
121
123
|
MigrationName1725900315712,
|
|
122
124
|
MigrationName1725901024444,
|
|
125
|
+
MigrationName1725975175669,
|
|
126
|
+
MigrationName1725976810107,
|
|
123
127
|
];
|
|
@@ -40,6 +40,51 @@ export class Service extends DatabaseService<Model> {
|
|
|
40
40
|
this.hardDeleteItemsOlderThanInDays("createdAt", 120);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
public async isIncidentAcknowledged(data: {
|
|
44
|
+
incidentId: ObjectID;
|
|
45
|
+
}): Promise<boolean> {
|
|
46
|
+
const incident: Model | null = await this.findOneBy({
|
|
47
|
+
query: {
|
|
48
|
+
_id: data.incidentId,
|
|
49
|
+
},
|
|
50
|
+
select: {
|
|
51
|
+
projectId: true,
|
|
52
|
+
currentIncidentState: {
|
|
53
|
+
order: true,
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
props: {
|
|
57
|
+
isRoot: true,
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (!incident) {
|
|
62
|
+
throw new BadDataException("Incident not found");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!incident.projectId) {
|
|
66
|
+
throw new BadDataException("Incient Project ID not found");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const ackIncidentState: IncidentState =
|
|
70
|
+
await IncidentStateService.getAcknowledgedIncidentState({
|
|
71
|
+
projectId: incident.projectId,
|
|
72
|
+
props: {
|
|
73
|
+
isRoot: true,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const currentIncidentStateOrder: number =
|
|
78
|
+
incident.currentIncidentState!.order!;
|
|
79
|
+
const ackIncidentStateOrder: number = ackIncidentState.order!;
|
|
80
|
+
|
|
81
|
+
if (currentIncidentStateOrder >= ackIncidentStateOrder) {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
|
|
43
88
|
public async acknowledgeIncident(
|
|
44
89
|
incidentId: ObjectID,
|
|
45
90
|
acknowledgedByUserId: ObjectID,
|
|
@@ -152,13 +152,13 @@ export class Service extends DatabaseService<IncidentState> {
|
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
public async
|
|
156
|
-
projectId: ObjectID
|
|
157
|
-
props: DatabaseCommonInteractionProps
|
|
158
|
-
): Promise<IncidentState
|
|
155
|
+
public async getAllIncidentStates(data: {
|
|
156
|
+
projectId: ObjectID;
|
|
157
|
+
props: DatabaseCommonInteractionProps;
|
|
158
|
+
}): Promise<Array<IncidentState>> {
|
|
159
159
|
const incidentStates: Array<IncidentState> = await this.findBy({
|
|
160
160
|
query: {
|
|
161
|
-
projectId: projectId,
|
|
161
|
+
projectId: data.projectId,
|
|
162
162
|
},
|
|
163
163
|
skip: 0,
|
|
164
164
|
limit: LIMIT_MAX,
|
|
@@ -168,10 +168,26 @@ export class Service extends DatabaseService<IncidentState> {
|
|
|
168
168
|
select: {
|
|
169
169
|
_id: true,
|
|
170
170
|
isResolvedState: true,
|
|
171
|
+
isAcknowledgedState: true,
|
|
172
|
+
isCreatedState: true,
|
|
173
|
+
order: true,
|
|
171
174
|
},
|
|
172
|
-
props: props,
|
|
175
|
+
props: data.props,
|
|
173
176
|
});
|
|
174
177
|
|
|
178
|
+
return incidentStates;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
public async getUnresolvedIncidentStates(
|
|
182
|
+
projectId: ObjectID,
|
|
183
|
+
props: DatabaseCommonInteractionProps,
|
|
184
|
+
): Promise<IncidentState[]> {
|
|
185
|
+
const incidentStates: Array<IncidentState> =
|
|
186
|
+
await this.getAllIncidentStates({
|
|
187
|
+
projectId: projectId,
|
|
188
|
+
props: props,
|
|
189
|
+
});
|
|
190
|
+
|
|
175
191
|
const unresolvedIncidentStates: Array<IncidentState> = [];
|
|
176
192
|
|
|
177
193
|
for (const state of incidentStates) {
|
|
@@ -184,5 +200,30 @@ export class Service extends DatabaseService<IncidentState> {
|
|
|
184
200
|
|
|
185
201
|
return unresolvedIncidentStates;
|
|
186
202
|
}
|
|
203
|
+
|
|
204
|
+
public async getAcknowledgedIncidentState(data: {
|
|
205
|
+
projectId: ObjectID;
|
|
206
|
+
props: DatabaseCommonInteractionProps;
|
|
207
|
+
}): Promise<IncidentState> {
|
|
208
|
+
const incidentStates: Array<IncidentState> =
|
|
209
|
+
await this.getAllIncidentStates({
|
|
210
|
+
projectId: data.projectId,
|
|
211
|
+
props: data.props,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
const ackIncidentState: IncidentState | undefined = incidentStates.find(
|
|
215
|
+
(incidentState: IncidentState) => {
|
|
216
|
+
return incidentState?.isAcknowledgedState;
|
|
217
|
+
},
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
if (!ackIncidentState) {
|
|
221
|
+
throw new BadDataException(
|
|
222
|
+
"Acknowledged Incident State not found for this project",
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return ackIncidentState;
|
|
227
|
+
}
|
|
187
228
|
}
|
|
188
229
|
export default new Service();
|
|
@@ -23,6 +23,28 @@ import ScheduledMaintenanceOwnerUser from "Common/Models/DatabaseModels/Schedule
|
|
|
23
23
|
import ScheduledMaintenanceState from "Common/Models/DatabaseModels/ScheduledMaintenanceState";
|
|
24
24
|
import ScheduledMaintenanceStateTimeline from "Common/Models/DatabaseModels/ScheduledMaintenanceStateTimeline";
|
|
25
25
|
import User from "Common/Models/DatabaseModels/User";
|
|
26
|
+
import Recurring from "../../Types/Events/Recurring";
|
|
27
|
+
import OneUptimeDate from "../../Types/Date";
|
|
28
|
+
import UpdateBy from "../Types/Database/UpdateBy";
|
|
29
|
+
import { FileRoute } from "Common/ServiceRoute";
|
|
30
|
+
import Dictionary from "Common/Types/Dictionary";
|
|
31
|
+
import EmailTemplateType from "Common/Types/Email/EmailTemplateType";
|
|
32
|
+
import SMS from "Common/Types/SMS/SMS";
|
|
33
|
+
import MailService from "Common/Server/Services/MailService";
|
|
34
|
+
import ProjectCallSMSConfigService from "Common/Server/Services/ProjectCallSMSConfigService";
|
|
35
|
+
import ProjectSmtpConfigService from "Common/Server/Services/ProjectSmtpConfigService";
|
|
36
|
+
import SmsService from "Common/Server/Services/SmsService";
|
|
37
|
+
import StatusPageResourceService from "Common/Server/Services/StatusPageResourceService";
|
|
38
|
+
import StatusPageService from "Common/Server/Services/StatusPageService";
|
|
39
|
+
import StatusPageSubscriberService from "Common/Server/Services/StatusPageSubscriberService";
|
|
40
|
+
import QueryHelper from "Common/Server/Types/Database/QueryHelper";
|
|
41
|
+
import Markdown, { MarkdownContentType } from "Common/Server/Types/Markdown";
|
|
42
|
+
import logger from "Common/Server/Utils/Logger";
|
|
43
|
+
import StatusPage from "Common/Models/DatabaseModels/StatusPage";
|
|
44
|
+
import StatusPageResource from "Common/Models/DatabaseModels/StatusPageResource";
|
|
45
|
+
import StatusPageSubscriber from "Common/Models/DatabaseModels/StatusPageSubscriber";
|
|
46
|
+
import Hostname from "../../Types/API/Hostname";
|
|
47
|
+
import Protocol from "../../Types/API/Protocol";
|
|
26
48
|
|
|
27
49
|
export class Service extends DatabaseService<Model> {
|
|
28
50
|
public constructor() {
|
|
@@ -30,6 +52,242 @@ export class Service extends DatabaseService<Model> {
|
|
|
30
52
|
this.hardDeleteItemsOlderThanInDays("createdAt", 120);
|
|
31
53
|
}
|
|
32
54
|
|
|
55
|
+
public async notififySubscribersOnEventScheduled(
|
|
56
|
+
scheduledEvents: Array<Model>,
|
|
57
|
+
): Promise<void> {
|
|
58
|
+
const host: Hostname = await DatabaseConfig.getHost();
|
|
59
|
+
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
|
|
60
|
+
|
|
61
|
+
for (const event of scheduledEvents) {
|
|
62
|
+
// get status page resources from monitors.
|
|
63
|
+
|
|
64
|
+
let statusPageResources: Array<StatusPageResource> = [];
|
|
65
|
+
|
|
66
|
+
if (event.monitors && event.monitors.length > 0) {
|
|
67
|
+
statusPageResources = await StatusPageResourceService.findBy({
|
|
68
|
+
query: {
|
|
69
|
+
monitorId: QueryHelper.any(
|
|
70
|
+
event.monitors
|
|
71
|
+
.filter((m: Monitor) => {
|
|
72
|
+
return m._id;
|
|
73
|
+
})
|
|
74
|
+
.map((m: Monitor) => {
|
|
75
|
+
return new ObjectID(m._id!);
|
|
76
|
+
}),
|
|
77
|
+
),
|
|
78
|
+
},
|
|
79
|
+
props: {
|
|
80
|
+
isRoot: true,
|
|
81
|
+
ignoreHooks: true,
|
|
82
|
+
},
|
|
83
|
+
skip: 0,
|
|
84
|
+
limit: LIMIT_PER_PROJECT,
|
|
85
|
+
select: {
|
|
86
|
+
_id: true,
|
|
87
|
+
displayName: true,
|
|
88
|
+
statusPageId: true,
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const statusPageToResources: Dictionary<Array<StatusPageResource>> = {};
|
|
94
|
+
|
|
95
|
+
for (const resource of statusPageResources) {
|
|
96
|
+
if (!resource.statusPageId) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!statusPageToResources[resource.statusPageId?.toString()]) {
|
|
101
|
+
statusPageToResources[resource.statusPageId?.toString()] = [];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
statusPageToResources[resource.statusPageId?.toString()]?.push(
|
|
105
|
+
resource,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const statusPages: Array<StatusPage> =
|
|
110
|
+
await StatusPageSubscriberService.getStatusPagesToSendNotification(
|
|
111
|
+
event.statusPages?.map((i: StatusPage) => {
|
|
112
|
+
return i.id!;
|
|
113
|
+
}) || [],
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
for (const statuspage of statusPages) {
|
|
117
|
+
if (!statuspage.id) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const subscribers: Array<StatusPageSubscriber> =
|
|
122
|
+
await StatusPageSubscriberService.getSubscribersByStatusPage(
|
|
123
|
+
statuspage.id!,
|
|
124
|
+
{
|
|
125
|
+
isRoot: true,
|
|
126
|
+
ignoreHooks: true,
|
|
127
|
+
},
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const statusPageURL: string = await StatusPageService.getStatusPageURL(
|
|
131
|
+
statuspage.id,
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const statusPageName: string =
|
|
135
|
+
statuspage.pageTitle || statuspage.name || "Status Page";
|
|
136
|
+
|
|
137
|
+
// Send email to Email subscribers.
|
|
138
|
+
|
|
139
|
+
const resourcesAffected: string =
|
|
140
|
+
statusPageToResources[statuspage._id!]
|
|
141
|
+
?.map((r: StatusPageResource) => {
|
|
142
|
+
return r.displayName;
|
|
143
|
+
})
|
|
144
|
+
.join(", ") || "";
|
|
145
|
+
|
|
146
|
+
for (const subscriber of subscribers) {
|
|
147
|
+
if (!subscriber._id) {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const shouldNotifySubscriber: boolean =
|
|
152
|
+
StatusPageSubscriberService.shouldSendNotification({
|
|
153
|
+
subscriber: subscriber,
|
|
154
|
+
statusPageResources: statusPageToResources[statuspage._id!] || [],
|
|
155
|
+
statusPage: statuspage,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
if (!shouldNotifySubscriber) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const unsubscribeUrl: string =
|
|
163
|
+
StatusPageSubscriberService.getUnsubscribeLink(
|
|
164
|
+
URL.fromString(statusPageURL),
|
|
165
|
+
subscriber.id!,
|
|
166
|
+
).toString();
|
|
167
|
+
|
|
168
|
+
if (subscriber.subscriberPhone) {
|
|
169
|
+
const sms: SMS = {
|
|
170
|
+
message: `
|
|
171
|
+
Scheduled Maintenance - ${statusPageName}
|
|
172
|
+
|
|
173
|
+
${event.title || ""}
|
|
174
|
+
|
|
175
|
+
${
|
|
176
|
+
resourcesAffected
|
|
177
|
+
? "Resources Affected: " + resourcesAffected
|
|
178
|
+
: ""
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
To view this event, visit ${statusPageURL}
|
|
182
|
+
|
|
183
|
+
To update notification preferences or unsubscribe, visit ${unsubscribeUrl}
|
|
184
|
+
`,
|
|
185
|
+
to: subscriber.subscriberPhone,
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
// send sms here.
|
|
189
|
+
SmsService.sendSms(sms, {
|
|
190
|
+
projectId: statuspage.projectId,
|
|
191
|
+
customTwilioConfig: ProjectCallSMSConfigService.toTwilioConfig(
|
|
192
|
+
statuspage.callSmsConfig,
|
|
193
|
+
),
|
|
194
|
+
}).catch((err: Error) => {
|
|
195
|
+
logger.error(err);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (subscriber.subscriberEmail) {
|
|
200
|
+
// send email here.
|
|
201
|
+
|
|
202
|
+
MailService.sendMail(
|
|
203
|
+
{
|
|
204
|
+
toEmail: subscriber.subscriberEmail,
|
|
205
|
+
templateType:
|
|
206
|
+
EmailTemplateType.SubscriberScheduledMaintenanceEventCreated,
|
|
207
|
+
vars: {
|
|
208
|
+
statusPageName: statusPageName,
|
|
209
|
+
statusPageUrl: statusPageURL,
|
|
210
|
+
logoUrl: statuspage.logoFileId
|
|
211
|
+
? new URL(httpProtocol, host)
|
|
212
|
+
.addRoute(FileRoute)
|
|
213
|
+
.addRoute("/image/" + statuspage.logoFileId)
|
|
214
|
+
.toString()
|
|
215
|
+
: "",
|
|
216
|
+
isPublicStatusPage: statuspage.isPublicStatusPage
|
|
217
|
+
? "true"
|
|
218
|
+
: "false",
|
|
219
|
+
resourcesAffected: resourcesAffected,
|
|
220
|
+
scheduledAt:
|
|
221
|
+
OneUptimeDate.getDateAsFormattedHTMLInMultipleTimezones({
|
|
222
|
+
date: event.startsAt!,
|
|
223
|
+
timezones: statuspage.subscriberTimezones || [],
|
|
224
|
+
}),
|
|
225
|
+
eventTitle: event.title || "",
|
|
226
|
+
eventDescription: await Markdown.convertToHTML(
|
|
227
|
+
event.description || "",
|
|
228
|
+
MarkdownContentType.Email,
|
|
229
|
+
),
|
|
230
|
+
unsubscribeUrl: unsubscribeUrl,
|
|
231
|
+
},
|
|
232
|
+
subject: "[Scheduled Maintenance] " + statusPageName,
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
mailServer: ProjectSmtpConfigService.toEmailServer(
|
|
236
|
+
statuspage.smtpConfig,
|
|
237
|
+
),
|
|
238
|
+
projectId: statuspage.projectId!,
|
|
239
|
+
},
|
|
240
|
+
).catch((err: Error) => {
|
|
241
|
+
logger.error(err);
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
protected override async onBeforeUpdate(
|
|
250
|
+
updateBy: UpdateBy<Model>,
|
|
251
|
+
): Promise<OnUpdate<Model>> {
|
|
252
|
+
if (
|
|
253
|
+
updateBy.query.id &&
|
|
254
|
+
updateBy.data.sendSubscriberNotificationsOnBeforeTheEvent
|
|
255
|
+
) {
|
|
256
|
+
const scheduledMaintenance: Model | null = await this.findOneById({
|
|
257
|
+
id: updateBy.query.id! as ObjectID,
|
|
258
|
+
select: {
|
|
259
|
+
startsAt: true,
|
|
260
|
+
},
|
|
261
|
+
props: {
|
|
262
|
+
isRoot: true,
|
|
263
|
+
},
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
if (!scheduledMaintenance) {
|
|
267
|
+
throw new BadDataException("Scheduled Maintennace Event not found");
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const startsAt: Date =
|
|
271
|
+
(updateBy.data.startsAt as Date) ||
|
|
272
|
+
(scheduledMaintenance.startsAt! as Date);
|
|
273
|
+
|
|
274
|
+
const nextTimeToNotifyBeforeTheEvent: Date | null =
|
|
275
|
+
this.getNextTimeToNotify({
|
|
276
|
+
eventScheduledDate: startsAt,
|
|
277
|
+
sendSubscriberNotifiationsOn: updateBy.data
|
|
278
|
+
.sendSubscriberNotificationsOnBeforeTheEvent as Array<Recurring>,
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
updateBy.data.nextSubscriberNotificationBeforeTheEventAt =
|
|
282
|
+
nextTimeToNotifyBeforeTheEvent;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return {
|
|
286
|
+
updateBy,
|
|
287
|
+
carryForward: null,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
33
291
|
protected override async onBeforeDelete(
|
|
34
292
|
deleteBy: DeleteBy<Model>,
|
|
35
293
|
): Promise<OnDelete<Model>> {
|
|
@@ -73,6 +331,36 @@ export class Service extends DatabaseService<Model> {
|
|
|
73
331
|
return onDelete;
|
|
74
332
|
}
|
|
75
333
|
|
|
334
|
+
public getNextTimeToNotify(data: {
|
|
335
|
+
eventScheduledDate: Date;
|
|
336
|
+
sendSubscriberNotifiationsOn: Array<Recurring>;
|
|
337
|
+
}): Date | null {
|
|
338
|
+
let recurringDate: Date | null = null;
|
|
339
|
+
|
|
340
|
+
for (const recurringItem of data.sendSubscriberNotifiationsOn) {
|
|
341
|
+
const notificationDate: Date = Recurring.getNextDateInterval(
|
|
342
|
+
data.eventScheduledDate,
|
|
343
|
+
recurringItem,
|
|
344
|
+
true,
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
// if this date is in the future. set it to recurring date.
|
|
348
|
+
if (OneUptimeDate.isInTheFuture(notificationDate)) {
|
|
349
|
+
recurringDate = notificationDate;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// if this new date is less than the recurring date then set it to recuring date. We need to get the least date.
|
|
353
|
+
|
|
354
|
+
if (recurringDate) {
|
|
355
|
+
if (OneUptimeDate.isBefore(notificationDate, recurringDate)) {
|
|
356
|
+
recurringDate = notificationDate;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return recurringDate;
|
|
362
|
+
}
|
|
363
|
+
|
|
76
364
|
protected override async onBeforeCreate(
|
|
77
365
|
createBy: CreateBy<Model>,
|
|
78
366
|
): Promise<OnCreate<Model>> {
|
|
@@ -105,6 +393,25 @@ export class Service extends DatabaseService<Model> {
|
|
|
105
393
|
createBy.data.currentScheduledMaintenanceStateId =
|
|
106
394
|
scheduledMaintenanceState.id;
|
|
107
395
|
|
|
396
|
+
// get next notification date.
|
|
397
|
+
|
|
398
|
+
if (
|
|
399
|
+
createBy.data.sendSubscriberNotificationsOnBeforeTheEvent &&
|
|
400
|
+
createBy.data.startsAt
|
|
401
|
+
) {
|
|
402
|
+
const nextNotificationDate: Date | null = this.getNextTimeToNotify({
|
|
403
|
+
eventScheduledDate: createBy.data.startsAt,
|
|
404
|
+
sendSubscriberNotifiationsOn:
|
|
405
|
+
createBy.data.sendSubscriberNotificationsOnBeforeTheEvent,
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
if (nextNotificationDate) {
|
|
409
|
+
// set this.
|
|
410
|
+
createBy.data.nextSubscriberNotificationBeforeTheEventAt =
|
|
411
|
+
nextNotificationDate;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
108
415
|
return { createBy, carryForward: null };
|
|
109
416
|
}
|
|
110
417
|
|
|
@@ -11,19 +11,22 @@ export default class DatabaseProperty extends SerializableObject {
|
|
|
11
11
|
|
|
12
12
|
protected static fromDatabase(
|
|
13
13
|
_value: string | number | JSONObject | JSONArray,
|
|
14
|
-
): DatabaseProperty | null {
|
|
14
|
+
): DatabaseProperty | Array<DatabaseProperty> | null {
|
|
15
15
|
throw new NotImplementedException();
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
protected static toDatabase(
|
|
19
|
-
_value:
|
|
19
|
+
_value:
|
|
20
|
+
| DatabaseProperty
|
|
21
|
+
| Array<DatabaseProperty>
|
|
22
|
+
| FindOperator<DatabaseProperty>,
|
|
20
23
|
): string | number | JSONObject | JSONArray | null {
|
|
21
24
|
throw new NotImplementedException();
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
protected static _fromDatabase(
|
|
25
28
|
value: string | number | JSONObject | JSONArray,
|
|
26
|
-
): DatabaseProperty | null {
|
|
29
|
+
): DatabaseProperty | Array<DatabaseProperty> | null {
|
|
27
30
|
return this.fromDatabase(value);
|
|
28
31
|
}
|
|
29
32
|
|