@oneuptime/common 10.8.0 → 10.8.2
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/CloudResourceInstance.ts +1 -2
- package/Models/DatabaseModels/CloudResourceOwnerTeam.ts +1 -2
- package/Models/DatabaseModels/CloudResourceOwnerUser.ts +1 -2
- package/Models/DatabaseModels/TelemetryException.ts +3 -2
- package/Server/API/TelemetryAPI.ts +78 -25
- package/Server/Infrastructure/Postgres/SchemaMigrations/1780931863719-AddTelemetryResourceMetadataColumns.ts +1 -3
- package/Server/Infrastructure/Postgres/SchemaMigrations/1780933132562-AddServerlessFunctionTables.ts +6 -2
- package/Server/Infrastructure/Postgres/SchemaMigrations/1780935387827-AddCloudResourceTables.ts +18 -6
- package/Server/Infrastructure/Postgres/SchemaMigrations/1781011482945-MigrationName.ts +2317 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Services/CloudResourceLabelRuleEngineService.ts +4 -1
- package/Server/Services/CloudResourceOwnerRuleEngineService.ts +4 -1
- package/Server/Services/LogAggregationService.ts +41 -16
- package/Server/Services/OpenTelemetryIngestService.ts +3 -1
- package/Server/Services/RumApplicationLabelRuleEngineService.ts +4 -1
- package/Server/Services/RumApplicationOwnerRuleEngineService.ts +4 -1
- package/Server/Services/ServerlessFunctionInstanceService.ts +4 -2
- package/Server/Services/ServerlessFunctionLabelRuleEngineService.ts +7 -2
- package/Server/Services/ServerlessFunctionOwnerRuleEngineService.ts +7 -2
- package/Server/Services/TraceAggregationService.ts +128 -4
- package/Server/Utils/Telemetry/ResourceFacetResolver.ts +4 -3
- package/Types/Permission.ts +4 -2
- package/UI/Components/BulkUpdate/BulkLabelActions.tsx +14 -14
- package/UI/Components/Forms/Fields/FormField.tsx +18 -12
- package/UI/Components/Input/Input.tsx +3 -1
- package/UI/Components/TextArea/TextArea.tsx +3 -1
- package/UI/Components/Toast/Toast.tsx +4 -2
- package/build/dist/Models/DatabaseModels/CloudResourceInstance.js.map +1 -1
- package/build/dist/Models/DatabaseModels/CloudResourceOwnerTeam.js.map +1 -1
- package/build/dist/Models/DatabaseModels/CloudResourceOwnerUser.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TelemetryException.js.map +1 -1
- package/build/dist/Server/API/TelemetryAPI.js +61 -23
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780931863719-AddTelemetryResourceMetadataColumns.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780933132562-AddServerlessFunctionTables.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780935387827-AddCloudResourceTables.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1781011482945-MigrationName.js +798 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1781011482945-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/CloudResourceLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/CloudResourceOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/LogAggregationService.js +33 -10
- package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
- package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
- package/build/dist/Server/Services/RumApplicationLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/RumApplicationOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/ServerlessFunctionInstanceService.js +4 -2
- package/build/dist/Server/Services/ServerlessFunctionInstanceService.js.map +1 -1
- package/build/dist/Server/Services/ServerlessFunctionLabelRuleEngineService.js +2 -1
- package/build/dist/Server/Services/ServerlessFunctionLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/ServerlessFunctionOwnerRuleEngineService.js +2 -1
- package/build/dist/Server/Services/ServerlessFunctionOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/TraceAggregationService.js +105 -4
- package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ResourceFacetResolver.js.map +1 -1
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/UI/Components/BulkUpdate/BulkLabelActions.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js +15 -12
- package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
- package/build/dist/UI/Components/Input/Input.js +3 -1
- package/build/dist/UI/Components/Input/Input.js.map +1 -1
- package/build/dist/UI/Components/TextArea/TextArea.js +3 -1
- package/build/dist/UI/Components/TextArea/TextArea.js.map +1 -1
- package/build/dist/UI/Components/Toast/Toast.js +4 -2
- package/build/dist/UI/Components/Toast/Toast.js.map +1 -1
- package/package.json +1 -1
|
@@ -378,6 +378,7 @@ import { AddRumApplicationRuleTables1780940998002 } from "./1780940998002-AddRum
|
|
|
378
378
|
import { AddTelemetryResourceInventoryTables1780941762204 } from "./1780941762204-AddTelemetryResourceInventoryTables";
|
|
379
379
|
import { AddRumApplicationSdkLanguage1780985763463 } from "./1780985763463-AddRumApplicationSdkLanguage";
|
|
380
380
|
import { RecastCloudResourcesByEnvironment1780987192743 } from "./1780987192743-RecastCloudResourcesByEnvironment";
|
|
381
|
+
import { MigrationName1781011482945 } from "./1781011482945-MigrationName";
|
|
381
382
|
|
|
382
383
|
export default [
|
|
383
384
|
InitialMigration,
|
|
@@ -760,4 +761,5 @@ export default [
|
|
|
760
761
|
AddTelemetryResourceInventoryTables1780941762204,
|
|
761
762
|
AddRumApplicationSdkLanguage1780985763463,
|
|
762
763
|
RecastCloudResourcesByEnvironment1780987192743,
|
|
764
|
+
MigrationName1781011482945,
|
|
763
765
|
];
|
|
@@ -153,7 +153,10 @@ class CloudResourceLabelRuleEngineServiceClass {
|
|
|
153
153
|
if (
|
|
154
154
|
rule.descriptionRegexPattern &&
|
|
155
155
|
(!cloudResource.description ||
|
|
156
|
-
!this.testRegex(
|
|
156
|
+
!this.testRegex(
|
|
157
|
+
rule.descriptionRegexPattern,
|
|
158
|
+
cloudResource.description,
|
|
159
|
+
))
|
|
157
160
|
) {
|
|
158
161
|
return false;
|
|
159
162
|
}
|
|
@@ -170,7 +170,10 @@ class CloudResourceOwnerRuleEngineServiceClass {
|
|
|
170
170
|
if (
|
|
171
171
|
rule.descriptionRegexPattern &&
|
|
172
172
|
(!cloudResource.description ||
|
|
173
|
-
!this.testRegex(
|
|
173
|
+
!this.testRegex(
|
|
174
|
+
rule.descriptionRegexPattern,
|
|
175
|
+
cloudResource.description,
|
|
176
|
+
))
|
|
174
177
|
) {
|
|
175
178
|
return false;
|
|
176
179
|
}
|
|
@@ -165,40 +165,65 @@ export class LogAggregationService {
|
|
|
165
165
|
private static buildHistogramStatement(request: HistogramRequest): Statement {
|
|
166
166
|
const intervalSeconds: number = request.bucketSizeInMinutes * 60;
|
|
167
167
|
|
|
168
|
+
/*
|
|
169
|
+
* Two-stage aggregation mirroring TraceAggregationService.getHistogram.
|
|
170
|
+
* The inner query groups by toStartOfInterval(time, INTERVAL 1 MINUTE) —
|
|
171
|
+
* the exact key expression of the proj_severity_histogram projection
|
|
172
|
+
* (projectId, severityText, minute) — and filters the window on that same
|
|
173
|
+
* expression rather than raw `time`. A raw `time` predicate references a
|
|
174
|
+
* column the aggregate projection does not store, so ClickHouse rejects
|
|
175
|
+
* the projection and full-scans (verified: 2.1M rows / ~46ms vs 978 rows /
|
|
176
|
+
* ~7ms with this form). The outer query re-buckets the tiny minute-level
|
|
177
|
+
* result to the requested size. Window edges round to the minute, which is
|
|
178
|
+
* consistent with the minute-bucketed output and only shifts the first/last
|
|
179
|
+
* bucket by the partial boundary minute when the range is not minute-aligned.
|
|
180
|
+
*
|
|
181
|
+
* A non-projection filter (serviceId, traceId, spanId, attributes) makes
|
|
182
|
+
* the inner query transparently fall back to a base-table scan — same cost
|
|
183
|
+
* as before, still correct.
|
|
184
|
+
*/
|
|
168
185
|
const statement: Statement = SQL`
|
|
169
186
|
SELECT
|
|
170
|
-
toStartOfInterval(
|
|
187
|
+
toStartOfInterval(minute, INTERVAL ${{
|
|
171
188
|
type: TableColumnType.Number,
|
|
172
189
|
value: intervalSeconds,
|
|
173
190
|
}} SECOND) AS bucket,
|
|
174
191
|
severityText,
|
|
175
|
-
|
|
176
|
-
FROM
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
AND time <= ${{
|
|
186
|
-
type: TableColumnType.Date,
|
|
187
|
-
value: request.endTime,
|
|
192
|
+
sum(cnt_minute) AS cnt
|
|
193
|
+
FROM (
|
|
194
|
+
SELECT
|
|
195
|
+
toStartOfInterval(time, INTERVAL 1 MINUTE) AS minute,
|
|
196
|
+
severityText,
|
|
197
|
+
count() AS cnt_minute
|
|
198
|
+
FROM ${LogAggregationService.TABLE_NAME}
|
|
199
|
+
WHERE projectId = ${{
|
|
200
|
+
type: TableColumnType.ObjectID,
|
|
201
|
+
value: request.projectId,
|
|
188
202
|
}}
|
|
203
|
+
AND toStartOfInterval(time, INTERVAL 1 MINUTE) >= toStartOfInterval(${{
|
|
204
|
+
type: TableColumnType.Date,
|
|
205
|
+
value: request.startTime,
|
|
206
|
+
}}, INTERVAL 1 MINUTE)
|
|
207
|
+
AND toStartOfInterval(time, INTERVAL 1 MINUTE) <= toStartOfInterval(${{
|
|
208
|
+
type: TableColumnType.Date,
|
|
209
|
+
value: request.endTime,
|
|
210
|
+
}}, INTERVAL 1 MINUTE)
|
|
189
211
|
`;
|
|
190
212
|
|
|
191
213
|
LogAggregationService.appendCommonFilters(statement, request);
|
|
192
214
|
|
|
193
|
-
statement.append(
|
|
215
|
+
statement.append(
|
|
216
|
+
" GROUP BY minute, severityText ) GROUP BY bucket, severityText ORDER BY bucket ASC",
|
|
217
|
+
);
|
|
194
218
|
|
|
195
219
|
/*
|
|
196
220
|
* Defense in depth: cap histogram runtime below nginx's 60s
|
|
197
221
|
* proxy_read_timeout. 'break' returns partial aggregated results
|
|
198
222
|
* rather than throwing, which is acceptable for a density viz.
|
|
223
|
+
* Explicitly enable projection use.
|
|
199
224
|
*/
|
|
200
225
|
statement.append(
|
|
201
|
-
" SETTINGS max_execution_time = 45, timeout_overflow_mode = 'break'",
|
|
226
|
+
" SETTINGS max_execution_time = 45, timeout_overflow_mode = 'break', optimize_use_projections = 1",
|
|
202
227
|
);
|
|
203
228
|
|
|
204
229
|
return statement;
|
|
@@ -108,7 +108,9 @@ export default class OTelIngestService {
|
|
|
108
108
|
attribute["value"] &&
|
|
109
109
|
(attribute["value"] as JSONObject)["stringValue"]
|
|
110
110
|
) {
|
|
111
|
-
const value: unknown = (attribute["value"] as JSONObject)[
|
|
111
|
+
const value: unknown = (attribute["value"] as JSONObject)[
|
|
112
|
+
"stringValue"
|
|
113
|
+
];
|
|
112
114
|
if (typeof value === "string" && value.trim()) {
|
|
113
115
|
return value.trim();
|
|
114
116
|
}
|
|
@@ -153,7 +153,10 @@ class RumApplicationLabelRuleEngineServiceClass {
|
|
|
153
153
|
if (
|
|
154
154
|
rule.descriptionRegexPattern &&
|
|
155
155
|
(!rumApplication.description ||
|
|
156
|
-
!this.testRegex(
|
|
156
|
+
!this.testRegex(
|
|
157
|
+
rule.descriptionRegexPattern,
|
|
158
|
+
rumApplication.description,
|
|
159
|
+
))
|
|
157
160
|
) {
|
|
158
161
|
return false;
|
|
159
162
|
}
|
|
@@ -170,7 +170,10 @@ class RumApplicationOwnerRuleEngineServiceClass {
|
|
|
170
170
|
if (
|
|
171
171
|
rule.descriptionRegexPattern &&
|
|
172
172
|
(!rumApplication.description ||
|
|
173
|
-
!this.testRegex(
|
|
173
|
+
!this.testRegex(
|
|
174
|
+
rule.descriptionRegexPattern,
|
|
175
|
+
rumApplication.description,
|
|
176
|
+
))
|
|
174
177
|
) {
|
|
175
178
|
return false;
|
|
176
179
|
}
|
|
@@ -47,8 +47,10 @@ export class Service extends DatabaseService<Model> {
|
|
|
47
47
|
item.lastSeenAt = OneUptimeDate.getCurrentDate();
|
|
48
48
|
await this.create({ data: item, props: { isRoot: true } });
|
|
49
49
|
} catch (err) {
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
/*
|
|
51
|
+
* Inventory is best-effort — a unique-violation race or transient error
|
|
52
|
+
* must never fail ingest.
|
|
53
|
+
*/
|
|
52
54
|
logger.warn(
|
|
53
55
|
`ServerlessFunctionInstanceService.recordInstance failed: ${
|
|
54
56
|
err instanceof Error ? err.message : String(err)
|
|
@@ -126,7 +126,10 @@ class ServerlessFunctionLabelRuleEngineServiceClass {
|
|
|
126
126
|
rule: ServerlessFunctionLabelRule,
|
|
127
127
|
): boolean {
|
|
128
128
|
if (rule.matchLabels && rule.matchLabels.length > 0) {
|
|
129
|
-
if (
|
|
129
|
+
if (
|
|
130
|
+
!serverlessFunction.labels ||
|
|
131
|
+
serverlessFunction.labels.length === 0
|
|
132
|
+
) {
|
|
130
133
|
return false;
|
|
131
134
|
}
|
|
132
135
|
const ruleLabelIds: Array<string> = rule.matchLabels.map((l: Label) => {
|
|
@@ -173,7 +176,9 @@ class ServerlessFunctionLabelRuleEngineServiceClass {
|
|
|
173
176
|
const regex: RegExp = new RegExp(pattern, "i");
|
|
174
177
|
return regex.test(value);
|
|
175
178
|
} catch {
|
|
176
|
-
logger.warn(
|
|
179
|
+
logger.warn(
|
|
180
|
+
`Invalid regex in serverless function label rule: ${pattern}`,
|
|
181
|
+
);
|
|
177
182
|
return false;
|
|
178
183
|
}
|
|
179
184
|
}
|
|
@@ -143,7 +143,10 @@ class ServerlessFunctionOwnerRuleEngineServiceClass {
|
|
|
143
143
|
rule: ServerlessFunctionOwnerRule,
|
|
144
144
|
): boolean {
|
|
145
145
|
if (rule.matchLabels && rule.matchLabels.length > 0) {
|
|
146
|
-
if (
|
|
146
|
+
if (
|
|
147
|
+
!serverlessFunction.labels ||
|
|
148
|
+
serverlessFunction.labels.length === 0
|
|
149
|
+
) {
|
|
147
150
|
return false;
|
|
148
151
|
}
|
|
149
152
|
const ruleLabelIds: Array<string> = rule.matchLabels.map((l: Label) => {
|
|
@@ -190,7 +193,9 @@ class ServerlessFunctionOwnerRuleEngineServiceClass {
|
|
|
190
193
|
const regex: RegExp = new RegExp(pattern, "i");
|
|
191
194
|
return regex.test(value);
|
|
192
195
|
} catch {
|
|
193
|
-
logger.warn(
|
|
196
|
+
logger.warn(
|
|
197
|
+
`Invalid regex in serverless function owner rule: ${pattern}`,
|
|
198
|
+
);
|
|
194
199
|
return false;
|
|
195
200
|
}
|
|
196
201
|
}
|
|
@@ -345,6 +345,121 @@ export class TraceAggregationService {
|
|
|
345
345
|
return result;
|
|
346
346
|
}
|
|
347
347
|
|
|
348
|
+
/*
|
|
349
|
+
* Accurate per-service and per-status counts over the FULL time window,
|
|
350
|
+
* computed with a real GROUP BY instead of the recent-N sample in
|
|
351
|
+
* getFacetValuesFromSample(). The sample saturates with whichever service
|
|
352
|
+
* is chattiest right now, so high-volume services that aren't in the most
|
|
353
|
+
* recent N root spans read 0 — the "top 1000" symptom. This GROUP BY is
|
|
354
|
+
* exact regardless of skew.
|
|
355
|
+
*
|
|
356
|
+
* It is cheap because it rides the proj_hist_by_minute aggregate projection
|
|
357
|
+
* (projectId, minute, serviceId, statusCode, isRootSpan -> count): a 1-month
|
|
358
|
+
* window reads a few thousand pre-aggregated minute rows in single-digit ms
|
|
359
|
+
* instead of scanning tens of millions of raw spans. Two things are required
|
|
360
|
+
* for ClickHouse to actually pick that projection, both load-bearing:
|
|
361
|
+
* 1. The time predicate must be on toStartOfMinute(startTime) — the
|
|
362
|
+
* projection's key expression — NOT raw startTime. A raw startTime
|
|
363
|
+
* filter references a column the aggregate projection does not store, so
|
|
364
|
+
* ClickHouse rejects the projection and full-scans. (Window edges land
|
|
365
|
+
* on minute boundaries, consistent with the minute-bucketed histogram.)
|
|
366
|
+
* 2. Every other predicate must be on a projection column. isRootSpan,
|
|
367
|
+
* serviceId and statusCode all are, so the default sidebar load and
|
|
368
|
+
* drill-down-by-service stay on the projection. A non-projection filter
|
|
369
|
+
* (kind / name / traceId / attributes) transparently falls back to a
|
|
370
|
+
* base-table scan — still correct, still bounded by max_execution_time.
|
|
371
|
+
*
|
|
372
|
+
* serviceId is intentionally NOT disambiguated by serviceType here. Resource
|
|
373
|
+
* IDs are globally unique, so a single serviceId -> count map correctly
|
|
374
|
+
* serves the service / host / docker host / k8s cluster facets once merged
|
|
375
|
+
* against each Postgres source-of-truth list (a host id never collides with
|
|
376
|
+
* a service id, so an unrelated entry is simply never looked up). Omitting
|
|
377
|
+
* the serviceType predicate keeps the query projection-eligible.
|
|
378
|
+
*/
|
|
379
|
+
@CaptureSpan()
|
|
380
|
+
public static async getResourceFacetCounts(
|
|
381
|
+
request: MultiFacetRequest,
|
|
382
|
+
): Promise<{
|
|
383
|
+
serviceCounts: Map<string, number>;
|
|
384
|
+
statusCounts: Map<string, number>;
|
|
385
|
+
}> {
|
|
386
|
+
const statement: Statement = new Statement();
|
|
387
|
+
statement.append(
|
|
388
|
+
`SELECT serviceId, statusCode, count() AS cnt FROM ${TraceAggregationService.TABLE_NAME}`,
|
|
389
|
+
);
|
|
390
|
+
statement.append(
|
|
391
|
+
SQL` WHERE projectId = ${{
|
|
392
|
+
type: TableColumnType.ObjectID,
|
|
393
|
+
value: request.projectId,
|
|
394
|
+
}} AND toStartOfMinute(startTime) >= toStartOfMinute(${{
|
|
395
|
+
type: TableColumnType.Date,
|
|
396
|
+
value: request.startTime,
|
|
397
|
+
}}) AND toStartOfMinute(startTime) <= toStartOfMinute(${{
|
|
398
|
+
type: TableColumnType.Date,
|
|
399
|
+
value: request.endTime,
|
|
400
|
+
}})`,
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
TraceAggregationService.appendCommonFilters(statement, request);
|
|
404
|
+
|
|
405
|
+
statement.append(" GROUP BY serviceId, statusCode");
|
|
406
|
+
|
|
407
|
+
/*
|
|
408
|
+
* Cap runtime below nginx's 60s proxy_read_timeout and explicitly allow
|
|
409
|
+
* projection use so proj_hist_by_minute is read when eligible (see the
|
|
410
|
+
* toStartOfMinute note above).
|
|
411
|
+
*/
|
|
412
|
+
statement.append(
|
|
413
|
+
" SETTINGS max_execution_time = 45, timeout_overflow_mode = 'break', optimize_use_projections = 1",
|
|
414
|
+
);
|
|
415
|
+
|
|
416
|
+
const serviceCounts: Map<string, number> = new Map<string, number>();
|
|
417
|
+
const statusCounts: Map<string, number> = new Map<string, number>();
|
|
418
|
+
|
|
419
|
+
const dbResult: Results = await SpanService.executeQuery(statement);
|
|
420
|
+
|
|
421
|
+
let rows: Array<JSONObject> = [];
|
|
422
|
+
try {
|
|
423
|
+
const response: DbJSONResponse = await dbResult.json<{
|
|
424
|
+
data?: Array<JSONObject>;
|
|
425
|
+
}>();
|
|
426
|
+
rows = response.data || [];
|
|
427
|
+
} catch {
|
|
428
|
+
/*
|
|
429
|
+
* 'break' mode can return truncated JSON on timeout. Degrade to empty
|
|
430
|
+
* counts — the Postgres resolver still lists every resource (count 0),
|
|
431
|
+
* which is no worse than the prior transient-failure behavior.
|
|
432
|
+
*/
|
|
433
|
+
logger.warn(
|
|
434
|
+
"Resource facet count query returned unparseable response, returning empty counts",
|
|
435
|
+
);
|
|
436
|
+
return { serviceCounts, statusCounts };
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
for (const row of rows) {
|
|
440
|
+
const cnt: number = Number(row["cnt"] || 0);
|
|
441
|
+
|
|
442
|
+
const rawServiceId: unknown = row["serviceId"];
|
|
443
|
+
if (rawServiceId !== undefined && rawServiceId !== null) {
|
|
444
|
+
const serviceId: string = String(rawServiceId);
|
|
445
|
+
if (serviceId.length > 0) {
|
|
446
|
+
serviceCounts.set(
|
|
447
|
+
serviceId,
|
|
448
|
+
(serviceCounts.get(serviceId) || 0) + cnt,
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const rawStatusCode: unknown = row["statusCode"];
|
|
454
|
+
if (rawStatusCode !== undefined && rawStatusCode !== null) {
|
|
455
|
+
const statusCode: string = String(rawStatusCode);
|
|
456
|
+
statusCounts.set(statusCode, (statusCounts.get(statusCode) || 0) + cnt);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return { serviceCounts, statusCounts };
|
|
461
|
+
}
|
|
462
|
+
|
|
348
463
|
private static mapStatusCodeToSeries(code: number): string {
|
|
349
464
|
if (code === 1) {
|
|
350
465
|
return "ok";
|
|
@@ -366,6 +481,15 @@ export class TraceAggregationService {
|
|
|
366
481
|
* even for multi-week ranges. The outer query then re-buckets the tiny
|
|
367
482
|
* minute-level result to the requested bucket size.
|
|
368
483
|
*
|
|
484
|
+
* The time window is filtered on toStartOfMinute(startTime) — the
|
|
485
|
+
* projection's key expression — NOT raw startTime. A raw startTime
|
|
486
|
+
* predicate references a column the aggregate projection does not store,
|
|
487
|
+
* so ClickHouse rejects proj_hist_by_minute and full-scans the base table
|
|
488
|
+
* (verified: 32M rows / ~220ms vs 1.5K rows / ~6ms with this form). The
|
|
489
|
+
* window edges round to the minute, which is consistent with the
|
|
490
|
+
* minute-bucketed output and only shifts the first/last bucket by the
|
|
491
|
+
* partial boundary minute when the range is not minute-aligned.
|
|
492
|
+
*
|
|
369
493
|
* If any non-projection filter (kind, name, traceId, nameSearchText,
|
|
370
494
|
* attributes) is active, ClickHouse transparently falls back to
|
|
371
495
|
* scanning the main table for the inner query — same cost as before.
|
|
@@ -388,14 +512,14 @@ export class TraceAggregationService {
|
|
|
388
512
|
type: TableColumnType.ObjectID,
|
|
389
513
|
value: request.projectId,
|
|
390
514
|
}}
|
|
391
|
-
AND startTime >= ${{
|
|
515
|
+
AND toStartOfMinute(startTime) >= toStartOfMinute(${{
|
|
392
516
|
type: TableColumnType.Date,
|
|
393
517
|
value: request.startTime,
|
|
394
|
-
}}
|
|
395
|
-
AND startTime <= ${{
|
|
518
|
+
}})
|
|
519
|
+
AND toStartOfMinute(startTime) <= toStartOfMinute(${{
|
|
396
520
|
type: TableColumnType.Date,
|
|
397
521
|
value: request.endTime,
|
|
398
|
-
}}
|
|
522
|
+
}})
|
|
399
523
|
`;
|
|
400
524
|
|
|
401
525
|
TraceAggregationService.appendCommonFilters(statement, request);
|
|
@@ -389,8 +389,8 @@ export default class ResourceFacetResolver {
|
|
|
389
389
|
});
|
|
390
390
|
}
|
|
391
391
|
|
|
392
|
-
const apps: Array<RumApplicationModel> =
|
|
393
|
-
|
|
392
|
+
const apps: Array<RumApplicationModel> = await RumApplicationService.findBy(
|
|
393
|
+
{
|
|
394
394
|
query: query as any,
|
|
395
395
|
select: {
|
|
396
396
|
_id: true,
|
|
@@ -400,7 +400,8 @@ export default class ResourceFacetResolver {
|
|
|
400
400
|
limit: new PositiveNumber(limit),
|
|
401
401
|
skip: new PositiveNumber(0),
|
|
402
402
|
props: { isRoot: true },
|
|
403
|
-
}
|
|
403
|
+
},
|
|
404
|
+
);
|
|
404
405
|
|
|
405
406
|
return ResourceFacetResolver.mergeCounts(
|
|
406
407
|
apps.map(
|
package/Types/Permission.ts
CHANGED
|
@@ -7627,7 +7627,8 @@ export class PermissionHelper {
|
|
|
7627
7627
|
{
|
|
7628
7628
|
permission: Permission.EditRumApplication,
|
|
7629
7629
|
title: "Edit RUM Application",
|
|
7630
|
-
description:
|
|
7630
|
+
description:
|
|
7631
|
+
"This permission can edit RUM Application of this project.",
|
|
7631
7632
|
isAssignableToTenant: true,
|
|
7632
7633
|
isAccessControlPermission: true,
|
|
7633
7634
|
isRolePermission: false,
|
|
@@ -7636,7 +7637,8 @@ export class PermissionHelper {
|
|
|
7636
7637
|
{
|
|
7637
7638
|
permission: Permission.ReadRumApplication,
|
|
7638
7639
|
title: "Read RUM Application",
|
|
7639
|
-
description:
|
|
7640
|
+
description:
|
|
7641
|
+
"This permission can read RUM Application of this project.",
|
|
7640
7642
|
isAssignableToTenant: true,
|
|
7641
7643
|
isAccessControlPermission: true,
|
|
7642
7644
|
isRolePermission: false,
|
|
@@ -208,9 +208,9 @@ function useBulkLabelActions<T extends BaseModel>(
|
|
|
208
208
|
* modal offers only labels that can really be removed instead of every
|
|
209
209
|
* label in the project.
|
|
210
210
|
*/
|
|
211
|
-
const loadLabelsForSelectedItems: (
|
|
211
|
+
const loadLabelsForSelectedItems: (items: Array<T>) => Promise<void> = async (
|
|
212
212
|
items: Array<T>,
|
|
213
|
-
)
|
|
213
|
+
): Promise<void> => {
|
|
214
214
|
setIsLoadingRemoveLabels(true);
|
|
215
215
|
|
|
216
216
|
try {
|
|
@@ -259,20 +259,20 @@ function useBulkLabelActions<T extends BaseModel>(
|
|
|
259
259
|
}
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
-
const options: Array<DropdownOption> = Array.from(
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
};
|
|
262
|
+
const options: Array<DropdownOption> = Array.from(labelById.values()).map(
|
|
263
|
+
(label: Label) => {
|
|
264
|
+
const option: DropdownOption = {
|
|
265
|
+
label: label.name || "",
|
|
266
|
+
value: label._id?.toString() || "",
|
|
267
|
+
};
|
|
269
268
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
269
|
+
if (label.color) {
|
|
270
|
+
option.color = label.color;
|
|
271
|
+
}
|
|
273
272
|
|
|
274
|
-
|
|
275
|
-
|
|
273
|
+
return option;
|
|
274
|
+
},
|
|
275
|
+
);
|
|
276
276
|
|
|
277
277
|
options.sort((a: DropdownOption, b: DropdownOption) => {
|
|
278
278
|
return a.label.localeCompare(b.label);
|
|
@@ -40,6 +40,7 @@ import Radio, { RadioValue } from "../../Radio/Radio";
|
|
|
40
40
|
import { BasicRadioButtonOption } from "../../RadioButtons/BasicRadioButtons";
|
|
41
41
|
import HorizontalRule from "../../HorizontalRule/HorizontalRule";
|
|
42
42
|
import MarkdownEditor from "../../Markdown.tsx/MarkdownEditor";
|
|
43
|
+
import useTranslateValue from "../../../Utils/Translation";
|
|
43
44
|
|
|
44
45
|
export interface ComponentProps<T extends GenericObject> {
|
|
45
46
|
field: Field<T>;
|
|
@@ -61,6 +62,11 @@ const FormField: <T extends GenericObject>(
|
|
|
61
62
|
) => ReactElement = <T extends GenericObject>(
|
|
62
63
|
props: ComponentProps<T>,
|
|
63
64
|
): ReactElement => {
|
|
65
|
+
const { translateString } = useTranslateValue();
|
|
66
|
+
const translatedPlaceholder: string | undefined = translateString(
|
|
67
|
+
props.field.placeholder,
|
|
68
|
+
);
|
|
69
|
+
|
|
64
70
|
type onChangeFunction = (value: JSONValue) => void;
|
|
65
71
|
|
|
66
72
|
const onChange: onChangeFunction = (value: JSONValue): void => {
|
|
@@ -337,7 +343,7 @@ const FormField: <T extends GenericObject>(
|
|
|
337
343
|
(props.field.defaultValue as any) ||
|
|
338
344
|
undefined
|
|
339
345
|
}
|
|
340
|
-
placeholder={
|
|
346
|
+
placeholder={translatedPlaceholder || undefined}
|
|
341
347
|
/>
|
|
342
348
|
)}
|
|
343
349
|
|
|
@@ -351,7 +357,7 @@ const FormField: <T extends GenericObject>(
|
|
|
351
357
|
props.setFieldTouched(props.fieldName, true);
|
|
352
358
|
}}
|
|
353
359
|
tabIndex={index}
|
|
354
|
-
placeholder={
|
|
360
|
+
placeholder={translatedPlaceholder || ""}
|
|
355
361
|
initialValue={
|
|
356
362
|
props.currentValues &&
|
|
357
363
|
(props.currentValues as any)[props.fieldName]
|
|
@@ -371,7 +377,7 @@ const FormField: <T extends GenericObject>(
|
|
|
371
377
|
props.setFieldTouched(props.fieldName, true);
|
|
372
378
|
}}
|
|
373
379
|
tabIndex={index}
|
|
374
|
-
placeholder={
|
|
380
|
+
placeholder={translatedPlaceholder || ""}
|
|
375
381
|
initialValue={
|
|
376
382
|
props.currentValues &&
|
|
377
383
|
(props.currentValues as any)[props.fieldName]
|
|
@@ -414,7 +420,7 @@ const FormField: <T extends GenericObject>(
|
|
|
414
420
|
labelField={props.field.dropdownModal.labelField}
|
|
415
421
|
valueField={props.field.dropdownModal.valueField}
|
|
416
422
|
options={props.field.dropdownOptions || []}
|
|
417
|
-
placeholder={
|
|
423
|
+
placeholder={translatedPlaceholder || ""}
|
|
418
424
|
value={
|
|
419
425
|
props.currentValues &&
|
|
420
426
|
(props.currentValues as any)[props.fieldName]
|
|
@@ -442,7 +448,7 @@ const FormField: <T extends GenericObject>(
|
|
|
442
448
|
FormFieldSchemaType.MultiSelectDropdown
|
|
443
449
|
}
|
|
444
450
|
options={props.field.dropdownOptions || []}
|
|
445
|
-
placeholder={
|
|
451
|
+
placeholder={translatedPlaceholder || ""}
|
|
446
452
|
value={
|
|
447
453
|
props.currentValues &&
|
|
448
454
|
(props.currentValues as any)[props.fieldName]
|
|
@@ -585,7 +591,7 @@ const FormField: <T extends GenericObject>(
|
|
|
585
591
|
? (props.currentValues as any)[props.fieldName]
|
|
586
592
|
: ""
|
|
587
593
|
}
|
|
588
|
-
placeholder={
|
|
594
|
+
placeholder={translatedPlaceholder || ""}
|
|
589
595
|
/>
|
|
590
596
|
)}
|
|
591
597
|
|
|
@@ -614,7 +620,7 @@ const FormField: <T extends GenericObject>(
|
|
|
614
620
|
? (props.currentValues as any)[props.fieldName]
|
|
615
621
|
: ""
|
|
616
622
|
}
|
|
617
|
-
placeholder={
|
|
623
|
+
placeholder={translatedPlaceholder || ""}
|
|
618
624
|
/>
|
|
619
625
|
)}
|
|
620
626
|
|
|
@@ -637,7 +643,7 @@ const FormField: <T extends GenericObject>(
|
|
|
637
643
|
? (props.currentValues as any)[props.fieldName]
|
|
638
644
|
: ""
|
|
639
645
|
}
|
|
640
|
-
placeholder={
|
|
646
|
+
placeholder={translatedPlaceholder || ""}
|
|
641
647
|
/>
|
|
642
648
|
)}
|
|
643
649
|
|
|
@@ -660,7 +666,7 @@ const FormField: <T extends GenericObject>(
|
|
|
660
666
|
? (props.currentValues as any)[props.fieldName]
|
|
661
667
|
: "",
|
|
662
668
|
|
|
663
|
-
placeholder:
|
|
669
|
+
placeholder: translatedPlaceholder || "",
|
|
664
670
|
})}
|
|
665
671
|
|
|
666
672
|
{(props.field.fieldType === FormFieldSchemaType.HTML ||
|
|
@@ -684,7 +690,7 @@ const FormField: <T extends GenericObject>(
|
|
|
684
690
|
? (props.currentValues as any)[props.fieldName]
|
|
685
691
|
: ""
|
|
686
692
|
}
|
|
687
|
-
placeholder={
|
|
693
|
+
placeholder={translatedPlaceholder || ""}
|
|
688
694
|
/>
|
|
689
695
|
)}
|
|
690
696
|
|
|
@@ -741,7 +747,7 @@ const FormField: <T extends GenericObject>(
|
|
|
741
747
|
? []
|
|
742
748
|
: undefined
|
|
743
749
|
}
|
|
744
|
-
placeholder={
|
|
750
|
+
placeholder={translatedPlaceholder || ""}
|
|
745
751
|
/>
|
|
746
752
|
)}
|
|
747
753
|
|
|
@@ -849,7 +855,7 @@ const FormField: <T extends GenericObject>(
|
|
|
849
855
|
? (props.currentValues as any)[props.fieldName]
|
|
850
856
|
: props.field.defaultValue || ""
|
|
851
857
|
}
|
|
852
|
-
placeholder={
|
|
858
|
+
placeholder={translatedPlaceholder || ""}
|
|
853
859
|
/>
|
|
854
860
|
)}
|
|
855
861
|
</div>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Tailwind
|
|
2
2
|
import { Logger } from "../../Utils/Logger";
|
|
3
|
+
import useTranslateValue from "../../Utils/Translation";
|
|
3
4
|
import Icon from "../Icon/Icon";
|
|
4
5
|
import OneUptimeDate from "../../../Types/Date";
|
|
5
6
|
import IconProp from "../../../Types/Icon/IconProp";
|
|
@@ -46,6 +47,7 @@ export interface ComponentProps {
|
|
|
46
47
|
const Input: FunctionComponent<ComponentProps> = (
|
|
47
48
|
props: ComponentProps,
|
|
48
49
|
): ReactElement => {
|
|
50
|
+
const { translateString } = useTranslateValue();
|
|
49
51
|
let className: string = "";
|
|
50
52
|
|
|
51
53
|
if (!props.className) {
|
|
@@ -199,7 +201,7 @@ const Input: FunctionComponent<ComponentProps> = (
|
|
|
199
201
|
? "1"
|
|
200
202
|
: undefined
|
|
201
203
|
}
|
|
202
|
-
placeholder={props.placeholder}
|
|
204
|
+
placeholder={translateString(props.placeholder)}
|
|
203
205
|
className={className}
|
|
204
206
|
onBlur={() => {
|
|
205
207
|
if (props.onBlur) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Icon from "../Icon/Icon";
|
|
2
2
|
import IconProp from "../../../Types/Icon/IconProp";
|
|
3
|
+
import useTranslateValue from "../../Utils/Translation";
|
|
3
4
|
import React, {
|
|
4
5
|
FunctionComponent,
|
|
5
6
|
ReactElement,
|
|
@@ -25,6 +26,7 @@ export interface ComponentProps {
|
|
|
25
26
|
const TextArea: FunctionComponent<ComponentProps> = (
|
|
26
27
|
props: ComponentProps,
|
|
27
28
|
): ReactElement => {
|
|
29
|
+
const { translateString } = useTranslateValue();
|
|
28
30
|
const [text, setText] = useState<string>(props.initialValue || "");
|
|
29
31
|
|
|
30
32
|
let className: string = "";
|
|
@@ -61,7 +63,7 @@ const TextArea: FunctionComponent<ComponentProps> = (
|
|
|
61
63
|
<div className="relative mt-2 mb-1 rounded-md shadow-sm">
|
|
62
64
|
<textarea
|
|
63
65
|
autoFocus={props.autoFocus}
|
|
64
|
-
placeholder={props.placeholder}
|
|
66
|
+
placeholder={translateString(props.placeholder)}
|
|
65
67
|
data-testid={props.dataTestId}
|
|
66
68
|
className={`${className || ""}`}
|
|
67
69
|
value={text}
|