@oneuptime/common 10.4.8 → 10.4.9

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 (82) hide show
  1. package/Server/API/TelemetryExceptionAPI.ts +62 -0
  2. package/Server/Services/TelemetryExceptionService.ts +225 -0
  3. package/Server/Types/Database/QueryHelper.ts +35 -0
  4. package/Server/Types/Database/QueryUtil.ts +74 -2
  5. package/Types/Dashboard/DashboardComponents/DashboardAlertListComponent.ts +1 -0
  6. package/Types/Dashboard/DashboardComponents/DashboardDockerContainerListComponent.ts +1 -0
  7. package/Types/Dashboard/DashboardComponents/DashboardDockerHostListComponent.ts +1 -0
  8. package/Types/Dashboard/DashboardComponents/DashboardDockerImageListComponent.ts +1 -0
  9. package/Types/Dashboard/DashboardComponents/DashboardDockerNetworkListComponent.ts +1 -0
  10. package/Types/Dashboard/DashboardComponents/DashboardDockerVolumeListComponent.ts +1 -0
  11. package/Types/Dashboard/DashboardComponents/DashboardHostListComponent.ts +1 -0
  12. package/Types/Dashboard/DashboardComponents/DashboardIncidentListComponent.ts +1 -0
  13. package/Types/Dashboard/DashboardComponents/DashboardKubernetesCronJobListComponent.ts +1 -0
  14. package/Types/Dashboard/DashboardComponents/DashboardKubernetesDaemonSetListComponent.ts +1 -0
  15. package/Types/Dashboard/DashboardComponents/DashboardKubernetesDeploymentListComponent.ts +1 -0
  16. package/Types/Dashboard/DashboardComponents/DashboardKubernetesJobListComponent.ts +1 -0
  17. package/Types/Dashboard/DashboardComponents/DashboardKubernetesNamespaceListComponent.ts +1 -0
  18. package/Types/Dashboard/DashboardComponents/DashboardKubernetesNodeListComponent.ts +1 -0
  19. package/Types/Dashboard/DashboardComponents/DashboardKubernetesPodListComponent.ts +1 -0
  20. package/Types/Dashboard/DashboardComponents/DashboardKubernetesStatefulSetListComponent.ts +1 -0
  21. package/Types/Dashboard/DashboardComponents/DashboardMonitorListComponent.ts +1 -0
  22. package/Types/Dashboard/DashboardComponents/DashboardTraceListComponent.ts +1 -0
  23. package/Types/Time/RangeStartAndEndDateTime.ts +8 -0
  24. package/Types/Time/TimeRange.ts +1 -0
  25. package/UI/Components/LogsViewer/components/LogTimeRangePicker.tsx +1 -0
  26. package/UI/Components/ModelTable/BaseModelTable.tsx +64 -17
  27. package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +1 -0
  28. package/Utils/Dashboard/Components/DashboardAlertListComponent.ts +5 -0
  29. package/Utils/Dashboard/Components/DashboardDockerContainerListComponent.ts +7 -0
  30. package/Utils/Dashboard/Components/DashboardDockerHostListComponent.ts +5 -0
  31. package/Utils/Dashboard/Components/DashboardDockerImageListComponent.ts +5 -0
  32. package/Utils/Dashboard/Components/DashboardDockerNetworkListComponent.ts +5 -0
  33. package/Utils/Dashboard/Components/DashboardDockerVolumeListComponent.ts +5 -0
  34. package/Utils/Dashboard/Components/DashboardHostListComponent.ts +3 -0
  35. package/Utils/Dashboard/Components/DashboardIncidentListComponent.ts +5 -0
  36. package/Utils/Dashboard/Components/DashboardKubernetesResourceListShared.ts +3 -0
  37. package/Utils/Dashboard/Components/DashboardListSharedArgs.ts +31 -0
  38. package/Utils/Dashboard/Components/DashboardMonitorListComponent.ts +5 -0
  39. package/Utils/Dashboard/Components/DashboardTraceListComponent.ts +5 -0
  40. package/build/dist/Server/API/TelemetryExceptionAPI.js +37 -1
  41. package/build/dist/Server/API/TelemetryExceptionAPI.js.map +1 -1
  42. package/build/dist/Server/Services/TelemetryExceptionService.js +175 -0
  43. package/build/dist/Server/Services/TelemetryExceptionService.js.map +1 -1
  44. package/build/dist/Server/Types/Database/QueryHelper.js +35 -0
  45. package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
  46. package/build/dist/Server/Types/Database/QueryUtil.js +58 -2
  47. package/build/dist/Server/Types/Database/QueryUtil.js.map +1 -1
  48. package/build/dist/Types/Time/RangeStartAndEndDateTime.js +4 -0
  49. package/build/dist/Types/Time/RangeStartAndEndDateTime.js.map +1 -1
  50. package/build/dist/Types/Time/TimeRange.js +1 -0
  51. package/build/dist/Types/Time/TimeRange.js.map +1 -1
  52. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js +1 -0
  53. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js.map +1 -1
  54. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +48 -13
  55. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  56. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +1 -0
  57. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -1
  58. package/build/dist/Utils/Dashboard/Components/DashboardAlertListComponent.js +2 -0
  59. package/build/dist/Utils/Dashboard/Components/DashboardAlertListComponent.js.map +1 -1
  60. package/build/dist/Utils/Dashboard/Components/DashboardDockerContainerListComponent.js +2 -0
  61. package/build/dist/Utils/Dashboard/Components/DashboardDockerContainerListComponent.js.map +1 -1
  62. package/build/dist/Utils/Dashboard/Components/DashboardDockerHostListComponent.js +2 -0
  63. package/build/dist/Utils/Dashboard/Components/DashboardDockerHostListComponent.js.map +1 -1
  64. package/build/dist/Utils/Dashboard/Components/DashboardDockerImageListComponent.js +2 -0
  65. package/build/dist/Utils/Dashboard/Components/DashboardDockerImageListComponent.js.map +1 -1
  66. package/build/dist/Utils/Dashboard/Components/DashboardDockerNetworkListComponent.js +2 -0
  67. package/build/dist/Utils/Dashboard/Components/DashboardDockerNetworkListComponent.js.map +1 -1
  68. package/build/dist/Utils/Dashboard/Components/DashboardDockerVolumeListComponent.js +2 -0
  69. package/build/dist/Utils/Dashboard/Components/DashboardDockerVolumeListComponent.js.map +1 -1
  70. package/build/dist/Utils/Dashboard/Components/DashboardHostListComponent.js +2 -0
  71. package/build/dist/Utils/Dashboard/Components/DashboardHostListComponent.js.map +1 -1
  72. package/build/dist/Utils/Dashboard/Components/DashboardIncidentListComponent.js +2 -0
  73. package/build/dist/Utils/Dashboard/Components/DashboardIncidentListComponent.js.map +1 -1
  74. package/build/dist/Utils/Dashboard/Components/DashboardKubernetesResourceListShared.js +2 -0
  75. package/build/dist/Utils/Dashboard/Components/DashboardKubernetesResourceListShared.js.map +1 -1
  76. package/build/dist/Utils/Dashboard/Components/DashboardListSharedArgs.js +21 -0
  77. package/build/dist/Utils/Dashboard/Components/DashboardListSharedArgs.js.map +1 -0
  78. package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js +2 -0
  79. package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js.map +1 -1
  80. package/build/dist/Utils/Dashboard/Components/DashboardTraceListComponent.js +2 -0
  81. package/build/dist/Utils/Dashboard/Components/DashboardTraceListComponent.js.map +1 -1
  82. package/package.json +1 -1
@@ -1,9 +1,13 @@
1
1
  import TelemetryException from "../../Models/DatabaseModels/TelemetryException";
2
+ import TelemetryServiceModel from "../../Models/DatabaseModels/Service";
2
3
  import AIAgentTask from "../../Models/DatabaseModels/AIAgentTask";
3
4
  import AIAgentTaskTelemetryException from "../../Models/DatabaseModels/AIAgentTaskTelemetryException";
4
5
  import BadDataException from "../../Types/Exception/BadDataException";
5
6
  import ObjectID from "../../Types/ObjectID";
7
+ import BaseModel from "../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
6
8
  import TelemetryExceptionService, {
9
+ DashboardServiceSummary,
10
+ DashboardSummaryResult,
7
11
  Service as TelemetryExceptionServiceType,
8
12
  } from "../Services/TelemetryExceptionService";
9
13
  import AIAgentTaskTelemetryExceptionService from "../Services/AIAgentTaskTelemetryExceptionService";
@@ -21,6 +25,7 @@ import AIAgentTaskStatus, {
21
25
  AIAgentTaskStatusHelper,
22
26
  } from "../../Types/AI/AIAgentTaskStatus";
23
27
  import QueryHelper from "../Types/Database/QueryHelper";
28
+ import { JSONArray, JSONObject } from "../../Types/JSON";
24
29
 
25
30
  export default class TelemetryExceptionAPI extends BaseAPI<
26
31
  TelemetryException,
@@ -58,6 +63,23 @@ export default class TelemetryExceptionAPI extends BaseAPI<
58
63
  }
59
64
  },
60
65
  );
66
+
67
+ /*
68
+ * Aggregated dashboard summary for the Exceptions overview page.
69
+ * Returns counts, top/recent exceptions, and per-service summaries
70
+ * in a single round-trip with one SQL GROUP BY for service aggregation.
71
+ */
72
+ this.router.post(
73
+ `${new this.entityType().getCrudApiPath()?.toString()}/dashboard-summary`,
74
+ UserMiddleware.getUserMiddleware,
75
+ async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
76
+ try {
77
+ await this.getDashboardSummary(req, res);
78
+ } catch (err) {
79
+ next(err);
80
+ }
81
+ },
82
+ );
61
83
  }
62
84
 
63
85
  private async createAIAgentTask(
@@ -166,4 +188,44 @@ export default class TelemetryExceptionAPI extends BaseAPI<
166
188
  },
167
189
  });
168
190
  }
191
+
192
+ private async getDashboardSummary(
193
+ req: ExpressRequest,
194
+ res: ExpressResponse,
195
+ ): Promise<void> {
196
+ const props: DatabaseCommonInteractionProps =
197
+ await CommonAPI.getDatabaseCommonInteractionProps(req);
198
+
199
+ const summary: DashboardSummaryResult =
200
+ await this.service.getDashboardSummary(props);
201
+
202
+ const topExceptionsJson: JSONArray = BaseModel.toJSONArray(
203
+ summary.topExceptions,
204
+ TelemetryException,
205
+ );
206
+
207
+ const recentExceptionsJson: JSONArray = BaseModel.toJSONArray(
208
+ summary.recentExceptions,
209
+ TelemetryException,
210
+ );
211
+
212
+ const serviceSummariesJson: JSONArray = summary.serviceSummaries.map(
213
+ (entry: DashboardServiceSummary): JSONObject => {
214
+ return {
215
+ service: BaseModel.toJSON(entry.service, TelemetryServiceModel),
216
+ unresolvedCount: entry.unresolvedCount,
217
+ totalOccurrences: entry.totalOccurrences,
218
+ };
219
+ },
220
+ );
221
+
222
+ return Response.sendJsonObjectResponse(req, res, {
223
+ unresolvedCount: summary.unresolvedCount,
224
+ resolvedCount: summary.resolvedCount,
225
+ archivedCount: summary.archivedCount,
226
+ topExceptions: topExceptionsJson,
227
+ recentExceptions: recentExceptionsJson,
228
+ serviceSummaries: serviceSummariesJson,
229
+ });
230
+ }
169
231
  }
@@ -1,8 +1,11 @@
1
1
  import DatabaseService from "./DatabaseService";
2
2
  import Model from "../../Models/DatabaseModels/TelemetryException";
3
+ import TelemetryServiceModel from "../../Models/DatabaseModels/Service";
3
4
  import AIAgentTask from "../../Models/DatabaseModels/AIAgentTask";
4
5
  import AIAgentTaskTelemetryException from "../../Models/DatabaseModels/AIAgentTaskTelemetryException";
5
6
  import ObjectID from "../../Types/ObjectID";
7
+ import PositiveNumber from "../../Types/PositiveNumber";
8
+ import SortOrder from "../../Types/BaseDatabase/SortOrder";
6
9
  import BadDataException from "../../Types/Exception/BadDataException";
7
10
  import AIAgentTaskType from "../../Types/AI/AIAgentTaskType";
8
11
  import AIAgentTaskStatus from "../../Types/AI/AIAgentTaskStatus";
@@ -10,7 +13,9 @@ import { FixExceptionTaskMetadata } from "../../Types/AI/AIAgentTaskMetadata";
10
13
  import DatabaseCommonInteractionProps from "../../Types/BaseDatabase/DatabaseCommonInteractionProps";
11
14
  import AIAgentTaskService from "./AIAgentTaskService";
12
15
  import AIAgentTaskTelemetryExceptionService from "./AIAgentTaskTelemetryExceptionService";
16
+ import ServiceService from "./ServiceService";
13
17
  import QueryHelper from "../Types/Database/QueryHelper";
18
+ import ModelPermission from "../Types/Database/Permissions/Index";
14
19
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
15
20
 
16
21
  export interface CreateAIAgentTaskForExceptionParams {
@@ -18,6 +23,21 @@ export interface CreateAIAgentTaskForExceptionParams {
18
23
  props: DatabaseCommonInteractionProps;
19
24
  }
20
25
 
26
+ export interface DashboardServiceSummary {
27
+ service: TelemetryServiceModel;
28
+ unresolvedCount: number;
29
+ totalOccurrences: number;
30
+ }
31
+
32
+ export interface DashboardSummaryResult {
33
+ unresolvedCount: number;
34
+ resolvedCount: number;
35
+ archivedCount: number;
36
+ topExceptions: Array<Model>;
37
+ recentExceptions: Array<Model>;
38
+ serviceSummaries: Array<DashboardServiceSummary>;
39
+ }
40
+
21
41
  export class Service extends DatabaseService<Model> {
22
42
  public constructor() {
23
43
  super(Model);
@@ -141,6 +161,211 @@ export class Service extends DatabaseService<Model> {
141
161
  return createdTask;
142
162
  }
143
163
 
164
+ @CaptureSpan()
165
+ public async getDashboardSummary(
166
+ props: DatabaseCommonInteractionProps,
167
+ ): Promise<DashboardSummaryResult> {
168
+ if (!props.tenantId) {
169
+ throw new BadDataException("Project ID is required");
170
+ }
171
+
172
+ const projectId: ObjectID = props.tenantId;
173
+
174
+ const exceptionSelect: any = {
175
+ _id: true,
176
+ message: true,
177
+ exceptionType: true,
178
+ fingerprint: true,
179
+ isResolved: true,
180
+ isArchived: true,
181
+ occuranceCount: true,
182
+ lastSeenAt: true,
183
+ firstSeenAt: true,
184
+ environment: true,
185
+ service: {
186
+ _id: true,
187
+ name: true,
188
+ serviceColor: true,
189
+ },
190
+ };
191
+
192
+ const [
193
+ unresolvedCount,
194
+ resolvedCount,
195
+ archivedCount,
196
+ topExceptions,
197
+ recentExceptions,
198
+ serviceSummaries,
199
+ ] = await Promise.all([
200
+ this.countBy({
201
+ query: {
202
+ projectId,
203
+ isResolved: false,
204
+ isArchived: false,
205
+ },
206
+ props,
207
+ }),
208
+ this.countBy({
209
+ query: {
210
+ projectId,
211
+ isResolved: true,
212
+ isArchived: false,
213
+ },
214
+ props,
215
+ }),
216
+ this.countBy({
217
+ query: {
218
+ projectId,
219
+ isArchived: true,
220
+ },
221
+ props,
222
+ }),
223
+ this.findBy({
224
+ query: {
225
+ projectId,
226
+ isResolved: false,
227
+ isArchived: false,
228
+ },
229
+ select: exceptionSelect,
230
+ limit: 10,
231
+ skip: 0,
232
+ sort: {
233
+ occuranceCount: SortOrder.Descending,
234
+ },
235
+ props,
236
+ }),
237
+ this.findBy({
238
+ query: {
239
+ projectId,
240
+ isResolved: false,
241
+ isArchived: false,
242
+ },
243
+ select: exceptionSelect,
244
+ limit: 5,
245
+ skip: 0,
246
+ sort: {
247
+ lastSeenAt: SortOrder.Descending,
248
+ },
249
+ props,
250
+ }),
251
+ this.aggregateUnresolvedByService(projectId, props),
252
+ ]);
253
+
254
+ return {
255
+ unresolvedCount: unresolvedCount.toNumber(),
256
+ resolvedCount: resolvedCount.toNumber(),
257
+ archivedCount: archivedCount.toNumber(),
258
+ topExceptions,
259
+ recentExceptions,
260
+ serviceSummaries,
261
+ };
262
+ }
263
+
264
+ @CaptureSpan()
265
+ private async aggregateUnresolvedByService(
266
+ projectId: ObjectID,
267
+ props: DatabaseCommonInteractionProps,
268
+ ): Promise<Array<DashboardServiceSummary>> {
269
+ /*
270
+ * Assert the caller has read permission on TelemetryException. We don't
271
+ * use the returned filtered query — we run a raw GROUP BY below for
272
+ * efficiency — but this throws if the user lacks read access.
273
+ */
274
+ await ModelPermission.checkReadQueryPermission(
275
+ Model,
276
+ {
277
+ projectId,
278
+ isResolved: false,
279
+ isArchived: false,
280
+ },
281
+ null,
282
+ props,
283
+ );
284
+
285
+ interface AggregateRow {
286
+ serviceId: string | null;
287
+ unresolvedCount: string;
288
+ totalOccurrences: string | null;
289
+ }
290
+
291
+ const rows: Array<AggregateRow> = (await this.getQueryBuilder(
292
+ "TelemetryException",
293
+ )
294
+ .select(`"TelemetryException"."serviceId"`, "serviceId")
295
+ .addSelect(`COUNT(*)`, "unresolvedCount")
296
+ .addSelect(
297
+ `COALESCE(SUM("TelemetryException"."occuranceCount"), 0)`,
298
+ "totalOccurrences",
299
+ )
300
+ .where(`"TelemetryException"."projectId" = :projectId`, {
301
+ projectId: projectId.toString(),
302
+ })
303
+ .andWhere(`"TelemetryException"."isResolved" = false`)
304
+ .andWhere(`"TelemetryException"."isArchived" = false`)
305
+ .andWhere(`"TelemetryException"."deletedAt" IS NULL`)
306
+ .andWhere(`"TelemetryException"."serviceId" IS NOT NULL`)
307
+ .groupBy(`"TelemetryException"."serviceId"`)
308
+ .orderBy(`"unresolvedCount"`, "DESC")
309
+ .getRawMany()) as Array<AggregateRow>;
310
+
311
+ if (rows.length === 0) {
312
+ return [];
313
+ }
314
+
315
+ const serviceIds: Array<string> = [];
316
+ for (const row of rows) {
317
+ if (row.serviceId) {
318
+ serviceIds.push(row.serviceId);
319
+ }
320
+ }
321
+
322
+ if (serviceIds.length === 0) {
323
+ return [];
324
+ }
325
+
326
+ const services: Array<TelemetryServiceModel> = await ServiceService.findBy({
327
+ query: {
328
+ projectId,
329
+ _id: QueryHelper.any(serviceIds),
330
+ },
331
+ select: {
332
+ _id: true,
333
+ name: true,
334
+ serviceColor: true,
335
+ },
336
+ limit: new PositiveNumber(serviceIds.length),
337
+ skip: new PositiveNumber(0),
338
+ props,
339
+ });
340
+
341
+ const serviceById: Map<string, TelemetryServiceModel> = new Map();
342
+ for (const service of services) {
343
+ if (service._id) {
344
+ serviceById.set(service._id, service);
345
+ }
346
+ }
347
+
348
+ const summaries: Array<DashboardServiceSummary> = [];
349
+ for (const row of rows) {
350
+ if (!row.serviceId) {
351
+ continue;
352
+ }
353
+ const service: TelemetryServiceModel | undefined = serviceById.get(
354
+ row.serviceId,
355
+ );
356
+ if (!service) {
357
+ continue;
358
+ }
359
+ summaries.push({
360
+ service,
361
+ unresolvedCount: parseInt(row.unresolvedCount, 10) || 0,
362
+ totalOccurrences: parseInt(row.totalOccurrences || "0", 10) || 0,
363
+ });
364
+ }
365
+
366
+ return summaries;
367
+ }
368
+
144
369
  private buildFixExceptionMetadata(params: {
145
370
  telemetryException: Model;
146
371
  telemetryExceptionId: ObjectID;
@@ -289,6 +289,41 @@ export default class QueryHelper {
289
289
  );
290
290
  }
291
291
 
292
+ /**
293
+ * Matches owner rows that have no rows in the join table — i.e. the
294
+ * many-to-many collection is empty. Apply to the owner's primary id column.
295
+ */
296
+ @CaptureSpan()
297
+ public static noEntitiesInManyToMany(data: {
298
+ joinTableName: string;
299
+ ownerColumnName: string;
300
+ }): FindWhereProperty<any> {
301
+ const joinTable: string = data.joinTableName.replace(/"/g, '""');
302
+ const ownerCol: string = data.ownerColumnName.replace(/"/g, '""');
303
+
304
+ return Raw((alias: string) => {
305
+ return `(${alias} NOT IN (SELECT "${joinTable}"."${ownerCol}" FROM "${joinTable}" WHERE "${joinTable}"."${ownerCol}" IS NOT NULL))`;
306
+ }, {});
307
+ }
308
+
309
+ /**
310
+ * Matches owner rows that have at least one row in the join table — i.e.
311
+ * the many-to-many collection has at least one entry. Apply to the
312
+ * owner's primary id column.
313
+ */
314
+ @CaptureSpan()
315
+ public static anyEntitiesInManyToMany(data: {
316
+ joinTableName: string;
317
+ ownerColumnName: string;
318
+ }): FindWhereProperty<any> {
319
+ const joinTable: string = data.joinTableName.replace(/"/g, '""');
320
+ const ownerCol: string = data.ownerColumnName.replace(/"/g, '""');
321
+
322
+ return Raw((alias: string) => {
323
+ return `(${alias} IN (SELECT "${joinTable}"."${ownerCol}" FROM "${joinTable}" WHERE "${joinTable}"."${ownerCol}" IS NOT NULL))`;
324
+ }, {});
325
+ }
326
+
292
327
  /**
293
328
  * Returns a filter that matches owner rows that are linked to *all* of the
294
329
  * provided related entity ids through a many-to-many join table. The
@@ -131,7 +131,45 @@ export default class QueryUtil {
131
131
  query[key] instanceof NotNull &&
132
132
  tableColumnMetadata
133
133
  ) {
134
- query[key] = QueryHelper.notNull();
134
+ if (tableColumnMetadata.type === TableColumnType.EntityArray) {
135
+ const manyToManyMeta: {
136
+ joinTableName: string;
137
+ ownerColumnName: string;
138
+ relationColumnName: string;
139
+ } | null = QueryUtil.getManyToManyRelationMetadata(modelType, key);
140
+
141
+ if (manyToManyMeta) {
142
+ const subqueryFilter: any = QueryHelper.anyEntitiesInManyToMany({
143
+ joinTableName: manyToManyMeta.joinTableName,
144
+ ownerColumnName: manyToManyMeta.ownerColumnName,
145
+ });
146
+
147
+ delete query[key];
148
+
149
+ const existingIdFilter: any = (query as any)._id;
150
+ if (existingIdFilter instanceof FindOperator) {
151
+ (query as any)._id = And(existingIdFilter, subqueryFilter);
152
+ } else if (
153
+ existingIdFilter &&
154
+ typeof existingIdFilter === Typeof.String
155
+ ) {
156
+ (query as any)._id = And(
157
+ QueryHelper.equalTo(existingIdFilter as string),
158
+ subqueryFilter,
159
+ );
160
+ } else {
161
+ (query as any)._id = subqueryFilter;
162
+ }
163
+ } else {
164
+ /*
165
+ * Metadata unavailable — drop the filter rather than emit
166
+ * invalid SQL against a relation that isn't a real column.
167
+ */
168
+ delete query[key];
169
+ }
170
+ } else {
171
+ query[key] = QueryHelper.notNull();
172
+ }
135
173
  } else if (
136
174
  query[key] &&
137
175
  query[key] instanceof EqualToOrNull &&
@@ -234,7 +272,41 @@ export default class QueryUtil {
234
272
  query[key] instanceof IsNull &&
235
273
  tableColumnMetadata
236
274
  ) {
237
- query[key] = QueryHelper.isNull() as any;
275
+ if (tableColumnMetadata.type === TableColumnType.EntityArray) {
276
+ const manyToManyMeta: {
277
+ joinTableName: string;
278
+ ownerColumnName: string;
279
+ relationColumnName: string;
280
+ } | null = QueryUtil.getManyToManyRelationMetadata(modelType, key);
281
+
282
+ if (manyToManyMeta) {
283
+ const subqueryFilter: any = QueryHelper.noEntitiesInManyToMany({
284
+ joinTableName: manyToManyMeta.joinTableName,
285
+ ownerColumnName: manyToManyMeta.ownerColumnName,
286
+ });
287
+
288
+ delete query[key];
289
+
290
+ const existingIdFilter: any = (query as any)._id;
291
+ if (existingIdFilter instanceof FindOperator) {
292
+ (query as any)._id = And(existingIdFilter, subqueryFilter);
293
+ } else if (
294
+ existingIdFilter &&
295
+ typeof existingIdFilter === Typeof.String
296
+ ) {
297
+ (query as any)._id = And(
298
+ QueryHelper.equalTo(existingIdFilter as string),
299
+ subqueryFilter,
300
+ );
301
+ } else {
302
+ (query as any)._id = subqueryFilter;
303
+ }
304
+ } else {
305
+ delete query[key];
306
+ }
307
+ } else {
308
+ query[key] = QueryHelper.isNull() as any;
309
+ }
238
310
  } else if (
239
311
  query[key] &&
240
312
  query[key] instanceof InBetween &&
@@ -8,6 +8,7 @@ export default interface DashboardAlertListComponent extends BaseComponent {
8
8
  arguments: {
9
9
  title?: string | undefined;
10
10
  maxRows?: number | undefined;
11
+ viewMode?: "list" | "honeycomb" | undefined;
11
12
  stateFilter?: string | undefined;
12
13
  severityIds?: Array<string> | undefined;
13
14
  stateIds?: Array<string> | undefined;
@@ -9,6 +9,7 @@ export default interface DashboardDockerContainerListComponent
9
9
  arguments: {
10
10
  title?: string | undefined;
11
11
  maxRows?: number | undefined;
12
+ viewMode?: "list" | "honeycomb" | undefined;
12
13
  dockerHostIds?: Array<string> | undefined;
13
14
  imageName?: string | undefined;
14
15
  };
@@ -9,6 +9,7 @@ export default interface DashboardDockerHostListComponent
9
9
  arguments: {
10
10
  title?: string | undefined;
11
11
  maxRows?: number | undefined;
12
+ viewMode?: "list" | "honeycomb" | undefined;
12
13
  statusFilter?: string | undefined;
13
14
  };
14
15
  }
@@ -9,6 +9,7 @@ export default interface DashboardDockerImageListComponent
9
9
  arguments: {
10
10
  title?: string | undefined;
11
11
  maxRows?: number | undefined;
12
+ viewMode?: "list" | "honeycomb" | undefined;
12
13
  dockerHostIds?: Array<string> | undefined;
13
14
  nameSearch?: string | undefined;
14
15
  };
@@ -9,6 +9,7 @@ export default interface DashboardDockerNetworkListComponent
9
9
  arguments: {
10
10
  title?: string | undefined;
11
11
  maxRows?: number | undefined;
12
+ viewMode?: "list" | "honeycomb" | undefined;
12
13
  dockerHostIds?: Array<string> | undefined;
13
14
  };
14
15
  }
@@ -9,6 +9,7 @@ export default interface DashboardDockerVolumeListComponent
9
9
  arguments: {
10
10
  title?: string | undefined;
11
11
  maxRows?: number | undefined;
12
+ viewMode?: "list" | "honeycomb" | undefined;
12
13
  dockerHostIds?: Array<string> | undefined;
13
14
  };
14
15
  }
@@ -8,6 +8,7 @@ export default interface DashboardHostListComponent extends BaseComponent {
8
8
  arguments: {
9
9
  title?: string | undefined;
10
10
  maxRows?: number | undefined;
11
+ viewMode?: "list" | "honeycomb" | undefined;
11
12
  statusFilter?: string | undefined;
12
13
  osTypeFilter?: string | undefined;
13
14
  };
@@ -8,6 +8,7 @@ export default interface DashboardIncidentListComponent extends BaseComponent {
8
8
  arguments: {
9
9
  title?: string | undefined;
10
10
  maxRows?: number | undefined;
11
+ viewMode?: "list" | "honeycomb" | undefined;
11
12
  stateFilter?: string | undefined;
12
13
  severityIds?: Array<string> | undefined;
13
14
  stateIds?: Array<string> | undefined;
@@ -9,6 +9,7 @@ export default interface DashboardKubernetesCronJobListComponent
9
9
  arguments: {
10
10
  title?: string | undefined;
11
11
  maxRows?: number | undefined;
12
+ viewMode?: "list" | "honeycomb" | undefined;
12
13
  kubernetesClusterIds?: Array<string> | undefined;
13
14
  namespaces?: string | undefined;
14
15
  };
@@ -9,6 +9,7 @@ export default interface DashboardKubernetesDaemonSetListComponent
9
9
  arguments: {
10
10
  title?: string | undefined;
11
11
  maxRows?: number | undefined;
12
+ viewMode?: "list" | "honeycomb" | undefined;
12
13
  kubernetesClusterIds?: Array<string> | undefined;
13
14
  namespaces?: string | undefined;
14
15
  };
@@ -9,6 +9,7 @@ export default interface DashboardKubernetesDeploymentListComponent
9
9
  arguments: {
10
10
  title?: string | undefined;
11
11
  maxRows?: number | undefined;
12
+ viewMode?: "list" | "honeycomb" | undefined;
12
13
  kubernetesClusterIds?: Array<string> | undefined;
13
14
  namespaces?: string | undefined;
14
15
  };
@@ -9,6 +9,7 @@ export default interface DashboardKubernetesJobListComponent
9
9
  arguments: {
10
10
  title?: string | undefined;
11
11
  maxRows?: number | undefined;
12
+ viewMode?: "list" | "honeycomb" | undefined;
12
13
  kubernetesClusterIds?: Array<string> | undefined;
13
14
  namespaces?: string | undefined;
14
15
  };
@@ -9,6 +9,7 @@ export default interface DashboardKubernetesNamespaceListComponent
9
9
  arguments: {
10
10
  title?: string | undefined;
11
11
  maxRows?: number | undefined;
12
+ viewMode?: "list" | "honeycomb" | undefined;
12
13
  kubernetesClusterIds?: Array<string> | undefined;
13
14
  };
14
15
  }
@@ -9,6 +9,7 @@ export default interface DashboardKubernetesNodeListComponent
9
9
  arguments: {
10
10
  title?: string | undefined;
11
11
  maxRows?: number | undefined;
12
+ viewMode?: "list" | "honeycomb" | undefined;
12
13
  kubernetesClusterIds?: Array<string> | undefined;
13
14
  readinessFilter?: string | undefined;
14
15
  };
@@ -9,6 +9,7 @@ export default interface DashboardKubernetesPodListComponent
9
9
  arguments: {
10
10
  title?: string | undefined;
11
11
  maxRows?: number | undefined;
12
+ viewMode?: "list" | "honeycomb" | undefined;
12
13
  kubernetesClusterIds?: Array<string> | undefined;
13
14
  namespaces?: string | undefined;
14
15
  podPhases?: Array<string> | undefined;
@@ -9,6 +9,7 @@ export default interface DashboardKubernetesStatefulSetListComponent
9
9
  arguments: {
10
10
  title?: string | undefined;
11
11
  maxRows?: number | undefined;
12
+ viewMode?: "list" | "honeycomb" | undefined;
12
13
  kubernetesClusterIds?: Array<string> | undefined;
13
14
  namespaces?: string | undefined;
14
15
  };
@@ -8,6 +8,7 @@ export default interface DashboardMonitorListComponent extends BaseComponent {
8
8
  arguments: {
9
9
  title?: string | undefined;
10
10
  maxRows?: number | undefined;
11
+ viewMode?: "list" | "honeycomb" | undefined;
11
12
  statusFilter?: string | undefined;
12
13
  monitorStatusIds?: Array<string> | undefined;
13
14
  monitorTypes?: Array<string> | undefined;
@@ -9,5 +9,6 @@ export default interface DashboardTraceListComponent extends BaseComponent {
9
9
  title?: string | undefined;
10
10
  statusFilter?: string | undefined;
11
11
  maxRows?: number | undefined;
12
+ viewMode?: "list" | "honeycomb" | undefined;
12
13
  };
13
14
  }
@@ -13,6 +13,14 @@ export class RangeStartAndEndDateTimeUtil {
13
13
  ): InBetween<Date> {
14
14
  const currentDate: Date = OneUptimeDate.getCurrentDate();
15
15
 
16
+ // 5 mins.
17
+ if (dashboardStartAndEndDate.range === TimeRange.PAST_FIVE_MINS) {
18
+ return new InBetween<Date>(
19
+ OneUptimeDate.addRemoveMinutes(currentDate, -5),
20
+ currentDate,
21
+ );
22
+ }
23
+
16
24
  // 30 mins.
17
25
  if (dashboardStartAndEndDate.range === TimeRange.PAST_THIRTY_MINS) {
18
26
  return new InBetween<Date>(
@@ -1,4 +1,5 @@
1
1
  enum Range {
2
+ PAST_FIVE_MINS = "Past 5 Mins",
2
3
  PAST_THIRTY_MINS = "Past 30 Mins",
3
4
  PAST_ONE_HOUR = "Past 1 Hour",
4
5
  PAST_TWO_HOURS = "Past 2 Hours",
@@ -22,6 +22,7 @@ export interface LogTimeRangePickerProps {
22
22
 
23
23
  // Preset options to show in the dropdown (ordered for log investigation use)
24
24
  const PRESET_OPTIONS: Array<{ range: TimeRange; label: string }> = [
25
+ { range: TimeRange.PAST_FIVE_MINS, label: "Past 5 Minutes" },
25
26
  { range: TimeRange.PAST_THIRTY_MINS, label: "Past 30 Minutes" },
26
27
  { range: TimeRange.PAST_ONE_HOUR, label: "Past 1 Hour" },
27
28
  { range: TimeRange.PAST_TWO_HOURS, label: "Past 2 Hours" },