@oneuptime/common 12.0.14 → 12.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/DatabaseModels/GlobalOidc.ts +20 -0
- package/Models/DatabaseModels/GlobalSso.ts +20 -0
- package/Models/DatabaseModels/MarketingConversion.ts +19 -9
- package/Models/DatabaseModels/NetworkDeviceLinkRule.ts +46 -0
- package/Models/DatabaseModels/StatusPage.ts +7 -4
- package/Server/API/StatusPageAPI.ts +29 -4
- package/Server/EnvironmentConfig.ts +7 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787800000000-AddScopeToNetworkDeviceLinkRule.ts +19 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787900000000-AddRestrictToAttachedProjectsToGlobalSso.ts +40 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Middleware/SlackAuthorization.ts +43 -6
- package/Server/Middleware/UserAuthorization.ts +236 -26
- package/Server/Middleware/WhatsAppAuthorization.ts +43 -3
- package/Server/Services/GlobalOidcProjectService.ts +126 -1
- package/Server/Services/GlobalOidcService.ts +132 -0
- package/Server/Services/GlobalSsoProjectService.ts +126 -1
- package/Server/Services/GlobalSsoService.ts +132 -0
- package/Server/Services/UserEmailService.ts +4 -4
- package/Server/Utils/CronTab.ts +73 -0
- package/Server/Utils/GlobalSsoAuthorization.ts +175 -0
- package/Server/Utils/Marketing/ConversionUploadProvider.ts +35 -5
- package/Server/Utils/Marketing/Providers/GoogleAds.ts +1 -1
- package/Server/Utils/Marketing/Providers/LinkedIn.ts +1 -1
- package/Server/Utils/Marketing/Providers/Meta.ts +1 -1
- package/Server/Utils/Marketing/Providers/MicrosoftAds.ts +1 -1
- package/Server/Utils/Marketing/Providers/Reddit.ts +1 -1
- package/Server/Utils/Monitor/Criteria/APIRequestCriteria.ts +48 -33
- package/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.ts +35 -30
- package/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.ts +33 -28
- package/Server/Utils/Monitor/Criteria/EvaluateOverTime.ts +475 -47
- package/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.ts +35 -30
- package/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.ts +34 -28
- package/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.ts +33 -28
- package/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.ts +69 -40
- package/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.ts +35 -29
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +8 -0
- package/Server/Utils/Monitor/MonitorResource.ts +161 -21
- package/Server/Utils/StatusPageContentSecurityPolicy.ts +56 -0
- package/Server/Utils/StatusPageCustomizationAccess.ts +117 -0
- package/Tests/Server/API/StatusPageCustomizationOrigin.test.ts +348 -0
- package/Tests/Server/Infrastructure/Postgres/FixTotpOtpUrlAlgorithmMigration.test.ts +9 -59
- package/Tests/Server/Infrastructure/Postgres/SchemaMigrationsOrdering.test.ts +227 -0
- package/Tests/Server/Middleware/SlackAuthorization.test.ts +149 -0
- package/Tests/Server/Middleware/UserAuthorization.test.ts +27 -13
- package/Tests/Server/Middleware/UserAuthorizationGlobalSsoGovernance.test.ts +1455 -0
- package/Tests/Server/Middleware/UserAuthorizationSSOProvider.test.ts +91 -2
- package/Tests/Server/Middleware/WhatsAppAuthorization.test.ts +233 -0
- package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +21 -8
- package/Tests/Server/Services/GlobalSsoProviderAuthorization.test.ts +1242 -0
- package/Tests/Server/Services/MonitorResourceProbeAgreementHydration.test.ts +459 -0
- package/Tests/Server/Services/MonitorResourceProbeAgreementReuse.test.ts +10 -0
- package/Tests/Server/Types/AnalyticsDatabase/AggregateUtilBucketTimestamp.test.ts +416 -0
- package/Tests/Server/Types/Database/QueryHelperManyToManyAndEmptyValues.test.ts +551 -0
- package/Tests/Server/Types/Database/SelectUtil.test.ts +348 -0
- package/Tests/Server/Utils/CronTab.test.ts +116 -0
- package/Tests/Server/Utils/GlobalSSOToken.test.ts +581 -0
- package/Tests/Server/Utils/GlobalSsoAuthorization.test.ts +1401 -0
- package/Tests/Server/Utils/Marketing/AdUploadableConversionTypes.test.ts +254 -0
- package/Tests/Server/Utils/Marketing/ConversionUploadProvider.test.ts +1 -1
- package/Tests/Server/Utils/Monitor/Criteria/APIRequestCriteriaEvaluateOverTime.test.ts +508 -0
- package/Tests/Server/Utils/Monitor/Criteria/APIRequestCriteriaPortTimings.test.ts +30 -9
- package/Tests/Server/Utils/Monitor/Criteria/EvaluateOverTime.test.ts +987 -0
- package/Tests/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.test.ts +916 -0
- package/Tests/Server/Utils/Monitor/Criteria/ProfileMonitorCriteria.test.ts +382 -0
- package/Tests/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.test.ts +1219 -0
- package/Tests/Server/Utils/Monitor/MonitorResourceIngestedStepSelection.test.ts +86 -0
- package/Tests/Server/Utils/StatusPageContentSecurityPolicy.test.ts +136 -0
- package/Tests/Server/Utils/StatusPageCustomizationAccess.test.ts +223 -0
- package/Tests/Types/Monitor/MonitorStepConfigHelpers.test.ts +165 -0
- package/Tests/Types/NetworkDevice/NetworkDeviceLinkRuleScope.test.ts +215 -0
- package/Tests/UI/Components/Forms/FormSubmitAnalytics.test.tsx +206 -0
- package/Tests/UI/Utils/AIChatExport/ConversationMarkdown.test.ts +774 -0
- package/Tests/UI/Utils/AIChatExport/MarkdownBlocks.test.ts +791 -0
- package/Tests/UI/Utils/AIChatExport/MarkdownSafety.test.ts +544 -0
- package/Tests/Utils/Analytics.test.ts +187 -0
- package/Tests/Utils/Monitor/MonitorMetricType.test.ts +80 -0
- package/Tests/Utils/Monitor/NetworkDeviceLinkRuleUtil.test.ts +1291 -0
- package/Tests/Utils/NetworkDiscovery/UnclaimedScanDiagnosis.test.ts +214 -0
- package/Types/Marketing/MarketingConversion.ts +12 -0
- package/Types/Monitor/CriteriaFilter.ts +9 -0
- package/Types/NetworkDevice/NetworkDeviceLinkRuleScope.ts +51 -0
- package/UI/Components/FormModal/BasicFormModal.tsx +12 -0
- package/UI/Components/Forms/BasicForm.tsx +13 -1
- package/UI/Components/Forms/BasicModelForm.tsx +2 -1
- package/UI/Components/Forms/ModelForm.tsx +10 -1
- package/UI/Components/Forms/Utils/FormAnalyticsName.ts +39 -0
- package/UI/Components/ModelFormModal/ModelFormModal.tsx +6 -1
- package/Utils/Analytics.ts +55 -2
- package/Utils/Monitor/MonitorMetricType.ts +40 -0
- package/Utils/Monitor/NetworkDeviceLinkRuleUtil.ts +587 -37
- package/Utils/NetworkDiscovery/UnclaimedScanDiagnosis.ts +115 -0
- package/build/dist/Models/DatabaseModels/GlobalOidc.js +21 -0
- package/build/dist/Models/DatabaseModels/GlobalOidc.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalSso.js +21 -0
- package/build/dist/Models/DatabaseModels/GlobalSso.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MarketingConversion.js +20 -10
- package/build/dist/Models/DatabaseModels/MarketingConversion.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceLinkRule.js +47 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceLinkRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +4 -4
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +25 -4
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +6 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787800000000-AddScopeToNetworkDeviceLinkRule.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787800000000-AddScopeToNetworkDeviceLinkRule.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787900000000-AddRestrictToAttachedProjectsToGlobalSso.js +29 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787900000000-AddRestrictToAttachedProjectsToGlobalSso.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/SlackAuthorization.js +28 -4
- package/build/dist/Server/Middleware/SlackAuthorization.js.map +1 -1
- package/build/dist/Server/Middleware/UserAuthorization.js +197 -22
- package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
- package/build/dist/Server/Middleware/WhatsAppAuthorization.js +32 -2
- package/build/dist/Server/Middleware/WhatsAppAuthorization.js.map +1 -1
- package/build/dist/Server/Services/GlobalOidcProjectService.js +105 -0
- package/build/dist/Server/Services/GlobalOidcProjectService.js.map +1 -1
- package/build/dist/Server/Services/GlobalOidcService.js +127 -0
- package/build/dist/Server/Services/GlobalOidcService.js.map +1 -1
- package/build/dist/Server/Services/GlobalSsoProjectService.js +105 -0
- package/build/dist/Server/Services/GlobalSsoProjectService.js.map +1 -1
- package/build/dist/Server/Services/GlobalSsoService.js +127 -0
- package/build/dist/Server/Services/GlobalSsoService.js.map +1 -1
- package/build/dist/Server/Services/UserEmailService.js +4 -4
- package/build/dist/Server/Utils/CronTab.js +66 -0
- package/build/dist/Server/Utils/CronTab.js.map +1 -1
- package/build/dist/Server/Utils/GlobalSsoAuthorization.js +112 -0
- package/build/dist/Server/Utils/GlobalSsoAuthorization.js.map +1 -0
- package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js +17 -1
- package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/Meta.js +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/Meta.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/Reddit.js +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/Reddit.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js +37 -35
- package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.js +21 -25
- package/build/dist/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js +20 -22
- package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js +370 -42
- package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.js +21 -25
- package/build/dist/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js +21 -25
- package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js +21 -25
- package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.js +64 -52
- package/build/dist/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js +25 -29
- package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +8 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +123 -23
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/StatusPageContentSecurityPolicy.js +39 -0
- package/build/dist/Server/Utils/StatusPageContentSecurityPolicy.js.map +1 -0
- package/build/dist/Server/Utils/StatusPageCustomizationAccess.js +85 -0
- package/build/dist/Server/Utils/StatusPageCustomizationAccess.js.map +1 -0
- package/build/dist/Types/Marketing/MarketingConversion.js +10 -0
- package/build/dist/Types/Marketing/MarketingConversion.js.map +1 -1
- package/build/dist/Types/Monitor/CriteriaFilter.js +1 -0
- package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
- package/build/dist/Types/NetworkDevice/NetworkDeviceLinkRuleScope.js +46 -0
- package/build/dist/Types/NetworkDevice/NetworkDeviceLinkRuleScope.js.map +1 -0
- package/build/dist/UI/Components/FormModal/BasicFormModal.js +2 -1
- package/build/dist/UI/Components/FormModal/BasicFormModal.js.map +1 -1
- package/build/dist/UI/Components/Forms/BasicForm.js +10 -1
- package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
- package/build/dist/UI/Components/Forms/BasicModelForm.js +2 -1
- package/build/dist/UI/Components/Forms/BasicModelForm.js.map +1 -1
- package/build/dist/UI/Components/Forms/ModelForm.js +4 -1
- package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
- package/build/dist/UI/Components/Forms/Utils/FormAnalyticsName.js +33 -0
- package/build/dist/UI/Components/Forms/Utils/FormAnalyticsName.js.map +1 -0
- package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js +2 -1
- package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
- package/build/dist/Utils/Analytics.js +50 -2
- package/build/dist/Utils/Analytics.js.map +1 -1
- package/build/dist/Utils/Monitor/MonitorMetricType.js +37 -0
- package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkDeviceLinkRuleUtil.js +318 -37
- package/build/dist/Utils/Monitor/NetworkDeviceLinkRuleUtil.js.map +1 -1
- package/build/dist/Utils/NetworkDiscovery/UnclaimedScanDiagnosis.js +87 -0
- package/build/dist/Utils/NetworkDiscovery/UnclaimedScanDiagnosis.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import { AggregateUtil } from "../../../../Server/Types/AnalyticsDatabase/AggregateBy";
|
|
2
|
+
import AggregationInterval from "../../../../Types/BaseDatabase/AggregationInterval";
|
|
3
|
+
import { describe, expect, it } from "@jest/globals";
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
* AggregateUtil is the server's single source of truth for turning a query
|
|
7
|
+
* window into an aggregation bucket size and for compiling that bucket into a
|
|
8
|
+
* ClickHouse timestamp expression. The tests below exercise the real branches:
|
|
9
|
+
*
|
|
10
|
+
* - getAggregationInterval: window-derived tier ladder, its <=-inclusive
|
|
11
|
+
* boundaries, and the explicit-override precedence (including Total, which
|
|
12
|
+
* the window picker itself never returns, and invalid overrides that fall
|
|
13
|
+
* through to the window).
|
|
14
|
+
* - isTotalAggregation: the Total-vs-everything-else predicate.
|
|
15
|
+
* - buildBucketTimestampExpression: the sub-hour toStartOfInterval tiers vs
|
|
16
|
+
* the lowercased date_trunc default, plus raw expression interpolation.
|
|
17
|
+
* - buildBucketTimestampSelect: the Total min() shape vs the aliased bucket
|
|
18
|
+
* expression it delegates to.
|
|
19
|
+
*
|
|
20
|
+
* Everything is deterministic: getAggregationInterval only reads the numeric
|
|
21
|
+
* millisecond diff between the two dates, so windows are built from a fixed
|
|
22
|
+
* epoch anchor plus an exact millisecond span.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/*
|
|
26
|
+
* Fixed anchor for every window. getAggregationInterval depends only on
|
|
27
|
+
* endDate - startDate, so the absolute instant is irrelevant; pinning it makes
|
|
28
|
+
* the boundary arithmetic obvious.
|
|
29
|
+
*/
|
|
30
|
+
const ANCHOR_MS: number = 0;
|
|
31
|
+
|
|
32
|
+
/*
|
|
33
|
+
* Tier upper bounds, copied from the implementation so a change in the ladder
|
|
34
|
+
* is caught here rather than silently re-tiering every chart. Each value is the
|
|
35
|
+
* largest diff (inclusive) that still resolves to the tier it labels.
|
|
36
|
+
*/
|
|
37
|
+
const THREE_HOURS_MS: number = 1000 * 60 * 60 * 3;
|
|
38
|
+
const TWELVE_HOURS_MS: number = 1000 * 60 * 60 * 12;
|
|
39
|
+
const TWENTY_FOUR_HOURS_MS: number = 1000 * 60 * 60 * 24;
|
|
40
|
+
const THREE_DAYS_MS: number = 1000 * 60 * 60 * 24 * 3;
|
|
41
|
+
const SEVEN_DAYS_MS: number = 1000 * 60 * 60 * 24 * 7;
|
|
42
|
+
const SIX_WEEKS_MS: number = 1000 * 60 * 60 * 24 * 7 * 6;
|
|
43
|
+
const SIX_MONTHS_MS: number = 1000 * 60 * 60 * 24 * 30 * 6;
|
|
44
|
+
const SIX_YEARS_MS: number = 1000 * 60 * 60 * 24 * 365 * 6;
|
|
45
|
+
|
|
46
|
+
function intervalForDiff(
|
|
47
|
+
diffMs: number,
|
|
48
|
+
aggregationInterval?: AggregationInterval,
|
|
49
|
+
): AggregationInterval {
|
|
50
|
+
const startDate: Date = new Date(ANCHOR_MS);
|
|
51
|
+
const endDate: Date = new Date(ANCHOR_MS + diffMs);
|
|
52
|
+
return AggregateUtil.getAggregationInterval({
|
|
53
|
+
startDate,
|
|
54
|
+
endDate,
|
|
55
|
+
aggregationInterval,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
describe("AggregateUtil.getAggregationInterval window-derived ladder", () => {
|
|
60
|
+
it("returns Minute for a zero-length window", () => {
|
|
61
|
+
const result: AggregationInterval = intervalForDiff(0);
|
|
62
|
+
expect(result).toBe(AggregationInterval.Minute);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("returns Minute for a negative (end-before-start) window", () => {
|
|
66
|
+
/*
|
|
67
|
+
* A malformed window makes the diff negative, which is still <= every
|
|
68
|
+
* upper bound, so it collapses to the finest tier rather than throwing.
|
|
69
|
+
*/
|
|
70
|
+
const result: AggregationInterval = intervalForDiff(-1000 * 60 * 60);
|
|
71
|
+
expect(result).toBe(AggregationInterval.Minute);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("never returns Total from the window derivation (huge window -> Year)", () => {
|
|
75
|
+
const result: AggregationInterval = intervalForDiff(SIX_YEARS_MS + 1);
|
|
76
|
+
expect(result).toBe(AggregationInterval.Year);
|
|
77
|
+
expect(result).not.toBe(AggregationInterval.Total);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
type LadderCase = {
|
|
81
|
+
label: string;
|
|
82
|
+
diffMs: number;
|
|
83
|
+
expected: AggregationInterval;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/*
|
|
87
|
+
* Each tier is probed at three points: comfortably inside the tier, exactly
|
|
88
|
+
* on its inclusive upper bound (must stay in the tier), and one millisecond
|
|
89
|
+
* past it (must roll to the next coarser tier). The last pair proves the
|
|
90
|
+
* ladder uses <= and not <.
|
|
91
|
+
*/
|
|
92
|
+
const ladderCases: Array<LadderCase> = [
|
|
93
|
+
{
|
|
94
|
+
label: "1 hour -> Minute",
|
|
95
|
+
diffMs: 1000 * 60 * 60,
|
|
96
|
+
expected: AggregationInterval.Minute,
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
label: "exactly 3 hours -> Minute (inclusive bound)",
|
|
100
|
+
diffMs: THREE_HOURS_MS,
|
|
101
|
+
expected: AggregationInterval.Minute,
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
label: "3 hours + 1ms -> FiveMinutes",
|
|
105
|
+
diffMs: THREE_HOURS_MS + 1,
|
|
106
|
+
expected: AggregationInterval.FiveMinutes,
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
label: "exactly 12 hours -> FiveMinutes (inclusive bound)",
|
|
110
|
+
diffMs: TWELVE_HOURS_MS,
|
|
111
|
+
expected: AggregationInterval.FiveMinutes,
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
label: "12 hours + 1ms -> FifteenMinutes",
|
|
115
|
+
diffMs: TWELVE_HOURS_MS + 1,
|
|
116
|
+
expected: AggregationInterval.FifteenMinutes,
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
label: "exactly 24 hours -> FifteenMinutes (inclusive bound)",
|
|
120
|
+
diffMs: TWENTY_FOUR_HOURS_MS,
|
|
121
|
+
expected: AggregationInterval.FifteenMinutes,
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
label: "24 hours + 1ms -> ThirtyMinutes",
|
|
125
|
+
diffMs: TWENTY_FOUR_HOURS_MS + 1,
|
|
126
|
+
expected: AggregationInterval.ThirtyMinutes,
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
label: "exactly 3 days -> ThirtyMinutes (inclusive bound)",
|
|
130
|
+
diffMs: THREE_DAYS_MS,
|
|
131
|
+
expected: AggregationInterval.ThirtyMinutes,
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
label: "3 days + 1ms -> Hour",
|
|
135
|
+
diffMs: THREE_DAYS_MS + 1,
|
|
136
|
+
expected: AggregationInterval.Hour,
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
label: "exactly 7 days -> Hour (inclusive bound)",
|
|
140
|
+
diffMs: SEVEN_DAYS_MS,
|
|
141
|
+
expected: AggregationInterval.Hour,
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
label: "7 days + 1ms -> Day",
|
|
145
|
+
diffMs: SEVEN_DAYS_MS + 1,
|
|
146
|
+
expected: AggregationInterval.Day,
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
label: "exactly 6 weeks -> Day (inclusive bound)",
|
|
150
|
+
diffMs: SIX_WEEKS_MS,
|
|
151
|
+
expected: AggregationInterval.Day,
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
label: "6 weeks + 1ms -> Week",
|
|
155
|
+
diffMs: SIX_WEEKS_MS + 1,
|
|
156
|
+
expected: AggregationInterval.Week,
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
label: "exactly 6 months -> Week (inclusive bound)",
|
|
160
|
+
diffMs: SIX_MONTHS_MS,
|
|
161
|
+
expected: AggregationInterval.Week,
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
label: "6 months + 1ms -> Month",
|
|
165
|
+
diffMs: SIX_MONTHS_MS + 1,
|
|
166
|
+
expected: AggregationInterval.Month,
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
label: "exactly 6 years -> Month (inclusive bound)",
|
|
170
|
+
diffMs: SIX_YEARS_MS,
|
|
171
|
+
expected: AggregationInterval.Month,
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
label: "6 years + 1ms -> Year",
|
|
175
|
+
diffMs: SIX_YEARS_MS + 1,
|
|
176
|
+
expected: AggregationInterval.Year,
|
|
177
|
+
},
|
|
178
|
+
];
|
|
179
|
+
|
|
180
|
+
it.each(ladderCases)(
|
|
181
|
+
"resolves $label",
|
|
182
|
+
({ diffMs, expected }: LadderCase) => {
|
|
183
|
+
const result: AggregationInterval = intervalForDiff(diffMs);
|
|
184
|
+
expect(result).toBe(expected);
|
|
185
|
+
},
|
|
186
|
+
);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe("AggregateUtil.getAggregationInterval explicit override precedence", () => {
|
|
190
|
+
it("returns a valid override verbatim, ignoring the (finer) window tier", () => {
|
|
191
|
+
/*
|
|
192
|
+
* A 1-hour window would derive Minute, but the override pins Year, proving
|
|
193
|
+
* the override short-circuits the window derivation entirely.
|
|
194
|
+
*/
|
|
195
|
+
const result: AggregationInterval = intervalForDiff(
|
|
196
|
+
1000 * 60 * 60,
|
|
197
|
+
AggregationInterval.Year,
|
|
198
|
+
);
|
|
199
|
+
expect(result).toBe(AggregationInterval.Year);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("returns a valid override verbatim, ignoring the (coarser) window tier", () => {
|
|
203
|
+
const result: AggregationInterval = intervalForDiff(
|
|
204
|
+
SIX_YEARS_MS + 1,
|
|
205
|
+
AggregationInterval.Minute,
|
|
206
|
+
);
|
|
207
|
+
expect(result).toBe(AggregationInterval.Minute);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("honours a Total override even though the window picker never yields Total", () => {
|
|
211
|
+
const result: AggregationInterval = intervalForDiff(
|
|
212
|
+
1000 * 60 * 60,
|
|
213
|
+
AggregationInterval.Total,
|
|
214
|
+
);
|
|
215
|
+
expect(result).toBe(AggregationInterval.Total);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("falls through to the window tier when the override is not a real enum value", () => {
|
|
219
|
+
/*
|
|
220
|
+
* Object.values(AggregationInterval).includes(...) rejects an unknown
|
|
221
|
+
* string, so the derivation runs and a 1-hour window resolves to Minute.
|
|
222
|
+
*/
|
|
223
|
+
const bogusOverride: AggregationInterval =
|
|
224
|
+
"NotARealInterval" as AggregationInterval;
|
|
225
|
+
const result: AggregationInterval = intervalForDiff(
|
|
226
|
+
1000 * 60 * 60,
|
|
227
|
+
bogusOverride,
|
|
228
|
+
);
|
|
229
|
+
expect(result).toBe(AggregationInterval.Minute);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("preserves every enum value when passed as an explicit override", () => {
|
|
233
|
+
const allIntervals: Array<AggregationInterval> =
|
|
234
|
+
Object.values(AggregationInterval);
|
|
235
|
+
for (const interval of allIntervals) {
|
|
236
|
+
const result: AggregationInterval = intervalForDiff(
|
|
237
|
+
1000 * 60 * 60,
|
|
238
|
+
interval,
|
|
239
|
+
);
|
|
240
|
+
expect(result).toBe(interval);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
describe("AggregateUtil.isTotalAggregation", () => {
|
|
246
|
+
it("is true only for Total", () => {
|
|
247
|
+
expect(AggregateUtil.isTotalAggregation(AggregationInterval.Total)).toBe(
|
|
248
|
+
true,
|
|
249
|
+
);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it("is false for every non-Total interval", () => {
|
|
253
|
+
const nonTotalIntervals: Array<AggregationInterval> = Object.values(
|
|
254
|
+
AggregationInterval,
|
|
255
|
+
).filter((interval: AggregationInterval) => {
|
|
256
|
+
return interval !== AggregationInterval.Total;
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
for (const interval of nonTotalIntervals) {
|
|
260
|
+
expect(AggregateUtil.isTotalAggregation(interval)).toBe(false);
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
describe("AggregateUtil.buildBucketTimestampExpression", () => {
|
|
266
|
+
const COL: string = "createdAt";
|
|
267
|
+
|
|
268
|
+
it("compiles FiveMinutes to a 5 MINUTE toStartOfInterval", () => {
|
|
269
|
+
const result: string = AggregateUtil.buildBucketTimestampExpression(
|
|
270
|
+
AggregationInterval.FiveMinutes,
|
|
271
|
+
COL,
|
|
272
|
+
);
|
|
273
|
+
expect(result).toBe(`toStartOfInterval(${COL}, INTERVAL 5 MINUTE)`);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it("compiles FifteenMinutes to a 15 MINUTE toStartOfInterval", () => {
|
|
277
|
+
const result: string = AggregateUtil.buildBucketTimestampExpression(
|
|
278
|
+
AggregationInterval.FifteenMinutes,
|
|
279
|
+
COL,
|
|
280
|
+
);
|
|
281
|
+
expect(result).toBe(`toStartOfInterval(${COL}, INTERVAL 15 MINUTE)`);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it("compiles ThirtyMinutes to a 30 MINUTE toStartOfInterval", () => {
|
|
285
|
+
const result: string = AggregateUtil.buildBucketTimestampExpression(
|
|
286
|
+
AggregationInterval.ThirtyMinutes,
|
|
287
|
+
COL,
|
|
288
|
+
);
|
|
289
|
+
expect(result).toBe(`toStartOfInterval(${COL}, INTERVAL 30 MINUTE)`);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
type DefaultCase = {
|
|
293
|
+
interval: AggregationInterval;
|
|
294
|
+
unit: string;
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
/*
|
|
298
|
+
* The default branch lowercases the enum value into both the date_trunc unit
|
|
299
|
+
* and the INTERVAL 1 <unit> guard. These calendar units happen to be valid
|
|
300
|
+
* ClickHouse units, which is exactly why they route through the default arm.
|
|
301
|
+
*/
|
|
302
|
+
const defaultCases: Array<DefaultCase> = [
|
|
303
|
+
{ interval: AggregationInterval.Minute, unit: "minute" },
|
|
304
|
+
{ interval: AggregationInterval.Hour, unit: "hour" },
|
|
305
|
+
{ interval: AggregationInterval.Day, unit: "day" },
|
|
306
|
+
{ interval: AggregationInterval.Week, unit: "week" },
|
|
307
|
+
{ interval: AggregationInterval.Month, unit: "month" },
|
|
308
|
+
{ interval: AggregationInterval.Year, unit: "year" },
|
|
309
|
+
];
|
|
310
|
+
|
|
311
|
+
it.each(defaultCases)(
|
|
312
|
+
"compiles $interval via the lowercased date_trunc default",
|
|
313
|
+
({ interval, unit }: DefaultCase) => {
|
|
314
|
+
const result: string = AggregateUtil.buildBucketTimestampExpression(
|
|
315
|
+
interval,
|
|
316
|
+
COL,
|
|
317
|
+
);
|
|
318
|
+
expect(result).toBe(
|
|
319
|
+
`date_trunc('${unit}', toStartOfInterval(${COL}, INTERVAL 1 ${unit}))`,
|
|
320
|
+
);
|
|
321
|
+
},
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
it("routes Total through the default branch as a lowercased unit in isolation", () => {
|
|
325
|
+
/*
|
|
326
|
+
* buildBucketTimestampSelect short-circuits Total before ever calling this
|
|
327
|
+
* function, but called directly Total falls into the default arm and is
|
|
328
|
+
* lowercased like any other value. This pins that isolated behavior so a
|
|
329
|
+
* future refactor cannot change it unnoticed.
|
|
330
|
+
*/
|
|
331
|
+
const result: string = AggregateUtil.buildBucketTimestampExpression(
|
|
332
|
+
AggregationInterval.Total,
|
|
333
|
+
COL,
|
|
334
|
+
);
|
|
335
|
+
expect(result).toBe(
|
|
336
|
+
`date_trunc('total', toStartOfInterval(${COL}, INTERVAL 1 total))`,
|
|
337
|
+
);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it("interpolates a compound builder expression verbatim in the sub-hour branch", () => {
|
|
341
|
+
const expression: string = "toDateTime(t.created_at)";
|
|
342
|
+
const result: string = AggregateUtil.buildBucketTimestampExpression(
|
|
343
|
+
AggregationInterval.FiveMinutes,
|
|
344
|
+
expression,
|
|
345
|
+
);
|
|
346
|
+
expect(result).toBe(`toStartOfInterval(${expression}, INTERVAL 5 MINUTE)`);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it("interpolates a compound builder expression verbatim in the default branch", () => {
|
|
350
|
+
const expression: string = "toDateTime(t.created_at)";
|
|
351
|
+
const result: string = AggregateUtil.buildBucketTimestampExpression(
|
|
352
|
+
AggregationInterval.Hour,
|
|
353
|
+
expression,
|
|
354
|
+
);
|
|
355
|
+
expect(result).toBe(
|
|
356
|
+
`date_trunc('hour', toStartOfInterval(${expression}, INTERVAL 1 hour))`,
|
|
357
|
+
);
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
describe("AggregateUtil.buildBucketTimestampSelect", () => {
|
|
362
|
+
const COL: string = "createdAt";
|
|
363
|
+
|
|
364
|
+
it("emits a self-aliased min() for Total (whole-window bucket)", () => {
|
|
365
|
+
const result: string = AggregateUtil.buildBucketTimestampSelect(
|
|
366
|
+
AggregationInterval.Total,
|
|
367
|
+
COL,
|
|
368
|
+
);
|
|
369
|
+
expect(result).toBe(`min(${COL}) as ${COL}`);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it("aliases the bucket expression for a sub-hour interval", () => {
|
|
373
|
+
const result: string = AggregateUtil.buildBucketTimestampSelect(
|
|
374
|
+
AggregationInterval.FiveMinutes,
|
|
375
|
+
COL,
|
|
376
|
+
);
|
|
377
|
+
expect(result).toBe(
|
|
378
|
+
`toStartOfInterval(${COL}, INTERVAL 5 MINUTE) as ${COL}`,
|
|
379
|
+
);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it("aliases the date_trunc expression for a calendar interval", () => {
|
|
383
|
+
const result: string = AggregateUtil.buildBucketTimestampSelect(
|
|
384
|
+
AggregationInterval.Minute,
|
|
385
|
+
COL,
|
|
386
|
+
);
|
|
387
|
+
expect(result).toBe(
|
|
388
|
+
`date_trunc('minute', toStartOfInterval(${COL}, INTERVAL 1 minute)) as ${COL}`,
|
|
389
|
+
);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it("delegates non-Total intervals to buildBucketTimestampExpression plus an alias", () => {
|
|
393
|
+
/*
|
|
394
|
+
* The select fragment for any non-Total interval must be exactly the
|
|
395
|
+
* bucket expression suffixed with `as <col>`; anything else would mean the
|
|
396
|
+
* two helpers had drifted apart.
|
|
397
|
+
*/
|
|
398
|
+
const nonTotalIntervals: Array<AggregationInterval> = Object.values(
|
|
399
|
+
AggregationInterval,
|
|
400
|
+
).filter((interval: AggregationInterval) => {
|
|
401
|
+
return interval !== AggregationInterval.Total;
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
for (const interval of nonTotalIntervals) {
|
|
405
|
+
const expression: string = AggregateUtil.buildBucketTimestampExpression(
|
|
406
|
+
interval,
|
|
407
|
+
COL,
|
|
408
|
+
);
|
|
409
|
+
const select: string = AggregateUtil.buildBucketTimestampSelect(
|
|
410
|
+
interval,
|
|
411
|
+
COL,
|
|
412
|
+
);
|
|
413
|
+
expect(select).toBe(`${expression} as ${COL}`);
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
});
|