@oneuptime/common 10.0.37 → 10.0.39

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 (60) hide show
  1. package/Server/API/TelemetryAPI.ts +10 -0
  2. package/Server/Services/LogAggregationService.ts +24 -1
  3. package/Server/Types/Workflow/Components/Conditions/IfElse.ts +68 -21
  4. package/Server/Utils/Telemetry/Telemetry.ts +38 -19
  5. package/Types/Workflow/Component.ts +1 -0
  6. package/Types/Workflow/Components/Condition.ts +24 -0
  7. package/UI/Components/Charts/Area/AreaChart.tsx +81 -0
  8. package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +106 -63
  9. package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +986 -0
  10. package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +1 -1
  11. package/UI/Components/Charts/ChartLibrary/Utils/ChartColors.ts +18 -1
  12. package/UI/Components/Charts/Utils/XAxis.ts +26 -21
  13. package/UI/Components/ConditionsTable/ConditionsTable.tsx +86 -67
  14. package/UI/Components/Dictionary/DictionaryOfStingsViewer.tsx +48 -28
  15. package/UI/Components/Filters/FiltersForm.tsx +19 -13
  16. package/UI/Components/InfoCard/InfoCard.tsx +3 -1
  17. package/UI/Components/LogsViewer/LogsViewer.tsx +9 -4
  18. package/UI/Components/LogsViewer/components/ActiveFilterChips.tsx +29 -2
  19. package/UI/Components/LogsViewer/types.ts +1 -0
  20. package/UI/Components/Workflow/Utils.ts +32 -1
  21. package/build/dist/Server/API/TelemetryAPI.js +8 -0
  22. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  23. package/build/dist/Server/Services/LogAggregationService.js +12 -0
  24. package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
  25. package/build/dist/Server/Types/Workflow/Components/Conditions/IfElse.js +49 -19
  26. package/build/dist/Server/Types/Workflow/Components/Conditions/IfElse.js.map +1 -1
  27. package/build/dist/Server/Utils/Telemetry/Telemetry.js +29 -15
  28. package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
  29. package/build/dist/Types/Workflow/Component.js +1 -0
  30. package/build/dist/Types/Workflow/Component.js.map +1 -1
  31. package/build/dist/Types/Workflow/Components/Condition.js +24 -0
  32. package/build/dist/Types/Workflow/Components/Condition.js.map +1 -1
  33. package/build/dist/UI/Components/Charts/Area/AreaChart.js +39 -0
  34. package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -0
  35. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +28 -9
  36. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
  37. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +376 -0
  38. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -0
  39. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +1 -1
  40. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
  41. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/ChartColors.js +15 -0
  42. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/ChartColors.js.map +1 -1
  43. package/build/dist/UI/Components/Charts/Utils/XAxis.js +25 -21
  44. package/build/dist/UI/Components/Charts/Utils/XAxis.js.map +1 -1
  45. package/build/dist/UI/Components/ConditionsTable/ConditionsTable.js +51 -30
  46. package/build/dist/UI/Components/ConditionsTable/ConditionsTable.js.map +1 -1
  47. package/build/dist/UI/Components/Dictionary/DictionaryOfStingsViewer.js +23 -11
  48. package/build/dist/UI/Components/Dictionary/DictionaryOfStingsViewer.js.map +1 -1
  49. package/build/dist/UI/Components/Filters/FiltersForm.js +10 -6
  50. package/build/dist/UI/Components/Filters/FiltersForm.js.map +1 -1
  51. package/build/dist/UI/Components/InfoCard/InfoCard.js +1 -1
  52. package/build/dist/UI/Components/InfoCard/InfoCard.js.map +1 -1
  53. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +5 -1
  54. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  55. package/build/dist/UI/Components/LogsViewer/components/ActiveFilterChips.js +17 -2
  56. package/build/dist/UI/Components/LogsViewer/components/ActiveFilterChips.js.map +1 -1
  57. package/build/dist/UI/Components/LogsViewer/types.js.map +1 -1
  58. package/build/dist/UI/Components/Workflow/Utils.js +28 -1
  59. package/build/dist/UI/Components/Workflow/Utils.js.map +1 -1
  60. package/package.json +1 -1
@@ -158,6 +158,10 @@ router.post(
158
158
  ? (body["spanIds"] as Array<string>)
159
159
  : undefined;
160
160
 
161
+ const attributes: Record<string, string> | undefined = body["attributes"]
162
+ ? (body["attributes"] as Record<string, string>)
163
+ : undefined;
164
+
161
165
  const request: HistogramRequest = {
162
166
  projectId: databaseProps.tenantId,
163
167
  startTime,
@@ -168,6 +172,7 @@ router.post(
168
172
  bodySearchText,
169
173
  traceIds,
170
174
  spanIds,
175
+ attributes,
171
176
  };
172
177
 
173
178
  const buckets: Array<HistogramBucket> =
@@ -242,6 +247,10 @@ router.post(
242
247
  ? (body["spanIds"] as Array<string>)
243
248
  : undefined;
244
249
 
250
+ const attributes: Record<string, string> | undefined = body["attributes"]
251
+ ? (body["attributes"] as Record<string, string>)
252
+ : undefined;
253
+
245
254
  const facets: Record<string, Array<FacetValue>> = {};
246
255
 
247
256
  for (const facetKey of facetKeys) {
@@ -256,6 +265,7 @@ router.post(
256
265
  bodySearchText,
257
266
  traceIds,
258
267
  spanIds,
268
+ attributes,
259
269
  };
260
270
 
261
271
  facets[facetKey] = await LogAggregationService.getFacetValues(request);
@@ -25,6 +25,7 @@ export interface HistogramRequest {
25
25
  bodySearchText?: string | undefined;
26
26
  traceIds?: Array<string> | undefined;
27
27
  spanIds?: Array<string> | undefined;
28
+ attributes?: Record<string, string> | undefined;
28
29
  }
29
30
 
30
31
  export interface FacetValue {
@@ -43,6 +44,7 @@ export interface FacetRequest {
43
44
  bodySearchText?: string | undefined;
44
45
  traceIds?: Array<string> | undefined;
45
46
  spanIds?: Array<string> | undefined;
47
+ attributes?: Record<string, string> | undefined;
46
48
  }
47
49
 
48
50
  export type AnalyticsChartType = "timeseries" | "toplist" | "table";
@@ -589,7 +591,12 @@ export class LogAggregationService {
589
591
  statement: Statement,
590
592
  request: Pick<
591
593
  HistogramRequest,
592
- "serviceIds" | "severityTexts" | "bodySearchText" | "traceIds" | "spanIds"
594
+ | "serviceIds"
595
+ | "severityTexts"
596
+ | "bodySearchText"
597
+ | "traceIds"
598
+ | "spanIds"
599
+ | "attributes"
593
600
  >,
594
601
  ): void {
595
602
  if (request.serviceIds && request.serviceIds.length > 0) {
@@ -640,6 +647,22 @@ export class LogAggregationService {
640
647
  }}`,
641
648
  );
642
649
  }
650
+
651
+ if (request.attributes && Object.keys(request.attributes).length > 0) {
652
+ for (const [attrKey, attrValue] of Object.entries(request.attributes)) {
653
+ LogAggregationService.validateFacetKey(attrKey);
654
+
655
+ statement.append(
656
+ SQL` AND attributes[${{
657
+ type: TableColumnType.Text,
658
+ value: attrKey,
659
+ }}] = ${{
660
+ type: TableColumnType.Text,
661
+ value: attrValue,
662
+ }}`,
663
+ );
664
+ }
665
+ }
643
666
  }
644
667
 
645
668
  @CaptureSpan()
@@ -9,6 +9,7 @@ import ComponentMetadata, {
9
9
  import ComponentID from "../../../../../Types/Workflow/ComponentID";
10
10
  import Components, {
11
11
  ConditionOperator,
12
+ ConditionValueType,
12
13
  } from "../../../../../Types/Workflow/Components/Condition";
13
14
  import CaptureSpan from "../../../../Utils/Telemetry/CaptureSpan";
14
15
 
@@ -61,29 +62,75 @@ export default class IfElse extends ComponentCode {
61
62
  * Inject dependencies
62
63
  */
63
64
 
64
- for (const key in args) {
65
- if (key === "operator") {
66
- continue;
67
- }
68
-
69
- const value: JSONValue = args[key];
65
+ // Get explicit types from dropdowns, default to text
66
+ let input1Type: ConditionValueType =
67
+ (args["input-1-type"] as ConditionValueType) || ConditionValueType.Text;
68
+ let input2Type: ConditionValueType =
69
+ (args["input-2-type"] as ConditionValueType) || ConditionValueType.Text;
70
70
 
71
- let shouldHaveQuotes: boolean = false;
71
+ /*
72
+ * When types differ, coerce both to the more specific type
73
+ * so comparisons like text "true" == boolean true work correctly.
74
+ * Priority: Null/Undefined keep as-is, Boolean > Number > Text.
75
+ */
76
+ if (input1Type !== input2Type) {
77
+ type IsNullishFunction = (t: ConditionValueType) => boolean;
78
+
79
+ const isNullish: IsNullishFunction = (
80
+ t: ConditionValueType,
81
+ ): boolean => {
82
+ return (
83
+ t === ConditionValueType.Null || t === ConditionValueType.Undefined
84
+ );
85
+ };
72
86
 
73
- if (
74
- typeof value === "string" &&
75
- value !== "null" &&
76
- value !== "undefined"
77
- ) {
78
- shouldHaveQuotes = true;
87
+ if (!isNullish(input1Type) && !isNullish(input2Type)) {
88
+ const typePriority: Record<string, number> = {
89
+ [ConditionValueType.Boolean]: 2,
90
+ [ConditionValueType.Number]: 1,
91
+ [ConditionValueType.Text]: 0,
92
+ };
93
+
94
+ const p1: number = typePriority[input1Type] ?? 0;
95
+ const p2: number = typePriority[input2Type] ?? 0;
96
+ const commonType: ConditionValueType =
97
+ p1 >= p2 ? input1Type : input2Type;
98
+ input1Type = commonType;
99
+ input2Type = commonType;
79
100
  }
101
+ }
80
102
 
81
- if (typeof value === "object") {
82
- args[key] = JSON.stringify(args[key]);
103
+ type FormatValueFunction = (
104
+ value: JSONValue,
105
+ valueType: ConditionValueType,
106
+ ) => string;
107
+
108
+ const formatValue: FormatValueFunction = (
109
+ value: JSONValue,
110
+ valueType: ConditionValueType,
111
+ ): string => {
112
+ const strValue: string =
113
+ typeof value === "object"
114
+ ? JSON.stringify(value)
115
+ : String(value ?? "");
116
+
117
+ switch (valueType) {
118
+ case ConditionValueType.Boolean:
119
+ return strValue === "true" ? "true" : "false";
120
+ case ConditionValueType.Number:
121
+ return isNaN(Number(strValue)) ? "0" : String(Number(strValue));
122
+ case ConditionValueType.Null:
123
+ return "null";
124
+ case ConditionValueType.Undefined:
125
+ return "undefined";
126
+ case ConditionValueType.Text:
127
+ default:
128
+ return `"${strValue}"`;
83
129
  }
130
+ };
84
131
 
85
- args[key] = shouldHaveQuotes ? `"${args[key]}"` : args[key];
86
- }
132
+ args["input-1"] = formatValue(args["input-1"], input1Type);
133
+ args["input-2"] = formatValue(args["input-2"], input2Type);
87
134
 
88
135
  type SerializeFunction = (arg: string) => string;
89
136
 
@@ -107,13 +154,13 @@ export default class IfElse extends ComponentCode {
107
154
  `;
108
155
 
109
156
  if (args["operator"] === ConditionOperator.Contains) {
110
- code += `return input1.includes(input2);`;
157
+ code += `return String(input1).includes(String(input2));`;
111
158
  } else if (args["operator"] === ConditionOperator.DoesNotContain) {
112
- code += `return !input1.includes(input2);`;
159
+ code += `return !String(input1).includes(String(input2));`;
113
160
  } else if (args["operator"] === ConditionOperator.StartsWith) {
114
- code += `return input1.startsWith(input2);`;
161
+ code += `return String(input1).startsWith(String(input2));`;
115
162
  } else if (args["operator"] === ConditionOperator.EndsWith) {
116
- code += `return input1.endsWith(input2);`;
163
+ code += `return String(input1).endsWith(String(input2));`;
117
164
  } else {
118
165
  code += `return input1 ${(args["operator"] as string) || "=="} input2;`;
119
166
  }
@@ -211,33 +211,50 @@ export default class TelemetryUtil {
211
211
  const jsonValue: JSONObject = value as JSONObject;
212
212
 
213
213
  if (jsonValue && typeof jsonValue === "object") {
214
- if (Object.prototype.hasOwnProperty.call(jsonValue, "stringValue")) {
215
- const stringValue: JSONValue = jsonValue["stringValue"];
214
+ // Handle both camelCase (JSON encoding) and snake_case (protobuf via protobufjs)
215
+ if (
216
+ Object.prototype.hasOwnProperty.call(jsonValue, "stringValue") ||
217
+ Object.prototype.hasOwnProperty.call(jsonValue, "string_value")
218
+ ) {
219
+ const stringValue: JSONValue =
220
+ jsonValue["stringValue"] ?? jsonValue["string_value"];
216
221
  finalObj =
217
222
  stringValue !== undefined && stringValue !== null
218
223
  ? (stringValue as string)
219
224
  : "";
220
- } else if (Object.prototype.hasOwnProperty.call(jsonValue, "intValue")) {
221
- const intValue: JSONValue = jsonValue["intValue"];
225
+ } else if (
226
+ Object.prototype.hasOwnProperty.call(jsonValue, "intValue") ||
227
+ Object.prototype.hasOwnProperty.call(jsonValue, "int_value")
228
+ ) {
229
+ const intValue: JSONValue =
230
+ jsonValue["intValue"] ?? jsonValue["int_value"];
222
231
  if (intValue !== undefined && intValue !== null) {
223
232
  finalObj = intValue as number;
224
233
  }
225
234
  } else if (
226
- Object.prototype.hasOwnProperty.call(jsonValue, "doubleValue")
235
+ Object.prototype.hasOwnProperty.call(jsonValue, "doubleValue") ||
236
+ Object.prototype.hasOwnProperty.call(jsonValue, "double_value")
227
237
  ) {
228
- const doubleValue: JSONValue = jsonValue["doubleValue"];
238
+ const doubleValue: JSONValue =
239
+ jsonValue["doubleValue"] ?? jsonValue["double_value"];
229
240
  if (doubleValue !== undefined && doubleValue !== null) {
230
241
  finalObj = doubleValue as number;
231
242
  }
232
- } else if (Object.prototype.hasOwnProperty.call(jsonValue, "boolValue")) {
233
- finalObj = jsonValue["boolValue"] as boolean;
234
243
  } else if (
235
- jsonValue["arrayValue"] &&
236
- (jsonValue["arrayValue"] as JSONObject)["values"]
244
+ Object.prototype.hasOwnProperty.call(jsonValue, "boolValue") ||
245
+ Object.prototype.hasOwnProperty.call(jsonValue, "bool_value")
246
+ ) {
247
+ finalObj = (jsonValue["boolValue"] ??
248
+ jsonValue["bool_value"]) as boolean;
249
+ } else if (
250
+ (jsonValue["arrayValue"] &&
251
+ (jsonValue["arrayValue"] as JSONObject)["values"]) ||
252
+ (jsonValue["array_value"] &&
253
+ (jsonValue["array_value"] as JSONObject)["values"])
237
254
  ) {
238
- const values: JSONArray = (jsonValue["arrayValue"] as JSONObject)[
239
- "values"
240
- ] as JSONArray;
255
+ const arrayVal: JSONObject = (jsonValue["arrayValue"] ||
256
+ jsonValue["array_value"]) as JSONObject;
257
+ const values: JSONArray = arrayVal["values"] as JSONArray;
241
258
  finalObj = values.map((v: JSONObject) => {
242
259
  return this.getAttributeValues(
243
260
  prefixKeysWithString,
@@ -290,17 +307,19 @@ export default class TelemetryUtil {
290
307
 
291
308
  finalObj = flattenedFields;
292
309
  } else if (
293
- jsonValue["kvlistValue"] &&
294
- (jsonValue["kvlistValue"] as JSONObject)["values"]
310
+ (jsonValue["kvlistValue"] &&
311
+ (jsonValue["kvlistValue"] as JSONObject)["values"]) ||
312
+ (jsonValue["kvlist_value"] &&
313
+ (jsonValue["kvlist_value"] as JSONObject)["values"])
295
314
  ) {
296
- const values: JSONArray = (jsonValue["kvlistValue"] as JSONObject)[
297
- "values"
298
- ] as JSONArray;
315
+ const kvlistVal: JSONObject = (jsonValue["kvlistValue"] ||
316
+ jsonValue["kvlist_value"]) as JSONObject;
317
+ const values: JSONArray = kvlistVal["values"] as JSONArray;
299
318
  finalObj = this.getAttributes({
300
319
  prefixKeysWithString,
301
320
  items: values,
302
321
  });
303
- } else if ("nullValue" in jsonValue) {
322
+ } else if ("nullValue" in jsonValue || "null_value" in jsonValue) {
304
323
  finalObj = null;
305
324
  }
306
325
  }
@@ -26,6 +26,7 @@ export enum ComponentInputType {
26
26
  HTML = "HTML",
27
27
  Operator = "Operator",
28
28
  Markdown = "Markdown",
29
+ ValueType = "Value Type",
29
30
  }
30
31
 
31
32
  export enum ComponentType {
@@ -5,6 +5,14 @@ import ComponentMetadata, {
5
5
  ComponentType,
6
6
  } from "./../Component";
7
7
 
8
+ export enum ConditionValueType {
9
+ Text = "text",
10
+ Boolean = "boolean",
11
+ Number = "number",
12
+ Null = "null",
13
+ Undefined = "undefined",
14
+ }
15
+
8
16
  export enum ConditionOperator {
9
17
  EqualTo = "==",
10
18
  NotEqualTo = "!=",
@@ -27,6 +35,14 @@ const components: Array<ComponentMetadata> = [
27
35
  iconProp: IconProp.Condition,
28
36
  componentType: ComponentType.Component,
29
37
  arguments: [
38
+ {
39
+ type: ComponentInputType.ValueType,
40
+ name: "Input 1 Type",
41
+ description: "Type of Input 1. Defaults to Text if not selected.",
42
+ placeholder: "Text",
43
+ required: false,
44
+ id: "input-1-type",
45
+ },
30
46
  {
31
47
  type: ComponentInputType.Text,
32
48
  name: "Input 1",
@@ -43,6 +59,14 @@ const components: Array<ComponentMetadata> = [
43
59
  required: true,
44
60
  id: "operator",
45
61
  },
62
+ {
63
+ type: ComponentInputType.ValueType,
64
+ name: "Input 2 Type",
65
+ description: "Type of Input 2. Defaults to Text if not selected.",
66
+ placeholder: "Text",
67
+ required: false,
68
+ id: "input-2-type",
69
+ },
46
70
  {
47
71
  type: ComponentInputType.Text,
48
72
  name: "Input 2",
@@ -0,0 +1,81 @@
1
+ import { AreaChart } from "../ChartLibrary/AreaChart/AreaChart";
2
+ import React, { FunctionComponent, ReactElement, useEffect } from "react";
3
+ import SeriesPoint from "../Types/SeriesPoints";
4
+ import { XAxis } from "../Types/XAxis/XAxis";
5
+ import YAxis from "../Types/YAxis/YAxis";
6
+ import ChartCurve from "../Types/ChartCurve";
7
+ import ChartDataPoint from "../ChartLibrary/Types/ChartDataPoint";
8
+ import DataPointUtil from "../Utils/DataPoint";
9
+
10
+ export interface ComponentProps {
11
+ data: Array<SeriesPoint>;
12
+ xAxis: XAxis;
13
+ yAxis: YAxis;
14
+ curve: ChartCurve;
15
+ sync: boolean;
16
+ heightInPx?: number | undefined;
17
+ }
18
+
19
+ export interface AreaInternalProps extends ComponentProps {
20
+ syncid: string;
21
+ }
22
+
23
+ const AreaChartElement: FunctionComponent<AreaInternalProps> = (
24
+ props: AreaInternalProps,
25
+ ): ReactElement => {
26
+ const [records, setRecords] = React.useState<Array<ChartDataPoint>>([]);
27
+
28
+ const categories: Array<string> = props.data.map((item: SeriesPoint) => {
29
+ return item.seriesName;
30
+ });
31
+
32
+ useEffect(() => {
33
+ if (!props.data || props.data.length === 0) {
34
+ setRecords([]);
35
+ }
36
+
37
+ const records: Array<ChartDataPoint> = DataPointUtil.getChartDataPoints({
38
+ seriesPoints: props.data,
39
+ xAxis: props.xAxis,
40
+ yAxis: props.yAxis,
41
+ });
42
+
43
+ setRecords(records);
44
+ }, [props.data]);
45
+
46
+ const className: string = props.heightInPx ? `` : "h-80";
47
+ const style: React.CSSProperties = props.heightInPx
48
+ ? { height: `${props.heightInPx}px` }
49
+ : {};
50
+
51
+ return (
52
+ <AreaChart
53
+ className={className}
54
+ style={style}
55
+ data={records}
56
+ tickGap={1}
57
+ index={"Time"}
58
+ categories={categories}
59
+ colors={[
60
+ "blue",
61
+ "emerald",
62
+ "violet",
63
+ "amber",
64
+ "cyan",
65
+ "pink",
66
+ "lime",
67
+ "fuchsia",
68
+ "indigo",
69
+ "rose",
70
+ ]}
71
+ valueFormatter={props.yAxis.options.formatter || undefined}
72
+ showTooltip={true}
73
+ connectNulls={true}
74
+ curve={props.curve || ChartCurve.MONOTONE}
75
+ syncid={props.sync ? props.syncid : undefined}
76
+ yAxisWidth={60}
77
+ />
78
+ );
79
+ };
80
+
81
+ export default AreaChartElement;
@@ -3,6 +3,9 @@ import LineChart, { ComponentProps as LineChartProps } from "../Line/LineChart";
3
3
  import BarChartElement, {
4
4
  ComponentProps as BarChartProps,
5
5
  } from "../Bar/BarChart";
6
+ import AreaChartElement, {
7
+ ComponentProps as AreaChartProps,
8
+ } from "../Area/AreaChart";
6
9
  import React, { FunctionComponent, ReactElement } from "react";
7
10
 
8
11
  export enum ChartType {
@@ -16,7 +19,7 @@ export interface Chart {
16
19
  title: string;
17
20
  description?: string | undefined;
18
21
  type: ChartType;
19
- props: LineChartProps | BarChartProps;
22
+ props: LineChartProps | BarChartProps | AreaChartProps;
20
23
  }
21
24
 
22
25
  export interface ComponentProps {
@@ -31,71 +34,111 @@ const ChartGroup: FunctionComponent<ComponentProps> = (
31
34
  ): ReactElement => {
32
35
  const syncId: string = Text.generateRandomText(10);
33
36
 
37
+ const isLastChart: (index: number) => boolean = (index: number): boolean => {
38
+ return index === props.charts.length - 1;
39
+ };
40
+
41
+ type GetChartContentFunction = (chart: Chart, index: number) => ReactElement;
42
+
43
+ const getChartContent: GetChartContentFunction = (
44
+ chart: Chart,
45
+ index: number,
46
+ ): ReactElement => {
47
+ switch (chart.type) {
48
+ case ChartType.LINE:
49
+ return (
50
+ <LineChart
51
+ key={index}
52
+ {...(chart.props as LineChartProps)}
53
+ syncid={syncId}
54
+ heightInPx={props.heightInPx}
55
+ />
56
+ );
57
+ case ChartType.BAR:
58
+ return (
59
+ <BarChartElement
60
+ key={index}
61
+ {...(chart.props as BarChartProps)}
62
+ syncid={syncId}
63
+ heightInPx={props.heightInPx}
64
+ />
65
+ );
66
+ case ChartType.AREA:
67
+ return (
68
+ <AreaChartElement
69
+ key={index}
70
+ {...(chart.props as AreaChartProps)}
71
+ syncid={syncId}
72
+ heightInPx={props.heightInPx}
73
+ />
74
+ );
75
+ default:
76
+ return <></>;
77
+ }
78
+ };
79
+
80
+ // When hideCard is true, render charts in a clean vertical stack with dividers
81
+ if (props.hideCard) {
82
+ return (
83
+ <div className="space-y-0">
84
+ {props.charts.map((chart: Chart, index: number) => {
85
+ return (
86
+ <div
87
+ key={index}
88
+ className={`${!isLastChart(index) ? "border-b border-gray-100" : ""} ${props.chartCssClass || ""}`}
89
+ >
90
+ <div className="px-1 pt-5 pb-4">
91
+ <div className="mb-1">
92
+ <h3 className="text-sm font-semibold text-gray-700 tracking-tight">
93
+ {chart.title}
94
+ </h3>
95
+ {chart.description && (
96
+ <p className="mt-0.5 text-xs text-gray-400 hidden md:block">
97
+ {chart.description}
98
+ </p>
99
+ )}
100
+ </div>
101
+ {getChartContent(chart, index)}
102
+ </div>
103
+ </div>
104
+ );
105
+ })}
106
+ </div>
107
+ );
108
+ }
109
+
110
+ // When showing cards, use the grid layout
111
+ const gridCols: string =
112
+ props.charts.length > 1 ? "lg:grid-cols-2" : "lg:grid-cols-1";
113
+
34
114
  return (
35
- <div className="lg:grid grid-cols-1 gap-5 space-y-5 lg:space-y-0">
115
+ <div
116
+ className={`grid grid-cols-1 ${gridCols} gap-4 space-y-4 lg:space-y-0`}
117
+ >
36
118
  {props.charts.map((chart: Chart, index: number) => {
37
- switch (chart.type) {
38
- case ChartType.LINE:
39
- return (
40
- <div
41
- key={index}
42
- className={`p-6 ${props.hideCard ? "" : "rounded-md bg-white shadow"} ${props.chartCssClass || ""}`}
43
- >
44
- <h2
45
- data-testid="card-details-heading"
46
- id="card-details-heading"
47
- className="text-lg font-medium leading-6 text-gray-900"
48
- >
49
- {chart.title}
50
- </h2>
51
- {chart.description && (
52
- <p
53
- data-testid="card-description"
54
- className="mt-1 text-sm text-gray-500 w-full hidden md:block"
55
- >
56
- {chart.description}
57
- </p>
58
- )}
59
- <LineChart
60
- key={index}
61
- {...(chart.props as LineChartProps)}
62
- syncid={syncId}
63
- heightInPx={props.heightInPx}
64
- />
65
- </div>
66
- );
67
- case ChartType.BAR:
68
- return (
69
- <div
70
- key={index}
71
- className={`p-6 ${props.hideCard ? "" : "rounded-md bg-white shadow"} ${props.chartCssClass || ""}`}
119
+ return (
120
+ <div
121
+ key={index}
122
+ className={`p-5 rounded-lg border border-gray-200 bg-white shadow-sm ${props.chartCssClass || ""}`}
123
+ >
124
+ <h2
125
+ data-testid="card-details-heading"
126
+ id="card-details-heading"
127
+ className="text-base font-semibold leading-6 text-gray-900"
128
+ >
129
+ {chart.title}
130
+ </h2>
131
+ {chart.description && (
132
+ <p
133
+ data-testid="card-description"
134
+ className="mt-0.5 text-sm text-gray-500 w-full hidden md:block"
72
135
  >
73
- <h2
74
- data-testid="card-details-heading"
75
- id="card-details-heading"
76
- className="text-lg font-medium leading-6 text-gray-900"
77
- >
78
- {chart.title}
79
- </h2>
80
- {chart.description && (
81
- <p
82
- data-testid="card-description"
83
- className="mt-1 text-sm text-gray-500 w-full hidden md:block"
84
- >
85
- {chart.description}
86
- </p>
87
- )}
88
- <BarChartElement
89
- key={index}
90
- {...(chart.props as BarChartProps)}
91
- syncid={syncId}
92
- heightInPx={props.heightInPx}
93
- />
94
- </div>
95
- );
96
- default:
97
- return <></>;
98
- }
136
+ {chart.description}
137
+ </p>
138
+ )}
139
+ {getChartContent(chart, index)}
140
+ </div>
141
+ );
99
142
  })}
100
143
  </div>
101
144
  );