@oneuptime/common 9.2.17 → 9.2.20

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.
Files changed (40) hide show
  1. package/Server/API/AlertAPI.ts +139 -0
  2. package/Server/API/IncidentAPI.ts +132 -0
  3. package/Server/API/ScheduledMaintenanceAPI.ts +164 -0
  4. package/Server/Services/AIService.ts +0 -1
  5. package/Server/Services/IncidentService.ts +0 -1
  6. package/Server/Utils/AI/AlertAIContextBuilder.ts +264 -0
  7. package/Server/Utils/AI/IncidentAIContextBuilder.ts +212 -0
  8. package/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.ts +345 -0
  9. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +78 -1
  10. package/Server/Utils/Workspace/Slack/Slack.ts +80 -1
  11. package/Tests/Types/Domain.test.ts +24 -3
  12. package/Types/Domain.ts +21 -24
  13. package/UI/Components/AI/GenerateFromAIModal.tsx +157 -20
  14. package/build/dist/Server/API/AlertAPI.js +94 -0
  15. package/build/dist/Server/API/AlertAPI.js.map +1 -0
  16. package/build/dist/Server/API/IncidentAPI.js +88 -1
  17. package/build/dist/Server/API/IncidentAPI.js.map +1 -1
  18. package/build/dist/Server/API/ScheduledMaintenanceAPI.js +103 -0
  19. package/build/dist/Server/API/ScheduledMaintenanceAPI.js.map +1 -0
  20. package/build/dist/Server/Services/AIService.js +0 -1
  21. package/build/dist/Server/Services/AIService.js.map +1 -1
  22. package/build/dist/Server/Services/IncidentService.js +0 -1
  23. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  24. package/build/dist/Server/Utils/AI/AlertAIContextBuilder.js +238 -0
  25. package/build/dist/Server/Utils/AI/AlertAIContextBuilder.js.map +1 -0
  26. package/build/dist/Server/Utils/AI/IncidentAIContextBuilder.js +189 -0
  27. package/build/dist/Server/Utils/AI/IncidentAIContextBuilder.js.map +1 -1
  28. package/build/dist/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.js +311 -0
  29. package/build/dist/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.js.map +1 -0
  30. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +59 -2
  31. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  32. package/build/dist/Server/Utils/Workspace/Slack/Slack.js +61 -1
  33. package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
  34. package/build/dist/Tests/Types/Domain.test.js +19 -3
  35. package/build/dist/Tests/Types/Domain.test.js.map +1 -1
  36. package/build/dist/Types/Domain.js +18 -16
  37. package/build/dist/Types/Domain.js.map +1 -1
  38. package/build/dist/UI/Components/AI/GenerateFromAIModal.js +116 -3
  39. package/build/dist/UI/Components/AI/GenerateFromAIModal.js.map +1 -1
  40. package/package.json +1 -1
@@ -0,0 +1,264 @@
1
+ import ObjectID from "../../../Types/ObjectID";
2
+ import Alert from "../../../Models/DatabaseModels/Alert";
3
+ import AlertStateTimeline from "../../../Models/DatabaseModels/AlertStateTimeline";
4
+ import AlertInternalNote from "../../../Models/DatabaseModels/AlertInternalNote";
5
+ import AlertService from "../../Services/AlertService";
6
+ import AlertStateTimelineService from "../../Services/AlertStateTimelineService";
7
+ import AlertInternalNoteService from "../../Services/AlertInternalNoteService";
8
+ import CaptureSpan from "../Telemetry/CaptureSpan";
9
+ import OneUptimeDate from "../../../Types/Date";
10
+ import SortOrder from "../../../Types/BaseDatabase/SortOrder";
11
+ import { LLMMessage } from "../LLM/LLMService";
12
+
13
+ export interface AlertContextData {
14
+ alert: Alert;
15
+ stateTimeline: Array<AlertStateTimeline>;
16
+ internalNotes: Array<AlertInternalNote>;
17
+ }
18
+
19
+ export interface AIGenerationContext {
20
+ contextText: string;
21
+ systemPrompt: string;
22
+ messages: Array<LLMMessage>;
23
+ }
24
+
25
+ export default class AlertAIContextBuilder {
26
+ @CaptureSpan()
27
+ public static async buildAlertContext(data: {
28
+ alertId: ObjectID;
29
+ }): Promise<AlertContextData> {
30
+ const alert: Alert | null = await AlertService.findOneById({
31
+ id: data.alertId,
32
+ select: {
33
+ _id: true,
34
+ title: true,
35
+ description: true,
36
+ createdAt: true,
37
+ customFields: true,
38
+ projectId: true,
39
+ alertSeverity: {
40
+ name: true,
41
+ color: true,
42
+ },
43
+ currentAlertState: {
44
+ name: true,
45
+ color: true,
46
+ },
47
+ monitor: {
48
+ name: true,
49
+ },
50
+ labels: {
51
+ name: true,
52
+ color: true,
53
+ },
54
+ rootCause: true,
55
+ remediationNotes: true,
56
+ },
57
+ props: {
58
+ isRoot: true,
59
+ },
60
+ });
61
+
62
+ if (!alert) {
63
+ throw new Error("Alert not found");
64
+ }
65
+
66
+ // Fetch state timeline
67
+ const stateTimeline: Array<AlertStateTimeline> =
68
+ await AlertStateTimelineService.findBy({
69
+ query: {
70
+ alertId: data.alertId,
71
+ },
72
+ select: {
73
+ _id: true,
74
+ createdAt: true,
75
+ startsAt: true,
76
+ endsAt: true,
77
+ rootCause: true,
78
+ alertState: {
79
+ name: true,
80
+ color: true,
81
+ },
82
+ createdByUser: {
83
+ name: true,
84
+ email: true,
85
+ },
86
+ },
87
+ sort: {
88
+ startsAt: SortOrder.Ascending,
89
+ },
90
+ limit: 100,
91
+ skip: 0,
92
+ props: {
93
+ isRoot: true,
94
+ },
95
+ });
96
+
97
+ // Fetch internal notes
98
+ const internalNotes: Array<AlertInternalNote> =
99
+ await AlertInternalNoteService.findBy({
100
+ query: {
101
+ alertId: data.alertId,
102
+ },
103
+ select: {
104
+ _id: true,
105
+ note: true,
106
+ createdAt: true,
107
+ createdByUser: {
108
+ name: true,
109
+ email: true,
110
+ },
111
+ },
112
+ sort: {
113
+ createdAt: SortOrder.Ascending,
114
+ },
115
+ limit: 100,
116
+ skip: 0,
117
+ props: {
118
+ isRoot: true,
119
+ },
120
+ });
121
+
122
+ return {
123
+ alert,
124
+ stateTimeline,
125
+ internalNotes,
126
+ };
127
+ }
128
+
129
+ @CaptureSpan()
130
+ public static formatAlertContextForNote(
131
+ contextData: AlertContextData,
132
+ template?: string,
133
+ ): AIGenerationContext {
134
+ const { alert, stateTimeline, internalNotes } = contextData;
135
+
136
+ let contextText: string = "";
137
+
138
+ // Basic alert information
139
+ contextText += "# Alert Information\n\n";
140
+ contextText += `**Title:** ${alert.title || "N/A"}\n\n`;
141
+ contextText += `**Description:** ${alert.description || "N/A"}\n\n`;
142
+ contextText += `**Severity:** ${alert.alertSeverity?.name || "N/A"}\n\n`;
143
+ contextText += `**Current State:** ${alert.currentAlertState?.name || "N/A"}\n\n`;
144
+ contextText += `**Created At:** ${alert.createdAt ? OneUptimeDate.getDateAsFormattedString(alert.createdAt) : "N/A"}\n\n`;
145
+
146
+ // Affected monitor
147
+ if (alert.monitor) {
148
+ contextText += `**Monitor:** ${alert.monitor.name || "N/A"}\n\n`;
149
+ }
150
+
151
+ // Labels
152
+ if (alert.labels && alert.labels.length > 0) {
153
+ contextText += "**Labels:** ";
154
+ contextText += alert.labels
155
+ .map((l: { name?: string }) => {
156
+ return l.name;
157
+ })
158
+ .join(", ");
159
+ contextText += "\n\n";
160
+ }
161
+
162
+ // Root cause if available
163
+ if (alert.rootCause) {
164
+ contextText += `**Root Cause:** ${alert.rootCause}\n\n`;
165
+ }
166
+
167
+ // Remediation notes if available
168
+ if (alert.remediationNotes) {
169
+ contextText += `**Remediation Notes:** ${alert.remediationNotes}\n\n`;
170
+ }
171
+
172
+ // State timeline
173
+ if (stateTimeline.length > 0) {
174
+ contextText += "# State Timeline\n\n";
175
+ for (const timeline of stateTimeline) {
176
+ const startTime: string = timeline.startsAt
177
+ ? OneUptimeDate.getDateAsFormattedString(timeline.startsAt)
178
+ : "N/A";
179
+ const stateName: string =
180
+ timeline.alertState?.name?.toString() || "Unknown";
181
+ const createdBy: string =
182
+ timeline.createdByUser?.name?.toString() ||
183
+ timeline.createdByUser?.email?.toString() ||
184
+ "System";
185
+
186
+ contextText += `- **${startTime}**: State changed to **${stateName}** by ${createdBy}\n`;
187
+ if (timeline.rootCause) {
188
+ contextText += ` - Root cause noted: ${timeline.rootCause}\n`;
189
+ }
190
+ }
191
+ contextText += "\n";
192
+ }
193
+
194
+ // Internal notes
195
+ if (internalNotes.length > 0) {
196
+ contextText += "# Internal Notes (Private)\n\n";
197
+ for (const note of internalNotes) {
198
+ const noteTime: string = note.createdAt
199
+ ? OneUptimeDate.getDateAsFormattedString(note.createdAt)
200
+ : "N/A";
201
+ const createdBy: string =
202
+ note.createdByUser?.name?.toString() ||
203
+ note.createdByUser?.email?.toString() ||
204
+ "Unknown";
205
+
206
+ contextText += `**[${noteTime}] ${createdBy}:**\n`;
207
+ contextText += `${note.note || "N/A"}\n\n`;
208
+ }
209
+ }
210
+
211
+ // System prompt for note generation (alerts only have internal notes)
212
+ let systemPrompt: string;
213
+
214
+ if (template) {
215
+ systemPrompt = `You are an expert Site Reliability Engineer (SRE). Your task is to fill in an internal alert note template based on the provided alert data.
216
+
217
+ CRITICAL INSTRUCTIONS:
218
+ - You MUST use ONLY the exact template structure provided below
219
+ - Fill in each section of the template with relevant information from the alert data
220
+ - Do NOT add any new sections, headers, or content that is not part of the template
221
+ - Do NOT add introductions, conclusions, or any text outside the template structure
222
+ - Be technical and detailed - this is for the internal team
223
+ - Include relevant technical details, observations, and analysis
224
+
225
+ TEMPLATE TO FILL (use this exact structure):
226
+
227
+ ${template}`;
228
+ } else {
229
+ systemPrompt = `You are an expert Site Reliability Engineer (SRE). Your task is to generate an internal alert note for the team.
230
+
231
+ The note should:
232
+ 1. Provide technical details about the alert and its current status
233
+ 2. Document observations, findings, or actions taken
234
+ 3. Include relevant metrics or error messages if mentioned in the context
235
+ 4. Be detailed enough to help team members understand the situation
236
+ 5. Use technical language appropriate for the engineering team
237
+
238
+ Write in markdown format for better readability. Be thorough and technical.`;
239
+ }
240
+
241
+ // Build user message
242
+ const userMessage: string = template
243
+ ? `Fill in the template above using ONLY the following alert data. Output only the filled template, nothing else:\n\n${contextText}`
244
+ : `Based on the following alert data, please generate an internal technical alert note:\n\n${contextText}`;
245
+
246
+ // Build messages array
247
+ const messages: Array<LLMMessage> = [
248
+ {
249
+ role: "system",
250
+ content: systemPrompt,
251
+ },
252
+ {
253
+ role: "user",
254
+ content: userMessage,
255
+ },
256
+ ];
257
+
258
+ return {
259
+ contextText,
260
+ systemPrompt,
261
+ messages,
262
+ };
263
+ }
264
+ }
@@ -392,6 +392,218 @@ Write in a professional, clear, and concise manner. Use markdown formatting for
392
392
  };
393
393
  }
394
394
 
395
+ @CaptureSpan()
396
+ public static formatIncidentContextForNote(
397
+ contextData: IncidentContextData,
398
+ noteType: "public" | "internal",
399
+ template?: string,
400
+ ): AIGenerationContext {
401
+ const {
402
+ incident,
403
+ stateTimeline,
404
+ internalNotes,
405
+ publicNotes,
406
+ workspaceMessages,
407
+ } = contextData;
408
+
409
+ let contextText: string = "";
410
+
411
+ // Basic incident information
412
+ contextText += "# Incident Information\n\n";
413
+ contextText += `**Title:** ${incident.title || "N/A"}\n\n`;
414
+ contextText += `**Description:** ${incident.description || "N/A"}\n\n`;
415
+ contextText += `**Severity:** ${incident.incidentSeverity?.name || "N/A"}\n\n`;
416
+ contextText += `**Current State:** ${incident.currentIncidentState?.name || "N/A"}\n\n`;
417
+ contextText += `**Created At:** ${incident.createdAt ? OneUptimeDate.getDateAsFormattedString(incident.createdAt) : "N/A"}\n\n`;
418
+
419
+ // Affected monitors
420
+ if (incident.monitors && incident.monitors.length > 0) {
421
+ contextText += "**Affected Monitors:** ";
422
+ contextText += incident.monitors
423
+ .map((m: { name?: string }) => {
424
+ return m.name;
425
+ })
426
+ .join(", ");
427
+ contextText += "\n\n";
428
+ }
429
+
430
+ // Labels
431
+ if (incident.labels && incident.labels.length > 0) {
432
+ contextText += "**Labels:** ";
433
+ contextText += incident.labels
434
+ .map((l: { name?: string }) => {
435
+ return l.name;
436
+ })
437
+ .join(", ");
438
+ contextText += "\n\n";
439
+ }
440
+
441
+ // Root cause if available
442
+ if (incident.rootCause) {
443
+ contextText += `**Root Cause:** ${incident.rootCause}\n\n`;
444
+ }
445
+
446
+ // State timeline
447
+ if (stateTimeline.length > 0) {
448
+ contextText += "# State Timeline\n\n";
449
+ for (const timeline of stateTimeline) {
450
+ const startTime: string = timeline.startsAt
451
+ ? OneUptimeDate.getDateAsFormattedString(timeline.startsAt)
452
+ : "N/A";
453
+ const stateName: string =
454
+ timeline.incidentState?.name?.toString() || "Unknown";
455
+ const createdBy: string =
456
+ timeline.createdByUser?.name?.toString() ||
457
+ timeline.createdByUser?.email?.toString() ||
458
+ "System";
459
+
460
+ contextText += `- **${startTime}**: State changed to **${stateName}** by ${createdBy}\n`;
461
+ if (timeline.rootCause) {
462
+ contextText += ` - Root cause noted: ${timeline.rootCause}\n`;
463
+ }
464
+ }
465
+ contextText += "\n";
466
+ }
467
+
468
+ // Include internal notes for context (for both note types)
469
+ if (internalNotes.length > 0) {
470
+ contextText += "# Internal Notes (Private)\n\n";
471
+ for (const note of internalNotes) {
472
+ const noteTime: string = note.createdAt
473
+ ? OneUptimeDate.getDateAsFormattedString(note.createdAt)
474
+ : "N/A";
475
+ const createdBy: string =
476
+ note.createdByUser?.name?.toString() ||
477
+ note.createdByUser?.email?.toString() ||
478
+ "Unknown";
479
+
480
+ contextText += `**[${noteTime}] ${createdBy}:**\n`;
481
+ contextText += `${note.note || "N/A"}\n\n`;
482
+ }
483
+ }
484
+
485
+ // Public notes
486
+ if (publicNotes.length > 0) {
487
+ contextText += "# Public Notes\n\n";
488
+ for (const note of publicNotes) {
489
+ const noteTime: string = note.postedAt
490
+ ? OneUptimeDate.getDateAsFormattedString(note.postedAt)
491
+ : note.createdAt
492
+ ? OneUptimeDate.getDateAsFormattedString(note.createdAt)
493
+ : "N/A";
494
+ const createdBy: string =
495
+ note.createdByUser?.name?.toString() ||
496
+ note.createdByUser?.email?.toString() ||
497
+ "Unknown";
498
+
499
+ contextText += `**[${noteTime}] ${createdBy}:**\n`;
500
+ contextText += `${note.note || "N/A"}\n\n`;
501
+ }
502
+ }
503
+
504
+ // Workspace messages (Slack/Teams)
505
+ if (workspaceMessages.length > 0) {
506
+ contextText += "# Discussion from Incident Channel\n\n";
507
+ contextText += WorkspaceUtil.formatMessagesAsContext(workspaceMessages, {
508
+ includeTimestamp: true,
509
+ includeUsername: true,
510
+ maxLength: 20000,
511
+ });
512
+ contextText += "\n\n";
513
+ }
514
+
515
+ // System prompt for note generation
516
+ let systemPrompt: string;
517
+
518
+ if (noteType === "public") {
519
+ if (template) {
520
+ systemPrompt = `You are an expert incident communicator. Your task is to fill in a public incident note template based on the provided incident data.
521
+
522
+ CRITICAL INSTRUCTIONS:
523
+ - You MUST use ONLY the exact template structure provided below
524
+ - Fill in each section of the template with relevant information from the incident data
525
+ - Do NOT add any new sections, headers, or content that is not part of the template
526
+ - Do NOT add introductions, conclusions, or any text outside the template structure
527
+ - Write in a professional, clear, and customer-friendly manner
528
+ - Focus on what customers need to know: impact, current status, and next steps
529
+ - Avoid technical jargon - keep it understandable for non-technical readers
530
+ - Be concise but informative
531
+
532
+ TEMPLATE TO FILL (use this exact structure):
533
+
534
+ ${template}`;
535
+ } else {
536
+ systemPrompt = `You are an expert incident communicator. Your task is to generate a public incident note that will be visible to customers on the status page.
537
+
538
+ The note should:
539
+ 1. Be written in a professional, customer-friendly tone
540
+ 2. Clearly communicate the current status of the incident
541
+ 3. Explain the impact on users without excessive technical details
542
+ 4. Provide information about what is being done to resolve the issue
543
+ 5. Set appropriate expectations about resolution timing if known
544
+ 6. Be concise but informative
545
+
546
+ DO NOT include:
547
+ - Internal technical details that customers don't need
548
+ - Blame or finger-pointing
549
+ - Confidential information
550
+ - Excessive jargon
551
+
552
+ Write in markdown format for better readability.`;
553
+ }
554
+ } else if (template) {
555
+ // Internal note with template
556
+ systemPrompt = `You are an expert Site Reliability Engineer (SRE). Your task is to fill in an internal incident note template based on the provided incident data.
557
+
558
+ CRITICAL INSTRUCTIONS:
559
+ - You MUST use ONLY the exact template structure provided below
560
+ - Fill in each section of the template with relevant information from the incident data
561
+ - Do NOT add any new sections, headers, or content that is not part of the template
562
+ - Do NOT add introductions, conclusions, or any text outside the template structure
563
+ - Be technical and detailed - this is for the internal team
564
+ - Include relevant technical details, metrics, and observations
565
+
566
+ TEMPLATE TO FILL (use this exact structure):
567
+
568
+ ${template}`;
569
+ } else {
570
+ // Internal note without template
571
+ systemPrompt = `You are an expert Site Reliability Engineer (SRE). Your task is to generate an internal incident note for the response team.
572
+
573
+ The note should:
574
+ 1. Provide technical details about the current situation
575
+ 2. Document observations, findings, or actions taken
576
+ 3. Include relevant metrics or error messages if mentioned in the context
577
+ 4. Be detailed enough to help team members understand the situation
578
+ 5. Use technical language appropriate for the engineering team
579
+
580
+ Write in markdown format for better readability. Be thorough and technical.`;
581
+ }
582
+
583
+ // Build user message
584
+ const userMessage: string = template
585
+ ? `Fill in the template above using ONLY the following incident data. Output only the filled template, nothing else:\n\n${contextText}`
586
+ : `Based on the following incident data, please generate ${noteType === "public" ? "a customer-facing public" : "an internal technical"} incident note:\n\n${contextText}`;
587
+
588
+ // Build messages array
589
+ const messages: Array<LLMMessage> = [
590
+ {
591
+ role: "system",
592
+ content: systemPrompt,
593
+ },
594
+ {
595
+ role: "user",
596
+ content: userMessage,
597
+ },
598
+ ];
599
+
600
+ return {
601
+ contextText,
602
+ systemPrompt,
603
+ messages,
604
+ };
605
+ }
606
+
395
607
  @CaptureSpan()
396
608
  public static buildGenericAIContext(data: {
397
609
  systemPrompt: string;