@oneuptime/common 12.0.17 → 12.0.19

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 (220) hide show
  1. package/Models/DatabaseModels/EnterpriseLicense.ts +23 -0
  2. package/Models/DatabaseModels/Index.ts +4 -2
  3. package/Models/DatabaseModels/Monitor.ts +39 -9
  4. package/Models/DatabaseModels/UserMicrosoftTeams.ts +344 -0
  5. package/Models/DatabaseModels/UserNotificationRule.ts +144 -8
  6. package/Models/DatabaseModels/UserNotificationSetting.ts +34 -0
  7. package/Models/DatabaseModels/UserOnCallLogTimeline.ts +95 -0
  8. package/Models/DatabaseModels/UserSlack.ts +341 -0
  9. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +9 -0
  10. package/Server/API/UserMicrosoftTeamsAPI.ts +127 -0
  11. package/Server/API/UserSlackAPI.ts +125 -0
  12. package/Server/EnvironmentConfig.ts +12 -107
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.ts +33 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.ts +51 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.ts +117 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.ts +197 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
  18. package/Server/Infrastructure/Queue.ts +7 -0
  19. package/Server/Services/BillingService.ts +8 -0
  20. package/Server/Services/EnterpriseLicenseService.ts +60 -0
  21. package/Server/Services/Index.ts +6 -2
  22. package/Server/Services/LlmLogService.ts +25 -9
  23. package/Server/Services/OnCallReadinessService.ts +86 -2
  24. package/Server/Services/ProjectService.ts +67 -0
  25. package/Server/Services/UserMicrosoftTeamsService.ts +208 -0
  26. package/Server/Services/UserNotificationMethodAdminService.ts +167 -5
  27. package/Server/Services/UserNotificationRuleAdminService.ts +40 -6
  28. package/Server/Services/UserNotificationRuleService.ts +589 -11
  29. package/Server/Services/UserNotificationSettingService.ts +138 -0
  30. package/Server/Services/UserService.ts +28 -0
  31. package/Server/Services/UserSlackService.ts +218 -0
  32. package/Server/Services/WorkspaceProjectAuthTokenService.ts +67 -1
  33. package/Server/Services/WorkspaceUserAuthTokenService.ts +73 -0
  34. package/Server/Services/WorkspaceUserNotificationService.ts +190 -0
  35. package/Server/Utils/Marketing/MarketingEventUtil.ts +145 -0
  36. package/Server/Utils/Marketing/MarketingEventWebhook.ts +114 -0
  37. package/Server/Utils/Monitor/MonitorLogUtil.ts +14 -1
  38. package/Server/Utils/Monitor/MonitorPayloadRedaction.ts +321 -0
  39. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +191 -4
  40. package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +22 -14
  41. package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +22 -11
  42. package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +37 -1
  43. package/Tests/App/Dashboard/OnCallRulesTable.test.tsx +18 -10
  44. package/Tests/App/Dashboard/PayAsYouGoNotices.test.tsx +17 -0
  45. package/Tests/Models/DatabaseModels/DomainRoleTierCoverage.test.ts +27 -1
  46. package/Tests/Models/DatabaseModels/MonitorSecretKeyColumnAccessControl.test.ts +214 -0
  47. package/Tests/Server/API/OnCallReadinessAPI.test.ts +145 -26
  48. package/Tests/Server/API/UserNotificationMethodAdminAPI.test.ts +2 -2
  49. package/Tests/Server/Services/AIServiceDailyBudget.test.ts +11 -1
  50. package/Tests/Server/Services/AdminRuleEditGuards.test.ts +22 -8
  51. package/Tests/Server/Services/BillingService.test.ts +20 -0
  52. package/Tests/Server/Services/DeliverNotificationForRuleExtraction.test.ts +18 -2
  53. package/Tests/Server/Services/EpisodeRuleSeverityRepair.test.ts +5 -1
  54. package/Tests/Server/Services/LlmLogServiceTokenAggregateParams.test.ts +640 -0
  55. package/Tests/Server/Services/NotificationChannelEventCoverage.test.ts +246 -22
  56. package/Tests/Server/Services/NotificationDeletionImpact.test.ts +136 -8
  57. package/Tests/Server/Services/OnCallNotificationFallback.test.ts +191 -8
  58. package/Tests/Server/Services/OnCallReadinessService.test.ts +343 -18
  59. package/Tests/Server/Services/SeverityRuleBackfill.test.ts +5 -1
  60. package/Tests/Server/Services/UserMicrosoftTeamsService.test.ts +343 -0
  61. package/Tests/Server/Services/UserNotificationMethodAdminService.test.ts +209 -8
  62. package/Tests/Server/Services/UserNotificationRuleAdminGuards.test.ts +22 -6
  63. package/Tests/Server/Services/UserNotificationRuleDefaultCreation.test.ts +12 -6
  64. package/Tests/Server/Services/UserNotificationRuleExecuteItem.test.ts +37 -3
  65. package/Tests/Server/Services/UserNotificationRuleWorkspaceDelivery.test.ts +669 -0
  66. package/Tests/Server/Services/UserNotificationSettingWorkspaceChannels.test.ts +410 -0
  67. package/Tests/Server/Services/UserSlackService.test.ts +407 -0
  68. package/Tests/Server/Services/WorkspaceProjectAuthTokenDisconnectCascade.test.ts +224 -0
  69. package/Tests/Server/Services/WorkspaceUserAuthTokenNotificationMethodCascade.test.ts +189 -0
  70. package/Tests/Server/Services/WorkspaceUserNotificationService.test.ts +496 -0
  71. package/Tests/Server/Types/Database/Permissions/AdminNotificationRuleAccess.test.ts +30 -17
  72. package/Tests/Server/Types/Database/Permissions/CreateOwnershipScoping.test.ts +8 -3
  73. package/Tests/Server/Types/Database/Permissions/OwnerOnlyColumns.test.ts +72 -3
  74. package/Tests/Server/Types/Database/Permissions/UserNotificationRuleScoping.test.ts +22 -18
  75. package/Tests/Server/Types/Database/Permissions/WorkspaceMethodStampedColumnCreate.test.ts +178 -0
  76. package/Tests/Server/Utils/Marketing/MarketingEventUtil.test.ts +259 -0
  77. package/Tests/Server/Utils/Marketing/MarketingEventWebhook.test.ts +257 -0
  78. package/Tests/Server/Utils/Monitor/MonitorLogUtilRedaction.test.ts +300 -0
  79. package/Tests/Server/Utils/Monitor/MonitorPayloadRedaction.test.ts +381 -0
  80. package/Tests/Server/Utils/Runbook/RunbookExecutePermission.test.ts +228 -0
  81. package/Tests/Server/Utils/SessionReplay/SessionReplayErasureTombstone.test.ts +189 -0
  82. package/Tests/Server/Utils/SessionReplay/SessionReplayUsage.test.ts +205 -0
  83. package/Tests/Server/Utils/Telemetry/AppMetrics.test.ts +275 -0
  84. package/Tests/Server/Utils/Telemetry/TelemetryContext.test.ts +279 -0
  85. package/Tests/Server/Utils/Workspace/MicrosoftTeamsDirectMessage.test.ts +454 -0
  86. package/Tests/Types/Kubernetes/KubernetesObjectParsers.test.ts +794 -0
  87. package/Tests/Types/Kubernetes/KubernetesRightSizing.test.ts +53 -0
  88. package/Tests/Types/Measurement/MeasurementAggregationType.test.ts +100 -0
  89. package/Tests/Types/Monitor/MonitorStepConfigHelpers.test.ts +114 -0
  90. package/Tests/Types/Workspace/WorkspaceType.test.ts +53 -0
  91. package/Tests/UI/Utils/NotificationMethodUtil.test.ts +137 -9
  92. package/Tests/UI/Utils/PermissionGate.test.ts +105 -0
  93. package/Tests/Utils/Dashboard/Components/DashboardComponentDefaults.test.ts +257 -0
  94. package/Types/Billing/PayAsYouGoPricing.ts +1 -1
  95. package/Types/Marketing/MarketingEvent.ts +97 -0
  96. package/UI/Utils/NotificationMethodUtil.ts +41 -1
  97. package/UI/Utils/PermissionGate.ts +56 -0
  98. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js +24 -0
  99. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js.map +1 -1
  100. package/build/dist/Models/DatabaseModels/Index.js +4 -2
  101. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  102. package/build/dist/Models/DatabaseModels/Monitor.js +39 -9
  103. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  104. package/build/dist/Models/DatabaseModels/UserMicrosoftTeams.js +363 -0
  105. package/build/dist/Models/DatabaseModels/UserMicrosoftTeams.js.map +1 -0
  106. package/build/dist/Models/DatabaseModels/UserNotificationRule.js +145 -8
  107. package/build/dist/Models/DatabaseModels/UserNotificationRule.js.map +1 -1
  108. package/build/dist/Models/DatabaseModels/UserNotificationSetting.js +38 -0
  109. package/build/dist/Models/DatabaseModels/UserNotificationSetting.js.map +1 -1
  110. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js +96 -0
  111. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js.map +1 -1
  112. package/build/dist/Models/DatabaseModels/UserSlack.js +361 -0
  113. package/build/dist/Models/DatabaseModels/UserSlack.js.map +1 -0
  114. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  115. package/build/dist/Server/API/UserMicrosoftTeamsAPI.js +82 -0
  116. package/build/dist/Server/API/UserMicrosoftTeamsAPI.js.map +1 -0
  117. package/build/dist/Server/API/UserSlackAPI.js +81 -0
  118. package/build/dist/Server/API/UserSlackAPI.js.map +1 -0
  119. package/build/dist/Server/EnvironmentConfig.js +10 -70
  120. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  121. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.js +26 -0
  122. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.js.map +1 -0
  123. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.js +37 -0
  124. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.js.map +1 -0
  125. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js +106 -0
  126. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js.map +1 -0
  127. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js +78 -0
  128. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js.map +1 -0
  129. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
  130. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  131. package/build/dist/Server/Infrastructure/Queue.js +7 -0
  132. package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
  133. package/build/dist/Server/Services/BillingService.js +6 -0
  134. package/build/dist/Server/Services/BillingService.js.map +1 -1
  135. package/build/dist/Server/Services/EnterpriseLicenseService.js +65 -0
  136. package/build/dist/Server/Services/EnterpriseLicenseService.js.map +1 -1
  137. package/build/dist/Server/Services/Index.js +6 -2
  138. package/build/dist/Server/Services/Index.js.map +1 -1
  139. package/build/dist/Server/Services/LlmLogService.js +25 -9
  140. package/build/dist/Server/Services/LlmLogService.js.map +1 -1
  141. package/build/dist/Server/Services/OnCallReadinessService.js +74 -2
  142. package/build/dist/Server/Services/OnCallReadinessService.js.map +1 -1
  143. package/build/dist/Server/Services/ProjectService.js +56 -0
  144. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  145. package/build/dist/Server/Services/UserMicrosoftTeamsService.js +183 -0
  146. package/build/dist/Server/Services/UserMicrosoftTeamsService.js.map +1 -0
  147. package/build/dist/Server/Services/UserNotificationMethodAdminService.js +138 -2
  148. package/build/dist/Server/Services/UserNotificationMethodAdminService.js.map +1 -1
  149. package/build/dist/Server/Services/UserNotificationRuleAdminService.js +32 -4
  150. package/build/dist/Server/Services/UserNotificationRuleAdminService.js.map +1 -1
  151. package/build/dist/Server/Services/UserNotificationRuleService.js +499 -67
  152. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  153. package/build/dist/Server/Services/UserNotificationSettingService.js +118 -0
  154. package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
  155. package/build/dist/Server/Services/UserService.js +26 -1
  156. package/build/dist/Server/Services/UserService.js.map +1 -1
  157. package/build/dist/Server/Services/UserSlackService.js +195 -0
  158. package/build/dist/Server/Services/UserSlackService.js.map +1 -0
  159. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js +64 -1
  160. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js.map +1 -1
  161. package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js +70 -0
  162. package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js.map +1 -1
  163. package/build/dist/Server/Services/WorkspaceUserNotificationService.js +129 -0
  164. package/build/dist/Server/Services/WorkspaceUserNotificationService.js.map +1 -0
  165. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js +94 -0
  166. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js.map +1 -0
  167. package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js +93 -0
  168. package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js.map +1 -0
  169. package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js +12 -1
  170. package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js.map +1 -1
  171. package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js +233 -0
  172. package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js.map +1 -0
  173. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +141 -4
  174. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  175. package/build/dist/Types/Billing/PayAsYouGoPricing.js +1 -1
  176. package/build/dist/Types/Billing/PayAsYouGoPricing.js.map +1 -1
  177. package/build/dist/Types/Marketing/MarketingEvent.js +43 -0
  178. package/build/dist/Types/Marketing/MarketingEvent.js.map +1 -0
  179. package/build/dist/UI/Utils/NotificationMethodUtil.js +33 -1
  180. package/build/dist/UI/Utils/NotificationMethodUtil.js.map +1 -1
  181. package/build/dist/UI/Utils/PermissionGate.js +32 -0
  182. package/build/dist/UI/Utils/PermissionGate.js.map +1 -1
  183. package/package.json +1 -1
  184. package/Models/DatabaseModels/MarketingConversion.ts +0 -410
  185. package/Server/Services/MarketingConversionService.ts +0 -10
  186. package/Server/Utils/Marketing/ConversionUploadProvider.ts +0 -215
  187. package/Server/Utils/Marketing/ConversionUploadProviders.ts +0 -22
  188. package/Server/Utils/Marketing/Providers/GoogleAds.ts +0 -333
  189. package/Server/Utils/Marketing/Providers/LinkedIn.ts +0 -185
  190. package/Server/Utils/Marketing/Providers/Meta.ts +0 -182
  191. package/Server/Utils/Marketing/Providers/MicrosoftAds.ts +0 -223
  192. package/Server/Utils/Marketing/Providers/Reddit.ts +0 -208
  193. package/Tests/Server/Utils/Marketing/AdUploadableConversionTypes.test.ts +0 -315
  194. package/Tests/Server/Utils/Marketing/ConversionUploadProvider.test.ts +0 -300
  195. package/Tests/Server/Utils/Marketing/GoogleAds.test.ts +0 -592
  196. package/Tests/Server/Utils/Marketing/LinkedIn.test.ts +0 -282
  197. package/Tests/Server/Utils/Marketing/Meta.test.ts +0 -304
  198. package/Tests/Server/Utils/Marketing/MicrosoftAds.test.ts +0 -263
  199. package/Tests/Server/Utils/Marketing/Reddit.test.ts +0 -259
  200. package/Types/Marketing/MarketingConversion.ts +0 -53
  201. package/build/dist/Models/DatabaseModels/MarketingConversion.js +0 -445
  202. package/build/dist/Models/DatabaseModels/MarketingConversion.js.map +0 -1
  203. package/build/dist/Server/Services/MarketingConversionService.js +0 -9
  204. package/build/dist/Server/Services/MarketingConversionService.js.map +0 -1
  205. package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js +0 -93
  206. package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js.map +0 -1
  207. package/build/dist/Server/Utils/Marketing/ConversionUploadProviders.js +0 -20
  208. package/build/dist/Server/Utils/Marketing/ConversionUploadProviders.js.map +0 -1
  209. package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js +0 -221
  210. package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js.map +0 -1
  211. package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js +0 -137
  212. package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js.map +0 -1
  213. package/build/dist/Server/Utils/Marketing/Providers/Meta.js +0 -133
  214. package/build/dist/Server/Utils/Marketing/Providers/Meta.js.map +0 -1
  215. package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js +0 -143
  216. package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js.map +0 -1
  217. package/build/dist/Server/Utils/Marketing/Providers/Reddit.js +0 -145
  218. package/build/dist/Server/Utils/Marketing/Providers/Reddit.js.map +0 -1
  219. package/build/dist/Types/Marketing/MarketingConversion.js +0 -52
  220. package/build/dist/Types/Marketing/MarketingConversion.js.map +0 -1
@@ -464,4 +464,109 @@ describe("PermissionGate", () => {
464
464
  ).toBeNull();
465
465
  });
466
466
  });
467
+ /*
468
+ * Column-level gating, added for https://github.com/OneUptime/oneuptime/issues/3360.
469
+ *
470
+ * Record-level gating decides whether to render a button. Column-level
471
+ * gating decides whether a field may go into a SELECT, and the consequence
472
+ * of getting it wrong is much worse: ColumnPermission on the server throws
473
+ * `User is not allowed to read on <column> column of <model>` and fails the
474
+ * ENTIRE request, so one over-eager field blanks the whole page rather than
475
+ * one value. Monitor's secret keys are the first columns on Monitor a
476
+ * legitimate dashboard user can be refused, which is what made this helper
477
+ * necessary.
478
+ */
479
+ describe("canReadColumn", () => {
480
+ test("refuses a secret key column to a Viewer", () => {
481
+ permissionsForTest = [Permission.Viewer];
482
+
483
+ expect(
484
+ PermissionGate.canReadColumn(new Monitor(), "serverMonitorSecretKey"),
485
+ ).toBe(false);
486
+ });
487
+
488
+ test("allows a secret key column to a role that can rotate it", () => {
489
+ permissionsForTest = [Permission.ProjectAdmin];
490
+
491
+ expect(
492
+ PermissionGate.canReadColumn(new Monitor(), "serverMonitorSecretKey"),
493
+ ).toBe(true);
494
+ });
495
+
496
+ test("still allows the columns a Viewer is meant to see", () => {
497
+ /*
498
+ * The regression that would break the monitor page for read-only users:
499
+ * if this ever returns false, Pages/Monitor/View/Index.tsx stops asking
500
+ * for monitorType and the page cannot decide what kind of monitor it is
501
+ * looking at.
502
+ */
503
+ permissionsForTest = [Permission.Viewer];
504
+
505
+ expect(PermissionGate.canReadColumn(new Monitor(), "monitorType")).toBe(
506
+ true,
507
+ );
508
+ });
509
+
510
+ test("allows a master admin everything", () => {
511
+ isMasterAdminForTest = true;
512
+ permissionsForTest = [];
513
+
514
+ expect(
515
+ PermissionGate.canReadColumn(new Monitor(), "serverMonitorSecretKey"),
516
+ ).toBe(true);
517
+ });
518
+
519
+ test("fails closed while the permission snapshot has not loaded", () => {
520
+ /*
521
+ * The opposite call from PermissionGate.check, and deliberately so.
522
+ * check() hides a button rather than accuse someone of lacking a
523
+ * permission they hold. Here the recoverable outcome is omitting the
524
+ * field - the page renders without one value. Failing open would send
525
+ * the column and hard-fail the request.
526
+ */
527
+ permissionsForTest = [];
528
+
529
+ expect(
530
+ PermissionGate.canReadColumn(new Monitor(), "serverMonitorSecretKey"),
531
+ ).toBe(false);
532
+ });
533
+
534
+ test("allows a column that declares no read access control", () => {
535
+ /*
536
+ * Nothing to enforce, and ColumnPermission agrees - it only refuses
537
+ * columns that name permissions the user does not hold.
538
+ */
539
+ permissionsForTest = [Permission.Viewer];
540
+
541
+ expect(
542
+ PermissionGate.canReadColumn(
543
+ {
544
+ getColumnAccessControlForAllColumns: () => {
545
+ return { someColumn: { create: [], read: [], update: [] } };
546
+ },
547
+ },
548
+ "someColumn",
549
+ ),
550
+ ).toBe(true);
551
+ });
552
+
553
+ test("allows a column name the model does not know about", () => {
554
+ permissionsForTest = [Permission.Viewer];
555
+
556
+ expect(PermissionGate.canReadColumn(new Monitor(), "noSuchColumn")).toBe(
557
+ true,
558
+ );
559
+ });
560
+
561
+ test("honours an explicitly supplied permission snapshot", () => {
562
+ // Storage says Viewer; the caller passes something else and wins.
563
+ permissionsForTest = [Permission.Viewer];
564
+
565
+ expect(
566
+ PermissionGate.canReadColumn(new Monitor(), "serverMonitorSecretKey", {
567
+ permissions: [Permission.ProjectOwner],
568
+ }),
569
+ ).toBe(true);
570
+ });
571
+ });
467
572
  });
@@ -0,0 +1,257 @@
1
+ import { describe, expect, test } from "@jest/globals";
2
+ import DashboardComponentType from "../../../../Types/Dashboard/DashboardComponentType";
3
+ import DashboardBaseComponent from "../../../../Types/Dashboard/DashboardComponents/DashboardBaseComponent";
4
+ import ObjectID from "../../../../Types/ObjectID";
5
+ import { ObjectType } from "../../../../Types/JSON";
6
+ import DashboardAlertListComponentUtil from "../../../../Utils/Dashboard/Components/DashboardAlertListComponent";
7
+ import DashboardCephOsdListComponentUtil from "../../../../Utils/Dashboard/Components/DashboardCephOsdListComponent";
8
+ import DashboardCephPoolListComponentUtil from "../../../../Utils/Dashboard/Components/DashboardCephPoolListComponent";
9
+ import DashboardChartComponentUtil from "../../../../Utils/Dashboard/Components/DashboardChartComponent";
10
+ import DashboardClockComponentUtil from "../../../../Utils/Dashboard/Components/DashboardClockComponent";
11
+ import DashboardDataSourceChartComponentUtil from "../../../../Utils/Dashboard/Components/DashboardDataSourceChartComponent";
12
+ import DashboardDataSourceGaugeComponentUtil from "../../../../Utils/Dashboard/Components/DashboardDataSourceGaugeComponent";
13
+ import DashboardDataSourceTableComponentUtil from "../../../../Utils/Dashboard/Components/DashboardDataSourceTableComponent";
14
+ import DashboardDataSourceValueComponentUtil from "../../../../Utils/Dashboard/Components/DashboardDataSourceValueComponent";
15
+ import DashboardDockerContainerListComponentUtil from "../../../../Utils/Dashboard/Components/DashboardDockerContainerListComponent";
16
+ import DashboardDockerHostListComponentUtil from "../../../../Utils/Dashboard/Components/DashboardDockerHostListComponent";
17
+ import DashboardDockerImageListComponentUtil from "../../../../Utils/Dashboard/Components/DashboardDockerImageListComponent";
18
+ import DashboardDockerNetworkListComponentUtil from "../../../../Utils/Dashboard/Components/DashboardDockerNetworkListComponent";
19
+ import DashboardDockerSwarmNodeListComponentUtil from "../../../../Utils/Dashboard/Components/DashboardDockerSwarmNodeListComponent";
20
+ import DashboardDockerSwarmServiceListComponentUtil from "../../../../Utils/Dashboard/Components/DashboardDockerSwarmServiceListComponent";
21
+ import DashboardDockerVolumeListComponentUtil from "../../../../Utils/Dashboard/Components/DashboardDockerVolumeListComponent";
22
+ import DashboardGaugeComponentUtil from "../../../../Utils/Dashboard/Components/DashboardGaugeComponent";
23
+ import DashboardHostListComponentUtil from "../../../../Utils/Dashboard/Components/DashboardHostListComponent";
24
+ import DashboardHtmlComponentUtil from "../../../../Utils/Dashboard/Components/DashboardHtmlComponent";
25
+ import DashboardIncidentListComponentUtil from "../../../../Utils/Dashboard/Components/DashboardIncidentListComponent";
26
+ import DashboardKubernetesCronJobListComponentUtil from "../../../../Utils/Dashboard/Components/DashboardKubernetesCronJobListComponent";
27
+ import DashboardKubernetesDaemonSetListComponentUtil from "../../../../Utils/Dashboard/Components/DashboardKubernetesDaemonSetListComponent";
28
+ import DashboardKubernetesDeploymentListComponentUtil from "../../../../Utils/Dashboard/Components/DashboardKubernetesDeploymentListComponent";
29
+ import DashboardKubernetesJobListComponentUtil from "../../../../Utils/Dashboard/Components/DashboardKubernetesJobListComponent";
30
+ import DashboardKubernetesNamespaceListComponentUtil from "../../../../Utils/Dashboard/Components/DashboardKubernetesNamespaceListComponent";
31
+ import DashboardKubernetesNodeListComponentUtil from "../../../../Utils/Dashboard/Components/DashboardKubernetesNodeListComponent";
32
+ import DashboardKubernetesPodListComponentUtil from "../../../../Utils/Dashboard/Components/DashboardKubernetesPodListComponent";
33
+ import DashboardKubernetesStatefulSetListComponentUtil from "../../../../Utils/Dashboard/Components/DashboardKubernetesStatefulSetListComponent";
34
+ import DashboardLogChartComponentUtil from "../../../../Utils/Dashboard/Components/DashboardLogChartComponent";
35
+ import DashboardLogStreamComponentUtil from "../../../../Utils/Dashboard/Components/DashboardLogStreamComponent";
36
+ import DashboardMonitorListComponentUtil from "../../../../Utils/Dashboard/Components/DashboardMonitorListComponent";
37
+ import DashboardNetworkMapComponentUtil from "../../../../Utils/Dashboard/Components/DashboardNetworkMapComponent";
38
+ import DashboardPodmanContainerListComponentUtil from "../../../../Utils/Dashboard/Components/DashboardPodmanContainerListComponent";
39
+ import DashboardPodmanHostListComponentUtil from "../../../../Utils/Dashboard/Components/DashboardPodmanHostListComponent";
40
+ import DashboardPodmanImageListComponentUtil from "../../../../Utils/Dashboard/Components/DashboardPodmanImageListComponent";
41
+ import DashboardPodmanNetworkListComponentUtil from "../../../../Utils/Dashboard/Components/DashboardPodmanNetworkListComponent";
42
+ import DashboardPodmanVolumeListComponentUtil from "../../../../Utils/Dashboard/Components/DashboardPodmanVolumeListComponent";
43
+ import DashboardProxmoxGuestListComponentUtil from "../../../../Utils/Dashboard/Components/DashboardProxmoxGuestListComponent";
44
+ import DashboardProxmoxNodeListComponentUtil from "../../../../Utils/Dashboard/Components/DashboardProxmoxNodeListComponent";
45
+ import DashboardSecurityEventsFlowComponentUtil from "../../../../Utils/Dashboard/Components/DashboardSecurityEventsFlowComponent";
46
+ import DashboardSecurityEventsListComponentUtil from "../../../../Utils/Dashboard/Components/DashboardSecurityEventsListComponent";
47
+ import DashboardSloComponentUtil from "../../../../Utils/Dashboard/Components/DashboardSloComponent";
48
+ import DashboardTableComponentUtil from "../../../../Utils/Dashboard/Components/DashboardTableComponent";
49
+ import DashboardTextComponentUtil from "../../../../Utils/Dashboard/Components/DashboardTextComponent";
50
+ import DashboardTraceChartComponentUtil from "../../../../Utils/Dashboard/Components/DashboardTraceChartComponent";
51
+ import DashboardTraceListComponentUtil from "../../../../Utils/Dashboard/Components/DashboardTraceListComponent";
52
+ import DashboardTraceTableComponentUtil from "../../../../Utils/Dashboard/Components/DashboardTraceTableComponent";
53
+ import DashboardValueComponentUtil from "../../../../Utils/Dashboard/Components/DashboardValueComponent";
54
+ import DashboardBaseComponentUtil from "../../../../Utils/Dashboard/Components/DashboardBaseComponent";
55
+
56
+ /*
57
+ * Every dashboard widget type ships a getDefaultComponent() that returns the
58
+ * seed component the editor drops onto the canvas when a user first adds that
59
+ * widget. Unlike getComponentSettingsArguments (dispatched through Index.ts and
60
+ * already covered by DashboardComponentsUtil.test.ts), these defaults have no
61
+ * central dispatcher — each util overrides the method independently. That makes
62
+ * them easy to get subtly wrong in ways the compiler cannot catch:
63
+ *
64
+ * - copy-pasting a util and forgetting to change the componentType field, so
65
+ * two widget types seed the same type and the editor renders the wrong one;
66
+ * - a min dimension larger than the default dimension, so the widget is born
67
+ * smaller than its own minimum and the resize logic clamps it on drop;
68
+ * - a zero/negative width or height, so the widget is invisible on the grid;
69
+ * - a shared componentId constant instead of a freshly generated ObjectID, so
70
+ * two widgets on the same dashboard collide on id.
71
+ *
72
+ * This suite pins those invariants for all widget types at once. The TYPE_TO_UTIL
73
+ * map below must stay in lockstep with the enum; the exhaustiveness test fails
74
+ * loudly if a new widget type is added without a default to guard here.
75
+ */
76
+
77
+ type ComponentUtil = {
78
+ getDefaultComponent: () => DashboardBaseComponent;
79
+ };
80
+
81
+ const TYPE_TO_UTIL: Record<DashboardComponentType, ComponentUtil> = {
82
+ [DashboardComponentType.Chart]: DashboardChartComponentUtil,
83
+ [DashboardComponentType.Text]: DashboardTextComponentUtil,
84
+ [DashboardComponentType.Clock]: DashboardClockComponentUtil,
85
+ [DashboardComponentType.Value]: DashboardValueComponentUtil,
86
+ [DashboardComponentType.Table]: DashboardTableComponentUtil,
87
+ [DashboardComponentType.Gauge]: DashboardGaugeComponentUtil,
88
+ [DashboardComponentType.DataSourceChart]:
89
+ DashboardDataSourceChartComponentUtil,
90
+ [DashboardComponentType.DataSourceValue]:
91
+ DashboardDataSourceValueComponentUtil,
92
+ [DashboardComponentType.DataSourceGauge]:
93
+ DashboardDataSourceGaugeComponentUtil,
94
+ [DashboardComponentType.DataSourceTable]:
95
+ DashboardDataSourceTableComponentUtil,
96
+ [DashboardComponentType.LogStream]: DashboardLogStreamComponentUtil,
97
+ [DashboardComponentType.LogChart]: DashboardLogChartComponentUtil,
98
+ [DashboardComponentType.SecurityEventsList]:
99
+ DashboardSecurityEventsListComponentUtil,
100
+ [DashboardComponentType.SecurityEventsFlow]:
101
+ DashboardSecurityEventsFlowComponentUtil,
102
+ [DashboardComponentType.TraceList]: DashboardTraceListComponentUtil,
103
+ [DashboardComponentType.TraceChart]: DashboardTraceChartComponentUtil,
104
+ [DashboardComponentType.TraceTable]: DashboardTraceTableComponentUtil,
105
+ [DashboardComponentType.IncidentList]: DashboardIncidentListComponentUtil,
106
+ [DashboardComponentType.AlertList]: DashboardAlertListComponentUtil,
107
+ [DashboardComponentType.MonitorList]: DashboardMonitorListComponentUtil,
108
+ [DashboardComponentType.Slo]: DashboardSloComponentUtil,
109
+ [DashboardComponentType.KubernetesPodList]:
110
+ DashboardKubernetesPodListComponentUtil,
111
+ [DashboardComponentType.KubernetesNodeList]:
112
+ DashboardKubernetesNodeListComponentUtil,
113
+ [DashboardComponentType.KubernetesNamespaceList]:
114
+ DashboardKubernetesNamespaceListComponentUtil,
115
+ [DashboardComponentType.KubernetesDeploymentList]:
116
+ DashboardKubernetesDeploymentListComponentUtil,
117
+ [DashboardComponentType.KubernetesStatefulSetList]:
118
+ DashboardKubernetesStatefulSetListComponentUtil,
119
+ [DashboardComponentType.KubernetesDaemonSetList]:
120
+ DashboardKubernetesDaemonSetListComponentUtil,
121
+ [DashboardComponentType.KubernetesJobList]:
122
+ DashboardKubernetesJobListComponentUtil,
123
+ [DashboardComponentType.KubernetesCronJobList]:
124
+ DashboardKubernetesCronJobListComponentUtil,
125
+ [DashboardComponentType.DockerHostList]: DashboardDockerHostListComponentUtil,
126
+ [DashboardComponentType.DockerContainerList]:
127
+ DashboardDockerContainerListComponentUtil,
128
+ [DashboardComponentType.DockerImageList]:
129
+ DashboardDockerImageListComponentUtil,
130
+ [DashboardComponentType.DockerNetworkList]:
131
+ DashboardDockerNetworkListComponentUtil,
132
+ [DashboardComponentType.DockerVolumeList]:
133
+ DashboardDockerVolumeListComponentUtil,
134
+ [DashboardComponentType.PodmanHostList]: DashboardPodmanHostListComponentUtil,
135
+ [DashboardComponentType.PodmanContainerList]:
136
+ DashboardPodmanContainerListComponentUtil,
137
+ [DashboardComponentType.PodmanImageList]:
138
+ DashboardPodmanImageListComponentUtil,
139
+ [DashboardComponentType.PodmanNetworkList]:
140
+ DashboardPodmanNetworkListComponentUtil,
141
+ [DashboardComponentType.PodmanVolumeList]:
142
+ DashboardPodmanVolumeListComponentUtil,
143
+ [DashboardComponentType.HostList]: DashboardHostListComponentUtil,
144
+ [DashboardComponentType.ProxmoxNodeList]:
145
+ DashboardProxmoxNodeListComponentUtil,
146
+ [DashboardComponentType.ProxmoxGuestList]:
147
+ DashboardProxmoxGuestListComponentUtil,
148
+ [DashboardComponentType.DockerSwarmNodeList]:
149
+ DashboardDockerSwarmNodeListComponentUtil,
150
+ [DashboardComponentType.DockerSwarmServiceList]:
151
+ DashboardDockerSwarmServiceListComponentUtil,
152
+ [DashboardComponentType.CephOsdList]: DashboardCephOsdListComponentUtil,
153
+ [DashboardComponentType.CephPoolList]: DashboardCephPoolListComponentUtil,
154
+ [DashboardComponentType.NetworkMap]: DashboardNetworkMapComponentUtil,
155
+ [DashboardComponentType.Html]: DashboardHtmlComponentUtil,
156
+ };
157
+
158
+ const ALL_TYPES: Array<DashboardComponentType> = Object.values(
159
+ DashboardComponentType,
160
+ );
161
+
162
+ describe("Dashboard component getDefaultComponent()", () => {
163
+ test("every DashboardComponentType has a default component guarded here", () => {
164
+ // If a new widget type is added to the enum, this fails until it is mapped.
165
+ for (const type of ALL_TYPES) {
166
+ expect(TYPE_TO_UTIL[type]).toBeDefined();
167
+ }
168
+ // And nothing stale: the map must not carry keys the enum dropped.
169
+ expect(Object.keys(TYPE_TO_UTIL).sort()).toEqual([...ALL_TYPES].sort());
170
+ });
171
+
172
+ test.each(ALL_TYPES)(
173
+ "%s default is a well-formed dashboard component",
174
+ (type: DashboardComponentType) => {
175
+ const component: DashboardBaseComponent =
176
+ TYPE_TO_UTIL[type].getDefaultComponent();
177
+
178
+ // It must be tagged as a dashboard component so serialization routes it right.
179
+ expect(component._type).toBe(ObjectType.DashboardComponent);
180
+
181
+ /*
182
+ * The seeded componentType must match the type the editor asked for; a
183
+ * copy-paste that leaves the wrong type here makes the widget render as a
184
+ * different kind than the one the user picked.
185
+ */
186
+ expect(component.componentType).toBe(type);
187
+
188
+ // A zero/negative footprint means the widget is invisible on the grid.
189
+ expect(component.widthInDashboardUnits).toBeGreaterThan(0);
190
+ expect(component.heightInDashboardUnits).toBeGreaterThan(0);
191
+ expect(component.minWidthInDashboardUnits).toBeGreaterThan(0);
192
+ expect(component.minHeightInDashboardUnits).toBeGreaterThan(0);
193
+
194
+ /*
195
+ * The default size must not start below its own minimum, or the resize
196
+ * clamp shrinks the widget the instant it is dropped.
197
+ */
198
+ expect(component.widthInDashboardUnits).toBeGreaterThanOrEqual(
199
+ component.minWidthInDashboardUnits,
200
+ );
201
+ expect(component.heightInDashboardUnits).toBeGreaterThanOrEqual(
202
+ component.minHeightInDashboardUnits,
203
+ );
204
+
205
+ // A widget is dropped at the origin; negative offsets place it off-grid.
206
+ expect(component.topInDashboardUnits).toBeGreaterThanOrEqual(0);
207
+ expect(component.leftInDashboardUnits).toBeGreaterThanOrEqual(0);
208
+
209
+ // The id must be a real, generated ObjectID (UUID), not a placeholder.
210
+ expect(component.componentId).toBeInstanceOf(ObjectID);
211
+ expect(ObjectID.isValidUUID(component.componentId.toString())).toBe(true);
212
+ },
213
+ );
214
+
215
+ test("no two widget types seed the same componentType", () => {
216
+ /*
217
+ * Guards the classic copy-paste bug: duplicating a util and forgetting to
218
+ * change componentType. Every default must report a distinct type.
219
+ */
220
+ const seededTypes: Array<DashboardComponentType> = ALL_TYPES.map(
221
+ (type: DashboardComponentType) => {
222
+ return TYPE_TO_UTIL[type].getDefaultComponent().componentType;
223
+ },
224
+ );
225
+ expect(new Set<DashboardComponentType>(seededTypes).size).toBe(
226
+ seededTypes.length,
227
+ );
228
+ });
229
+
230
+ test("each call generates a fresh componentId so widgets never collide", () => {
231
+ /*
232
+ * Two of the same widget added to one dashboard must not share an id, or
233
+ * selection/deletion in the editor would act on both at once. This only
234
+ * holds if getDefaultComponent() calls ObjectID.generate() per call rather
235
+ * than reusing a module-level constant.
236
+ */
237
+ for (const type of ALL_TYPES) {
238
+ const first: DashboardBaseComponent =
239
+ TYPE_TO_UTIL[type].getDefaultComponent();
240
+ const second: DashboardBaseComponent =
241
+ TYPE_TO_UTIL[type].getDefaultComponent();
242
+ expect(first.componentId.toString()).not.toBe(
243
+ second.componentId.toString(),
244
+ );
245
+ }
246
+ });
247
+
248
+ test("the base component util refuses to seed a component", () => {
249
+ /*
250
+ * The base class intentionally throws so a util that forgets to override
251
+ * getDefaultComponent() fails fast instead of returning a typeless stub.
252
+ */
253
+ expect(() => {
254
+ return DashboardBaseComponentUtil.getDefaultComponent();
255
+ }).toThrow();
256
+ });
257
+ });
@@ -10,7 +10,7 @@
10
10
  * worse than quoting no price at all, so there is exactly one copy.
11
11
  */
12
12
 
13
- // Telemetry ingest - logs, traces, metrics and profiles.
13
+ // Telemetry ingest - logs, traces, metrics, profiles and security events.
14
14
  export const TELEMETRY_PRICE_IN_USD_PER_GB: number = 0.1;
15
15
 
16
16
  /*
@@ -0,0 +1,97 @@
1
+ import { JSONObject } from "../JSON";
2
+
3
+ /*
4
+ * The outbound marketing event contract.
5
+ *
6
+ * OneUptime does not keep a conversion ledger. The four moments worth
7
+ * measuring are emitted as signed webhooks at the instant they happen and are
8
+ * not stored here afterwards, so this interface — not a database table — is
9
+ * the whole record of what a conversion looks like.
10
+ *
11
+ * Two consequences follow from having no store, and both are the receiver's
12
+ * job rather than ours:
13
+ *
14
+ * - DEDUPLICATION. `eventId` is stable for a given real-world occurrence, so
15
+ * a redelivery carries the id the first attempt did. Retries are expected
16
+ * (the queue retries, and Cal retries into us), so a receiver that keys on
17
+ * anything else will double-count.
18
+ * - ORDERING. Events are delivered per-occurrence with no sequence number.
19
+ * A signup and a plan change seconds apart may arrive either way round;
20
+ * order by `occurredAt`, never by arrival.
21
+ *
22
+ * Names are the same snake_case the browser funnel uses
23
+ * (Common/Types/Analytics/RevenueEvent.ts) so one event means one thing on
24
+ * both sides. Adding a property is safe; changing what an existing one means
25
+ * requires a new schema version.
26
+ */
27
+ export enum MarketingEventType {
28
+ SignUp = "sign_up",
29
+ MeetingBooked = "meeting_booked",
30
+ SubscriptionUpgraded = "subscription_upgraded",
31
+ SubscriptionDowngraded = "subscription_downgraded",
32
+ /*
33
+ * A sales-led licence was issued, carrying its annual contract value.
34
+ *
35
+ * This is the only event that reports enterprise revenue, and the only one
36
+ * whose email is typed in by a human rather than captured from a session —
37
+ * which is exactly what makes it joinable: set EnterpriseLicense.email to
38
+ * the address the customer booked with and this event shares an identity
39
+ * with the meeting_booked that preceded it, months earlier.
40
+ */
41
+ EnterpriseLicenseIssued = "enterprise_license_issued",
42
+ }
43
+
44
+ export const MARKETING_EVENT_SCHEMA_VERSION: number = 1;
45
+
46
+ /*
47
+ * The campaign the converting visitor carried.
48
+ *
49
+ * Copied from the User or Project row for a signup or a plan change, and read
50
+ * out of Cal booking metadata for a booked meeting. Every field is optional:
51
+ * a conversion with no attribution at all is still a conversion, and is still
52
+ * emitted.
53
+ */
54
+ export interface MarketingEventAttribution extends JSONObject {
55
+ utmSource?: string | undefined;
56
+ utmMedium?: string | undefined;
57
+ utmCampaign?: string | undefined;
58
+ utmTerm?: string | undefined;
59
+ utmContent?: string | undefined;
60
+ utmUrl?: string | undefined;
61
+ clickIds: JSONObject;
62
+ firstTouch: JSONObject;
63
+ }
64
+
65
+ export interface MarketingEvent extends JSONObject {
66
+ schemaVersion: number;
67
+ /*
68
+ * Stable per real-world occurrence, and the receiver's deduplication key:
69
+ *
70
+ * sign_up:{userId}
71
+ * meeting_booked:{calBookingUid}
72
+ * subscription_upgraded:{projectId}:{occurredAt}
73
+ * subscription_downgraded:{projectId}:{occurredAt}
74
+ * enterprise_license_issued:{enterpriseLicenseId}
75
+ *
76
+ * Three of these are naturally unique — a user signs up once, a booking has
77
+ * one uid, a licence row is issued once — so Cal retrying a delivery or the
78
+ * queue retrying a job cannot produce a second conversion. A plan change can
79
+ * legitimately recur for one project, so its id carries the instant it
80
+ * happened.
81
+ */
82
+ eventId: string;
83
+ eventType: MarketingEventType;
84
+ // ISO 8601, UTC. The moment the conversion happened, not the moment it was sent.
85
+ occurredAt: string;
86
+ /*
87
+ * Identity. Both forms are sent: the address for a direct CRM join, and its
88
+ * SHA-256 (trimmed and lowercased before hashing, per the usual ad-platform
89
+ * normalisation) for anywhere a digest is wanted instead. Absent only when
90
+ * OneUptime genuinely has no address for the conversion.
91
+ */
92
+ email?: string | undefined;
93
+ emailHash?: string | undefined;
94
+ attribution: MarketingEventAttribution;
95
+ // Event-specific detail. See the per-event builders in MarketingEventUtil.
96
+ data: JSONObject;
97
+ }
@@ -7,6 +7,8 @@ import UserNotificationRule from "../../Models/DatabaseModels/UserNotificationRu
7
7
  import UserPush from "../../Models/DatabaseModels/UserPush";
8
8
  import UserSMS from "../../Models/DatabaseModels/UserSMS";
9
9
  import UserTelegram from "../../Models/DatabaseModels/UserTelegram";
10
+ import UserSlack from "../../Models/DatabaseModels/UserSlack";
11
+ import UserMicrosoftTeams from "../../Models/DatabaseModels/UserMicrosoftTeams";
10
12
  import UserWebhook from "../../Models/DatabaseModels/UserWebhook";
11
13
  import UserWhatsApp from "../../Models/DatabaseModels/UserWhatsApp";
12
14
  import ObjectID from "../../Types/ObjectID";
@@ -37,6 +39,8 @@ export interface NotificationMethodModels {
37
39
  userPush: Array<UserPush>;
38
40
  userWhatsApps: Array<UserWhatsApp>;
39
41
  userTelegrams: Array<UserTelegram>;
42
+ userSlacks: Array<UserSlack>;
43
+ userMicrosoftTeamsAccounts: Array<UserMicrosoftTeams>;
40
44
  userWebhooks: Array<UserWebhook>;
41
45
  }
42
46
 
@@ -74,6 +78,16 @@ const NOTIFICATION_METHOD_RELATIONS: Array<NotificationMethodRelation> = [
74
78
  title: "Telegram",
75
79
  columns: ["telegramUserHandle", "telegramChatId"],
76
80
  },
81
+ {
82
+ relationName: "userSlack",
83
+ title: "Slack",
84
+ columns: ["slackUserName", "slackUserId"],
85
+ },
86
+ {
87
+ relationName: "userMicrosoftTeams",
88
+ title: "Microsoft Teams",
89
+ columns: ["microsoftTeamsUserName", "microsoftTeamsUserId"],
90
+ },
77
91
  /*
78
92
  * Only `name` is read here. A webhook URL is a bearer credential for
79
93
  * Slack/Discord/Teams style hooks, so it is deliberately never selected into
@@ -185,6 +199,24 @@ export default class NotificationMethodUtil {
185
199
  );
186
200
  }
187
201
 
202
+ if (model instanceof UserSlack) {
203
+ return (
204
+ "Slack: " +
205
+ (NotificationMethodUtil.readColumn(model, "slackUserName") ||
206
+ NotificationMethodUtil.readColumn(model, "slackUserId") ||
207
+ "Unknown Account")
208
+ );
209
+ }
210
+
211
+ if (model instanceof UserMicrosoftTeams) {
212
+ return (
213
+ "Microsoft Teams: " +
214
+ (NotificationMethodUtil.readColumn(model, "microsoftTeamsUserName") ||
215
+ NotificationMethodUtil.readColumn(model, "microsoftTeamsUserId") ||
216
+ "Unknown Account")
217
+ );
218
+ }
219
+
188
220
  const identifier: string =
189
221
  NotificationMethodUtil.readColumn(model, "phone") ||
190
222
  NotificationMethodUtil.readColumn(model, "email") ||
@@ -207,7 +239,7 @@ export default class NotificationMethodUtil {
207
239
 
208
240
  /*
209
241
  * Maps the id chosen in the dropdown back onto the rule being created. The
210
- * dropdown is single-select, so at most one of the seven foreign keys is set.
242
+ * dropdown is single-select, so at most one of the nine foreign keys is set.
211
243
  * An id that matches nothing leaves the rule untouched, and the server then
212
244
  * rejects the create as having no notification method.
213
245
  */
@@ -258,6 +290,12 @@ export default class NotificationMethodUtil {
258
290
  assignById(models.userTelegrams, (id: ObjectID) => {
259
291
  rule.userTelegramId = id;
260
292
  });
293
+ assignById(models.userSlacks, (id: ObjectID) => {
294
+ rule.userSlackId = id;
295
+ });
296
+ assignById(models.userMicrosoftTeamsAccounts, (id: ObjectID) => {
297
+ rule.userMicrosoftTeamsId = id;
298
+ });
261
299
  assignById(models.userWebhooks, (id: ObjectID) => {
262
300
  rule.userWebhookId = id;
263
301
  });
@@ -279,6 +317,8 @@ export default class NotificationMethodUtil {
279
317
  ...models.userPush,
280
318
  ...models.userWhatsApps,
281
319
  ...models.userTelegrams,
320
+ ...models.userSlacks,
321
+ ...models.userMicrosoftTeamsAccounts,
282
322
  ...models.userWebhooks,
283
323
  ];
284
324
  }
@@ -4,6 +4,7 @@ import Permission, {
4
4
  PermissionHelper,
5
5
  PermissionProps,
6
6
  } from "../../Types/Permission";
7
+ import { ColumnAccessControl } from "../../Types/BaseDatabase/AccessControl";
7
8
  import PermissionUtil from "./Permission";
8
9
  import User from "./User";
9
10
 
@@ -52,6 +53,15 @@ export interface PermissionCheckableModel {
52
53
  getDeletePermissions: () => Array<Permission>;
53
54
  }
54
55
 
56
+ /*
57
+ * A model that declares column-level access control. Separate from
58
+ * PermissionCheckableModel because column gating answers a different question
59
+ * ("may this field be SELECTED?") and only the database models carry it.
60
+ */
61
+ export interface ColumnPermissionCheckableModel {
62
+ getColumnAccessControlForAllColumns: () => Dictionary<ColumnAccessControl>;
63
+ }
64
+
55
65
  export interface PermissionGateOptions {
56
66
  /*
57
67
  * Overrides the permissions read from storage. Only used by tests and by
@@ -270,6 +280,52 @@ export default class PermissionGate {
270
280
  };
271
281
  }
272
282
 
283
+ /*
284
+ * Whether the signed-in user may SELECT a column.
285
+ *
286
+ * Column access control is enforced differently from record access control:
287
+ * asking for a column you cannot read is not degraded, it is fatal.
288
+ * ColumnPermission throws `User is not allowed to read on <column> column of
289
+ * <model>` and the whole request fails, so one unreadable field in a select
290
+ * takes down the entire page rather than blanking one value. That is why
291
+ * ModelAction has no column-level members (see the comment on the enum) and
292
+ * why callers must ask this BEFORE building the select, not after.
293
+ *
294
+ * Deliberately fails closed. When the permission snapshot has not landed yet
295
+ * this returns false and the caller omits the field: the page renders
296
+ * without that one value, which is recoverable. Failing open would send the
297
+ * column anyway and hard-fail the request, which is not.
298
+ *
299
+ * A column with no declared read ACL is readable - there is nothing to
300
+ * enforce, and ColumnPermission agrees.
301
+ */
302
+ public static canReadColumn(
303
+ model: ColumnPermissionCheckableModel,
304
+ columnName: string,
305
+ options?: PermissionGateOptions | undefined,
306
+ ): boolean {
307
+ if (User.isMasterAdmin()) {
308
+ return true;
309
+ }
310
+
311
+ const accessControl: ColumnAccessControl | undefined =
312
+ model.getColumnAccessControlForAllColumns()[columnName];
313
+
314
+ const columnPermissions: Array<Permission> = accessControl?.read || [];
315
+
316
+ if (columnPermissions.length === 0) {
317
+ return true;
318
+ }
319
+
320
+ const userPermissions: Array<Permission> =
321
+ options?.permissions ?? PermissionUtil.getAllPermissions();
322
+
323
+ return PermissionHelper.doesPermissionsIntersect(
324
+ userPermissions,
325
+ columnPermissions,
326
+ );
327
+ }
328
+
273
329
  /* Test seam - the props lookup is memoized for the lifetime of the page. */
274
330
  public static clearPermissionPropsCache(): void {
275
331
  permissionPropsCache = null;