@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.
- package/Models/DatabaseModels/EnterpriseLicense.ts +23 -0
- package/Models/DatabaseModels/Index.ts +4 -2
- package/Models/DatabaseModels/Monitor.ts +39 -9
- package/Models/DatabaseModels/UserMicrosoftTeams.ts +344 -0
- package/Models/DatabaseModels/UserNotificationRule.ts +144 -8
- package/Models/DatabaseModels/UserNotificationSetting.ts +34 -0
- package/Models/DatabaseModels/UserOnCallLogTimeline.ts +95 -0
- package/Models/DatabaseModels/UserSlack.ts +341 -0
- package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +9 -0
- package/Server/API/UserMicrosoftTeamsAPI.ts +127 -0
- package/Server/API/UserSlackAPI.ts +125 -0
- package/Server/EnvironmentConfig.ts +12 -107
- package/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.ts +33 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.ts +51 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.ts +117 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.ts +197 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
- package/Server/Infrastructure/Queue.ts +7 -0
- package/Server/Services/BillingService.ts +8 -0
- package/Server/Services/EnterpriseLicenseService.ts +60 -0
- package/Server/Services/Index.ts +6 -2
- package/Server/Services/LlmLogService.ts +25 -9
- package/Server/Services/OnCallReadinessService.ts +86 -2
- package/Server/Services/ProjectService.ts +67 -0
- package/Server/Services/UserMicrosoftTeamsService.ts +208 -0
- package/Server/Services/UserNotificationMethodAdminService.ts +167 -5
- package/Server/Services/UserNotificationRuleAdminService.ts +40 -6
- package/Server/Services/UserNotificationRuleService.ts +589 -11
- package/Server/Services/UserNotificationSettingService.ts +138 -0
- package/Server/Services/UserService.ts +28 -0
- package/Server/Services/UserSlackService.ts +218 -0
- package/Server/Services/WorkspaceProjectAuthTokenService.ts +67 -1
- package/Server/Services/WorkspaceUserAuthTokenService.ts +73 -0
- package/Server/Services/WorkspaceUserNotificationService.ts +190 -0
- package/Server/Utils/Marketing/MarketingEventUtil.ts +145 -0
- package/Server/Utils/Marketing/MarketingEventWebhook.ts +114 -0
- package/Server/Utils/Monitor/MonitorLogUtil.ts +14 -1
- package/Server/Utils/Monitor/MonitorPayloadRedaction.ts +321 -0
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +191 -4
- package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +22 -14
- package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +22 -11
- package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +37 -1
- package/Tests/App/Dashboard/OnCallRulesTable.test.tsx +18 -10
- package/Tests/App/Dashboard/PayAsYouGoNotices.test.tsx +17 -0
- package/Tests/Models/DatabaseModels/DomainRoleTierCoverage.test.ts +27 -1
- package/Tests/Models/DatabaseModels/MonitorSecretKeyColumnAccessControl.test.ts +214 -0
- package/Tests/Server/API/OnCallReadinessAPI.test.ts +145 -26
- package/Tests/Server/API/UserNotificationMethodAdminAPI.test.ts +2 -2
- package/Tests/Server/Services/AIServiceDailyBudget.test.ts +11 -1
- package/Tests/Server/Services/AdminRuleEditGuards.test.ts +22 -8
- package/Tests/Server/Services/BillingService.test.ts +20 -0
- package/Tests/Server/Services/DeliverNotificationForRuleExtraction.test.ts +18 -2
- package/Tests/Server/Services/EpisodeRuleSeverityRepair.test.ts +5 -1
- package/Tests/Server/Services/LlmLogServiceTokenAggregateParams.test.ts +640 -0
- package/Tests/Server/Services/NotificationChannelEventCoverage.test.ts +246 -22
- package/Tests/Server/Services/NotificationDeletionImpact.test.ts +136 -8
- package/Tests/Server/Services/OnCallNotificationFallback.test.ts +191 -8
- package/Tests/Server/Services/OnCallReadinessService.test.ts +343 -18
- package/Tests/Server/Services/SeverityRuleBackfill.test.ts +5 -1
- package/Tests/Server/Services/UserMicrosoftTeamsService.test.ts +343 -0
- package/Tests/Server/Services/UserNotificationMethodAdminService.test.ts +209 -8
- package/Tests/Server/Services/UserNotificationRuleAdminGuards.test.ts +22 -6
- package/Tests/Server/Services/UserNotificationRuleDefaultCreation.test.ts +12 -6
- package/Tests/Server/Services/UserNotificationRuleExecuteItem.test.ts +37 -3
- package/Tests/Server/Services/UserNotificationRuleWorkspaceDelivery.test.ts +669 -0
- package/Tests/Server/Services/UserNotificationSettingWorkspaceChannels.test.ts +410 -0
- package/Tests/Server/Services/UserSlackService.test.ts +407 -0
- package/Tests/Server/Services/WorkspaceProjectAuthTokenDisconnectCascade.test.ts +224 -0
- package/Tests/Server/Services/WorkspaceUserAuthTokenNotificationMethodCascade.test.ts +189 -0
- package/Tests/Server/Services/WorkspaceUserNotificationService.test.ts +496 -0
- package/Tests/Server/Types/Database/Permissions/AdminNotificationRuleAccess.test.ts +30 -17
- package/Tests/Server/Types/Database/Permissions/CreateOwnershipScoping.test.ts +8 -3
- package/Tests/Server/Types/Database/Permissions/OwnerOnlyColumns.test.ts +72 -3
- package/Tests/Server/Types/Database/Permissions/UserNotificationRuleScoping.test.ts +22 -18
- package/Tests/Server/Types/Database/Permissions/WorkspaceMethodStampedColumnCreate.test.ts +178 -0
- package/Tests/Server/Utils/Marketing/MarketingEventUtil.test.ts +259 -0
- package/Tests/Server/Utils/Marketing/MarketingEventWebhook.test.ts +257 -0
- package/Tests/Server/Utils/Monitor/MonitorLogUtilRedaction.test.ts +300 -0
- package/Tests/Server/Utils/Monitor/MonitorPayloadRedaction.test.ts +381 -0
- package/Tests/Server/Utils/Runbook/RunbookExecutePermission.test.ts +228 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayErasureTombstone.test.ts +189 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayUsage.test.ts +205 -0
- package/Tests/Server/Utils/Telemetry/AppMetrics.test.ts +275 -0
- package/Tests/Server/Utils/Telemetry/TelemetryContext.test.ts +279 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsDirectMessage.test.ts +454 -0
- package/Tests/Types/Kubernetes/KubernetesObjectParsers.test.ts +794 -0
- package/Tests/Types/Kubernetes/KubernetesRightSizing.test.ts +53 -0
- package/Tests/Types/Measurement/MeasurementAggregationType.test.ts +100 -0
- package/Tests/Types/Monitor/MonitorStepConfigHelpers.test.ts +114 -0
- package/Tests/Types/Workspace/WorkspaceType.test.ts +53 -0
- package/Tests/UI/Utils/NotificationMethodUtil.test.ts +137 -9
- package/Tests/UI/Utils/PermissionGate.test.ts +105 -0
- package/Tests/Utils/Dashboard/Components/DashboardComponentDefaults.test.ts +257 -0
- package/Types/Billing/PayAsYouGoPricing.ts +1 -1
- package/Types/Marketing/MarketingEvent.ts +97 -0
- package/UI/Utils/NotificationMethodUtil.ts +41 -1
- package/UI/Utils/PermissionGate.ts +56 -0
- package/build/dist/Models/DatabaseModels/EnterpriseLicense.js +24 -0
- package/build/dist/Models/DatabaseModels/EnterpriseLicense.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +4 -2
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Monitor.js +39 -9
- package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserMicrosoftTeams.js +363 -0
- package/build/dist/Models/DatabaseModels/UserMicrosoftTeams.js.map +1 -0
- package/build/dist/Models/DatabaseModels/UserNotificationRule.js +145 -8
- package/build/dist/Models/DatabaseModels/UserNotificationRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserNotificationSetting.js +38 -0
- package/build/dist/Models/DatabaseModels/UserNotificationSetting.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js +96 -0
- package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserSlack.js +361 -0
- package/build/dist/Models/DatabaseModels/UserSlack.js.map +1 -0
- package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
- package/build/dist/Server/API/UserMicrosoftTeamsAPI.js +82 -0
- package/build/dist/Server/API/UserMicrosoftTeamsAPI.js.map +1 -0
- package/build/dist/Server/API/UserSlackAPI.js +81 -0
- package/build/dist/Server/API/UserSlackAPI.js.map +1 -0
- package/build/dist/Server/EnvironmentConfig.js +10 -70
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.js +26 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.js +37 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js +106 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js +78 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/Queue.js +7 -0
- package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
- package/build/dist/Server/Services/BillingService.js +6 -0
- package/build/dist/Server/Services/BillingService.js.map +1 -1
- package/build/dist/Server/Services/EnterpriseLicenseService.js +65 -0
- package/build/dist/Server/Services/EnterpriseLicenseService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +6 -2
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/LlmLogService.js +25 -9
- package/build/dist/Server/Services/LlmLogService.js.map +1 -1
- package/build/dist/Server/Services/OnCallReadinessService.js +74 -2
- package/build/dist/Server/Services/OnCallReadinessService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +56 -0
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/UserMicrosoftTeamsService.js +183 -0
- package/build/dist/Server/Services/UserMicrosoftTeamsService.js.map +1 -0
- package/build/dist/Server/Services/UserNotificationMethodAdminService.js +138 -2
- package/build/dist/Server/Services/UserNotificationMethodAdminService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleAdminService.js +32 -4
- package/build/dist/Server/Services/UserNotificationRuleAdminService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js +499 -67
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationSettingService.js +118 -0
- package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +26 -1
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Services/UserSlackService.js +195 -0
- package/build/dist/Server/Services/UserSlackService.js.map +1 -0
- package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js +64 -1
- package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js +70 -0
- package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceUserNotificationService.js +129 -0
- package/build/dist/Server/Services/WorkspaceUserNotificationService.js.map +1 -0
- package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js +94 -0
- package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js.map +1 -0
- package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js +93 -0
- package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js +12 -1
- package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js +233 -0
- package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +141 -4
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/Billing/PayAsYouGoPricing.js +1 -1
- package/build/dist/Types/Billing/PayAsYouGoPricing.js.map +1 -1
- package/build/dist/Types/Marketing/MarketingEvent.js +43 -0
- package/build/dist/Types/Marketing/MarketingEvent.js.map +1 -0
- package/build/dist/UI/Utils/NotificationMethodUtil.js +33 -1
- package/build/dist/UI/Utils/NotificationMethodUtil.js.map +1 -1
- package/build/dist/UI/Utils/PermissionGate.js +32 -0
- package/build/dist/UI/Utils/PermissionGate.js.map +1 -1
- package/package.json +1 -1
- package/Models/DatabaseModels/MarketingConversion.ts +0 -410
- package/Server/Services/MarketingConversionService.ts +0 -10
- package/Server/Utils/Marketing/ConversionUploadProvider.ts +0 -215
- package/Server/Utils/Marketing/ConversionUploadProviders.ts +0 -22
- package/Server/Utils/Marketing/Providers/GoogleAds.ts +0 -333
- package/Server/Utils/Marketing/Providers/LinkedIn.ts +0 -185
- package/Server/Utils/Marketing/Providers/Meta.ts +0 -182
- package/Server/Utils/Marketing/Providers/MicrosoftAds.ts +0 -223
- package/Server/Utils/Marketing/Providers/Reddit.ts +0 -208
- package/Tests/Server/Utils/Marketing/AdUploadableConversionTypes.test.ts +0 -315
- package/Tests/Server/Utils/Marketing/ConversionUploadProvider.test.ts +0 -300
- package/Tests/Server/Utils/Marketing/GoogleAds.test.ts +0 -592
- package/Tests/Server/Utils/Marketing/LinkedIn.test.ts +0 -282
- package/Tests/Server/Utils/Marketing/Meta.test.ts +0 -304
- package/Tests/Server/Utils/Marketing/MicrosoftAds.test.ts +0 -263
- package/Tests/Server/Utils/Marketing/Reddit.test.ts +0 -259
- package/Types/Marketing/MarketingConversion.ts +0 -53
- package/build/dist/Models/DatabaseModels/MarketingConversion.js +0 -445
- package/build/dist/Models/DatabaseModels/MarketingConversion.js.map +0 -1
- package/build/dist/Server/Services/MarketingConversionService.js +0 -9
- package/build/dist/Server/Services/MarketingConversionService.js.map +0 -1
- package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js +0 -93
- package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js.map +0 -1
- package/build/dist/Server/Utils/Marketing/ConversionUploadProviders.js +0 -20
- package/build/dist/Server/Utils/Marketing/ConversionUploadProviders.js.map +0 -1
- package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js +0 -221
- package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js.map +0 -1
- package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js +0 -137
- package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js.map +0 -1
- package/build/dist/Server/Utils/Marketing/Providers/Meta.js +0 -133
- package/build/dist/Server/Utils/Marketing/Providers/Meta.js.map +0 -1
- package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js +0 -143
- package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js.map +0 -1
- package/build/dist/Server/Utils/Marketing/Providers/Reddit.js +0 -145
- package/build/dist/Server/Utils/Marketing/Providers/Reddit.js.map +0 -1
- package/build/dist/Types/Marketing/MarketingConversion.js +0 -52
- 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
|
|
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
|
|
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;
|