@oneuptime/common 12.0.27 → 12.0.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/AnalyticsModels/RumSessionChunk.ts +52 -0
- package/Models/DatabaseModels/CloudResource.ts +17 -1
- package/Models/DatabaseModels/DashboardDomain.ts +43 -0
- package/Models/DatabaseModels/MonitorTemplate.ts +15 -3
- package/Models/DatabaseModels/RumApplication.ts +35 -2
- package/Models/DatabaseModels/StatusPageDomain.ts +43 -0
- package/Server/API/DashboardDomainAPI.ts +74 -0
- package/Server/API/MicrosoftTeamsAPI.ts +81 -180
- package/Server/API/StatusPageDomainAPI.ts +74 -0
- package/Server/API/TelemetryAPI.ts +104 -15
- package/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.ts +43 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.ts +40 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Middleware/ProjectAuthorization.ts +6 -1
- package/Server/Services/DashboardDomainService.ts +131 -0
- package/Server/Services/LogPipelineProcessorService.ts +88 -0
- package/Server/Services/MonitorTemplateService.ts +70 -11
- package/Server/Services/StatusPageDomainService.ts +163 -3
- package/Server/Services/StatusPagePrivateUserSessionService.ts +197 -56
- package/Server/Types/Domain.ts +17 -0
- package/Server/Utils/APIKey/AccessPermission.ts +24 -0
- package/Server/Utils/Greenlock/Greenlock.ts +156 -58
- package/Server/Utils/LogPipelineProcessorValidation.ts +112 -0
- package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.ts +1094 -45
- package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.ts +153 -17
- package/Server/Utils/SessionReplay/SessionReplayGateCache.ts +12 -5
- package/Server/Utils/SessionReplay/SessionReplayIdentity.ts +102 -0
- package/Server/Utils/SessionReplay/SessionReplayReadService.ts +11 -2
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.ts +23 -11
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.ts +0 -170
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.ts +158 -0
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +23 -11
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +35 -13
- package/Tests/App/Dashboard/MonitorTemplateColumnAndFacet.test.tsx +505 -0
- package/Tests/App/StatusPage/StatusPageLoginCodeBootstrap.test.ts +106 -0
- package/Tests/App/StatusPage/StatusPageLoginCodeUtil.test.ts +84 -0
- package/Tests/App/StatusPage/StatusPageRedirectSafety.test.ts +119 -0
- package/Tests/Models/DatabaseModels/SessionReplayModels.test.ts +81 -0
- package/Tests/Models/MonitorTemplateMonitorNameColumn.test.ts +258 -0
- package/Tests/Server/API/CustomDomainReissueSslAPI.test.ts +469 -0
- package/Tests/Server/API/MicrosoftTeamsBotMessagesEndpoint.test.ts +729 -0
- package/Tests/Server/API/MicrosoftTeamsBotTestEndpoint.test.ts +26 -0
- package/Tests/Server/API/MicrosoftTeamsLegacyWebhookRemoval.test.ts +402 -0
- package/Tests/Server/API/SessionReplayAPI.test.ts +232 -0
- package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +4 -0
- package/Tests/Server/Services/AddCertificateReissueRequestedAtToDomainsMigration.test.ts +287 -0
- package/Tests/Server/Services/CustomDomainCertificateReissue.test.ts +477 -0
- package/Tests/Server/Services/LogPipelineProcessorSaveValidation.test.ts +249 -0
- package/Tests/Server/Services/MonitorTemplateServiceBulkSync.test.ts +549 -0
- package/Tests/Server/Services/MonitorTemplateServiceNetworkDeviceSync.test.ts +16 -3
- package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +57 -0
- package/Tests/Server/Services/SecurityEventLastErrorColumnWidth.test.ts +151 -15
- package/Tests/Server/Services/StatusPagePrivateUserSessionService.test.ts +587 -0
- package/Tests/Server/Types/Database/Permissions/ApiKeyFileAccess.test.ts +286 -0
- package/Tests/Server/Types/Database/Permissions/MonitorTemplateColumnAccess.test.ts +328 -0
- package/Tests/Server/Types/Database/QueryHelperFindWithSameTextAndIsNull.test.ts +83 -0
- package/Tests/Server/Utils/AI/SRE/ConfidenceSignal.test.ts +377 -0
- package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +16 -1
- package/Tests/Server/Utils/Greenlock/CertificateRenewalSchedule.test.ts +250 -0
- package/Tests/Server/Utils/LogPipelineProcessorValidation.test.ts +198 -0
- package/Tests/Server/Utils/SecurityEvent/ConnectorErrorMessage.test.ts +48 -9
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsAuth.test.ts +726 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsClient.test.ts +74 -11
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsErrorHandling.test.ts +1107 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPoller.test.ts +20 -3
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerFailureTaxonomy.test.ts +51 -11
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerHardening.test.ts +1246 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsRequestContract.test.ts +961 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsResponseParsing.test.ts +653 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsActionAuthorization.test.ts +880 -0
- package/Tests/Types/API/HTTPResponse.test.ts +175 -0
- package/Tests/Types/API/Route.test.ts +36 -4
- package/Tests/Types/Database/DatabaseProperty.test.ts +171 -0
- package/Tests/Types/DateLocalShortDateTimeString.test.ts +518 -0
- package/Tests/Types/DateLocalTimeString.test.ts +259 -0
- package/Tests/Types/DateUserPrefers12HourFormat.test.ts +316 -0
- package/Tests/UI/Components/LogsHistogram.test.tsx +41 -2
- package/Tests/UI/Components/LogsViewerEmptyStateTimeRange.test.tsx +212 -0
- package/Tests/UI/Components/TelemetryChartClockFormat.test.tsx +359 -0
- package/Tests/UI/Components/TimeRangePickerClockFormat.test.tsx +422 -0
- package/Tests/UI/Components/TimeRangePickerDropdown.test.tsx +146 -8
- package/Tests/UI/MicrosoftTeams/MicrosoftTeamsMessagingEndpointGuidance.test.tsx +520 -0
- package/Tests/UI/Utils/Navigation.test.ts +82 -0
- package/Tests/Utils/Array.test.ts +100 -0
- package/Tests/Utils/CertificateReissue.test.ts +225 -0
- package/Tests/Utils/Grok/Grok.test.ts +492 -0
- package/Tests/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.test.ts +181 -13
- package/Types/API/Route.ts +20 -5
- package/Types/Date.ts +143 -13
- package/Types/Rum/SessionReplay.ts +76 -1
- package/UI/Components/Date/TimeRangePickerDropdown.tsx +9 -5
- package/UI/Components/LogsViewer/LogsViewer.tsx +12 -7
- package/UI/Components/LogsViewer/components/HistogramTooltip.tsx +18 -17
- package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +21 -28
- package/UI/Components/LogsViewer/components/LogsHistogram.tsx +2 -4
- package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +2 -4
- package/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.tsx +17 -16
- package/UI/Utils/Navigation.ts +42 -0
- package/Utils/Array.ts +37 -0
- package/Utils/CertificateReissue.ts +128 -0
- package/Utils/Grok/Grok.ts +461 -0
- package/Utils/Grok/GrokPatterns.ts +118 -0
- package/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.ts +58 -6
- package/build/dist/Models/AnalyticsModels/RumSessionChunk.js +48 -0
- package/build/dist/Models/AnalyticsModels/RumSessionChunk.js.map +1 -1
- package/build/dist/Models/DatabaseModels/CloudResource.js +19 -2
- package/build/dist/Models/DatabaseModels/CloudResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DashboardDomain.js +44 -0
- package/build/dist/Models/DatabaseModels/DashboardDomain.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorTemplate.js +17 -4
- package/build/dist/Models/DatabaseModels/MonitorTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RumApplication.js +37 -3
- package/build/dist/Models/DatabaseModels/RumApplication.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageDomain.js +44 -0
- package/build/dist/Models/DatabaseModels/StatusPageDomain.js.map +1 -1
- package/build/dist/Server/API/DashboardDomainAPI.js +43 -1
- package/build/dist/Server/API/DashboardDomainAPI.js.map +1 -1
- package/build/dist/Server/API/MicrosoftTeamsAPI.js +74 -118
- package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
- package/build/dist/Server/API/StatusPageDomainAPI.js +43 -1
- package/build/dist/Server/API/StatusPageDomainAPI.js.map +1 -1
- package/build/dist/Server/API/TelemetryAPI.js +67 -14
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.js +34 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.js +28 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.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/ProjectAuthorization.js +6 -1
- package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
- package/build/dist/Server/Services/DashboardDomainService.js +104 -0
- package/build/dist/Server/Services/DashboardDomainService.js.map +1 -1
- package/build/dist/Server/Services/LogPipelineProcessorService.js +65 -0
- package/build/dist/Server/Services/LogPipelineProcessorService.js.map +1 -1
- package/build/dist/Server/Services/MonitorTemplateService.js +57 -11
- package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageDomainService.js +132 -3
- package/build/dist/Server/Services/StatusPageDomainService.js.map +1 -1
- package/build/dist/Server/Services/StatusPagePrivateUserSessionService.js +112 -32
- package/build/dist/Server/Services/StatusPagePrivateUserSessionService.js.map +1 -1
- package/build/dist/Server/Types/Domain.js +16 -0
- package/build/dist/Server/Types/Domain.js.map +1 -1
- package/build/dist/Server/Utils/APIKey/AccessPermission.js +24 -0
- package/build/dist/Server/Utils/APIKey/AccessPermission.js.map +1 -1
- package/build/dist/Server/Utils/Greenlock/Greenlock.js +110 -35
- package/build/dist/Server/Utils/Greenlock/Greenlock.js.map +1 -1
- package/build/dist/Server/Utils/LogPipelineProcessorValidation.js +77 -0
- package/build/dist/Server/Utils/LogPipelineProcessorValidation.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js +777 -36
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js.map +1 -1
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js +108 -17
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js.map +1 -1
- package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js.map +1 -1
- package/build/dist/Server/Utils/SessionReplay/SessionReplayIdentity.js +59 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayIdentity.js.map +1 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js +9 -2
- package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js +17 -6
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js +0 -159
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.js +128 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +17 -6
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +25 -13
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/API/Route.js +16 -5
- package/build/dist/Types/API/Route.js.map +1 -1
- package/build/dist/Types/Date.js +115 -13
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/Rum/SessionReplay.js +38 -0
- package/build/dist/Types/Rum/SessionReplay.js.map +1 -1
- package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js +9 -5
- package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +12 -7
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/HistogramTooltip.js +15 -14
- package/build/dist/UI/Components/LogsViewer/components/HistogramTooltip.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +17 -22
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +2 -4
- package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +2 -4
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js +15 -14
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js.map +1 -1
- package/build/dist/UI/Utils/Navigation.js +30 -0
- package/build/dist/UI/Utils/Navigation.js.map +1 -1
- package/build/dist/Utils/Array.js +25 -0
- package/build/dist/Utils/Array.js.map +1 -1
- package/build/dist/Utils/CertificateReissue.js +97 -0
- package/build/dist/Utils/CertificateReissue.js.map +1 -0
- package/build/dist/Utils/Grok/Grok.js +324 -0
- package/build/dist/Utils/Grok/Grok.js.map +1 -0
- package/build/dist/Utils/Grok/GrokPatterns.js +112 -0
- package/build/dist/Utils/Grok/GrokPatterns.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js +63 -3
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js.map +1 -1
- package/package.json +1 -1
- package/UI/Utils/JsonWebToken.ts +0 -14
- package/build/dist/UI/Utils/JsonWebToken.js +0 -10
- package/build/dist/UI/Utils/JsonWebToken.js.map +0 -1
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/** @timezone America/Los_Angeles */
|
|
2
|
+
/**
|
|
3
|
+
* The clock reading behind every timestamp on the Logs / Traces / Metrics
|
|
4
|
+
* explorers - histogram x-axis ticks, histogram tooltips, span rows.
|
|
5
|
+
*
|
|
6
|
+
* Those sites used to call `date.toLocaleTimeString([], { hour12: false })`
|
|
7
|
+
* directly, which pinned them to a 24-hour clock and to the zone the browser
|
|
8
|
+
* process happens to run in. So a user whose computer keeps an AM/PM clock
|
|
9
|
+
* read "14:30" off a chart, and a user who had set a timezone in User
|
|
10
|
+
* Settings read a chart that disagreed with the log rows underneath it - in
|
|
11
|
+
* one Logs explorer the table's Time column and the histogram directly above
|
|
12
|
+
* it were formatted by two different rules.
|
|
13
|
+
*
|
|
14
|
+
* The docblock above pins the *browser* zone to Los Angeles and every case
|
|
15
|
+
* below configures a different zone, so a helper that quietly fell back to the
|
|
16
|
+
* process zone fails rather than passing by coincidence.
|
|
17
|
+
*/
|
|
18
|
+
import OneUptimeDate from "../../Types/Date";
|
|
19
|
+
import Timezone from "../../Types/Timezone";
|
|
20
|
+
import { afterEach, describe, expect, it, jest } from "@jest/globals";
|
|
21
|
+
|
|
22
|
+
const UTC: Timezone = Timezone.UTC;
|
|
23
|
+
const NEW_YORK: Timezone = Timezone.AmericaNew_York;
|
|
24
|
+
const KOLKATA: Timezone = Timezone.AsiaKolkata;
|
|
25
|
+
|
|
26
|
+
/** 14:30 UTC - an afternoon, so a 12-hour clock has real work to do. */
|
|
27
|
+
const AFTERNOON: Date = new Date("2024-03-01T14:30:45.000Z");
|
|
28
|
+
|
|
29
|
+
type Options = {
|
|
30
|
+
includeMinutes?: boolean;
|
|
31
|
+
includeSeconds?: boolean;
|
|
32
|
+
use12HourFormat?: boolean | undefined;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function format(date: Date, options?: Options): string {
|
|
36
|
+
return OneUptimeDate.getLocalTimeString(date, options);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
// Never leak a pinned zone or a mocked preference into the rest of the suite.
|
|
41
|
+
OneUptimeDate.setUserTimezone(null);
|
|
42
|
+
jest.restoreAllMocks();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("OneUptimeDate.getLocalTimeString", () => {
|
|
46
|
+
/*
|
|
47
|
+
* The chart x-axes in UI/Components/Charts/Utils/XAxis.ts label ticks with
|
|
48
|
+
* this helper and are laid out for the fixed width of a 24-hour reading.
|
|
49
|
+
* They pass no preference, so the default has to stay where it was - these
|
|
50
|
+
* two cases are what makes the new option opt-in rather than a behaviour
|
|
51
|
+
* change for all nine of its existing callers.
|
|
52
|
+
*/
|
|
53
|
+
describe("with no preference passed", () => {
|
|
54
|
+
it("keeps the 24-hour reading it has always produced", () => {
|
|
55
|
+
OneUptimeDate.setUserTimezone(UTC);
|
|
56
|
+
|
|
57
|
+
expect(format(AFTERNOON)).toBe("14:30");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("does not consult the machine's clock preference at all", () => {
|
|
61
|
+
OneUptimeDate.setUserTimezone(UTC);
|
|
62
|
+
|
|
63
|
+
jest.spyOn(OneUptimeDate, "getUserPrefers12HourFormat");
|
|
64
|
+
|
|
65
|
+
format(AFTERNOON);
|
|
66
|
+
|
|
67
|
+
expect(OneUptimeDate.getUserPrefers12HourFormat).not.toHaveBeenCalled();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("zero-pads the hour so ticks stay the same width", () => {
|
|
71
|
+
OneUptimeDate.setUserTimezone(UTC);
|
|
72
|
+
|
|
73
|
+
expect(format(new Date("2024-03-01T09:05:00.000Z"))).toBe("09:05");
|
|
74
|
+
expect(format(new Date("2024-03-01T00:00:00.000Z"))).toBe("00:00");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("appends seconds only when asked", () => {
|
|
78
|
+
OneUptimeDate.setUserTimezone(UTC);
|
|
79
|
+
|
|
80
|
+
expect(format(AFTERNOON, { includeSeconds: true })).toBe("14:30:45");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("returns the bare hour when minutes are turned off", () => {
|
|
84
|
+
OneUptimeDate.setUserTimezone(UTC);
|
|
85
|
+
|
|
86
|
+
expect(format(AFTERNOON, { includeMinutes: false })).toBe("14");
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe("on a 12-hour clock", () => {
|
|
91
|
+
it("marks an afternoon time PM", () => {
|
|
92
|
+
OneUptimeDate.setUserTimezone(UTC);
|
|
93
|
+
|
|
94
|
+
expect(format(AFTERNOON, { use12HourFormat: true })).toBe("2:30 PM");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("marks a morning time AM", () => {
|
|
98
|
+
OneUptimeDate.setUserTimezone(UTC);
|
|
99
|
+
|
|
100
|
+
expect(
|
|
101
|
+
format(new Date("2024-03-01T09:05:00.000Z"), {
|
|
102
|
+
use12HourFormat: true,
|
|
103
|
+
}),
|
|
104
|
+
).toBe("9:05 AM");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// The two readings a naive `hours % 12` gets wrong.
|
|
108
|
+
it("writes midnight as 12 AM rather than 0 AM", () => {
|
|
109
|
+
OneUptimeDate.setUserTimezone(UTC);
|
|
110
|
+
|
|
111
|
+
expect(
|
|
112
|
+
format(new Date("2024-03-01T00:00:00.000Z"), {
|
|
113
|
+
use12HourFormat: true,
|
|
114
|
+
}),
|
|
115
|
+
).toBe("12:00 AM");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("writes noon as 12 PM rather than 0 PM", () => {
|
|
119
|
+
OneUptimeDate.setUserTimezone(UTC);
|
|
120
|
+
|
|
121
|
+
expect(
|
|
122
|
+
format(new Date("2024-03-01T12:00:00.000Z"), {
|
|
123
|
+
use12HourFormat: true,
|
|
124
|
+
}),
|
|
125
|
+
).toBe("12:00 PM");
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("keeps the marker after the seconds", () => {
|
|
129
|
+
OneUptimeDate.setUserTimezone(UTC);
|
|
130
|
+
|
|
131
|
+
expect(
|
|
132
|
+
format(AFTERNOON, { use12HourFormat: true, includeSeconds: true }),
|
|
133
|
+
).toBe("2:30:45 PM");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("keeps the marker on a bare hour", () => {
|
|
137
|
+
OneUptimeDate.setUserTimezone(UTC);
|
|
138
|
+
|
|
139
|
+
expect(
|
|
140
|
+
format(AFTERNOON, { use12HourFormat: true, includeMinutes: false }),
|
|
141
|
+
).toBe("2 PM");
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
/*
|
|
146
|
+
* The browser here is in Los Angeles (see the docblock). None of the
|
|
147
|
+
* readings below are the Los Angeles ones, which is the point.
|
|
148
|
+
*/
|
|
149
|
+
describe("reading the configured timezone rather than the browser's", () => {
|
|
150
|
+
it("shifts a whole-hour offset", () => {
|
|
151
|
+
OneUptimeDate.setUserTimezone(NEW_YORK);
|
|
152
|
+
|
|
153
|
+
expect(format(AFTERNOON)).toBe("09:30");
|
|
154
|
+
expect(format(AFTERNOON, { use12HourFormat: true })).toBe("9:30 AM");
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("shifts a half-hour offset", () => {
|
|
158
|
+
OneUptimeDate.setUserTimezone(KOLKATA);
|
|
159
|
+
|
|
160
|
+
expect(format(AFTERNOON)).toBe("20:00");
|
|
161
|
+
expect(format(AFTERNOON, { use12HourFormat: true })).toBe("8:00 PM");
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("crosses the date line into the next day", () => {
|
|
165
|
+
OneUptimeDate.setUserTimezone(KOLKATA);
|
|
166
|
+
|
|
167
|
+
// 23:00 UTC is already 04:30 the following morning in Kolkata.
|
|
168
|
+
expect(format(new Date("2024-03-01T23:00:00.000Z"))).toBe("04:30");
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("falls back to the browser zone when nothing is configured", () => {
|
|
172
|
+
OneUptimeDate.setUserTimezone(null);
|
|
173
|
+
|
|
174
|
+
// 14:30 UTC is 06:30 in Los Angeles on a March morning (PST, UTC-8).
|
|
175
|
+
expect(format(AFTERNOON)).toBe("06:30");
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
/*
|
|
181
|
+
* "Is this bucket today?" is what the histogram tooltips ask before deciding
|
|
182
|
+
* whether a timestamp needs its date spelled out. They used to ask it with
|
|
183
|
+
* `date.toDateString() === now.toDateString()`, which compares calendar days
|
|
184
|
+
* in the browser's zone - so within a few hours of midnight in the configured
|
|
185
|
+
* zone the date got dropped from buckets that needed it and added to buckets
|
|
186
|
+
* that did not.
|
|
187
|
+
*/
|
|
188
|
+
describe("OneUptimeDate.areOnTheSameLocalDay", () => {
|
|
189
|
+
const BEFORE_MIDNIGHT_IN_KOLKATA: Date = new Date("2024-03-01T18:00:00.000Z");
|
|
190
|
+
const AFTER_MIDNIGHT_IN_KOLKATA: Date = new Date("2024-03-01T19:00:00.000Z");
|
|
191
|
+
|
|
192
|
+
it("separates two instants that straddle midnight in the configured zone", () => {
|
|
193
|
+
OneUptimeDate.setUserTimezone(KOLKATA);
|
|
194
|
+
|
|
195
|
+
/*
|
|
196
|
+
* 23:30 and 00:30 in Kolkata - two different days there, one hour apart.
|
|
197
|
+
* Both are still the same Friday morning in Los Angeles, which is exactly
|
|
198
|
+
* the disagreement the browser-zone comparison could not see.
|
|
199
|
+
*/
|
|
200
|
+
expect(
|
|
201
|
+
OneUptimeDate.areOnTheSameLocalDay(
|
|
202
|
+
BEFORE_MIDNIGHT_IN_KOLKATA,
|
|
203
|
+
AFTER_MIDNIGHT_IN_KOLKATA,
|
|
204
|
+
),
|
|
205
|
+
).toBe(false);
|
|
206
|
+
|
|
207
|
+
expect(
|
|
208
|
+
OneUptimeDate.areOnTheSameDay(
|
|
209
|
+
BEFORE_MIDNIGHT_IN_KOLKATA,
|
|
210
|
+
AFTER_MIDNIGHT_IN_KOLKATA,
|
|
211
|
+
),
|
|
212
|
+
).toBe(true);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it("joins two instants the browser's zone splits across midnight", () => {
|
|
216
|
+
OneUptimeDate.setUserTimezone(KOLKATA);
|
|
217
|
+
|
|
218
|
+
// 07:30 and 21:30 on the same Kolkata day; Feb 29 and Mar 1 in Los Angeles.
|
|
219
|
+
const morning: Date = new Date("2024-03-01T02:00:00.000Z");
|
|
220
|
+
const evening: Date = new Date("2024-03-01T16:00:00.000Z");
|
|
221
|
+
|
|
222
|
+
expect(OneUptimeDate.areOnTheSameLocalDay(morning, evening)).toBe(true);
|
|
223
|
+
expect(OneUptimeDate.areOnTheSameDay(morning, evening)).toBe(false);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it("accepts the string form the histogram buckets arrive as", () => {
|
|
227
|
+
OneUptimeDate.setUserTimezone(UTC);
|
|
228
|
+
|
|
229
|
+
expect(
|
|
230
|
+
OneUptimeDate.areOnTheSameLocalDay(
|
|
231
|
+
"2024-03-01T00:30:00.000Z",
|
|
232
|
+
"2024-03-01T23:30:00.000Z",
|
|
233
|
+
),
|
|
234
|
+
).toBe(true);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("falls back to the browser zone when nothing is configured", () => {
|
|
238
|
+
OneUptimeDate.setUserTimezone(null);
|
|
239
|
+
|
|
240
|
+
/*
|
|
241
|
+
* 16:00 and 20:00 UTC are 08:00 and 12:00 the same Los Angeles day, but
|
|
242
|
+
* two different UTC-relative readings - the browser's zone is the one
|
|
243
|
+
* that gets to answer when the user has configured none.
|
|
244
|
+
*/
|
|
245
|
+
expect(
|
|
246
|
+
OneUptimeDate.areOnTheSameLocalDay(
|
|
247
|
+
new Date("2024-03-01T16:00:00.000Z"),
|
|
248
|
+
new Date("2024-03-01T20:00:00.000Z"),
|
|
249
|
+
),
|
|
250
|
+
).toBe(true);
|
|
251
|
+
|
|
252
|
+
expect(
|
|
253
|
+
OneUptimeDate.areOnTheSameLocalDay(
|
|
254
|
+
new Date("2024-03-01T02:00:00.000Z"),
|
|
255
|
+
new Date("2024-03-01T16:00:00.000Z"),
|
|
256
|
+
),
|
|
257
|
+
).toBe(false);
|
|
258
|
+
});
|
|
259
|
+
});
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How OneUptime decides whether to write times on a 12- or a 24-hour clock.
|
|
3
|
+
*
|
|
4
|
+
* The answer has to come from the machine the person is sitting at: a browser
|
|
5
|
+
* resolves its default locale against the operating system's own clock setting,
|
|
6
|
+
* so toggling macOS's "24-Hour Time" (or the Windows/Linux equivalent) is meant
|
|
7
|
+
* to flip every time in the product.
|
|
8
|
+
*
|
|
9
|
+
* The bug these lock in: the check used to format a time and look for the
|
|
10
|
+
* letters "am"/"pm" in it. That is only how Latin-script locales mark the day
|
|
11
|
+
* period - ko-KR writes 오후, ar-EG writes م - so 12-hour machines in those
|
|
12
|
+
* locales were misread as 24-hour and got 24-hour times back. Intl reports the
|
|
13
|
+
* hour cycle directly, so it is asked first and the string probe is kept only
|
|
14
|
+
* as a fallback for an engine without a usable Intl.
|
|
15
|
+
*/
|
|
16
|
+
import OneUptimeDate from "../../Types/Date";
|
|
17
|
+
import Timezone from "../../Types/Timezone";
|
|
18
|
+
import { afterEach, describe, expect, it, jest } from "@jest/globals";
|
|
19
|
+
|
|
20
|
+
type ResolvedOptionsStub = {
|
|
21
|
+
hour12?: boolean | undefined;
|
|
22
|
+
hourCycle?: string | undefined;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/*
|
|
26
|
+
* Stand in for the browser's resolved locale. `locales` records what the probe
|
|
27
|
+
* asked for so a test can assert it requested an hour at all - Intl only
|
|
28
|
+
* reports `hour12` when the format includes one, so a probe that forgot to ask
|
|
29
|
+
* would silently fall through to the string check on every engine.
|
|
30
|
+
*/
|
|
31
|
+
type IntlProbeCall = {
|
|
32
|
+
locales: Intl.LocalesArgument;
|
|
33
|
+
options: Intl.DateTimeFormatOptions | undefined;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const probeCalls: Array<IntlProbeCall> = [];
|
|
37
|
+
|
|
38
|
+
type StubIntlFunction = (resolved: ResolvedOptionsStub | (() => never)) => void;
|
|
39
|
+
|
|
40
|
+
const stubIntl: StubIntlFunction = (
|
|
41
|
+
resolved: ResolvedOptionsStub | (() => never),
|
|
42
|
+
): void => {
|
|
43
|
+
jest
|
|
44
|
+
.spyOn(Intl, "DateTimeFormat")
|
|
45
|
+
.mockImplementation(
|
|
46
|
+
(
|
|
47
|
+
locales?: Intl.LocalesArgument,
|
|
48
|
+
options?: Intl.DateTimeFormatOptions,
|
|
49
|
+
) => {
|
|
50
|
+
probeCalls.push({ locales: locales, options: options });
|
|
51
|
+
|
|
52
|
+
if (typeof resolved === "function") {
|
|
53
|
+
resolved();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
resolvedOptions: () => {
|
|
58
|
+
return resolved as Intl.ResolvedDateTimeFormatOptions;
|
|
59
|
+
},
|
|
60
|
+
} as unknown as Intl.DateTimeFormat;
|
|
61
|
+
},
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
type StubFormattedTimeFunction = (formatted: string) => void;
|
|
66
|
+
|
|
67
|
+
const stubFormattedTime: StubFormattedTimeFunction = (
|
|
68
|
+
formatted: string,
|
|
69
|
+
): void => {
|
|
70
|
+
jest.spyOn(Date.prototype, "toLocaleTimeString").mockImplementation(() => {
|
|
71
|
+
return formatted;
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/*
|
|
76
|
+
* Delegate to the real Intl but pin the locale, so a case can assert what a
|
|
77
|
+
* machine actually configured to that locale reports rather than a hand-made
|
|
78
|
+
* stub of it.
|
|
79
|
+
*/
|
|
80
|
+
type StubIntlWithLocaleFunction = (locale: string) => void;
|
|
81
|
+
|
|
82
|
+
const stubIntlWithLocale: StubIntlWithLocaleFunction = (
|
|
83
|
+
locale: string,
|
|
84
|
+
): void => {
|
|
85
|
+
const realDateTimeFormat: typeof Intl.DateTimeFormat = Intl.DateTimeFormat;
|
|
86
|
+
|
|
87
|
+
jest
|
|
88
|
+
.spyOn(Intl, "DateTimeFormat")
|
|
89
|
+
.mockImplementation(
|
|
90
|
+
(
|
|
91
|
+
_locales?: Intl.LocalesArgument,
|
|
92
|
+
options?: Intl.DateTimeFormatOptions,
|
|
93
|
+
) => {
|
|
94
|
+
return new realDateTimeFormat(locale, options);
|
|
95
|
+
},
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
/*
|
|
99
|
+
* Hold the legacy string probe to a bare 24-hour reading. Without this the
|
|
100
|
+
* fallback still sees the HOST's locale, so on a US runner the old am/pm
|
|
101
|
+
* substring check would answer "12-hour" by luck and the cases below would
|
|
102
|
+
* pass against the very implementation they exist to rule out.
|
|
103
|
+
*/
|
|
104
|
+
stubFormattedTime("14:30:00");
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
describe("OneUptimeDate.getUserPrefers12HourFormat", () => {
|
|
108
|
+
afterEach(() => {
|
|
109
|
+
probeCalls.length = 0;
|
|
110
|
+
jest.restoreAllMocks();
|
|
111
|
+
OneUptimeDate.setUserTimezone(null);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe("reading the machine's clock setting from Intl", () => {
|
|
115
|
+
it("reports a 12-hour machine as 12-hour", () => {
|
|
116
|
+
stubIntl({ hour12: true, hourCycle: "h12" });
|
|
117
|
+
|
|
118
|
+
expect(OneUptimeDate.getUserPrefers12HourFormat()).toBe(true);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("reports a 24-hour machine as 24-hour", () => {
|
|
122
|
+
stubIntl({ hour12: false, hourCycle: "h23" });
|
|
123
|
+
|
|
124
|
+
expect(OneUptimeDate.getUserPrefers12HourFormat()).toBe(false);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("asks for an hour, without which Intl reports no clock at all", () => {
|
|
128
|
+
stubIntl({ hour12: true });
|
|
129
|
+
|
|
130
|
+
OneUptimeDate.getUserPrefers12HourFormat();
|
|
131
|
+
|
|
132
|
+
expect(probeCalls).toHaveLength(1);
|
|
133
|
+
expect(probeCalls[0]?.options?.hour).toBeDefined();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("asks for the machine's own locale rather than pinning one", () => {
|
|
137
|
+
stubIntl({ hour12: true });
|
|
138
|
+
|
|
139
|
+
OneUptimeDate.getUserPrefers12HourFormat();
|
|
140
|
+
|
|
141
|
+
/*
|
|
142
|
+
* Assert the probe ran before reading what it asked for - without this,
|
|
143
|
+
* an implementation that never called Intl would satisfy the line below
|
|
144
|
+
* on an empty array.
|
|
145
|
+
*/
|
|
146
|
+
expect(probeCalls).toHaveLength(1);
|
|
147
|
+
// `undefined` is what makes Intl resolve the browser's default locale.
|
|
148
|
+
expect(probeCalls[0]?.locales).toBeUndefined();
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe("engines that report the cycle but not the boolean", () => {
|
|
153
|
+
it("treats h12 as a 12-hour clock", () => {
|
|
154
|
+
stubIntl({ hourCycle: "h12" });
|
|
155
|
+
|
|
156
|
+
expect(OneUptimeDate.getUserPrefers12HourFormat()).toBe(true);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("treats h11 as a 12-hour clock", () => {
|
|
160
|
+
stubIntl({ hourCycle: "h11" });
|
|
161
|
+
|
|
162
|
+
expect(OneUptimeDate.getUserPrefers12HourFormat()).toBe(true);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("treats h23 as a 24-hour clock", () => {
|
|
166
|
+
stubIntl({ hourCycle: "h23" });
|
|
167
|
+
|
|
168
|
+
expect(OneUptimeDate.getUserPrefers12HourFormat()).toBe(false);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("treats h24 as a 24-hour clock", () => {
|
|
172
|
+
stubIntl({ hourCycle: "h24" });
|
|
173
|
+
|
|
174
|
+
expect(OneUptimeDate.getUserPrefers12HourFormat()).toBe(false);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("prefers the boolean when both are reported and they disagree", () => {
|
|
178
|
+
/*
|
|
179
|
+
* hour12 is the value the spec says to honour; hourCycle is only read
|
|
180
|
+
* because older engines omit hour12 entirely.
|
|
181
|
+
*/
|
|
182
|
+
stubIntl({ hour12: false, hourCycle: "h12" });
|
|
183
|
+
|
|
184
|
+
expect(OneUptimeDate.getUserPrefers12HourFormat()).toBe(false);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
describe("real locales", () => {
|
|
189
|
+
it("reads en-US as a 12-hour machine", () => {
|
|
190
|
+
stubIntlWithLocale("en-US");
|
|
191
|
+
|
|
192
|
+
expect(OneUptimeDate.getUserPrefers12HourFormat()).toBe(true);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("reads en-GB as a 24-hour machine", () => {
|
|
196
|
+
stubIntlWithLocale("en-GB");
|
|
197
|
+
|
|
198
|
+
expect(OneUptimeDate.getUserPrefers12HourFormat()).toBe(false);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("reads de-DE as a 24-hour machine", () => {
|
|
202
|
+
stubIntlWithLocale("de-DE");
|
|
203
|
+
|
|
204
|
+
expect(OneUptimeDate.getUserPrefers12HourFormat()).toBe(false);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it("honours an explicit 24-hour override on a 12-hour locale", () => {
|
|
208
|
+
// What a US machine with "24-Hour Time" switched on resolves to.
|
|
209
|
+
stubIntlWithLocale("en-US-u-hc-h23");
|
|
210
|
+
|
|
211
|
+
expect(OneUptimeDate.getUserPrefers12HourFormat()).toBe(false);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("honours an explicit 12-hour override on a 24-hour locale", () => {
|
|
215
|
+
stubIntlWithLocale("de-DE-u-hc-h12");
|
|
216
|
+
|
|
217
|
+
expect(OneUptimeDate.getUserPrefers12HourFormat()).toBe(true);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("reads ko-KR as a 12-hour machine even though it writes 오후, not PM", () => {
|
|
221
|
+
/*
|
|
222
|
+
* The regression this whole change exists for: the old string probe saw
|
|
223
|
+
* "오후 2:30:00", found no "am"/"pm", and told a Korean user on a
|
|
224
|
+
* 12-hour machine they were on a 24-hour one.
|
|
225
|
+
*/
|
|
226
|
+
stubIntlWithLocale("ko-KR");
|
|
227
|
+
|
|
228
|
+
expect(OneUptimeDate.getUserPrefers12HourFormat()).toBe(true);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it("reads ar-EG as a 12-hour machine even though it writes م, not PM", () => {
|
|
232
|
+
stubIntlWithLocale("ar-EG");
|
|
233
|
+
|
|
234
|
+
expect(OneUptimeDate.getUserPrefers12HourFormat()).toBe(true);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
describe("falling back when Intl cannot answer", () => {
|
|
239
|
+
it("reads a Latin AM/PM out of a formatted time when Intl throws", () => {
|
|
240
|
+
stubIntl(() => {
|
|
241
|
+
throw new Error("Intl unavailable");
|
|
242
|
+
});
|
|
243
|
+
stubFormattedTime("2:30:00 PM");
|
|
244
|
+
|
|
245
|
+
expect(OneUptimeDate.getUserPrefers12HourFormat()).toBe(true);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it("reads a bare 24-hour time out of a formatted time when Intl throws", () => {
|
|
249
|
+
stubIntl(() => {
|
|
250
|
+
throw new Error("Intl unavailable");
|
|
251
|
+
});
|
|
252
|
+
stubFormattedTime("14:30:00");
|
|
253
|
+
|
|
254
|
+
expect(OneUptimeDate.getUserPrefers12HourFormat()).toBe(false);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("falls back when Intl answers with neither a boolean nor a cycle", () => {
|
|
258
|
+
stubIntl({});
|
|
259
|
+
stubFormattedTime("2:30:00 AM");
|
|
260
|
+
|
|
261
|
+
expect(OneUptimeDate.getUserPrefers12HourFormat()).toBe(true);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it("matches a lowercase day period", () => {
|
|
265
|
+
stubIntl({});
|
|
266
|
+
stubFormattedTime("2:30:00 pm");
|
|
267
|
+
|
|
268
|
+
expect(OneUptimeDate.getUserPrefers12HourFormat()).toBe(true);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it("does not throw when Intl is broken and the time has no day period", () => {
|
|
272
|
+
stubIntl(() => {
|
|
273
|
+
throw new Error("Intl unavailable");
|
|
274
|
+
});
|
|
275
|
+
stubFormattedTime("14:30:00");
|
|
276
|
+
|
|
277
|
+
expect(() => {
|
|
278
|
+
return OneUptimeDate.getUserPrefers12HourFormat();
|
|
279
|
+
}).not.toThrow();
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
describe("as a decision the rest of the product reads", () => {
|
|
284
|
+
it("drives the clock of the time range picker's custom label", () => {
|
|
285
|
+
/*
|
|
286
|
+
* The user-visible consequence, end to end: the same instant is written
|
|
287
|
+
* two different ways purely on the strength of this one answer.
|
|
288
|
+
*/
|
|
289
|
+
/*
|
|
290
|
+
* Pin the zone: without it this reads 2:30 PM on a UTC runner and 6:30 AM
|
|
291
|
+
* on a Los Angeles one, and the assertion is about the clock, not the
|
|
292
|
+
* zone.
|
|
293
|
+
*/
|
|
294
|
+
OneUptimeDate.setUserTimezone(Timezone.UTC);
|
|
295
|
+
|
|
296
|
+
stubIntlWithLocale("en-US");
|
|
297
|
+
const on12HourMachine: string =
|
|
298
|
+
OneUptimeDate.getDateAsLocalShortDateTimeString(
|
|
299
|
+
new Date("2024-03-01T14:30:00.000Z"),
|
|
300
|
+
{ use12HourFormat: OneUptimeDate.getUserPrefers12HourFormat() },
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
jest.restoreAllMocks();
|
|
304
|
+
|
|
305
|
+
stubIntlWithLocale("en-GB");
|
|
306
|
+
const on24HourMachine: string =
|
|
307
|
+
OneUptimeDate.getDateAsLocalShortDateTimeString(
|
|
308
|
+
new Date("2024-03-01T14:30:00.000Z"),
|
|
309
|
+
{ use12HourFormat: OneUptimeDate.getUserPrefers12HourFormat() },
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
expect(on12HourMachine).toBe("Mar 1, 2:30 PM");
|
|
313
|
+
expect(on24HourMachine).toBe("Mar 1, 14:30");
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
});
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
import LogsHistogram from "../../../UI/Components/LogsViewer/components/LogsHistogram";
|
|
4
4
|
import { HistogramBucket } from "../../../UI/Components/LogsViewer/types";
|
|
5
5
|
import LogSeverity from "../../../Types/Log/LogSeverity";
|
|
6
|
+
import OneUptimeDate from "../../../Types/Date";
|
|
6
7
|
import { render, screen } from "@testing-library/react";
|
|
7
8
|
import React from "react";
|
|
8
|
-
import { describe, expect, test } from "@jest/globals";
|
|
9
|
+
import { afterEach, describe, expect, test } from "@jest/globals";
|
|
9
10
|
|
|
10
11
|
/*
|
|
11
12
|
* ResponsiveContainer measures its parent, which is always 0x0 in jsdom, so the
|
|
@@ -39,7 +40,27 @@ function seriesCount(container: HTMLElement): number {
|
|
|
39
40
|
return container.querySelectorAll(".recharts-bar").length;
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
|
|
43
|
+
/*
|
|
44
|
+
* Matches a tick on either clock. The axis follows the machine's 12/24-hour
|
|
45
|
+
* preference now, so a pattern that only knew "HH:mm" would quietly classify
|
|
46
|
+
* every tick as a count label on an AM/PM machine and leave the assertions
|
|
47
|
+
* below comparing empty arrays.
|
|
48
|
+
*/
|
|
49
|
+
const CLOCK_LABEL: RegExp = /^\d{1,2}:\d{2}( [AP]M)?$/;
|
|
50
|
+
|
|
51
|
+
/*
|
|
52
|
+
* The tick labels are the machine's business, not the test's, so every case
|
|
53
|
+
* that reads them says which clock it is asking about.
|
|
54
|
+
*/
|
|
55
|
+
function pinClock(use12HourFormat: boolean): void {
|
|
56
|
+
jest
|
|
57
|
+
.spyOn(OneUptimeDate, "getUserPrefers12HourFormat")
|
|
58
|
+
.mockReturnValue(use12HourFormat);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
afterEach(() => {
|
|
62
|
+
jest.restoreAllMocks();
|
|
63
|
+
});
|
|
43
64
|
|
|
44
65
|
/*
|
|
45
66
|
* Recharts portals tick labels out of their axis group, so the two axes are
|
|
@@ -126,6 +147,8 @@ describe("LogsHistogram", () => {
|
|
|
126
147
|
});
|
|
127
148
|
|
|
128
149
|
test("labels the time axis in the reader's clock", () => {
|
|
150
|
+
pinClock(false);
|
|
151
|
+
|
|
129
152
|
const { container } = render(
|
|
130
153
|
<LogsHistogram buckets={ONE_SEVERITY} isLoading={false} />,
|
|
131
154
|
);
|
|
@@ -133,6 +156,20 @@ describe("LogsHistogram", () => {
|
|
|
133
156
|
expect(axisLabels(container, "time")).toContain("12:00");
|
|
134
157
|
});
|
|
135
158
|
|
|
159
|
+
/*
|
|
160
|
+
* Noon is where the two clocks are hardest to tell apart - "12:00" is a
|
|
161
|
+
* valid reading on both - so the marker is the whole assertion.
|
|
162
|
+
*/
|
|
163
|
+
test("labels the time axis with AM/PM when that is the reader's clock", () => {
|
|
164
|
+
pinClock(true);
|
|
165
|
+
|
|
166
|
+
const { container } = render(
|
|
167
|
+
<LogsHistogram buckets={ONE_SEVERITY} isLoading={false} />,
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
expect(axisLabels(container, "time")).toContain("12:00 PM");
|
|
171
|
+
});
|
|
172
|
+
|
|
136
173
|
test("lists only the severities present in the window", () => {
|
|
137
174
|
render(
|
|
138
175
|
<LogsHistogram
|
|
@@ -176,6 +213,8 @@ describe("LogsHistogram", () => {
|
|
|
176
213
|
});
|
|
177
214
|
|
|
178
215
|
test("follows the window as it slides off the oldest bucket", () => {
|
|
216
|
+
pinClock(false);
|
|
217
|
+
|
|
179
218
|
const view: ReturnType<typeof render> = render(
|
|
180
219
|
<LogsHistogram buckets={ONE_SEVERITY} isLoading={false} />,
|
|
181
220
|
);
|