@oneuptime/common 10.4.9 → 10.4.11

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 (107) hide show
  1. package/Models/AnalyticsModels/ExceptionInstance.ts +47 -1
  2. package/Models/AnalyticsModels/Log.ts +47 -1
  3. package/Models/AnalyticsModels/Metric.ts +1 -7
  4. package/Models/AnalyticsModels/Profile.ts +47 -1
  5. package/Models/AnalyticsModels/ProfileSample.ts +47 -1
  6. package/Models/AnalyticsModels/Span.ts +47 -1
  7. package/Models/DatabaseModels/DockerHost.ts +83 -0
  8. package/Models/DatabaseModels/Host.ts +83 -0
  9. package/Models/DatabaseModels/Index.ts +0 -2
  10. package/Models/DatabaseModels/KubernetesCluster.ts +83 -0
  11. package/Server/Infrastructure/Postgres/SchemaMigrations/1779199346010-AddTelemetryRetentionConfig.ts +1 -1
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/1779277271302-DropServiceDependencyTable.ts +44 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1779282769946-AddTelemetryRetentionToHostDockerKubernetes.ts +50 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  15. package/Server/Services/AlertService.ts +2 -4
  16. package/Server/Services/IncidentService.ts +2 -4
  17. package/Server/Services/Index.ts +0 -2
  18. package/Server/Services/LogAggregationService.ts +54 -6
  19. package/Server/Services/OpenTelemetryIngestService.ts +132 -0
  20. package/Server/Services/TraceAggregationService.ts +83 -8
  21. package/Server/Utils/Monitor/MonitorMetricUtil.ts +2 -4
  22. package/Server/Utils/Telemetry/Telemetry.ts +38 -0
  23. package/Tests/Server/Middleware/UserAuthorization.test.ts +5 -0
  24. package/Types/Permission.ts +0 -46
  25. package/Types/Telemetry/ServiceType.ts +19 -0
  26. package/Types/Time/RangeStartAndEndDateTime.ts +8 -0
  27. package/Types/Time/TimeRange.ts +1 -0
  28. package/UI/Components/Forms/Validation.ts +2 -2
  29. package/UI/Components/LogsViewer/LogsViewer.tsx +135 -17
  30. package/UI/Components/LogsViewer/components/LogTimeRangePicker.tsx +1 -0
  31. package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +84 -1
  32. package/UI/Components/Telemetry/TelemetryRetentionConfigForm.tsx +0 -1
  33. package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +1 -0
  34. package/build/dist/Models/AnalyticsModels/ExceptionInstance.js +41 -1
  35. package/build/dist/Models/AnalyticsModels/ExceptionInstance.js.map +1 -1
  36. package/build/dist/Models/AnalyticsModels/Log.js +41 -1
  37. package/build/dist/Models/AnalyticsModels/Log.js.map +1 -1
  38. package/build/dist/Models/AnalyticsModels/Metric.js +0 -7
  39. package/build/dist/Models/AnalyticsModels/Metric.js.map +1 -1
  40. package/build/dist/Models/AnalyticsModels/Profile.js +41 -1
  41. package/build/dist/Models/AnalyticsModels/Profile.js.map +1 -1
  42. package/build/dist/Models/AnalyticsModels/ProfileSample.js +41 -1
  43. package/build/dist/Models/AnalyticsModels/ProfileSample.js.map +1 -1
  44. package/build/dist/Models/AnalyticsModels/Span.js +41 -1
  45. package/build/dist/Models/AnalyticsModels/Span.js.map +1 -1
  46. package/build/dist/Models/DatabaseModels/DockerHost.js +84 -0
  47. package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
  48. package/build/dist/Models/DatabaseModels/Host.js +84 -0
  49. package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
  50. package/build/dist/Models/DatabaseModels/Index.js +0 -2
  51. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  52. package/build/dist/Models/DatabaseModels/KubernetesCluster.js +84 -0
  53. package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
  54. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779199346010-AddTelemetryRetentionConfig.js.map +1 -1
  55. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779277271302-DropServiceDependencyTable.js +42 -0
  56. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779277271302-DropServiceDependencyTable.js.map +1 -0
  57. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779282769946-AddTelemetryRetentionToHostDockerKubernetes.js +22 -0
  58. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779282769946-AddTelemetryRetentionToHostDockerKubernetes.js.map +1 -0
  59. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  60. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  61. package/build/dist/Server/Services/AlertService.js +2 -1
  62. package/build/dist/Server/Services/AlertService.js.map +1 -1
  63. package/build/dist/Server/Services/IncidentService.js +2 -1
  64. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  65. package/build/dist/Server/Services/Index.js +0 -2
  66. package/build/dist/Server/Services/Index.js.map +1 -1
  67. package/build/dist/Server/Services/LogAggregationService.js +46 -4
  68. package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
  69. package/build/dist/Server/Services/OpenTelemetryIngestService.js +103 -0
  70. package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
  71. package/build/dist/Server/Services/TraceAggregationService.js +66 -6
  72. package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
  73. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js +2 -1
  74. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js.map +1 -1
  75. package/build/dist/Server/Utils/Telemetry/Telemetry.js +26 -0
  76. package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
  77. package/build/dist/Tests/Server/Middleware/UserAuthorization.test.js +3 -0
  78. package/build/dist/Tests/Server/Middleware/UserAuthorization.test.js.map +1 -1
  79. package/build/dist/Types/Permission.js +0 -40
  80. package/build/dist/Types/Permission.js.map +1 -1
  81. package/build/dist/Types/Telemetry/ServiceType.js +20 -0
  82. package/build/dist/Types/Telemetry/ServiceType.js.map +1 -0
  83. package/build/dist/Types/Time/RangeStartAndEndDateTime.js +4 -0
  84. package/build/dist/Types/Time/RangeStartAndEndDateTime.js.map +1 -1
  85. package/build/dist/Types/Time/TimeRange.js +1 -0
  86. package/build/dist/Types/Time/TimeRange.js.map +1 -1
  87. package/build/dist/UI/Components/Forms/Validation.js +2 -2
  88. package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
  89. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +106 -16
  90. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  91. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js +1 -0
  92. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js.map +1 -1
  93. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +67 -1
  94. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
  95. package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigForm.js.map +1 -1
  96. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +1 -0
  97. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -1
  98. package/package.json +1 -1
  99. package/Models/DatabaseModels/ServiceDependency.ts +0 -529
  100. package/Server/Services/ServiceDependencyService.ts +0 -48
  101. package/UI/Components/Graphs/ServiceDependencyGraph.tsx +0 -286
  102. package/build/dist/Models/DatabaseModels/ServiceDependency.js +0 -545
  103. package/build/dist/Models/DatabaseModels/ServiceDependency.js.map +0 -1
  104. package/build/dist/Server/Services/ServiceDependencyService.js +0 -47
  105. package/build/dist/Server/Services/ServiceDependencyService.js.map +0 -1
  106. package/build/dist/UI/Components/Graphs/ServiceDependencyGraph.js +0 -206
  107. package/build/dist/UI/Components/Graphs/ServiceDependencyGraph.js.map +0 -1
@@ -5,10 +5,8 @@ import MetricService from "../../Services/MetricService";
5
5
  import GlobalConfigService from "../../Services/GlobalConfigService";
6
6
  import GlobalConfig from "../../../Models/DatabaseModels/GlobalConfig";
7
7
  import DataToProcess from "./DataToProcess";
8
- import {
9
- MetricPointType,
10
- ServiceType,
11
- } from "../../../Models/AnalyticsModels/Metric";
8
+ import { MetricPointType } from "../../../Models/AnalyticsModels/Metric";
9
+ import ServiceType from "../../../Types/Telemetry/ServiceType";
12
10
  import MetricType from "../../../Models/DatabaseModels/MetricType";
13
11
  import BasicInfrastructureMetrics, {
14
12
  NetworkInterfaceMetrics,
@@ -135,6 +135,44 @@ export default class TelemetryUtil {
135
135
  };
136
136
  }
137
137
 
138
+ /*
139
+ * Cross-cutting resource stamps. Spread alongside the service stamp on
140
+ * every analytics row so a single ClickHouse query can find "all
141
+ * telemetry from host X" / "from docker host Y" / "from cluster Z"
142
+ * regardless of which Service the row primarily belongs to. Without
143
+ * these, host context would only be queryable as the raw OTel
144
+ * `resource.host.name` string with no stable id link.
145
+ */
146
+ public static getAttributesForHostIdAndHostName(data: {
147
+ hostId: ObjectID;
148
+ hostName: string;
149
+ }): Dictionary<AttributeType> {
150
+ return {
151
+ "oneuptime.host.id": data.hostId.toString(),
152
+ "oneuptime.host.name": data.hostName,
153
+ };
154
+ }
155
+
156
+ public static getAttributesForDockerHostIdAndHostName(data: {
157
+ dockerHostId: ObjectID;
158
+ hostName: string;
159
+ }): Dictionary<AttributeType> {
160
+ return {
161
+ "oneuptime.docker.host.id": data.dockerHostId.toString(),
162
+ "oneuptime.docker.host.name": data.hostName,
163
+ };
164
+ }
165
+
166
+ public static getAttributesForKubernetesClusterIdAndName(data: {
167
+ kubernetesClusterId: ObjectID;
168
+ clusterName: string;
169
+ }): Dictionary<AttributeType> {
170
+ return {
171
+ "oneuptime.kubernetes.cluster.id": data.kubernetesClusterId.toString(),
172
+ "oneuptime.kubernetes.cluster.name": data.clusterName,
173
+ };
174
+ }
175
+
138
176
  @CaptureSpan()
139
177
  public static getAttributes(data: {
140
178
  prefixKeysWithString: string;
@@ -2,6 +2,7 @@ import ProjectMiddleware from "../../../Server/Middleware/ProjectAuthorization";
2
2
  import UserMiddleware from "../../../Server/Middleware/UserAuthorization";
3
3
  import AccessTokenService from "../../../Server/Services/AccessTokenService";
4
4
  import ProjectService from "../../../Server/Services/ProjectService";
5
+ import TeamMemberService from "../../../Server/Services/TeamMemberService";
5
6
  import UserService from "../../../Server/Services/UserService";
6
7
  import {
7
8
  ExpressRequest,
@@ -44,6 +45,7 @@ jest.mock("../../../Server/Services/UserService");
44
45
  jest.mock("../../../Server/Services/AccessTokenService");
45
46
  jest.mock("../../../Server/Utils/Response");
46
47
  jest.mock("../../../Server/Services/ProjectService");
48
+ jest.mock("../../../Server/Services/TeamMemberService");
47
49
  jest.mock("../../../Types/HashedString");
48
50
  jest.mock("../../../Types/JSONFunctions");
49
51
 
@@ -290,6 +292,9 @@ describe("UserMiddleware", () => {
290
292
  ).mockReturnValue(mockedAccessToken);
291
293
  getJestSpyOn(JSONWebToken, "decode").mockReturnValue(jwtTokenData);
292
294
  getJestSpyOn(HashedString, "hashValue").mockResolvedValue(hashValue);
295
+ getJestSpyOn(TeamMemberService, "getTeamIdsForUser").mockResolvedValue(
296
+ [],
297
+ );
293
298
  });
294
299
 
295
300
  beforeEach(() => {
@@ -1124,11 +1124,6 @@ enum Permission {
1124
1124
  EditService = "EditService",
1125
1125
  ReadService = "ReadService",
1126
1126
 
1127
- CreateServiceDependency = "CreateServiceDependency",
1128
- DeleteServiceDependency = "DeleteServiceDependency",
1129
- EditServiceDependency = "EditServiceDependency",
1130
- ReadServiceDependency = "ReadServiceDependency",
1131
-
1132
1127
  CreateServiceMonitor = "CreateServiceMonitor",
1133
1128
  DeleteServiceMonitor = "DeleteServiceMonitor",
1134
1129
  EditServiceMonitor = "EditServiceMonitor",
@@ -7528,47 +7523,6 @@ export class PermissionHelper {
7528
7523
  group: PermissionGroup.ServiceCatalog,
7529
7524
  },
7530
7525
 
7531
- {
7532
- permission: Permission.CreateServiceDependency,
7533
- title: "Create Service Dependency",
7534
- description:
7535
- "This permission can create Service Dependencies in this project.",
7536
- isAssignableToTenant: true,
7537
- isAccessControlPermission: false,
7538
- isRolePermission: false,
7539
- group: PermissionGroup.ServiceCatalog,
7540
- },
7541
- {
7542
- permission: Permission.DeleteServiceDependency,
7543
- title: "Delete Service Dependency",
7544
- description:
7545
- "This permission can delete Service Dependencies of this project.",
7546
- isAssignableToTenant: true,
7547
- isAccessControlPermission: false,
7548
- isRolePermission: false,
7549
- group: PermissionGroup.ServiceCatalog,
7550
- },
7551
- {
7552
- permission: Permission.EditServiceDependency,
7553
- title: "Edit Service Dependency",
7554
- description:
7555
- "This permission can edit Service Dependencies of this project.",
7556
- isAssignableToTenant: true,
7557
- isAccessControlPermission: false,
7558
- isRolePermission: false,
7559
- group: PermissionGroup.ServiceCatalog,
7560
- },
7561
- {
7562
- permission: Permission.ReadServiceDependency,
7563
- title: "Read Service Dependency",
7564
- description:
7565
- "This permission can read Service Dependencies of this project.",
7566
- isAssignableToTenant: true,
7567
- isAccessControlPermission: false,
7568
- isRolePermission: false,
7569
- group: PermissionGroup.ServiceCatalog,
7570
- },
7571
-
7572
7526
  {
7573
7527
  permission: Permission.CreateServiceMonitor,
7574
7528
  title: "Create Service Monitor",
@@ -0,0 +1,19 @@
1
+ /*
2
+ * Discriminator stored in the `serviceType` column of every telemetry
3
+ * row. Tells the read side which Postgres table the row's `serviceId`
4
+ * points at (the column name is historical — semantically it's a
5
+ * resource-type discriminator, since hosts, docker hosts, k8s clusters
6
+ * and monitors all reuse the `serviceId` slot to avoid synthesising
7
+ * placeholder Service rows just to satisfy the ClickHouse primary key).
8
+ */
9
+ enum ServiceType {
10
+ OpenTelemetry = "OpenTelemetry",
11
+ Monitor = "Monitor",
12
+ Alert = "Alert",
13
+ Incident = "Incident",
14
+ Host = "Host",
15
+ DockerHost = "DockerHost",
16
+ KubernetesCluster = "KubernetesCluster",
17
+ }
18
+
19
+ export default ServiceType;
@@ -21,6 +21,14 @@ export class RangeStartAndEndDateTimeUtil {
21
21
  );
22
22
  }
23
23
 
24
+ // 15 mins.
25
+ if (dashboardStartAndEndDate.range === TimeRange.PAST_FIFTEEN_MINS) {
26
+ return new InBetween<Date>(
27
+ OneUptimeDate.addRemoveMinutes(currentDate, -15),
28
+ currentDate,
29
+ );
30
+ }
31
+
24
32
  // 30 mins.
25
33
  if (dashboardStartAndEndDate.range === TimeRange.PAST_THIRTY_MINS) {
26
34
  return new InBetween<Date>(
@@ -1,5 +1,6 @@
1
1
  enum Range {
2
2
  PAST_FIVE_MINS = "Past 5 Mins",
3
+ PAST_FIFTEEN_MINS = "Past 15 Mins",
3
4
  PAST_THIRTY_MINS = "Past 30 Mins",
4
5
  PAST_ONE_HOUR = "Past 1 Hour",
5
6
  PAST_TWO_HOURS = "Past 2 Hours",
@@ -47,8 +47,8 @@ export default class Validation {
47
47
  }
48
48
 
49
49
  if (field.validation.noSpecialCharacters) {
50
- if (!content.match(/^[A-Za-z0-9]*$/)) {
51
- return `${field.title || name} should not have special characters.`;
50
+ if (!content.match(/^[A-Za-z0-9_-]*$/)) {
51
+ return `${field.title || name} can only contain letters, numbers, hyphens (-), and underscores (_).`;
52
52
  }
53
53
  }
54
54
 
@@ -21,6 +21,9 @@ import { APP_API_URL } from "../../Config";
21
21
  import PageLoader from "../Loader/PageLoader";
22
22
  import ErrorMessage from "../ErrorMessage/ErrorMessage";
23
23
  import Service from "../../../Models/DatabaseModels/Service";
24
+ import Host from "../../../Models/DatabaseModels/Host";
25
+ import DockerHost from "../../../Models/DatabaseModels/DockerHost";
26
+ import KubernetesCluster from "../../../Models/DatabaseModels/KubernetesCluster";
24
27
  import { LIMIT_PER_PROJECT } from "../../../Types/Database/LimitMax";
25
28
  import SortOrder from "../../../Types/BaseDatabase/SortOrder";
26
29
  import ListResult from "../../../Types/BaseDatabase/ListResult";
@@ -210,6 +213,13 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
210
213
  const [pageError, setPageError] = useState<string>("");
211
214
 
212
215
  const [serviceMap, setServiceMap] = useState<Dictionary<Service>>({});
216
+ const [hostMap, setHostMap] = useState<Dictionary<Host>>({});
217
+ const [dockerHostMap, setDockerHostMap] = useState<Dictionary<DockerHost>>(
218
+ {},
219
+ );
220
+ const [kubernetesClusterMap, setKubernetesClusterMap] = useState<
221
+ Dictionary<KubernetesCluster>
222
+ >({});
213
223
 
214
224
  const [selectedLogId, setSelectedLogId] = useState<string | null>(null);
215
225
  const [focusedRowIndex, setFocusedRowIndex] = useState<number>(-1);
@@ -373,33 +383,105 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
373
383
  setIsPageLoading(true);
374
384
  setPageError("");
375
385
 
376
- const telemetryServices: ListResult<Service> = await ModelAPI.getList({
377
- modelType: Service,
378
- query: {},
379
- select: {
380
- name: true,
381
- serviceColor: true,
382
- },
383
- limit: LIMIT_PER_PROJECT,
384
- skip: 0,
385
- sort: {
386
- name: SortOrder.Ascending,
387
- },
388
- });
389
- const services: Dictionary<Service> = {};
386
+ const [telemetryServices, hosts, dockerHosts, kubernetesClusters]: [
387
+ ListResult<Service>,
388
+ ListResult<Host>,
389
+ ListResult<DockerHost>,
390
+ ListResult<KubernetesCluster>,
391
+ ] = await Promise.all([
392
+ ModelAPI.getList({
393
+ modelType: Service,
394
+ query: {},
395
+ select: {
396
+ name: true,
397
+ serviceColor: true,
398
+ },
399
+ limit: LIMIT_PER_PROJECT,
400
+ skip: 0,
401
+ sort: {
402
+ name: SortOrder.Ascending,
403
+ },
404
+ }),
405
+ ModelAPI.getList({
406
+ modelType: Host,
407
+ query: {},
408
+ select: {
409
+ name: true,
410
+ hostIdentifier: true,
411
+ },
412
+ limit: LIMIT_PER_PROJECT,
413
+ skip: 0,
414
+ sort: {
415
+ name: SortOrder.Ascending,
416
+ },
417
+ }),
418
+ ModelAPI.getList({
419
+ modelType: DockerHost,
420
+ query: {},
421
+ select: {
422
+ name: true,
423
+ hostIdentifier: true,
424
+ },
425
+ limit: LIMIT_PER_PROJECT,
426
+ skip: 0,
427
+ sort: {
428
+ name: SortOrder.Ascending,
429
+ },
430
+ }),
431
+ ModelAPI.getList({
432
+ modelType: KubernetesCluster,
433
+ query: {},
434
+ select: {
435
+ name: true,
436
+ clusterIdentifier: true,
437
+ },
438
+ limit: LIMIT_PER_PROJECT,
439
+ skip: 0,
440
+ sort: {
441
+ name: SortOrder.Ascending,
442
+ },
443
+ }),
444
+ ]);
390
445
 
446
+ const services: Dictionary<Service> = {};
391
447
  telemetryServices.data.forEach((service: Service) => {
392
448
  if (!service.id) {
393
449
  return;
394
450
  }
395
-
396
451
  services[service.id.toString()] = service;
397
452
  });
398
453
 
454
+ const hostsById: Dictionary<Host> = {};
455
+ hosts.data.forEach((host: Host) => {
456
+ if (!host.id) {
457
+ return;
458
+ }
459
+ hostsById[host.id.toString()] = host;
460
+ });
461
+
462
+ const dockerHostsById: Dictionary<DockerHost> = {};
463
+ dockerHosts.data.forEach((dockerHost: DockerHost) => {
464
+ if (!dockerHost.id) {
465
+ return;
466
+ }
467
+ dockerHostsById[dockerHost.id.toString()] = dockerHost;
468
+ });
469
+
470
+ const clustersById: Dictionary<KubernetesCluster> = {};
471
+ kubernetesClusters.data.forEach((cluster: KubernetesCluster) => {
472
+ if (!cluster.id) {
473
+ return;
474
+ }
475
+ clustersById[cluster.id.toString()] = cluster;
476
+ });
477
+
399
478
  setServiceMap(services);
479
+ setHostMap(hostsById);
480
+ setDockerHostMap(dockerHostsById);
481
+ setKubernetesClusterMap(clustersById);
400
482
  } catch (err) {
401
483
  setPageError(
402
- `We couldn't load telemetry service metadata. ${API.getFriendlyErrorMessage(err as Error)}`,
484
+ `We couldn't load telemetry resource metadata. ${API.getFriendlyErrorMessage(err as Error)}`,
403
485
  );
404
486
  } finally {
405
487
  setIsPageLoading(false);
@@ -724,10 +806,43 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
724
806
  displayValue: service?.name || filter.value,
725
807
  };
726
808
  }
809
+ if (filter.facetKey === "hostId" && hostMap[filter.value]) {
810
+ const host: Host | undefined = hostMap[filter.value];
811
+ return {
812
+ ...filter,
813
+ displayValue: host?.name || host?.hostIdentifier || filter.value,
814
+ };
815
+ }
816
+ if (filter.facetKey === "dockerHostId" && dockerHostMap[filter.value]) {
817
+ const dockerHost: DockerHost | undefined = dockerHostMap[filter.value];
818
+ return {
819
+ ...filter,
820
+ displayValue:
821
+ dockerHost?.name || dockerHost?.hostIdentifier || filter.value,
822
+ };
823
+ }
824
+ if (
825
+ filter.facetKey === "kubernetesClusterId" &&
826
+ kubernetesClusterMap[filter.value]
827
+ ) {
828
+ const cluster: KubernetesCluster | undefined =
829
+ kubernetesClusterMap[filter.value];
830
+ return {
831
+ ...filter,
832
+ displayValue:
833
+ cluster?.name || cluster?.clusterIdentifier || filter.value,
834
+ };
835
+ }
727
836
 
728
837
  return filter;
729
838
  });
730
- }, [props.activeFilters, serviceMap]);
839
+ }, [
840
+ props.activeFilters,
841
+ serviceMap,
842
+ hostMap,
843
+ dockerHostMap,
844
+ kubernetesClusterMap,
845
+ ]);
731
846
 
732
847
  /*
733
848
  * Replace serviceId UUIDs with human-readable names in value suggestions,
@@ -939,6 +1054,9 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
939
1054
  facetData={props.facetData}
940
1055
  isLoading={props.facetLoading || false}
941
1056
  serviceMap={serviceMap}
1057
+ hostMap={hostMap}
1058
+ dockerHostMap={dockerHostMap}
1059
+ kubernetesClusterMap={kubernetesClusterMap}
942
1060
  onIncludeFilter={props.onFacetInclude || (() => {})}
943
1061
  onExcludeFilter={props.onFacetExclude || (() => {})}
944
1062
  activeFilters={props.activeFilters}
@@ -23,6 +23,7 @@ export interface LogTimeRangePickerProps {
23
23
  // Preset options to show in the dropdown (ordered for log investigation use)
24
24
  const PRESET_OPTIONS: Array<{ range: TimeRange; label: string }> = [
25
25
  { range: TimeRange.PAST_FIVE_MINS, label: "Past 5 Minutes" },
26
+ { range: TimeRange.PAST_FIFTEEN_MINS, label: "Past 15 Minutes" },
26
27
  { range: TimeRange.PAST_THIRTY_MINS, label: "Past 30 Minutes" },
27
28
  { range: TimeRange.PAST_ONE_HOUR, label: "Past 1 Hour" },
28
29
  { range: TimeRange.PAST_TWO_HOURS, label: "Past 2 Hours" },
@@ -7,6 +7,9 @@ import {
7
7
  } from "../types";
8
8
  import FacetSection from "./FacetSection";
9
9
  import Service from "../../../../Models/DatabaseModels/Service";
10
+ import Host from "../../../../Models/DatabaseModels/Host";
11
+ import DockerHost from "../../../../Models/DatabaseModels/DockerHost";
12
+ import KubernetesCluster from "../../../../Models/DatabaseModels/KubernetesCluster";
10
13
  import Dictionary from "../../../../Types/Dictionary";
11
14
  import ComponentLoader from "../../ComponentLoader/ComponentLoader";
12
15
  import { getSeverityColor } from "./severityColors";
@@ -16,6 +19,9 @@ export interface LogsFacetSidebarProps {
16
19
  facetData: FacetData;
17
20
  isLoading: boolean;
18
21
  serviceMap: Dictionary<Service>;
22
+ hostMap?: Dictionary<Host>;
23
+ dockerHostMap?: Dictionary<DockerHost>;
24
+ kubernetesClusterMap?: Dictionary<KubernetesCluster>;
19
25
  onIncludeFilter: (facetKey: string, value: string) => void;
20
26
  onExcludeFilter: (facetKey: string, value: string) => void;
21
27
  activeFilters?: Array<ActiveFilter> | undefined;
@@ -72,10 +78,63 @@ function buildServiceColorMap(
72
78
  return map;
73
79
  }
74
80
 
81
+ function buildHostDisplayMap(
82
+ hostMap: Dictionary<Host> | undefined,
83
+ ): Record<string, string> {
84
+ const map: Record<string, string> = {};
85
+ if (!hostMap) {
86
+ return map;
87
+ }
88
+ for (const [id, host] of Object.entries(hostMap)) {
89
+ const label: string | undefined = host?.name || host?.hostIdentifier;
90
+ if (label) {
91
+ map[id] = label;
92
+ }
93
+ }
94
+ return map;
95
+ }
96
+
97
+ function buildDockerHostDisplayMap(
98
+ dockerHostMap: Dictionary<DockerHost> | undefined,
99
+ ): Record<string, string> {
100
+ const map: Record<string, string> = {};
101
+ if (!dockerHostMap) {
102
+ return map;
103
+ }
104
+ for (const [id, dockerHost] of Object.entries(dockerHostMap)) {
105
+ const label: string | undefined =
106
+ dockerHost?.name || dockerHost?.hostIdentifier;
107
+ if (label) {
108
+ map[id] = label;
109
+ }
110
+ }
111
+ return map;
112
+ }
113
+
114
+ function buildClusterDisplayMap(
115
+ clusterMap: Dictionary<KubernetesCluster> | undefined,
116
+ ): Record<string, string> {
117
+ const map: Record<string, string> = {};
118
+ if (!clusterMap) {
119
+ return map;
120
+ }
121
+ for (const [id, cluster] of Object.entries(clusterMap)) {
122
+ const label: string | undefined =
123
+ cluster?.name || cluster?.clusterIdentifier;
124
+ if (label) {
125
+ map[id] = label;
126
+ }
127
+ }
128
+ return map;
129
+ }
130
+
75
131
  function getFacetTitle(key: string): string {
76
132
  const titleMap: Record<string, string> = {
77
133
  severityText: "Severity",
78
134
  serviceId: "Service",
135
+ hostId: "Host",
136
+ dockerHostId: "Docker Host",
137
+ kubernetesClusterId: "Kubernetes Cluster",
79
138
  traceId: "Trace ID",
80
139
  spanId: "Span ID",
81
140
  };
@@ -98,8 +157,26 @@ const LogsFacetSidebar: FunctionComponent<LogsFacetSidebarProps> = (
98
157
  return buildServiceColorMap(props.serviceMap);
99
158
  }, [props.serviceMap]);
100
159
 
160
+ const hostDisplayMap: Record<string, string> = useMemo(() => {
161
+ return buildHostDisplayMap(props.hostMap);
162
+ }, [props.hostMap]);
163
+
164
+ const dockerHostDisplayMap: Record<string, string> = useMemo(() => {
165
+ return buildDockerHostDisplayMap(props.dockerHostMap);
166
+ }, [props.dockerHostMap]);
167
+
168
+ const clusterDisplayMap: Record<string, string> = useMemo(() => {
169
+ return buildClusterDisplayMap(props.kubernetesClusterMap);
170
+ }, [props.kubernetesClusterMap]);
171
+
101
172
  const facetKeys: Array<string> = useMemo(() => {
102
- const priorityKeys: Array<string> = ["severityText", "serviceId"];
173
+ const priorityKeys: Array<string> = [
174
+ "severityText",
175
+ "serviceId",
176
+ "hostId",
177
+ "dockerHostId",
178
+ "kubernetesClusterId",
179
+ ];
103
180
  const otherKeys: Array<string> = Object.keys(props.facetData).filter(
104
181
  (key: string) => {
105
182
  return !priorityKeys.includes(key);
@@ -191,6 +268,12 @@ const LogsFacetSidebar: FunctionComponent<LogsFacetSidebarProps> = (
191
268
  if (key === "serviceId") {
192
269
  valueDisplayMap = serviceDisplayMap;
193
270
  valueColorMap = serviceColorMap;
271
+ } else if (key === "hostId") {
272
+ valueDisplayMap = hostDisplayMap;
273
+ } else if (key === "dockerHostId") {
274
+ valueDisplayMap = dockerHostDisplayMap;
275
+ } else if (key === "kubernetesClusterId") {
276
+ valueDisplayMap = clusterDisplayMap;
194
277
  } else if (key === "severityText") {
195
278
  valueColorMap = severityColorMap;
196
279
  }
@@ -11,7 +11,6 @@ import FieldLabelElement from "../Detail/FieldLabel";
11
11
  import Input, { InputType } from "../Input/Input";
12
12
 
13
13
  export interface ComponentProps {
14
- error?: string | undefined;
15
14
  onChange?: ((value: TelemetryRetentionConfig | null) => void) | undefined;
16
15
  value?: TelemetryRetentionConfig | undefined;
17
16
  initialValue?: TelemetryRetentionConfig | undefined;
@@ -22,6 +22,7 @@ export interface TelemetryTimeRangePickerProps {
22
22
 
23
23
  const PRESET_OPTIONS: Array<{ range: TimeRange; label: string }> = [
24
24
  { range: TimeRange.PAST_FIVE_MINS, label: "Past 5 Minutes" },
25
+ { range: TimeRange.PAST_FIFTEEN_MINS, label: "Past 15 Minutes" },
25
26
  { range: TimeRange.PAST_THIRTY_MINS, label: "Past 30 Minutes" },
26
27
  { range: TimeRange.PAST_ONE_HOUR, label: "Past 1 Hour" },
27
28
  { range: TimeRange.PAST_TWO_HOURS, label: "Past 2 Hours" },
@@ -38,7 +38,7 @@ export default class ExceptionInstance extends AnalyticsBaseModel {
38
38
  const serviceIdColumn = new AnalyticsTableColumn({
39
39
  key: "serviceId",
40
40
  title: "Service ID",
41
- description: "ID of the Service which created the log",
41
+ description: "ID of the resource the exception belongs to (Service / Host / DockerHost / KubernetesCluster / Monitor — disambiguated by serviceType)",
42
42
  required: true,
43
43
  type: TableColumnType.ObjectID,
44
44
  accessControl: {
@@ -62,6 +62,39 @@ export default class ExceptionInstance extends AnalyticsBaseModel {
62
62
  update: [],
63
63
  },
64
64
  });
65
+ const serviceTypeColumn = new AnalyticsTableColumn({
66
+ key: "serviceType",
67
+ title: "Service Type",
68
+ description: "Discriminator for serviceId — tells the read side which resource table to dispatch to",
69
+ required: false,
70
+ type: TableColumnType.Text,
71
+ skipIndex: {
72
+ name: "idx_service_type",
73
+ type: SkipIndexType.Set,
74
+ params: [10],
75
+ granularity: 4,
76
+ },
77
+ accessControl: {
78
+ read: [
79
+ Permission.ProjectOwner,
80
+ Permission.ProjectAdmin,
81
+ Permission.ProjectMember,
82
+ Permission.TelemetryAdmin,
83
+ Permission.TelemetryMember,
84
+ Permission.TelemetryViewer,
85
+ Permission.ReadTelemetryException,
86
+ ],
87
+ create: [
88
+ Permission.ProjectOwner,
89
+ Permission.ProjectAdmin,
90
+ Permission.ProjectMember,
91
+ Permission.TelemetryAdmin,
92
+ Permission.TelemetryMember,
93
+ Permission.CreateTelemetryException,
94
+ ],
95
+ update: [],
96
+ },
97
+ });
65
98
  const timeColumn = new AnalyticsTableColumn({
66
99
  key: "time",
67
100
  title: "Time",
@@ -573,6 +606,7 @@ export default class ExceptionInstance extends AnalyticsBaseModel {
573
606
  tableColumns: [
574
607
  projectIdColumn,
575
608
  serviceIdColumn,
609
+ serviceTypeColumn,
576
610
  timeColumn,
577
611
  timeUnixNanoColumn,
578
612
  exceptionTypeColumn,
@@ -614,6 +648,12 @@ export default class ExceptionInstance extends AnalyticsBaseModel {
614
648
  set serviceId(v) {
615
649
  this.setColumnValue("serviceId", v);
616
650
  }
651
+ get serviceType() {
652
+ return this.getColumnValue("serviceType");
653
+ }
654
+ set serviceType(v) {
655
+ this.setColumnValue("serviceType", v);
656
+ }
617
657
  get time() {
618
658
  return this.getColumnValue("time");
619
659
  }