@oneuptime/common 7.0.3882 → 7.0.3899

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 (34) hide show
  1. package/Models/AnalyticsModels/TelemetryAttribute.ts +8 -8
  2. package/Server/API/TelemetryAPI.ts +2 -4
  3. package/Server/API/WorkspaceNotificationRuleAPI.ts +48 -0
  4. package/Server/Services/IncidentService.ts +3 -1
  5. package/Server/Services/OpenTelemetryIngestService.ts +5 -10
  6. package/Server/Services/TelemetryAttributeService.ts +13 -27
  7. package/Server/Services/WorkspaceNotificationRuleService.ts +209 -1
  8. package/Server/Utils/Telemetry/CaptureSpan.ts +7 -1
  9. package/Server/Utils/Telemetry/Telemetry.ts +125 -83
  10. package/Server/Utils/Workspace/Slack/Slack.ts +65 -10
  11. package/Server/Utils/Workspace/WorkspaceBase.ts +9 -0
  12. package/build/dist/Models/AnalyticsModels/TelemetryAttribute.js +8 -8
  13. package/build/dist/Models/AnalyticsModels/TelemetryAttribute.js.map +1 -1
  14. package/build/dist/Server/API/TelemetryAPI.js +2 -3
  15. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  16. package/build/dist/Server/API/WorkspaceNotificationRuleAPI.js +29 -0
  17. package/build/dist/Server/API/WorkspaceNotificationRuleAPI.js.map +1 -0
  18. package/build/dist/Server/Services/IncidentService.js +3 -1
  19. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  20. package/build/dist/Server/Services/OpenTelemetryIngestService.js +5 -7
  21. package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
  22. package/build/dist/Server/Services/TelemetryAttributeService.js +13 -24
  23. package/build/dist/Server/Services/TelemetryAttributeService.js.map +1 -1
  24. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +166 -1
  25. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
  26. package/build/dist/Server/Utils/Telemetry/CaptureSpan.js +1 -1
  27. package/build/dist/Server/Utils/Telemetry/CaptureSpan.js.map +1 -1
  28. package/build/dist/Server/Utils/Telemetry/Telemetry.js +84 -56
  29. package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
  30. package/build/dist/Server/Utils/Workspace/Slack/Slack.js +57 -24
  31. package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
  32. package/build/dist/Server/Utils/Workspace/WorkspaceBase.js +9 -0
  33. package/build/dist/Server/Utils/Workspace/WorkspaceBase.js.map +1 -1
  34. package/package.json +2 -2
@@ -106,11 +106,11 @@ export default class TelemetryAttribute extends AnalyticsBaseModel {
106
106
  }),
107
107
 
108
108
  new AnalyticsTableColumn({
109
- key: "attribute",
110
- title: "Attribute",
111
- description: "Attribute",
109
+ key: "attributes",
110
+ title: "Attributes",
111
+ description: "Attributes",
112
112
  required: true,
113
- type: TableColumnType.Text,
113
+ type: TableColumnType.JSONArray,
114
114
  accessControl: {
115
115
  read: [
116
116
  Permission.ProjectOwner,
@@ -153,11 +153,11 @@ export default class TelemetryAttribute extends AnalyticsBaseModel {
153
153
  this.setColumnValue("telemetryType", v);
154
154
  }
155
155
 
156
- public get attribute(): string | undefined {
157
- return this.getColumnValue("attribute") as string | undefined;
156
+ public get attributes(): Array<string> | undefined {
157
+ return this.getColumnValue("attributes") as Array<string> | undefined;
158
158
  }
159
159
 
160
- public set attribute(v: string | undefined) {
161
- this.setColumnValue("attribute", v);
160
+ public set attributes(v: Array<string> | undefined) {
161
+ this.setColumnValue("attributes", v);
162
162
  }
163
163
  }
@@ -11,7 +11,6 @@ import CommonAPI from "./CommonAPI";
11
11
  import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps";
12
12
  import TelemetryType from "Common/Types/Telemetry/TelemetryType";
13
13
  import TelemetryAttributeService from "../Services/TelemetryAttributeService";
14
- import ArrayUtil from "Common/Utils/Array";
15
14
 
16
15
  const router: ExpressRouter = Express.getRouter();
17
16
 
@@ -72,12 +71,11 @@ const getAttributes: GetAttributesFunction = async (
72
71
  );
73
72
  }
74
73
 
75
- const attributes: string[] = ArrayUtil.removeDuplicates(
74
+ const attributes: string[] =
76
75
  await TelemetryAttributeService.fetchAttributes({
77
76
  projectId: databaseProps.tenantId,
78
77
  telemetryType,
79
- }),
80
- );
78
+ });
81
79
 
82
80
  return Response.sendJsonObjectResponse(req, res, {
83
81
  attributes: attributes,
@@ -0,0 +1,48 @@
1
+ import UserMiddleware from "../Middleware/UserAuthorization";
2
+ import WorkspaceNotificationRuleService, {
3
+ Service as WorkspaceNotificationRuleServiceType,
4
+ } from "../Services/WorkspaceNotificationRuleService";
5
+ import {
6
+ ExpressRequest,
7
+ ExpressResponse,
8
+ NextFunction,
9
+ } from "../Utils/Express";
10
+ import Response from "../Utils/Response";
11
+ import BaseAPI from "./BaseAPI";
12
+ import CommonAPI from "./CommonAPI";
13
+ import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps";
14
+ import WorkspaceNotificationRule from "Common/Models/DatabaseModels/WorkspaceNotificationRule";
15
+ import ObjectID from "../../Types/ObjectID";
16
+
17
+ export default class WorkspaceNotificationRuleAPI extends BaseAPI<
18
+ WorkspaceNotificationRule,
19
+ WorkspaceNotificationRuleServiceType
20
+ > {
21
+ public constructor() {
22
+ super(WorkspaceNotificationRule, WorkspaceNotificationRuleService);
23
+
24
+ this.router.get(
25
+ `${new this.entityType().getCrudApiPath()?.toString()}/test/:workspaceNotifcationRuleId`,
26
+ UserMiddleware.getUserMiddleware,
27
+ async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
28
+ try {
29
+ const databaseProps: DatabaseCommonInteractionProps =
30
+ await CommonAPI.getDatabaseCommonInteractionProps(req);
31
+
32
+ await this.service.testRule({
33
+ ruleId: new ObjectID(
34
+ req.params["workspaceNotifcationRuleId"] as string,
35
+ ),
36
+ props: databaseProps,
37
+ projectId: databaseProps.tenantId!,
38
+ testByUserId: databaseProps.userId!,
39
+ });
40
+
41
+ return Response.sendEmptySuccessResponse(req, res);
42
+ } catch (e) {
43
+ next(e);
44
+ }
45
+ },
46
+ );
47
+ }
48
+ }
@@ -302,7 +302,9 @@ export class Service extends DatabaseService<Model> {
302
302
  return 0;
303
303
  }
304
304
 
305
- return lastIncident.incidentNumber || 0;
305
+ return lastIncident.incidentNumber
306
+ ? Number(lastIncident.incidentNumber)
307
+ : 0;
306
308
  }
307
309
 
308
310
  @CaptureSpan()
@@ -1,6 +1,5 @@
1
1
  import OneUptimeDate from "Common/Types/Date";
2
2
  import { JSONArray, JSONObject } from "Common/Types/JSON";
3
- import JSONFunctions from "Common/Types/JSONFunctions";
4
3
  import ObjectID from "Common/Types/ObjectID";
5
4
  import Metric, {
6
5
  AggregationTemporality,
@@ -166,21 +165,17 @@ export default class OTelIngestService {
166
165
  }
167
166
 
168
167
  newDbMetric.attributes = {
169
- ...(newDbMetric.attributes || {}),
168
+ ...TelemetryUtil.getAttributesForServiceIdAndServiceName({
169
+ serviceId: data.telemetryServiceId,
170
+ serviceName: data.telemetryServiceName,
171
+ }),
170
172
  ...TelemetryUtil.getAttributes({
171
173
  items: datapoint["attributes"] as JSONArray,
172
- telemetryServiceId: data.telemetryServiceId,
173
- telemetryServiceName: data.telemetryServiceName,
174
+ prefixKeysWithString: "metricAttributes",
174
175
  }),
175
176
  };
176
177
  }
177
178
 
178
- if (newDbMetric.attributes) {
179
- newDbMetric.attributes = JSONFunctions.flattenObject(
180
- newDbMetric.attributes,
181
- );
182
- }
183
-
184
179
  // aggregationTemporality
185
180
 
186
181
  if (aggregationTemporality) {
@@ -2,7 +2,6 @@ import TelemetryType from "../../Types/Telemetry/TelemetryType";
2
2
  import ClickhouseDatabase from "../Infrastructure/ClickhouseDatabase";
3
3
  import AnalyticsDatabaseService from "./AnalyticsDatabaseService";
4
4
  import TelemetryAttribute from "Common/Models/AnalyticsModels/TelemetryAttribute";
5
- import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
6
5
  import ObjectID from "../../Types/ObjectID";
7
6
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
8
7
 
@@ -16,30 +15,24 @@ export class TelemetryAttributeService extends AnalyticsDatabaseService<Telemetr
16
15
  projectId: ObjectID;
17
16
  telemetryType: TelemetryType;
18
17
  }): Promise<string[]> {
19
- const attributes: TelemetryAttribute[] = await this.findBy({
18
+ const telemetryAttribute: TelemetryAttribute | null = await this.findOneBy({
20
19
  query: {
21
20
  projectId: data.projectId,
22
21
  telemetryType: data.telemetryType,
23
22
  },
24
23
  select: {
25
- attribute: true,
24
+ attributes: true,
26
25
  },
27
- limit: LIMIT_PER_PROJECT,
28
- skip: 0,
29
26
  props: {
30
27
  isRoot: true,
31
28
  },
32
29
  });
33
30
 
34
- const dbAttributes: string[] = attributes
35
- .map((attribute: TelemetryAttribute) => {
36
- return attribute.attribute;
37
- })
38
- .filter((attribute: string | undefined) => {
39
- return Boolean(attribute);
40
- }) as string[];
41
-
42
- return dbAttributes.sort();
31
+ return telemetryAttribute &&
32
+ telemetryAttribute.attributes &&
33
+ telemetryAttribute
34
+ ? telemetryAttribute.attributes
35
+ : [];
43
36
  }
44
37
 
45
38
  @CaptureSpan()
@@ -61,21 +54,14 @@ export class TelemetryAttributeService extends AnalyticsDatabaseService<Telemetr
61
54
  },
62
55
  });
63
56
 
64
- const telemetryAttributes: TelemetryAttribute[] = [];
65
-
66
- // insert new attributes
67
- for (const attribute of attributes) {
68
- const telemetryAttribute: TelemetryAttribute = new TelemetryAttribute();
69
-
70
- telemetryAttribute.projectId = projectId;
71
- telemetryAttribute.telemetryType = telemetryType;
72
- telemetryAttribute.attribute = attribute;
57
+ const telemetryAttribute: TelemetryAttribute = new TelemetryAttribute();
73
58
 
74
- telemetryAttributes.push(telemetryAttribute);
75
- }
59
+ telemetryAttribute.projectId = projectId;
60
+ telemetryAttribute.telemetryType = telemetryType;
61
+ telemetryAttribute.attributes = attributes;
76
62
 
77
- await this.createMany({
78
- items: telemetryAttributes,
63
+ await this.create({
64
+ data: telemetryAttribute,
79
65
  props: {
80
66
  isRoot: true,
81
67
  },
@@ -38,6 +38,8 @@ import WorkspaceNotificationRule from "Common/Models/DatabaseModels/WorkspaceNot
38
38
  import UserService from "./UserService";
39
39
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
40
40
  import Monitor from "../../Models/DatabaseModels/Monitor";
41
+ import Text from "../../Types/Text";
42
+ import DatabaseCommonInteractionProps from "../../Types/BaseDatabase/DatabaseCommonInteractionProps";
41
43
 
42
44
  export interface MessageBlocksByWorkspaceType {
43
45
  workspaceType: WorkspaceType;
@@ -56,6 +58,209 @@ export class Service extends DatabaseService<WorkspaceNotificationRule> {
56
58
  super(WorkspaceNotificationRule);
57
59
  }
58
60
 
61
+ @CaptureSpan()
62
+ public async testRule(data: {
63
+ ruleId: ObjectID;
64
+ projectId: ObjectID;
65
+ testByUserId: ObjectID; // this can be useful to invite user to channels if the channels was created.
66
+ props: DatabaseCommonInteractionProps;
67
+ }): Promise<void> {
68
+ const rule: WorkspaceNotificationRule | null = await this.findOneById({
69
+ id: data.ruleId,
70
+ select: {
71
+ notificationRule: true,
72
+ workspaceType: true,
73
+ eventType: true,
74
+ name: true,
75
+ },
76
+ props: {
77
+ isRoot: true,
78
+ },
79
+ });
80
+
81
+ if (!rule) {
82
+ throw new BadDataException("Rule not found");
83
+ }
84
+
85
+ // check fi the testUser account is connected to workspace or not.
86
+ const userWOrkspaceAuth: WorkspaceUserAuthToken | null =
87
+ await WorkspaceUserAuthTokenService.findOneBy({
88
+ query: {
89
+ projectId: data.projectId,
90
+ userId: data.testByUserId,
91
+ workspaceType: rule.workspaceType!,
92
+ },
93
+ select: {
94
+ workspaceUserId: true,
95
+ },
96
+ props: {
97
+ isRoot: true,
98
+ },
99
+ });
100
+
101
+ if (!userWOrkspaceAuth || !userWOrkspaceAuth.workspaceUserId) {
102
+ throw new BadDataException(
103
+ "This account is not connected to " +
104
+ rule.workspaceType +
105
+ ". Please go to User Settings and connect the account.",
106
+ );
107
+ }
108
+
109
+ const projectAuth: WorkspaceProjectAuthToken | null =
110
+ await WorkspaceProjectAuthTokenService.findOneBy({
111
+ query: {
112
+ projectId: data.projectId,
113
+ workspaceType: rule.workspaceType!,
114
+ },
115
+ select: {
116
+ workspaceType: true,
117
+ authToken: true,
118
+ },
119
+ props: {
120
+ isRoot: true,
121
+ },
122
+ });
123
+
124
+ if (!projectAuth) {
125
+ throw new BadDataException(
126
+ "This project is not connected to " +
127
+ rule.workspaceType +
128
+ ". Please go to Project Settings and connect the account.",
129
+ );
130
+ }
131
+
132
+ const projectAuthToken: string = projectAuth.authToken!;
133
+
134
+ const notificationRule: BaseNotificationRule =
135
+ rule.notificationRule as BaseNotificationRule;
136
+
137
+ // now send test message to these channels.
138
+ const messageBlocksByWorkspaceTypes: Array<MessageBlocksByWorkspaceType> =
139
+ [];
140
+
141
+ // use markdown to create blocks
142
+ messageBlocksByWorkspaceTypes.push({
143
+ workspaceType: rule.workspaceType!,
144
+ messageBlocks: [
145
+ {
146
+ _type: "WorkspacePayloadMarkdown",
147
+ text: `This is a test message for rule **${rule.name}**`,
148
+ } as WorkspacePayloadMarkdown,
149
+ ],
150
+ });
151
+
152
+ let existingChannelNames: Array<string> = [];
153
+
154
+ let createdChannels: NotificationRuleWorkspaceChannel[] = [];
155
+
156
+ if ((notificationRule as IncidentNotificationRule).shouldCreateNewChannel) {
157
+ const generateRandomString: string = Text.generateRandomText(5);
158
+
159
+ try {
160
+ // create channel
161
+ createdChannels = await this.createChannelsBasedOnRules({
162
+ projectOrUserAuthTokenForWorkspace: projectAuthToken,
163
+ workspaceType: rule.workspaceType!,
164
+ notificationRules: [rule],
165
+ channelNameSiffix: generateRandomString,
166
+ notificationEventType: rule.eventType!,
167
+ });
168
+ } catch (err) {
169
+ throw new BadDataException(
170
+ "Cannot create a new channel. " + (err as Error)?.message,
171
+ );
172
+ }
173
+
174
+ try {
175
+ await this.inviteUsersBasedOnRulesAndWorkspaceChannels({
176
+ workspaceChannels: createdChannels,
177
+ projectId: data.projectId,
178
+ notificationRules: [rule],
179
+ userIds: [data.testByUserId],
180
+ });
181
+ } catch (err) {
182
+ throw new BadDataException(
183
+ "Cannot invite users to the channel. " + (err as Error)?.message,
184
+ );
185
+ }
186
+ }
187
+
188
+ if (notificationRule.shouldPostToExistingChannel) {
189
+ existingChannelNames = this.getExistingChannelNamesFromNotificationRules({
190
+ notificationRules: [notificationRule],
191
+ });
192
+
193
+ for (const channelName of existingChannelNames) {
194
+ try {
195
+ // check if these channels exist.
196
+ const channelExists: boolean =
197
+ await WorkspaceUtil.getWorkspaceTypeUtil(
198
+ rule.workspaceType!,
199
+ ).doesChannelExist({
200
+ authToken: projectAuthToken,
201
+ channelName: channelName,
202
+ });
203
+
204
+ if (!channelExists) {
205
+ throw new BadDataException(
206
+ `Channel ${channelName} does not exist.`,
207
+ );
208
+ }
209
+ } catch (err) {
210
+ throw new BadDataException((err as Error)?.message);
211
+ }
212
+ }
213
+
214
+ // post message
215
+
216
+ for (const createdChannel of createdChannels) {
217
+ try {
218
+ await WorkspaceUtil.postMessageToAllWorkspaceChannelsAsBot({
219
+ projectId: data.projectId,
220
+ messagePayloadsByWorkspace: messageBlocksByWorkspaceTypes.map(
221
+ (messageBlocksByWorkspaceType: MessageBlocksByWorkspaceType) => {
222
+ return {
223
+ _type: "WorkspaceMessagePayload",
224
+ workspaceType: messageBlocksByWorkspaceType.workspaceType,
225
+ messageBlocks: messageBlocksByWorkspaceType.messageBlocks,
226
+ channelNames: [],
227
+ channelIds: [createdChannel.id],
228
+ };
229
+ },
230
+ ),
231
+ });
232
+ } catch (err) {
233
+ throw new BadDataException(
234
+ "Cannot post message to channel. " + (err as Error)?.message,
235
+ );
236
+ }
237
+ }
238
+
239
+ for (const existingChannelName of existingChannelNames) {
240
+ try {
241
+ await WorkspaceUtil.postMessageToAllWorkspaceChannelsAsBot({
242
+ projectId: data.projectId,
243
+ messagePayloadsByWorkspace: messageBlocksByWorkspaceTypes.map(
244
+ (messageBlocksByWorkspaceType: MessageBlocksByWorkspaceType) => {
245
+ return {
246
+ _type: "WorkspaceMessagePayload",
247
+ workspaceType: messageBlocksByWorkspaceType.workspaceType,
248
+ messageBlocks: messageBlocksByWorkspaceType.messageBlocks,
249
+ channelNames: [existingChannelName],
250
+ channelIds: [],
251
+ };
252
+ },
253
+ ),
254
+ });
255
+ } catch (err) {
256
+ throw new BadDataException(
257
+ "Cannot post message to channel. " + (err as Error)?.message,
258
+ );
259
+ }
260
+ }
261
+ }
262
+ }
263
+
59
264
  @CaptureSpan()
60
265
  public async sendWorkspaceMarkdownNotification(data: {
61
266
  projectId: ObjectID;
@@ -103,6 +308,7 @@ export class Service extends DatabaseService<WorkspaceNotificationRule> {
103
308
  projectId: data.projectId,
104
309
  notificationRuleEventType: NotificationRuleEventType.Monitor,
105
310
  workspaceType: messageBlocksByWorkspaceType.workspaceType,
311
+ notificationFor: data.notificationFor,
106
312
  });
107
313
 
108
314
  let monitorChannels: Array<WorkspaceChannel> = [];
@@ -198,15 +404,17 @@ export class Service extends DatabaseService<WorkspaceNotificationRule> {
198
404
  projectId: ObjectID;
199
405
  workspaceType: WorkspaceType;
200
406
  notificationRuleEventType: NotificationRuleEventType;
407
+ notificationFor: NotificationFor;
201
408
  }): Promise<Array<string>> {
202
409
  logger.debug("getExistingChannelNamesBasedOnEventType called with data:");
203
410
  logger.debug(data);
204
411
 
205
412
  const notificationRules: Array<WorkspaceNotificationRule> =
206
- await this.getNotificationRules({
413
+ await this.getMatchingNotificationRules({
207
414
  projectId: data.projectId,
208
415
  workspaceType: data.workspaceType,
209
416
  notificationRuleEventType: data.notificationRuleEventType,
417
+ notificationFor: data.notificationFor,
210
418
  });
211
419
 
212
420
  logger.debug("Notification rules retrieved:");
@@ -63,7 +63,13 @@ function CaptureSpan(data?: {
63
63
  return Telemetry.startActiveSpan({
64
64
  name: name,
65
65
  options: {
66
- attributes: spanAttributes,
66
+ attributes: {
67
+ ...spanAttributes,
68
+ hey: "there",
69
+ thisIsNumber: 123,
70
+ thisIsBoolean: true,
71
+ thisIsArray: [1, 2, 3],
72
+ },
67
73
  },
68
74
  fn: (span: Span) => {
69
75
  let result: any = null;