@oneuptime/common 10.0.69 → 10.0.70
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/KubernetesCluster.ts +5 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1776865086264-MigrationName.ts +11 -8
- package/Server/Infrastructure/Postgres/SchemaMigrations/1776881254913-DedupeKubernetesClustersAndAddUniqueIndex.ts +134 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Services/DatabaseService.ts +10 -27
- package/Server/Services/KubernetesResourceService.ts +33 -10
- package/Server/Types/Database/QueryHelper.ts +127 -0
- package/Server/Types/Database/QueryUtil.ts +244 -0
- package/Types/BaseDatabase/EndsWith.ts +41 -0
- package/Types/BaseDatabase/IncludesAll.ts +45 -0
- package/Types/BaseDatabase/IncludesNone.ts +48 -0
- package/Types/BaseDatabase/NotContains.ts +41 -0
- package/Types/BaseDatabase/StartsWith.ts +41 -0
- package/Types/JSON.ts +20 -0
- package/Types/SerializableObjectDictionary.ts +10 -0
- package/UI/Components/Filters/BooleanFilter.tsx +1 -0
- package/UI/Components/Filters/DateFilter.tsx +212 -25
- package/UI/Components/Filters/DropdownFilter.tsx +1 -0
- package/UI/Components/Filters/EntityFilter.tsx +214 -41
- package/UI/Components/Filters/FilterViewer.tsx +228 -146
- package/UI/Components/Filters/FilterViewerItem.tsx +1 -11
- package/UI/Components/Filters/FiltersForm.tsx +148 -97
- package/UI/Components/Filters/NumberFilter.tsx +219 -34
- package/UI/Components/Filters/OperatorSelector.tsx +91 -0
- package/UI/Components/Filters/TextFilter.tsx +182 -71
- package/UI/Components/Filters/Types/FilterOperator.ts +73 -0
- package/UI/Components/ModelTable/BaseModelTable.tsx +8 -0
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js +7 -1
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776865086264-MigrationName.js +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776865086264-MigrationName.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776881254913-DedupeKubernetesClustersAndAddUniqueIndex.js +123 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776881254913-DedupeKubernetesClustersAndAddUniqueIndex.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +9 -6
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesResourceService.js +4 -2
- package/build/dist/Server/Services/KubernetesResourceService.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryHelper.js +110 -0
- package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryUtil.js +180 -0
- package/build/dist/Server/Types/Database/QueryUtil.js.map +1 -1
- package/build/dist/Types/BaseDatabase/EndsWith.js +31 -0
- package/build/dist/Types/BaseDatabase/EndsWith.js.map +1 -0
- package/build/dist/Types/BaseDatabase/IncludesAll.js +34 -0
- package/build/dist/Types/BaseDatabase/IncludesAll.js.map +1 -0
- package/build/dist/Types/BaseDatabase/IncludesNone.js +34 -0
- package/build/dist/Types/BaseDatabase/IncludesNone.js.map +1 -0
- package/build/dist/Types/BaseDatabase/NotContains.js +31 -0
- package/build/dist/Types/BaseDatabase/NotContains.js.map +1 -0
- package/build/dist/Types/BaseDatabase/StartsWith.js +31 -0
- package/build/dist/Types/BaseDatabase/StartsWith.js.map +1 -0
- package/build/dist/Types/JSON.js +5 -0
- package/build/dist/Types/JSON.js.map +1 -1
- package/build/dist/Types/SerializableObjectDictionary.js +10 -0
- package/build/dist/Types/SerializableObjectDictionary.js.map +1 -1
- package/build/dist/UI/Components/Filters/BooleanFilter.js +1 -1
- package/build/dist/UI/Components/Filters/BooleanFilter.js.map +1 -1
- package/build/dist/UI/Components/Filters/DateFilter.js +158 -14
- package/build/dist/UI/Components/Filters/DateFilter.js.map +1 -1
- package/build/dist/UI/Components/Filters/DropdownFilter.js +1 -1
- package/build/dist/UI/Components/Filters/DropdownFilter.js.map +1 -1
- package/build/dist/UI/Components/Filters/EntityFilter.js +174 -30
- package/build/dist/UI/Components/Filters/EntityFilter.js.map +1 -1
- package/build/dist/UI/Components/Filters/FilterViewer.js +188 -97
- package/build/dist/UI/Components/Filters/FilterViewer.js.map +1 -1
- package/build/dist/UI/Components/Filters/FilterViewerItem.js +1 -6
- package/build/dist/UI/Components/Filters/FilterViewerItem.js.map +1 -1
- package/build/dist/UI/Components/Filters/FiltersForm.js +46 -38
- package/build/dist/UI/Components/Filters/FiltersForm.js.map +1 -1
- package/build/dist/UI/Components/Filters/NumberFilter.js +165 -23
- package/build/dist/UI/Components/Filters/NumberFilter.js.map +1 -1
- package/build/dist/UI/Components/Filters/OperatorSelector.js +41 -0
- package/build/dist/UI/Components/Filters/OperatorSelector.js.map +1 -0
- package/build/dist/UI/Components/Filters/TextFilter.js +130 -53
- package/build/dist/UI/Components/Filters/TextFilter.js.map +1 -1
- package/build/dist/UI/Components/Filters/Types/FilterOperator.js +63 -0
- package/build/dist/UI/Components/Filters/Types/FilterOperator.js.map +1 -0
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +7 -0
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/package.json +1 -1
|
@@ -73,6 +73,11 @@ import {
|
|
|
73
73
|
})
|
|
74
74
|
@CrudApiEndpoint(new Route("/kubernetes-cluster"))
|
|
75
75
|
@SlugifyColumn("name", "slug")
|
|
76
|
+
// Enforce one cluster row per (projectId, clusterIdentifier) at the DB level.
|
|
77
|
+
// Without this, two pods emitting OTel telemetry for a new cluster at the
|
|
78
|
+
// same time (e.g. when the agent is first installed or during a rolling
|
|
79
|
+
// update) race in findOrCreateByClusterIdentifier and create duplicate rows.
|
|
80
|
+
@Index(["projectId", "clusterIdentifier"], { unique: true })
|
|
76
81
|
@TableMetadata({
|
|
77
82
|
tableName: "KubernetesCluster",
|
|
78
83
|
singularName: "Kubernetes Cluster",
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
2
2
|
|
|
3
3
|
export class MigrationName1776865086264 implements MigrationInterface {
|
|
4
|
-
|
|
4
|
+
public name: string = "MigrationName1776865086264";
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
await queryRunner.query(`ALTER TABLE "KubernetesResource" DROP COLUMN "containerCount"`);
|
|
12
|
-
}
|
|
6
|
+
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
7
|
+
await queryRunner.query(
|
|
8
|
+
`ALTER TABLE "KubernetesResource" ADD "containerCount" integer`,
|
|
9
|
+
);
|
|
10
|
+
}
|
|
13
11
|
|
|
12
|
+
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
13
|
+
await queryRunner.query(
|
|
14
|
+
`ALTER TABLE "KubernetesResource" DROP COLUMN "containerCount"`,
|
|
15
|
+
);
|
|
16
|
+
}
|
|
14
17
|
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Before this migration, KubernetesCluster had an app-level
|
|
5
|
+
* @UniqueColumnBy("projectId") check and a non-unique index on
|
|
6
|
+
* clusterIdentifier, but no DB-level uniqueness. Under concurrent telemetry
|
|
7
|
+
* from multiple agent pods (happens every time the agent is installed or
|
|
8
|
+
* rolls out), findOrCreateByClusterIdentifier would race between its find
|
|
9
|
+
* and its create, and the DB accepted both inserts — producing duplicate
|
|
10
|
+
* rows with identical (projectId, clusterIdentifier).
|
|
11
|
+
*
|
|
12
|
+
* This migration:
|
|
13
|
+
* 1. Reparents all FKs that reference duplicate clusters — KubernetesResource,
|
|
14
|
+
* KubernetesClusterOwnerUser, KubernetesClusterOwnerTeam — onto the
|
|
15
|
+
* oldest surviving row in each duplicate group.
|
|
16
|
+
* 2. Deletes the duplicate (non-survivor) rows.
|
|
17
|
+
* 3. Creates a DB-level unique index on (projectId, clusterIdentifier) so
|
|
18
|
+
* future races are rejected by the DB — the service's existing
|
|
19
|
+
* catch-and-refetch in findOrCreateByClusterIdentifier then returns the
|
|
20
|
+
* winning row instead of producing a duplicate.
|
|
21
|
+
*
|
|
22
|
+
* The auto-generator also picked up unrelated OnCallDutyPolicyScheduleLayer
|
|
23
|
+
* default-value drift. That's dev-environment drift, not the bug we're fixing;
|
|
24
|
+
* stripped from this migration.
|
|
25
|
+
*/
|
|
26
|
+
export class DedupeKubernetesClustersAndAddUniqueIndex1776881254913
|
|
27
|
+
implements MigrationInterface
|
|
28
|
+
{
|
|
29
|
+
public name: string = "DedupeKubernetesClustersAndAddUniqueIndex1776881254913";
|
|
30
|
+
|
|
31
|
+
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
32
|
+
// 1: reparent KubernetesResource FKs from duplicates -> survivor.
|
|
33
|
+
await queryRunner.query(`
|
|
34
|
+
WITH survivors AS (
|
|
35
|
+
SELECT DISTINCT ON ("projectId", "clusterIdentifier")
|
|
36
|
+
_id AS survivor_id,
|
|
37
|
+
"projectId",
|
|
38
|
+
"clusterIdentifier"
|
|
39
|
+
FROM "KubernetesCluster"
|
|
40
|
+
ORDER BY "projectId", "clusterIdentifier", "createdAt" ASC, _id ASC
|
|
41
|
+
),
|
|
42
|
+
losers AS (
|
|
43
|
+
SELECT kc._id AS loser_id, s.survivor_id
|
|
44
|
+
FROM "KubernetesCluster" kc
|
|
45
|
+
JOIN survivors s
|
|
46
|
+
ON s."projectId" = kc."projectId"
|
|
47
|
+
AND s."clusterIdentifier" = kc."clusterIdentifier"
|
|
48
|
+
WHERE kc._id <> s.survivor_id
|
|
49
|
+
)
|
|
50
|
+
UPDATE "KubernetesResource" kr
|
|
51
|
+
SET "kubernetesClusterId" = l.survivor_id
|
|
52
|
+
FROM losers l
|
|
53
|
+
WHERE kr."kubernetesClusterId" = l.loser_id;
|
|
54
|
+
`);
|
|
55
|
+
|
|
56
|
+
// 2: reparent KubernetesClusterOwnerUser FKs.
|
|
57
|
+
await queryRunner.query(`
|
|
58
|
+
WITH survivors AS (
|
|
59
|
+
SELECT DISTINCT ON ("projectId", "clusterIdentifier")
|
|
60
|
+
_id AS survivor_id,
|
|
61
|
+
"projectId",
|
|
62
|
+
"clusterIdentifier"
|
|
63
|
+
FROM "KubernetesCluster"
|
|
64
|
+
ORDER BY "projectId", "clusterIdentifier", "createdAt" ASC, _id ASC
|
|
65
|
+
),
|
|
66
|
+
losers AS (
|
|
67
|
+
SELECT kc._id AS loser_id, s.survivor_id
|
|
68
|
+
FROM "KubernetesCluster" kc
|
|
69
|
+
JOIN survivors s
|
|
70
|
+
ON s."projectId" = kc."projectId"
|
|
71
|
+
AND s."clusterIdentifier" = kc."clusterIdentifier"
|
|
72
|
+
WHERE kc._id <> s.survivor_id
|
|
73
|
+
)
|
|
74
|
+
UPDATE "KubernetesClusterOwnerUser" o
|
|
75
|
+
SET "kubernetesClusterId" = l.survivor_id
|
|
76
|
+
FROM losers l
|
|
77
|
+
WHERE o."kubernetesClusterId" = l.loser_id;
|
|
78
|
+
`);
|
|
79
|
+
|
|
80
|
+
// 3: reparent KubernetesClusterOwnerTeam FKs.
|
|
81
|
+
await queryRunner.query(`
|
|
82
|
+
WITH survivors AS (
|
|
83
|
+
SELECT DISTINCT ON ("projectId", "clusterIdentifier")
|
|
84
|
+
_id AS survivor_id,
|
|
85
|
+
"projectId",
|
|
86
|
+
"clusterIdentifier"
|
|
87
|
+
FROM "KubernetesCluster"
|
|
88
|
+
ORDER BY "projectId", "clusterIdentifier", "createdAt" ASC, _id ASC
|
|
89
|
+
),
|
|
90
|
+
losers AS (
|
|
91
|
+
SELECT kc._id AS loser_id, s.survivor_id
|
|
92
|
+
FROM "KubernetesCluster" kc
|
|
93
|
+
JOIN survivors s
|
|
94
|
+
ON s."projectId" = kc."projectId"
|
|
95
|
+
AND s."clusterIdentifier" = kc."clusterIdentifier"
|
|
96
|
+
WHERE kc._id <> s.survivor_id
|
|
97
|
+
)
|
|
98
|
+
UPDATE "KubernetesClusterOwnerTeam" o
|
|
99
|
+
SET "kubernetesClusterId" = l.survivor_id
|
|
100
|
+
FROM losers l
|
|
101
|
+
WHERE o."kubernetesClusterId" = l.loser_id;
|
|
102
|
+
`);
|
|
103
|
+
|
|
104
|
+
// 4: delete duplicate rows now that nothing references them.
|
|
105
|
+
await queryRunner.query(`
|
|
106
|
+
WITH survivors AS (
|
|
107
|
+
SELECT DISTINCT ON ("projectId", "clusterIdentifier")
|
|
108
|
+
_id AS survivor_id,
|
|
109
|
+
"projectId",
|
|
110
|
+
"clusterIdentifier"
|
|
111
|
+
FROM "KubernetesCluster"
|
|
112
|
+
ORDER BY "projectId", "clusterIdentifier", "createdAt" ASC, _id ASC
|
|
113
|
+
)
|
|
114
|
+
DELETE FROM "KubernetesCluster" kc
|
|
115
|
+
USING survivors s
|
|
116
|
+
WHERE s."projectId" = kc."projectId"
|
|
117
|
+
AND s."clusterIdentifier" = kc."clusterIdentifier"
|
|
118
|
+
AND kc._id <> s.survivor_id;
|
|
119
|
+
`);
|
|
120
|
+
|
|
121
|
+
// 5: add the DB-level composite unique index.
|
|
122
|
+
await queryRunner.query(
|
|
123
|
+
`CREATE UNIQUE INDEX "IDX_9756988b48848f4f7532a2af0d" ON "KubernetesCluster" ("projectId", "clusterIdentifier") `,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
128
|
+
await queryRunner.query(
|
|
129
|
+
`DROP INDEX "public"."IDX_9756988b48848f4f7532a2af0d"`,
|
|
130
|
+
);
|
|
131
|
+
// Duplicate rows dropped in up() are lost — a down-migration cannot
|
|
132
|
+
// resurrect them (and reinstating duplicates is not desirable anyway).
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -288,6 +288,7 @@ import { MigrationName1776544084793 } from "./1776544084793-MigrationName";
|
|
|
288
288
|
import { MigrationName1776761171349 } from "./1776761171349-MigrationName";
|
|
289
289
|
import { MigrationName1776801030808 } from "./1776801030808-MigrationName";
|
|
290
290
|
import { MigrationName1776865086264 } from "./1776865086264-MigrationName";
|
|
291
|
+
import { DedupeKubernetesClustersAndAddUniqueIndex1776881254913 } from "./1776881254913-DedupeKubernetesClustersAndAddUniqueIndex";
|
|
291
292
|
export default [
|
|
292
293
|
InitialMigration,
|
|
293
294
|
MigrationName1717678334852,
|
|
@@ -579,4 +580,5 @@ export default [
|
|
|
579
580
|
MigrationName1776761171349,
|
|
580
581
|
MigrationName1776801030808,
|
|
581
582
|
MigrationName1776865086264,
|
|
583
|
+
DedupeKubernetesClustersAndAddUniqueIndex1776881254913,
|
|
582
584
|
];
|
|
@@ -69,6 +69,7 @@ import { FindWhere } from "../../Types/BaseDatabase/Query";
|
|
|
69
69
|
import Realtime from "../Utils/Realtime";
|
|
70
70
|
import ModelEventType from "../../Types/Realtime/ModelEventType";
|
|
71
71
|
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
72
|
+
import type AuditLogServiceType from "./AuditLogService";
|
|
72
73
|
|
|
73
74
|
class DatabaseService<TBaseModel extends BaseModel> extends BaseService {
|
|
74
75
|
public modelType!: { new (): TBaseModel };
|
|
@@ -782,14 +783,9 @@ class DatabaseService<TBaseModel extends BaseModel> extends BaseService {
|
|
|
782
783
|
* which extend DatabaseService). A top-level import leaves
|
|
783
784
|
* DatabaseService undefined at class-extension time for subclasses.
|
|
784
785
|
*/
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
model: TBaseModel;
|
|
789
|
-
createdItem: TBaseModel;
|
|
790
|
-
props: DatabaseCommonInteractionProps;
|
|
791
|
-
}) => Promise<void>;
|
|
792
|
-
} = require("./AuditLogService").default;
|
|
786
|
+
const auditLogService: typeof AuditLogServiceType =
|
|
787
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
|
|
788
|
+
require("./AuditLogService").default;
|
|
793
789
|
await auditLogService.recordCreate({
|
|
794
790
|
model: this.getModel(),
|
|
795
791
|
createdItem: createBy.data,
|
|
@@ -1237,15 +1233,9 @@ class DatabaseService<TBaseModel extends BaseModel> extends BaseService {
|
|
|
1237
1233
|
}
|
|
1238
1234
|
|
|
1239
1235
|
if (this.getModel().enableAuditLogOn?.delete && items.length > 0) {
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
model: TBaseModel;
|
|
1244
|
-
deletedItem: TBaseModel;
|
|
1245
|
-
itemId: ObjectID;
|
|
1246
|
-
props: DatabaseCommonInteractionProps;
|
|
1247
|
-
}) => Promise<void>;
|
|
1248
|
-
} = require("./AuditLogService").default;
|
|
1236
|
+
const auditLogService: typeof AuditLogServiceType =
|
|
1237
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
|
|
1238
|
+
require("./AuditLogService").default;
|
|
1249
1239
|
for (const item of items) {
|
|
1250
1240
|
if (item.id) {
|
|
1251
1241
|
await auditLogService.recordDelete({
|
|
@@ -1667,16 +1657,9 @@ class DatabaseService<TBaseModel extends BaseModel> extends BaseService {
|
|
|
1667
1657
|
!this.hasSameValues({ item, updatedItem }) &&
|
|
1668
1658
|
item.id
|
|
1669
1659
|
) {
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
model: TBaseModel;
|
|
1674
|
-
before: TBaseModel;
|
|
1675
|
-
updatedFields: JSONObject;
|
|
1676
|
-
itemId: ObjectID;
|
|
1677
|
-
props: DatabaseCommonInteractionProps;
|
|
1678
|
-
}) => Promise<void>;
|
|
1679
|
-
} = require("./AuditLogService").default;
|
|
1660
|
+
const auditLogService: typeof AuditLogServiceType =
|
|
1661
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
|
|
1662
|
+
require("./AuditLogService").default;
|
|
1680
1663
|
await auditLogService.recordUpdate({
|
|
1681
1664
|
model: this.getModel(),
|
|
1682
1665
|
before: item,
|
|
@@ -113,7 +113,10 @@ function buildDegradedPod(row: {
|
|
|
113
113
|
const scanForReason: (
|
|
114
114
|
list: Array<Record<string, unknown>>,
|
|
115
115
|
targetState: string,
|
|
116
|
-
) => { reason: string; message: string } | null = (
|
|
116
|
+
) => { reason: string; message: string } | null = (
|
|
117
|
+
list: Array<Record<string, unknown>>,
|
|
118
|
+
targetState: string,
|
|
119
|
+
) => {
|
|
117
120
|
for (const cs of list) {
|
|
118
121
|
if (cs["state"] !== targetState) {
|
|
119
122
|
continue;
|
|
@@ -144,8 +147,10 @@ function buildDegradedPod(row: {
|
|
|
144
147
|
reason = hit.reason;
|
|
145
148
|
message = hit.message;
|
|
146
149
|
} else {
|
|
147
|
-
|
|
148
|
-
|
|
150
|
+
/*
|
|
151
|
+
* Fall back to the pod-level reason/message fields set by the scheduler
|
|
152
|
+
* (e.g. "Unschedulable" with "0/3 nodes are available: ...").
|
|
153
|
+
*/
|
|
149
154
|
const topReason: unknown = status["reason"];
|
|
150
155
|
const topMessage: unknown = status["message"];
|
|
151
156
|
if (typeof topReason === "string") {
|
|
@@ -211,7 +216,9 @@ function buildDegradedNode(row: {
|
|
|
211
216
|
|
|
212
217
|
const findCondition: (
|
|
213
218
|
predicate: (c: Record<string, unknown>) => boolean,
|
|
214
|
-
) => Record<string, unknown> | null = (
|
|
219
|
+
) => Record<string, unknown> | null = (
|
|
220
|
+
predicate: (c: Record<string, unknown>) => boolean,
|
|
221
|
+
) => {
|
|
215
222
|
for (const c of conditions) {
|
|
216
223
|
if (predicate(c)) {
|
|
217
224
|
return c;
|
|
@@ -591,12 +598,28 @@ export class Service extends DatabaseService<Model> {
|
|
|
591
598
|
const containerCount: number =
|
|
592
599
|
parseInt(containerRows[0]?.total || "0", 10) || 0;
|
|
593
600
|
|
|
594
|
-
const degradedPods: Array<DegradedPod> = degradedPodRows.map(
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
601
|
+
const degradedPods: Array<DegradedPod> = degradedPodRows.map(
|
|
602
|
+
(row: {
|
|
603
|
+
name: string;
|
|
604
|
+
namespaceKey: string;
|
|
605
|
+
phase: string | null;
|
|
606
|
+
status: unknown;
|
|
607
|
+
}) => {
|
|
608
|
+
return buildDegradedPod(row);
|
|
609
|
+
},
|
|
610
|
+
);
|
|
611
|
+
const degradedNodes: Array<DegradedNode> = degradedNodeRows.map(
|
|
612
|
+
(row: {
|
|
613
|
+
name: string;
|
|
614
|
+
isReady: boolean | null;
|
|
615
|
+
hasMemoryPressure: boolean | null;
|
|
616
|
+
hasDiskPressure: boolean | null;
|
|
617
|
+
hasPidPressure: boolean | null;
|
|
618
|
+
status: unknown;
|
|
619
|
+
}) => {
|
|
620
|
+
return buildDegradedNode(row);
|
|
621
|
+
},
|
|
622
|
+
);
|
|
600
623
|
|
|
601
624
|
return {
|
|
602
625
|
countsByKind,
|
|
@@ -138,6 +138,48 @@ export default class QueryHelper {
|
|
|
138
138
|
);
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
@CaptureSpan()
|
|
142
|
+
public static notContains(name: string): FindWhereProperty<any> {
|
|
143
|
+
name = name.toLowerCase().trim();
|
|
144
|
+
const rid: string = Text.generateRandomText(10);
|
|
145
|
+
return Raw(
|
|
146
|
+
(alias: string) => {
|
|
147
|
+
return `(CAST(${alias} AS TEXT) NOT ILIKE :${rid} OR ${alias} IS NULL)`;
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
[rid]: `%${name}%`,
|
|
151
|
+
},
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
@CaptureSpan()
|
|
156
|
+
public static startsWith(name: string): FindWhereProperty<any> {
|
|
157
|
+
name = name.toLowerCase().trim();
|
|
158
|
+
const rid: string = Text.generateRandomText(10);
|
|
159
|
+
return Raw(
|
|
160
|
+
(alias: string) => {
|
|
161
|
+
return `(CAST(${alias} AS TEXT) ILIKE :${rid})`;
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
[rid]: `${name}%`,
|
|
165
|
+
},
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
@CaptureSpan()
|
|
170
|
+
public static endsWith(name: string): FindWhereProperty<any> {
|
|
171
|
+
name = name.toLowerCase().trim();
|
|
172
|
+
const rid: string = Text.generateRandomText(10);
|
|
173
|
+
return Raw(
|
|
174
|
+
(alias: string) => {
|
|
175
|
+
return `(CAST(${alias} AS TEXT) ILIKE :${rid})`;
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
[rid]: `%${name}`,
|
|
179
|
+
},
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
141
183
|
@CaptureSpan()
|
|
142
184
|
public static all(values: Array<string | ObjectID>): FindWhereProperty<any> {
|
|
143
185
|
values = values.map((value: string | ObjectID) => {
|
|
@@ -168,6 +210,91 @@ export default class QueryHelper {
|
|
|
168
210
|
return this.in(values); // any and in are the same
|
|
169
211
|
}
|
|
170
212
|
|
|
213
|
+
/**
|
|
214
|
+
* Returns a filter that matches owner rows that are linked to *none* of the
|
|
215
|
+
* provided related entity ids through a many-to-many join table. The
|
|
216
|
+
* returned FindOperator is intended to be applied to the primary id column
|
|
217
|
+
* of the owner entity.
|
|
218
|
+
*/
|
|
219
|
+
@CaptureSpan()
|
|
220
|
+
public static noneEntitiesInManyToMany(data: {
|
|
221
|
+
values: Array<string | ObjectID>;
|
|
222
|
+
joinTableName: string;
|
|
223
|
+
ownerColumnName: string;
|
|
224
|
+
relationColumnName: string;
|
|
225
|
+
}): FindWhereProperty<any> {
|
|
226
|
+
const values: Array<string> = data.values.map(
|
|
227
|
+
(value: string | ObjectID) => {
|
|
228
|
+
return value.toString();
|
|
229
|
+
},
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
if (!values || values.length === 0) {
|
|
233
|
+
return Raw(() => {
|
|
234
|
+
return `TRUE = TRUE`;
|
|
235
|
+
}, {});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const valuesRid: string = Text.generateRandomText(10);
|
|
239
|
+
|
|
240
|
+
const joinTable: string = data.joinTableName.replace(/"/g, '""');
|
|
241
|
+
const ownerCol: string = data.ownerColumnName.replace(/"/g, '""');
|
|
242
|
+
const relationCol: string = data.relationColumnName.replace(/"/g, '""');
|
|
243
|
+
|
|
244
|
+
return Raw(
|
|
245
|
+
(alias: string) => {
|
|
246
|
+
return `(${alias} NOT IN (SELECT "${joinTable}"."${ownerCol}" FROM "${joinTable}" WHERE "${joinTable}"."${relationCol}" IN (:...${valuesRid})))`;
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
[valuesRid]: values,
|
|
250
|
+
},
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Returns a filter that matches owner rows that are linked to *all* of the
|
|
256
|
+
* provided related entity ids through a many-to-many join table. The
|
|
257
|
+
* returned FindOperator is intended to be applied to the primary id column
|
|
258
|
+
* of the owner entity.
|
|
259
|
+
*/
|
|
260
|
+
@CaptureSpan()
|
|
261
|
+
public static allEntitiesInManyToMany(data: {
|
|
262
|
+
values: Array<string | ObjectID>;
|
|
263
|
+
joinTableName: string;
|
|
264
|
+
ownerColumnName: string;
|
|
265
|
+
relationColumnName: string;
|
|
266
|
+
}): FindWhereProperty<any> {
|
|
267
|
+
const values: Array<string> = data.values.map(
|
|
268
|
+
(value: string | ObjectID) => {
|
|
269
|
+
return value.toString();
|
|
270
|
+
},
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
if (!values || values.length === 0) {
|
|
274
|
+
return Raw(() => {
|
|
275
|
+
return `TRUE = FALSE`;
|
|
276
|
+
}, {});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const valuesRid: string = Text.generateRandomText(10);
|
|
280
|
+
const countRid: string = Text.generateRandomText(10);
|
|
281
|
+
|
|
282
|
+
// Escape identifiers so they can safely be embedded in the SQL string.
|
|
283
|
+
const joinTable: string = data.joinTableName.replace(/"/g, '""');
|
|
284
|
+
const ownerCol: string = data.ownerColumnName.replace(/"/g, '""');
|
|
285
|
+
const relationCol: string = data.relationColumnName.replace(/"/g, '""');
|
|
286
|
+
|
|
287
|
+
return Raw(
|
|
288
|
+
(alias: string) => {
|
|
289
|
+
return `(${alias} IN (SELECT "${joinTable}"."${ownerCol}" FROM "${joinTable}" WHERE "${joinTable}"."${relationCol}" IN (:...${valuesRid}) GROUP BY "${joinTable}"."${ownerCol}" HAVING COUNT(DISTINCT "${joinTable}"."${relationCol}") >= :${countRid}))`;
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
[valuesRid]: values,
|
|
293
|
+
[countRid]: values.length,
|
|
294
|
+
},
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
171
298
|
private static in(
|
|
172
299
|
values: Array<string | ObjectID | number>,
|
|
173
300
|
): FindWhereProperty<any> {
|