@oneuptime/common 9.2.16 → 9.2.18
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/CodeRepository.ts +664 -0
- package/Models/DatabaseModels/Index.ts +8 -0
- package/Models/DatabaseModels/LlmLog.ts +818 -0
- package/Models/DatabaseModels/LlmProvider.ts +21 -0
- package/Models/DatabaseModels/Project.ts +206 -0
- package/Models/DatabaseModels/ServiceCatalogCodeRepository.ts +549 -0
- package/Server/API/AIBillingAPI.ts +126 -0
- package/Server/API/AlertAPI.ts +139 -0
- package/Server/API/GitHubAPI.ts +360 -0
- package/Server/API/IncidentAPI.ts +258 -0
- package/Server/API/ScheduledMaintenanceAPI.ts +164 -0
- package/Server/EnvironmentConfig.ts +44 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1765580181582-MigrationName.ts +79 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1765633554715-MigrationName.ts +75 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1765801357168-MigrationName.ts +32 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1765810218488-MigrationName.ts +69 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1765830758857-MigrationName.ts +111 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1765834537501-MigrationName.ts +39 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +12 -0
- package/Server/Services/AIBillingService.ts +247 -0
- package/Server/Services/AIService.ts +238 -0
- package/Server/Services/CodeRepositoryService.ts +10 -0
- package/Server/Services/IncidentService.ts +88 -0
- package/Server/Services/Index.ts +2 -0
- package/Server/Services/LlmLogService.ts +14 -0
- package/Server/Services/LlmProviderService.ts +58 -0
- package/Server/Services/ServiceCatalogCodeRepositoryService.ts +55 -0
- package/Server/Utils/AI/AlertAIContextBuilder.ts +264 -0
- package/Server/Utils/AI/IncidentAIContextBuilder.ts +710 -0
- package/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.ts +345 -0
- package/Server/Utils/CodeRepository/GitHub/GitHub.ts +226 -0
- package/Server/Utils/LLM/LLMService.ts +276 -0
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +166 -0
- package/Server/Utils/Workspace/Slack/Slack.ts +134 -0
- package/Server/Utils/Workspace/Workspace.ts +126 -0
- package/Tests/Types/Domain.test.ts +24 -3
- package/Types/CodeRepository/CodeRepositoryType.ts +1 -1
- package/Types/Domain.ts +21 -24
- package/Types/LlmLogStatus.ts +7 -0
- package/Types/Permission.ts +87 -0
- package/Types/ServiceCatalog/CodeRepositoryImprovementAction.ts +9 -0
- package/UI/Components/AI/AILoader.tsx +95 -0
- package/UI/Components/AI/GenerateFromAIModal.tsx +432 -0
- package/UI/Components/Modal/Modal.tsx +6 -1
- package/build/dist/Models/DatabaseModels/CodeRepository.js +689 -0
- package/build/dist/Models/DatabaseModels/CodeRepository.js.map +1 -0
- package/build/dist/Models/DatabaseModels/Index.js +7 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/LlmLog.js +856 -0
- package/build/dist/Models/DatabaseModels/LlmLog.js.map +1 -0
- package/build/dist/Models/DatabaseModels/LlmProvider.js +22 -0
- package/build/dist/Models/DatabaseModels/LlmProvider.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +220 -0
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceCatalogCodeRepository.js +565 -0
- package/build/dist/Models/DatabaseModels/ServiceCatalogCodeRepository.js.map +1 -0
- package/build/dist/Server/API/AIBillingAPI.js +58 -0
- package/build/dist/Server/API/AIBillingAPI.js.map +1 -0
- package/build/dist/Server/API/AlertAPI.js +94 -0
- package/build/dist/Server/API/AlertAPI.js.map +1 -0
- package/build/dist/Server/API/GitHubAPI.js +207 -0
- package/build/dist/Server/API/GitHubAPI.js.map +1 -0
- package/build/dist/Server/API/IncidentAPI.js +171 -1
- package/build/dist/Server/API/IncidentAPI.js.map +1 -1
- package/build/dist/Server/API/ScheduledMaintenanceAPI.js +103 -0
- package/build/dist/Server/API/ScheduledMaintenanceAPI.js.map +1 -0
- package/build/dist/Server/EnvironmentConfig.js +31 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765580181582-MigrationName.js +34 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765580181582-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765633554715-MigrationName.js +32 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765633554715-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765801357168-MigrationName.js +38 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765801357168-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765810218488-MigrationName.js +30 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765810218488-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765830758857-MigrationName.js +44 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765830758857-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765834537501-MigrationName.js +22 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765834537501-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/AIBillingService.js +187 -0
- package/build/dist/Server/Services/AIBillingService.js.map +1 -0
- package/build/dist/Server/Services/AIService.js +184 -0
- package/build/dist/Server/Services/AIService.js.map +1 -0
- package/build/dist/Server/Services/CodeRepositoryService.js +9 -0
- package/build/dist/Server/Services/CodeRepositoryService.js.map +1 -0
- package/build/dist/Server/Services/IncidentService.js +60 -0
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +2 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/LlmLogService.js +13 -0
- package/build/dist/Server/Services/LlmLogService.js.map +1 -0
- package/build/dist/Server/Services/LlmProviderService.js +65 -0
- package/build/dist/Server/Services/LlmProviderService.js.map +1 -1
- package/build/dist/Server/Services/ServiceCatalogCodeRepositoryService.js +54 -0
- package/build/dist/Server/Services/ServiceCatalogCodeRepositoryService.js.map +1 -0
- package/build/dist/Server/Utils/AI/AlertAIContextBuilder.js +238 -0
- package/build/dist/Server/Utils/AI/AlertAIContextBuilder.js.map +1 -0
- package/build/dist/Server/Utils/AI/IncidentAIContextBuilder.js +597 -0
- package/build/dist/Server/Utils/AI/IncidentAIContextBuilder.js.map +1 -0
- package/build/dist/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.js +311 -0
- package/build/dist/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.js.map +1 -0
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js +163 -0
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js.map +1 -1
- package/build/dist/Server/Utils/LLM/LLMService.js +225 -0
- package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +110 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/Slack.js +89 -0
- package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Workspace.js +80 -0
- package/build/dist/Server/Utils/Workspace/Workspace.js.map +1 -1
- package/build/dist/Tests/Types/Domain.test.js +19 -3
- package/build/dist/Tests/Types/Domain.test.js.map +1 -1
- package/build/dist/Types/CodeRepository/CodeRepositoryType.js +1 -1
- package/build/dist/Types/CodeRepository/CodeRepositoryType.js.map +1 -1
- package/build/dist/Types/Domain.js +18 -16
- package/build/dist/Types/Domain.js.map +1 -1
- package/build/dist/Types/LlmLogStatus.js +8 -0
- package/build/dist/Types/LlmLogStatus.js.map +1 -0
- package/build/dist/Types/Permission.js +74 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/ServiceCatalog/CodeRepositoryImprovementAction.js +10 -0
- package/build/dist/Types/ServiceCatalog/CodeRepositoryImprovementAction.js.map +1 -0
- package/build/dist/UI/Components/AI/AILoader.js +64 -0
- package/build/dist/UI/Components/AI/AILoader.js.map +1 -0
- package/build/dist/UI/Components/AI/GenerateFromAIModal.js +320 -0
- package/build/dist/UI/Components/AI/GenerateFromAIModal.js.map +1 -0
- package/build/dist/UI/Components/Modal/Modal.js +6 -1
- package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,710 @@
|
|
|
1
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
2
|
+
import Incident from "../../../Models/DatabaseModels/Incident";
|
|
3
|
+
import IncidentStateTimeline from "../../../Models/DatabaseModels/IncidentStateTimeline";
|
|
4
|
+
import IncidentInternalNote from "../../../Models/DatabaseModels/IncidentInternalNote";
|
|
5
|
+
import IncidentPublicNote from "../../../Models/DatabaseModels/IncidentPublicNote";
|
|
6
|
+
import IncidentService from "../../Services/IncidentService";
|
|
7
|
+
import IncidentStateTimelineService from "../../Services/IncidentStateTimelineService";
|
|
8
|
+
import IncidentInternalNoteService from "../../Services/IncidentInternalNoteService";
|
|
9
|
+
import IncidentPublicNoteService from "../../Services/IncidentPublicNoteService";
|
|
10
|
+
import WorkspaceUtil, { WorkspaceChannelMessage } from "../Workspace/Workspace";
|
|
11
|
+
import WorkspaceProjectAuthTokenService from "../../Services/WorkspaceProjectAuthTokenService";
|
|
12
|
+
import WorkspaceProjectAuthToken from "../../../Models/DatabaseModels/WorkspaceProjectAuthToken";
|
|
13
|
+
import logger from "../Logger";
|
|
14
|
+
import CaptureSpan from "../Telemetry/CaptureSpan";
|
|
15
|
+
import OneUptimeDate from "../../../Types/Date";
|
|
16
|
+
import SortOrder from "../../../Types/BaseDatabase/SortOrder";
|
|
17
|
+
import { LLMMessage } from "../LLM/LLMService";
|
|
18
|
+
import NotificationRuleWorkspaceChannel from "../../../Types/Workspace/NotificationRules/NotificationRuleWorkspaceChannel";
|
|
19
|
+
import WorkspaceType from "../../../Types/Workspace/WorkspaceType";
|
|
20
|
+
|
|
21
|
+
export interface IncidentContextData {
|
|
22
|
+
incident: Incident;
|
|
23
|
+
stateTimeline: Array<IncidentStateTimeline>;
|
|
24
|
+
internalNotes: Array<IncidentInternalNote>;
|
|
25
|
+
publicNotes: Array<IncidentPublicNote>;
|
|
26
|
+
workspaceMessages: Array<WorkspaceChannelMessage>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface AIGenerationContext {
|
|
30
|
+
contextText: string;
|
|
31
|
+
systemPrompt: string;
|
|
32
|
+
messages: Array<LLMMessage>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default class IncidentAIContextBuilder {
|
|
36
|
+
@CaptureSpan()
|
|
37
|
+
public static async buildIncidentContext(data: {
|
|
38
|
+
incidentId: ObjectID;
|
|
39
|
+
includeWorkspaceMessages?: boolean;
|
|
40
|
+
workspaceMessageLimit?: number;
|
|
41
|
+
}): Promise<IncidentContextData> {
|
|
42
|
+
const incident: Incident | null = await IncidentService.findOneById({
|
|
43
|
+
id: data.incidentId,
|
|
44
|
+
select: {
|
|
45
|
+
_id: true,
|
|
46
|
+
title: true,
|
|
47
|
+
description: true,
|
|
48
|
+
createdAt: true,
|
|
49
|
+
postmortemNote: true,
|
|
50
|
+
remediationNotes: true,
|
|
51
|
+
rootCause: true,
|
|
52
|
+
customFields: true,
|
|
53
|
+
projectId: true,
|
|
54
|
+
incidentSeverity: {
|
|
55
|
+
name: true,
|
|
56
|
+
color: true,
|
|
57
|
+
},
|
|
58
|
+
currentIncidentState: {
|
|
59
|
+
name: true,
|
|
60
|
+
color: true,
|
|
61
|
+
},
|
|
62
|
+
monitors: {
|
|
63
|
+
name: true,
|
|
64
|
+
},
|
|
65
|
+
labels: {
|
|
66
|
+
name: true,
|
|
67
|
+
color: true,
|
|
68
|
+
},
|
|
69
|
+
postUpdatesToWorkspaceChannels: true,
|
|
70
|
+
},
|
|
71
|
+
props: {
|
|
72
|
+
isRoot: true,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
if (!incident) {
|
|
77
|
+
throw new Error("Incident not found");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Fetch state timeline
|
|
81
|
+
const stateTimeline: Array<IncidentStateTimeline> =
|
|
82
|
+
await IncidentStateTimelineService.findBy({
|
|
83
|
+
query: {
|
|
84
|
+
incidentId: data.incidentId,
|
|
85
|
+
},
|
|
86
|
+
select: {
|
|
87
|
+
_id: true,
|
|
88
|
+
createdAt: true,
|
|
89
|
+
startsAt: true,
|
|
90
|
+
endsAt: true,
|
|
91
|
+
rootCause: true,
|
|
92
|
+
incidentState: {
|
|
93
|
+
name: true,
|
|
94
|
+
color: true,
|
|
95
|
+
},
|
|
96
|
+
createdByUser: {
|
|
97
|
+
name: true,
|
|
98
|
+
email: true,
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
sort: {
|
|
102
|
+
startsAt: SortOrder.Ascending,
|
|
103
|
+
},
|
|
104
|
+
limit: 100,
|
|
105
|
+
skip: 0,
|
|
106
|
+
props: {
|
|
107
|
+
isRoot: true,
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Fetch internal notes
|
|
112
|
+
const internalNotes: Array<IncidentInternalNote> =
|
|
113
|
+
await IncidentInternalNoteService.findBy({
|
|
114
|
+
query: {
|
|
115
|
+
incidentId: data.incidentId,
|
|
116
|
+
},
|
|
117
|
+
select: {
|
|
118
|
+
_id: true,
|
|
119
|
+
note: true,
|
|
120
|
+
createdAt: true,
|
|
121
|
+
createdByUser: {
|
|
122
|
+
name: true,
|
|
123
|
+
email: true,
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
sort: {
|
|
127
|
+
createdAt: SortOrder.Ascending,
|
|
128
|
+
},
|
|
129
|
+
limit: 100,
|
|
130
|
+
skip: 0,
|
|
131
|
+
props: {
|
|
132
|
+
isRoot: true,
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Fetch public notes
|
|
137
|
+
const publicNotes: Array<IncidentPublicNote> =
|
|
138
|
+
await IncidentPublicNoteService.findBy({
|
|
139
|
+
query: {
|
|
140
|
+
incidentId: data.incidentId,
|
|
141
|
+
},
|
|
142
|
+
select: {
|
|
143
|
+
_id: true,
|
|
144
|
+
note: true,
|
|
145
|
+
createdAt: true,
|
|
146
|
+
postedAt: true,
|
|
147
|
+
createdByUser: {
|
|
148
|
+
name: true,
|
|
149
|
+
email: true,
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
sort: {
|
|
153
|
+
createdAt: SortOrder.Ascending,
|
|
154
|
+
},
|
|
155
|
+
limit: 100,
|
|
156
|
+
skip: 0,
|
|
157
|
+
props: {
|
|
158
|
+
isRoot: true,
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Fetch workspace messages if requested and channels exist
|
|
163
|
+
let workspaceMessages: Array<WorkspaceChannelMessage> = [];
|
|
164
|
+
|
|
165
|
+
const workspaceChannels:
|
|
166
|
+
| Array<NotificationRuleWorkspaceChannel>
|
|
167
|
+
| undefined = incident.postUpdatesToWorkspaceChannels as
|
|
168
|
+
| Array<NotificationRuleWorkspaceChannel>
|
|
169
|
+
| undefined;
|
|
170
|
+
|
|
171
|
+
if (
|
|
172
|
+
data.includeWorkspaceMessages &&
|
|
173
|
+
workspaceChannels &&
|
|
174
|
+
workspaceChannels.length > 0 &&
|
|
175
|
+
incident.projectId
|
|
176
|
+
) {
|
|
177
|
+
try {
|
|
178
|
+
const fetchParams: {
|
|
179
|
+
projectId: ObjectID;
|
|
180
|
+
workspaceChannels: Array<NotificationRuleWorkspaceChannel>;
|
|
181
|
+
limit?: number;
|
|
182
|
+
oldestTimestamp?: Date;
|
|
183
|
+
} = {
|
|
184
|
+
projectId: incident.projectId,
|
|
185
|
+
workspaceChannels: workspaceChannels,
|
|
186
|
+
limit: data.workspaceMessageLimit || 500,
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
if (incident.createdAt) {
|
|
190
|
+
fetchParams.oldestTimestamp = incident.createdAt;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
workspaceMessages =
|
|
194
|
+
await this.getWorkspaceMessagesForIncident(fetchParams);
|
|
195
|
+
} catch (error) {
|
|
196
|
+
logger.error(`Error fetching workspace messages: ${error}`);
|
|
197
|
+
// Continue without workspace messages
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
incident,
|
|
203
|
+
stateTimeline,
|
|
204
|
+
internalNotes,
|
|
205
|
+
publicNotes,
|
|
206
|
+
workspaceMessages,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
@CaptureSpan()
|
|
211
|
+
public static formatIncidentContextForPostmortem(
|
|
212
|
+
contextData: IncidentContextData,
|
|
213
|
+
template?: string,
|
|
214
|
+
): AIGenerationContext {
|
|
215
|
+
const {
|
|
216
|
+
incident,
|
|
217
|
+
stateTimeline,
|
|
218
|
+
internalNotes,
|
|
219
|
+
publicNotes,
|
|
220
|
+
workspaceMessages,
|
|
221
|
+
} = contextData;
|
|
222
|
+
|
|
223
|
+
let contextText: string = "";
|
|
224
|
+
|
|
225
|
+
// Basic incident information
|
|
226
|
+
contextText += "# Incident Information\n\n";
|
|
227
|
+
contextText += `**Title:** ${incident.title || "N/A"}\n\n`;
|
|
228
|
+
contextText += `**Description:** ${incident.description || "N/A"}\n\n`;
|
|
229
|
+
contextText += `**Severity:** ${incident.incidentSeverity?.name || "N/A"}\n\n`;
|
|
230
|
+
contextText += `**Current State:** ${incident.currentIncidentState?.name || "N/A"}\n\n`;
|
|
231
|
+
contextText += `**Created At:** ${incident.createdAt ? OneUptimeDate.getDateAsFormattedString(incident.createdAt) : "N/A"}\n\n`;
|
|
232
|
+
|
|
233
|
+
// Affected monitors
|
|
234
|
+
if (incident.monitors && incident.monitors.length > 0) {
|
|
235
|
+
contextText += "**Affected Monitors:** ";
|
|
236
|
+
contextText += incident.monitors
|
|
237
|
+
.map((m: { name?: string }) => {
|
|
238
|
+
return m.name;
|
|
239
|
+
})
|
|
240
|
+
.join(", ");
|
|
241
|
+
contextText += "\n\n";
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Labels
|
|
245
|
+
if (incident.labels && incident.labels.length > 0) {
|
|
246
|
+
contextText += "**Labels:** ";
|
|
247
|
+
contextText += incident.labels
|
|
248
|
+
.map((l: { name?: string }) => {
|
|
249
|
+
return l.name;
|
|
250
|
+
})
|
|
251
|
+
.join(", ");
|
|
252
|
+
contextText += "\n\n";
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Root cause if available
|
|
256
|
+
if (incident.rootCause) {
|
|
257
|
+
contextText += `**Root Cause:** ${incident.rootCause}\n\n`;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Remediation notes if available
|
|
261
|
+
if (incident.remediationNotes) {
|
|
262
|
+
contextText += `**Remediation Notes:** ${incident.remediationNotes}\n\n`;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// State timeline
|
|
266
|
+
if (stateTimeline.length > 0) {
|
|
267
|
+
contextText += "# State Timeline\n\n";
|
|
268
|
+
for (const timeline of stateTimeline) {
|
|
269
|
+
const startTime: string = timeline.startsAt
|
|
270
|
+
? OneUptimeDate.getDateAsFormattedString(timeline.startsAt)
|
|
271
|
+
: "N/A";
|
|
272
|
+
const stateName: string =
|
|
273
|
+
timeline.incidentState?.name?.toString() || "Unknown";
|
|
274
|
+
const createdBy: string =
|
|
275
|
+
timeline.createdByUser?.name?.toString() ||
|
|
276
|
+
timeline.createdByUser?.email?.toString() ||
|
|
277
|
+
"System";
|
|
278
|
+
|
|
279
|
+
contextText += `- **${startTime}**: State changed to **${stateName}** by ${createdBy}\n`;
|
|
280
|
+
if (timeline.rootCause) {
|
|
281
|
+
contextText += ` - Root cause noted: ${timeline.rootCause}\n`;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
contextText += "\n";
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Internal notes
|
|
288
|
+
if (internalNotes.length > 0) {
|
|
289
|
+
contextText += "# Internal Notes (Private)\n\n";
|
|
290
|
+
for (const note of internalNotes) {
|
|
291
|
+
const noteTime: string = note.createdAt
|
|
292
|
+
? OneUptimeDate.getDateAsFormattedString(note.createdAt)
|
|
293
|
+
: "N/A";
|
|
294
|
+
const createdBy: string =
|
|
295
|
+
note.createdByUser?.name?.toString() ||
|
|
296
|
+
note.createdByUser?.email?.toString() ||
|
|
297
|
+
"Unknown";
|
|
298
|
+
|
|
299
|
+
contextText += `**[${noteTime}] ${createdBy}:**\n`;
|
|
300
|
+
contextText += `${note.note || "N/A"}\n\n`;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Public notes
|
|
305
|
+
if (publicNotes.length > 0) {
|
|
306
|
+
contextText += "# Public Notes\n\n";
|
|
307
|
+
for (const note of publicNotes) {
|
|
308
|
+
const noteTime: string = note.postedAt
|
|
309
|
+
? OneUptimeDate.getDateAsFormattedString(note.postedAt)
|
|
310
|
+
: note.createdAt
|
|
311
|
+
? OneUptimeDate.getDateAsFormattedString(note.createdAt)
|
|
312
|
+
: "N/A";
|
|
313
|
+
const createdBy: string =
|
|
314
|
+
note.createdByUser?.name?.toString() ||
|
|
315
|
+
note.createdByUser?.email?.toString() ||
|
|
316
|
+
"Unknown";
|
|
317
|
+
|
|
318
|
+
contextText += `**[${noteTime}] ${createdBy}:**\n`;
|
|
319
|
+
contextText += `${note.note || "N/A"}\n\n`;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Workspace messages (Slack/Teams)
|
|
324
|
+
if (workspaceMessages.length > 0) {
|
|
325
|
+
contextText += "# Discussion from Incident Channel\n\n";
|
|
326
|
+
contextText += WorkspaceUtil.formatMessagesAsContext(workspaceMessages, {
|
|
327
|
+
includeTimestamp: true,
|
|
328
|
+
includeUsername: true,
|
|
329
|
+
maxLength: 30000,
|
|
330
|
+
});
|
|
331
|
+
contextText += "\n\n";
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// System prompt for postmortem generation
|
|
335
|
+
let systemPrompt: string;
|
|
336
|
+
|
|
337
|
+
if (template) {
|
|
338
|
+
// When a template is provided, strictly fill only the template
|
|
339
|
+
systemPrompt = `You are an expert Site Reliability Engineer (SRE) and incident response specialist. Your task is to fill in an incident postmortem template based on the provided incident data.
|
|
340
|
+
|
|
341
|
+
CRITICAL INSTRUCTIONS:
|
|
342
|
+
- You MUST use ONLY the exact template structure provided below
|
|
343
|
+
- Fill in each section of the template with relevant information from the incident data
|
|
344
|
+
- Do NOT add any new sections, headers, or content that is not part of the template
|
|
345
|
+
- Do NOT add introductions, conclusions, or any text outside the template structure
|
|
346
|
+
- If a section in the template has no relevant data, write "No data available" or leave the placeholder text
|
|
347
|
+
- Be blameless - focus on systemic improvements rather than individual blame
|
|
348
|
+
- Write in a professional, clear, and concise manner
|
|
349
|
+
|
|
350
|
+
TEMPLATE TO FILL (use this exact structure):
|
|
351
|
+
|
|
352
|
+
${template}`;
|
|
353
|
+
} else {
|
|
354
|
+
// When no template is provided, use standard format
|
|
355
|
+
systemPrompt = `You are an expert Site Reliability Engineer (SRE) and incident response specialist. Your task is to generate a comprehensive, well-structured incident postmortem based on the provided incident data.
|
|
356
|
+
|
|
357
|
+
The postmortem should:
|
|
358
|
+
1. Be written in a blameless manner, focusing on systemic improvements rather than individual blame
|
|
359
|
+
2. Include a clear executive summary
|
|
360
|
+
3. Provide a detailed timeline of events
|
|
361
|
+
4. Identify the root cause(s) and contributing factors
|
|
362
|
+
5. Outline the impact on users and systems
|
|
363
|
+
6. List actionable items to prevent recurrence
|
|
364
|
+
7. Include lessons learned
|
|
365
|
+
|
|
366
|
+
Use a standard incident postmortem format with sections for: Executive Summary, Timeline, Root Cause Analysis, Impact, Action Items, and Lessons Learned.
|
|
367
|
+
|
|
368
|
+
Write in a professional, clear, and concise manner. Use markdown formatting for better readability.`;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Build user message based on whether template is provided
|
|
372
|
+
const userMessage: string = template
|
|
373
|
+
? `Fill in the template above using ONLY the following incident data. Output only the filled template, nothing else:\n\n${contextText}`
|
|
374
|
+
: `Based on the following incident data, please generate a comprehensive incident postmortem:\n\n${contextText}`;
|
|
375
|
+
|
|
376
|
+
// Build messages array
|
|
377
|
+
const messages: Array<LLMMessage> = [
|
|
378
|
+
{
|
|
379
|
+
role: "system",
|
|
380
|
+
content: systemPrompt,
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
role: "user",
|
|
384
|
+
content: userMessage,
|
|
385
|
+
},
|
|
386
|
+
];
|
|
387
|
+
|
|
388
|
+
return {
|
|
389
|
+
contextText,
|
|
390
|
+
systemPrompt,
|
|
391
|
+
messages,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
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
|
+
|
|
607
|
+
@CaptureSpan()
|
|
608
|
+
public static buildGenericAIContext(data: {
|
|
609
|
+
systemPrompt: string;
|
|
610
|
+
userPrompt: string;
|
|
611
|
+
context?: string;
|
|
612
|
+
}): AIGenerationContext {
|
|
613
|
+
let userContent: string = data.userPrompt;
|
|
614
|
+
|
|
615
|
+
if (data.context) {
|
|
616
|
+
userContent = `${data.userPrompt}\n\nContext:\n${data.context}`;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
const messages: Array<LLMMessage> = [
|
|
620
|
+
{
|
|
621
|
+
role: "system",
|
|
622
|
+
content: data.systemPrompt,
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
role: "user",
|
|
626
|
+
content: userContent,
|
|
627
|
+
},
|
|
628
|
+
];
|
|
629
|
+
|
|
630
|
+
return {
|
|
631
|
+
contextText: data.context || "",
|
|
632
|
+
systemPrompt: data.systemPrompt,
|
|
633
|
+
messages,
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
@CaptureSpan()
|
|
638
|
+
private static async getWorkspaceMessagesForIncident(data: {
|
|
639
|
+
projectId: ObjectID;
|
|
640
|
+
workspaceChannels: Array<NotificationRuleWorkspaceChannel>;
|
|
641
|
+
limit?: number;
|
|
642
|
+
oldestTimestamp?: Date;
|
|
643
|
+
}): Promise<Array<WorkspaceChannelMessage>> {
|
|
644
|
+
const allMessages: Array<WorkspaceChannelMessage> = [];
|
|
645
|
+
|
|
646
|
+
for (const channel of data.workspaceChannels) {
|
|
647
|
+
try {
|
|
648
|
+
// Get auth token for this workspace type
|
|
649
|
+
const projectAuth: WorkspaceProjectAuthToken | null =
|
|
650
|
+
await WorkspaceProjectAuthTokenService.getProjectAuth({
|
|
651
|
+
projectId: data.projectId,
|
|
652
|
+
workspaceType: channel.workspaceType,
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
if (!projectAuth || !projectAuth.authToken) {
|
|
656
|
+
logger.debug(
|
|
657
|
+
`No auth token found for workspace type: ${channel.workspaceType}`,
|
|
658
|
+
);
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
const messagesParams: {
|
|
663
|
+
channelId: string;
|
|
664
|
+
authToken: string;
|
|
665
|
+
projectId: ObjectID;
|
|
666
|
+
workspaceType: WorkspaceType;
|
|
667
|
+
teamId?: string;
|
|
668
|
+
limit?: number;
|
|
669
|
+
oldestTimestamp?: Date;
|
|
670
|
+
} = {
|
|
671
|
+
channelId: channel.id,
|
|
672
|
+
authToken: projectAuth.authToken,
|
|
673
|
+
projectId: data.projectId,
|
|
674
|
+
workspaceType: channel.workspaceType,
|
|
675
|
+
};
|
|
676
|
+
|
|
677
|
+
if (channel.teamId) {
|
|
678
|
+
messagesParams.teamId = channel.teamId;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
if (data.limit !== undefined) {
|
|
682
|
+
messagesParams.limit = data.limit;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
if (data.oldestTimestamp) {
|
|
686
|
+
messagesParams.oldestTimestamp = data.oldestTimestamp;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
const messages: Array<WorkspaceChannelMessage> =
|
|
690
|
+
await WorkspaceUtil.getChannelMessages(messagesParams);
|
|
691
|
+
|
|
692
|
+
allMessages.push(...messages);
|
|
693
|
+
} catch (error) {
|
|
694
|
+
logger.error(
|
|
695
|
+
`Error fetching messages from channel ${channel.id}: ${error}`,
|
|
696
|
+
);
|
|
697
|
+
// Continue with other channels even if one fails
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// Sort all messages by timestamp
|
|
702
|
+
allMessages.sort(
|
|
703
|
+
(a: WorkspaceChannelMessage, b: WorkspaceChannelMessage) => {
|
|
704
|
+
return a.timestamp.getTime() - b.timestamp.getTime();
|
|
705
|
+
},
|
|
706
|
+
);
|
|
707
|
+
|
|
708
|
+
return allMessages;
|
|
709
|
+
}
|
|
710
|
+
}
|