@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.
Files changed (248) hide show
  1. package/Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel.ts +49 -0
  2. package/Models/AnalyticsModels/AuditLog.ts +8 -0
  3. package/Models/AnalyticsModels/ExceptionInstance.ts +1 -0
  4. package/Models/AnalyticsModels/Log.ts +1 -0
  5. package/Models/AnalyticsModels/Metric.ts +10 -0
  6. package/Models/AnalyticsModels/MonitorLog.ts +1 -0
  7. package/Models/AnalyticsModels/Profile.ts +1 -0
  8. package/Models/AnalyticsModels/ProfileSample.ts +1 -0
  9. package/Models/AnalyticsModels/Span.ts +1 -0
  10. package/Models/DatabaseModels/Alert.ts +2 -0
  11. package/Models/DatabaseModels/AlertCustomField.ts +37 -0
  12. package/Models/DatabaseModels/AlertFeed.ts +1 -0
  13. package/Models/DatabaseModels/CallLog.ts +2 -0
  14. package/Models/DatabaseModels/DockerHost.ts +34 -0
  15. package/Models/DatabaseModels/EmailLog.ts +2 -0
  16. package/Models/DatabaseModels/Host.ts +34 -0
  17. package/Models/DatabaseModels/Incident.ts +1 -0
  18. package/Models/DatabaseModels/IncidentCustomField.ts +37 -0
  19. package/Models/DatabaseModels/IncidentFeed.ts +1 -0
  20. package/Models/DatabaseModels/IncidentMember.ts +9 -0
  21. package/Models/DatabaseModels/KubernetesCluster.ts +34 -0
  22. package/Models/DatabaseModels/MonitorCustomField.ts +37 -0
  23. package/Models/DatabaseModels/MonitorFeed.ts +1 -0
  24. package/Models/DatabaseModels/MonitorProbe.ts +1 -0
  25. package/Models/DatabaseModels/OnCallDutyPolicyCustomField.ts +37 -0
  26. package/Models/DatabaseModels/OnCallDutyPolicyTimeLog.ts +3 -0
  27. package/Models/DatabaseModels/ScheduledMaintenanceCustomField.ts +37 -0
  28. package/Models/DatabaseModels/SmsLog.ts +2 -0
  29. package/Models/DatabaseModels/StatusPageCustomField.ts +37 -0
  30. package/Models/DatabaseModels/StatusPageSubscriber.ts +2 -0
  31. package/Models/DatabaseModels/TableView.ts +40 -0
  32. package/Models/DatabaseModels/TeamMemberCustomField.ts +37 -0
  33. package/Models/DatabaseModels/TelemetryException.ts +2 -0
  34. package/Models/DatabaseModels/UserOnCallLog.ts +1 -0
  35. package/Models/DatabaseModels/WorkflowLog.ts +1 -0
  36. package/Server/API/BaseAnalyticsAPI.ts +128 -20
  37. package/Server/API/MetricAPI.ts +5 -138
  38. package/Server/API/ProjectAPI.ts +52 -15
  39. package/Server/API/StatusAPI.ts +103 -7
  40. package/Server/EnvironmentConfig.ts +69 -0
  41. package/Server/Infrastructure/Postgres/DataSourceOptions.ts +26 -1
  42. package/Server/Infrastructure/Postgres/SchemaMigrations/1779392865146-AddAgentVersionToKubernetesDockerHost.ts +29 -0
  43. package/Server/Infrastructure/Postgres/SchemaMigrations/1779392970424-AddPerformanceIndexes.ts +160 -0
  44. package/Server/Infrastructure/Postgres/SchemaMigrations/1779536271671-AddFacetsToTableView.ts +13 -0
  45. package/Server/Infrastructure/Postgres/SchemaMigrations/1779540427366-AddIsMemberNotifiedIndex.ts +34 -0
  46. package/Server/Infrastructure/Postgres/SchemaMigrations/1779619108628-AddDropdownOptionsToCustomFields.ts +67 -0
  47. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
  48. package/Server/Infrastructure/PostgresDatabase.ts +2 -5
  49. package/Server/Middleware/ProjectAuthorization.ts +31 -53
  50. package/Server/Middleware/UserAuthorization.ts +106 -64
  51. package/Server/Services/AccessTokenService.ts +1 -1
  52. package/Server/Services/AnalyticsDatabaseService.ts +24 -4
  53. package/Server/Services/ApiKeyService.ts +100 -1
  54. package/Server/Services/DockerHostService.ts +5 -0
  55. package/Server/Services/HostService.ts +6 -0
  56. package/Server/Services/KubernetesClusterService.ts +33 -10
  57. package/Server/Services/MetricService.ts +113 -0
  58. package/Server/Services/MonitorService.ts +10 -3
  59. package/Server/Services/ProjectService.ts +93 -2
  60. package/Server/Services/TeamMemberService.ts +36 -0
  61. package/Server/Services/UserService.ts +38 -0
  62. package/Server/Utils/Response.ts +4 -1
  63. package/Server/Utils/UserPermission/UserPermission.ts +17 -1
  64. package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +2 -2
  65. package/Types/API/HTTPResponse.ts +16 -0
  66. package/Types/BaseDatabase/ListResult.ts +6 -0
  67. package/Types/CustomField/CustomFieldType.ts +2 -0
  68. package/Types/Date.ts +9 -1
  69. package/Types/ListData.ts +14 -0
  70. package/Types/Monitor/DnsMonitor/DnsMonitorResponse.ts +3 -0
  71. package/Types/Monitor/DnssecMonitor/DnssecMonitorResponse.ts +5 -0
  72. package/Types/Monitor/DomainMonitor/DomainMonitorResponse.ts +4 -0
  73. package/Types/Monitor/ExternalStatusPageMonitor/ExternalStatusPageMonitorResponse.ts +4 -0
  74. package/Types/Monitor/SnmpMonitor/SnmpMonitorResponse.ts +3 -0
  75. package/Types/Probe/ProbeAttempt.ts +9 -0
  76. package/Types/Probe/ProbeMonitorResponse.ts +3 -0
  77. package/UI/Components/BulkUpdate/BulkOwnerActions.tsx +504 -0
  78. package/UI/Components/BulkUpdate/BulkUpdateForm.tsx +64 -54
  79. package/UI/Components/CustomFields/CustomFieldsDetail.tsx +38 -0
  80. package/UI/Components/CustomFields/DropdownOptionsInput.tsx +150 -0
  81. package/UI/Components/Detail/Detail.tsx +78 -11
  82. package/UI/Components/List/List.tsx +6 -0
  83. package/UI/Components/ModelTable/BaseModelTable.tsx +74 -2
  84. package/UI/Components/ModelTable/TableView.tsx +70 -30
  85. package/UI/Components/Pagination/Pagination.tsx +75 -33
  86. package/UI/Components/Table/Table.tsx +6 -0
  87. package/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.ts +1 -0
  88. package/build/dist/Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel.js +33 -0
  89. package/build/dist/Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel.js.map +1 -1
  90. package/build/dist/Models/AnalyticsModels/AuditLog.js +8 -0
  91. package/build/dist/Models/AnalyticsModels/AuditLog.js.map +1 -1
  92. package/build/dist/Models/AnalyticsModels/ExceptionInstance.js +1 -0
  93. package/build/dist/Models/AnalyticsModels/ExceptionInstance.js.map +1 -1
  94. package/build/dist/Models/AnalyticsModels/Log.js +1 -0
  95. package/build/dist/Models/AnalyticsModels/Log.js.map +1 -1
  96. package/build/dist/Models/AnalyticsModels/Metric.js +10 -0
  97. package/build/dist/Models/AnalyticsModels/Metric.js.map +1 -1
  98. package/build/dist/Models/AnalyticsModels/MonitorLog.js +1 -0
  99. package/build/dist/Models/AnalyticsModels/MonitorLog.js.map +1 -1
  100. package/build/dist/Models/AnalyticsModels/Profile.js +1 -0
  101. package/build/dist/Models/AnalyticsModels/Profile.js.map +1 -1
  102. package/build/dist/Models/AnalyticsModels/ProfileSample.js +1 -0
  103. package/build/dist/Models/AnalyticsModels/ProfileSample.js.map +1 -1
  104. package/build/dist/Models/AnalyticsModels/Span.js +1 -0
  105. package/build/dist/Models/AnalyticsModels/Span.js.map +1 -1
  106. package/build/dist/Models/DatabaseModels/Alert.js +3 -1
  107. package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
  108. package/build/dist/Models/DatabaseModels/AlertCustomField.js +38 -0
  109. package/build/dist/Models/DatabaseModels/AlertCustomField.js.map +1 -1
  110. package/build/dist/Models/DatabaseModels/AlertFeed.js +2 -1
  111. package/build/dist/Models/DatabaseModels/AlertFeed.js.map +1 -1
  112. package/build/dist/Models/DatabaseModels/CallLog.js +4 -1
  113. package/build/dist/Models/DatabaseModels/CallLog.js.map +1 -1
  114. package/build/dist/Models/DatabaseModels/DockerHost.js +35 -0
  115. package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
  116. package/build/dist/Models/DatabaseModels/EmailLog.js +4 -1
  117. package/build/dist/Models/DatabaseModels/EmailLog.js.map +1 -1
  118. package/build/dist/Models/DatabaseModels/Host.js +35 -0
  119. package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
  120. package/build/dist/Models/DatabaseModels/Incident.js +2 -1
  121. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  122. package/build/dist/Models/DatabaseModels/IncidentCustomField.js +38 -0
  123. package/build/dist/Models/DatabaseModels/IncidentCustomField.js.map +1 -1
  124. package/build/dist/Models/DatabaseModels/IncidentFeed.js +2 -1
  125. package/build/dist/Models/DatabaseModels/IncidentFeed.js.map +1 -1
  126. package/build/dist/Models/DatabaseModels/IncidentMember.js +11 -1
  127. package/build/dist/Models/DatabaseModels/IncidentMember.js.map +1 -1
  128. package/build/dist/Models/DatabaseModels/KubernetesCluster.js +35 -0
  129. package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
  130. package/build/dist/Models/DatabaseModels/MonitorCustomField.js +38 -0
  131. package/build/dist/Models/DatabaseModels/MonitorCustomField.js.map +1 -1
  132. package/build/dist/Models/DatabaseModels/MonitorFeed.js +2 -1
  133. package/build/dist/Models/DatabaseModels/MonitorFeed.js.map +1 -1
  134. package/build/dist/Models/DatabaseModels/MonitorProbe.js +2 -0
  135. package/build/dist/Models/DatabaseModels/MonitorProbe.js.map +1 -1
  136. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyCustomField.js +38 -0
  137. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyCustomField.js.map +1 -1
  138. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyTimeLog.js +3 -0
  139. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyTimeLog.js.map +1 -1
  140. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceCustomField.js +38 -0
  141. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceCustomField.js.map +1 -1
  142. package/build/dist/Models/DatabaseModels/SmsLog.js +4 -1
  143. package/build/dist/Models/DatabaseModels/SmsLog.js.map +1 -1
  144. package/build/dist/Models/DatabaseModels/StatusPageCustomField.js +38 -0
  145. package/build/dist/Models/DatabaseModels/StatusPageCustomField.js.map +1 -1
  146. package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js +4 -1
  147. package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js.map +1 -1
  148. package/build/dist/Models/DatabaseModels/TableView.js +40 -0
  149. package/build/dist/Models/DatabaseModels/TableView.js.map +1 -1
  150. package/build/dist/Models/DatabaseModels/TeamMemberCustomField.js +38 -0
  151. package/build/dist/Models/DatabaseModels/TeamMemberCustomField.js.map +1 -1
  152. package/build/dist/Models/DatabaseModels/TelemetryException.js +3 -1
  153. package/build/dist/Models/DatabaseModels/TelemetryException.js.map +1 -1
  154. package/build/dist/Models/DatabaseModels/UserOnCallLog.js +1 -0
  155. package/build/dist/Models/DatabaseModels/UserOnCallLog.js.map +1 -1
  156. package/build/dist/Models/DatabaseModels/WorkflowLog.js +2 -1
  157. package/build/dist/Models/DatabaseModels/WorkflowLog.js.map +1 -1
  158. package/build/dist/Server/API/BaseAnalyticsAPI.js +105 -18
  159. package/build/dist/Server/API/BaseAnalyticsAPI.js.map +1 -1
  160. package/build/dist/Server/API/MetricAPI.js +5 -113
  161. package/build/dist/Server/API/MetricAPI.js.map +1 -1
  162. package/build/dist/Server/API/ProjectAPI.js +42 -14
  163. package/build/dist/Server/API/ProjectAPI.js.map +1 -1
  164. package/build/dist/Server/API/StatusAPI.js +75 -8
  165. package/build/dist/Server/API/StatusAPI.js.map +1 -1
  166. package/build/dist/Server/EnvironmentConfig.js +41 -0
  167. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  168. package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js +20 -2
  169. package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js.map +1 -1
  170. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779392865146-AddAgentVersionToKubernetesDockerHost.js +16 -0
  171. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779392865146-AddAgentVersionToKubernetesDockerHost.js.map +1 -0
  172. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779392970424-AddPerformanceIndexes.js +63 -0
  173. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779392970424-AddPerformanceIndexes.js.map +1 -0
  174. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779536271671-AddFacetsToTableView.js +12 -0
  175. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779536271671-AddFacetsToTableView.js.map +1 -0
  176. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779540427366-AddIsMemberNotifiedIndex.js +27 -0
  177. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779540427366-AddIsMemberNotifiedIndex.js.map +1 -0
  178. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779619108628-AddDropdownOptionsToCustomFields.js +28 -0
  179. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779619108628-AddDropdownOptionsToCustomFields.js.map +1 -0
  180. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
  181. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  182. package/build/dist/Server/Infrastructure/PostgresDatabase.js +2 -2
  183. package/build/dist/Server/Infrastructure/PostgresDatabase.js.map +1 -1
  184. package/build/dist/Server/Middleware/ProjectAuthorization.js +21 -39
  185. package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
  186. package/build/dist/Server/Middleware/UserAuthorization.js +83 -50
  187. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  188. package/build/dist/Server/Services/AccessTokenService.js +1 -1
  189. package/build/dist/Server/Services/AccessTokenService.js.map +1 -1
  190. package/build/dist/Server/Services/AnalyticsDatabaseService.js +22 -3
  191. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  192. package/build/dist/Server/Services/ApiKeyService.js +86 -0
  193. package/build/dist/Server/Services/ApiKeyService.js.map +1 -1
  194. package/build/dist/Server/Services/DockerHostService.js +5 -1
  195. package/build/dist/Server/Services/DockerHostService.js.map +1 -1
  196. package/build/dist/Server/Services/HostService.js +5 -1
  197. package/build/dist/Server/Services/HostService.js.map +1 -1
  198. package/build/dist/Server/Services/KubernetesClusterService.js +21 -11
  199. package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
  200. package/build/dist/Server/Services/MetricService.js +89 -0
  201. package/build/dist/Server/Services/MetricService.js.map +1 -1
  202. package/build/dist/Server/Services/MonitorService.js +8 -3
  203. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  204. package/build/dist/Server/Services/ProjectService.js +84 -2
  205. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  206. package/build/dist/Server/Services/TeamMemberService.js +24 -0
  207. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  208. package/build/dist/Server/Services/UserService.js +36 -0
  209. package/build/dist/Server/Services/UserService.js.map +1 -1
  210. package/build/dist/Server/Utils/Response.js +6 -5
  211. package/build/dist/Server/Utils/Response.js.map +1 -1
  212. package/build/dist/Server/Utils/UserPermission/UserPermission.js +13 -1
  213. package/build/dist/Server/Utils/UserPermission/UserPermission.js.map +1 -1
  214. package/build/dist/Tests/Server/Services/AnalyticsDatabaseService.test.js +2 -2
  215. package/build/dist/Tests/Server/Services/AnalyticsDatabaseService.test.js.map +1 -1
  216. package/build/dist/Types/API/HTTPResponse.js +15 -0
  217. package/build/dist/Types/API/HTTPResponse.js.map +1 -1
  218. package/build/dist/Types/CustomField/CustomFieldType.js +2 -0
  219. package/build/dist/Types/CustomField/CustomFieldType.js.map +1 -1
  220. package/build/dist/Types/Date.js +10 -1
  221. package/build/dist/Types/Date.js.map +1 -1
  222. package/build/dist/Types/ListData.js +4 -0
  223. package/build/dist/Types/ListData.js.map +1 -1
  224. package/build/dist/Types/Probe/ProbeAttempt.js +2 -0
  225. package/build/dist/Types/Probe/ProbeAttempt.js.map +1 -0
  226. package/build/dist/UI/Components/BulkUpdate/BulkOwnerActions.js +376 -0
  227. package/build/dist/UI/Components/BulkUpdate/BulkOwnerActions.js.map +1 -0
  228. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js +32 -25
  229. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js.map +1 -1
  230. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +32 -0
  231. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
  232. package/build/dist/UI/Components/CustomFields/DropdownOptionsInput.js +84 -0
  233. package/build/dist/UI/Components/CustomFields/DropdownOptionsInput.js.map +1 -0
  234. package/build/dist/UI/Components/Detail/Detail.js +34 -3
  235. package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
  236. package/build/dist/UI/Components/List/List.js +1 -1
  237. package/build/dist/UI/Components/List/List.js.map +1 -1
  238. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +45 -5
  239. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  240. package/build/dist/UI/Components/ModelTable/TableView.js +40 -19
  241. package/build/dist/UI/Components/ModelTable/TableView.js.map +1 -1
  242. package/build/dist/UI/Components/Pagination/Pagination.js +62 -36
  243. package/build/dist/UI/Components/Pagination/Pagination.js.map +1 -1
  244. package/build/dist/UI/Components/Table/Table.js +1 -1
  245. package/build/dist/UI/Components/Table/Table.js.map +1 -1
  246. package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.js +1 -0
  247. package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.js.map +1 -1
  248. 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",
@@ -536,6 +536,7 @@ export default class UserOnCallLog extends BaseModel {
536
536
  read: [Permission.CurrentUser],
537
537
  update: [],
538
538
  })
539
+ @Index()
539
540
  @TableColumn({
540
541
  required: true,
541
542
  type: TableColumnType.ShortText,
@@ -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
- const [list, count] = await Promise.all([
272
- this.service.findBy({
273
- query,
274
- select,
275
- skip: skip,
276
- limit: limit,
277
- sort: sort,
278
- groupBy: groupBy,
279
- props: databaseProps,
280
- }),
281
- this.service.countBy({
282
- query,
283
- groupBy: groupBy,
284
- props: databaseProps,
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
- count,
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
- return Response.sendJsonObjectResponse(req, res, {
336
- ...(aggregateResult as any),
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()
@@ -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
- * Aggregate cache TTL. Dashboards typically auto-refresh every 30s+, so
19
- * an 8s window collapses bursts of identical requests (e.g. 12 widgets
20
- * loading on the same page) onto a single ClickHouse query while still
21
- * looking real-time to humans.
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
  }
@@ -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
- // get reseller for each project.
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 (project.resellerId) {
229
- const reseller: Reseller | null =
230
- await ResellerService.findOneById({
231
- id: project.resellerId,
232
- select: {
233
- enableTelemetryFeatures: true,
234
- },
235
- props: {
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
- if (!reseller) {
241
- continue;
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
- project.reseller = reseller;
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