@oneuptime/common 10.4.13 → 10.4.15
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/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel.ts +49 -0
- package/Models/AnalyticsModels/AuditLog.ts +8 -0
- package/Models/AnalyticsModels/ExceptionInstance.ts +1 -0
- package/Models/AnalyticsModels/Log.ts +1 -0
- package/Models/AnalyticsModels/Metric.ts +10 -0
- package/Models/AnalyticsModels/MonitorLog.ts +1 -0
- package/Models/AnalyticsModels/Profile.ts +1 -0
- package/Models/AnalyticsModels/ProfileSample.ts +1 -0
- package/Models/AnalyticsModels/Span.ts +1 -0
- package/Models/DatabaseModels/Alert.ts +2 -0
- package/Models/DatabaseModels/AlertCustomField.ts +37 -0
- package/Models/DatabaseModels/AlertFeed.ts +1 -0
- package/Models/DatabaseModels/CallLog.ts +2 -0
- package/Models/DatabaseModels/DockerHost.ts +34 -0
- package/Models/DatabaseModels/EmailLog.ts +2 -0
- package/Models/DatabaseModels/Host.ts +34 -0
- package/Models/DatabaseModels/Incident.ts +1 -0
- package/Models/DatabaseModels/IncidentCustomField.ts +37 -0
- package/Models/DatabaseModels/IncidentFeed.ts +1 -0
- package/Models/DatabaseModels/IncidentMember.ts +9 -0
- package/Models/DatabaseModels/KubernetesCluster.ts +34 -0
- package/Models/DatabaseModels/MonitorCustomField.ts +37 -0
- package/Models/DatabaseModels/MonitorFeed.ts +1 -0
- package/Models/DatabaseModels/MonitorProbe.ts +1 -0
- package/Models/DatabaseModels/OnCallDutyPolicyCustomField.ts +37 -0
- package/Models/DatabaseModels/OnCallDutyPolicyTimeLog.ts +3 -0
- package/Models/DatabaseModels/ScheduledMaintenanceCustomField.ts +37 -0
- package/Models/DatabaseModels/SmsLog.ts +2 -0
- package/Models/DatabaseModels/StatusPageCustomField.ts +37 -0
- package/Models/DatabaseModels/StatusPageSubscriber.ts +2 -0
- package/Models/DatabaseModels/TableView.ts +40 -0
- package/Models/DatabaseModels/TeamMemberCustomField.ts +37 -0
- package/Models/DatabaseModels/TelemetryException.ts +2 -0
- package/Models/DatabaseModels/UserOnCallLog.ts +1 -0
- package/Models/DatabaseModels/WorkflowLog.ts +1 -0
- package/Server/API/BaseAnalyticsAPI.ts +128 -20
- package/Server/API/MetricAPI.ts +5 -138
- package/Server/API/ProjectAPI.ts +52 -15
- package/Server/API/StatusAPI.ts +103 -7
- package/Server/EnvironmentConfig.ts +69 -0
- package/Server/Infrastructure/Postgres/DataSourceOptions.ts +26 -1
- package/Server/Infrastructure/Postgres/SchemaMigrations/1779392865146-AddAgentVersionToKubernetesDockerHost.ts +29 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1779392970424-AddPerformanceIndexes.ts +160 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1779536271671-AddFacetsToTableView.ts +13 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1779540427366-AddIsMemberNotifiedIndex.ts +34 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1779619108628-AddDropdownOptionsToCustomFields.ts +67 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
- package/Server/Infrastructure/PostgresDatabase.ts +2 -5
- package/Server/Middleware/ProjectAuthorization.ts +31 -53
- package/Server/Middleware/UserAuthorization.ts +106 -64
- package/Server/Services/AccessTokenService.ts +1 -1
- package/Server/Services/AnalyticsDatabaseService.ts +24 -4
- package/Server/Services/ApiKeyService.ts +100 -1
- package/Server/Services/DockerHostService.ts +5 -0
- package/Server/Services/HostService.ts +6 -0
- package/Server/Services/KubernetesClusterService.ts +33 -10
- package/Server/Services/MetricService.ts +113 -0
- package/Server/Services/MonitorService.ts +10 -3
- package/Server/Services/ProjectService.ts +93 -2
- package/Server/Services/TeamMemberService.ts +36 -0
- package/Server/Services/UserService.ts +38 -0
- package/Server/Utils/Response.ts +4 -1
- package/Server/Utils/UserPermission/UserPermission.ts +17 -1
- package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +2 -2
- package/Types/API/HTTPResponse.ts +16 -0
- package/Types/BaseDatabase/ListResult.ts +6 -0
- package/Types/CustomField/CustomFieldType.ts +2 -0
- package/Types/Date.ts +9 -1
- package/Types/ListData.ts +14 -0
- package/Types/Monitor/DnsMonitor/DnsMonitorResponse.ts +3 -0
- package/Types/Monitor/DnssecMonitor/DnssecMonitorResponse.ts +5 -0
- package/Types/Monitor/DomainMonitor/DomainMonitorResponse.ts +4 -0
- package/Types/Monitor/ExternalStatusPageMonitor/ExternalStatusPageMonitorResponse.ts +4 -0
- package/Types/Monitor/SnmpMonitor/SnmpMonitorResponse.ts +3 -0
- package/Types/Probe/ProbeAttempt.ts +9 -0
- package/Types/Probe/ProbeMonitorResponse.ts +3 -0
- package/UI/Components/BulkUpdate/BulkOwnerActions.tsx +504 -0
- package/UI/Components/BulkUpdate/BulkUpdateForm.tsx +64 -54
- package/UI/Components/CustomFields/CustomFieldsDetail.tsx +38 -0
- package/UI/Components/CustomFields/DropdownOptionsInput.tsx +150 -0
- package/UI/Components/Detail/Detail.tsx +78 -11
- package/UI/Components/List/List.tsx +6 -0
- package/UI/Components/ModelTable/BaseModelTable.tsx +74 -2
- package/UI/Components/ModelTable/TableView.tsx +70 -30
- package/UI/Components/Pagination/Pagination.tsx +75 -33
- package/UI/Components/Table/Table.tsx +6 -0
- package/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.ts +1 -0
- package/build/dist/Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel.js +33 -0
- package/build/dist/Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/AuditLog.js +8 -0
- package/build/dist/Models/AnalyticsModels/AuditLog.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/ExceptionInstance.js +1 -0
- package/build/dist/Models/AnalyticsModels/ExceptionInstance.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Log.js +1 -0
- package/build/dist/Models/AnalyticsModels/Log.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Metric.js +10 -0
- package/build/dist/Models/AnalyticsModels/Metric.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/MonitorLog.js +1 -0
- package/build/dist/Models/AnalyticsModels/MonitorLog.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Profile.js +1 -0
- package/build/dist/Models/AnalyticsModels/Profile.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/ProfileSample.js +1 -0
- package/build/dist/Models/AnalyticsModels/ProfileSample.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Span.js +1 -0
- package/build/dist/Models/AnalyticsModels/Span.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Alert.js +3 -1
- package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertCustomField.js +38 -0
- package/build/dist/Models/DatabaseModels/AlertCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertFeed.js +2 -1
- package/build/dist/Models/DatabaseModels/AlertFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/CallLog.js +4 -1
- package/build/dist/Models/DatabaseModels/CallLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DockerHost.js +35 -0
- package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
- package/build/dist/Models/DatabaseModels/EmailLog.js +4 -1
- package/build/dist/Models/DatabaseModels/EmailLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Host.js +35 -0
- package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Incident.js +2 -1
- package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentCustomField.js +38 -0
- package/build/dist/Models/DatabaseModels/IncidentCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentFeed.js +2 -1
- package/build/dist/Models/DatabaseModels/IncidentFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentMember.js +11 -1
- package/build/dist/Models/DatabaseModels/IncidentMember.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js +35 -0
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorCustomField.js +38 -0
- package/build/dist/Models/DatabaseModels/MonitorCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorFeed.js +2 -1
- package/build/dist/Models/DatabaseModels/MonitorFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorProbe.js +2 -0
- package/build/dist/Models/DatabaseModels/MonitorProbe.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyCustomField.js +38 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyTimeLog.js +3 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyTimeLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceCustomField.js +38 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/SmsLog.js +4 -1
- package/build/dist/Models/DatabaseModels/SmsLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageCustomField.js +38 -0
- package/build/dist/Models/DatabaseModels/StatusPageCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js +4 -1
- package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TableView.js +40 -0
- package/build/dist/Models/DatabaseModels/TableView.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TeamMemberCustomField.js +38 -0
- package/build/dist/Models/DatabaseModels/TeamMemberCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TelemetryException.js +3 -1
- package/build/dist/Models/DatabaseModels/TelemetryException.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserOnCallLog.js +1 -0
- package/build/dist/Models/DatabaseModels/UserOnCallLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkflowLog.js +2 -1
- package/build/dist/Models/DatabaseModels/WorkflowLog.js.map +1 -1
- package/build/dist/Server/API/BaseAnalyticsAPI.js +105 -18
- package/build/dist/Server/API/BaseAnalyticsAPI.js.map +1 -1
- package/build/dist/Server/API/MetricAPI.js +5 -113
- package/build/dist/Server/API/MetricAPI.js.map +1 -1
- package/build/dist/Server/API/ProjectAPI.js +42 -14
- package/build/dist/Server/API/ProjectAPI.js.map +1 -1
- package/build/dist/Server/API/StatusAPI.js +75 -8
- package/build/dist/Server/API/StatusAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +41 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js +20 -2
- package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779392865146-AddAgentVersionToKubernetesDockerHost.js +16 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779392865146-AddAgentVersionToKubernetesDockerHost.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779392970424-AddPerformanceIndexes.js +63 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779392970424-AddPerformanceIndexes.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779536271671-AddFacetsToTableView.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779536271671-AddFacetsToTableView.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779540427366-AddIsMemberNotifiedIndex.js +27 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779540427366-AddIsMemberNotifiedIndex.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779619108628-AddDropdownOptionsToCustomFields.js +28 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779619108628-AddDropdownOptionsToCustomFields.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/PostgresDatabase.js +2 -2
- package/build/dist/Server/Infrastructure/PostgresDatabase.js.map +1 -1
- package/build/dist/Server/Middleware/ProjectAuthorization.js +21 -39
- package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
- package/build/dist/Server/Middleware/UserAuthorization.js +83 -50
- package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
- package/build/dist/Server/Services/AccessTokenService.js +1 -1
- package/build/dist/Server/Services/AccessTokenService.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +22 -3
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/ApiKeyService.js +86 -0
- package/build/dist/Server/Services/ApiKeyService.js.map +1 -1
- package/build/dist/Server/Services/DockerHostService.js +5 -1
- package/build/dist/Server/Services/DockerHostService.js.map +1 -1
- package/build/dist/Server/Services/HostService.js +5 -1
- package/build/dist/Server/Services/HostService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesClusterService.js +21 -11
- package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
- package/build/dist/Server/Services/MetricService.js +89 -0
- package/build/dist/Server/Services/MetricService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +8 -3
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +84 -2
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/TeamMemberService.js +24 -0
- package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +36 -0
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Utils/Response.js +6 -5
- package/build/dist/Server/Utils/Response.js.map +1 -1
- package/build/dist/Server/Utils/UserPermission/UserPermission.js +13 -1
- package/build/dist/Server/Utils/UserPermission/UserPermission.js.map +1 -1
- package/build/dist/Tests/Server/Services/AnalyticsDatabaseService.test.js +2 -2
- package/build/dist/Tests/Server/Services/AnalyticsDatabaseService.test.js.map +1 -1
- package/build/dist/Types/API/HTTPResponse.js +15 -0
- package/build/dist/Types/API/HTTPResponse.js.map +1 -1
- package/build/dist/Types/CustomField/CustomFieldType.js +2 -0
- package/build/dist/Types/CustomField/CustomFieldType.js.map +1 -1
- package/build/dist/Types/Date.js +10 -1
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/ListData.js +4 -0
- package/build/dist/Types/ListData.js.map +1 -1
- package/build/dist/Types/Probe/ProbeAttempt.js +2 -0
- package/build/dist/Types/Probe/ProbeAttempt.js.map +1 -0
- package/build/dist/UI/Components/BulkUpdate/BulkOwnerActions.js +376 -0
- package/build/dist/UI/Components/BulkUpdate/BulkOwnerActions.js.map +1 -0
- package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js +32 -25
- package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +32 -0
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/DropdownOptionsInput.js +84 -0
- package/build/dist/UI/Components/CustomFields/DropdownOptionsInput.js.map +1 -0
- package/build/dist/UI/Components/Detail/Detail.js +34 -3
- package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
- package/build/dist/UI/Components/List/List.js +1 -1
- package/build/dist/UI/Components/List/List.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +45 -5
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/TableView.js +40 -19
- package/build/dist/UI/Components/ModelTable/TableView.js.map +1 -1
- package/build/dist/UI/Components/Pagination/Pagination.js +62 -36
- package/build/dist/UI/Components/Pagination/Pagination.js.map +1 -1
- package/build/dist/UI/Components/Table/Table.js +1 -1
- package/build/dist/UI/Components/Table/Table.js.map +1 -1
- package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.js +1 -0
- package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.js.map +1 -1
- package/package.json +1 -1
|
@@ -244,6 +244,43 @@ export default class StatusPageCustomField extends BaseModel {
|
|
|
244
244
|
})
|
|
245
245
|
public customFieldType?: CustomFieldType = undefined;
|
|
246
246
|
|
|
247
|
+
@ColumnAccessControl({
|
|
248
|
+
create: [
|
|
249
|
+
Permission.ProjectOwner,
|
|
250
|
+
Permission.ProjectAdmin,
|
|
251
|
+
Permission.CreateStatusPageCustomField,
|
|
252
|
+
],
|
|
253
|
+
read: [
|
|
254
|
+
Permission.ProjectOwner,
|
|
255
|
+
Permission.ProjectAdmin,
|
|
256
|
+
Permission.ProjectMember,
|
|
257
|
+
Permission.Viewer,
|
|
258
|
+
Permission.StatusPageAdmin,
|
|
259
|
+
Permission.StatusPageMember,
|
|
260
|
+
Permission.StatusPageViewer,
|
|
261
|
+
Permission.ReadStatusPageCustomField,
|
|
262
|
+
],
|
|
263
|
+
update: [
|
|
264
|
+
Permission.ProjectOwner,
|
|
265
|
+
Permission.ProjectAdmin,
|
|
266
|
+
Permission.EditStatusPageCustomField,
|
|
267
|
+
],
|
|
268
|
+
})
|
|
269
|
+
@TableColumn({
|
|
270
|
+
required: false,
|
|
271
|
+
type: TableColumnType.LongText,
|
|
272
|
+
title: "Dropdown Options",
|
|
273
|
+
description:
|
|
274
|
+
"Options for the dropdown field, one per line. Only used when Custom Field Type is Dropdown.",
|
|
275
|
+
example: "Option 1\nOption 2\nOption 3",
|
|
276
|
+
})
|
|
277
|
+
@Column({
|
|
278
|
+
nullable: true,
|
|
279
|
+
type: ColumnType.LongText,
|
|
280
|
+
length: ColumnLength.LongText,
|
|
281
|
+
})
|
|
282
|
+
public dropdownOptions?: string = undefined;
|
|
283
|
+
|
|
247
284
|
@ColumnAccessControl({
|
|
248
285
|
create: [
|
|
249
286
|
Permission.ProjectOwner,
|
|
@@ -94,6 +94,8 @@ import StatusPageEventType from "../../Types/StatusPage/StatusPageEventType";
|
|
|
94
94
|
@Entity({
|
|
95
95
|
name: "StatusPageSubscriber",
|
|
96
96
|
})
|
|
97
|
+
@Index(["statusPageId", "subscriberEmail"]) // Dedupe lookup on email subscribe
|
|
98
|
+
@Index(["statusPageId", "subscriberPhone"]) // Dedupe lookup on phone subscribe
|
|
97
99
|
export default class StatusPageSubscriber extends BaseModel {
|
|
98
100
|
@ColumnAccessControl({
|
|
99
101
|
create: [
|
|
@@ -22,6 +22,7 @@ import Query from "../../Types/BaseDatabase/Query";
|
|
|
22
22
|
import BaseModel from "./DatabaseBaseModel/DatabaseBaseModel";
|
|
23
23
|
import Sort from "../../Types/BaseDatabase/Sort";
|
|
24
24
|
import AnalyticsBaseModel from "../AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel";
|
|
25
|
+
import { JSONObject } from "../../Types/JSON";
|
|
25
26
|
|
|
26
27
|
@TableBillingAccessControl({
|
|
27
28
|
create: PlanType.Growth,
|
|
@@ -515,4 +516,43 @@ export default class TableView extends BaseModel {
|
|
|
515
516
|
default: 10,
|
|
516
517
|
})
|
|
517
518
|
public itemsOnPage?: number = undefined;
|
|
519
|
+
|
|
520
|
+
@ColumnAccessControl({
|
|
521
|
+
create: [
|
|
522
|
+
Permission.ProjectOwner,
|
|
523
|
+
Permission.ProjectAdmin,
|
|
524
|
+
Permission.CreateTableView,
|
|
525
|
+
],
|
|
526
|
+
read: [
|
|
527
|
+
Permission.ProjectOwner,
|
|
528
|
+
Permission.ProjectAdmin,
|
|
529
|
+
Permission.ProjectMember,
|
|
530
|
+
Permission.Viewer,
|
|
531
|
+
Permission.SettingsAdmin,
|
|
532
|
+
Permission.SettingsMember,
|
|
533
|
+
Permission.SettingsViewer,
|
|
534
|
+
Permission.ReadTableView,
|
|
535
|
+
],
|
|
536
|
+
update: [
|
|
537
|
+
Permission.ProjectOwner,
|
|
538
|
+
Permission.ProjectAdmin,
|
|
539
|
+
Permission.EditTableView,
|
|
540
|
+
],
|
|
541
|
+
})
|
|
542
|
+
@TableColumn({
|
|
543
|
+
title: "Facets",
|
|
544
|
+
required: false,
|
|
545
|
+
unique: false,
|
|
546
|
+
type: TableColumnType.JSON,
|
|
547
|
+
canReadOnRelationQuery: true,
|
|
548
|
+
description:
|
|
549
|
+
"Facet selections (owner, labels, status, etc.) for this table view",
|
|
550
|
+
example: '{"selectedOwnerKeys": ["user:abc"], "facetSelections": {}}',
|
|
551
|
+
})
|
|
552
|
+
@Column({
|
|
553
|
+
type: ColumnType.JSON,
|
|
554
|
+
unique: false,
|
|
555
|
+
nullable: true,
|
|
556
|
+
})
|
|
557
|
+
public facets?: JSONObject = undefined;
|
|
518
558
|
}
|
|
@@ -248,6 +248,43 @@ export default class TeamMemberCustomField extends BaseModel {
|
|
|
248
248
|
})
|
|
249
249
|
public customFieldType?: CustomFieldType = undefined;
|
|
250
250
|
|
|
251
|
+
@ColumnAccessControl({
|
|
252
|
+
create: [
|
|
253
|
+
Permission.ProjectOwner,
|
|
254
|
+
Permission.ProjectAdmin,
|
|
255
|
+
Permission.CreateTeamMemberCustomField,
|
|
256
|
+
],
|
|
257
|
+
read: [
|
|
258
|
+
Permission.ProjectOwner,
|
|
259
|
+
Permission.ProjectAdmin,
|
|
260
|
+
Permission.ProjectMember,
|
|
261
|
+
Permission.Viewer,
|
|
262
|
+
Permission.SettingsAdmin,
|
|
263
|
+
Permission.SettingsMember,
|
|
264
|
+
Permission.SettingsViewer,
|
|
265
|
+
Permission.ReadTeamMemberCustomField,
|
|
266
|
+
],
|
|
267
|
+
update: [
|
|
268
|
+
Permission.ProjectOwner,
|
|
269
|
+
Permission.ProjectAdmin,
|
|
270
|
+
Permission.EditTeamMemberCustomField,
|
|
271
|
+
],
|
|
272
|
+
})
|
|
273
|
+
@TableColumn({
|
|
274
|
+
required: false,
|
|
275
|
+
type: TableColumnType.LongText,
|
|
276
|
+
title: "Dropdown Options",
|
|
277
|
+
description:
|
|
278
|
+
"Options for the dropdown field, one per line. Only used when Custom Field Type is Dropdown.",
|
|
279
|
+
example: "Option 1\nOption 2\nOption 3",
|
|
280
|
+
})
|
|
281
|
+
@Column({
|
|
282
|
+
nullable: true,
|
|
283
|
+
type: ColumnType.LongText,
|
|
284
|
+
length: ColumnLength.LongText,
|
|
285
|
+
})
|
|
286
|
+
public dropdownOptions?: string = undefined;
|
|
287
|
+
|
|
251
288
|
@ColumnAccessControl({
|
|
252
289
|
create: [
|
|
253
290
|
Permission.ProjectOwner,
|
|
@@ -62,6 +62,7 @@ import Service from "./Service";
|
|
|
62
62
|
@Entity({
|
|
63
63
|
name: "TelemetryException",
|
|
64
64
|
})
|
|
65
|
+
@Index(["projectId", "isResolved", "isArchived"]) // Exceptions dashboard counts/filters
|
|
65
66
|
export default class TelemetryException extends DatabaseBaseModel {
|
|
66
67
|
@ColumnAccessControl({
|
|
67
68
|
create: [
|
|
@@ -1096,6 +1097,7 @@ export default class TelemetryException extends DatabaseBaseModel {
|
|
|
1096
1097
|
Permission.EditTelemetryException,
|
|
1097
1098
|
],
|
|
1098
1099
|
})
|
|
1100
|
+
@Index()
|
|
1099
1101
|
@TableColumn({
|
|
1100
1102
|
title: "Occurances",
|
|
1101
1103
|
description: "Number of times this exception has occurred",
|
|
@@ -60,6 +60,7 @@ import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
|
|
|
60
60
|
icon: IconProp.Logs,
|
|
61
61
|
tableDescription: "Logs of the workflows executed",
|
|
62
62
|
})
|
|
63
|
+
@Index(["workflowStatus", "createdAt"]) // Worker sweep for scheduled/timed-out runs
|
|
63
64
|
export default class WorkflowLog extends BaseModel {
|
|
64
65
|
@ColumnAccessControl({
|
|
65
66
|
create: [],
|
|
@@ -29,6 +29,22 @@ import { UserPermission } from "../../Types/Permission";
|
|
|
29
29
|
import PositiveNumber from "../../Types/PositiveNumber";
|
|
30
30
|
import AggregatedResult from "../../Types/BaseDatabase/AggregatedResult";
|
|
31
31
|
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
32
|
+
import GlobalCache from "../Infrastructure/GlobalCache";
|
|
33
|
+
import logger from "../Utils/Logger";
|
|
34
|
+
|
|
35
|
+
/*
|
|
36
|
+
* Aggregate cache TTL. Dashboards typically auto-refresh every 30s+,
|
|
37
|
+
* so an 8s window collapses bursts of identical requests (e.g. 12
|
|
38
|
+
* widgets loading on the same page) onto a single ClickHouse query
|
|
39
|
+
* while still looking real-time to humans.
|
|
40
|
+
*
|
|
41
|
+
* Project-scoped only: analytics data is project-wide and the
|
|
42
|
+
* service layer enforces project-scoped read permissions, so
|
|
43
|
+
* caching across users within the same project is safe. Endpoints
|
|
44
|
+
* with row-level access scoping should override `getAggregate` to
|
|
45
|
+
* skip the cache (or shape the key to include the access scope).
|
|
46
|
+
*/
|
|
47
|
+
const ANALYTICS_AGGREGATE_CACHE_TTL_SECONDS: number = 8;
|
|
32
48
|
|
|
33
49
|
export default class BaseAnalyticsAPI<
|
|
34
50
|
TAnalyticsDataModel extends AnalyticsDataModel,
|
|
@@ -268,29 +284,46 @@ export default class BaseAnalyticsAPI<
|
|
|
268
284
|
const databaseProps: DatabaseCommonInteractionProps =
|
|
269
285
|
await CommonAPI.getDatabaseCommonInteractionProps(req);
|
|
270
286
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
+
/*
|
|
288
|
+
* Skip the parallel countBy on analytics tables. countBy on Log /
|
|
289
|
+
* Span / Metric over wide time ranges scans every matching block
|
|
290
|
+
* (no LIMIT) and routinely dominates list-endpoint latency under
|
|
291
|
+
* heavy ingest. Instead we over-fetch by one row and derive
|
|
292
|
+
* `hasMore` from whether the extra row showed up. `count` is
|
|
293
|
+
* emitted as a lower bound (`skip + data.length + hasMore`) so
|
|
294
|
+
* older clients that read `count` keep rendering something
|
|
295
|
+
* sensible while newer clients use `hasMore` for prev/next.
|
|
296
|
+
*/
|
|
297
|
+
const overfetchLimit: PositiveNumber = new PositiveNumber(
|
|
298
|
+
limit.toNumber() + 1,
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
const list: Array<AnalyticsDataModel> = await this.service.findBy({
|
|
302
|
+
query,
|
|
303
|
+
select,
|
|
304
|
+
skip: skip,
|
|
305
|
+
limit: overfetchLimit,
|
|
306
|
+
sort: sort,
|
|
307
|
+
groupBy: groupBy,
|
|
308
|
+
props: databaseProps,
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
const hasMore: boolean = list.length > limit.toNumber();
|
|
312
|
+
if (hasMore) {
|
|
313
|
+
list.length = limit.toNumber();
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const lowerBoundCount: PositiveNumber = new PositiveNumber(
|
|
317
|
+
skip.toNumber() + list.length + (hasMore ? 1 : 0),
|
|
318
|
+
);
|
|
287
319
|
|
|
288
320
|
return Response.sendEntityArrayResponse(
|
|
289
321
|
req,
|
|
290
322
|
res,
|
|
291
323
|
list,
|
|
292
|
-
|
|
324
|
+
lowerBoundCount,
|
|
293
325
|
this.entityType,
|
|
326
|
+
{ hasMore },
|
|
294
327
|
);
|
|
295
328
|
}
|
|
296
329
|
|
|
@@ -327,14 +360,89 @@ export default class BaseAnalyticsAPI<
|
|
|
327
360
|
const databaseProps: DatabaseCommonInteractionProps =
|
|
328
361
|
await CommonAPI.getDatabaseCommonInteractionProps(req);
|
|
329
362
|
|
|
363
|
+
/*
|
|
364
|
+
* Short-lived project-scoped cache. A dashboard refresh fires
|
|
365
|
+
* one /aggregate call per widget — typically 10+ identical or
|
|
366
|
+
* near-identical aggregations against the same time window
|
|
367
|
+
* inside a few hundred milliseconds. Cache the result for 8s
|
|
368
|
+
* so the underlying ClickHouse aggregation runs once per
|
|
369
|
+
* burst. On cache outage (Redis down, parse error, …) we fall
|
|
370
|
+
* through to a live query so behavior degrades to today's.
|
|
371
|
+
*/
|
|
372
|
+
const projectId: string | undefined = databaseProps.tenantId?.toString();
|
|
373
|
+
const cacheNamespace: string = `${this.getEntityName()}-aggregate`;
|
|
374
|
+
const cacheKey: string | null = projectId
|
|
375
|
+
? `${projectId}:${this.buildAggregateCacheKey(aggregateBy)}`
|
|
376
|
+
: null;
|
|
377
|
+
|
|
378
|
+
if (cacheKey) {
|
|
379
|
+
try {
|
|
380
|
+
const cached: JSONObject | null = await GlobalCache.getJSONObject(
|
|
381
|
+
cacheNamespace,
|
|
382
|
+
cacheKey,
|
|
383
|
+
);
|
|
384
|
+
if (cached) {
|
|
385
|
+
return Response.sendJsonObjectResponse(req, res, cached);
|
|
386
|
+
}
|
|
387
|
+
} catch (err) {
|
|
388
|
+
logger.debug(`${cacheNamespace} cache read failed`);
|
|
389
|
+
logger.debug(err);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
330
393
|
const aggregateResult: AggregatedResult = await this.service.aggregateBy({
|
|
331
394
|
...aggregateBy,
|
|
332
395
|
props: databaseProps,
|
|
333
396
|
});
|
|
334
397
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
398
|
+
const responseBody: JSONObject = { ...(aggregateResult as any) };
|
|
399
|
+
|
|
400
|
+
if (cacheKey) {
|
|
401
|
+
try {
|
|
402
|
+
await GlobalCache.setJSON(cacheNamespace, cacheKey, responseBody, {
|
|
403
|
+
expiresInSeconds: ANALYTICS_AGGREGATE_CACHE_TTL_SECONDS,
|
|
404
|
+
});
|
|
405
|
+
} catch (err) {
|
|
406
|
+
logger.debug(`${cacheNamespace} cache write failed`);
|
|
407
|
+
logger.debug(err);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return Response.sendJsonObjectResponse(req, res, responseBody);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/*
|
|
415
|
+
* Stable serialization for the aggregate cache key. Date instances
|
|
416
|
+
* are normalized to ISO so two logically-equal time windows hit
|
|
417
|
+
* the same cache slot, and we sort object keys so the ordering is
|
|
418
|
+
* deterministic across clients and across V8 versions.
|
|
419
|
+
*/
|
|
420
|
+
protected buildAggregateCacheKey(
|
|
421
|
+
aggregateBy: AggregateBy<AnalyticsDataModel>,
|
|
422
|
+
): string {
|
|
423
|
+
return JSON.stringify(
|
|
424
|
+
aggregateBy,
|
|
425
|
+
(_key: string, value: unknown): unknown => {
|
|
426
|
+
if (value instanceof Date) {
|
|
427
|
+
return value.toISOString();
|
|
428
|
+
}
|
|
429
|
+
if (
|
|
430
|
+
value &&
|
|
431
|
+
typeof value === "object" &&
|
|
432
|
+
!Array.isArray(value) &&
|
|
433
|
+
(value as Record<string, unknown>).constructor === Object
|
|
434
|
+
) {
|
|
435
|
+
const sorted: Record<string, unknown> = {};
|
|
436
|
+
for (const k of Object.keys(
|
|
437
|
+
value as Record<string, unknown>,
|
|
438
|
+
).sort()) {
|
|
439
|
+
sorted[k] = (value as Record<string, unknown>)[k];
|
|
440
|
+
}
|
|
441
|
+
return sorted;
|
|
442
|
+
}
|
|
443
|
+
return value;
|
|
444
|
+
},
|
|
445
|
+
);
|
|
338
446
|
}
|
|
339
447
|
|
|
340
448
|
@CaptureSpan()
|
package/Server/API/MetricAPI.ts
CHANGED
|
@@ -1,149 +1,16 @@
|
|
|
1
|
-
import AggregateBy from "../../Types/BaseDatabase/AggregateBy";
|
|
2
|
-
import AggregatedResult from "../../Types/BaseDatabase/AggregatedResult";
|
|
3
|
-
import DatabaseCommonInteractionProps from "../../Types/BaseDatabase/DatabaseCommonInteractionProps";
|
|
4
|
-
import BadRequestException from "../../Types/Exception/BadRequestException";
|
|
5
|
-
import { JSONObject } from "../../Types/JSON";
|
|
6
|
-
import JSONFunctions from "../../Types/JSONFunctions";
|
|
7
1
|
import Metric from "../../Models/AnalyticsModels/Metric";
|
|
8
2
|
import { MetricService } from "../Services/MetricService";
|
|
9
|
-
import GlobalCache from "../Infrastructure/GlobalCache";
|
|
10
|
-
import logger from "../Utils/Logger";
|
|
11
|
-
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
12
|
-
import { ExpressRequest, ExpressResponse } from "../Utils/Express";
|
|
13
|
-
import Response from "../Utils/Response";
|
|
14
|
-
import CommonAPI from "./CommonAPI";
|
|
15
3
|
import BaseAnalyticsAPI from "./BaseAnalyticsAPI";
|
|
16
4
|
|
|
17
5
|
/*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
6
|
+
* Metric CRUD + aggregate endpoints. The 8-second project-scoped
|
|
7
|
+
* aggregate cache that used to live here has been promoted to
|
|
8
|
+
* `BaseAnalyticsAPI.getAggregate` so Log/Span/AuditLog/etc. benefit
|
|
9
|
+
* from the same dashboard-widget-burst collapse without duplicating
|
|
10
|
+
* the wrapper on every analytics API.
|
|
22
11
|
*/
|
|
23
|
-
const AGGREGATE_CACHE_NAMESPACE: string = "metric-aggregate";
|
|
24
|
-
const AGGREGATE_CACHE_TTL_SECONDS: number = 8;
|
|
25
|
-
|
|
26
12
|
export default class MetricAPI extends BaseAnalyticsAPI<Metric, MetricService> {
|
|
27
13
|
public constructor(service: MetricService) {
|
|
28
14
|
super(Metric, service);
|
|
29
15
|
}
|
|
30
|
-
|
|
31
|
-
/*
|
|
32
|
-
* Cached override of BaseAnalyticsAPI.getAggregate.
|
|
33
|
-
*
|
|
34
|
-
* Why a cache: each chart/value/gauge/table widget on a dashboard
|
|
35
|
-
* issues its own /aggregate call. With 10+ widgets and a small group
|
|
36
|
-
* of users hitting the same dashboard the underlying ClickHouse
|
|
37
|
-
* cluster sees the same heavy aggregation many times in close
|
|
38
|
-
* succession. Aggregations are read-only and pure (same input ->
|
|
39
|
-
* same output for the bucket interval), so a brief result cache is
|
|
40
|
-
* safe.
|
|
41
|
-
*
|
|
42
|
-
* Cache key: tenant project + the deserialized aggregateBy payload.
|
|
43
|
-
* We must include the project so cross-tenant collisions cannot
|
|
44
|
-
* leak data; we deliberately do NOT key on user id, because the
|
|
45
|
-
* service layer applies project-scoped read permissions and metric
|
|
46
|
-
* data is project-wide.
|
|
47
|
-
*
|
|
48
|
-
* Cache miss / Redis down: we fall through to the live query, so
|
|
49
|
-
* cache outages degrade to today's behavior, never error.
|
|
50
|
-
*/
|
|
51
|
-
@CaptureSpan()
|
|
52
|
-
public override async getAggregate(
|
|
53
|
-
req: ExpressRequest,
|
|
54
|
-
res: ExpressResponse,
|
|
55
|
-
): Promise<void> {
|
|
56
|
-
await this.onBeforeList(req, res);
|
|
57
|
-
|
|
58
|
-
let aggregateBy: AggregateBy<Metric> | null = null;
|
|
59
|
-
|
|
60
|
-
if (req.body && req.body["aggregateBy"]) {
|
|
61
|
-
aggregateBy = JSONFunctions.deserialize(
|
|
62
|
-
req.body["aggregateBy"] as JSONObject,
|
|
63
|
-
) as any;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (!aggregateBy) {
|
|
67
|
-
throw new BadRequestException("AggregateBy is required");
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const databaseProps: DatabaseCommonInteractionProps =
|
|
71
|
-
await CommonAPI.getDatabaseCommonInteractionProps(req);
|
|
72
|
-
|
|
73
|
-
const projectId: string | undefined = databaseProps.tenantId?.toString();
|
|
74
|
-
const cacheKey: string | null = projectId
|
|
75
|
-
? `${projectId}:${this.buildCacheKey(aggregateBy)}`
|
|
76
|
-
: null;
|
|
77
|
-
|
|
78
|
-
if (cacheKey) {
|
|
79
|
-
try {
|
|
80
|
-
const cached: JSONObject | null = await GlobalCache.getJSONObject(
|
|
81
|
-
AGGREGATE_CACHE_NAMESPACE,
|
|
82
|
-
cacheKey,
|
|
83
|
-
);
|
|
84
|
-
if (cached) {
|
|
85
|
-
return Response.sendJsonObjectResponse(req, res, cached);
|
|
86
|
-
}
|
|
87
|
-
} catch (err) {
|
|
88
|
-
// Cache fetch failed — fall through to a live query.
|
|
89
|
-
logger.debug("MetricAPI aggregate cache read failed");
|
|
90
|
-
logger.debug(err);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const aggregateResult: AggregatedResult = await this.service.aggregateBy({
|
|
95
|
-
...aggregateBy,
|
|
96
|
-
props: databaseProps,
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
const responseBody: JSONObject = { ...(aggregateResult as any) };
|
|
100
|
-
|
|
101
|
-
if (cacheKey) {
|
|
102
|
-
try {
|
|
103
|
-
await GlobalCache.setJSON(
|
|
104
|
-
AGGREGATE_CACHE_NAMESPACE,
|
|
105
|
-
cacheKey,
|
|
106
|
-
responseBody,
|
|
107
|
-
{ expiresInSeconds: AGGREGATE_CACHE_TTL_SECONDS },
|
|
108
|
-
);
|
|
109
|
-
} catch (err) {
|
|
110
|
-
logger.debug("MetricAPI aggregate cache write failed");
|
|
111
|
-
logger.debug(err);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return Response.sendJsonObjectResponse(req, res, responseBody);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
private buildCacheKey(aggregateBy: AggregateBy<Metric>): string {
|
|
119
|
-
/*
|
|
120
|
-
* Stable serialization. Date instances are normalized to ISO so two
|
|
121
|
-
* logically-equal time windows hit the same cache slot, and we sort
|
|
122
|
-
* keys via JSON.stringify replacer to keep ordering deterministic
|
|
123
|
-
* across clients and across versions of V8.
|
|
124
|
-
*/
|
|
125
|
-
return JSON.stringify(
|
|
126
|
-
aggregateBy,
|
|
127
|
-
(_key: string, value: unknown): unknown => {
|
|
128
|
-
if (value instanceof Date) {
|
|
129
|
-
return value.toISOString();
|
|
130
|
-
}
|
|
131
|
-
if (
|
|
132
|
-
value &&
|
|
133
|
-
typeof value === "object" &&
|
|
134
|
-
!Array.isArray(value) &&
|
|
135
|
-
(value as Record<string, unknown>).constructor === Object
|
|
136
|
-
) {
|
|
137
|
-
const sorted: Record<string, unknown> = {};
|
|
138
|
-
for (const k of Object.keys(
|
|
139
|
-
value as Record<string, unknown>,
|
|
140
|
-
).sort()) {
|
|
141
|
-
sorted[k] = (value as Record<string, unknown>)[k];
|
|
142
|
-
}
|
|
143
|
-
return sorted;
|
|
144
|
-
}
|
|
145
|
-
return value;
|
|
146
|
-
},
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
16
|
}
|
package/Server/API/ProjectAPI.ts
CHANGED
|
@@ -5,6 +5,7 @@ import ProjectService, {
|
|
|
5
5
|
} from "../Services/ProjectService";
|
|
6
6
|
import ResellerService from "../Services/ResellerService";
|
|
7
7
|
import TeamMemberService from "../Services/TeamMemberService";
|
|
8
|
+
import QueryHelper from "../Types/Database/QueryHelper";
|
|
8
9
|
import Select from "../Types/Database/Select";
|
|
9
10
|
import {
|
|
10
11
|
ExpressRequest,
|
|
@@ -223,25 +224,61 @@ export default class ProjectAPI extends BaseAPI<Project, ProjectServiceType> {
|
|
|
223
224
|
}
|
|
224
225
|
}
|
|
225
226
|
|
|
226
|
-
|
|
227
|
+
/*
|
|
228
|
+
* Batch-fetch resellers for every project in one query instead of
|
|
229
|
+
* one findOneById per project.
|
|
230
|
+
*/
|
|
231
|
+
const resellerIds: Array<ObjectID> = [];
|
|
232
|
+
const seenResellerIds: Set<string> = new Set<string>();
|
|
227
233
|
for (const project of projects) {
|
|
228
|
-
if (
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
isRoot: true,
|
|
237
|
-
},
|
|
238
|
-
});
|
|
234
|
+
if (
|
|
235
|
+
project.resellerId &&
|
|
236
|
+
!seenResellerIds.has(project.resellerId.toString())
|
|
237
|
+
) {
|
|
238
|
+
seenResellerIds.add(project.resellerId.toString());
|
|
239
|
+
resellerIds.push(project.resellerId);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
239
242
|
|
|
240
|
-
|
|
241
|
-
|
|
243
|
+
if (resellerIds.length > 0) {
|
|
244
|
+
const resellers: Array<Reseller> = await ResellerService.findBy({
|
|
245
|
+
query: {
|
|
246
|
+
_id: QueryHelper.any(
|
|
247
|
+
resellerIds.map((id: ObjectID) => {
|
|
248
|
+
return id.toString();
|
|
249
|
+
}),
|
|
250
|
+
),
|
|
251
|
+
},
|
|
252
|
+
select: {
|
|
253
|
+
_id: true,
|
|
254
|
+
enableTelemetryFeatures: true,
|
|
255
|
+
},
|
|
256
|
+
limit: LIMIT_PER_PROJECT,
|
|
257
|
+
skip: 0,
|
|
258
|
+
props: {
|
|
259
|
+
isRoot: true,
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
const resellersById: Map<string, Reseller> = new Map<
|
|
264
|
+
string,
|
|
265
|
+
Reseller
|
|
266
|
+
>();
|
|
267
|
+
for (const reseller of resellers) {
|
|
268
|
+
if (reseller._id) {
|
|
269
|
+
resellersById.set(reseller._id.toString(), reseller);
|
|
242
270
|
}
|
|
271
|
+
}
|
|
243
272
|
|
|
244
|
-
|
|
273
|
+
for (const project of projects) {
|
|
274
|
+
if (project.resellerId) {
|
|
275
|
+
const reseller: Reseller | undefined = resellersById.get(
|
|
276
|
+
project.resellerId.toString(),
|
|
277
|
+
);
|
|
278
|
+
if (reseller) {
|
|
279
|
+
project.reseller = reseller;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
245
282
|
}
|
|
246
283
|
}
|
|
247
284
|
|