@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.
Files changed (36) hide show
  1. package/Server/Infrastructure/Queue.ts +20 -11
  2. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +36 -0
  3. package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +3 -0
  4. package/Types/Dashboard/DashboardComponents/DashboardChartComponent.ts +2 -0
  5. package/Types/Metrics/MetricQueryConfigData.ts +6 -0
  6. package/Types/Monitor/NetworkMonitor/NetworkPathTrace.ts +43 -0
  7. package/Types/Probe/ProbeMonitorResponse.ts +2 -0
  8. package/Types/Probe/RequestFailedDetails.ts +36 -0
  9. package/UI/Components/Charts/Bar/BarChart.tsx +76 -0
  10. package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +34 -2
  11. package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +37 -5
  12. package/Utils/API.ts +305 -2
  13. package/Utils/Dashboard/Components/DashboardChartComponent.ts +20 -0
  14. package/build/dist/Server/Infrastructure/Queue.js +14 -3
  15. package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
  16. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +20 -0
  17. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  18. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
  19. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
  20. package/build/dist/Types/Metrics/MetricQueryConfigData.js +5 -1
  21. package/build/dist/Types/Metrics/MetricQueryConfigData.js.map +1 -1
  22. package/build/dist/Types/Monitor/NetworkMonitor/NetworkPathTrace.js +2 -0
  23. package/build/dist/Types/Monitor/NetworkMonitor/NetworkPathTrace.js.map +1 -0
  24. package/build/dist/Types/Probe/RequestFailedDetails.js +26 -0
  25. package/build/dist/Types/Probe/RequestFailedDetails.js.map +1 -0
  26. package/build/dist/UI/Components/Charts/Bar/BarChart.js +37 -0
  27. package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -0
  28. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +6 -0
  29. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
  30. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +38 -7
  31. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
  32. package/build/dist/Utils/API.js +249 -2
  33. package/build/dist/Utils/API.js.map +1 -1
  34. package/build/dist/Utils/Dashboard/Components/DashboardChartComponent.js +19 -0
  35. package/build/dist/Utils/Dashboard/Components/DashboardChartComponent.js.map +1 -1
  36. 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 this.getQueue(queueName).removeRepeatableByKey(
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 this.getQueue(queueName).add(
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
- <rect
87
- x={x}
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,