@oneuptime/common 10.4.7 → 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.
- package/Models/DatabaseModels/Service.ts +1 -1
- package/Server/API/TelemetryExceptionAPI.ts +62 -0
- package/Server/Services/OpenTelemetryIngestService.ts +8 -8
- package/Server/Services/TelemetryExceptionService.ts +225 -0
- package/Server/Types/Database/QueryHelper.ts +35 -0
- package/Server/Types/Database/QueryUtil.ts +74 -2
- package/Types/Dashboard/DashboardComponents/DashboardAlertListComponent.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardDockerContainerListComponent.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardDockerHostListComponent.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardDockerImageListComponent.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardDockerNetworkListComponent.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardDockerVolumeListComponent.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardHostListComponent.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardIncidentListComponent.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardKubernetesCronJobListComponent.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardKubernetesDaemonSetListComponent.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardKubernetesDeploymentListComponent.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardKubernetesJobListComponent.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardKubernetesNamespaceListComponent.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardKubernetesNodeListComponent.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardKubernetesPodListComponent.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardKubernetesStatefulSetListComponent.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardMonitorListComponent.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardTraceListComponent.ts +1 -0
- package/Types/Telemetry/TelemetryRetentionConfig.ts +8 -8
- package/Types/Time/RangeStartAndEndDateTime.ts +8 -0
- package/Types/Time/TimeRange.ts +1 -0
- package/UI/Components/LogsViewer/components/LogTimeRangePicker.tsx +1 -0
- package/UI/Components/ModelTable/BaseModelTable.tsx +64 -17
- package/UI/Components/Telemetry/TelemetryRetentionConfigForm.tsx +11 -11
- package/UI/Components/Telemetry/TelemetryRetentionConfigSummary.tsx +196 -78
- package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +1 -0
- package/Utils/Dashboard/Components/DashboardAlertListComponent.ts +5 -0
- package/Utils/Dashboard/Components/DashboardDockerContainerListComponent.ts +7 -0
- package/Utils/Dashboard/Components/DashboardDockerHostListComponent.ts +5 -0
- package/Utils/Dashboard/Components/DashboardDockerImageListComponent.ts +5 -0
- package/Utils/Dashboard/Components/DashboardDockerNetworkListComponent.ts +5 -0
- package/Utils/Dashboard/Components/DashboardDockerVolumeListComponent.ts +5 -0
- package/Utils/Dashboard/Components/DashboardHostListComponent.ts +3 -0
- package/Utils/Dashboard/Components/DashboardIncidentListComponent.ts +5 -0
- package/Utils/Dashboard/Components/DashboardKubernetesResourceListShared.ts +3 -0
- package/Utils/Dashboard/Components/DashboardListSharedArgs.ts +31 -0
- package/Utils/Dashboard/Components/DashboardMonitorListComponent.ts +5 -0
- package/Utils/Dashboard/Components/DashboardTraceListComponent.ts +5 -0
- package/build/dist/Models/DatabaseModels/Service.js +1 -1
- package/build/dist/Models/DatabaseModels/Service.js.map +1 -1
- package/build/dist/Server/API/TelemetryExceptionAPI.js +37 -1
- package/build/dist/Server/API/TelemetryExceptionAPI.js.map +1 -1
- package/build/dist/Server/Services/OpenTelemetryIngestService.js +5 -5
- package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryExceptionService.js +175 -0
- package/build/dist/Server/Services/TelemetryExceptionService.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryHelper.js +35 -0
- package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryUtil.js +58 -2
- package/build/dist/Server/Types/Database/QueryUtil.js.map +1 -1
- package/build/dist/Types/Telemetry/TelemetryRetentionConfig.js +3 -3
- package/build/dist/Types/Telemetry/TelemetryRetentionConfig.js.map +1 -1
- package/build/dist/Types/Time/RangeStartAndEndDateTime.js +4 -0
- package/build/dist/Types/Time/RangeStartAndEndDateTime.js.map +1 -1
- package/build/dist/Types/Time/TimeRange.js +1 -0
- package/build/dist/Types/Time/TimeRange.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js +1 -0
- package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +48 -13
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigForm.js +9 -9
- package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigForm.js.map +1 -1
- package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigSummary.js +107 -41
- package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigSummary.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -1
- package/build/dist/Utils/Dashboard/Components/DashboardAlertListComponent.js +2 -0
- package/build/dist/Utils/Dashboard/Components/DashboardAlertListComponent.js.map +1 -1
- package/build/dist/Utils/Dashboard/Components/DashboardDockerContainerListComponent.js +2 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDockerContainerListComponent.js.map +1 -1
- package/build/dist/Utils/Dashboard/Components/DashboardDockerHostListComponent.js +2 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDockerHostListComponent.js.map +1 -1
- package/build/dist/Utils/Dashboard/Components/DashboardDockerImageListComponent.js +2 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDockerImageListComponent.js.map +1 -1
- package/build/dist/Utils/Dashboard/Components/DashboardDockerNetworkListComponent.js +2 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDockerNetworkListComponent.js.map +1 -1
- package/build/dist/Utils/Dashboard/Components/DashboardDockerVolumeListComponent.js +2 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDockerVolumeListComponent.js.map +1 -1
- package/build/dist/Utils/Dashboard/Components/DashboardHostListComponent.js +2 -0
- package/build/dist/Utils/Dashboard/Components/DashboardHostListComponent.js.map +1 -1
- package/build/dist/Utils/Dashboard/Components/DashboardIncidentListComponent.js +2 -0
- package/build/dist/Utils/Dashboard/Components/DashboardIncidentListComponent.js.map +1 -1
- package/build/dist/Utils/Dashboard/Components/DashboardKubernetesResourceListShared.js +2 -0
- package/build/dist/Utils/Dashboard/Components/DashboardKubernetesResourceListShared.js.map +1 -1
- package/build/dist/Utils/Dashboard/Components/DashboardListSharedArgs.js +21 -0
- package/build/dist/Utils/Dashboard/Components/DashboardListSharedArgs.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js +2 -0
- package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js.map +1 -1
- package/build/dist/Utils/Dashboard/Components/DashboardTraceListComponent.js +2 -0
- package/build/dist/Utils/Dashboard/Components/DashboardTraceListComponent.js.map +1 -1
- package/package.json +1 -1
|
@@ -829,7 +829,7 @@ export default class Service extends BaseModel {
|
|
|
829
829
|
required: false,
|
|
830
830
|
title: "Telemetry Data Retention Overrides",
|
|
831
831
|
description:
|
|
832
|
-
"Per-pillar retention overrides for this service (logs by severity, traces by status, metrics, profiles). Unset fields
|
|
832
|
+
"Per-pillar retention overrides for this service (logs by severity, traces by status, metrics, profiles). Unset fields fall back to the service default, then the project's retention settings.",
|
|
833
833
|
})
|
|
834
834
|
@Column({
|
|
835
835
|
type: ColumnType.JSON,
|
|
@@ -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
|
}
|
|
@@ -27,14 +27,14 @@ export interface TelemetryServiceMetadata {
|
|
|
27
27
|
serviceId: ObjectID;
|
|
28
28
|
dataRententionInDays: number;
|
|
29
29
|
serviceRetentionConfig: TelemetryRetentionConfig | null;
|
|
30
|
-
|
|
30
|
+
serviceRetentionInDays: number | null;
|
|
31
31
|
projectRetentionConfig: TelemetryRetentionConfig | null;
|
|
32
|
-
|
|
32
|
+
projectRetentionInDays: number;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
interface ProjectRetentionContext {
|
|
36
36
|
projectRetentionConfig: TelemetryRetentionConfig | null;
|
|
37
|
-
|
|
37
|
+
projectRetentionInDays: number;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
export default class OTelIngestService {
|
|
@@ -55,7 +55,7 @@ export default class OTelIngestService {
|
|
|
55
55
|
|
|
56
56
|
return {
|
|
57
57
|
projectRetentionConfig: project?.telemetryRetentionConfig ?? null,
|
|
58
|
-
|
|
58
|
+
projectRetentionInDays:
|
|
59
59
|
project?.defaultTelemetryRetentionInDays || DEFAULT_RETENTION_IN_DAYS,
|
|
60
60
|
};
|
|
61
61
|
}
|
|
@@ -161,17 +161,17 @@ export default class OTelIngestService {
|
|
|
161
161
|
const buildMetadata: (svc: Service) => TelemetryServiceMetadata = (
|
|
162
162
|
svc: Service,
|
|
163
163
|
): TelemetryServiceMetadata => {
|
|
164
|
-
const
|
|
164
|
+
const serviceLevelRetention: number | null =
|
|
165
165
|
svc.retainTelemetryDataForDays ?? null;
|
|
166
166
|
return {
|
|
167
167
|
serviceName: data.serviceName,
|
|
168
168
|
serviceId: svc.id!,
|
|
169
169
|
dataRententionInDays:
|
|
170
|
-
|
|
170
|
+
serviceLevelRetention || projectContext.projectRetentionInDays,
|
|
171
171
|
serviceRetentionConfig: svc.telemetryRetentionConfig ?? null,
|
|
172
|
-
|
|
172
|
+
serviceRetentionInDays: serviceLevelRetention,
|
|
173
173
|
projectRetentionConfig: projectContext.projectRetentionConfig,
|
|
174
|
-
|
|
174
|
+
projectRetentionInDays: projectContext.projectRetentionInDays,
|
|
175
175
|
};
|
|
176
176
|
};
|
|
177
177
|
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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 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
|
};
|
|
@@ -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 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
|
};
|