@oneuptime/common 9.2.5 → 9.2.8
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/Infrastructure/Queue.ts +20 -11
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +36 -0
- package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +3 -0
- package/Types/Dashboard/DashboardComponents/DashboardChartComponent.ts +2 -0
- package/Types/Metrics/MetricQueryConfigData.ts +6 -0
- package/Types/Monitor/NetworkMonitor/NetworkPathTrace.ts +43 -0
- package/Types/Probe/ProbeMonitorResponse.ts +2 -0
- package/Types/Probe/RequestFailedDetails.ts +36 -0
- package/UI/Components/Charts/Bar/BarChart.tsx +76 -0
- package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +34 -2
- package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +37 -5
- package/Utils/API.ts +305 -2
- package/Utils/Dashboard/Components/DashboardChartComponent.ts +20 -0
- package/build/dist/Server/Infrastructure/Queue.js +14 -3
- package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +20 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
- package/build/dist/Types/Metrics/MetricQueryConfigData.js +5 -1
- package/build/dist/Types/Metrics/MetricQueryConfigData.js.map +1 -1
- package/build/dist/Types/Monitor/NetworkMonitor/NetworkPathTrace.js +2 -0
- package/build/dist/Types/Monitor/NetworkMonitor/NetworkPathTrace.js.map +1 -0
- package/build/dist/Types/Probe/RequestFailedDetails.js +26 -0
- package/build/dist/Types/Probe/RequestFailedDetails.js.map +1 -0
- package/build/dist/UI/Components/Charts/Bar/BarChart.js +37 -0
- package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -0
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +6 -0
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +38 -7
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
- package/build/dist/Utils/API.js +249 -2
- package/build/dist/Utils/API.js.map +1 -1
- package/build/dist/Utils/Dashboard/Components/DashboardChartComponent.js +19 -0
- package/build/dist/Utils/Dashboard/Components/DashboardChartComponent.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ClusterKey } from "../EnvironmentConfig";
|
|
2
2
|
import Dictionary from "../../Types/Dictionary";
|
|
3
3
|
import { JSONObject } from "../../Types/JSON";
|
|
4
|
-
import { Queue as BullQueue, Job, JobsOptions } from "bullmq";
|
|
4
|
+
import { Queue as BullQueue, Job, JobsOptions, RepeatableJob } from "bullmq";
|
|
5
5
|
import { ExpressAdapter } from "@bull-board/express";
|
|
6
6
|
import { createBullBoard } from "@bull-board/api";
|
|
7
7
|
import { BullMQAdapter } from "@bull-board/api/bullMQAdapter";
|
|
@@ -196,14 +196,29 @@ export default class Queue {
|
|
|
196
196
|
jobId: sanitizedJobId,
|
|
197
197
|
};
|
|
198
198
|
|
|
199
|
+
const queue: BullQueue = this.getQueue(queueName);
|
|
200
|
+
|
|
199
201
|
if (options && options.scheduleAt) {
|
|
200
202
|
optionsObject.repeat = {
|
|
201
203
|
pattern: options.scheduleAt,
|
|
204
|
+
// keep repeatable job keyed by jobId so multiple workers do not register duplicates
|
|
205
|
+
jobId: sanitizedJobId,
|
|
202
206
|
};
|
|
207
|
+
|
|
208
|
+
const repeatableJobs: RepeatableJob[] = await queue.getRepeatableJobs();
|
|
209
|
+
|
|
210
|
+
for (const repeatableJob of repeatableJobs) {
|
|
211
|
+
const isSameJob: boolean =
|
|
212
|
+
repeatableJob.name === jobName &&
|
|
213
|
+
repeatableJob.pattern === options.scheduleAt;
|
|
214
|
+
|
|
215
|
+
if (isSameJob) {
|
|
216
|
+
await queue.removeRepeatableByKey(repeatableJob.key);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
203
219
|
}
|
|
204
220
|
|
|
205
|
-
const job: Job | undefined =
|
|
206
|
-
await this.getQueue(queueName).getJob(sanitizedJobId);
|
|
221
|
+
const job: Job | undefined = await queue.getJob(sanitizedJobId);
|
|
207
222
|
|
|
208
223
|
if (job) {
|
|
209
224
|
await job.remove();
|
|
@@ -211,9 +226,7 @@ export default class Queue {
|
|
|
211
226
|
|
|
212
227
|
if (options?.repeatableKey) {
|
|
213
228
|
// remove existing repeatable job
|
|
214
|
-
await
|
|
215
|
-
options?.repeatableKey,
|
|
216
|
-
);
|
|
229
|
+
await queue.removeRepeatableByKey(options?.repeatableKey);
|
|
217
230
|
}
|
|
218
231
|
|
|
219
232
|
// Store repeatable jobs for re-adding on reconnect
|
|
@@ -228,11 +241,7 @@ export default class Queue {
|
|
|
228
241
|
};
|
|
229
242
|
}
|
|
230
243
|
|
|
231
|
-
const jobAdded: Job = await
|
|
232
|
-
jobName,
|
|
233
|
-
data,
|
|
234
|
-
optionsObject,
|
|
235
|
-
);
|
|
244
|
+
const jobAdded: Job = await queue.add(jobName, data, optionsObject);
|
|
236
245
|
|
|
237
246
|
return jobAdded;
|
|
238
247
|
}
|
|
@@ -26,6 +26,7 @@ import MonitorEvaluationSummary, {
|
|
|
26
26
|
} from "../../../Types/Monitor/MonitorEvaluationSummary";
|
|
27
27
|
import ProbeApiIngestResponse from "../../../Types/Probe/ProbeApiIngestResponse";
|
|
28
28
|
import ProbeMonitorResponse from "../../../Types/Probe/ProbeMonitorResponse";
|
|
29
|
+
import RequestFailedDetails from "../../../Types/Probe/RequestFailedDetails";
|
|
29
30
|
import IncomingMonitorRequest from "../../../Types/Monitor/IncomingMonitor/IncomingMonitorRequest";
|
|
30
31
|
import MonitorType from "../../../Types/Monitor/MonitorType";
|
|
31
32
|
import { CheckOn, CriteriaFilter } from "../../../Types/Monitor/CriteriaFilter";
|
|
@@ -472,6 +473,7 @@ ${contextBlock}
|
|
|
472
473
|
}): string | null {
|
|
473
474
|
const requestDetails: Array<string> = [];
|
|
474
475
|
const responseDetails: Array<string> = [];
|
|
476
|
+
const failureDetails: Array<string> = [];
|
|
475
477
|
|
|
476
478
|
const probeResponse: ProbeMonitorResponse | null =
|
|
477
479
|
MonitorCriteriaDataExtractor.getProbeMonitorResponse(input.dataToProcess);
|
|
@@ -526,6 +528,34 @@ ${contextBlock}
|
|
|
526
528
|
);
|
|
527
529
|
}
|
|
528
530
|
|
|
531
|
+
// Add Request Failed Details if available
|
|
532
|
+
if (probeResponse?.requestFailedDetails) {
|
|
533
|
+
const requestFailedDetails: RequestFailedDetails =
|
|
534
|
+
probeResponse.requestFailedDetails;
|
|
535
|
+
|
|
536
|
+
if (requestFailedDetails.failedPhase) {
|
|
537
|
+
failureDetails.push(
|
|
538
|
+
`- Failed Phase: ${requestFailedDetails.failedPhase}`,
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (requestFailedDetails.errorCode) {
|
|
543
|
+
failureDetails.push(`- Error Code: ${requestFailedDetails.errorCode}`);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (requestFailedDetails.errorDescription) {
|
|
547
|
+
failureDetails.push(
|
|
548
|
+
`- Error Description: ${requestFailedDetails.errorDescription}`,
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
if (requestFailedDetails.rawErrorMessage) {
|
|
553
|
+
failureDetails.push(
|
|
554
|
+
`- Raw Error Message: ${requestFailedDetails.rawErrorMessage}`,
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
529
559
|
const sections: Array<string> = [];
|
|
530
560
|
|
|
531
561
|
if (requestDetails.length > 0) {
|
|
@@ -536,6 +566,12 @@ ${contextBlock}
|
|
|
536
566
|
sections.push(`\n\n**Response Snapshot**\n${responseDetails.join("\n")}`);
|
|
537
567
|
}
|
|
538
568
|
|
|
569
|
+
if (failureDetails.length > 0) {
|
|
570
|
+
sections.push(
|
|
571
|
+
`\n\n**Request Failed Details**\n${failureDetails.join("\n")}`,
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
|
|
539
575
|
if (!sections.length) {
|
|
540
576
|
return null;
|
|
541
577
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import DashboardBaseComponent from "./DashboardBaseComponent";
|
|
2
|
+
import { DropdownOption } from "../../../UI/Components/Dropdown/Dropdown";
|
|
2
3
|
|
|
3
4
|
export enum ComponentInputType {
|
|
4
5
|
Text = "Text",
|
|
@@ -9,6 +10,7 @@ export enum ComponentInputType {
|
|
|
9
10
|
Decimal = "Decimal",
|
|
10
11
|
MetricsQueryConfig = "MetricsQueryConfig",
|
|
11
12
|
LongText = "Long Text",
|
|
13
|
+
Dropdown = "Dropdown",
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
export interface ComponentArgument<T extends DashboardBaseComponent> {
|
|
@@ -19,4 +21,5 @@ export interface ComponentArgument<T extends DashboardBaseComponent> {
|
|
|
19
21
|
id: keyof T["arguments"];
|
|
20
22
|
isAdvanced?: boolean | undefined;
|
|
21
23
|
placeholder?: string | undefined;
|
|
24
|
+
dropdownOptions?: Array<DropdownOption> | undefined;
|
|
22
25
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import MetricQueryConfigData from "../../Metrics/MetricQueryConfigData";
|
|
2
2
|
import ObjectID from "../../ObjectID";
|
|
3
3
|
import DashboardComponentType from "../DashboardComponentType";
|
|
4
|
+
import DashboardChartType from "../Chart/ChartType";
|
|
4
5
|
import BaseComponent from "./DashboardBaseComponent";
|
|
5
6
|
|
|
6
7
|
export default interface DashboardChartComponent extends BaseComponent {
|
|
@@ -12,5 +13,6 @@ export default interface DashboardChartComponent extends BaseComponent {
|
|
|
12
13
|
chartDescription?: string | undefined;
|
|
13
14
|
legendText?: string | undefined;
|
|
14
15
|
legendUnit?: string | undefined;
|
|
16
|
+
chartType?: DashboardChartType | undefined;
|
|
15
17
|
};
|
|
16
18
|
}
|
|
@@ -2,6 +2,11 @@ import AggregatedModel from "../BaseDatabase/AggregatedModel";
|
|
|
2
2
|
import MetricAliasData from "./MetricAliasData";
|
|
3
3
|
import MetricQueryData from "./MetricQueryData";
|
|
4
4
|
|
|
5
|
+
export enum MetricChartType {
|
|
6
|
+
LINE = "line",
|
|
7
|
+
BAR = "bar",
|
|
8
|
+
}
|
|
9
|
+
|
|
5
10
|
export interface ChartSeries {
|
|
6
11
|
title: string;
|
|
7
12
|
}
|
|
@@ -10,4 +15,5 @@ export default interface MetricQueryConfigData {
|
|
|
10
15
|
metricAliasData?: MetricAliasData | undefined;
|
|
11
16
|
metricQueryData: MetricQueryData;
|
|
12
17
|
getSeries?: ((data: AggregatedModel) => ChartSeries) | undefined;
|
|
18
|
+
chartType?: MetricChartType | undefined;
|
|
13
19
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents a single hop in a network traceroute
|
|
3
|
+
*/
|
|
4
|
+
export interface TraceRouteHop {
|
|
5
|
+
hopNumber: number;
|
|
6
|
+
address: string | undefined; // IP address of the hop, undefined if the hop timed out
|
|
7
|
+
hostName: string | undefined; // Hostname of the hop if DNS lookup succeeded
|
|
8
|
+
roundTripTimeInMS: number | undefined; // RTT in milliseconds, undefined if timed out
|
|
9
|
+
isTimeout: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Represents the result of a traceroute operation
|
|
14
|
+
*/
|
|
15
|
+
export interface TraceRoute {
|
|
16
|
+
hops: Array<TraceRouteHop>;
|
|
17
|
+
destinationAddress: string;
|
|
18
|
+
destinationHostName: string | undefined;
|
|
19
|
+
isComplete: boolean; // Whether the traceroute reached the destination
|
|
20
|
+
totalHops: number;
|
|
21
|
+
failedHop: number | undefined; // The hop number where the route failed, if any
|
|
22
|
+
failureMessage: string | undefined; // Failure message at the failed hop
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Represents DNS resolution information
|
|
27
|
+
*/
|
|
28
|
+
export interface DNSLookupResult {
|
|
29
|
+
hostName: string;
|
|
30
|
+
resolvedAddresses: Array<string>; // List of resolved IP addresses
|
|
31
|
+
resolvedInMS: number; // Time taken for DNS resolution
|
|
32
|
+
isSuccess: boolean;
|
|
33
|
+
errorMessage: string | undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Complete network path diagnosis result combining DNS and traceroute
|
|
38
|
+
*/
|
|
39
|
+
export default interface NetworkPathTrace {
|
|
40
|
+
dnsLookup?: DNSLookupResult | undefined;
|
|
41
|
+
traceRoute?: TraceRoute | undefined;
|
|
42
|
+
timestamp: Date;
|
|
43
|
+
}
|
|
@@ -9,6 +9,7 @@ import SyntheticMonitorResponse from "../Monitor/SyntheticMonitors/SyntheticMoni
|
|
|
9
9
|
import MonitorEvaluationSummary from "../Monitor/MonitorEvaluationSummary";
|
|
10
10
|
import ObjectID from "../ObjectID";
|
|
11
11
|
import Port from "../Port";
|
|
12
|
+
import RequestFailedDetails from "./RequestFailedDetails";
|
|
12
13
|
|
|
13
14
|
export default interface ProbeMonitorResponse {
|
|
14
15
|
projectId: ObjectID;
|
|
@@ -23,6 +24,7 @@ export default interface ProbeMonitorResponse {
|
|
|
23
24
|
monitorId: ObjectID;
|
|
24
25
|
probeId: ObjectID;
|
|
25
26
|
failureCause: string;
|
|
27
|
+
requestFailedDetails?: RequestFailedDetails | undefined;
|
|
26
28
|
sslResponse?: SslMonitorResponse | undefined;
|
|
27
29
|
syntheticMonitorResponse?: Array<SyntheticMonitorResponse> | undefined;
|
|
28
30
|
customCodeMonitorResponse?: CustomCodeMonitorResponse | undefined;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This type holds detailed error context for when a request fails
|
|
3
|
+
* This helps users understand exactly where and why a request failed
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export enum RequestFailedPhase {
|
|
7
|
+
// DNS resolution failed - could not resolve hostname to IP
|
|
8
|
+
DNSResolution = "DNS Resolution",
|
|
9
|
+
// TCP connection failed - could not establish connection to server
|
|
10
|
+
TCPConnection = "TCP Connection",
|
|
11
|
+
// TLS/SSL handshake failed
|
|
12
|
+
TLSHandshake = "TLS Handshake",
|
|
13
|
+
// Request was sent but timed out waiting for response
|
|
14
|
+
RequestTimeout = "Request Timeout",
|
|
15
|
+
// Server responded with an error status code
|
|
16
|
+
ServerResponse = "Server Response",
|
|
17
|
+
// Request was aborted
|
|
18
|
+
RequestAborted = "Request Aborted",
|
|
19
|
+
// Network error - general network failure
|
|
20
|
+
NetworkError = "Network Error",
|
|
21
|
+
// Certificate error
|
|
22
|
+
CertificateError = "Certificate Error",
|
|
23
|
+
// Unknown error
|
|
24
|
+
Unknown = "Unknown",
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default interface RequestFailedDetails {
|
|
28
|
+
// The phase at which the request failed
|
|
29
|
+
failedPhase: RequestFailedPhase;
|
|
30
|
+
// The error code from axios/node (e.g., ECONNREFUSED, ETIMEDOUT, ENOTFOUND, etc.)
|
|
31
|
+
errorCode?: string | undefined;
|
|
32
|
+
// A detailed, user-friendly explanation of what went wrong
|
|
33
|
+
errorDescription: string;
|
|
34
|
+
// The raw error message (for debugging purposes)
|
|
35
|
+
rawErrorMessage?: string | undefined;
|
|
36
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { BarChart } from "../ChartLibrary/BarChart/BarChart";
|
|
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 ChartDataPoint from "../ChartLibrary/Types/ChartDataPoint";
|
|
7
|
+
import DataPointUtil from "../Utils/DataPoint";
|
|
8
|
+
|
|
9
|
+
export interface ComponentProps {
|
|
10
|
+
data: Array<SeriesPoint>;
|
|
11
|
+
xAxis: XAxis;
|
|
12
|
+
yAxis: YAxis;
|
|
13
|
+
sync: boolean;
|
|
14
|
+
heightInPx?: number | undefined;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface BarInternalProps extends ComponentProps {
|
|
18
|
+
syncid: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const BarChartElement: FunctionComponent<BarInternalProps> = (
|
|
22
|
+
props: BarInternalProps,
|
|
23
|
+
): ReactElement => {
|
|
24
|
+
const [records, setRecords] = React.useState<Array<ChartDataPoint>>([]);
|
|
25
|
+
|
|
26
|
+
const categories: Array<string> = props.data.map((item: SeriesPoint) => {
|
|
27
|
+
return item.seriesName;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (!props.data || props.data.length === 0) {
|
|
32
|
+
setRecords([]);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const records: Array<ChartDataPoint> = DataPointUtil.getChartDataPoints({
|
|
36
|
+
seriesPoints: props.data,
|
|
37
|
+
xAxis: props.xAxis,
|
|
38
|
+
yAxis: props.yAxis,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
setRecords(records);
|
|
42
|
+
}, [props.data]);
|
|
43
|
+
|
|
44
|
+
const className: string = props.heightInPx ? `` : "h-80";
|
|
45
|
+
const style: React.CSSProperties = props.heightInPx
|
|
46
|
+
? { height: `${props.heightInPx}px` }
|
|
47
|
+
: {};
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<BarChart
|
|
51
|
+
className={className}
|
|
52
|
+
style={style}
|
|
53
|
+
data={records}
|
|
54
|
+
tickGap={1}
|
|
55
|
+
index={"Time"}
|
|
56
|
+
categories={categories}
|
|
57
|
+
colors={[
|
|
58
|
+
"indigo",
|
|
59
|
+
"rose",
|
|
60
|
+
"emerald",
|
|
61
|
+
"amber",
|
|
62
|
+
"cyan",
|
|
63
|
+
"gray",
|
|
64
|
+
"pink",
|
|
65
|
+
"lime",
|
|
66
|
+
"fuchsia",
|
|
67
|
+
]}
|
|
68
|
+
valueFormatter={props.yAxis.options.formatter || undefined}
|
|
69
|
+
showTooltip={true}
|
|
70
|
+
yAxisWidth={60}
|
|
71
|
+
syncid={props.sync ? props.syncid : undefined}
|
|
72
|
+
/>
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export default BarChartElement;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import Text from "../../../../Types/Text";
|
|
2
2
|
import LineChart, { ComponentProps as LineChartProps } from "../Line/LineChart";
|
|
3
|
+
import BarChartElement, {
|
|
4
|
+
ComponentProps as BarChartProps,
|
|
5
|
+
} from "../Bar/BarChart";
|
|
3
6
|
import React, { FunctionComponent, ReactElement } from "react";
|
|
4
7
|
|
|
5
8
|
export enum ChartType {
|
|
@@ -13,7 +16,7 @@ export interface Chart {
|
|
|
13
16
|
title: string;
|
|
14
17
|
description?: string | undefined;
|
|
15
18
|
type: ChartType;
|
|
16
|
-
props: LineChartProps;
|
|
19
|
+
props: LineChartProps | BarChartProps;
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
export interface ComponentProps {
|
|
@@ -55,7 +58,36 @@ const ChartGroup: FunctionComponent<ComponentProps> = (
|
|
|
55
58
|
)}
|
|
56
59
|
<LineChart
|
|
57
60
|
key={index}
|
|
58
|
-
{...chart.props}
|
|
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 || ""}`}
|
|
72
|
+
>
|
|
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)}
|
|
59
91
|
syncid={syncId}
|
|
60
92
|
heightInPx={props.heightInPx}
|
|
61
93
|
/>
|
|
@@ -82,12 +82,42 @@ const renderShape: (
|
|
|
82
82
|
width = Math.abs(width); // width must be a positive number
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
// Radius for rounded corners at top
|
|
86
|
+
const radius: number = Math.min(4, width / 2, height / 2);
|
|
87
|
+
|
|
88
|
+
/*
|
|
89
|
+
* Create path with rounded corners at the top only (for horizontal layout)
|
|
90
|
+
* For vertical layout, round the right side
|
|
91
|
+
*/
|
|
92
|
+
let path: string;
|
|
93
|
+
|
|
94
|
+
if (layout === "horizontal") {
|
|
95
|
+
// Rounded top corners for horizontal bars
|
|
96
|
+
path = `
|
|
97
|
+
M ${x},${y + height}
|
|
98
|
+
L ${x},${y + radius}
|
|
99
|
+
Q ${x},${y} ${x + radius},${y}
|
|
100
|
+
L ${x + width - radius},${y}
|
|
101
|
+
Q ${x + width},${y} ${x + width},${y + radius}
|
|
102
|
+
L ${x + width},${y + height}
|
|
103
|
+
Z
|
|
104
|
+
`;
|
|
105
|
+
} else {
|
|
106
|
+
// Rounded right corners for vertical bars
|
|
107
|
+
path = `
|
|
108
|
+
M ${x},${y}
|
|
109
|
+
L ${x + width - radius},${y}
|
|
110
|
+
Q ${x + width},${y} ${x + width},${y + radius}
|
|
111
|
+
L ${x + width},${y + height - radius}
|
|
112
|
+
Q ${x + width},${y + height} ${x + width - radius},${y + height}
|
|
113
|
+
L ${x},${y + height}
|
|
114
|
+
Z
|
|
115
|
+
`;
|
|
116
|
+
}
|
|
117
|
+
|
|
85
118
|
return (
|
|
86
|
-
<
|
|
87
|
-
|
|
88
|
-
y={y}
|
|
89
|
-
width={width}
|
|
90
|
-
height={height}
|
|
119
|
+
<path
|
|
120
|
+
d={path}
|
|
91
121
|
opacity={
|
|
92
122
|
activeBar || (activeLegend && activeLegend !== name)
|
|
93
123
|
? deepEqual(activeBar, { ...payload, value })
|
|
@@ -615,6 +645,7 @@ interface BarChartProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
615
645
|
legendPosition?: "left" | "center" | "right";
|
|
616
646
|
tooltipCallback?: (tooltipCallbackContent: TooltipProps) => void;
|
|
617
647
|
customTooltip?: React.ComponentType<TooltipProps>;
|
|
648
|
+
syncid?: string | undefined;
|
|
618
649
|
}
|
|
619
650
|
|
|
620
651
|
const BarChart: React.ForwardRefExoticComponent<
|
|
@@ -753,6 +784,7 @@ const BarChart: React.ForwardRefExoticComponent<
|
|
|
753
784
|
<ResponsiveContainer>
|
|
754
785
|
<RechartsBarChart
|
|
755
786
|
data={data}
|
|
787
|
+
syncId={props.syncid?.toString() || ""}
|
|
756
788
|
{...(hasOnValueChange && (activeLegend || activeBar)
|
|
757
789
|
? {
|
|
758
790
|
onClick: handleChartClick,
|