@oneuptime/common 10.0.85 → 10.0.88

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 (90) hide show
  1. package/Models/DatabaseModels/EnterpriseLicense.ts +54 -0
  2. package/Models/DatabaseModels/GlobalConfig.ts +51 -0
  3. package/Server/API/EnterpriseLicenseAPI.ts +83 -0
  4. package/Server/API/GlobalConfigAPI.ts +59 -0
  5. package/Server/API/TelemetryAPI.ts +24 -0
  6. package/Server/EnvironmentConfig.ts +10 -0
  7. package/Server/Infrastructure/Postgres/SchemaMigrations/1777629313843-MigrationName.ts +59 -0
  8. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  9. package/Server/Infrastructure/Queue.ts +4 -4
  10. package/Server/Services/TelemetryAttributeService.ts +37 -3
  11. package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +174 -7
  12. package/Tests/Types/Date.test.ts +46 -0
  13. package/Types/Date.ts +9 -4
  14. package/UI/Components/AutocompleteTextInput/AutocompleteTextInput.tsx +60 -21
  15. package/UI/Components/Dictionary/Dictionary.tsx +188 -26
  16. package/UI/Components/Dictionary/DictionaryFilterOperator.ts +357 -0
  17. package/UI/Components/Dictionary/DictionaryOfStrings.tsx +12 -7
  18. package/UI/Components/EditionLabel/EditionLabel.tsx +224 -10
  19. package/UI/Components/Filters/FilterViewer.tsx +81 -16
  20. package/UI/Components/Filters/FiltersForm.tsx +18 -3
  21. package/UI/Components/Filters/JSONFilter.tsx +11 -2
  22. package/UI/Components/Filters/Types/Filter.ts +3 -0
  23. package/UI/Components/Forms/Fields/FormField.tsx +6 -1
  24. package/UI/Components/Forms/Types/Field.ts +5 -0
  25. package/UI/Components/LogsViewer/LogsViewer.tsx +73 -4
  26. package/UI/Components/LogsViewer/components/LogSearchBar.tsx +77 -31
  27. package/UI/Components/LogsViewer/components/LogSearchSuggestions.tsx +44 -1
  28. package/UI/Components/LogsViewer/components/LogsFilterCard.tsx +7 -5
  29. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +6 -0
  30. package/UI/Components/TelemetryViewer/components/TelemetrySearchBar.tsx +84 -25
  31. package/UI/Components/TelemetryViewer/components/TelemetrySearchSuggestions.tsx +44 -1
  32. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js +57 -0
  33. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js.map +1 -1
  34. package/build/dist/Models/DatabaseModels/GlobalConfig.js +54 -0
  35. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  36. package/build/dist/Server/API/EnterpriseLicenseAPI.js +64 -1
  37. package/build/dist/Server/API/EnterpriseLicenseAPI.js.map +1 -1
  38. package/build/dist/Server/API/GlobalConfigAPI.js +47 -0
  39. package/build/dist/Server/API/GlobalConfigAPI.js.map +1 -1
  40. package/build/dist/Server/API/TelemetryAPI.js +9 -0
  41. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  42. package/build/dist/Server/EnvironmentConfig.js +3 -0
  43. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  44. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1777629313843-MigrationName.js +26 -0
  45. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1777629313843-MigrationName.js.map +1 -0
  46. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  47. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  48. package/build/dist/Server/Infrastructure/Queue.js +3 -3
  49. package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
  50. package/build/dist/Server/Services/TelemetryAttributeService.js +36 -7
  51. package/build/dist/Server/Services/TelemetryAttributeService.js.map +1 -1
  52. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +135 -5
  53. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
  54. package/build/dist/Tests/Types/Date.test.js +40 -0
  55. package/build/dist/Tests/Types/Date.test.js.map +1 -1
  56. package/build/dist/Types/Date.js +7 -2
  57. package/build/dist/Types/Date.js.map +1 -1
  58. package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js +21 -10
  59. package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js.map +1 -1
  60. package/build/dist/UI/Components/Dictionary/Dictionary.js +109 -16
  61. package/build/dist/UI/Components/Dictionary/Dictionary.js.map +1 -1
  62. package/build/dist/UI/Components/Dictionary/DictionaryFilterOperator.js +263 -0
  63. package/build/dist/UI/Components/Dictionary/DictionaryFilterOperator.js.map +1 -0
  64. package/build/dist/UI/Components/Dictionary/DictionaryOfStrings.js +10 -6
  65. package/build/dist/UI/Components/Dictionary/DictionaryOfStrings.js.map +1 -1
  66. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +124 -6
  67. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  68. package/build/dist/UI/Components/Filters/FilterViewer.js +50 -12
  69. package/build/dist/UI/Components/Filters/FilterViewer.js.map +1 -1
  70. package/build/dist/UI/Components/Filters/FiltersForm.js +5 -4
  71. package/build/dist/UI/Components/Filters/FiltersForm.js.map +1 -1
  72. package/build/dist/UI/Components/Filters/JSONFilter.js +1 -1
  73. package/build/dist/UI/Components/Filters/JSONFilter.js.map +1 -1
  74. package/build/dist/UI/Components/Forms/Fields/FormField.js +1 -1
  75. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  76. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +54 -5
  77. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  78. package/build/dist/UI/Components/LogsViewer/components/LogSearchBar.js +59 -29
  79. package/build/dist/UI/Components/LogsViewer/components/LogSearchBar.js.map +1 -1
  80. package/build/dist/UI/Components/LogsViewer/components/LogSearchSuggestions.js +10 -2
  81. package/build/dist/UI/Components/LogsViewer/components/LogSearchSuggestions.js.map +1 -1
  82. package/build/dist/UI/Components/LogsViewer/components/LogsFilterCard.js +2 -5
  83. package/build/dist/UI/Components/LogsViewer/components/LogsFilterCard.js.map +1 -1
  84. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +1 -1
  85. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
  86. package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js +59 -22
  87. package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js.map +1 -1
  88. package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchSuggestions.js +10 -2
  89. package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchSuggestions.js.map +1 -1
  90. package/package.json +1 -1
@@ -101,4 +101,58 @@ export default class EnterpriseLicense extends BaseModel {
101
101
  type: ColumnType.Number,
102
102
  })
103
103
  public annualContractValue?: number = undefined;
104
+
105
+ @ColumnAccessControl({
106
+ create: [],
107
+ read: [],
108
+ update: [],
109
+ })
110
+ @TableColumn({
111
+ required: false,
112
+ type: TableColumnType.Number,
113
+ title: "User Limit",
114
+ description:
115
+ "Maximum number of users allowed under this enterprise license.",
116
+ })
117
+ @Column({
118
+ nullable: true,
119
+ type: ColumnType.Number,
120
+ })
121
+ public userLimit?: number = undefined;
122
+
123
+ @ColumnAccessControl({
124
+ create: [],
125
+ read: [],
126
+ update: [],
127
+ })
128
+ @TableColumn({
129
+ required: false,
130
+ type: TableColumnType.Number,
131
+ title: "Current User Count",
132
+ description:
133
+ "Most recent user count reported by the customer's self-hosted installation.",
134
+ })
135
+ @Column({
136
+ nullable: true,
137
+ type: ColumnType.Number,
138
+ })
139
+ public currentUserCount?: number = undefined;
140
+
141
+ @ColumnAccessControl({
142
+ create: [],
143
+ read: [],
144
+ update: [],
145
+ })
146
+ @TableColumn({
147
+ required: false,
148
+ type: TableColumnType.Date,
149
+ title: "User Count Updated At",
150
+ description:
151
+ "Timestamp of the most recent user count report from the customer's self-hosted installation.",
152
+ })
153
+ @Column({
154
+ nullable: true,
155
+ type: ColumnType.Date,
156
+ })
157
+ public userCountUpdatedAt?: Date = undefined;
104
158
  }
@@ -756,6 +756,57 @@ export default class GlobalConfig extends GlobalConfigModel {
756
756
  })
757
757
  public enterpriseLicenseToken?: string = undefined;
758
758
 
759
+ @ColumnAccessControl({
760
+ create: [],
761
+ read: [],
762
+ update: [],
763
+ })
764
+ @TableColumn({
765
+ type: TableColumnType.Number,
766
+ title: "Enterprise License User Limit",
767
+ description:
768
+ "Maximum number of users permitted under the validated enterprise license.",
769
+ })
770
+ @Column({
771
+ type: ColumnType.Number,
772
+ nullable: true,
773
+ })
774
+ public enterpriseLicenseUserLimit?: number = undefined;
775
+
776
+ @ColumnAccessControl({
777
+ create: [],
778
+ read: [],
779
+ update: [],
780
+ })
781
+ @TableColumn({
782
+ type: TableColumnType.Number,
783
+ title: "Enterprise License Current User Count",
784
+ description:
785
+ "User count last reported to OneUptime for the validated enterprise license.",
786
+ })
787
+ @Column({
788
+ type: ColumnType.Number,
789
+ nullable: true,
790
+ })
791
+ public enterpriseLicenseCurrentUserCount?: number = undefined;
792
+
793
+ @ColumnAccessControl({
794
+ create: [],
795
+ read: [],
796
+ update: [],
797
+ })
798
+ @TableColumn({
799
+ type: TableColumnType.Date,
800
+ title: "Enterprise License User Count Updated At",
801
+ description:
802
+ "Timestamp of the most recent user count report sent to OneUptime for the validated enterprise license.",
803
+ })
804
+ @Column({
805
+ type: ColumnType.Date,
806
+ nullable: true,
807
+ })
808
+ public enterpriseLicenseUserCountUpdatedAt?: Date = undefined;
809
+
759
810
  @ColumnAccessControl({
760
811
  create: [],
761
812
  read: [],
@@ -6,6 +6,7 @@ import EnterpriseLicenseService, {
6
6
  } from "../Services/EnterpriseLicenseService";
7
7
  import UserMiddleware from "../Middleware/UserAuthorization";
8
8
  import JSONWebToken from "../Utils/JsonWebToken";
9
+ import OneUptimeDate from "../../Types/Date";
9
10
  import Response from "../Utils/Response";
10
11
  import {
11
12
  ExpressRequest,
@@ -52,6 +53,9 @@ export default class EnterpriseLicenseAPI extends BaseAPI<
52
53
  companyName: true,
53
54
  expiresAt: true,
54
55
  licenseKey: true,
56
+ userLimit: true,
57
+ currentUserCount: true,
58
+ userCountUpdatedAt: true,
55
59
  },
56
60
  props: {
57
61
  isRoot: true,
@@ -80,6 +84,8 @@ export default class EnterpriseLicenseAPI extends BaseAPI<
80
84
  companyName: license.companyName || "",
81
85
  expiresAt: license.expiresAt.toISOString(),
82
86
  licenseKey: license.licenseKey || "",
87
+ userLimit:
88
+ typeof license.userLimit === "number" ? license.userLimit : null,
83
89
  };
84
90
 
85
91
  const token: string = JSONWebToken.signJsonPayload(
@@ -91,6 +97,14 @@ export default class EnterpriseLicenseAPI extends BaseAPI<
91
97
  companyName: payload["companyName"] as string,
92
98
  expiresAt: payload["expiresAt"] as string,
93
99
  licenseKey: payload["licenseKey"] as string,
100
+ userLimit: payload["userLimit"],
101
+ currentUserCount:
102
+ typeof license.currentUserCount === "number"
103
+ ? license.currentUserCount
104
+ : null,
105
+ userCountUpdatedAt: license.userCountUpdatedAt
106
+ ? license.userCountUpdatedAt.toISOString()
107
+ : null,
94
108
  token,
95
109
  });
96
110
  } catch (err) {
@@ -98,5 +112,74 @@ export default class EnterpriseLicenseAPI extends BaseAPI<
98
112
  }
99
113
  },
100
114
  );
115
+
116
+ this.router.post(
117
+ `${new this.entityType().getCrudApiPath()?.toString()}/report-user-count`,
118
+ async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
119
+ try {
120
+ const licenseKey: string | undefined = (
121
+ req.body["licenseKey"] as string | undefined
122
+ )?.trim();
123
+ const rawUserCount: unknown = req.body["userCount"];
124
+
125
+ if (!licenseKey) {
126
+ throw new BadDataException("License key is required");
127
+ }
128
+
129
+ const userCount: number = Number(rawUserCount);
130
+
131
+ if (
132
+ !Number.isFinite(userCount) ||
133
+ userCount < 0 ||
134
+ !Number.isInteger(userCount)
135
+ ) {
136
+ throw new BadDataException(
137
+ "userCount must be a non-negative integer",
138
+ );
139
+ }
140
+
141
+ const license: EnterpriseLicense | null =
142
+ await EnterpriseLicenseService.findOneBy({
143
+ query: {
144
+ licenseKey: licenseKey,
145
+ },
146
+ select: {
147
+ _id: true,
148
+ userLimit: true,
149
+ },
150
+ props: {
151
+ isRoot: true,
152
+ },
153
+ });
154
+
155
+ if (!license) {
156
+ throw new BadDataException("License key is invalid");
157
+ }
158
+
159
+ const reportedAt: Date = OneUptimeDate.getCurrentDate();
160
+
161
+ await EnterpriseLicenseService.updateOneById({
162
+ id: license.id!,
163
+ data: {
164
+ currentUserCount: userCount,
165
+ userCountUpdatedAt: reportedAt,
166
+ },
167
+ props: {
168
+ isRoot: true,
169
+ ignoreHooks: true,
170
+ },
171
+ });
172
+
173
+ return Response.sendJsonObjectResponse(req, res, {
174
+ currentUserCount: userCount,
175
+ userCountUpdatedAt: reportedAt.toISOString(),
176
+ userLimit:
177
+ typeof license.userLimit === "number" ? license.userLimit : null,
178
+ });
179
+ } catch (err) {
180
+ next(err);
181
+ }
182
+ },
183
+ );
101
184
  }
102
185
  }
@@ -67,6 +67,9 @@ export default class GlobalConfigAPI extends BaseAPI<
67
67
  enterpriseLicenseExpiresAt: true,
68
68
  enterpriseLicenseKey: true,
69
69
  enterpriseLicenseToken: true,
70
+ enterpriseLicenseUserLimit: true,
71
+ enterpriseLicenseCurrentUserCount: true,
72
+ enterpriseLicenseUserCountUpdatedAt: true,
70
73
  },
71
74
  props: {
72
75
  isRoot: true,
@@ -80,6 +83,17 @@ export default class GlobalConfigAPI extends BaseAPI<
80
83
  : null,
81
84
  licenseKey: config?.enterpriseLicenseKey || null,
82
85
  token: config?.enterpriseLicenseToken || null,
86
+ userLimit:
87
+ typeof config?.enterpriseLicenseUserLimit === "number"
88
+ ? config.enterpriseLicenseUserLimit
89
+ : null,
90
+ currentUserCount:
91
+ typeof config?.enterpriseLicenseCurrentUserCount === "number"
92
+ ? config.enterpriseLicenseCurrentUserCount
93
+ : null,
94
+ userCountUpdatedAt: config?.enterpriseLicenseUserCountUpdatedAt
95
+ ? config.enterpriseLicenseUserCountUpdatedAt.toISOString()
96
+ : null,
83
97
  };
84
98
 
85
99
  return Response.sendJsonObjectResponse(req, res, responseBody);
@@ -143,11 +157,38 @@ export default class GlobalConfigAPI extends BaseAPI<
143
157
  licenseExpiry = parsedDate;
144
158
  }
145
159
 
160
+ const userLimitRaw: unknown = payload["userLimit"];
161
+ const userLimit: number | null =
162
+ typeof userLimitRaw === "number" && Number.isFinite(userLimitRaw)
163
+ ? userLimitRaw
164
+ : null;
165
+
166
+ const currentUserCountRaw: unknown = payload["currentUserCount"];
167
+ const currentUserCount: number | null =
168
+ typeof currentUserCountRaw === "number" &&
169
+ Number.isFinite(currentUserCountRaw)
170
+ ? currentUserCountRaw
171
+ : null;
172
+
173
+ const userCountUpdatedAtRaw: string | undefined = payload[
174
+ "userCountUpdatedAt"
175
+ ] as string | undefined;
176
+ let userCountUpdatedAt: Date | null = null;
177
+ if (userCountUpdatedAtRaw) {
178
+ const parsedReportedAt: Date = new Date(userCountUpdatedAtRaw);
179
+ if (!Number.isNaN(parsedReportedAt.getTime())) {
180
+ userCountUpdatedAt = parsedReportedAt;
181
+ }
182
+ }
183
+
146
184
  const updatePayload: PartialEntity<GlobalConfig> = {
147
185
  enterpriseCompanyName: companyNameRaw || null,
148
186
  enterpriseLicenseKey: licenseKeyRaw || null,
149
187
  enterpriseLicenseExpiresAt: licenseExpiry || null,
150
188
  enterpriseLicenseToken: licenseToken || null,
189
+ enterpriseLicenseUserLimit: userLimit,
190
+ enterpriseLicenseCurrentUserCount: currentUserCount,
191
+ enterpriseLicenseUserCountUpdatedAt: userCountUpdatedAt,
151
192
  };
152
193
 
153
194
  const globalConfigId: ObjectID = ObjectID.getZeroObjectID();
@@ -193,6 +234,19 @@ export default class GlobalConfigAPI extends BaseAPI<
193
234
  newConfig.enterpriseLicenseExpiresAt = licenseExpiry;
194
235
  }
195
236
 
237
+ if (userLimit !== null) {
238
+ newConfig.enterpriseLicenseUserLimit = userLimit;
239
+ }
240
+
241
+ if (currentUserCount !== null) {
242
+ newConfig.enterpriseLicenseCurrentUserCount = currentUserCount;
243
+ }
244
+
245
+ if (userCountUpdatedAt) {
246
+ newConfig.enterpriseLicenseUserCountUpdatedAt =
247
+ userCountUpdatedAt;
248
+ }
249
+
196
250
  await GlobalConfigService.create({
197
251
  data: newConfig,
198
252
  props: {
@@ -207,6 +261,11 @@ export default class GlobalConfigAPI extends BaseAPI<
207
261
  expiresAt: licenseExpiry ? licenseExpiry.toISOString() : null,
208
262
  licenseKey: licenseKeyRaw || null,
209
263
  token: licenseToken || null,
264
+ userLimit: userLimit,
265
+ currentUserCount: currentUserCount,
266
+ userCountUpdatedAt: userCountUpdatedAt
267
+ ? userCountUpdatedAt.toISOString()
268
+ : null,
210
269
  });
211
270
  } catch (err) {
212
271
  next(err);
@@ -80,6 +80,14 @@ router.post(
80
80
  },
81
81
  );
82
82
 
83
+ router.post(
84
+ "/telemetry/logs/get-attribute-values",
85
+ UserMiddleware.getUserMiddleware,
86
+ async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
87
+ return getAttributeValues(req, res, next, TelemetryType.Log);
88
+ },
89
+ );
90
+
83
91
  router.post(
84
92
  "/telemetry/traces/get-attributes",
85
93
  UserMiddleware.getUserMiddleware,
@@ -96,6 +104,22 @@ router.post(
96
104
  },
97
105
  );
98
106
 
107
+ router.post(
108
+ "/telemetry/exceptions/get-attributes",
109
+ UserMiddleware.getUserMiddleware,
110
+ async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
111
+ return getAttributes(req, res, next, TelemetryType.Exception);
112
+ },
113
+ );
114
+
115
+ router.post(
116
+ "/telemetry/exceptions/get-attribute-values",
117
+ UserMiddleware.getUserMiddleware,
118
+ async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
119
+ return getAttributeValues(req, res, next, TelemetryType.Exception);
120
+ },
121
+ );
122
+
99
123
  type GetAttributesFunction = (
100
124
  req: ExpressRequest,
101
125
  res: ExpressResponse,
@@ -162,6 +162,12 @@ export const ClusterKey: ObjectID = new ObjectID(
162
162
 
163
163
  export const HasClusterKey: boolean = Boolean(process.env["ONEUPTIME_SECRET"]);
164
164
 
165
+ export const EnableQueueDashboard: boolean =
166
+ process.env["ENABLE_QUEUE_DASHBOARD"] === "true";
167
+
168
+ export const QueueDashboardSecret: string =
169
+ process.env["QUEUE_DASHBOARD_SECRET"] || "";
170
+
165
171
  export const RegisterProbeKey: ObjectID = new ObjectID(
166
172
  process.env["REGISTER_PROBE_KEY"] || "secret",
167
173
  );
@@ -513,6 +519,10 @@ export const EnterpriseLicenseValidationUrl: URL = URL.fromString(
513
519
  "https://oneuptime.com/api/enterprise-license/validate",
514
520
  );
515
521
 
522
+ export const EnterpriseLicenseUserCountReportUrl: URL = URL.fromString(
523
+ "https://oneuptime.com/api/enterprise-license/report-user-count",
524
+ );
525
+
516
526
  // Inbound Email Configuration for Incoming Email Monitor
517
527
  export enum InboundEmailProviderType {
518
528
  SendGrid = "SendGrid",
@@ -0,0 +1,59 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class MigrationName1777629313843 implements MigrationInterface {
4
+ public name: string = "MigrationName1777629313843";
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(
8
+ `ALTER TABLE "GlobalConfig" ADD "enterpriseLicenseUserLimit" integer`,
9
+ );
10
+ await queryRunner.query(
11
+ `ALTER TABLE "GlobalConfig" ADD "enterpriseLicenseCurrentUserCount" integer`,
12
+ );
13
+ await queryRunner.query(
14
+ `ALTER TABLE "GlobalConfig" ADD "enterpriseLicenseUserCountUpdatedAt" TIMESTAMP WITH TIME ZONE`,
15
+ );
16
+ await queryRunner.query(
17
+ `ALTER TABLE "EnterpriseLicense" ADD "userLimit" integer`,
18
+ );
19
+ await queryRunner.query(
20
+ `ALTER TABLE "EnterpriseLicense" ADD "currentUserCount" integer`,
21
+ );
22
+ await queryRunner.query(
23
+ `ALTER TABLE "EnterpriseLicense" ADD "userCountUpdatedAt" TIMESTAMP WITH TIME ZONE`,
24
+ );
25
+ await queryRunner.query(
26
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type":"Recurring","value":{"intervalType":"Day","intervalCount":{"_type":"PositiveNumber","value":1}}}'`,
27
+ );
28
+ await queryRunner.query(
29
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type":"RestrictionTimes","value":{"restictionType":"None","dayRestrictionTimes":null,"weeklyRestrictionTimes":[]}}'`,
30
+ );
31
+ }
32
+
33
+ public async down(queryRunner: QueryRunner): Promise<void> {
34
+ await queryRunner.query(
35
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type": "RestrictionTimes", "value": {"restictionType": "None", "dayRestrictionTimes": null, "weeklyRestrictionTimes": []}}'`,
36
+ );
37
+ await queryRunner.query(
38
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type": "Recurring", "value": {"intervalType": "Day", "intervalCount": {"_type": "PositiveNumber", "value": 1}}}'`,
39
+ );
40
+ await queryRunner.query(
41
+ `ALTER TABLE "EnterpriseLicense" DROP COLUMN "userCountUpdatedAt"`,
42
+ );
43
+ await queryRunner.query(
44
+ `ALTER TABLE "EnterpriseLicense" DROP COLUMN "currentUserCount"`,
45
+ );
46
+ await queryRunner.query(
47
+ `ALTER TABLE "EnterpriseLicense" DROP COLUMN "userLimit"`,
48
+ );
49
+ await queryRunner.query(
50
+ `ALTER TABLE "GlobalConfig" DROP COLUMN "enterpriseLicenseUserCountUpdatedAt"`,
51
+ );
52
+ await queryRunner.query(
53
+ `ALTER TABLE "GlobalConfig" DROP COLUMN "enterpriseLicenseCurrentUserCount"`,
54
+ );
55
+ await queryRunner.query(
56
+ `ALTER TABLE "GlobalConfig" DROP COLUMN "enterpriseLicenseUserLimit"`,
57
+ );
58
+ }
59
+ }
@@ -295,6 +295,7 @@ import { AddTelemetryRetentionSettings1777018175127 } from "./1777018175127-AddT
295
295
  import { AddMonitorTemplate1777201966799 } from "./1777201966799-AddMonitorTemplate";
296
296
  import { MigrationName1777550162848 } from "./1777550162848-MigrationName";
297
297
  import { MigrationName1777571961028 } from "./1777571961028-MigrationName";
298
+ import { MigrationName1777629313843 } from "./1777629313843-MigrationName";
298
299
  export default [
299
300
  InitialMigration,
300
301
  MigrationName1717678334852,
@@ -593,4 +594,5 @@ export default [
593
594
  AddMonitorTemplate1777201966799,
594
595
  MigrationName1777550162848,
595
596
  MigrationName1777571961028,
597
+ MigrationName1777629313843,
596
598
  ];
@@ -1,4 +1,4 @@
1
- import { ClusterKey } from "../EnvironmentConfig";
1
+ import { QueueDashboardSecret } from "../EnvironmentConfig";
2
2
  import Dictionary from "../../Types/Dictionary";
3
3
  import { JSONObject } from "../../Types/JSON";
4
4
  import { Queue as BullQueue, Job, JobsOptions, RepeatableJob } from "bullmq";
@@ -153,7 +153,7 @@ export default class Queue {
153
153
 
154
154
  @CaptureSpan()
155
155
  public static getInspectorRoute(): string {
156
- return "/worker/inspect/queue/:clusterKey";
156
+ return "/worker/inspect/queue/:dashboardSecret";
157
157
  }
158
158
 
159
159
  @CaptureSpan()
@@ -174,8 +174,8 @@ export default class Queue {
174
174
 
175
175
  serverAdapter.setBasePath(
176
176
  this.getInspectorRoute().replace(
177
- "/:clusterKey",
178
- "/" + ClusterKey.toString(),
177
+ "/:dashboardSecret",
178
+ "/" + QueueDashboardSecret,
179
179
  ),
180
180
  );
181
181
 
@@ -3,6 +3,7 @@ import TelemetryType from "../../Types/Telemetry/TelemetryType";
3
3
  import LogDatabaseService from "./LogService";
4
4
  import MetricDatabaseService from "./MetricService";
5
5
  import SpanDatabaseService from "./SpanService";
6
+ import ExceptionInstanceService from "./ExceptionInstanceService";
6
7
  import TableColumnType from "../../Types/AnalyticsDatabase/TableColumnType";
7
8
  import { JSONObject } from "../../Types/JSON";
8
9
  import ObjectID from "../../Types/ObjectID";
@@ -18,7 +19,12 @@ type TelemetrySource = {
18
19
  service: AnalyticsDatabaseService<any>;
19
20
  tableName: string;
20
21
  attributesColumn: string;
21
- attributeKeysColumn: string;
22
+ /*
23
+ * Some tables (e.g. ExceptionInstance) don't have a separate
24
+ * attributeKeys array column — only the attributes map. Leave this
25
+ * undefined for those; the SQL falls back to mapKeys(attributes).
26
+ */
27
+ attributeKeysColumn?: string | undefined;
22
28
  timeColumn: string;
23
29
  };
24
30
 
@@ -62,6 +68,14 @@ export class TelemetryAttributeService {
62
68
  attributeKeysColumn: "attributeKeys",
63
69
  timeColumn: "startTime",
64
70
  };
71
+ case TelemetryType.Exception:
72
+ return {
73
+ service: ExceptionInstanceService,
74
+ tableName: ExceptionInstanceService.model.tableName,
75
+ attributesColumn: "attributes",
76
+ // ExceptionInstance has no attributeKeys column.
77
+ timeColumn: "time",
78
+ };
65
79
  default:
66
80
  return null;
67
81
  }
@@ -225,14 +239,21 @@ export class TelemetryAttributeService {
225
239
  projectId: ObjectID;
226
240
  tableName: string;
227
241
  attributesColumn: string;
228
- attributeKeysColumn: string;
242
+ attributeKeysColumn?: string | undefined;
229
243
  timeColumn: string;
230
244
  metricName?: string | undefined;
231
245
  }): Statement {
232
246
  const lookbackStartDate: Date =
233
247
  TelemetryAttributeService.getLookbackStartDate();
234
248
 
235
- const statement: Statement = SQL`
249
+ /*
250
+ * If the source has a denormalized attributeKeys array column, prefer it
251
+ * (avoids materializing every row's map). Otherwise fall back to
252
+ * mapKeys(attributes) — slower but works for tables that don't carry
253
+ * the precomputed array (e.g. ExceptionInstance).
254
+ */
255
+ const statement: Statement = data.attributeKeysColumn
256
+ ? SQL`
236
257
  WITH filtered AS (
237
258
  SELECT arrayJoin(
238
259
  if(
@@ -250,6 +271,19 @@ export class TelemetryAttributeService {
250
271
  NOT empty(${data.attributeKeysColumn}) OR
251
272
  NOT empty(${data.attributesColumn})
252
273
  )
274
+ AND ${data.timeColumn} >= ${{
275
+ type: TableColumnType.Date,
276
+ value: lookbackStartDate,
277
+ }}`
278
+ : SQL`
279
+ WITH filtered AS (
280
+ SELECT arrayJoin(mapKeys(${data.attributesColumn})) AS attribute
281
+ FROM ${data.tableName}
282
+ WHERE projectId = ${{
283
+ type: TableColumnType.ObjectID,
284
+ value: data.projectId,
285
+ }}
286
+ AND NOT empty(${data.attributesColumn})
253
287
  AND ${data.timeColumn} >= ${{
254
288
  type: TableColumnType.Date,
255
289
  value: lookbackStartDate,