@oneuptime/common 10.5.1 → 10.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/DatabaseModels/StatusPageGroup.ts +212 -0
- package/Models/DatabaseModels/StatusPageResource.ts +86 -0
- package/Models/DatabaseModels/TelemetryException.ts +10 -0
- package/Server/API/StatusPageAPI.ts +15 -0
- package/Server/API/TelemetryAPI.ts +406 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1779879993421-MigrationName.ts +68 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1779882573463-MigrationName.ts +65 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +5 -0
- package/Server/Services/ExceptionAggregationService.ts +51 -3
- package/Server/Services/LogAggregationService.ts +1 -0
- package/Server/Services/MetricAggregationService.ts +227 -0
- package/Server/Services/OpenTelemetryIngestService.ts +101 -1
- package/Server/Services/StatusPageService.ts +5 -0
- package/Server/Services/TraceAggregationService.ts +1 -0
- package/Server/Utils/Monitor/MonitorLogUtil.ts +146 -6
- package/Server/Utils/Telemetry/ResourceFacetResolver.ts +299 -0
- package/Types/Monitor/MonitorStep.ts +85 -0
- package/Types/StatusPage/StatusPageGroupViewMode.ts +6 -0
- package/UI/Components/Accordion/Accordion.tsx +32 -26
- package/UI/Components/LogsViewer/LogsViewer.tsx +10 -0
- package/UI/Components/LogsViewer/components/FacetSection.tsx +40 -3
- package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +23 -0
- package/UI/Components/LogsViewer/types.ts +2 -0
- package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +8 -0
- package/UI/Components/TelemetryViewer/components/TelemetryFacetSection.tsx +49 -3
- package/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.tsx +16 -0
- package/UI/Components/TelemetryViewer/types.ts +12 -0
- package/build/dist/Models/DatabaseModels/StatusPageGroup.js +217 -0
- package/build/dist/Models/DatabaseModels/StatusPageGroup.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageResource.js +88 -0
- package/build/dist/Models/DatabaseModels/StatusPageResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TelemetryException.js +11 -0
- package/build/dist/Models/DatabaseModels/TelemetryException.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +15 -0
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/API/TelemetryAPI.js +285 -0
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779879993421-MigrationName.js +42 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779879993421-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779882573463-MigrationName.js +28 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779882573463-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/ExceptionAggregationService.js +44 -4
- package/build/dist/Server/Services/ExceptionAggregationService.js.map +1 -1
- package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
- package/build/dist/Server/Services/MetricAggregationService.js +159 -0
- package/build/dist/Server/Services/MetricAggregationService.js.map +1 -0
- package/build/dist/Server/Services/OpenTelemetryIngestService.js +60 -3
- package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +5 -0
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js +127 -4
- package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ResourceFacetResolver.js +204 -0
- package/build/dist/Server/Utils/Telemetry/ResourceFacetResolver.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorStep.js +59 -0
- package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
- package/build/dist/Types/StatusPage/StatusPageGroupViewMode.js +7 -0
- package/build/dist/Types/StatusPage/StatusPageGroupViewMode.js.map +1 -0
- package/build/dist/UI/Components/Accordion/Accordion.js +11 -11
- package/build/dist/UI/Components/Accordion/Accordion.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/FacetSection.js +26 -6
- package/build/dist/UI/Components/LogsViewer/components/FacetSection.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +12 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/types.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +1 -1
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSection.js +32 -6
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSection.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js +6 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js.map +1 -1
- package/package.json +1 -1
|
@@ -32,7 +32,13 @@ import TraceAggregationService, {
|
|
|
32
32
|
import ExceptionAggregationService, {
|
|
33
33
|
HistogramBucket as ExceptionHistogramBucket,
|
|
34
34
|
HistogramRequest as ExceptionHistogramRequest,
|
|
35
|
+
FacetValue as ExceptionFacetValue,
|
|
36
|
+
FacetRequest as ExceptionFacetRequest,
|
|
35
37
|
} from "../Services/ExceptionAggregationService";
|
|
38
|
+
import MetricAggregationService, {
|
|
39
|
+
FacetValue as MetricFacetValue,
|
|
40
|
+
FacetRequest as MetricFacetRequest,
|
|
41
|
+
} from "../Services/MetricAggregationService";
|
|
36
42
|
import ProfileAggregationService, {
|
|
37
43
|
FlamegraphRequest,
|
|
38
44
|
FunctionListRequest,
|
|
@@ -55,6 +61,10 @@ import SortOrder from "../../Types/BaseDatabase/SortOrder";
|
|
|
55
61
|
import ObjectID from "../../Types/ObjectID";
|
|
56
62
|
import OneUptimeDate from "../../Types/Date";
|
|
57
63
|
import { JSONObject } from "../../Types/JSON";
|
|
64
|
+
import ResourceFacetResolver, {
|
|
65
|
+
ResolvedFacetValue,
|
|
66
|
+
ResourceFacetSpec,
|
|
67
|
+
} from "../Utils/Telemetry/ResourceFacetResolver";
|
|
58
68
|
|
|
59
69
|
const router: ExpressRouter = Express.getRouter();
|
|
60
70
|
|
|
@@ -393,6 +403,18 @@ router.post(
|
|
|
393
403
|
? (body["attributes"] as Record<string, string>)
|
|
394
404
|
: undefined;
|
|
395
405
|
|
|
406
|
+
/*
|
|
407
|
+
* Per-facet partial-match filter applied at the Postgres source-of-truth
|
|
408
|
+
* lookup stage. Only consulted for resource facets (serviceId / hostId /
|
|
409
|
+
* dockerHostId / kubernetesClusterId) — other facets continue to filter
|
|
410
|
+
* client-side over the loaded value list.
|
|
411
|
+
*/
|
|
412
|
+
const facetSearchText: Record<string, string> | undefined = body[
|
|
413
|
+
"facetSearchText"
|
|
414
|
+
]
|
|
415
|
+
? (body["facetSearchText"] as Record<string, string>)
|
|
416
|
+
: undefined;
|
|
417
|
+
|
|
396
418
|
/*
|
|
397
419
|
* Capture tenantId locally so TypeScript narrowing survives the
|
|
398
420
|
* async closure below (narrowing is lost across closure boundaries).
|
|
@@ -437,6 +459,40 @@ router.post(
|
|
|
437
459
|
facetResults,
|
|
438
460
|
);
|
|
439
461
|
|
|
462
|
+
/*
|
|
463
|
+
* Replace resource-facet results with the Postgres source-of-truth list
|
|
464
|
+
* (filtered by facetSearchText and enriched with displayName). See the
|
|
465
|
+
* trace facets handler above for the rationale — same pattern, same
|
|
466
|
+
* benefit: low-volume resources stay visible and search can reach
|
|
467
|
+
* resources outside the ClickHouse sample window.
|
|
468
|
+
*/
|
|
469
|
+
const resourceSpecs: Array<ResourceFacetSpec> = facetKeys
|
|
470
|
+
.filter((key: string): boolean => {
|
|
471
|
+
return ResourceFacetResolver.isResourceFacet(key);
|
|
472
|
+
})
|
|
473
|
+
.map((key: string): ResourceFacetSpec => {
|
|
474
|
+
const counts: Map<string, number> = new Map();
|
|
475
|
+
for (const fv of facets[key] || []) {
|
|
476
|
+
counts.set(fv.value, fv.count);
|
|
477
|
+
}
|
|
478
|
+
return {
|
|
479
|
+
facetKey: key,
|
|
480
|
+
counts,
|
|
481
|
+
searchText: facetSearchText?.[key],
|
|
482
|
+
limit,
|
|
483
|
+
};
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
if (resourceSpecs.length > 0) {
|
|
487
|
+
const resolved: Record<
|
|
488
|
+
string,
|
|
489
|
+
Array<ResolvedFacetValue>
|
|
490
|
+
> = await ResourceFacetResolver.resolve(projectId, resourceSpecs);
|
|
491
|
+
for (const key of Object.keys(resolved)) {
|
|
492
|
+
facets[key] = resolved[key] as Array<FacetValue>;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
440
496
|
return Response.sendJsonObjectResponse(req, res, {
|
|
441
497
|
facets: facets as unknown as JSONObject,
|
|
442
498
|
});
|
|
@@ -613,6 +669,18 @@ router.post(
|
|
|
613
669
|
? (body["attributes"] as Record<string, string>)
|
|
614
670
|
: undefined;
|
|
615
671
|
|
|
672
|
+
/*
|
|
673
|
+
* Per-facet partial-match filter applied at the Postgres source-of-truth
|
|
674
|
+
* lookup stage. Only consulted for resource facets (serviceId / hostId /
|
|
675
|
+
* dockerHostId / kubernetesClusterId) — other facets continue to filter
|
|
676
|
+
* client-side over the loaded value list.
|
|
677
|
+
*/
|
|
678
|
+
const facetSearchText: Record<string, string> | undefined = body[
|
|
679
|
+
"facetSearchText"
|
|
680
|
+
]
|
|
681
|
+
? (body["facetSearchText"] as Record<string, string>)
|
|
682
|
+
: undefined;
|
|
683
|
+
|
|
616
684
|
/*
|
|
617
685
|
* Compute all facets from a single sort-key-aligned sample query
|
|
618
686
|
* (ORDER BY startTime DESC LIMIT N) and count top-K in Node. This
|
|
@@ -648,6 +716,44 @@ router.post(
|
|
|
648
716
|
);
|
|
649
717
|
}
|
|
650
718
|
|
|
719
|
+
/*
|
|
720
|
+
* Replace resource-facet results with the Postgres source-of-truth list
|
|
721
|
+
* (filtered by facetSearchText and enriched with displayName). Counts
|
|
722
|
+
* come from the ClickHouse sample above — entities with no recent
|
|
723
|
+
* telemetry surface with count 0 instead of being hidden entirely. This
|
|
724
|
+
* means low-volume services / hosts still appear in the sidebar and the
|
|
725
|
+
* search box can find resources beyond the loaded subset.
|
|
726
|
+
*/
|
|
727
|
+
const resourceSpecs: Array<ResourceFacetSpec> = facetKeys
|
|
728
|
+
.filter((key: string): boolean => {
|
|
729
|
+
return ResourceFacetResolver.isResourceFacet(key);
|
|
730
|
+
})
|
|
731
|
+
.map((key: string): ResourceFacetSpec => {
|
|
732
|
+
const counts: Map<string, number> = new Map();
|
|
733
|
+
for (const fv of facets[key] || []) {
|
|
734
|
+
counts.set(fv.value, fv.count);
|
|
735
|
+
}
|
|
736
|
+
return {
|
|
737
|
+
facetKey: key,
|
|
738
|
+
counts,
|
|
739
|
+
searchText: facetSearchText?.[key],
|
|
740
|
+
limit,
|
|
741
|
+
};
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
if (resourceSpecs.length > 0) {
|
|
745
|
+
const resolved: Record<
|
|
746
|
+
string,
|
|
747
|
+
Array<ResolvedFacetValue>
|
|
748
|
+
> = await ResourceFacetResolver.resolve(
|
|
749
|
+
databaseProps.tenantId,
|
|
750
|
+
resourceSpecs,
|
|
751
|
+
);
|
|
752
|
+
for (const key of Object.keys(resolved)) {
|
|
753
|
+
facets[key] = resolved[key] as Array<TraceFacetValue>;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
651
757
|
return Response.sendJsonObjectResponse(req, res, {
|
|
652
758
|
facets: facets as unknown as JSONObject,
|
|
653
759
|
});
|
|
@@ -748,6 +854,306 @@ router.post(
|
|
|
748
854
|
},
|
|
749
855
|
);
|
|
750
856
|
|
|
857
|
+
// --- Exception Facets Endpoint ---
|
|
858
|
+
|
|
859
|
+
router.post(
|
|
860
|
+
"/telemetry/exceptions/facets",
|
|
861
|
+
UserMiddleware.getUserMiddleware,
|
|
862
|
+
async (
|
|
863
|
+
req: ExpressRequest,
|
|
864
|
+
res: ExpressResponse,
|
|
865
|
+
next: NextFunction,
|
|
866
|
+
): Promise<void> => {
|
|
867
|
+
try {
|
|
868
|
+
const databaseProps: DatabaseCommonInteractionProps =
|
|
869
|
+
await CommonAPI.getDatabaseCommonInteractionProps(req);
|
|
870
|
+
|
|
871
|
+
if (!databaseProps?.tenantId) {
|
|
872
|
+
return Response.sendErrorResponse(
|
|
873
|
+
req,
|
|
874
|
+
res,
|
|
875
|
+
new BadDataException("Invalid Project ID"),
|
|
876
|
+
);
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
const body: JSONObject = req.body as JSONObject;
|
|
880
|
+
|
|
881
|
+
const facetKeys: Array<string> = body["facetKeys"]
|
|
882
|
+
? (body["facetKeys"] as Array<string>)
|
|
883
|
+
: [
|
|
884
|
+
"serviceId",
|
|
885
|
+
"hostId",
|
|
886
|
+
"dockerHostId",
|
|
887
|
+
"kubernetesClusterId",
|
|
888
|
+
"exceptionType",
|
|
889
|
+
"environment",
|
|
890
|
+
];
|
|
891
|
+
|
|
892
|
+
const startTime: Date = body["startTime"]
|
|
893
|
+
? OneUptimeDate.fromString(body["startTime"] as string)
|
|
894
|
+
: OneUptimeDate.addRemoveHours(OneUptimeDate.getCurrentDate(), -24);
|
|
895
|
+
|
|
896
|
+
const endTime: Date = body["endTime"]
|
|
897
|
+
? OneUptimeDate.fromString(body["endTime"] as string)
|
|
898
|
+
: OneUptimeDate.getCurrentDate();
|
|
899
|
+
|
|
900
|
+
const limit: number = (body["limit"] as number) || 500;
|
|
901
|
+
|
|
902
|
+
const serviceIds: Array<ObjectID> | undefined = body["serviceIds"]
|
|
903
|
+
? (body["serviceIds"] as Array<string>).map((id: string) => {
|
|
904
|
+
return new ObjectID(id);
|
|
905
|
+
})
|
|
906
|
+
: undefined;
|
|
907
|
+
|
|
908
|
+
const exceptionTypes: Array<string> | undefined = body["exceptionTypes"]
|
|
909
|
+
? (body["exceptionTypes"] as Array<string>)
|
|
910
|
+
: undefined;
|
|
911
|
+
|
|
912
|
+
const environments: Array<string> | undefined = body["environments"]
|
|
913
|
+
? (body["environments"] as Array<string>)
|
|
914
|
+
: undefined;
|
|
915
|
+
|
|
916
|
+
const fingerprints: Array<string> | undefined = body["fingerprints"]
|
|
917
|
+
? (body["fingerprints"] as Array<string>)
|
|
918
|
+
: undefined;
|
|
919
|
+
|
|
920
|
+
const traceIds: Array<string> | undefined = body["traceIds"]
|
|
921
|
+
? (body["traceIds"] as Array<string>)
|
|
922
|
+
: undefined;
|
|
923
|
+
|
|
924
|
+
const escaped: boolean | undefined =
|
|
925
|
+
body["escaped"] === undefined ? undefined : Boolean(body["escaped"]);
|
|
926
|
+
|
|
927
|
+
const messageSearchText: string | undefined = body["messageSearchText"]
|
|
928
|
+
? (body["messageSearchText"] as string)
|
|
929
|
+
: undefined;
|
|
930
|
+
|
|
931
|
+
/*
|
|
932
|
+
* Per-facet partial-match filter applied at the Postgres source-of-truth
|
|
933
|
+
* lookup stage. Only consulted for resource facets — other facets
|
|
934
|
+
* continue to filter client-side over the loaded value list.
|
|
935
|
+
*/
|
|
936
|
+
const facetSearchText: Record<string, string> | undefined = body[
|
|
937
|
+
"facetSearchText"
|
|
938
|
+
]
|
|
939
|
+
? (body["facetSearchText"] as Record<string, string>)
|
|
940
|
+
: undefined;
|
|
941
|
+
|
|
942
|
+
const projectId: ObjectID = databaseProps.tenantId;
|
|
943
|
+
|
|
944
|
+
/*
|
|
945
|
+
* Per-facet ClickHouse query in parallel. Per-facet errors degrade
|
|
946
|
+
* gracefully to [] so a slow / failing facet can't block the others.
|
|
947
|
+
*/
|
|
948
|
+
const facetResults: Array<readonly [string, Array<ExceptionFacetValue>]> =
|
|
949
|
+
await Promise.all(
|
|
950
|
+
facetKeys.map(
|
|
951
|
+
async (
|
|
952
|
+
facetKey: string,
|
|
953
|
+
): Promise<readonly [string, Array<ExceptionFacetValue>]> => {
|
|
954
|
+
try {
|
|
955
|
+
const request: ExceptionFacetRequest = {
|
|
956
|
+
projectId,
|
|
957
|
+
startTime,
|
|
958
|
+
endTime,
|
|
959
|
+
facetKey,
|
|
960
|
+
limit,
|
|
961
|
+
serviceIds,
|
|
962
|
+
exceptionTypes,
|
|
963
|
+
environments,
|
|
964
|
+
fingerprints,
|
|
965
|
+
traceIds,
|
|
966
|
+
escaped,
|
|
967
|
+
messageSearchText,
|
|
968
|
+
};
|
|
969
|
+
const values: Array<ExceptionFacetValue> =
|
|
970
|
+
await ExceptionAggregationService.getFacetValues(request);
|
|
971
|
+
return [facetKey, values] as const;
|
|
972
|
+
} catch {
|
|
973
|
+
return [facetKey, [] as Array<ExceptionFacetValue>] as const;
|
|
974
|
+
}
|
|
975
|
+
},
|
|
976
|
+
),
|
|
977
|
+
);
|
|
978
|
+
|
|
979
|
+
const facets: Record<
|
|
980
|
+
string,
|
|
981
|
+
Array<ExceptionFacetValue>
|
|
982
|
+
> = Object.fromEntries(facetResults);
|
|
983
|
+
|
|
984
|
+
/*
|
|
985
|
+
* Replace resource-facet results with the Postgres source-of-truth list
|
|
986
|
+
* (filtered by facetSearchText and enriched with displayName). Same
|
|
987
|
+
* pattern as the trace/log facets endpoints.
|
|
988
|
+
*/
|
|
989
|
+
const resourceSpecs: Array<ResourceFacetSpec> = facetKeys
|
|
990
|
+
.filter((key: string): boolean => {
|
|
991
|
+
return ResourceFacetResolver.isResourceFacet(key);
|
|
992
|
+
})
|
|
993
|
+
.map((key: string): ResourceFacetSpec => {
|
|
994
|
+
const counts: Map<string, number> = new Map();
|
|
995
|
+
for (const fv of facets[key] || []) {
|
|
996
|
+
counts.set(fv.value, fv.count);
|
|
997
|
+
}
|
|
998
|
+
return {
|
|
999
|
+
facetKey: key,
|
|
1000
|
+
counts,
|
|
1001
|
+
searchText: facetSearchText?.[key],
|
|
1002
|
+
limit,
|
|
1003
|
+
};
|
|
1004
|
+
});
|
|
1005
|
+
|
|
1006
|
+
if (resourceSpecs.length > 0) {
|
|
1007
|
+
const resolved: Record<
|
|
1008
|
+
string,
|
|
1009
|
+
Array<ResolvedFacetValue>
|
|
1010
|
+
> = await ResourceFacetResolver.resolve(projectId, resourceSpecs);
|
|
1011
|
+
for (const key of Object.keys(resolved)) {
|
|
1012
|
+
facets[key] = resolved[key] as Array<ExceptionFacetValue>;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
return Response.sendJsonObjectResponse(req, res, {
|
|
1017
|
+
facets: facets as unknown as JSONObject,
|
|
1018
|
+
});
|
|
1019
|
+
} catch (err: unknown) {
|
|
1020
|
+
next(err);
|
|
1021
|
+
}
|
|
1022
|
+
},
|
|
1023
|
+
);
|
|
1024
|
+
|
|
1025
|
+
// --- Metric Facets Endpoint ---
|
|
1026
|
+
|
|
1027
|
+
router.post(
|
|
1028
|
+
"/telemetry/metrics/facets",
|
|
1029
|
+
UserMiddleware.getUserMiddleware,
|
|
1030
|
+
async (
|
|
1031
|
+
req: ExpressRequest,
|
|
1032
|
+
res: ExpressResponse,
|
|
1033
|
+
next: NextFunction,
|
|
1034
|
+
): Promise<void> => {
|
|
1035
|
+
try {
|
|
1036
|
+
const databaseProps: DatabaseCommonInteractionProps =
|
|
1037
|
+
await CommonAPI.getDatabaseCommonInteractionProps(req);
|
|
1038
|
+
|
|
1039
|
+
if (!databaseProps?.tenantId) {
|
|
1040
|
+
return Response.sendErrorResponse(
|
|
1041
|
+
req,
|
|
1042
|
+
res,
|
|
1043
|
+
new BadDataException("Invalid Project ID"),
|
|
1044
|
+
);
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
const body: JSONObject = req.body as JSONObject;
|
|
1048
|
+
|
|
1049
|
+
const facetKeys: Array<string> = body["facetKeys"]
|
|
1050
|
+
? (body["facetKeys"] as Array<string>)
|
|
1051
|
+
: ["serviceId", "hostId", "dockerHostId", "kubernetesClusterId"];
|
|
1052
|
+
|
|
1053
|
+
const startTime: Date = body["startTime"]
|
|
1054
|
+
? OneUptimeDate.fromString(body["startTime"] as string)
|
|
1055
|
+
: OneUptimeDate.addRemoveHours(OneUptimeDate.getCurrentDate(), -1);
|
|
1056
|
+
|
|
1057
|
+
const endTime: Date = body["endTime"]
|
|
1058
|
+
? OneUptimeDate.fromString(body["endTime"] as string)
|
|
1059
|
+
: OneUptimeDate.getCurrentDate();
|
|
1060
|
+
|
|
1061
|
+
const limit: number = (body["limit"] as number) || 500;
|
|
1062
|
+
|
|
1063
|
+
const serviceIds: Array<ObjectID> | undefined = body["serviceIds"]
|
|
1064
|
+
? (body["serviceIds"] as Array<string>).map((id: string) => {
|
|
1065
|
+
return new ObjectID(id);
|
|
1066
|
+
})
|
|
1067
|
+
: undefined;
|
|
1068
|
+
|
|
1069
|
+
const metricNames: Array<string> | undefined = body["metricNames"]
|
|
1070
|
+
? (body["metricNames"] as Array<string>)
|
|
1071
|
+
: undefined;
|
|
1072
|
+
|
|
1073
|
+
const facetSearchText: Record<string, string> | undefined = body[
|
|
1074
|
+
"facetSearchText"
|
|
1075
|
+
]
|
|
1076
|
+
? (body["facetSearchText"] as Record<string, string>)
|
|
1077
|
+
: undefined;
|
|
1078
|
+
|
|
1079
|
+
const projectId: ObjectID = databaseProps.tenantId;
|
|
1080
|
+
|
|
1081
|
+
/*
|
|
1082
|
+
* Per-facet ClickHouse GROUP BY in parallel. Per-facet errors degrade
|
|
1083
|
+
* to [] so a slow facet doesn't block the rest.
|
|
1084
|
+
*/
|
|
1085
|
+
const facetResults: Array<readonly [string, Array<MetricFacetValue>]> =
|
|
1086
|
+
await Promise.all(
|
|
1087
|
+
facetKeys.map(
|
|
1088
|
+
async (
|
|
1089
|
+
facetKey: string,
|
|
1090
|
+
): Promise<readonly [string, Array<MetricFacetValue>]> => {
|
|
1091
|
+
try {
|
|
1092
|
+
const request: MetricFacetRequest = {
|
|
1093
|
+
projectId,
|
|
1094
|
+
startTime,
|
|
1095
|
+
endTime,
|
|
1096
|
+
facetKey,
|
|
1097
|
+
limit,
|
|
1098
|
+
serviceIds,
|
|
1099
|
+
metricNames,
|
|
1100
|
+
};
|
|
1101
|
+
const values: Array<MetricFacetValue> =
|
|
1102
|
+
await MetricAggregationService.getFacetValues(request);
|
|
1103
|
+
return [facetKey, values] as const;
|
|
1104
|
+
} catch {
|
|
1105
|
+
return [facetKey, [] as Array<MetricFacetValue>] as const;
|
|
1106
|
+
}
|
|
1107
|
+
},
|
|
1108
|
+
),
|
|
1109
|
+
);
|
|
1110
|
+
|
|
1111
|
+
const facets: Record<
|
|
1112
|
+
string,
|
|
1113
|
+
Array<MetricFacetValue>
|
|
1114
|
+
> = Object.fromEntries(facetResults);
|
|
1115
|
+
|
|
1116
|
+
/*
|
|
1117
|
+
* Replace resource-facet results with the Postgres source-of-truth list
|
|
1118
|
+
* (filtered by facetSearchText and enriched with displayName). Same
|
|
1119
|
+
* pattern as the trace / log / exception facets endpoints.
|
|
1120
|
+
*/
|
|
1121
|
+
const resourceSpecs: Array<ResourceFacetSpec> = facetKeys
|
|
1122
|
+
.filter((key: string): boolean => {
|
|
1123
|
+
return ResourceFacetResolver.isResourceFacet(key);
|
|
1124
|
+
})
|
|
1125
|
+
.map((key: string): ResourceFacetSpec => {
|
|
1126
|
+
const counts: Map<string, number> = new Map();
|
|
1127
|
+
for (const fv of facets[key] || []) {
|
|
1128
|
+
counts.set(fv.value, fv.count);
|
|
1129
|
+
}
|
|
1130
|
+
return {
|
|
1131
|
+
facetKey: key,
|
|
1132
|
+
counts,
|
|
1133
|
+
searchText: facetSearchText?.[key],
|
|
1134
|
+
limit,
|
|
1135
|
+
};
|
|
1136
|
+
});
|
|
1137
|
+
|
|
1138
|
+
if (resourceSpecs.length > 0) {
|
|
1139
|
+
const resolved: Record<
|
|
1140
|
+
string,
|
|
1141
|
+
Array<ResolvedFacetValue>
|
|
1142
|
+
> = await ResourceFacetResolver.resolve(projectId, resourceSpecs);
|
|
1143
|
+
for (const key of Object.keys(resolved)) {
|
|
1144
|
+
facets[key] = resolved[key] as Array<MetricFacetValue>;
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
return Response.sendJsonObjectResponse(req, res, {
|
|
1149
|
+
facets: facets as unknown as JSONObject,
|
|
1150
|
+
});
|
|
1151
|
+
} catch (err: unknown) {
|
|
1152
|
+
next(err);
|
|
1153
|
+
}
|
|
1154
|
+
},
|
|
1155
|
+
);
|
|
1156
|
+
|
|
751
1157
|
// --- Log Analytics Endpoint ---
|
|
752
1158
|
|
|
753
1159
|
router.post(
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
2
|
+
|
|
3
|
+
export class MigrationName1779879993421 implements MigrationInterface {
|
|
4
|
+
public name = "MigrationName1779879993421";
|
|
5
|
+
|
|
6
|
+
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
7
|
+
await queryRunner.query(
|
|
8
|
+
`DROP INDEX IF EXISTS "public"."IDX_telemetry_exception_project_service_fingerprint"`,
|
|
9
|
+
);
|
|
10
|
+
await queryRunner.query(
|
|
11
|
+
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type":"Recurring","value":{"intervalType":"Day","intervalCount":{"_type":"PositiveNumber","value":1}}}'`,
|
|
12
|
+
);
|
|
13
|
+
await queryRunner.query(
|
|
14
|
+
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type":"RestrictionTimes","value":{"restictionType":"None","dayRestrictionTimes":null,"weeklyRestrictionTimes":[]}}'`,
|
|
15
|
+
);
|
|
16
|
+
/*
|
|
17
|
+
* Remove pre-existing duplicates so the unique index below can be created.
|
|
18
|
+
* Done as: one cheap GROUP BY to find duplicate keys, then a small DELETE
|
|
19
|
+
* per key — keeps every individual statement well under the 30s
|
|
20
|
+
* statement/query timeout that applies to the migration connection.
|
|
21
|
+
*/
|
|
22
|
+
const duplicateKeys: Array<{
|
|
23
|
+
projectId: string;
|
|
24
|
+
serviceId: string;
|
|
25
|
+
fingerprint: string;
|
|
26
|
+
}> = await queryRunner.query(
|
|
27
|
+
`SELECT "projectId", "serviceId", "fingerprint"
|
|
28
|
+
FROM "TelemetryException"
|
|
29
|
+
GROUP BY "projectId", "serviceId", "fingerprint"
|
|
30
|
+
HAVING COUNT(*) > 1`,
|
|
31
|
+
);
|
|
32
|
+
for (const key of duplicateKeys) {
|
|
33
|
+
await queryRunner.query(
|
|
34
|
+
`DELETE FROM "TelemetryException"
|
|
35
|
+
WHERE "projectId" = $1
|
|
36
|
+
AND "serviceId" = $2
|
|
37
|
+
AND "fingerprint" = $3
|
|
38
|
+
AND "_id" <> (
|
|
39
|
+
SELECT "_id" FROM "TelemetryException"
|
|
40
|
+
WHERE "projectId" = $1
|
|
41
|
+
AND "serviceId" = $2
|
|
42
|
+
AND "fingerprint" = $3
|
|
43
|
+
ORDER BY "lastSeenAt" DESC NULLS LAST, "_id" DESC
|
|
44
|
+
LIMIT 1
|
|
45
|
+
)`,
|
|
46
|
+
[key.projectId, key.serviceId, key.fingerprint],
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
await queryRunner.query(
|
|
50
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_1f55d43a0b73e883bb226158c7" ON "TelemetryException" ("projectId", "serviceId", "fingerprint") `,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
55
|
+
await queryRunner.query(
|
|
56
|
+
`DROP INDEX IF EXISTS "public"."IDX_1f55d43a0b73e883bb226158c7"`,
|
|
57
|
+
);
|
|
58
|
+
await queryRunner.query(
|
|
59
|
+
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type": "RestrictionTimes", "value": {"restictionType": "None", "dayRestrictionTimes": null, "weeklyRestrictionTimes": []}}'`,
|
|
60
|
+
);
|
|
61
|
+
await queryRunner.query(
|
|
62
|
+
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type": "Recurring", "value": {"intervalType": "Day", "intervalCount": {"_type": "PositiveNumber", "value": 1}}}'`,
|
|
63
|
+
);
|
|
64
|
+
await queryRunner.query(
|
|
65
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_telemetry_exception_project_service_fingerprint" ON "TelemetryException" ("projectId", "serviceId", "fingerprint") `,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
2
|
+
|
|
3
|
+
export class MigrationName1779882573463 implements MigrationInterface {
|
|
4
|
+
public name = "MigrationName1779882573463";
|
|
5
|
+
|
|
6
|
+
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
7
|
+
await queryRunner.query(
|
|
8
|
+
`ALTER TABLE "StatusPageGroup" ADD "viewMode" character varying DEFAULT 'List'`,
|
|
9
|
+
);
|
|
10
|
+
await queryRunner.query(
|
|
11
|
+
`ALTER TABLE "StatusPageGroup" ADD "rowAxisLabel" character varying(100)`,
|
|
12
|
+
);
|
|
13
|
+
await queryRunner.query(
|
|
14
|
+
`ALTER TABLE "StatusPageGroup" ADD "columnAxisLabel" character varying(100)`,
|
|
15
|
+
);
|
|
16
|
+
await queryRunner.query(
|
|
17
|
+
`ALTER TABLE "StatusPageGroup" ADD "rowAxisValues" character varying(500)`,
|
|
18
|
+
);
|
|
19
|
+
await queryRunner.query(
|
|
20
|
+
`ALTER TABLE "StatusPageGroup" ADD "columnAxisValues" character varying(500)`,
|
|
21
|
+
);
|
|
22
|
+
await queryRunner.query(
|
|
23
|
+
`ALTER TABLE "StatusPageResource" ADD "rowAxisValue" character varying(100)`,
|
|
24
|
+
);
|
|
25
|
+
await queryRunner.query(
|
|
26
|
+
`ALTER TABLE "StatusPageResource" ADD "columnAxisValue" character varying(100)`,
|
|
27
|
+
);
|
|
28
|
+
await queryRunner.query(
|
|
29
|
+
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type":"Recurring","value":{"intervalType":"Day","intervalCount":{"_type":"PositiveNumber","value":1}}}'`,
|
|
30
|
+
);
|
|
31
|
+
await queryRunner.query(
|
|
32
|
+
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type":"RestrictionTimes","value":{"restictionType":"None","dayRestrictionTimes":null,"weeklyRestrictionTimes":[]}}'`,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
37
|
+
await queryRunner.query(
|
|
38
|
+
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type": "RestrictionTimes", "value": {"restictionType": "None", "dayRestrictionTimes": null, "weeklyRestrictionTimes": []}}'`,
|
|
39
|
+
);
|
|
40
|
+
await queryRunner.query(
|
|
41
|
+
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type": "Recurring", "value": {"intervalType": "Day", "intervalCount": {"_type": "PositiveNumber", "value": 1}}}'`,
|
|
42
|
+
);
|
|
43
|
+
await queryRunner.query(
|
|
44
|
+
`ALTER TABLE "StatusPageResource" DROP COLUMN "columnAxisValue"`,
|
|
45
|
+
);
|
|
46
|
+
await queryRunner.query(
|
|
47
|
+
`ALTER TABLE "StatusPageResource" DROP COLUMN "rowAxisValue"`,
|
|
48
|
+
);
|
|
49
|
+
await queryRunner.query(
|
|
50
|
+
`ALTER TABLE "StatusPageGroup" DROP COLUMN "columnAxisValues"`,
|
|
51
|
+
);
|
|
52
|
+
await queryRunner.query(
|
|
53
|
+
`ALTER TABLE "StatusPageGroup" DROP COLUMN "rowAxisValues"`,
|
|
54
|
+
);
|
|
55
|
+
await queryRunner.query(
|
|
56
|
+
`ALTER TABLE "StatusPageGroup" DROP COLUMN "columnAxisLabel"`,
|
|
57
|
+
);
|
|
58
|
+
await queryRunner.query(
|
|
59
|
+
`ALTER TABLE "StatusPageGroup" DROP COLUMN "rowAxisLabel"`,
|
|
60
|
+
);
|
|
61
|
+
await queryRunner.query(
|
|
62
|
+
`ALTER TABLE "StatusPageGroup" DROP COLUMN "viewMode"`,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -354,6 +354,9 @@ import { AttachServiceToScheduledMaintenanceTemplatesAndLabelRules1779742211961
|
|
|
354
354
|
import { MigrationName1779790539196 } from "./1779790539196-MigrationName";
|
|
355
355
|
import { ExpandOwnerRuleInheritFlags1779823516881 } from "./1779823516881-ExpandOwnerRuleInheritFlags";
|
|
356
356
|
import { RenameStatusPageZhToZhCN1779827700000 } from "./1779827700000-RenameStatusPageZhToZhCN";
|
|
357
|
+
import { MigrationName1779879993421 } from "./1779879993421-MigrationName";
|
|
358
|
+
import { MigrationName1779882573463 } from "./1779882573463-MigrationName";
|
|
359
|
+
|
|
357
360
|
export default [
|
|
358
361
|
InitialMigration,
|
|
359
362
|
MigrationName1717678334852,
|
|
@@ -711,4 +714,6 @@ export default [
|
|
|
711
714
|
MigrationName1779790539196,
|
|
712
715
|
ExpandOwnerRuleInheritFlags1779823516881,
|
|
713
716
|
RenameStatusPageZhToZhCN1779827700000,
|
|
717
|
+
MigrationName1779879993421,
|
|
718
|
+
MigrationName1779882573463,
|
|
714
719
|
];
|