@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.
Files changed (77) hide show
  1. package/Models/DatabaseModels/StatusPageGroup.ts +212 -0
  2. package/Models/DatabaseModels/StatusPageResource.ts +86 -0
  3. package/Models/DatabaseModels/TelemetryException.ts +10 -0
  4. package/Server/API/StatusPageAPI.ts +15 -0
  5. package/Server/API/TelemetryAPI.ts +406 -0
  6. package/Server/Infrastructure/Postgres/SchemaMigrations/1779879993421-MigrationName.ts +68 -0
  7. package/Server/Infrastructure/Postgres/SchemaMigrations/1779882573463-MigrationName.ts +65 -0
  8. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +5 -0
  9. package/Server/Services/ExceptionAggregationService.ts +51 -3
  10. package/Server/Services/LogAggregationService.ts +1 -0
  11. package/Server/Services/MetricAggregationService.ts +227 -0
  12. package/Server/Services/OpenTelemetryIngestService.ts +101 -1
  13. package/Server/Services/StatusPageService.ts +5 -0
  14. package/Server/Services/TraceAggregationService.ts +1 -0
  15. package/Server/Utils/Monitor/MonitorLogUtil.ts +146 -6
  16. package/Server/Utils/Telemetry/ResourceFacetResolver.ts +299 -0
  17. package/Types/Monitor/MonitorStep.ts +85 -0
  18. package/Types/StatusPage/StatusPageGroupViewMode.ts +6 -0
  19. package/UI/Components/Accordion/Accordion.tsx +32 -26
  20. package/UI/Components/LogsViewer/LogsViewer.tsx +10 -0
  21. package/UI/Components/LogsViewer/components/FacetSection.tsx +40 -3
  22. package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +23 -0
  23. package/UI/Components/LogsViewer/types.ts +2 -0
  24. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +8 -0
  25. package/UI/Components/TelemetryViewer/components/TelemetryFacetSection.tsx +49 -3
  26. package/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.tsx +16 -0
  27. package/UI/Components/TelemetryViewer/types.ts +12 -0
  28. package/build/dist/Models/DatabaseModels/StatusPageGroup.js +217 -0
  29. package/build/dist/Models/DatabaseModels/StatusPageGroup.js.map +1 -1
  30. package/build/dist/Models/DatabaseModels/StatusPageResource.js +88 -0
  31. package/build/dist/Models/DatabaseModels/StatusPageResource.js.map +1 -1
  32. package/build/dist/Models/DatabaseModels/TelemetryException.js +11 -0
  33. package/build/dist/Models/DatabaseModels/TelemetryException.js.map +1 -1
  34. package/build/dist/Server/API/StatusPageAPI.js +15 -0
  35. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  36. package/build/dist/Server/API/TelemetryAPI.js +285 -0
  37. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  38. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779879993421-MigrationName.js +42 -0
  39. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779879993421-MigrationName.js.map +1 -0
  40. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779882573463-MigrationName.js +28 -0
  41. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779882573463-MigrationName.js.map +1 -0
  42. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  43. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  44. package/build/dist/Server/Services/ExceptionAggregationService.js +44 -4
  45. package/build/dist/Server/Services/ExceptionAggregationService.js.map +1 -1
  46. package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
  47. package/build/dist/Server/Services/MetricAggregationService.js +159 -0
  48. package/build/dist/Server/Services/MetricAggregationService.js.map +1 -0
  49. package/build/dist/Server/Services/OpenTelemetryIngestService.js +60 -3
  50. package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
  51. package/build/dist/Server/Services/StatusPageService.js +5 -0
  52. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  53. package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
  54. package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js +127 -4
  55. package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js.map +1 -1
  56. package/build/dist/Server/Utils/Telemetry/ResourceFacetResolver.js +204 -0
  57. package/build/dist/Server/Utils/Telemetry/ResourceFacetResolver.js.map +1 -0
  58. package/build/dist/Types/Monitor/MonitorStep.js +59 -0
  59. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  60. package/build/dist/Types/StatusPage/StatusPageGroupViewMode.js +7 -0
  61. package/build/dist/Types/StatusPage/StatusPageGroupViewMode.js.map +1 -0
  62. package/build/dist/UI/Components/Accordion/Accordion.js +11 -11
  63. package/build/dist/UI/Components/Accordion/Accordion.js.map +1 -1
  64. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +1 -1
  65. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  66. package/build/dist/UI/Components/LogsViewer/components/FacetSection.js +26 -6
  67. package/build/dist/UI/Components/LogsViewer/components/FacetSection.js.map +1 -1
  68. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +12 -1
  69. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
  70. package/build/dist/UI/Components/LogsViewer/types.js.map +1 -1
  71. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +1 -1
  72. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
  73. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSection.js +32 -6
  74. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSection.js.map +1 -1
  75. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js +6 -1
  76. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js.map +1 -1
  77. 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
  ];