@oneuptime/common 10.2.4 → 10.2.6
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/Service.ts +26 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1778582583897-MigrationName.ts +15 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Services/OpenTelemetryIngestService.ts +15 -0
- package/Server/Services/ServiceService.ts +37 -0
- package/Server/Types/Database/QueryHelper.ts +38 -0
- package/Server/Types/Database/QueryUtil.ts +77 -0
- package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +52 -0
- package/Types/BaseDatabase/MultiSearch.ts +53 -0
- package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardChartComponent.ts +2 -0
- package/Types/JSON.ts +3 -0
- package/Types/SerializableObjectDictionary.ts +2 -0
- package/UI/Components/ModelTable/BaseModelTable.tsx +988 -4
- package/Utils/Dashboard/Components/DashboardChartComponent.ts +11 -0
- package/build/dist/Models/DatabaseModels/Service.js +28 -0
- package/build/dist/Models/DatabaseModels/Service.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1778582583897-MigrationName.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1778582583897-MigrationName.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/OpenTelemetryIngestService.js +11 -0
- package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
- package/build/dist/Server/Services/ServiceService.js +34 -0
- package/build/dist/Server/Services/ServiceService.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryHelper.js +33 -0
- package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryUtil.js +64 -0
- package/build/dist/Server/Types/Database/QueryUtil.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +44 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
- package/build/dist/Types/BaseDatabase/MultiSearch.js +44 -0
- package/build/dist/Types/BaseDatabase/MultiSearch.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
- package/build/dist/Types/JSON.js +1 -0
- package/build/dist/Types/JSON.js.map +1 -1
- package/build/dist/Types/SerializableObjectDictionary.js +2 -0
- package/build/dist/Types/SerializableObjectDictionary.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +591 -7
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/Utils/Dashboard/Components/DashboardChartComponent.js +9 -0
- package/build/dist/Utils/Dashboard/Components/DashboardChartComponent.js.map +1 -1
- package/package.json +1 -1
|
@@ -725,4 +725,30 @@ export default class Service extends BaseModel {
|
|
|
725
725
|
})
|
|
726
726
|
public metricDownsamplingRetentionDays?: MetricDownsamplingRetentionDays =
|
|
727
727
|
undefined;
|
|
728
|
+
|
|
729
|
+
@ColumnAccessControl({
|
|
730
|
+
create: [],
|
|
731
|
+
read: [
|
|
732
|
+
Permission.ProjectOwner,
|
|
733
|
+
Permission.ProjectAdmin,
|
|
734
|
+
Permission.ProjectMember,
|
|
735
|
+
Permission.Viewer,
|
|
736
|
+
Permission.SettingsManager,
|
|
737
|
+
Permission.ReadService,
|
|
738
|
+
Permission.ReadAllProjectResources,
|
|
739
|
+
],
|
|
740
|
+
update: [],
|
|
741
|
+
})
|
|
742
|
+
@TableColumn({
|
|
743
|
+
required: false,
|
|
744
|
+
type: TableColumnType.Date,
|
|
745
|
+
canReadOnRelationQuery: true,
|
|
746
|
+
title: "Last Seen At",
|
|
747
|
+
description: "When telemetry was last received for this service",
|
|
748
|
+
})
|
|
749
|
+
@Column({
|
|
750
|
+
nullable: true,
|
|
751
|
+
type: ColumnType.Date,
|
|
752
|
+
})
|
|
753
|
+
public lastSeenAt?: Date = undefined;
|
|
728
754
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
2
|
+
|
|
3
|
+
export class MigrationName1778582583897 implements MigrationInterface {
|
|
4
|
+
public name = "MigrationName1778582583897";
|
|
5
|
+
|
|
6
|
+
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
7
|
+
await queryRunner.query(
|
|
8
|
+
`ALTER TABLE "Service" ADD "lastSeenAt" TIMESTAMP WITH TIME ZONE`,
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
13
|
+
await queryRunner.query(`ALTER TABLE "Service" DROP COLUMN "lastSeenAt"`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -316,6 +316,7 @@ import { AddProjectOIDC1778506655291 } from "./1778506655291-AddProjectOIDC";
|
|
|
316
316
|
import { MigrationName1778514515756 } from "./1778514515756-MigrationName";
|
|
317
317
|
import { MigrationName1778521361934 } from "./1778521361934-MigrationName";
|
|
318
318
|
import { AddStatusPageOIDC1778522070962 } from "./1778522070962-AddStatusPageOIDC";
|
|
319
|
+
import { MigrationName1778582583897 } from "./1778582583897-MigrationName";
|
|
319
320
|
export default [
|
|
320
321
|
InitialMigration,
|
|
321
322
|
MigrationName1717678334852,
|
|
@@ -635,4 +636,5 @@ export default [
|
|
|
635
636
|
MigrationName1778514515756,
|
|
636
637
|
MigrationName1778521361934,
|
|
637
638
|
AddStatusPageOIDC1778522070962,
|
|
639
|
+
MigrationName1778582583897,
|
|
638
640
|
];
|
|
@@ -64,6 +64,21 @@ export default class OTelIngestService {
|
|
|
64
64
|
projectId: data.projectId,
|
|
65
65
|
});
|
|
66
66
|
|
|
67
|
+
/*
|
|
68
|
+
* Touch `lastSeenAt` on the service. Throttled per-service inside
|
|
69
|
+
* ServiceService.updateLastSeen so the steady-state firehose costs
|
|
70
|
+
* one in-memory cache lookup per batch.
|
|
71
|
+
*/
|
|
72
|
+
try {
|
|
73
|
+
await ServiceService.updateLastSeen(result.serviceId);
|
|
74
|
+
} catch (err) {
|
|
75
|
+
logger.warn(
|
|
76
|
+
`telemetryServiceFromName lastSeen update failed for "${data.serviceName}": ${
|
|
77
|
+
err instanceof Error ? err.message : String(err)
|
|
78
|
+
}`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
67
82
|
/*
|
|
68
83
|
* Promote `oneuptime.label.<dim>=<val>` resource attributes into
|
|
69
84
|
* project labels and attach them to the discovered service. The
|
|
@@ -6,6 +6,7 @@ import ArrayUtil from "../../Utils/Array";
|
|
|
6
6
|
import { BrightColors } from "../../Types/BrandColors";
|
|
7
7
|
import BadDataException from "../../Types/Exception/BadDataException";
|
|
8
8
|
import ObjectID from "../../Types/ObjectID";
|
|
9
|
+
import OneUptimeDate from "../../Types/Date";
|
|
9
10
|
import Model from "../../Models/DatabaseModels/Service";
|
|
10
11
|
import Label from "../../Models/DatabaseModels/Label";
|
|
11
12
|
import Project from "../../Models/DatabaseModels/Project";
|
|
@@ -16,6 +17,9 @@ import crypto from "crypto";
|
|
|
16
17
|
|
|
17
18
|
const DEFAULT_TELEMETRY_RETENTION_IN_DAYS: number = 15;
|
|
18
19
|
|
|
20
|
+
const LAST_SEEN_CACHE_NAMESPACE: string = "service-last-seen";
|
|
21
|
+
const LAST_SEEN_THROTTLE_SECONDS: number = 60;
|
|
22
|
+
|
|
19
23
|
const LABELS_APPLIED_CACHE_NAMESPACE: string = "service-labels-applied";
|
|
20
24
|
const LABELS_APPLIED_CACHE_TTL_SECONDS: number = 60;
|
|
21
25
|
|
|
@@ -80,6 +84,39 @@ export class Service extends DatabaseService<Model> {
|
|
|
80
84
|
return DEFAULT_TELEMETRY_RETENTION_IN_DAYS;
|
|
81
85
|
}
|
|
82
86
|
|
|
87
|
+
/*
|
|
88
|
+
* Refresh `lastSeenAt` for a service. Throttled per-service so the
|
|
89
|
+
* steady-state telemetry firehose (every metric/log/trace batch
|
|
90
|
+
* re-resolves the same serviceId) costs one in-memory cache lookup
|
|
91
|
+
* per batch instead of a DB write.
|
|
92
|
+
*/
|
|
93
|
+
@CaptureSpan()
|
|
94
|
+
public async updateLastSeen(serviceId: ObjectID): Promise<void> {
|
|
95
|
+
const cacheKey: string = serviceId.toString();
|
|
96
|
+
const cached: string | null = await GlobalCache.getString(
|
|
97
|
+
LAST_SEEN_CACHE_NAMESPACE,
|
|
98
|
+
cacheKey,
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
if (cached) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
await GlobalCache.setString(LAST_SEEN_CACHE_NAMESPACE, cacheKey, "1", {
|
|
106
|
+
expiresInSeconds: LAST_SEEN_THROTTLE_SECONDS,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
await this.updateOneById({
|
|
110
|
+
id: serviceId,
|
|
111
|
+
data: {
|
|
112
|
+
lastSeenAt: OneUptimeDate.getCurrentDate(),
|
|
113
|
+
},
|
|
114
|
+
props: {
|
|
115
|
+
isRoot: true,
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
83
120
|
/**
|
|
84
121
|
* Additively attach labels to a telemetry service. Existing labels
|
|
85
122
|
* are never removed — manual labels set via the UI survive ingest.
|
|
@@ -138,6 +138,44 @@ export default class QueryHelper {
|
|
|
138
138
|
);
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Searches the provided entity property names with a single OR-joined ILIKE.
|
|
143
|
+
*
|
|
144
|
+
* IMPORTANT: emit unquoted `alias.propertyName` references and let
|
|
145
|
+
* TypeORM's `replacePropertyNamesForTheWholeQuery` post-processor escape
|
|
146
|
+
* the table alias and translate property names → DB column names. Pre-
|
|
147
|
+
* quoting (e.g. `Incident."title"`) bypasses that pass, which leaves an
|
|
148
|
+
* unquoted `Incident` in the final SQL — Postgres then lowercases it and
|
|
149
|
+
* fails with `missing FROM-clause entry for table "incident"`.
|
|
150
|
+
*/
|
|
151
|
+
@CaptureSpan()
|
|
152
|
+
public static multiSearch(
|
|
153
|
+
entityPropertyNames: Array<string>,
|
|
154
|
+
value: string,
|
|
155
|
+
): FindWhereProperty<any> {
|
|
156
|
+
const trimmed: string = value.toLowerCase().trim();
|
|
157
|
+
const rid: string = Text.generateRandomText(10);
|
|
158
|
+
|
|
159
|
+
return Raw(
|
|
160
|
+
(alias: string) => {
|
|
161
|
+
const tableAlias: string = alias.includes(".")
|
|
162
|
+
? (alias.split(".")[0] as string)
|
|
163
|
+
: alias;
|
|
164
|
+
|
|
165
|
+
const orConditions: string = entityPropertyNames
|
|
166
|
+
.map((field: string) => {
|
|
167
|
+
return `(CAST(${tableAlias}.${field} AS TEXT) ILIKE :${rid})`;
|
|
168
|
+
})
|
|
169
|
+
.join(" OR ");
|
|
170
|
+
|
|
171
|
+
return `(${orConditions})`;
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
[rid]: `%${trimmed}%`,
|
|
175
|
+
},
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
141
179
|
@CaptureSpan()
|
|
142
180
|
public static notContains(name: string): FindWhereProperty<any> {
|
|
143
181
|
name = name.toLowerCase().trim();
|
|
@@ -17,6 +17,7 @@ import LessThanOrEqual from "../../../Types/BaseDatabase/LessThanOrEqual";
|
|
|
17
17
|
import NotEqual from "../../../Types/BaseDatabase/NotEqual";
|
|
18
18
|
import NotNull from "../../../Types/BaseDatabase/NotNull";
|
|
19
19
|
import Search from "../../../Types/BaseDatabase/Search";
|
|
20
|
+
import MultiSearch from "../../../Types/BaseDatabase/MultiSearch";
|
|
20
21
|
import { TableColumnMetadata } from "../../../Types/Database/TableColumn";
|
|
21
22
|
import TableColumnType from "../../../Types/Database/TableColumnType";
|
|
22
23
|
import { JSONObject } from "../../../Types/JSON";
|
|
@@ -43,6 +44,82 @@ export default class QueryUtil {
|
|
|
43
44
|
|
|
44
45
|
query = query as Query<TBaseModel>;
|
|
45
46
|
|
|
47
|
+
/*
|
|
48
|
+
* Multi-field text search:
|
|
49
|
+
* A MultiSearch operator on any key fans out into an ILIKE OR across the
|
|
50
|
+
* listed entity fields. We hang the Raw expression off `_id` so it lands
|
|
51
|
+
* in the WHERE clause without TypeORM treating the synthetic key as a
|
|
52
|
+
* real column. Falls through silently if metadata is unavailable or no
|
|
53
|
+
* fields resolve (e.g. property name typo).
|
|
54
|
+
*/
|
|
55
|
+
for (const key in query) {
|
|
56
|
+
const value: any = query[key];
|
|
57
|
+
if (!(value instanceof MultiSearch)) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
delete query[key];
|
|
62
|
+
|
|
63
|
+
const ms: MultiSearch = value as MultiSearch;
|
|
64
|
+
if (!ms.value || ms.fields.length === 0) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/*
|
|
69
|
+
* Validate the requested fields against entity metadata so we only
|
|
70
|
+
* emit identifiers TypeORM's post-processor can map back to real
|
|
71
|
+
* columns (otherwise the alias.field token survives unescaped and
|
|
72
|
+
* Postgres lowercases the table name → "missing FROM-clause entry").
|
|
73
|
+
*/
|
|
74
|
+
const validPropertyNames: Array<string> = [];
|
|
75
|
+
if (PostgresAppInstance.isConnected()) {
|
|
76
|
+
const dataSource: DataSource | null =
|
|
77
|
+
PostgresAppInstance.getDataSource();
|
|
78
|
+
if (dataSource) {
|
|
79
|
+
let entityMetadata: EntityMetadata | undefined;
|
|
80
|
+
try {
|
|
81
|
+
entityMetadata = dataSource.getMetadata(modelType);
|
|
82
|
+
} catch {
|
|
83
|
+
entityMetadata = undefined;
|
|
84
|
+
}
|
|
85
|
+
if (entityMetadata) {
|
|
86
|
+
for (const fieldName of ms.fields) {
|
|
87
|
+
const column: any = entityMetadata.columns.find((c: any) => {
|
|
88
|
+
return c.propertyName === fieldName;
|
|
89
|
+
});
|
|
90
|
+
if (column) {
|
|
91
|
+
validPropertyNames.push(fieldName);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (validPropertyNames.length === 0) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const rawFilter: any = QueryHelper.multiSearch(
|
|
103
|
+
validPropertyNames,
|
|
104
|
+
ms.value,
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const existingIdFilter: any = (query as any)._id;
|
|
108
|
+
if (existingIdFilter instanceof FindOperator) {
|
|
109
|
+
(query as any)._id = And(existingIdFilter, rawFilter);
|
|
110
|
+
} else if (
|
|
111
|
+
existingIdFilter &&
|
|
112
|
+
typeof existingIdFilter === Typeof.String
|
|
113
|
+
) {
|
|
114
|
+
(query as any)._id = And(
|
|
115
|
+
QueryHelper.equalTo(existingIdFilter as string),
|
|
116
|
+
rawFilter,
|
|
117
|
+
);
|
|
118
|
+
} else {
|
|
119
|
+
(query as any)._id = rawFilter;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
46
123
|
for (const key in query) {
|
|
47
124
|
const tableColumnMetadata: TableColumnMetadata =
|
|
48
125
|
model.getTableColumnMetadata(key);
|
|
@@ -29,6 +29,7 @@ import NotEqual from "../../../Types/BaseDatabase/NotEqual";
|
|
|
29
29
|
import NotContains from "../../../Types/BaseDatabase/NotContains";
|
|
30
30
|
import NotNull from "../../../Types/BaseDatabase/NotNull";
|
|
31
31
|
import Search from "../../../Types/BaseDatabase/Search";
|
|
32
|
+
import MultiSearch from "../../../Types/BaseDatabase/MultiSearch";
|
|
32
33
|
import StartsWith from "../../../Types/BaseDatabase/StartsWith";
|
|
33
34
|
import EndsWith from "../../../Types/BaseDatabase/EndsWith";
|
|
34
35
|
import SortOrder from "../../../Types/BaseDatabase/SortOrder";
|
|
@@ -356,6 +357,57 @@ export default class StatementGenerator<TBaseModel extends AnalyticsBaseModel> {
|
|
|
356
357
|
let first: boolean = true;
|
|
357
358
|
for (const key in query) {
|
|
358
359
|
const value: any = query[key];
|
|
360
|
+
|
|
361
|
+
/*
|
|
362
|
+
* MultiSearch is a synthetic operator that fans out into an ILIKE OR
|
|
363
|
+
* across multiple columns — it does not correspond to `key` itself, so
|
|
364
|
+
* we resolve column metadata per field below.
|
|
365
|
+
*/
|
|
366
|
+
if (value instanceof MultiSearch) {
|
|
367
|
+
const ms: MultiSearch = value;
|
|
368
|
+
if (!ms.value || ms.fields.length === 0) {
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const resolvedColumns: Array<AnalyticsTableColumn> = [];
|
|
373
|
+
for (const field of ms.fields) {
|
|
374
|
+
const col: AnalyticsTableColumn | null =
|
|
375
|
+
this.model.getTableColumn(field);
|
|
376
|
+
if (col) {
|
|
377
|
+
resolvedColumns.push(col);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (resolvedColumns.length === 0) {
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (first) {
|
|
386
|
+
first = false;
|
|
387
|
+
whereStatement.append(SQL`AND (`);
|
|
388
|
+
} else {
|
|
389
|
+
whereStatement.append(SQL` AND (`);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
let isFirstCol: boolean = true;
|
|
393
|
+
for (const col of resolvedColumns) {
|
|
394
|
+
if (isFirstCol) {
|
|
395
|
+
isFirstCol = false;
|
|
396
|
+
} else {
|
|
397
|
+
whereStatement.append(SQL` OR `);
|
|
398
|
+
}
|
|
399
|
+
whereStatement.append(
|
|
400
|
+
SQL`${col.key} ILIKE ${{
|
|
401
|
+
value: new Search<string>(ms.value),
|
|
402
|
+
type: col.type,
|
|
403
|
+
}}`,
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
whereStatement.append(SQL`)`);
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
|
|
359
411
|
const tableColumn: AnalyticsTableColumn | null =
|
|
360
412
|
this.model.getTableColumn(key);
|
|
361
413
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import BadDataException from "../Exception/BadDataException";
|
|
2
|
+
import { JSONObject, ObjectType } from "../JSON";
|
|
3
|
+
import QueryOperator from "./QueryOperator";
|
|
4
|
+
|
|
5
|
+
export default class MultiSearch extends QueryOperator<string> {
|
|
6
|
+
private _fields: Array<string> = [];
|
|
7
|
+
private _value: string = "";
|
|
8
|
+
|
|
9
|
+
public get fields(): Array<string> {
|
|
10
|
+
return this._fields;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
public set fields(v: Array<string>) {
|
|
14
|
+
this._fields = v;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public get value(): string {
|
|
18
|
+
return this._value;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public set value(v: string) {
|
|
22
|
+
this._value = v;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public constructor(data: { fields: Array<string>; value: string }) {
|
|
26
|
+
super();
|
|
27
|
+
this.fields = data.fields;
|
|
28
|
+
this.value = data.value;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public override toString(): string {
|
|
32
|
+
return this.value;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public override toJSON(): JSONObject {
|
|
36
|
+
return {
|
|
37
|
+
_type: ObjectType.MultiSearch,
|
|
38
|
+
value: this.value,
|
|
39
|
+
fields: this.fields,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public static override fromJSON(json: JSONObject): MultiSearch {
|
|
44
|
+
if (json["_type"] === ObjectType.MultiSearch) {
|
|
45
|
+
return new MultiSearch({
|
|
46
|
+
fields: (json["fields"] as Array<string>) || [],
|
|
47
|
+
value: (json["value"] as string) || "",
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
throw new BadDataException("Invalid JSON: " + JSON.stringify(json));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -10,6 +10,7 @@ export enum ComponentInputType {
|
|
|
10
10
|
Decimal = "Decimal",
|
|
11
11
|
MetricsQueryConfig = "MetricsQueryConfig",
|
|
12
12
|
MetricsQueryConfigs = "MetricsQueryConfigs",
|
|
13
|
+
MetricsFormulaConfigs = "MetricsFormulaConfigs",
|
|
13
14
|
LongText = "Long Text",
|
|
14
15
|
Dropdown = "Dropdown",
|
|
15
16
|
MultiSelectDropdown = "MultiSelectDropdown",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import MetricFormulaConfigData from "../../Metrics/MetricFormulaConfigData";
|
|
1
2
|
import MetricQueryConfigData from "../../Metrics/MetricQueryConfigData";
|
|
2
3
|
import ObjectID from "../../ObjectID";
|
|
3
4
|
import DashboardComponentType from "../DashboardComponentType";
|
|
@@ -10,6 +11,7 @@ export default interface DashboardChartComponent extends BaseComponent {
|
|
|
10
11
|
arguments: {
|
|
11
12
|
metricQueryConfig?: MetricQueryConfigData | undefined;
|
|
12
13
|
metricQueryConfigs?: Array<MetricQueryConfigData> | undefined;
|
|
14
|
+
metricFormulaConfigs?: Array<MetricFormulaConfigData> | undefined;
|
|
13
15
|
chartTitle?: string | undefined;
|
|
14
16
|
chartDescription?: string | undefined;
|
|
15
17
|
chartType?: DashboardChartType | undefined;
|
package/Types/JSON.ts
CHANGED
|
@@ -17,6 +17,7 @@ import LessThanOrEqual from "./BaseDatabase/LessThanOrEqual";
|
|
|
17
17
|
import NotEqual from "./BaseDatabase/NotEqual";
|
|
18
18
|
import NotNull from "./BaseDatabase/NotNull";
|
|
19
19
|
import Search from "./BaseDatabase/Search";
|
|
20
|
+
import MultiSearch from "./BaseDatabase/MultiSearch";
|
|
20
21
|
import CallRequest from "./Call/CallRequest";
|
|
21
22
|
import Color from "./Color";
|
|
22
23
|
import { CompareType } from "./Database/CompareBase";
|
|
@@ -61,6 +62,7 @@ export enum ObjectType {
|
|
|
61
62
|
URL = "URL",
|
|
62
63
|
Permission = "Permission",
|
|
63
64
|
Search = "Search",
|
|
65
|
+
MultiSearch = "MultiSearch",
|
|
64
66
|
GreaterThan = "GreaterThan",
|
|
65
67
|
GreaterThanOrEqual = "GreaterThanOrEqual",
|
|
66
68
|
GreaterThanOrNull = "GreaterThanOrNull",
|
|
@@ -123,6 +125,7 @@ export type JSONValue =
|
|
|
123
125
|
| FilterType
|
|
124
126
|
| Array<FilterType>
|
|
125
127
|
| Search<string>
|
|
128
|
+
| MultiSearch
|
|
126
129
|
| Domain
|
|
127
130
|
| Array<Domain>
|
|
128
131
|
| Array<Search<string>>
|
|
@@ -20,6 +20,7 @@ import GreaterThanOrNull from "./BaseDatabase/GreaterThanOrNull";
|
|
|
20
20
|
import NotEqual from "./BaseDatabase/NotEqual";
|
|
21
21
|
import NotNull from "./BaseDatabase/NotNull";
|
|
22
22
|
import Search from "./BaseDatabase/Search";
|
|
23
|
+
import MultiSearch from "./BaseDatabase/MultiSearch";
|
|
23
24
|
import Color from "./Color";
|
|
24
25
|
import OneUptimeDate from "./Date";
|
|
25
26
|
import Dictionary from "./Dictionary";
|
|
@@ -59,6 +60,7 @@ const SerializableObjectDictionary: Dictionary<any> = {
|
|
|
59
60
|
[ObjectType.URL]: URL,
|
|
60
61
|
[ObjectType.IP]: IP,
|
|
61
62
|
[ObjectType.Search]: Search,
|
|
63
|
+
[ObjectType.MultiSearch]: MultiSearch,
|
|
62
64
|
[ObjectType.GreaterThan]: GreaterThan,
|
|
63
65
|
[ObjectType.GreaterThanOrEqual]: GreaterThanOrEqual,
|
|
64
66
|
[ObjectType.LessThan]: LessThan,
|