@oneuptime/common 10.0.65 → 10.0.66
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/DockerHostOwnerTeam.ts +464 -0
- package/Models/DatabaseModels/DockerHostOwnerUser.ts +463 -0
- package/Models/DatabaseModels/Index.ts +24 -0
- package/Models/DatabaseModels/KubernetesClusterOwnerTeam.ts +464 -0
- package/Models/DatabaseModels/KubernetesClusterOwnerUser.ts +463 -0
- package/Models/DatabaseModels/KubernetesResource.ts +548 -0
- package/Models/DatabaseModels/MetricPipelineRule.ts +804 -0
- package/Models/DatabaseModels/MetricRecordingRule.ts +470 -0
- package/Models/DatabaseModels/Monitor.ts +2 -0
- package/Models/DatabaseModels/Project.ts +53 -0
- package/Models/DatabaseModels/Service.ts +79 -0
- package/Models/DatabaseModels/TraceDropFilter.ts +508 -0
- package/Models/DatabaseModels/TracePipeline.ts +436 -0
- package/Models/DatabaseModels/TracePipelineProcessor.ts +454 -0
- package/Models/DatabaseModels/TraceRecordingRule.ts +470 -0
- package/Models/DatabaseModels/TraceScrubRule.ts +546 -0
- package/Server/API/KubernetesResourceAPI.ts +129 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1776504277320-MigrationName.ts +399 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1776505976155-AddTracePipelineTables.ts +205 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1776509413763-MigrationName.ts +335 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1776541018853-MigrationName.ts +29 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1776544084793-MigrationName.ts +53 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -1
- package/Server/Services/DockerHostOwnerTeamService.ts +10 -0
- package/Server/Services/DockerHostOwnerUserService.ts +10 -0
- package/Server/Services/KubernetesClusterOwnerTeamService.ts +10 -0
- package/Server/Services/KubernetesClusterOwnerUserService.ts +10 -0
- package/Server/Services/KubernetesResourceService.ts +351 -0
- package/Server/Services/MetricPipelineRuleService.ts +10 -0
- package/Server/Services/MetricRecordingRuleService.ts +10 -0
- package/Server/Services/TraceDropFilterService.ts +10 -0
- package/Server/Services/TracePipelineProcessorService.ts +10 -0
- package/Server/Services/TracePipelineService.ts +10 -0
- package/Server/Services/TraceRecordingRuleService.ts +10 -0
- package/Server/Services/TraceScrubRuleService.ts +10 -0
- package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +71 -9
- package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +483 -75
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +379 -6
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.ts +502 -0
- package/Tests/Utils/MetricUnitUtil.test.ts +216 -0
- package/Tests/Utils/Metrics/MetricFormulaEvaluator.test.ts +269 -0
- package/Tests/Utils/Metrics/MetricResultUnitConverter.test.ts +231 -0
- package/Tests/Utils/RecordingRuleExpression.test.ts +177 -0
- package/Types/Kubernetes/KubernetesInventoryExtractor.ts +327 -0
- package/Types/Kubernetes/KubernetesObjectParser.ts +1949 -0
- package/Types/Metrics/MetricDownsamplingRetentionDays.ts +49 -0
- package/Types/Metrics/MetricFormulaConfigData.ts +4 -0
- package/Types/Metrics/MetricPipelineRuleFilterCondition.ts +136 -0
- package/Types/Metrics/MetricPipelineRuleType.ts +27 -0
- package/Types/Metrics/RecordingRuleDefinition.ts +180 -0
- package/Types/Monitor/CriteriaFilter.ts +43 -0
- package/Types/Monitor/MetricMonitor/MetricCriteriaContext.ts +70 -0
- package/Types/Permission.ts +520 -0
- package/Types/Trace/TraceAggregationType.ts +17 -0
- package/Types/Trace/TraceDropFilterAction.ts +6 -0
- package/Types/Trace/TracePipelineProcessorType.ts +56 -0
- package/Types/Trace/TraceRecordingRuleDefinition.ts +218 -0
- package/Types/Trace/TraceScrubAction.ts +7 -0
- package/Types/Trace/TraceScrubField.ts +8 -0
- package/Types/Trace/TraceScrubPatternType.ts +10 -0
- package/UI/Components/CardSelect/CardSelect.tsx +9 -1
- package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +6 -10
- package/UI/Components/Forms/Fields/FormField.tsx +1 -0
- package/UI/Components/Forms/Types/Field.ts +1 -0
- package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +57 -0
- package/UI/Components/Page/Page.tsx +6 -0
- package/Utils/MetricUnitUtil.ts +289 -0
- package/Utils/Metrics/MetricFormulaEvaluator.ts +610 -0
- package/Utils/Metrics/MetricResultUnitConverter.ts +91 -0
- package/Utils/Metrics/RecordingRuleExpression.ts +359 -0
- package/Utils/ValueFormatter.ts +137 -13
- package/build/dist/Models/DatabaseModels/DockerHostOwnerTeam.js +480 -0
- package/build/dist/Models/DatabaseModels/DockerHostOwnerTeam.js.map +1 -0
- package/build/dist/Models/DatabaseModels/DockerHostOwnerUser.js +479 -0
- package/build/dist/Models/DatabaseModels/DockerHostOwnerUser.js.map +1 -0
- package/build/dist/Models/DatabaseModels/Index.js +24 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesClusterOwnerTeam.js +480 -0
- package/build/dist/Models/DatabaseModels/KubernetesClusterOwnerTeam.js.map +1 -0
- package/build/dist/Models/DatabaseModels/KubernetesClusterOwnerUser.js +479 -0
- package/build/dist/Models/DatabaseModels/KubernetesClusterOwnerUser.js.map +1 -0
- package/build/dist/Models/DatabaseModels/KubernetesResource.js +590 -0
- package/build/dist/Models/DatabaseModels/KubernetesResource.js.map +1 -0
- package/build/dist/Models/DatabaseModels/MetricPipelineRule.js +836 -0
- package/build/dist/Models/DatabaseModels/MetricPipelineRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/MetricRecordingRule.js +497 -0
- package/build/dist/Models/DatabaseModels/MetricRecordingRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/Monitor.js +2 -0
- package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +53 -0
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Service.js +79 -0
- package/build/dist/Models/DatabaseModels/Service.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TraceDropFilter.js +536 -0
- package/build/dist/Models/DatabaseModels/TraceDropFilter.js.map +1 -0
- package/build/dist/Models/DatabaseModels/TracePipeline.js +462 -0
- package/build/dist/Models/DatabaseModels/TracePipeline.js.map +1 -0
- package/build/dist/Models/DatabaseModels/TracePipelineProcessor.js +476 -0
- package/build/dist/Models/DatabaseModels/TracePipelineProcessor.js.map +1 -0
- package/build/dist/Models/DatabaseModels/TraceRecordingRule.js +497 -0
- package/build/dist/Models/DatabaseModels/TraceRecordingRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/TraceScrubRule.js +575 -0
- package/build/dist/Models/DatabaseModels/TraceScrubRule.js.map +1 -0
- package/build/dist/Server/API/KubernetesResourceAPI.js +98 -0
- package/build/dist/Server/API/KubernetesResourceAPI.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776504277320-MigrationName.js +144 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776504277320-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776505976155-AddTracePipelineTables.js +82 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776505976155-AddTracePipelineTables.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776509413763-MigrationName.js +118 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776509413763-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776541018853-MigrationName.js +16 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776541018853-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776544084793-MigrationName.js +24 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776544084793-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/DockerHostOwnerTeamService.js +9 -0
- package/build/dist/Server/Services/DockerHostOwnerTeamService.js.map +1 -0
- package/build/dist/Server/Services/DockerHostOwnerUserService.js +9 -0
- package/build/dist/Server/Services/DockerHostOwnerUserService.js.map +1 -0
- package/build/dist/Server/Services/KubernetesClusterOwnerTeamService.js +9 -0
- package/build/dist/Server/Services/KubernetesClusterOwnerTeamService.js.map +1 -0
- package/build/dist/Server/Services/KubernetesClusterOwnerUserService.js +9 -0
- package/build/dist/Server/Services/KubernetesClusterOwnerUserService.js.map +1 -0
- package/build/dist/Server/Services/KubernetesResourceService.js +237 -0
- package/build/dist/Server/Services/KubernetesResourceService.js.map +1 -0
- package/build/dist/Server/Services/MetricPipelineRuleService.js +9 -0
- package/build/dist/Server/Services/MetricPipelineRuleService.js.map +1 -0
- package/build/dist/Server/Services/MetricRecordingRuleService.js +9 -0
- package/build/dist/Server/Services/MetricRecordingRuleService.js.map +1 -0
- package/build/dist/Server/Services/TraceDropFilterService.js +9 -0
- package/build/dist/Server/Services/TraceDropFilterService.js.map +1 -0
- package/build/dist/Server/Services/TracePipelineProcessorService.js +9 -0
- package/build/dist/Server/Services/TracePipelineProcessorService.js.map +1 -0
- package/build/dist/Server/Services/TracePipelineService.js +9 -0
- package/build/dist/Server/Services/TracePipelineService.js.map +1 -0
- package/build/dist/Server/Services/TraceRecordingRuleService.js +9 -0
- package/build/dist/Server/Services/TraceRecordingRuleService.js.map +1 -0
- package/build/dist/Server/Services/TraceScrubRuleService.js +9 -0
- package/build/dist/Server/Services/TraceScrubRuleService.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +56 -9
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +335 -53
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +277 -5
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.js +407 -0
- package/build/dist/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.js.map +1 -0
- package/build/dist/Tests/Utils/MetricUnitUtil.test.js +159 -0
- package/build/dist/Tests/Utils/MetricUnitUtil.test.js.map +1 -0
- package/build/dist/Tests/Utils/Metrics/MetricFormulaEvaluator.test.js +224 -0
- package/build/dist/Tests/Utils/Metrics/MetricFormulaEvaluator.test.js.map +1 -0
- package/build/dist/Tests/Utils/Metrics/MetricResultUnitConverter.test.js +180 -0
- package/build/dist/Tests/Utils/Metrics/MetricResultUnitConverter.test.js.map +1 -0
- package/build/dist/Tests/Utils/RecordingRuleExpression.test.js +142 -0
- package/build/dist/Tests/Utils/RecordingRuleExpression.test.js.map +1 -0
- package/build/dist/Types/Kubernetes/KubernetesInventoryExtractor.js +200 -0
- package/build/dist/Types/Kubernetes/KubernetesInventoryExtractor.js.map +1 -0
- package/build/dist/Types/Kubernetes/KubernetesObjectParser.js +1205 -0
- package/build/dist/Types/Kubernetes/KubernetesObjectParser.js.map +1 -0
- package/build/dist/Types/Metrics/MetricDownsamplingRetentionDays.js +32 -0
- package/build/dist/Types/Metrics/MetricDownsamplingRetentionDays.js.map +1 -0
- package/build/dist/Types/Metrics/MetricPipelineRuleFilterCondition.js +103 -0
- package/build/dist/Types/Metrics/MetricPipelineRuleFilterCondition.js.map +1 -0
- package/build/dist/Types/Metrics/MetricPipelineRuleType.js +27 -0
- package/build/dist/Types/Metrics/MetricPipelineRuleType.js.map +1 -0
- package/build/dist/Types/Metrics/RecordingRuleDefinition.js +110 -0
- package/build/dist/Types/Metrics/RecordingRuleDefinition.js.map +1 -0
- package/build/dist/Types/Monitor/CriteriaFilter.js +22 -0
- package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
- package/build/dist/Types/Monitor/MetricMonitor/MetricCriteriaContext.js +2 -0
- package/build/dist/Types/Monitor/MetricMonitor/MetricCriteriaContext.js.map +1 -0
- package/build/dist/Types/Permission.js +454 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Trace/TraceAggregationType.js +18 -0
- package/build/dist/Types/Trace/TraceAggregationType.js.map +1 -0
- package/build/dist/Types/Trace/TraceDropFilterAction.js +7 -0
- package/build/dist/Types/Trace/TraceDropFilterAction.js.map +1 -0
- package/build/dist/Types/Trace/TracePipelineProcessorType.js +10 -0
- package/build/dist/Types/Trace/TracePipelineProcessorType.js.map +1 -0
- package/build/dist/Types/Trace/TraceRecordingRuleDefinition.js +145 -0
- package/build/dist/Types/Trace/TraceRecordingRuleDefinition.js.map +1 -0
- package/build/dist/Types/Trace/TraceScrubAction.js +8 -0
- package/build/dist/Types/Trace/TraceScrubAction.js.map +1 -0
- package/build/dist/Types/Trace/TraceScrubField.js +9 -0
- package/build/dist/Types/Trace/TraceScrubField.js.map +1 -0
- package/build/dist/Types/Trace/TraceScrubPatternType.js +11 -0
- package/build/dist/Types/Trace/TraceScrubPatternType.js.map +1 -0
- package/build/dist/UI/Components/CardSelect/CardSelect.js +3 -1
- package/build/dist/UI/Components/CardSelect/CardSelect.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +6 -9
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js +1 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +30 -0
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
- package/build/dist/UI/Components/Page/Page.js +1 -0
- package/build/dist/UI/Components/Page/Page.js.map +1 -1
- package/build/dist/Utils/MetricUnitUtil.js +232 -0
- package/build/dist/Utils/MetricUnitUtil.js.map +1 -0
- package/build/dist/Utils/Metrics/MetricFormulaEvaluator.js +453 -0
- package/build/dist/Utils/Metrics/MetricFormulaEvaluator.js.map +1 -0
- package/build/dist/Utils/Metrics/MetricResultUnitConverter.js +61 -0
- package/build/dist/Utils/Metrics/MetricResultUnitConverter.js.map +1 -0
- package/build/dist/Utils/Metrics/RecordingRuleExpression.js +298 -0
- package/build/dist/Utils/Metrics/RecordingRuleExpression.js.map +1 -0
- package/build/dist/Utils/ValueFormatter.js +123 -13
- package/build/dist/Utils/ValueFormatter.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import DatabaseService from "./DatabaseService";
|
|
2
|
+
import Model from "../../Models/DatabaseModels/KubernetesResource";
|
|
3
|
+
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
4
|
+
import ObjectID from "../../Types/ObjectID";
|
|
5
|
+
import OneUptimeDate from "../../Types/Date";
|
|
6
|
+
import { ParsedKubernetesResource } from "../../Types/Kubernetes/KubernetesInventoryExtractor";
|
|
7
|
+
import logger from "../Utils/Logger";
|
|
8
|
+
|
|
9
|
+
/*
|
|
10
|
+
* ------------------------------------------------------------------
|
|
11
|
+
* KubernetesResourceService
|
|
12
|
+
*
|
|
13
|
+
* Writes and reads the inventory table populated by the telemetry
|
|
14
|
+
* ingest path. Callers are either:
|
|
15
|
+
* - OtelLogsIngestService (bulkUpsert, from processLogsAsync)
|
|
16
|
+
* - CleanupStaleResources worker (deleteStaleForCluster)
|
|
17
|
+
* - KubernetesResourceAPI / KubernetesObjectFetcher (reads via
|
|
18
|
+
* the inherited DatabaseService CRUD)
|
|
19
|
+
*
|
|
20
|
+
* ------------------------------------------------------------------
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export type { ParsedKubernetesResource };
|
|
24
|
+
|
|
25
|
+
export interface InventorySummary {
|
|
26
|
+
countsByKind: Record<string, number>;
|
|
27
|
+
/*
|
|
28
|
+
* Sum of `jsonb_array_length(spec->'containers')` across all pods in
|
|
29
|
+
* the cluster. Containers aren't a top-level kind in the inventory,
|
|
30
|
+
* so we derive the total server-side so the sidebar badge and the
|
|
31
|
+
* Containers page agree.
|
|
32
|
+
*/
|
|
33
|
+
containerCount: number;
|
|
34
|
+
podPhaseCounts: {
|
|
35
|
+
running: number;
|
|
36
|
+
pending: number;
|
|
37
|
+
failed: number;
|
|
38
|
+
succeeded: number;
|
|
39
|
+
unknown: number;
|
|
40
|
+
};
|
|
41
|
+
nodeReadyCounts: {
|
|
42
|
+
ready: number;
|
|
43
|
+
notReady: number;
|
|
44
|
+
};
|
|
45
|
+
nodePressureCounts: {
|
|
46
|
+
memoryPressure: number;
|
|
47
|
+
diskPressure: number;
|
|
48
|
+
pidPressure: number;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const UPSERT_BATCH_SIZE: number = 500;
|
|
53
|
+
const STALE_DELETE_WARN_THRESHOLD: number = 100;
|
|
54
|
+
|
|
55
|
+
/*
|
|
56
|
+
* Column order used by both bulkUpsert() and its generated parameter tuples.
|
|
57
|
+
* Keep this and the INSERT column list in perfect sync.
|
|
58
|
+
*/
|
|
59
|
+
const UPSERT_COLUMNS: Array<keyof ParsedKubernetesResource | string> = [
|
|
60
|
+
"projectId",
|
|
61
|
+
"kubernetesClusterId",
|
|
62
|
+
"kind",
|
|
63
|
+
"namespaceKey",
|
|
64
|
+
"name",
|
|
65
|
+
"uid",
|
|
66
|
+
"phase",
|
|
67
|
+
"isReady",
|
|
68
|
+
"hasMemoryPressure",
|
|
69
|
+
"hasDiskPressure",
|
|
70
|
+
"hasPidPressure",
|
|
71
|
+
"labels",
|
|
72
|
+
"annotations",
|
|
73
|
+
"ownerReferences",
|
|
74
|
+
"spec",
|
|
75
|
+
"status",
|
|
76
|
+
"lastSeenAt",
|
|
77
|
+
"resourceCreationTimestamp",
|
|
78
|
+
"version",
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
export class Service extends DatabaseService<Model> {
|
|
82
|
+
public constructor() {
|
|
83
|
+
super(Model);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Upsert a batch of parsed resources for a single (project, cluster)
|
|
88
|
+
* pair. Uses ON CONFLICT on the UNIQUE (projectId, clusterId, kind,
|
|
89
|
+
* namespaceKey, name) index with a dominance guard on lastSeenAt
|
|
90
|
+
* so out-of-order ingest never regresses a newer snapshot.
|
|
91
|
+
*/
|
|
92
|
+
@CaptureSpan()
|
|
93
|
+
public async bulkUpsert(data: {
|
|
94
|
+
projectId: ObjectID;
|
|
95
|
+
kubernetesClusterId: ObjectID;
|
|
96
|
+
resources: Array<ParsedKubernetesResource>;
|
|
97
|
+
}): Promise<void> {
|
|
98
|
+
if (data.resources.length === 0) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Chunk to keep individual statement parameter counts reasonable.
|
|
103
|
+
for (let i: number = 0; i < data.resources.length; i += UPSERT_BATCH_SIZE) {
|
|
104
|
+
const chunk: Array<ParsedKubernetesResource> = data.resources.slice(
|
|
105
|
+
i,
|
|
106
|
+
i + UPSERT_BATCH_SIZE,
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const valueFragments: Array<string> = [];
|
|
110
|
+
const params: Array<unknown> = [];
|
|
111
|
+
let paramIndex: number = 1;
|
|
112
|
+
|
|
113
|
+
for (const r of chunk) {
|
|
114
|
+
const placeholders: Array<string> = [];
|
|
115
|
+
for (let c: number = 0; c < UPSERT_COLUMNS.length; c++) {
|
|
116
|
+
placeholders.push(`$${paramIndex++}`);
|
|
117
|
+
}
|
|
118
|
+
valueFragments.push(`(${placeholders.join(", ")})`);
|
|
119
|
+
|
|
120
|
+
params.push(
|
|
121
|
+
data.projectId.toString(),
|
|
122
|
+
data.kubernetesClusterId.toString(),
|
|
123
|
+
r.kind,
|
|
124
|
+
r.namespaceKey,
|
|
125
|
+
r.name,
|
|
126
|
+
r.uid,
|
|
127
|
+
r.phase,
|
|
128
|
+
r.isReady,
|
|
129
|
+
r.hasMemoryPressure,
|
|
130
|
+
r.hasDiskPressure,
|
|
131
|
+
r.hasPidPressure,
|
|
132
|
+
r.labels ? JSON.stringify(r.labels) : null,
|
|
133
|
+
r.annotations ? JSON.stringify(r.annotations) : null,
|
|
134
|
+
r.ownerReferences ? JSON.stringify(r.ownerReferences) : null,
|
|
135
|
+
r.spec ? JSON.stringify(r.spec) : null,
|
|
136
|
+
r.status ? JSON.stringify(r.status) : null,
|
|
137
|
+
r.lastSeenAt,
|
|
138
|
+
r.resourceCreationTimestamp,
|
|
139
|
+
0, // version (BaseModel @VersionColumn)
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const sql: string = `
|
|
144
|
+
INSERT INTO "KubernetesResource" (
|
|
145
|
+
"projectId", "kubernetesClusterId", "kind", "namespaceKey", "name",
|
|
146
|
+
"uid", "phase", "isReady",
|
|
147
|
+
"hasMemoryPressure", "hasDiskPressure", "hasPidPressure",
|
|
148
|
+
"labels", "annotations", "ownerReferences", "spec", "status",
|
|
149
|
+
"lastSeenAt", "resourceCreationTimestamp", "version"
|
|
150
|
+
)
|
|
151
|
+
VALUES ${valueFragments.join(", ")}
|
|
152
|
+
ON CONFLICT ("projectId", "kubernetesClusterId", "kind", "namespaceKey", "name")
|
|
153
|
+
DO UPDATE SET
|
|
154
|
+
"uid" = EXCLUDED."uid",
|
|
155
|
+
"phase" = EXCLUDED."phase",
|
|
156
|
+
"isReady" = EXCLUDED."isReady",
|
|
157
|
+
"hasMemoryPressure" = EXCLUDED."hasMemoryPressure",
|
|
158
|
+
"hasDiskPressure" = EXCLUDED."hasDiskPressure",
|
|
159
|
+
"hasPidPressure" = EXCLUDED."hasPidPressure",
|
|
160
|
+
"labels" = EXCLUDED."labels",
|
|
161
|
+
"annotations" = EXCLUDED."annotations",
|
|
162
|
+
"ownerReferences" = EXCLUDED."ownerReferences",
|
|
163
|
+
"spec" = EXCLUDED."spec",
|
|
164
|
+
"status" = EXCLUDED."status",
|
|
165
|
+
"lastSeenAt" = EXCLUDED."lastSeenAt",
|
|
166
|
+
"resourceCreationTimestamp" = EXCLUDED."resourceCreationTimestamp",
|
|
167
|
+
"updatedAt" = now()
|
|
168
|
+
WHERE EXCLUDED."lastSeenAt" >= "KubernetesResource"."lastSeenAt"
|
|
169
|
+
`;
|
|
170
|
+
|
|
171
|
+
await this.getRepository().manager.query(sql, params);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Hard-delete all resources in a cluster whose last snapshot is
|
|
177
|
+
* older than olderThan. Returns the number of deleted rows.
|
|
178
|
+
*/
|
|
179
|
+
@CaptureSpan()
|
|
180
|
+
public async deleteStaleForCluster(data: {
|
|
181
|
+
kubernetesClusterId: ObjectID;
|
|
182
|
+
olderThan: Date;
|
|
183
|
+
}): Promise<number> {
|
|
184
|
+
const result: Array<{ affected?: number }> | { affected?: number } =
|
|
185
|
+
await this.getRepository().manager.query(
|
|
186
|
+
`DELETE FROM "KubernetesResource" WHERE "kubernetesClusterId" = $1 AND "lastSeenAt" < $2`,
|
|
187
|
+
[data.kubernetesClusterId.toString(), data.olderThan],
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
// Postgres driver returns [rows, affected] for DELETE — normalize.
|
|
191
|
+
let affected: number = 0;
|
|
192
|
+
if (Array.isArray(result) && result.length >= 2) {
|
|
193
|
+
const second: unknown = (result as Array<unknown>)[1];
|
|
194
|
+
if (typeof second === "number") {
|
|
195
|
+
affected = second;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (affected > STALE_DELETE_WARN_THRESHOLD) {
|
|
200
|
+
logger.warn(
|
|
201
|
+
`KubernetesResource cleanup deleted ${affected} stale rows for cluster ${data.kubernetesClusterId.toString()} — larger than expected; investigate agent health.`,
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return affected;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Compute the overview-page summary in Postgres. Returns counts per
|
|
210
|
+
* resource kind plus pod phase / node condition breakdowns in a
|
|
211
|
+
* single round-trip.
|
|
212
|
+
*/
|
|
213
|
+
@CaptureSpan()
|
|
214
|
+
public async getInventorySummary(data: {
|
|
215
|
+
projectId: ObjectID;
|
|
216
|
+
kubernetesClusterId: ObjectID;
|
|
217
|
+
}): Promise<InventorySummary> {
|
|
218
|
+
const manager: ReturnType<Service["getRepository"]>["manager"] =
|
|
219
|
+
this.getRepository().manager;
|
|
220
|
+
|
|
221
|
+
const [kindRows, podRows, nodeRows, containerRows]: [
|
|
222
|
+
Array<{ kind: string; count: string }>,
|
|
223
|
+
Array<{ phase: string | null; count: string }>,
|
|
224
|
+
Array<{
|
|
225
|
+
ready: string;
|
|
226
|
+
notReady: string;
|
|
227
|
+
memoryPressure: string;
|
|
228
|
+
diskPressure: string;
|
|
229
|
+
pidPressure: string;
|
|
230
|
+
}>,
|
|
231
|
+
Array<{ total: string }>,
|
|
232
|
+
] = await Promise.all([
|
|
233
|
+
manager.query(
|
|
234
|
+
`SELECT "kind", COUNT(*)::text AS count
|
|
235
|
+
FROM "KubernetesResource"
|
|
236
|
+
WHERE "projectId" = $1 AND "kubernetesClusterId" = $2 AND "deletedAt" IS NULL
|
|
237
|
+
GROUP BY "kind"`,
|
|
238
|
+
[data.projectId.toString(), data.kubernetesClusterId.toString()],
|
|
239
|
+
),
|
|
240
|
+
manager.query(
|
|
241
|
+
`SELECT "phase", COUNT(*)::text AS count
|
|
242
|
+
FROM "KubernetesResource"
|
|
243
|
+
WHERE "projectId" = $1 AND "kubernetesClusterId" = $2 AND "kind" = 'Pod' AND "deletedAt" IS NULL
|
|
244
|
+
GROUP BY "phase"`,
|
|
245
|
+
[data.projectId.toString(), data.kubernetesClusterId.toString()],
|
|
246
|
+
),
|
|
247
|
+
manager.query(
|
|
248
|
+
`SELECT
|
|
249
|
+
COUNT(*) FILTER (WHERE "isReady" IS TRUE)::text AS "ready",
|
|
250
|
+
COUNT(*) FILTER (WHERE "isReady" IS FALSE)::text AS "notReady",
|
|
251
|
+
COUNT(*) FILTER (WHERE "hasMemoryPressure" IS TRUE)::text AS "memoryPressure",
|
|
252
|
+
COUNT(*) FILTER (WHERE "hasDiskPressure" IS TRUE)::text AS "diskPressure",
|
|
253
|
+
COUNT(*) FILTER (WHERE "hasPidPressure" IS TRUE)::text AS "pidPressure"
|
|
254
|
+
FROM "KubernetesResource"
|
|
255
|
+
WHERE "projectId" = $1 AND "kubernetesClusterId" = $2 AND "kind" = 'Node' AND "deletedAt" IS NULL`,
|
|
256
|
+
[data.projectId.toString(), data.kubernetesClusterId.toString()],
|
|
257
|
+
),
|
|
258
|
+
manager.query(
|
|
259
|
+
`SELECT COALESCE(SUM(
|
|
260
|
+
CASE WHEN jsonb_typeof("spec"->'containers') = 'array'
|
|
261
|
+
THEN jsonb_array_length("spec"->'containers')
|
|
262
|
+
ELSE 0 END
|
|
263
|
+
), 0)::text AS total
|
|
264
|
+
FROM "KubernetesResource"
|
|
265
|
+
WHERE "projectId" = $1 AND "kubernetesClusterId" = $2 AND "kind" = 'Pod' AND "deletedAt" IS NULL`,
|
|
266
|
+
[data.projectId.toString(), data.kubernetesClusterId.toString()],
|
|
267
|
+
),
|
|
268
|
+
]);
|
|
269
|
+
|
|
270
|
+
const countsByKind: Record<string, number> = {};
|
|
271
|
+
for (const row of kindRows) {
|
|
272
|
+
countsByKind[row.kind] = parseInt(row.count, 10) || 0;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const podPhaseCounts: InventorySummary["podPhaseCounts"] = {
|
|
276
|
+
running: 0,
|
|
277
|
+
pending: 0,
|
|
278
|
+
failed: 0,
|
|
279
|
+
succeeded: 0,
|
|
280
|
+
unknown: 0,
|
|
281
|
+
};
|
|
282
|
+
for (const row of podRows) {
|
|
283
|
+
const phase: string = row.phase || "Unknown";
|
|
284
|
+
const count: number = parseInt(row.count, 10) || 0;
|
|
285
|
+
if (phase === "Running") {
|
|
286
|
+
podPhaseCounts.running = count;
|
|
287
|
+
} else if (phase === "Pending") {
|
|
288
|
+
podPhaseCounts.pending = count;
|
|
289
|
+
} else if (phase === "Failed") {
|
|
290
|
+
podPhaseCounts.failed = count;
|
|
291
|
+
} else if (phase === "Succeeded") {
|
|
292
|
+
podPhaseCounts.succeeded = count;
|
|
293
|
+
} else {
|
|
294
|
+
podPhaseCounts.unknown += count;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const nodeRow:
|
|
299
|
+
| {
|
|
300
|
+
ready: string;
|
|
301
|
+
notReady: string;
|
|
302
|
+
memoryPressure: string;
|
|
303
|
+
diskPressure: string;
|
|
304
|
+
pidPressure: string;
|
|
305
|
+
}
|
|
306
|
+
| undefined = nodeRows[0];
|
|
307
|
+
|
|
308
|
+
const containerCount: number =
|
|
309
|
+
parseInt(containerRows[0]?.total || "0", 10) || 0;
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
countsByKind,
|
|
313
|
+
containerCount,
|
|
314
|
+
podPhaseCounts,
|
|
315
|
+
nodeReadyCounts: {
|
|
316
|
+
ready: parseInt(nodeRow?.ready || "0", 10) || 0,
|
|
317
|
+
notReady: parseInt(nodeRow?.notReady || "0", 10) || 0,
|
|
318
|
+
},
|
|
319
|
+
nodePressureCounts: {
|
|
320
|
+
memoryPressure: parseInt(nodeRow?.memoryPressure || "0", 10) || 0,
|
|
321
|
+
diskPressure: parseInt(nodeRow?.diskPressure || "0", 10) || 0,
|
|
322
|
+
pidPressure: parseInt(nodeRow?.pidPressure || "0", 10) || 0,
|
|
323
|
+
},
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Helper for the cleanup worker: snapshot-interval aware cutoff.
|
|
329
|
+
* 3× the 5-minute snapshot interval. Tune via CLEANUP_THRESHOLD_MINUTES.
|
|
330
|
+
*/
|
|
331
|
+
public getStaleThresholdDate(nowOverride?: Date): Date {
|
|
332
|
+
const minutes: number = this.getStaleThresholdMinutes();
|
|
333
|
+
return OneUptimeDate.addRemoveMinutes(
|
|
334
|
+
nowOverride || OneUptimeDate.getCurrentDate(),
|
|
335
|
+
-minutes,
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
public getStaleThresholdMinutes(): number {
|
|
340
|
+
const raw: string | undefined = process.env["K8S_INVENTORY_STALE_MINUTES"];
|
|
341
|
+
if (raw) {
|
|
342
|
+
const parsed: number = parseInt(raw, 10);
|
|
343
|
+
if (!isNaN(parsed) && parsed >= 5) {
|
|
344
|
+
return parsed;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return 15;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export default new Service();
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import DatabaseService from "./DatabaseService";
|
|
2
|
+
import Model from "../../Models/DatabaseModels/MetricRecordingRule";
|
|
3
|
+
|
|
4
|
+
export class Service extends DatabaseService<Model> {
|
|
5
|
+
public constructor() {
|
|
6
|
+
super(Model);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default new Service();
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import DatabaseService from "./DatabaseService";
|
|
2
|
+
import Model from "../../Models/DatabaseModels/TracePipelineProcessor";
|
|
3
|
+
|
|
4
|
+
export class Service extends DatabaseService<Model> {
|
|
5
|
+
public constructor() {
|
|
6
|
+
super(Model);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default new Service();
|
|
@@ -389,6 +389,8 @@ export default class CompareCriteria {
|
|
|
389
389
|
value: Array<number> | number;
|
|
390
390
|
threshold: number;
|
|
391
391
|
criteriaFilter: CriteriaFilter;
|
|
392
|
+
metricDisplayName?: string | undefined;
|
|
393
|
+
unit?: string | undefined;
|
|
392
394
|
}): string | null {
|
|
393
395
|
if (data.value === null || data.value === undefined) {
|
|
394
396
|
return null;
|
|
@@ -412,6 +414,8 @@ export default class CompareCriteria {
|
|
|
412
414
|
values: data.value,
|
|
413
415
|
threshold: data.threshold as number,
|
|
414
416
|
criteriaFilter: data.criteriaFilter,
|
|
417
|
+
metricDisplayName: data.metricDisplayName,
|
|
418
|
+
unit: data.unit,
|
|
415
419
|
});
|
|
416
420
|
}
|
|
417
421
|
|
|
@@ -432,6 +436,8 @@ export default class CompareCriteria {
|
|
|
432
436
|
values: data.value,
|
|
433
437
|
threshold: data.threshold as number,
|
|
434
438
|
criteriaFilter: data.criteriaFilter,
|
|
439
|
+
metricDisplayName: data.metricDisplayName,
|
|
440
|
+
unit: data.unit,
|
|
435
441
|
});
|
|
436
442
|
}
|
|
437
443
|
|
|
@@ -452,6 +458,8 @@ export default class CompareCriteria {
|
|
|
452
458
|
values: data.value,
|
|
453
459
|
threshold: data.threshold as number,
|
|
454
460
|
criteriaFilter: data.criteriaFilter,
|
|
461
|
+
metricDisplayName: data.metricDisplayName,
|
|
462
|
+
unit: data.unit,
|
|
455
463
|
});
|
|
456
464
|
}
|
|
457
465
|
|
|
@@ -472,6 +480,8 @@ export default class CompareCriteria {
|
|
|
472
480
|
values: data.value,
|
|
473
481
|
threshold: data.threshold as number,
|
|
474
482
|
criteriaFilter: data.criteriaFilter,
|
|
483
|
+
metricDisplayName: data.metricDisplayName,
|
|
484
|
+
unit: data.unit,
|
|
475
485
|
});
|
|
476
486
|
}
|
|
477
487
|
|
|
@@ -492,6 +502,8 @@ export default class CompareCriteria {
|
|
|
492
502
|
values: data.value,
|
|
493
503
|
threshold: data.threshold as number,
|
|
494
504
|
criteriaFilter: data.criteriaFilter,
|
|
505
|
+
metricDisplayName: data.metricDisplayName,
|
|
506
|
+
unit: data.unit,
|
|
495
507
|
});
|
|
496
508
|
}
|
|
497
509
|
|
|
@@ -512,6 +524,8 @@ export default class CompareCriteria {
|
|
|
512
524
|
values: data.value,
|
|
513
525
|
threshold: data.threshold as number,
|
|
514
526
|
criteriaFilter: data.criteriaFilter,
|
|
527
|
+
metricDisplayName: data.metricDisplayName,
|
|
528
|
+
unit: data.unit,
|
|
515
529
|
});
|
|
516
530
|
}
|
|
517
531
|
|
|
@@ -526,6 +540,8 @@ export default class CompareCriteria {
|
|
|
526
540
|
values: Array<number | boolean> | number | boolean | string;
|
|
527
541
|
threshold: number | string | boolean;
|
|
528
542
|
criteriaFilter: CriteriaFilter;
|
|
543
|
+
metricDisplayName?: string | undefined;
|
|
544
|
+
unit?: string | undefined;
|
|
529
545
|
}): string {
|
|
530
546
|
// CPU Percent over the last 5 minutes is 10 which is less than the threshold of 20
|
|
531
547
|
let message: string = "";
|
|
@@ -546,7 +562,17 @@ export default class CompareCriteria {
|
|
|
546
562
|
message += "All values of";
|
|
547
563
|
}
|
|
548
564
|
|
|
549
|
-
|
|
565
|
+
/*
|
|
566
|
+
* Prefer a metric-specific display name over the generic "Metric Value"
|
|
567
|
+
* label when evaluating metric monitors.
|
|
568
|
+
*/
|
|
569
|
+
const label: string =
|
|
570
|
+
data.metricDisplayName &&
|
|
571
|
+
data.criteriaFilter.checkOn === CheckOn.MetricValue
|
|
572
|
+
? data.metricDisplayName
|
|
573
|
+
: data.criteriaFilter.checkOn;
|
|
574
|
+
|
|
575
|
+
message += ` ${label}`;
|
|
550
576
|
|
|
551
577
|
if (data.criteriaFilter.checkOn === CheckOn.DiskUsagePercent) {
|
|
552
578
|
const diskPath: string =
|
|
@@ -562,6 +588,8 @@ export default class CompareCriteria {
|
|
|
562
588
|
message += ` over the last ${data.criteriaFilter.evaluateOverTimeOptions.timeValueInMinutes} minutes`;
|
|
563
589
|
}
|
|
564
590
|
|
|
591
|
+
const unitSuffix: string = data.unit ? ` ${data.unit}` : "";
|
|
592
|
+
|
|
565
593
|
if (
|
|
566
594
|
data.criteriaFilter.filterType !== FilterType.True &&
|
|
567
595
|
data.criteriaFilter.filterType !== FilterType.False
|
|
@@ -570,29 +598,29 @@ export default class CompareCriteria {
|
|
|
570
598
|
data.values,
|
|
571
599
|
);
|
|
572
600
|
|
|
573
|
-
message += ` is ${formattedValues}`;
|
|
601
|
+
message += ` is ${formattedValues}${unitSuffix}`;
|
|
574
602
|
|
|
575
603
|
message += " which is";
|
|
576
604
|
}
|
|
577
605
|
|
|
578
606
|
switch (data.criteriaFilter.filterType) {
|
|
579
607
|
case FilterType.GreaterThan:
|
|
580
|
-
message += ` greater than ${CompareCriteria.formatSingleValue(data.threshold)}. `;
|
|
608
|
+
message += ` greater than ${CompareCriteria.formatSingleValue(data.threshold)}${unitSuffix}. `;
|
|
581
609
|
break;
|
|
582
610
|
case FilterType.GreaterThanOrEqualTo:
|
|
583
|
-
message += ` greater than or equal to ${CompareCriteria.formatSingleValue(data.threshold)}. `;
|
|
611
|
+
message += ` greater than or equal to ${CompareCriteria.formatSingleValue(data.threshold)}${unitSuffix}. `;
|
|
584
612
|
break;
|
|
585
613
|
case FilterType.LessThan:
|
|
586
|
-
message += ` less than ${CompareCriteria.formatSingleValue(data.threshold)}. `;
|
|
614
|
+
message += ` less than ${CompareCriteria.formatSingleValue(data.threshold)}${unitSuffix}. `;
|
|
587
615
|
break;
|
|
588
616
|
case FilterType.LessThanOrEqualTo:
|
|
589
|
-
message += ` less than or equal to ${CompareCriteria.formatSingleValue(data.threshold)}. `;
|
|
617
|
+
message += ` less than or equal to ${CompareCriteria.formatSingleValue(data.threshold)}${unitSuffix}. `;
|
|
590
618
|
break;
|
|
591
619
|
case FilterType.NotEqualTo:
|
|
592
|
-
message += ` not equal to ${CompareCriteria.formatSingleValue(data.threshold)}. `;
|
|
620
|
+
message += ` not equal to ${CompareCriteria.formatSingleValue(data.threshold)}${unitSuffix}. `;
|
|
593
621
|
break;
|
|
594
622
|
case FilterType.EqualTo:
|
|
595
|
-
message += ` equal to ${CompareCriteria.formatSingleValue(data.threshold)}. `;
|
|
623
|
+
message += ` equal to ${CompareCriteria.formatSingleValue(data.threshold)}${unitSuffix}. `;
|
|
596
624
|
break;
|
|
597
625
|
case FilterType.Contains:
|
|
598
626
|
message += ` contains ${CompareCriteria.formatSingleValue(data.threshold)}. `;
|
|
@@ -621,11 +649,45 @@ export default class CompareCriteria {
|
|
|
621
649
|
values: Array<number | boolean> | number | boolean | string,
|
|
622
650
|
): string {
|
|
623
651
|
if (Array.isArray(values)) {
|
|
624
|
-
|
|
652
|
+
/*
|
|
653
|
+
* For a small number of values, list them verbatim so the message
|
|
654
|
+
* reads naturally ("is 42, 55, 60"). For larger arrays, summarize
|
|
655
|
+
* — dumping 30+ numbers on one line makes the root cause unreadable
|
|
656
|
+
* and the detailed breakdown is shown in the Breaching Samples
|
|
657
|
+
* table below.
|
|
658
|
+
*/
|
|
659
|
+
const MAX_INLINE: number = 5;
|
|
660
|
+
|
|
661
|
+
if (values.length <= MAX_INLINE) {
|
|
662
|
+
return values
|
|
663
|
+
.map((value: number | boolean) => {
|
|
664
|
+
return CompareCriteria.formatSingleValue(value);
|
|
665
|
+
})
|
|
666
|
+
.join(", ");
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
const numericValues: Array<number> = values.filter(
|
|
670
|
+
(value: number | boolean): value is number => {
|
|
671
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
672
|
+
},
|
|
673
|
+
);
|
|
674
|
+
|
|
675
|
+
if (numericValues.length === values.length && numericValues.length > 0) {
|
|
676
|
+
const min: number = Math.min(...numericValues);
|
|
677
|
+
const max: number = Math.max(...numericValues);
|
|
678
|
+
return `${numericValues.length} samples between ${CompareCriteria.formatSingleValue(
|
|
679
|
+
min,
|
|
680
|
+
)} and ${CompareCriteria.formatSingleValue(max)}`;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// Fall back to a truncated list when not all values are numeric
|
|
684
|
+
const head: string = values
|
|
685
|
+
.slice(0, MAX_INLINE)
|
|
625
686
|
.map((value: number | boolean) => {
|
|
626
687
|
return CompareCriteria.formatSingleValue(value);
|
|
627
688
|
})
|
|
628
689
|
.join(", ");
|
|
690
|
+
return `${head}, … (${values.length} values total)`;
|
|
629
691
|
}
|
|
630
692
|
|
|
631
693
|
return CompareCriteria.formatSingleValue(values);
|