@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.
- package/Server/API/TelemetryAPI.ts +10 -0
- package/Server/Services/LogAggregationService.ts +24 -1
- package/Server/Types/Workflow/Components/Conditions/IfElse.ts +68 -21
- package/Server/Utils/Telemetry/Telemetry.ts +38 -19
- package/Types/Workflow/Component.ts +1 -0
- package/Types/Workflow/Components/Condition.ts +24 -0
- package/UI/Components/Charts/Area/AreaChart.tsx +81 -0
- package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +106 -63
- package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +986 -0
- package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +1 -1
- package/UI/Components/Charts/ChartLibrary/Utils/ChartColors.ts +18 -1
- package/UI/Components/Charts/Utils/XAxis.ts +26 -21
- package/UI/Components/ConditionsTable/ConditionsTable.tsx +86 -67
- package/UI/Components/Dictionary/DictionaryOfStingsViewer.tsx +48 -28
- package/UI/Components/Filters/FiltersForm.tsx +19 -13
- package/UI/Components/InfoCard/InfoCard.tsx +3 -1
- package/UI/Components/LogsViewer/LogsViewer.tsx +9 -4
- package/UI/Components/LogsViewer/components/ActiveFilterChips.tsx +29 -2
- package/UI/Components/LogsViewer/types.ts +1 -0
- package/UI/Components/Workflow/Utils.ts +32 -1
- package/build/dist/Server/API/TelemetryAPI.js +8 -0
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/Services/LogAggregationService.js +12 -0
- package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/Conditions/IfElse.js +49 -19
- package/build/dist/Server/Types/Workflow/Components/Conditions/IfElse.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/Telemetry.js +29 -15
- package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
- package/build/dist/Types/Workflow/Component.js +1 -0
- package/build/dist/Types/Workflow/Component.js.map +1 -1
- package/build/dist/Types/Workflow/Components/Condition.js +24 -0
- package/build/dist/Types/Workflow/Components/Condition.js.map +1 -1
- package/build/dist/UI/Components/Charts/Area/AreaChart.js +39 -0
- package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -0
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +28 -9
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +376 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/ChartColors.js +15 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/ChartColors.js.map +1 -1
- package/build/dist/UI/Components/Charts/Utils/XAxis.js +25 -21
- package/build/dist/UI/Components/Charts/Utils/XAxis.js.map +1 -1
- package/build/dist/UI/Components/ConditionsTable/ConditionsTable.js +51 -30
- package/build/dist/UI/Components/ConditionsTable/ConditionsTable.js.map +1 -1
- package/build/dist/UI/Components/Dictionary/DictionaryOfStingsViewer.js +23 -11
- package/build/dist/UI/Components/Dictionary/DictionaryOfStingsViewer.js.map +1 -1
- package/build/dist/UI/Components/Filters/FiltersForm.js +10 -6
- package/build/dist/UI/Components/Filters/FiltersForm.js.map +1 -1
- package/build/dist/UI/Components/InfoCard/InfoCard.js +1 -1
- package/build/dist/UI/Components/InfoCard/InfoCard.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +5 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/ActiveFilterChips.js +17 -2
- package/build/dist/UI/Components/LogsViewer/components/ActiveFilterChips.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/types.js.map +1 -1
- package/build/dist/UI/Components/Workflow/Utils.js +28 -1
- package/build/dist/UI/Components/Workflow/Utils.js.map +1 -1
- 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
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
82
|
-
|
|
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
|
-
|
|
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
|
-
|
|
215
|
-
|
|
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 (
|
|
221
|
-
|
|
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 =
|
|
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
|
|
236
|
-
(jsonValue
|
|
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
|
|
239
|
-
"
|
|
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
|
-
|
|
310
|
+
(jsonValue["kvlistValue"] &&
|
|
311
|
+
(jsonValue["kvlistValue"] as JSONObject)["values"]) ||
|
|
312
|
+
(jsonValue["kvlist_value"] &&
|
|
313
|
+
(jsonValue["kvlist_value"] as JSONObject)["values"])
|
|
295
314
|
) {
|
|
296
|
-
const
|
|
297
|
-
"
|
|
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
|
}
|
|
@@ -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
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
);
|