@oneuptime/common 10.0.96 → 10.0.97

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 (128) hide show
  1. package/Models/AnalyticsModels/Log.ts +6 -0
  2. package/Models/AnalyticsModels/Metric.ts +6 -0
  3. package/Models/AnalyticsModels/Profile.ts +6 -0
  4. package/Models/AnalyticsModels/Span.ts +6 -0
  5. package/Models/DatabaseModels/Alert.ts +52 -0
  6. package/Models/DatabaseModels/DockerHost.ts +3 -10
  7. package/Models/DatabaseModels/Host.ts +1015 -0
  8. package/Models/DatabaseModels/HostOwnerTeam.ts +462 -0
  9. package/Models/DatabaseModels/HostOwnerUser.ts +461 -0
  10. package/Models/DatabaseModels/Incident.ts +52 -0
  11. package/Models/DatabaseModels/Index.ts +6 -0
  12. package/Models/DatabaseModels/KubernetesCluster.ts +0 -7
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1778006035712-AddHostTables.ts +201 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/1778013317872-AddHostIpAddresses.ts +15 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/1778066346303-WidenHostOsVersionToLongText.ts +42 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1778070278986-MigrationName.ts +79 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
  18. package/Server/Services/HostOwnerTeamService.ts +10 -0
  19. package/Server/Services/HostOwnerUserService.ts +10 -0
  20. package/Server/Services/HostService.ts +227 -0
  21. package/Server/Services/LogAggregationService.ts +10 -3
  22. package/Server/Services/MetricService.ts +200 -0
  23. package/Server/Services/TraceAggregationService.ts +8 -3
  24. package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +46 -18
  25. package/Server/Utils/Monitor/MonitorAlert.ts +31 -0
  26. package/Server/Utils/Monitor/MonitorIncident.ts +31 -0
  27. package/Tests/Server/Services/LogAggregationService.test.ts +25 -0
  28. package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +145 -0
  29. package/Types/Metrics/MetricQueryConfigData.ts +9 -0
  30. package/Types/Permission.ts +134 -0
  31. package/UI/Components/Charts/Area/AreaChart.tsx +1 -1
  32. package/UI/Components/Charts/Bar/BarChart.tsx +1 -1
  33. package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +15 -8
  34. package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +12 -9
  35. package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +17 -10
  36. package/UI/Components/Charts/Line/LineChart.tsx +1 -1
  37. package/UI/Components/ExpandableText/ExpandableText.tsx +29 -7
  38. package/UI/Components/JSONTable/JSONTable.tsx +27 -1
  39. package/UI/Components/LogsViewer/LogsViewer.tsx +3 -0
  40. package/UI/Components/LogsViewer/components/LogDetailsPanel.tsx +109 -23
  41. package/UI/Components/LogsViewer/components/LogSearchBar.tsx +11 -4
  42. package/UI/Components/Navbar/NavBarMenu.tsx +17 -2
  43. package/UI/Components/TelemetryViewer/components/TelemetrySearchBar.tsx +10 -3
  44. package/Utils/ValueFormatter.ts +57 -3
  45. package/build/dist/Models/AnalyticsModels/Log.js +6 -0
  46. package/build/dist/Models/AnalyticsModels/Log.js.map +1 -1
  47. package/build/dist/Models/AnalyticsModels/Metric.js +6 -0
  48. package/build/dist/Models/AnalyticsModels/Metric.js.map +1 -1
  49. package/build/dist/Models/AnalyticsModels/Profile.js +6 -0
  50. package/build/dist/Models/AnalyticsModels/Profile.js.map +1 -1
  51. package/build/dist/Models/AnalyticsModels/Span.js +6 -0
  52. package/build/dist/Models/AnalyticsModels/Span.js.map +1 -1
  53. package/build/dist/Models/DatabaseModels/Alert.js +51 -0
  54. package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
  55. package/build/dist/Models/DatabaseModels/DockerHost.js +3 -10
  56. package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
  57. package/build/dist/Models/DatabaseModels/Host.js +1041 -0
  58. package/build/dist/Models/DatabaseModels/Host.js.map +1 -0
  59. package/build/dist/Models/DatabaseModels/HostOwnerTeam.js +480 -0
  60. package/build/dist/Models/DatabaseModels/HostOwnerTeam.js.map +1 -0
  61. package/build/dist/Models/DatabaseModels/HostOwnerUser.js +479 -0
  62. package/build/dist/Models/DatabaseModels/HostOwnerUser.js.map +1 -0
  63. package/build/dist/Models/DatabaseModels/Incident.js +51 -0
  64. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  65. package/build/dist/Models/DatabaseModels/Index.js +6 -0
  66. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  67. package/build/dist/Models/DatabaseModels/KubernetesCluster.js +0 -7
  68. package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
  69. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1778006035712-AddHostTables.js +76 -0
  70. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1778006035712-AddHostTables.js.map +1 -0
  71. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1778013317872-AddHostIpAddresses.js +12 -0
  72. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1778013317872-AddHostIpAddresses.js.map +1 -0
  73. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1778066346303-WidenHostOsVersionToLongText.js +31 -0
  74. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1778066346303-WidenHostOsVersionToLongText.js.map +1 -0
  75. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1778070278986-MigrationName.js +34 -0
  76. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1778070278986-MigrationName.js.map +1 -0
  77. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
  78. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  79. package/build/dist/Server/Services/HostOwnerTeamService.js +9 -0
  80. package/build/dist/Server/Services/HostOwnerTeamService.js.map +1 -0
  81. package/build/dist/Server/Services/HostOwnerUserService.js +9 -0
  82. package/build/dist/Server/Services/HostOwnerUserService.js.map +1 -0
  83. package/build/dist/Server/Services/HostService.js +206 -0
  84. package/build/dist/Server/Services/HostService.js.map +1 -0
  85. package/build/dist/Server/Services/LogAggregationService.js +10 -3
  86. package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
  87. package/build/dist/Server/Services/MetricService.js +160 -0
  88. package/build/dist/Server/Services/MetricService.js.map +1 -1
  89. package/build/dist/Server/Services/TraceAggregationService.js +8 -3
  90. package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
  91. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +46 -18
  92. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
  93. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +26 -0
  94. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  95. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +26 -0
  96. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  97. package/build/dist/Tests/Server/Services/LogAggregationService.test.js +13 -0
  98. package/build/dist/Tests/Server/Services/LogAggregationService.test.js.map +1 -1
  99. package/build/dist/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.js +123 -0
  100. package/build/dist/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.js.map +1 -1
  101. package/build/dist/Types/Permission.js +120 -0
  102. package/build/dist/Types/Permission.js.map +1 -1
  103. package/build/dist/UI/Components/Charts/Area/AreaChart.js +1 -1
  104. package/build/dist/UI/Components/Charts/Bar/BarChart.js +1 -1
  105. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +13 -7
  106. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
  107. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +11 -9
  108. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
  109. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +16 -10
  110. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
  111. package/build/dist/UI/Components/Charts/Line/LineChart.js +1 -1
  112. package/build/dist/UI/Components/ExpandableText/ExpandableText.js +10 -5
  113. package/build/dist/UI/Components/ExpandableText/ExpandableText.js.map +1 -1
  114. package/build/dist/UI/Components/JSONTable/JSONTable.js +8 -1
  115. package/build/dist/UI/Components/JSONTable/JSONTable.js.map +1 -1
  116. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +1 -1
  117. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  118. package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js +40 -14
  119. package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js.map +1 -1
  120. package/build/dist/UI/Components/LogsViewer/components/LogSearchBar.js +10 -4
  121. package/build/dist/UI/Components/LogsViewer/components/LogSearchBar.js.map +1 -1
  122. package/build/dist/UI/Components/Navbar/NavBarMenu.js +15 -2
  123. package/build/dist/UI/Components/Navbar/NavBarMenu.js.map +1 -1
  124. package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js +10 -3
  125. package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js.map +1 -1
  126. package/build/dist/Utils/ValueFormatter.js +47 -3
  127. package/build/dist/Utils/ValueFormatter.js.map +1 -1
  128. package/package.json +1 -1
@@ -812,6 +812,21 @@ enum Permission {
812
812
  EditDockerHostOwnerUser = "EditDockerHostOwnerUser",
813
813
  ReadDockerHostOwnerUser = "ReadDockerHostOwnerUser",
814
814
 
815
+ CreateHost = "CreateHost",
816
+ DeleteHost = "DeleteHost",
817
+ EditHost = "EditHost",
818
+ ReadHost = "ReadHost",
819
+
820
+ CreateHostOwnerTeam = "CreateHostOwnerTeam",
821
+ DeleteHostOwnerTeam = "DeleteHostOwnerTeam",
822
+ EditHostOwnerTeam = "EditHostOwnerTeam",
823
+ ReadHostOwnerTeam = "ReadHostOwnerTeam",
824
+
825
+ CreateHostOwnerUser = "CreateHostOwnerUser",
826
+ DeleteHostOwnerUser = "DeleteHostOwnerUser",
827
+ EditHostOwnerUser = "EditHostOwnerUser",
828
+ ReadHostOwnerUser = "ReadHostOwnerUser",
829
+
815
830
  CreateService = "CreateService",
816
831
  DeleteService = "DeleteService",
817
832
  EditService = "EditService",
@@ -5602,6 +5617,125 @@ export class PermissionHelper {
5602
5617
  group: PermissionGroup.Telemetry,
5603
5618
  },
5604
5619
 
5620
+ {
5621
+ permission: Permission.CreateHost,
5622
+ title: "Create Host",
5623
+ description: "This permission can create Host in this project.",
5624
+ isAssignableToTenant: true,
5625
+ isAccessControlPermission: false,
5626
+ isRolePermission: false,
5627
+ group: PermissionGroup.Telemetry,
5628
+ },
5629
+ {
5630
+ permission: Permission.DeleteHost,
5631
+ title: "Delete Host",
5632
+ description: "This permission can delete Host of this project.",
5633
+ isAssignableToTenant: true,
5634
+ isAccessControlPermission: true,
5635
+ isRolePermission: false,
5636
+ group: PermissionGroup.Telemetry,
5637
+ },
5638
+ {
5639
+ permission: Permission.EditHost,
5640
+ title: "Edit Host",
5641
+ description: "This permission can edit Host of this project.",
5642
+ isAssignableToTenant: true,
5643
+ isAccessControlPermission: true,
5644
+ isRolePermission: false,
5645
+ group: PermissionGroup.Telemetry,
5646
+ },
5647
+ {
5648
+ permission: Permission.ReadHost,
5649
+ title: "Read Host",
5650
+ description: "This permission can read Host of this project.",
5651
+ isAssignableToTenant: true,
5652
+ isAccessControlPermission: true,
5653
+ isRolePermission: false,
5654
+ group: PermissionGroup.Telemetry,
5655
+ },
5656
+
5657
+ {
5658
+ permission: Permission.CreateHostOwnerTeam,
5659
+ title: "Create Host Team Owner",
5660
+ description:
5661
+ "This permission can create Host Team Owner of this project.",
5662
+ isAssignableToTenant: true,
5663
+ isAccessControlPermission: false,
5664
+ isRolePermission: false,
5665
+ group: PermissionGroup.Telemetry,
5666
+ },
5667
+ {
5668
+ permission: Permission.DeleteHostOwnerTeam,
5669
+ title: "Delete Host Team Owner",
5670
+ description:
5671
+ "This permission can delete Host Team Owner of this project.",
5672
+ isAssignableToTenant: true,
5673
+ isAccessControlPermission: false,
5674
+ isRolePermission: false,
5675
+ group: PermissionGroup.Telemetry,
5676
+ },
5677
+ {
5678
+ permission: Permission.EditHostOwnerTeam,
5679
+ title: "Edit Host Team Owner",
5680
+ description:
5681
+ "This permission can edit Host Team Owner of this project.",
5682
+ isAssignableToTenant: true,
5683
+ isAccessControlPermission: false,
5684
+ isRolePermission: false,
5685
+ group: PermissionGroup.Telemetry,
5686
+ },
5687
+ {
5688
+ permission: Permission.ReadHostOwnerTeam,
5689
+ title: "Read Host Team Owner",
5690
+ description:
5691
+ "This permission can read Host Team Owner of this project.",
5692
+ isAssignableToTenant: true,
5693
+ isAccessControlPermission: false,
5694
+ isRolePermission: false,
5695
+ group: PermissionGroup.Telemetry,
5696
+ },
5697
+
5698
+ {
5699
+ permission: Permission.CreateHostOwnerUser,
5700
+ title: "Create Host User Owner",
5701
+ description:
5702
+ "This permission can create Host User Owner of this project.",
5703
+ isAssignableToTenant: true,
5704
+ isAccessControlPermission: false,
5705
+ isRolePermission: false,
5706
+ group: PermissionGroup.Telemetry,
5707
+ },
5708
+ {
5709
+ permission: Permission.DeleteHostOwnerUser,
5710
+ title: "Delete Host User Owner",
5711
+ description:
5712
+ "This permission can delete Host User Owner of this project.",
5713
+ isAssignableToTenant: true,
5714
+ isAccessControlPermission: false,
5715
+ isRolePermission: false,
5716
+ group: PermissionGroup.Telemetry,
5717
+ },
5718
+ {
5719
+ permission: Permission.EditHostOwnerUser,
5720
+ title: "Edit Host User Owner",
5721
+ description:
5722
+ "This permission can edit Host User Owner of this project.",
5723
+ isAssignableToTenant: true,
5724
+ isAccessControlPermission: false,
5725
+ isRolePermission: false,
5726
+ group: PermissionGroup.Telemetry,
5727
+ },
5728
+ {
5729
+ permission: Permission.ReadHostOwnerUser,
5730
+ title: "Read Host User Owner",
5731
+ description:
5732
+ "This permission can read Host User Owner of this project.",
5733
+ isAssignableToTenant: true,
5734
+ isAccessControlPermission: false,
5735
+ isRolePermission: false,
5736
+ group: PermissionGroup.Telemetry,
5737
+ },
5738
+
5605
5739
  {
5606
5740
  permission: Permission.CreateService,
5607
5741
  title: "Create Service",
@@ -115,7 +115,7 @@ const AreaChartElement: FunctionComponent<AreaInternalProps> = (
115
115
  connectNulls={true}
116
116
  curve={props.curve || ChartCurve.MONOTONE}
117
117
  syncid={props.sync ? props.syncid : undefined}
118
- yAxisWidth={60}
118
+ yAxisWidth={64}
119
119
  onValueChange={() => {}}
120
120
  referenceLines={props.referenceLines}
121
121
  formattedExemplarPoints={
@@ -79,7 +79,7 @@ const BarChartElement: FunctionComponent<BarInternalProps> = (
79
79
  valueFormatter={props.yAxis.options.formatter || undefined}
80
80
  showTooltip={true}
81
81
  showLegend={props.showLegend !== false}
82
- yAxisWidth={60}
82
+ yAxisWidth={64}
83
83
  syncid={props.sync ? props.syncid : undefined}
84
84
  onValueChange={() => {}}
85
85
  referenceLines={props.referenceLines}
@@ -695,9 +695,9 @@ const AreaChart: React.ForwardRefExoticComponent<
695
695
  : () => {}
696
696
  }
697
697
  margin={{
698
- bottom: (xAxisLabel ? 30 : undefined) as unknown as number,
699
- left: (yAxisLabel ? 20 : undefined) as unknown as number,
700
- right: (yAxisLabel ? 5 : undefined) as unknown as number,
698
+ bottom: (xAxisLabel ? 30 : 8) as unknown as number,
699
+ left: (yAxisLabel ? 20 : 0) as unknown as number,
700
+ right: (yAxisLabel ? 5 : 8) as unknown as number,
701
701
  top: 5,
702
702
  }}
703
703
  >
@@ -724,7 +724,8 @@ const AreaChart: React.ForwardRefExoticComponent<
724
724
  </defs>
725
725
  {showGridLines ? (
726
726
  <CartesianGrid
727
- className={cx("stroke-gray-200 stroke-1")}
727
+ className={cx("stroke-gray-100")}
728
+ strokeDasharray="3 3"
728
729
  horizontal={true}
729
730
  vertical={false}
730
731
  />
@@ -734,7 +735,10 @@ const AreaChart: React.ForwardRefExoticComponent<
734
735
  hide={!showXAxis}
735
736
  dataKey={index}
736
737
  interval={startEndOnly ? "preserveStartEnd" : intervalType}
737
- tick={{ transform: "translate(0, 6)" }}
738
+ tick={{
739
+ transform: "translate(0, 8)",
740
+ fontWeight: 500,
741
+ }}
738
742
  ticks={
739
743
  startEndOnly
740
744
  ? ([
@@ -748,7 +752,7 @@ const AreaChart: React.ForwardRefExoticComponent<
748
752
  }
749
753
  fill=""
750
754
  stroke=""
751
- className={cx("text-xs", "fill-gray-500")}
755
+ className={cx("text-xs tabular-nums", "fill-gray-600")}
752
756
  tickLine={false}
753
757
  axisLine={false}
754
758
  minTickGap={tickGap}
@@ -770,10 +774,13 @@ const AreaChart: React.ForwardRefExoticComponent<
770
774
  tickLine={false}
771
775
  type="number"
772
776
  domain={yAxisDomain as AxisDomain}
773
- tick={{ transform: "translate(-3, 0)" }}
777
+ tick={{
778
+ transform: "translate(-4, 0)",
779
+ fontWeight: 500,
780
+ }}
774
781
  fill=""
775
782
  stroke=""
776
- className={cx("text-xs", "fill-gray-500")}
783
+ className={cx("text-xs tabular-nums", "fill-gray-600")}
777
784
  tickFormatter={valueFormatter}
778
785
  allowDecimals={allowDecimals}
779
786
  >
@@ -794,9 +794,9 @@ const BarChart: React.ForwardRefExoticComponent<
794
794
  }
795
795
  : {})}
796
796
  margin={{
797
- bottom: xAxisLabel ? 30 : 0,
797
+ bottom: xAxisLabel ? 30 : 8,
798
798
  left: yAxisLabel ? 20 : 0,
799
- right: yAxisLabel ? 5 : 0,
799
+ right: yAxisLabel ? 5 : 8,
800
800
  top: 5,
801
801
  }}
802
802
  stackOffset={type === "percent" ? "expand" : "none"}
@@ -805,7 +805,8 @@ const BarChart: React.ForwardRefExoticComponent<
805
805
  >
806
806
  {showGridLines ? (
807
807
  <CartesianGrid
808
- className={cx("stroke-gray-200 stroke-1")}
808
+ className={cx("stroke-gray-100")}
809
+ strokeDasharray="3 3"
809
810
  horizontal={layout !== "vertical"}
810
811
  vertical={layout === "vertical"}
811
812
  />
@@ -814,15 +815,16 @@ const BarChart: React.ForwardRefExoticComponent<
814
815
  hide={!showXAxis}
815
816
  tick={{
816
817
  transform:
817
- layout !== "vertical" ? "translate(0, 6)" : undefined,
818
+ layout !== "vertical" ? "translate(0, 8)" : undefined,
819
+ fontWeight: 500,
818
820
  }}
819
821
  fill=""
820
822
  stroke=""
821
823
  className={cx(
822
824
  // base
823
- "text-xs",
825
+ "text-xs tabular-nums",
824
826
  // text fill
825
- "fill-gray-500",
827
+ "fill-gray-600",
826
828
  { "mt-4": layout !== "vertical" },
827
829
  )}
828
830
  tickLine={false}
@@ -872,15 +874,16 @@ const BarChart: React.ForwardRefExoticComponent<
872
874
  stroke=""
873
875
  className={cx(
874
876
  // base
875
- "text-xs",
877
+ "text-xs tabular-nums",
876
878
  // text fill
877
- "fill-gray-500",
879
+ "fill-gray-600",
878
880
  )}
879
881
  tick={{
880
882
  transform:
881
883
  layout !== "vertical"
882
- ? "translate(-3, 0)"
884
+ ? "translate(-4, 0)"
883
885
  : "translate(0, 0)",
886
+ fontWeight: 500,
884
887
  }}
885
888
  {...(layout !== "vertical"
886
889
  ? {
@@ -710,15 +710,16 @@ const LineChart: React.ForwardRefExoticComponent<
710
710
  : () => {} // do nothing
711
711
  }
712
712
  margin={{
713
- bottom: (xAxisLabel ? 30 : undefined) as unknown as number,
714
- left: (yAxisLabel ? 20 : undefined) as unknown as number,
715
- right: (yAxisLabel ? 5 : undefined) as unknown as number,
713
+ bottom: (xAxisLabel ? 30 : 8) as unknown as number,
714
+ left: (yAxisLabel ? 20 : 0) as unknown as number,
715
+ right: (yAxisLabel ? 5 : 8) as unknown as number,
716
716
  top: 5,
717
717
  }}
718
718
  >
719
719
  {showGridLines ? (
720
720
  <CartesianGrid
721
- className={cx("stroke-gray-200 stroke-1")}
721
+ className={cx("stroke-gray-100")}
722
+ strokeDasharray="3 3"
722
723
  horizontal={true}
723
724
  vertical={false}
724
725
  />
@@ -728,7 +729,10 @@ const LineChart: React.ForwardRefExoticComponent<
728
729
  hide={!showXAxis}
729
730
  dataKey={index}
730
731
  interval={startEndOnly ? "preserveStartEnd" : intervalType}
731
- tick={{ transform: "translate(0, 6)" }}
732
+ tick={{
733
+ transform: "translate(0, 8)",
734
+ fontWeight: 500,
735
+ }}
732
736
  ticks={
733
737
  startEndOnly
734
738
  ? ([
@@ -741,9 +745,9 @@ const LineChart: React.ForwardRefExoticComponent<
741
745
  stroke=""
742
746
  className={cx(
743
747
  // base
744
- "text-xs",
748
+ "text-xs tabular-nums",
745
749
  // text fill
746
- "fill-gray-500",
750
+ "fill-gray-600",
747
751
  )}
748
752
  tickLine={false}
749
753
  axisLine={false}
@@ -766,14 +770,17 @@ const LineChart: React.ForwardRefExoticComponent<
766
770
  tickLine={false}
767
771
  type="number"
768
772
  domain={yAxisDomain as AxisDomain}
769
- tick={{ transform: "translate(-3, 0)" }}
773
+ tick={{
774
+ transform: "translate(-4, 0)",
775
+ fontWeight: 500,
776
+ }}
770
777
  fill=""
771
778
  stroke=""
772
779
  className={cx(
773
780
  // base
774
- "text-xs",
781
+ "text-xs tabular-nums",
775
782
  // text fill
776
- "fill-gray-500",
783
+ "fill-gray-600",
777
784
  )}
778
785
  tickFormatter={valueFormatter}
779
786
  allowDecimals={allowDecimals}
@@ -114,7 +114,7 @@ const LineChartElement: FunctionComponent<LineInternalProps> = (
114
114
  connectNulls={true}
115
115
  curve={props.curve}
116
116
  syncid={props.sync ? props.syncid : undefined}
117
- yAxisWidth={60}
117
+ yAxisWidth={64}
118
118
  onValueChange={() => {}}
119
119
  referenceLines={props.referenceLines}
120
120
  formattedExemplarPoints={
@@ -17,23 +17,45 @@ const ExpandableText: FunctionComponent<ComponentProps> = (
17
17
  }
18
18
 
19
19
  const isLong: boolean = props.text.length > maxLength;
20
+ const baseTextClass: string = props.className || "text-sm text-gray-900";
20
21
 
21
22
  if (!isLong) {
22
- return (
23
- <span className={props.className || "text-gray-600"}>{props.text}</span>
24
- );
23
+ return <span className={baseTextClass}>{props.text}</span>;
25
24
  }
26
25
 
26
+ const truncated: string =
27
+ props.text.substring(0, maxLength).replace(/\s+$/u, "") + "…";
28
+
27
29
  return (
28
- <span className={props.className || "text-gray-600"}>
29
- {isExpanded ? props.text : props.text.substring(0, maxLength) + "..."}
30
+ <span className="inline align-baseline">
31
+ <span className={`${baseTextClass} break-words align-baseline`}>
32
+ {isExpanded ? props.text : truncated}
33
+ </span>
30
34
  <button
35
+ type="button"
31
36
  onClick={() => {
32
37
  setIsExpanded(!isExpanded);
33
38
  }}
34
- className="ml-1.5 text-xs text-indigo-600 hover:text-indigo-800 font-medium"
39
+ aria-expanded={isExpanded}
40
+ className="ml-2 inline-flex items-center gap-1 align-baseline rounded text-xs font-medium text-indigo-600 hover:text-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-1 transition-colors"
35
41
  >
36
- {isExpanded ? "Less" : "More"}
42
+ <span>{isExpanded ? "Show less" : "Show more"}</span>
43
+ <svg
44
+ className={`w-3 h-3 transition-transform duration-200 ${
45
+ isExpanded ? "rotate-180" : ""
46
+ }`}
47
+ fill="none"
48
+ viewBox="0 0 24 24"
49
+ stroke="currentColor"
50
+ strokeWidth={2.25}
51
+ aria-hidden="true"
52
+ >
53
+ <path
54
+ strokeLinecap="round"
55
+ strokeLinejoin="round"
56
+ d="M19 9l-7 7-7-7"
57
+ />
58
+ </svg>
37
59
  </button>
38
60
  </span>
39
61
  );
@@ -1,11 +1,19 @@
1
1
  import React, { FunctionComponent, ReactElement, useMemo } from "react";
2
2
  import CopyableButton from "../CopyableButton/CopyableButton";
3
+ import Icon from "../Icon/Icon";
4
+ import IconProp from "../../../Types/Icon/IconProp";
3
5
  import JSONFunctions from "../../../Types/JSONFunctions";
4
6
 
5
7
  export interface JSONTableProps {
6
8
  json: { [key: string]: any } | null | undefined;
7
9
  title?: string | undefined;
8
10
  className?: string | undefined;
11
+ /*
12
+ * Optional. When provided, each row gets a "filter by" button that calls
13
+ * this with the flat dot-notation key and the (possibly normalised) value.
14
+ * Wired by callers to add the pair as a search filter on the parent listing.
15
+ */
16
+ onFilterByAttribute?: ((key: string, value: string) => void) | undefined;
9
17
  // Always flattened (dot notation) for consistency.
10
18
  }
11
19
 
@@ -144,6 +152,9 @@ const JSONTable: FunctionComponent<JSONTableProps> = (
144
152
  </thead>
145
153
  <tbody className="divide-y divide-gray-100">
146
154
  {flatItems.map((item: FlatItem) => {
155
+ const canFilter: boolean = Boolean(
156
+ props.onFilterByAttribute && item.value && item.value !== "-",
157
+ );
147
158
  return (
148
159
  <tr key={item.key} className="group hover:bg-gray-50 text-sm">
149
160
  <td className="font-mono px-3 py-2 align-top text-gray-700 break-all whitespace-pre-wrap">
@@ -165,7 +176,22 @@ const JSONTable: FunctionComponent<JSONTableProps> = (
165
176
  <span>{item.value}</span>
166
177
  )}
167
178
  </div>
168
- <div className="opacity-0 group-hover:opacity-100 transition-opacity duration-150">
179
+ <div className="flex items-center gap-1 opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 transition-opacity duration-150">
180
+ {canFilter && (
181
+ <button
182
+ type="button"
183
+ className="rounded p-1 text-gray-400 hover:bg-gray-100 hover:text-indigo-600"
184
+ title={`Filter by ${item.key}: ${item.value}`}
185
+ onClick={() => {
186
+ props.onFilterByAttribute!(item.key, item.value);
187
+ }}
188
+ >
189
+ <Icon
190
+ icon={IconProp.Filter}
191
+ className="h-3.5 w-3.5"
192
+ />
193
+ </button>
194
+ )}
169
195
  <CopyableButton textToBeCopied={item.value} />
170
196
  </div>
171
197
  </div>
@@ -993,6 +993,9 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
993
993
  getSpanRoute={props.getSpanRoute}
994
994
  variant="embedded"
995
995
  projectId={props.projectId}
996
+ onFilterByAttribute={
997
+ handleFieldValueSelectWithServiceResolve
998
+ }
996
999
  />
997
1000
  );
998
1001
  }}
@@ -16,7 +16,6 @@ import Icon from "../../Icon/Icon";
16
16
  import IconProp from "../../../../Types/Icon/IconProp";
17
17
  import Link from "../../Link/Link";
18
18
  import OneUptimeDate from "../../../../Types/Date";
19
- import JSONFunctions from "../../../../Types/JSONFunctions";
20
19
  import SeverityBadge from "./SeverityBadge";
21
20
  import { JSONObject } from "../../../../Types/JSON";
22
21
  import API from "../../../Utils/API/API";
@@ -40,6 +39,18 @@ export interface LogDetailsPanelProps {
40
39
  | undefined;
41
40
  variant?: "floating" | "embedded";
42
41
  projectId?: ObjectID | undefined;
42
+ /*
43
+ * Called when the user clicks "filter by" on an attribute row. The key is
44
+ * the flat attribute key as stored in the data (e.g. `requestId`,
45
+ * `oneuptime.service.id`); the value is the raw value. Wires into the
46
+ * same path as picking a value from the search bar autocomplete.
47
+ */
48
+ onFilterByAttribute?: ((key: string, value: string) => void) | undefined;
49
+ }
50
+
51
+ interface AttributeEntry {
52
+ key: string;
53
+ value: string;
43
54
  }
44
55
 
45
56
  interface PreparedBody {
@@ -120,20 +131,42 @@ const LogDetailsPanel: FunctionComponent<LogDetailsPanelProps> = (
120
131
  return prepareBody(props.log.body?.toString());
121
132
  }, [props.log.body]);
122
133
 
123
- const prettyAttributes: string | null = useMemo(() => {
124
- if (!props.log.attributes) {
125
- return null;
134
+ const attributeEntries: Array<AttributeEntry> = useMemo(() => {
135
+ const raw: Record<string, unknown> | undefined = props.log.attributes as
136
+ | Record<string, unknown>
137
+ | undefined;
138
+
139
+ if (!raw) {
140
+ return [];
126
141
  }
127
142
 
128
- try {
129
- const normalized: Record<string, unknown> = JSONFunctions.unflattenObject(
130
- props.log.attributes || {},
131
- );
132
- return JSON.stringify(normalized, null, 2);
133
- } catch {
143
+ return Object.keys(raw)
144
+ .sort((left: string, right: string): number => {
145
+ return left.localeCompare(right);
146
+ })
147
+ .map((key: string): AttributeEntry => {
148
+ const rawValue: unknown = raw[key];
149
+ const value: string =
150
+ rawValue === null || rawValue === undefined
151
+ ? ""
152
+ : typeof rawValue === "string"
153
+ ? rawValue
154
+ : JSON.stringify(rawValue);
155
+ return { key, value };
156
+ });
157
+ }, [props.log.attributes]);
158
+
159
+ const attributesAsJson: string | null = useMemo(() => {
160
+ if (attributeEntries.length === 0) {
134
161
  return null;
135
162
  }
136
- }, [props.log.attributes]);
163
+
164
+ const flat: Record<string, string> = {};
165
+ for (const entry of attributeEntries) {
166
+ flat[entry.key] = entry.value;
167
+ }
168
+ return JSON.stringify(flat, null, 2);
169
+ }, [attributeEntries]);
137
170
 
138
171
  const traceId: string = props.log.traceId?.toString() || "";
139
172
  const spanId: string = props.log.spanId?.toString() || "";
@@ -486,22 +519,75 @@ const LogDetailsPanel: FunctionComponent<LogDetailsPanelProps> = (
486
519
  </section>
487
520
  )}
488
521
 
489
- {prettyAttributes && (
522
+ {attributeEntries.length > 0 && (
490
523
  <section className="space-y-3">
491
524
  <header className="flex items-center justify-between text-[11px] uppercase tracking-wide text-gray-400">
492
525
  <span>Attributes</span>
493
- <CopyTextButton
494
- textToBeCopied={prettyAttributes}
495
- size="xs"
496
- variant="ghost"
497
- iconOnly={false}
498
- title="Copy attributes"
499
- />
526
+ {attributesAsJson && (
527
+ <CopyTextButton
528
+ textToBeCopied={attributesAsJson}
529
+ size="xs"
530
+ variant="ghost"
531
+ iconOnly={false}
532
+ title="Copy attributes as JSON"
533
+ />
534
+ )}
500
535
  </header>
501
- <div className={`rounded-lg border ${surfaceCardClass} p-4`}>
502
- <pre className="max-h-72 overflow-auto whitespace-pre-wrap break-words font-mono text-[13px] leading-6 text-gray-800">
503
- {prettyAttributes}
504
- </pre>
536
+ <div
537
+ className={`max-h-80 overflow-auto rounded-lg border ${surfaceCardClass}`}
538
+ >
539
+ <ul className="divide-y divide-gray-200">
540
+ {attributeEntries.map((entry: AttributeEntry) => {
541
+ return (
542
+ <li
543
+ key={entry.key}
544
+ className="group flex items-start gap-3 px-3 py-2 hover:bg-white"
545
+ >
546
+ <span
547
+ className="w-56 flex-none truncate font-mono text-[12px] text-gray-500"
548
+ title={entry.key}
549
+ >
550
+ {entry.key}
551
+ </span>
552
+ <span
553
+ className="min-w-0 flex-1 break-all font-mono text-[12px] text-gray-800"
554
+ title={entry.value}
555
+ >
556
+ {entry.value || (
557
+ <span className="italic text-gray-400">empty</span>
558
+ )}
559
+ </span>
560
+ <div className="flex flex-none items-center gap-1 opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100">
561
+ {props.onFilterByAttribute && entry.value && (
562
+ <button
563
+ type="button"
564
+ className="rounded p-1 text-gray-400 hover:bg-gray-100 hover:text-indigo-600"
565
+ title={`Filter by ${entry.key}: ${entry.value}`}
566
+ onClick={() => {
567
+ props.onFilterByAttribute!(
568
+ entry.key,
569
+ entry.value,
570
+ );
571
+ }}
572
+ >
573
+ <Icon
574
+ icon={IconProp.Filter}
575
+ className="h-3.5 w-3.5"
576
+ />
577
+ </button>
578
+ )}
579
+ <CopyTextButton
580
+ textToBeCopied={entry.value}
581
+ size="xs"
582
+ variant="ghost"
583
+ iconOnly={true}
584
+ title={`Copy ${entry.key}`}
585
+ />
586
+ </div>
587
+ </li>
588
+ );
589
+ })}
590
+ </ul>
505
591
  </div>
506
592
  </section>
507
593
  )}