@oneuptime/common 14.0.2 → 14.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/Models/DatabaseModels/AlertFeed.ts +2 -0
  2. package/Models/DatabaseModels/IncidentAlert.ts +540 -0
  3. package/Models/DatabaseModels/IncidentFeed.ts +2 -0
  4. package/Models/DatabaseModels/Index.ts +2 -0
  5. package/Models/DatabaseModels/Project.ts +60 -0
  6. package/Models/DatabaseModels/RumApplication.ts +52 -1
  7. package/Server/Infrastructure/Postgres/SchemaMigrations/1794800000000-AddSessionReplaySameOriginTracePropagation.ts +28 -0
  8. package/Server/Infrastructure/Postgres/SchemaMigrations/1794900000000-AddIncidentAlert.ts +89 -0
  9. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  10. package/Server/Services/IncidentAlertService.ts +1811 -0
  11. package/Server/Services/IncidentService.ts +210 -10
  12. package/Server/Services/IncidentStateTimelineService.ts +24 -0
  13. package/Server/Services/Index.ts +2 -0
  14. package/Server/Utils/SessionReplay/SessionReplayGateCache.ts +24 -2
  15. package/Tests/App/Dashboard/BulkIncidentLinkActions.test.tsx +869 -0
  16. package/Tests/App/Dashboard/CephMetricTooltips.test.tsx +1777 -0
  17. package/Tests/App/Dashboard/CloudFleetSummary.test.ts +8 -0
  18. package/Tests/App/Dashboard/CloudMetricTooltips.test.tsx +887 -0
  19. package/Tests/App/Dashboard/ContainerHostMetricTooltips.test.tsx +1309 -0
  20. package/Tests/App/Dashboard/DiscoveryScanLivePage.test.tsx +39 -0
  21. package/Tests/App/Dashboard/DockerSwarmClustersListTooltips.test.tsx +414 -0
  22. package/Tests/App/Dashboard/DockerSwarmMetricTooltips.test.ts +796 -0
  23. package/Tests/App/Dashboard/DockerSwarmOverviewTooltips.test.tsx +747 -0
  24. package/Tests/App/Dashboard/ExceptionMonitorEnvironmentFilter.test.tsx +38 -18
  25. package/Tests/App/Dashboard/HostDetailViewTooltips.test.tsx +725 -0
  26. package/Tests/App/Dashboard/HostListColumnTooltips.test.tsx +472 -0
  27. package/Tests/App/Dashboard/HostMetricTooltips.test.ts +709 -0
  28. package/Tests/App/Dashboard/HostOverviewTooltips.test.tsx +698 -0
  29. package/Tests/App/Dashboard/HostTooltipHarness.ts +124 -0
  30. package/Tests/App/Dashboard/HostsListTooltips.test.tsx +395 -0
  31. package/Tests/App/Dashboard/IncidentAlertLinkDialog.test.tsx +691 -0
  32. package/Tests/App/Dashboard/IncidentAlertLinkHelpers.test.ts +458 -0
  33. package/Tests/App/Dashboard/IncidentAlertLinkPages.test.tsx +828 -0
  34. package/Tests/App/Dashboard/IncidentAlertLinkSideMenus.test.tsx +231 -0
  35. package/Tests/App/Dashboard/IncidentCreateFromAlerts.test.tsx +689 -0
  36. package/Tests/App/Dashboard/IoTMetricTooltips.test.tsx +1123 -0
  37. package/Tests/App/Dashboard/KubernetesClusterMetricTooltips.test.ts +837 -0
  38. package/Tests/App/Dashboard/KubernetesClusterOverviewTooltips.test.tsx +699 -0
  39. package/Tests/App/Dashboard/KubernetesContainersTabTooltips.test.tsx +320 -0
  40. package/Tests/App/Dashboard/KubernetesCostTooltips.test.tsx +502 -0
  41. package/Tests/App/Dashboard/KubernetesInsightsTooltips.test.tsx +222 -0
  42. package/Tests/App/Dashboard/KubernetesResourceMetricTooltips.test.ts +561 -0
  43. package/Tests/App/Dashboard/KubernetesResourcePagesTooltips.test.tsx +1168 -0
  44. package/Tests/App/Dashboard/LinkIncidentAlertModal.test.tsx +154 -0
  45. package/Tests/App/Dashboard/MetricDescriptionRules.ts +170 -0
  46. package/Tests/App/Dashboard/MetricDescriptionsCatalog.test.ts +206 -0
  47. package/Tests/App/Dashboard/MetricDescriptionsConsistency.test.ts +275 -0
  48. package/Tests/App/Dashboard/NetworkDeviceTooltips.test.tsx +852 -0
  49. package/Tests/App/Dashboard/NetworkDiagnosticsTooltips.test.tsx +556 -0
  50. package/Tests/App/Dashboard/NetworkMetricTooltips.test.ts +1348 -0
  51. package/Tests/App/Dashboard/NetworkSiteTooltips.test.tsx +679 -0
  52. package/Tests/App/Dashboard/ProxmoxMetricTooltips.test.ts +592 -0
  53. package/Tests/App/Dashboard/ProxmoxOverviewTooltips.test.tsx +1002 -0
  54. package/Tests/App/Dashboard/ProxmoxResourcePageTooltips.test.tsx +923 -0
  55. package/Tests/App/Dashboard/ResourceOverviewTileTooltips.test.tsx +1066 -0
  56. package/Tests/App/Dashboard/RumOverviewPage.test.tsx +1195 -0
  57. package/Tests/App/Dashboard/ServerlessMetricTooltips.test.tsx +474 -0
  58. package/Tests/App/Dashboard/ServiceMetricTooltips.test.tsx +772 -0
  59. package/Tests/App/Dashboard/SessionReplayAuditTable.test.tsx +56 -1
  60. package/Tests/App/Dashboard/TelemetryMetricsSignals.test.ts +1207 -0
  61. package/Tests/App/Dashboard/TelemetryResourceTabTooltips.test.tsx +437 -0
  62. package/Tests/App/Dashboard/VMwareMetricTooltips.test.ts +801 -0
  63. package/Tests/App/Dashboard/VMwareOverviewTooltips.test.tsx +601 -0
  64. package/Tests/App/Dashboard/VMwareResourcePagesTooltips.test.tsx +646 -0
  65. package/Tests/App/Dashboard/VMwareTooltipHarness.ts +145 -0
  66. package/Tests/Models/DatabaseModels/IncidentAlertModel.test.ts +554 -0
  67. package/Tests/Models/DatabaseModels/SessionReplayModels.test.ts +117 -0
  68. package/Tests/Server/Infrastructure/Postgres/AddIncidentAlertMigration.test.ts +341 -0
  69. package/Tests/Server/Infrastructure/Postgres/AddSessionReplaySameOriginTracePropagationMigration.test.ts +247 -0
  70. package/Tests/Server/Services/IncidentAlertPostgres.test.ts +903 -0
  71. package/Tests/Server/Services/IncidentAlertRegistration.test.ts +96 -0
  72. package/Tests/Server/Services/IncidentAlertService.test.ts +2225 -0
  73. package/Tests/Server/Services/IncidentAlertStateCascade.test.ts +970 -0
  74. package/Tests/Server/Services/IncidentCreateFromAlerts.test.ts +1284 -0
  75. package/Tests/Server/Utils/SessionReplay/SessionReplayGateCachePolicy.test.ts +103 -0
  76. package/Tests/Types/Rum/WebVitals.test.ts +173 -0
  77. package/Tests/UI/Components/FieldLabelInfoCardTooltip.test.tsx +413 -0
  78. package/Tests/UI/Components/InfoTooltip.test.tsx +242 -0
  79. package/Tests/UI/Components/ModelTable/BaseModelTableBulkDelete.test.tsx +207 -3
  80. package/Tests/UI/Components/ModelTable/BaseModelTableBulkDeleteVerb.test.tsx +367 -0
  81. package/Tests/UI/Components/ModelTable/BaseModelTableCreateButtonTitle.test.tsx +275 -0
  82. package/Tests/UI/Components/TableHeaderTooltip.test.tsx +662 -0
  83. package/Tests/UI/Rum/PrivacySummaryCard.test.tsx +166 -9
  84. package/Tests/UI/Rum/RecordingHealthTooltips.test.tsx +447 -0
  85. package/Tests/UI/Rum/ReplayCorrelationPanel.test.tsx +164 -0
  86. package/Tests/UI/Rum/ReplayFrameCapture.test.ts +6750 -0
  87. package/Tests/UI/Rum/ReplayRail.test.tsx +723 -12
  88. package/Tests/UI/Rum/ReplayRailDetail.test.tsx +86 -0
  89. package/Tests/UI/Rum/ReplayScreenshot.test.ts +2124 -0
  90. package/Tests/UI/Rum/ReplayScreenshotActions.test.tsx +3288 -0
  91. package/Tests/UI/Rum/ReplayStage.test.tsx +62 -0
  92. package/Tests/UI/Rum/ReplayStageOverlays.test.tsx +983 -2
  93. package/Tests/UI/Rum/SessionReplaySettingsPolicyLoading.test.tsx +149 -0
  94. package/Tests/UI/Rum/SessionReplaySetupGuide.test.tsx +107 -6
  95. package/Tests/UI/Rum/SessionReplayUsersTableTooltips.test.tsx +330 -0
  96. package/Tests/UI/Rum/UserFlowTooltips.test.tsx +886 -0
  97. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +10 -0
  98. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +2 -0
  99. package/Tests/UI/Utils/PermissionGate.test.ts +76 -0
  100. package/Tests/Utils/Incident/IncidentFromAlerts.test.ts +696 -0
  101. package/Tests/Utils/Rum/SessionTraceState.test.ts +239 -0
  102. package/Types/Incident/IncidentAlertLink.ts +38 -0
  103. package/Types/Permission.ts +48 -0
  104. package/Types/Rum/SessionReplay.ts +25 -6
  105. package/Types/Rum/WebVitals.ts +52 -0
  106. package/UI/Components/Detail/FieldLabel.tsx +8 -0
  107. package/UI/Components/InfoCard/InfoCard.tsx +47 -1
  108. package/UI/Components/ModelTable/BaseModelTable.tsx +95 -17
  109. package/UI/Components/ModelTable/Column.ts +5 -0
  110. package/UI/Components/Table/TableHeader.tsx +47 -2
  111. package/UI/Components/Table/Types/Column.ts +7 -0
  112. package/UI/Components/Tooltip/InfoTooltip.tsx +78 -0
  113. package/UI/Utils/PermissionGate.ts +11 -2
  114. package/Utils/Incident/IncidentFromAlerts.ts +516 -0
  115. package/Utils/Rum/SessionTraceState.ts +212 -0
  116. package/build/dist/Models/DatabaseModels/AlertFeed.js +2 -0
  117. package/build/dist/Models/DatabaseModels/AlertFeed.js.map +1 -1
  118. package/build/dist/Models/DatabaseModels/IncidentAlert.js +553 -0
  119. package/build/dist/Models/DatabaseModels/IncidentAlert.js.map +1 -0
  120. package/build/dist/Models/DatabaseModels/IncidentFeed.js +2 -0
  121. package/build/dist/Models/DatabaseModels/IncidentFeed.js.map +1 -1
  122. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  123. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  124. package/build/dist/Models/DatabaseModels/Project.js +62 -0
  125. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  126. package/build/dist/Models/DatabaseModels/RumApplication.js +53 -1
  127. package/build/dist/Models/DatabaseModels/RumApplication.js.map +1 -1
  128. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794800000000-AddSessionReplaySameOriginTracePropagation.js +20 -0
  129. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794800000000-AddSessionReplaySameOriginTracePropagation.js.map +1 -0
  130. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794900000000-AddIncidentAlert.js +42 -0
  131. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794900000000-AddIncidentAlert.js.map +1 -0
  132. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  133. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  134. package/build/dist/Server/Services/IncidentAlertService.js +1370 -0
  135. package/build/dist/Server/Services/IncidentAlertService.js.map +1 -0
  136. package/build/dist/Server/Services/IncidentService.js +164 -12
  137. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  138. package/build/dist/Server/Services/IncidentStateTimelineService.js +21 -0
  139. package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
  140. package/build/dist/Server/Services/Index.js +2 -0
  141. package/build/dist/Server/Services/Index.js.map +1 -1
  142. package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js +8 -0
  143. package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js.map +1 -1
  144. package/build/dist/Types/Incident/IncidentAlertLink.js +34 -0
  145. package/build/dist/Types/Incident/IncidentAlertLink.js.map +1 -0
  146. package/build/dist/Types/Permission.js +42 -0
  147. package/build/dist/Types/Permission.js.map +1 -1
  148. package/build/dist/Types/Rum/WebVitals.js +25 -0
  149. package/build/dist/Types/Rum/WebVitals.js.map +1 -1
  150. package/build/dist/UI/Components/Detail/FieldLabel.js +2 -0
  151. package/build/dist/UI/Components/Detail/FieldLabel.js.map +1 -1
  152. package/build/dist/UI/Components/InfoCard/InfoCard.js +22 -1
  153. package/build/dist/UI/Components/InfoCard/InfoCard.js.map +1 -1
  154. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +66 -14
  155. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  156. package/build/dist/UI/Components/Table/TableHeader.js +18 -2
  157. package/build/dist/UI/Components/Table/TableHeader.js.map +1 -1
  158. package/build/dist/UI/Components/Tooltip/InfoTooltip.js +28 -0
  159. package/build/dist/UI/Components/Tooltip/InfoTooltip.js.map +1 -0
  160. package/build/dist/UI/Utils/PermissionGate.js +4 -2
  161. package/build/dist/UI/Utils/PermissionGate.js.map +1 -1
  162. package/build/dist/Utils/Incident/IncidentFromAlerts.js +306 -0
  163. package/build/dist/Utils/Incident/IncidentFromAlerts.js.map +1 -0
  164. package/build/dist/Utils/Rum/SessionTraceState.js +154 -0
  165. package/build/dist/Utils/Rum/SessionTraceState.js.map +1 -0
  166. package/package.json +1 -1
@@ -0,0 +1,561 @@
1
+ import { describe, expect, test } from "@jest/globals";
2
+ import {
3
+ KUBERNETES_RESOURCE_METRIC_DESCRIPTIONS,
4
+ KubernetesResourceMetric,
5
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/MetricDescriptions/KubernetesResourceMetricDescriptions";
6
+ import {
7
+ expectReadableDescriptionRecord,
8
+ expectTitleExplained,
9
+ } from "./MetricDescriptionRules";
10
+
11
+ /*
12
+ * The (i) tooltips on the Kubernetes resource pages: the list tables, the
13
+ * Overview summary fields of every resource's detail page, the node's
14
+ * Network Throughput chart and a pod's container cards.
15
+ *
16
+ * The same column title means different things from page to page - "CPU" is
17
+ * a node's own usage on the Nodes list, a share of the NODE's allocatable
18
+ * CPU on the Pods list, and a SUM of those shares on a Deployment row - so
19
+ * beyond the shared readability rules these pin what each text promises
20
+ * against what the page actually computes (see the notes in
21
+ * KubernetesResourceMetricDescriptions.ts).
22
+ */
23
+
24
+ const D: Record<KubernetesResourceMetric, string> =
25
+ KUBERNETES_RESOURCE_METRIC_DESCRIPTIONS;
26
+
27
+ // Every title a customer sees beside an (i), with the text it opens.
28
+ const TITLES: Array<[string, KubernetesResourceMetric]> = [
29
+ // Built-in list columns.
30
+ ["CPU", "nodeCpu"],
31
+ ["Memory", "nodeMemory"],
32
+ ["CPU", "podCpu"],
33
+ ["Memory", "podMemory"],
34
+ ["CPU", "containerCpu"],
35
+ ["Memory", "containerMemory"],
36
+ ["CPU", "deploymentCpu"],
37
+ ["Memory", "deploymentMemory"],
38
+ ["CPU", "statefulSetCpu"],
39
+ ["Memory", "statefulSetMemory"],
40
+ ["CPU", "daemonSetCpu"],
41
+ ["Memory", "daemonSetMemory"],
42
+ ["CPU", "jobCpu"],
43
+ ["Memory", "jobMemory"],
44
+ ["CPU", "cronJobCpu"],
45
+ ["Memory", "cronJobMemory"],
46
+ ["CPU", "namespaceCpu"],
47
+ ["Memory", "namespaceMemory"],
48
+ // Custom list columns.
49
+ ["Containers", "podContainers"],
50
+ ["Restarts", "containerRestarts"],
51
+ ["Ready", "deploymentReady"],
52
+ ["Ready", "statefulSetReady"],
53
+ ["Ready", "daemonSetReady"],
54
+ ["Min Replicas", "hpaMinReplicas"],
55
+ ["Max Replicas", "hpaMaxReplicas"],
56
+ ["Current", "hpaCurrentReplicas"],
57
+ ["Desired", "hpaDesiredReplicas"],
58
+ ["Capacity", "pvcCapacity"],
59
+ ["Capacity", "pvCapacity"],
60
+ // Detail summary fields and headers.
61
+ ["CPU (Capacity / Allocatable)", "nodeCpuCapacity"],
62
+ ["Memory (Capacity / Allocatable)", "nodeMemoryCapacity"],
63
+ ["Pods (Capacity)", "nodePodCapacity"],
64
+ ["Network Throughput", "nodeNetworkThroughput"],
65
+ ["Restarts", "podRestarts"],
66
+ ["Rollout Status", "deploymentRolloutStatus"],
67
+ ["Desired Replicas", "deploymentDesiredReplicas"],
68
+ ["Ready Replicas", "deploymentReadyReplicas"],
69
+ ["Available", "deploymentAvailableReplicas"],
70
+ ["Unavailable", "deploymentUnavailableReplicas"],
71
+ ["Replicas", "statefulSetReplicas"],
72
+ ["Ready Replicas", "statefulSetReadyReplicas"],
73
+ ["Desired Scheduled", "daemonSetDesiredScheduled"],
74
+ ["Current Scheduled", "daemonSetCurrentScheduled"],
75
+ ["Number Ready", "daemonSetNumberReady"],
76
+ ["Number Available", "daemonSetNumberAvailable"],
77
+ ["Completions", "jobCompletions"],
78
+ ["Parallelism", "jobParallelism"],
79
+ ["Backoff Limit", "jobBackoffLimit"],
80
+ ["Active", "jobActive"],
81
+ ["Succeeded", "jobSucceeded"],
82
+ ["Failed", "jobFailed"],
83
+ ["Active Jobs", "cronJobActiveJobs"],
84
+ ["Successful Jobs History Limit", "cronJobSuccessfulJobsHistoryLimit"],
85
+ ["Failed Jobs History Limit", "cronJobFailedJobsHistoryLimit"],
86
+ ["Requested Storage", "pvcRequestedStorage"],
87
+ ["Metrics", "hpaMetrics"],
88
+ ["Recommendation (app)", "vpaRecommendation"],
89
+ // A pod's container cards.
90
+ ["State", "containerState"],
91
+ ["Ready", "containerReady"],
92
+ ["Requests", "containerRequests"],
93
+ ["Limits", "containerLimits"],
94
+ ];
95
+
96
+ const AVERAGE_PATTERN: RegExp = /averag/i;
97
+ const SENTENCE_END: RegExp = /[.!?](\s|$)/g;
98
+ const ALLOCATABLE_GLOSS: RegExp =
99
+ /allocatable (CPU|memory)( on its node)? \(what the node can hand out to pods\)/;
100
+
101
+ const LIST_LATEST_CPU: Array<KubernetesResourceMetric> = [
102
+ "nodeCpu",
103
+ "podCpu",
104
+ "containerCpu",
105
+ ];
106
+
107
+ const LIST_LATEST_MEMORY: Array<KubernetesResourceMetric> = [
108
+ "nodeMemory",
109
+ "podMemory",
110
+ "containerMemory",
111
+ ];
112
+
113
+ // Workloads are matched to their pods by name only (see bugsFound).
114
+ const WORKLOAD_SUMS: Array<KubernetesResourceMetric> = [
115
+ "deploymentCpu",
116
+ "deploymentMemory",
117
+ "statefulSetCpu",
118
+ "statefulSetMemory",
119
+ "daemonSetCpu",
120
+ "daemonSetMemory",
121
+ "jobCpu",
122
+ "jobMemory",
123
+ "cronJobCpu",
124
+ "cronJobMemory",
125
+ ];
126
+
127
+ const NAMESPACE_SUMS: Array<KubernetesResourceMetric> = [
128
+ "namespaceCpu",
129
+ "namespaceMemory",
130
+ ];
131
+
132
+ const ALL_SUMS: Array<KubernetesResourceMetric> = [
133
+ ...WORKLOAD_SUMS,
134
+ ...NAMESPACE_SUMS,
135
+ ];
136
+
137
+ const CPU_SUMS: Array<KubernetesResourceMetric> = ALL_SUMS.filter(
138
+ (key: KubernetesResourceMetric): boolean => {
139
+ return key.endsWith("Cpu");
140
+ },
141
+ );
142
+
143
+ const MEMORY_SUMS: Array<KubernetesResourceMetric> = ALL_SUMS.filter(
144
+ (key: KubernetesResourceMetric): boolean => {
145
+ return key.endsWith("Memory");
146
+ },
147
+ );
148
+
149
+ describe("Kubernetes resource metric descriptions", () => {
150
+ test("every text is a short, finished, jargon-explaining sentence, and no two metrics share one", () => {
151
+ expectReadableDescriptionRecord(
152
+ KUBERNETES_RESOURCE_METRIC_DESCRIPTIONS,
153
+ "KUBERNETES_RESOURCE_METRIC_DESCRIPTIONS",
154
+ );
155
+ });
156
+
157
+ test("every text is one or two sentences", () => {
158
+ const tooLong: Array<string> = [];
159
+
160
+ for (const [key, text] of Object.entries(D)) {
161
+ // A sentence ends at . ! or ? followed by a space or the end.
162
+ const sentences: number = (text.match(SENTENCE_END) || []).length;
163
+
164
+ if (sentences < 1 || sentences > 2) {
165
+ tooLong.push(`${key}: ${sentences} sentences`);
166
+ }
167
+ }
168
+
169
+ expect(tooLong).toEqual([]);
170
+ });
171
+
172
+ test("every title on the pages is explained by its text", () => {
173
+ for (const [title, key] of TITLES) {
174
+ expectTitleExplained(title, D[key]);
175
+ }
176
+ });
177
+
178
+ test("the title table covers every description exactly once", () => {
179
+ const keys: Array<KubernetesResourceMetric> = TITLES.map(
180
+ (entry: [string, KubernetesResourceMetric]): KubernetesResourceMetric => {
181
+ return entry[1];
182
+ },
183
+ );
184
+
185
+ expect(new Set(keys).size).toBe(keys.length);
186
+ expect([...keys].sort()).toEqual(Object.keys(D).sort());
187
+ });
188
+
189
+ test("no text talks down or sells", () => {
190
+ for (const text of Object.values(D)) {
191
+ expect(text).not.toMatch(
192
+ /\b(simply|just|obviously|easily|powerful|seamless|blazing)\b/i,
193
+ );
194
+ }
195
+ });
196
+ });
197
+
198
+ describe("list CPU and Memory: a single latest reading, never an average", () => {
199
+ test.each([...LIST_LATEST_CPU, ...LIST_LATEST_MEMORY])(
200
+ "%s says it turns N/A after 15 minutes without a reading",
201
+ (key: KubernetesResourceMetric) => {
202
+ expect(D[key]).toContain("N/A");
203
+ expect(D[key]).toContain("15 minutes");
204
+ expect(D[key]).toMatch(/most recent/);
205
+ },
206
+ );
207
+
208
+ test.each(LIST_LATEST_CPU)(
209
+ "%s says it is the latest sample, not an average",
210
+ (key: KubernetesResourceMetric) => {
211
+ expect(D[key]).toContain("latest sample, not an average");
212
+ },
213
+ );
214
+
215
+ test("no list text claims an average anywhere else", () => {
216
+ for (const key of [
217
+ ...LIST_LATEST_CPU,
218
+ ...LIST_LATEST_MEMORY,
219
+ ...ALL_SUMS,
220
+ ]) {
221
+ const withoutDisclaimer: string = D[key].replace("not an average", "");
222
+
223
+ expect(withoutDisclaimer).not.toMatch(AVERAGE_PATTERN);
224
+ }
225
+ });
226
+
227
+ test("none of them follows a time picker - the lists have none", () => {
228
+ for (const key of [
229
+ ...LIST_LATEST_CPU,
230
+ ...LIST_LATEST_MEMORY,
231
+ ...ALL_SUMS,
232
+ ]) {
233
+ expect(D[key]).not.toMatch(/selected (time )?range/);
234
+ }
235
+ });
236
+ });
237
+
238
+ describe("what each CPU percentage is a share of", () => {
239
+ test("a node's CPU is measured against its own allocatable CPU", () => {
240
+ expect(D.nodeCpu).toContain("share of its allocatable CPU");
241
+ expect(D.nodeCpu).toContain("reservation");
242
+ });
243
+
244
+ test("a pod's CPU is a share of its NODE, not of its own request or limit", () => {
245
+ expect(D.podCpu).toContain("allocatable CPU on its node");
246
+ expect(D.podCpu).toContain("not of the pod's own CPU request or limit");
247
+ });
248
+
249
+ test("a container's CPU is a share of its node, not of its own limit", () => {
250
+ expect(D.containerCpu).toContain("allocatable CPU on its node");
251
+ expect(D.containerCpu).toContain("not of its own CPU limit");
252
+ });
253
+
254
+ test("no per-resource CPU text claims to be a share of a request or limit", () => {
255
+ for (const key of LIST_LATEST_CPU) {
256
+ expect(D[key]).not.toMatch(
257
+ /share of (its|the pod's) (CPU )?(request|limit)/,
258
+ );
259
+ }
260
+ });
261
+ });
262
+
263
+ describe("what each memory percentage is a share of", () => {
264
+ test("a node's memory is against its allocatable memory, falling back to its total", () => {
265
+ expect(D.nodeMemory).toContain("allocatable memory");
266
+ expect(D.nodeMemory).toContain(
267
+ "total memory when allocatable is not reported",
268
+ );
269
+ expect(D.nodeMemory).toContain("file cache");
270
+ });
271
+
272
+ test("a pod's memory is against the limits its containers set, falling back to its node", () => {
273
+ expect(D.podMemory).toContain(
274
+ "sum of the memory limits its containers set",
275
+ );
276
+ expect(D.podMemory).toContain(
277
+ "node's allocatable memory (what the node can hand out to pods) when none sets a limit",
278
+ );
279
+ });
280
+
281
+ test("a container's memory is against its own limit, or shown as an amount", () => {
282
+ expect(D.containerMemory).toContain("share of its memory limit");
283
+ expect(D.containerMemory).toContain(
284
+ "Without a limit only the amount is shown",
285
+ );
286
+ });
287
+
288
+ test.each(LIST_LATEST_MEMORY)(
289
+ "%s says the capped percentage stops at 100%%",
290
+ (key: KubernetesResourceMetric) => {
291
+ expect(D[key]).toMatch(/(stops at|up to) 100%/);
292
+ },
293
+ );
294
+ });
295
+
296
+ describe("workload and namespace columns are sums over pods", () => {
297
+ test.each(ALL_SUMS)(
298
+ "%s says the pods are added up and the total can pass 100%%",
299
+ (key: KubernetesResourceMetric) => {
300
+ expect(D[key]).toMatch(/added up|summed|the sum of/);
301
+ expect(D[key]).toContain("can pass 100%");
302
+ },
303
+ );
304
+
305
+ test.each(ALL_SUMS)(
306
+ "%s counts only pods that reported in the last 15 minutes",
307
+ (key: KubernetesResourceMetric) => {
308
+ expect(D[key]).toContain("15 minutes");
309
+ },
310
+ );
311
+
312
+ test.each(CPU_SUMS)(
313
+ "%s sums each pod's share of ITS OWN node",
314
+ (key: KubernetesResourceMetric) => {
315
+ expect(D[key]).toMatch(/share of its (own )?node's allocatable CPU/);
316
+ },
317
+ );
318
+
319
+ test.each(MEMORY_SUMS)(
320
+ "%s gives both the amount and the summed share of node memory",
321
+ (key: KubernetesResourceMetric) => {
322
+ expect(D[key]).toContain("total amount");
323
+ expect(D[key]).toContain("node's allocatable memory");
324
+ },
325
+ );
326
+
327
+ /*
328
+ * "Allocatable" is Kubernetes jargon, and each list page shows only its own
329
+ * CPU and Memory columns - so every text that divides by it says what it
330
+ * is, in the same words.
331
+ */
332
+ test.each([
333
+ ...ALL_SUMS,
334
+ "podCpu",
335
+ "podMemory",
336
+ "containerCpu",
337
+ ] as Array<KubernetesResourceMetric>)(
338
+ "%s says what allocatable means",
339
+ (key: KubernetesResourceMetric) => {
340
+ expect(D[key]).toMatch(ALLOCATABLE_GLOSS);
341
+ },
342
+ );
343
+
344
+ test.each(WORKLOAD_SUMS)(
345
+ "%s is honest that same-named workloads in other namespaces are added in",
346
+ (key: KubernetesResourceMetric) => {
347
+ expect(D[key]).toMatch(/same-named \w+ in other namespaces/);
348
+ },
349
+ );
350
+
351
+ test.each(NAMESPACE_SUMS)(
352
+ "%s groups by namespace, so it makes no same-name caveat",
353
+ (key: KubernetesResourceMetric) => {
354
+ expect(D[key]).not.toContain("same-named");
355
+ expect(D[key]).toContain("every pod in this namespace");
356
+ },
357
+ );
358
+
359
+ test("the bar is capped, the number is not", () => {
360
+ expect(D.deploymentCpu).toContain("the bar stops there");
361
+ expect(D.namespaceCpu).toContain("the bar stops there");
362
+ });
363
+
364
+ test("jobs and CronJobs explain why they are usually N/A", () => {
365
+ expect(D.jobCpu).toContain("N/A soon after the job ends");
366
+ expect(D.jobMemory).toContain("N/A soon after the job ends");
367
+ expect(D.cronJobCpu).toContain("N/A between runs");
368
+ expect(D.cronJobMemory).toContain("N/A between runs");
369
+ });
370
+
371
+ test("every workload's texts name their own kind", () => {
372
+ expect(D.deploymentCpu).toContain("deployment's pods");
373
+ expect(D.statefulSetCpu).toContain("StatefulSet's pods");
374
+ expect(D.daemonSetCpu).toContain("DaemonSet's pods");
375
+ expect(D.jobCpu).toContain("job's pods");
376
+ expect(D.cronJobCpu).toContain("CronJob's runs");
377
+ expect(D.deploymentMemory).toContain("deployment's pods");
378
+ expect(D.statefulSetMemory).toContain("StatefulSet's pods");
379
+ expect(D.daemonSetMemory).toContain("DaemonSet's pods");
380
+ expect(D.jobMemory).toContain("job's pods");
381
+ expect(D.cronJobMemory).toContain("CronJob's runs");
382
+ });
383
+ });
384
+
385
+ describe("Kubernetes units and jargon are explained where they appear", () => {
386
+ test("CPU quantities explain the m suffix", () => {
387
+ expect(D.nodeCpuCapacity).toContain("m suffix means thousandths of a core");
388
+ expect(D.containerRequests).toContain(
389
+ "m suffix means thousandths of a core",
390
+ );
391
+ expect(D.vpaRecommendation).toContain("millicores (m)");
392
+ });
393
+
394
+ test("memory and storage quantities explain Ki, Mi and Gi", () => {
395
+ expect(D.nodeMemoryCapacity).toContain(
396
+ "kibibytes, mebibytes and gibibytes",
397
+ );
398
+ expect(D.pvcCapacity).toContain("Gi means gibibytes");
399
+ expect(D.pvCapacity).toContain("Gi is gibibytes");
400
+ });
401
+
402
+ test("capacity and allocatable are told apart", () => {
403
+ for (const key of [
404
+ "nodeCpuCapacity",
405
+ "nodeMemoryCapacity",
406
+ ] as Array<KubernetesResourceMetric>) {
407
+ expect(D[key]).toMatch(/^Capacity is the node's total/);
408
+ expect(D[key]).toContain("allocatable is what");
409
+ }
410
+ });
411
+
412
+ test("limits say what happens when they are crossed", () => {
413
+ expect(D.containerLimits).toContain("throttled");
414
+ expect(D.containerLimits).toContain("OOMKilled");
415
+ expect(D.containerRequests).toContain("placed on a node");
416
+ });
417
+
418
+ test("readiness and availability are defined", () => {
419
+ expect(D.deploymentReadyReplicas).toContain("readiness checks");
420
+ expect(D.deploymentAvailableReplicas).toContain("minReadySeconds");
421
+ expect(D.daemonSetNumberAvailable).toContain("minReadySeconds");
422
+ expect(D.containerReady).toContain("readiness check");
423
+ });
424
+
425
+ test("the HPA target explains that Utilization is a share of CPU requests", () => {
426
+ expect(D.hpaMetrics).toContain("cpu (Utilization: 70)");
427
+ expect(D.hpaMetrics).toContain("70% of the pods' CPU requests");
428
+ });
429
+
430
+ test("a PVC's capacity can differ from what it requested", () => {
431
+ expect(D.pvcCapacity).toContain("more than was requested");
432
+ expect(D.pvcCapacity).toContain("N/A until the claim is bound");
433
+ expect(D.pvcRequestedStorage).toContain(
434
+ "provisioned capacity can be larger",
435
+ );
436
+ });
437
+ });
438
+
439
+ describe("detail fields describe what the page reads", () => {
440
+ test("a pod's restart total leaves init containers out, as the page does", () => {
441
+ expect(D.podRestarts).toContain("not counting init containers");
442
+ expect(D.podContainers).toContain("not counting init containers");
443
+ });
444
+
445
+ test("the rollout status names the exact rule the badge uses", () => {
446
+ expect(D.deploymentRolloutStatus).toContain(
447
+ "ready pods matches the desired count and none are unavailable",
448
+ );
449
+ expect(D.deploymentRolloutStatus).toContain(
450
+ "bar shows ready pods out of desired",
451
+ );
452
+ });
453
+
454
+ test("Unavailable says it only appears when above zero", () => {
455
+ expect(D.deploymentUnavailableReplicas).toContain(
456
+ "Shown only when above zero",
457
+ );
458
+ });
459
+
460
+ test("the colour of a ready badge is explained where it is coloured", () => {
461
+ expect(D.statefulSetReadyReplicas).toContain("Green when all are ready");
462
+ expect(D.daemonSetNumberReady).toContain("Green when all are");
463
+ });
464
+
465
+ test("a job's zero spec values are explained, since the page shows 0 when unset", () => {
466
+ expect(D.jobCompletions).toContain("0 means no count was set");
467
+ expect(D.jobParallelism).toContain("0 means the job is effectively paused");
468
+ expect(D.jobBackoffLimit).toContain("6 unless the job sets another value");
469
+ });
470
+
471
+ test("CronJob Active Jobs is honest that it is not read correctly yet", () => {
472
+ /*
473
+ * The parser reads CronJob status.active - a LIST of job references -
474
+ * with parseInt, so the field is always 0 (see bugsFound). The text must
475
+ * not promise a live count.
476
+ */
477
+ expect(D.cronJobActiveJobs).toContain("shows 0 even during a run");
478
+ expect(D.cronJobActiveJobs).toContain("Jobs list");
479
+ });
480
+
481
+ test("network throughput says it is a per-second rate over the chart's range", () => {
482
+ expect(D.nodeNetworkThroughput).toContain("Bytes per second");
483
+ expect(D.nodeNetworkThroughput).toContain(
484
+ "added up across its network interfaces",
485
+ );
486
+ expect(D.nodeNetworkThroughput).toContain("counters grew");
487
+ expect(D.nodeNetworkThroughput).toContain("selected time range");
488
+ });
489
+
490
+ test("the HPA's desired count is kept between min and max", () => {
491
+ expect(D.hpaDesiredReplicas).toContain("between the min and max");
492
+ expect(D.hpaMinReplicas).toContain("fewest pods");
493
+ expect(D.hpaMaxReplicas).toContain("most pods");
494
+ });
495
+
496
+ test("CronJob history limits say what is kept and the Kubernetes defaults", () => {
497
+ /*
498
+ * Kubernetes defaults successfulJobsHistoryLimit to 3 and
499
+ * failedJobsHistoryLimit to 1, so the stored object carries those.
500
+ */
501
+ expect(D.cronJobSuccessfulJobsHistoryLimit).toContain(
502
+ "successful runs (Jobs)",
503
+ );
504
+ expect(D.cronJobSuccessfulJobsHistoryLimit).toContain(
505
+ "Kubernetes keeps 3 unless",
506
+ );
507
+ expect(D.cronJobFailedJobsHistoryLimit).toContain("failed runs (Jobs)");
508
+ expect(D.cronJobFailedJobsHistoryLimit).toContain(
509
+ "Kubernetes keeps 1 unless",
510
+ );
511
+ });
512
+ });
513
+
514
+ describe("texts do not overstate what Kubernetes reports", () => {
515
+ test("a node's CPU counts system processes too, so it can pass 100%", () => {
516
+ /*
517
+ * k8s.node.cpu.utilization is the whole node's cores in use, divided by
518
+ * ALLOCATABLE cores (capacity minus the system reservation), and the
519
+ * table prints the number uncapped - so a busy node reads over 100%.
520
+ */
521
+ expect(D.nodeCpu).toContain("system processes included");
522
+ expect(D.nodeCpu).toContain("so it can pass 100%");
523
+ });
524
+
525
+ test("a container with no readiness check still counts as ready", () => {
526
+ expect(D.containerReady).toContain(
527
+ "one without a readiness check counts as ready once it runs",
528
+ );
529
+ });
530
+
531
+ test("DaemonSet Current Scheduled counts placed pods, not only started ones", () => {
532
+ expect(D.daemonSetCurrentScheduled).toContain("placed on them");
533
+ expect(D.daemonSetCurrentScheduled).toContain("even if it has not started");
534
+ expect(D.daemonSetCurrentScheduled).not.toContain("running");
535
+ });
536
+
537
+ test("the HPA Metrics field names what the page cannot spell out", () => {
538
+ /*
539
+ * The parser reads only spec.metrics[].resource, so ContainerResource,
540
+ * Pods, Object and External targets render as their bare type.
541
+ */
542
+ expect(D.hpaMetrics).toContain(
543
+ "custom, external and per-container targets show only their type name",
544
+ );
545
+ expect(D.hpaMetrics).not.toContain("other than CPU or memory");
546
+ });
547
+
548
+ test("a CPU limit throttles at the limit; memory kills past it", () => {
549
+ expect(D.containerLimits).toContain("At its CPU limit");
550
+ expect(D.containerLimits).toContain("over its memory limit");
551
+ });
552
+
553
+ test("the pod container count does not claim every init container exits", () => {
554
+ // Native sidecars are init containers that keep running.
555
+ expect(D.podContainers).not.toContain("exit");
556
+ });
557
+
558
+ test("a VPA memory recommendation can carry a decimal k suffix", () => {
559
+ expect(D.vpaRecommendation).toMatch(/\bk\b/);
560
+ });
561
+ });