@oneuptime/common 8.0.5414 → 8.0.5438
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/Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel.ts +22 -0
- package/Models/AnalyticsModels/ExceptionInstance.ts +341 -323
- package/Models/AnalyticsModels/Log.ts +278 -231
- package/Models/AnalyticsModels/Metric.ts +504 -446
- package/Models/AnalyticsModels/MonitorLog.ts +99 -93
- package/Models/AnalyticsModels/Span.ts +473 -417
- package/Server/Services/AlertService.ts +12 -0
- package/Server/Services/IncidentService.ts +12 -0
- package/Server/Services/OpenTelemetryIngestService.ts +4 -0
- package/Server/Services/TelemetryAttributeService.ts +21 -4
- package/Server/Utils/Monitor/MonitorResource.ts +24 -0
- package/Server/Utils/Telemetry/Telemetry.ts +13 -0
- package/Types/AnalyticsDatabase/MaterializedView.ts +4 -0
- package/Types/AnalyticsDatabase/Projection.ts +4 -0
- package/Types/Date.ts +108 -0
- package/UI/Components/AutocompleteTextInput/AutocompleteTextInput.tsx +250 -0
- package/UI/Components/Dictionary/Dictionary.tsx +53 -66
- package/UI/Components/Filters/JSONFilter.tsx +2 -2
- package/UI/Components/Forms/Fields/FormField.tsx +2 -2
- package/UI/Components/GanttChart/Bar/Index.tsx +13 -0
- package/UI/Components/GanttChart/Index.tsx +7 -1
- package/UI/Components/GanttChart/Row/Index.tsx +1 -0
- package/UI/Components/GanttChart/Row/Row.tsx +101 -10
- package/UI/Components/GanttChart/Row/RowLabel.tsx +7 -2
- package/UI/Components/GanttChart/Rows.tsx +7 -1
- package/UI/Components/LogsViewer/LogItem.tsx +149 -10
- package/UI/Components/LogsViewer/LogsViewer.tsx +25 -1
- package/build/dist/Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel.js +16 -0
- package/build/dist/Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/ExceptionInstance.js +325 -310
- package/build/dist/Models/AnalyticsModels/ExceptionInstance.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Log.js +263 -222
- package/build/dist/Models/AnalyticsModels/Log.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Metric.js +477 -427
- package/build/dist/Models/AnalyticsModels/Metric.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/MonitorLog.js +95 -90
- package/build/dist/Models/AnalyticsModels/MonitorLog.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Span.js +449 -400
- package/build/dist/Models/AnalyticsModels/Span.js.map +1 -1
- package/build/dist/Server/Services/AlertService.js +4 -0
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +4 -0
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/OpenTelemetryIngestService.js +1 -0
- package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryAttributeService.js +19 -4
- package/build/dist/Server/Services/TelemetryAttributeService.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +12 -0
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/Telemetry.js +6 -0
- package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
- package/build/dist/Types/AnalyticsDatabase/MaterializedView.js +2 -0
- package/build/dist/Types/AnalyticsDatabase/MaterializedView.js.map +1 -0
- package/build/dist/Types/AnalyticsDatabase/Projection.js +2 -0
- package/build/dist/Types/AnalyticsDatabase/Projection.js.map +1 -0
- package/build/dist/Types/Date.js +82 -0
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js +143 -0
- package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js.map +1 -0
- package/build/dist/UI/Components/Dictionary/Dictionary.js +25 -36
- package/build/dist/UI/Components/Dictionary/Dictionary.js.map +1 -1
- package/build/dist/UI/Components/Filters/JSONFilter.js +2 -2
- package/build/dist/UI/Components/Filters/JSONFilter.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js +2 -2
- package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
- package/build/dist/UI/Components/GanttChart/Bar/Index.js +10 -0
- package/build/dist/UI/Components/GanttChart/Bar/Index.js.map +1 -1
- package/build/dist/UI/Components/GanttChart/Index.js +6 -2
- package/build/dist/UI/Components/GanttChart/Index.js.map +1 -1
- package/build/dist/UI/Components/GanttChart/Row/Index.js.map +1 -1
- package/build/dist/UI/Components/GanttChart/Row/Row.js +62 -9
- package/build/dist/UI/Components/GanttChart/Row/Row.js.map +1 -1
- package/build/dist/UI/Components/GanttChart/Row/RowLabel.js +2 -2
- package/build/dist/UI/Components/GanttChart/Row/RowLabel.js.map +1 -1
- package/build/dist/UI/Components/GanttChart/Rows.js +5 -1
- package/build/dist/UI/Components/GanttChart/Rows.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogItem.js +73 -5
- package/build/dist/UI/Components/LogsViewer/LogItem.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +7 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/package.json +1 -1
|
@@ -1155,6 +1155,9 @@ ${alertSeverity.name}
|
|
|
1155
1155
|
alertSeverityId: alert.alertSeverity?._id?.toString(),
|
|
1156
1156
|
alertSeverityName: alert.alertSeverity?.name?.toString(),
|
|
1157
1157
|
};
|
|
1158
|
+
alertCountMetric.attributeKeys = TelemetryUtil.getAttributeKeys(
|
|
1159
|
+
alertCountMetric.attributes,
|
|
1160
|
+
);
|
|
1158
1161
|
|
|
1159
1162
|
alertCountMetric.time = alertStartsAt;
|
|
1160
1163
|
alertCountMetric.timeUnixNano = OneUptimeDate.toUnixNano(
|
|
@@ -1203,6 +1206,9 @@ ${alertSeverity.name}
|
|
|
1203
1206
|
alertSeverityId: alert.alertSeverity?._id?.toString(),
|
|
1204
1207
|
alertSeverityName: alert.alertSeverity?.name?.toString(),
|
|
1205
1208
|
};
|
|
1209
|
+
timeToAcknowledgeMetric.attributeKeys = TelemetryUtil.getAttributeKeys(
|
|
1210
|
+
timeToAcknowledgeMetric.attributes,
|
|
1211
|
+
);
|
|
1206
1212
|
|
|
1207
1213
|
timeToAcknowledgeMetric.time =
|
|
1208
1214
|
ackAlertStateTimeline?.startsAt ||
|
|
@@ -1256,6 +1262,9 @@ ${alertSeverity.name}
|
|
|
1256
1262
|
alertSeverityId: alert.alertSeverity?._id?.toString(),
|
|
1257
1263
|
alertSeverityName: alert.alertSeverity?.name?.toString(),
|
|
1258
1264
|
};
|
|
1265
|
+
timeToResolveMetric.attributeKeys = TelemetryUtil.getAttributeKeys(
|
|
1266
|
+
timeToResolveMetric.attributes,
|
|
1267
|
+
);
|
|
1259
1268
|
|
|
1260
1269
|
timeToResolveMetric.time =
|
|
1261
1270
|
resolvedAlertStateTimeline?.startsAt ||
|
|
@@ -1302,6 +1311,9 @@ ${alertSeverity.name}
|
|
|
1302
1311
|
alertSeverityId: alert.alertSeverity?._id?.toString(),
|
|
1303
1312
|
alertSeverityName: alert.alertSeverity?.name?.toString(),
|
|
1304
1313
|
};
|
|
1314
|
+
alertDurationMetric.attributeKeys = TelemetryUtil.getAttributeKeys(
|
|
1315
|
+
alertDurationMetric.attributes,
|
|
1316
|
+
);
|
|
1305
1317
|
|
|
1306
1318
|
alertDurationMetric.time =
|
|
1307
1319
|
lastAlertStateTimeline?.startsAt ||
|
|
@@ -1956,6 +1956,9 @@ ${incidentSeverity.name}
|
|
|
1956
1956
|
incidentSeverityId: incident.incidentSeverity?._id?.toString(),
|
|
1957
1957
|
incidentSeverityName: incident.incidentSeverity?.name?.toString(),
|
|
1958
1958
|
};
|
|
1959
|
+
incidentCountMetric.attributeKeys = TelemetryUtil.getAttributeKeys(
|
|
1960
|
+
incidentCountMetric.attributes,
|
|
1961
|
+
);
|
|
1959
1962
|
|
|
1960
1963
|
incidentCountMetric.time = incidentStartsAt;
|
|
1961
1964
|
incidentCountMetric.timeUnixNano = OneUptimeDate.toUnixNano(
|
|
@@ -2013,6 +2016,9 @@ ${incidentSeverity.name}
|
|
|
2013
2016
|
incidentSeverityId: incident.incidentSeverity?._id?.toString(),
|
|
2014
2017
|
incidentSeverityName: incident.incidentSeverity?.name?.toString(),
|
|
2015
2018
|
};
|
|
2019
|
+
timeToAcknowledgeMetric.attributeKeys = TelemetryUtil.getAttributeKeys(
|
|
2020
|
+
timeToAcknowledgeMetric.attributes,
|
|
2021
|
+
);
|
|
2016
2022
|
|
|
2017
2023
|
timeToAcknowledgeMetric.time =
|
|
2018
2024
|
ackIncidentStateTimeline?.startsAt ||
|
|
@@ -2075,6 +2081,9 @@ ${incidentSeverity.name}
|
|
|
2075
2081
|
incidentSeverityId: incident.incidentSeverity?._id?.toString(),
|
|
2076
2082
|
incidentSeverityName: incident.incidentSeverity?.name?.toString(),
|
|
2077
2083
|
};
|
|
2084
|
+
timeToResolveMetric.attributeKeys = TelemetryUtil.getAttributeKeys(
|
|
2085
|
+
timeToResolveMetric.attributes,
|
|
2086
|
+
);
|
|
2078
2087
|
|
|
2079
2088
|
timeToResolveMetric.time =
|
|
2080
2089
|
resolvedIncidentStateTimeline?.startsAt ||
|
|
@@ -2132,6 +2141,9 @@ ${incidentSeverity.name}
|
|
|
2132
2141
|
incidentSeverityId: incident.incidentSeverity?._id?.toString(),
|
|
2133
2142
|
incidentSeverityName: incident.incidentSeverity?.name?.toString(),
|
|
2134
2143
|
};
|
|
2144
|
+
incidentDurationMetric.attributeKeys = TelemetryUtil.getAttributeKeys(
|
|
2145
|
+
incidentDurationMetric.attributes,
|
|
2146
|
+
);
|
|
2135
2147
|
|
|
2136
2148
|
incidentDurationMetric.time =
|
|
2137
2149
|
lastIncidentStateTimeline?.startsAt ||
|
|
@@ -18,6 +18,7 @@ type TelemetrySource = {
|
|
|
18
18
|
service: AnalyticsDatabaseService<any>;
|
|
19
19
|
tableName: string;
|
|
20
20
|
attributesColumn: string;
|
|
21
|
+
attributeKeysColumn: string;
|
|
21
22
|
timeColumn: string;
|
|
22
23
|
};
|
|
23
24
|
|
|
@@ -42,6 +43,7 @@ export class TelemetryAttributeService {
|
|
|
42
43
|
service: LogDatabaseService,
|
|
43
44
|
tableName: LogDatabaseService.model.tableName,
|
|
44
45
|
attributesColumn: "attributes",
|
|
46
|
+
attributeKeysColumn: "attributeKeys",
|
|
45
47
|
timeColumn: "time",
|
|
46
48
|
};
|
|
47
49
|
case TelemetryType.Metric:
|
|
@@ -49,6 +51,7 @@ export class TelemetryAttributeService {
|
|
|
49
51
|
service: MetricDatabaseService,
|
|
50
52
|
tableName: MetricDatabaseService.model.tableName,
|
|
51
53
|
attributesColumn: "attributes",
|
|
54
|
+
attributeKeysColumn: "attributeKeys",
|
|
52
55
|
timeColumn: "time",
|
|
53
56
|
};
|
|
54
57
|
case TelemetryType.Trace:
|
|
@@ -56,6 +59,7 @@ export class TelemetryAttributeService {
|
|
|
56
59
|
service: SpanDatabaseService,
|
|
57
60
|
tableName: SpanDatabaseService.model.tableName,
|
|
58
61
|
attributesColumn: "attributes",
|
|
62
|
+
attributeKeysColumn: "attributeKeys",
|
|
59
63
|
timeColumn: "startTime",
|
|
60
64
|
};
|
|
61
65
|
default:
|
|
@@ -213,6 +217,7 @@ export class TelemetryAttributeService {
|
|
|
213
217
|
projectId: ObjectID;
|
|
214
218
|
tableName: string;
|
|
215
219
|
attributesColumn: string;
|
|
220
|
+
attributeKeysColumn: string;
|
|
216
221
|
timeColumn: string;
|
|
217
222
|
}): Statement {
|
|
218
223
|
const lookbackStartDate: Date =
|
|
@@ -220,14 +225,24 @@ export class TelemetryAttributeService {
|
|
|
220
225
|
|
|
221
226
|
const statement: Statement = SQL`
|
|
222
227
|
WITH filtered AS (
|
|
223
|
-
SELECT
|
|
228
|
+
SELECT arrayJoin(
|
|
229
|
+
if(
|
|
230
|
+
${data.attributeKeysColumn} IS NULL OR empty(${data.attributeKeysColumn}),
|
|
231
|
+
JSONExtractKeys(${data.attributesColumn}),
|
|
232
|
+
${data.attributeKeysColumn}
|
|
233
|
+
)
|
|
234
|
+
) AS attribute
|
|
224
235
|
FROM ${data.tableName}
|
|
225
236
|
WHERE projectId = ${{
|
|
226
237
|
type: TableColumnType.ObjectID,
|
|
227
238
|
value: data.projectId,
|
|
228
239
|
}}
|
|
229
|
-
AND
|
|
230
|
-
|
|
240
|
+
AND (
|
|
241
|
+
${data.attributeKeysColumn} IS NOT NULL OR (
|
|
242
|
+
${data.attributesColumn} IS NOT NULL AND
|
|
243
|
+
${data.attributesColumn} != ''
|
|
244
|
+
)
|
|
245
|
+
)
|
|
231
246
|
AND ${data.timeColumn} >= ${{
|
|
232
247
|
type: TableColumnType.Date,
|
|
233
248
|
value: lookbackStartDate,
|
|
@@ -238,8 +253,9 @@ export class TelemetryAttributeService {
|
|
|
238
253
|
value: TelemetryAttributeService.ROW_SCAN_LIMIT,
|
|
239
254
|
}}
|
|
240
255
|
)
|
|
241
|
-
SELECT DISTINCT
|
|
256
|
+
SELECT DISTINCT attribute
|
|
242
257
|
FROM filtered
|
|
258
|
+
WHERE attribute IS NOT NULL AND attribute != ''
|
|
243
259
|
ORDER BY attribute ASC
|
|
244
260
|
LIMIT ${{
|
|
245
261
|
type: TableColumnType.Number,
|
|
@@ -259,6 +275,7 @@ export class TelemetryAttributeService {
|
|
|
259
275
|
projectId: data.projectId,
|
|
260
276
|
tableName: data.source.tableName,
|
|
261
277
|
attributesColumn: data.source.attributesColumn,
|
|
278
|
+
attributeKeysColumn: data.source.attributeKeysColumn,
|
|
262
279
|
timeColumn: data.source.timeColumn,
|
|
263
280
|
});
|
|
264
281
|
|
|
@@ -61,6 +61,12 @@ import MonitorLogService from "../../Services/MonitorLogService";
|
|
|
61
61
|
import ExceptionMessages from "../../../Types/Exception/ExceptionMessages";
|
|
62
62
|
|
|
63
63
|
export default class MonitorResourceUtil {
|
|
64
|
+
private static setAttributeKeys(metric: Metric): void {
|
|
65
|
+
metric.attributeKeys = TelemetryUtil.getAttributeKeys(
|
|
66
|
+
metric.attributes as JSONObject,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
64
70
|
@CaptureSpan()
|
|
65
71
|
public static async monitorResource(
|
|
66
72
|
dataToProcess: DataToProcess,
|
|
@@ -683,6 +689,8 @@ export default class MonitorResourceUtil {
|
|
|
683
689
|
monitorMetric.attributes["probeName"] = data.probeName.toString();
|
|
684
690
|
}
|
|
685
691
|
|
|
692
|
+
MonitorResourceUtil.setAttributeKeys(monitorMetric);
|
|
693
|
+
|
|
686
694
|
monitorMetric.time = OneUptimeDate.getCurrentDate();
|
|
687
695
|
monitorMetric.timeUnixNano = OneUptimeDate.getCurrentDateAsUnixNano();
|
|
688
696
|
monitorMetric.metricPointType = MetricPointType.Sum;
|
|
@@ -730,6 +738,8 @@ export default class MonitorResourceUtil {
|
|
|
730
738
|
monitorMetric.attributes["probeName"] = data.probeName.toString();
|
|
731
739
|
}
|
|
732
740
|
|
|
741
|
+
MonitorResourceUtil.setAttributeKeys(monitorMetric);
|
|
742
|
+
|
|
733
743
|
monitorMetric.time = OneUptimeDate.getCurrentDate();
|
|
734
744
|
monitorMetric.timeUnixNano = OneUptimeDate.getCurrentDateAsUnixNano();
|
|
735
745
|
monitorMetric.metricPointType = MetricPointType.Sum;
|
|
@@ -768,6 +778,8 @@ export default class MonitorResourceUtil {
|
|
|
768
778
|
monitorMetric.attributes["probeName"] = data.probeName.toString();
|
|
769
779
|
}
|
|
770
780
|
|
|
781
|
+
MonitorResourceUtil.setAttributeKeys(monitorMetric);
|
|
782
|
+
|
|
771
783
|
monitorMetric.time = OneUptimeDate.getCurrentDate();
|
|
772
784
|
monitorMetric.timeUnixNano = OneUptimeDate.getCurrentDateAsUnixNano();
|
|
773
785
|
monitorMetric.metricPointType = MetricPointType.Sum;
|
|
@@ -809,6 +821,8 @@ export default class MonitorResourceUtil {
|
|
|
809
821
|
monitorMetric.attributes["probeName"] = data.probeName.toString();
|
|
810
822
|
}
|
|
811
823
|
|
|
824
|
+
MonitorResourceUtil.setAttributeKeys(monitorMetric);
|
|
825
|
+
|
|
812
826
|
monitorMetric.time = OneUptimeDate.getCurrentDate();
|
|
813
827
|
monitorMetric.timeUnixNano = OneUptimeDate.getCurrentDateAsUnixNano();
|
|
814
828
|
monitorMetric.metricPointType = MetricPointType.Sum;
|
|
@@ -857,6 +871,8 @@ export default class MonitorResourceUtil {
|
|
|
857
871
|
monitorMetric.attributes["probeName"] = data.probeName.toString();
|
|
858
872
|
}
|
|
859
873
|
|
|
874
|
+
MonitorResourceUtil.setAttributeKeys(monitorMetric);
|
|
875
|
+
|
|
860
876
|
monitorMetric.time = OneUptimeDate.getCurrentDate();
|
|
861
877
|
monitorMetric.timeUnixNano = OneUptimeDate.getCurrentDateAsUnixNano();
|
|
862
878
|
monitorMetric.metricPointType = MetricPointType.Sum;
|
|
@@ -909,6 +925,8 @@ export default class MonitorResourceUtil {
|
|
|
909
925
|
monitorMetric.attributes["probeName"] = data.probeName.toString();
|
|
910
926
|
}
|
|
911
927
|
|
|
928
|
+
MonitorResourceUtil.setAttributeKeys(monitorMetric);
|
|
929
|
+
|
|
912
930
|
monitorMetric.time = OneUptimeDate.getCurrentDate();
|
|
913
931
|
monitorMetric.timeUnixNano = OneUptimeDate.getCurrentDateAsUnixNano();
|
|
914
932
|
monitorMetric.metricPointType = MetricPointType.Sum;
|
|
@@ -952,6 +970,8 @@ export default class MonitorResourceUtil {
|
|
|
952
970
|
monitorMetric.attributes["probeName"] = data.probeName.toString();
|
|
953
971
|
}
|
|
954
972
|
|
|
973
|
+
MonitorResourceUtil.setAttributeKeys(monitorMetric);
|
|
974
|
+
|
|
955
975
|
monitorMetric.time = OneUptimeDate.getCurrentDate();
|
|
956
976
|
monitorMetric.timeUnixNano = OneUptimeDate.getCurrentDateAsUnixNano();
|
|
957
977
|
monitorMetric.metricPointType = MetricPointType.Sum;
|
|
@@ -995,6 +1015,8 @@ export default class MonitorResourceUtil {
|
|
|
995
1015
|
monitorMetric.attributes["probeName"] = data.probeName.toString();
|
|
996
1016
|
}
|
|
997
1017
|
|
|
1018
|
+
MonitorResourceUtil.setAttributeKeys(monitorMetric);
|
|
1019
|
+
|
|
998
1020
|
monitorMetric.time = OneUptimeDate.getCurrentDate();
|
|
999
1021
|
monitorMetric.timeUnixNano = OneUptimeDate.getCurrentDateAsUnixNano();
|
|
1000
1022
|
monitorMetric.metricPointType = MetricPointType.Sum;
|
|
@@ -1029,6 +1051,8 @@ export default class MonitorResourceUtil {
|
|
|
1029
1051
|
).probeId.toString(),
|
|
1030
1052
|
};
|
|
1031
1053
|
|
|
1054
|
+
MonitorResourceUtil.setAttributeKeys(monitorMetric);
|
|
1055
|
+
|
|
1032
1056
|
monitorMetric.time = OneUptimeDate.getCurrentDate();
|
|
1033
1057
|
monitorMetric.timeUnixNano = OneUptimeDate.getCurrentDateAsUnixNano();
|
|
1034
1058
|
monitorMetric.metricPointType = MetricPointType.Sum;
|
|
@@ -252,4 +252,17 @@ export default class TelemetryUtil {
|
|
|
252
252
|
|
|
253
253
|
return finalObj;
|
|
254
254
|
}
|
|
255
|
+
|
|
256
|
+
public static getAttributeKeys(
|
|
257
|
+
attributes:
|
|
258
|
+
| Dictionary<AttributeType | Array<AttributeType> | JSONObject>
|
|
259
|
+
| JSONObject
|
|
260
|
+
| undefined,
|
|
261
|
+
): Array<string> {
|
|
262
|
+
if (!attributes || typeof attributes !== "object") {
|
|
263
|
+
return [];
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return Object.keys(attributes).sort();
|
|
267
|
+
}
|
|
255
268
|
}
|
package/Types/Date.ts
CHANGED
|
@@ -1076,6 +1076,114 @@ export default class OneUptimeDate {
|
|
|
1076
1076
|
return formattedString;
|
|
1077
1077
|
}
|
|
1078
1078
|
|
|
1079
|
+
public static getHumanizedDurationFromNanoseconds(data: {
|
|
1080
|
+
nanoseconds: number;
|
|
1081
|
+
maxParts?: number;
|
|
1082
|
+
}): string {
|
|
1083
|
+
let { nanoseconds } = data;
|
|
1084
|
+
const maxParts: number = data.maxParts ?? 2;
|
|
1085
|
+
|
|
1086
|
+
if (!Number.isFinite(nanoseconds)) {
|
|
1087
|
+
return "-";
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
if (nanoseconds === 0) {
|
|
1091
|
+
return "0 ms";
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
const sign: string = nanoseconds < 0 ? "-" : "";
|
|
1095
|
+
nanoseconds = Math.abs(nanoseconds);
|
|
1096
|
+
|
|
1097
|
+
if (nanoseconds < 1000) {
|
|
1098
|
+
return sign + Math.round(nanoseconds).toString() + " ns";
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
const microseconds: number = nanoseconds / 1000;
|
|
1102
|
+
if (microseconds < 1000) {
|
|
1103
|
+
return sign + OneUptimeDate.formatDurationValue(microseconds) + " μs";
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
const milliseconds: number = nanoseconds / 1000000;
|
|
1107
|
+
if (milliseconds < 1000) {
|
|
1108
|
+
return sign + OneUptimeDate.formatDurationValue(milliseconds) + " ms";
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
const seconds: number = nanoseconds / 1000000000;
|
|
1112
|
+
|
|
1113
|
+
if (seconds < 60) {
|
|
1114
|
+
return sign + OneUptimeDate.formatDurationValue(seconds) + " s";
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
const units: Array<{ label: string; seconds: number }> = [
|
|
1118
|
+
{ label: "d", seconds: 86400 },
|
|
1119
|
+
{ label: "h", seconds: 3600 },
|
|
1120
|
+
{ label: "m", seconds: 60 },
|
|
1121
|
+
{ label: "s", seconds: 1 },
|
|
1122
|
+
];
|
|
1123
|
+
|
|
1124
|
+
let remainingSeconds: number = Math.floor(seconds);
|
|
1125
|
+
const parts: string[] = [];
|
|
1126
|
+
|
|
1127
|
+
for (const unit of units) {
|
|
1128
|
+
if (parts.length >= maxParts) {
|
|
1129
|
+
break;
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
if (remainingSeconds < unit.seconds) {
|
|
1133
|
+
continue;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
const value: number = Math.floor(remainingSeconds / unit.seconds);
|
|
1137
|
+
|
|
1138
|
+
if (value > 0) {
|
|
1139
|
+
parts.push(`${value}${unit.label}`);
|
|
1140
|
+
remainingSeconds -= value * unit.seconds;
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
const fractionalSeconds: number = seconds - Math.floor(seconds);
|
|
1145
|
+
const leftoverSeconds: number = remainingSeconds + fractionalSeconds;
|
|
1146
|
+
|
|
1147
|
+
if (leftoverSeconds > 0) {
|
|
1148
|
+
if (parts.length === 0) {
|
|
1149
|
+
parts.push(`${OneUptimeDate.formatDurationValue(leftoverSeconds)}s`);
|
|
1150
|
+
} else if (parts[parts.length - 1]?.endsWith("s")) {
|
|
1151
|
+
const numericValue: number = parseFloat(
|
|
1152
|
+
parts[parts.length - 1]!.slice(0, -1),
|
|
1153
|
+
);
|
|
1154
|
+
const updatedSeconds: number = numericValue + leftoverSeconds;
|
|
1155
|
+
parts[parts.length - 1] =
|
|
1156
|
+
`${OneUptimeDate.formatDurationValue(updatedSeconds)}s`;
|
|
1157
|
+
} else if (parts.length < maxParts) {
|
|
1158
|
+
parts.push(`${OneUptimeDate.formatDurationValue(leftoverSeconds)}s`);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
if (parts.length === 0) {
|
|
1163
|
+
return sign + OneUptimeDate.formatDurationValue(seconds) + " s";
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
const trimmedParts: string[] = parts.slice(0, maxParts);
|
|
1167
|
+
|
|
1168
|
+
if (sign) {
|
|
1169
|
+
trimmedParts[0] = sign + trimmedParts[0];
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
return trimmedParts.join(" ");
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
private static formatDurationValue(value: number): string {
|
|
1176
|
+
const abs: number = Math.abs(value);
|
|
1177
|
+
|
|
1178
|
+
if (abs >= 100) {
|
|
1179
|
+
return Math.round(value).toString();
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
const decimalPlaces: number = 2;
|
|
1183
|
+
const fixed: string = value.toFixed(decimalPlaces);
|
|
1184
|
+
return fixed.replace(/\.0+$/, "").replace(/(\.\d*?[1-9])0+$/, "$1");
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1079
1187
|
public static getGmtOffsetByTimezone(timezone: Timezone): number {
|
|
1080
1188
|
return moment.tz(timezone).utcOffset();
|
|
1081
1189
|
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
FunctionComponent,
|
|
3
|
+
ReactElement,
|
|
4
|
+
useEffect,
|
|
5
|
+
useMemo,
|
|
6
|
+
useRef,
|
|
7
|
+
useState,
|
|
8
|
+
} from "react";
|
|
9
|
+
|
|
10
|
+
export interface ComponentProps {
|
|
11
|
+
value?: string;
|
|
12
|
+
onChange?: ((value: string) => void) | undefined;
|
|
13
|
+
suggestions?: Array<string> | undefined;
|
|
14
|
+
placeholder?: string | undefined;
|
|
15
|
+
className?: string | undefined;
|
|
16
|
+
menuClassName?: string | undefined;
|
|
17
|
+
disabled?: boolean | undefined;
|
|
18
|
+
autoFocus?: boolean | undefined;
|
|
19
|
+
dataTestId?: string | undefined;
|
|
20
|
+
onFocus?: (() => void) | undefined;
|
|
21
|
+
onBlur?: (() => void) | undefined;
|
|
22
|
+
outerDivClassName?: string | undefined;
|
|
23
|
+
disableSpellCheck?: boolean | undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const BASE_INPUT_CLASS: string =
|
|
27
|
+
"block w-full rounded-md border border-gray-300 bg-white py-2 pl-3 pr-3 text-sm placeholder-gray-500 focus:border-indigo-500 focus:text-gray-900 focus:placeholder-gray-400 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm";
|
|
28
|
+
|
|
29
|
+
const MAX_SUGGESTIONS: number = 50;
|
|
30
|
+
|
|
31
|
+
// Provides a free-form text input with an optional suggestion dropdown.
|
|
32
|
+
const AutocompleteTextInput: FunctionComponent<ComponentProps> = (
|
|
33
|
+
props: ComponentProps,
|
|
34
|
+
): ReactElement => {
|
|
35
|
+
const [inputValue, setInputValue] = useState<string>(props.value || "");
|
|
36
|
+
const [isMenuVisible, setIsMenuVisible] = useState<boolean>(false);
|
|
37
|
+
const [highlightedIndex, setHighlightedIndex] = useState<number>(-1);
|
|
38
|
+
const containerRef: React.MutableRefObject<HTMLDivElement | null> =
|
|
39
|
+
useRef<HTMLDivElement | null>(null);
|
|
40
|
+
const blurTimeoutRef: React.MutableRefObject<number | null> = useRef<
|
|
41
|
+
number | null
|
|
42
|
+
>(null);
|
|
43
|
+
const listboxIdRef: React.MutableRefObject<string> = useRef<string>(
|
|
44
|
+
`autocomplete-suggestions-${Math.random().toString(36).slice(2, 10)}`,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
setInputValue(props.value || "");
|
|
49
|
+
}, [props.value]);
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
const handleClickOutside: (event: MouseEvent) => void = (
|
|
53
|
+
event: MouseEvent,
|
|
54
|
+
) => {
|
|
55
|
+
if (
|
|
56
|
+
containerRef.current &&
|
|
57
|
+
event.target instanceof Node &&
|
|
58
|
+
!containerRef.current.contains(event.target)
|
|
59
|
+
) {
|
|
60
|
+
setIsMenuVisible(false);
|
|
61
|
+
setHighlightedIndex(-1);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
66
|
+
return () => {
|
|
67
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
68
|
+
};
|
|
69
|
+
}, []);
|
|
70
|
+
|
|
71
|
+
const suggestions: Array<string> = useMemo(() => {
|
|
72
|
+
const uniqueSuggestions: Array<string> = props.suggestions
|
|
73
|
+
? Array.from(new Set(props.suggestions))
|
|
74
|
+
: [];
|
|
75
|
+
|
|
76
|
+
if (uniqueSuggestions.length === 0) {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const normalizedInput: string = inputValue.trim().toLowerCase();
|
|
81
|
+
|
|
82
|
+
if (normalizedInput === "") {
|
|
83
|
+
return uniqueSuggestions.slice(0, MAX_SUGGESTIONS);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return uniqueSuggestions
|
|
87
|
+
.filter((suggestion: string) => {
|
|
88
|
+
return suggestion.toLowerCase().includes(normalizedInput);
|
|
89
|
+
})
|
|
90
|
+
.slice(0, MAX_SUGGESTIONS);
|
|
91
|
+
}, [inputValue, props.suggestions]);
|
|
92
|
+
|
|
93
|
+
const showMenu: boolean = isMenuVisible && suggestions.length > 0;
|
|
94
|
+
|
|
95
|
+
const getInputClassName: () => string = (): string => {
|
|
96
|
+
let className: string = props.className || BASE_INPUT_CLASS;
|
|
97
|
+
|
|
98
|
+
if (props.disabled) {
|
|
99
|
+
className += " bg-gray-100 text-gray-500 cursor-not-allowed";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return className;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const clearBlurTimeout: () => void = (): void => {
|
|
106
|
+
if (blurTimeoutRef.current !== null) {
|
|
107
|
+
window.clearTimeout(blurTimeoutRef.current);
|
|
108
|
+
blurTimeoutRef.current = null;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const handleSuggestionSelect: (value: string) => void = (value: string) => {
|
|
113
|
+
clearBlurTimeout();
|
|
114
|
+
setInputValue(value);
|
|
115
|
+
props.onChange?.(value);
|
|
116
|
+
setIsMenuVisible(false);
|
|
117
|
+
setHighlightedIndex(-1);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const handleInputChange: (
|
|
121
|
+
event: React.ChangeEvent<HTMLInputElement>,
|
|
122
|
+
) => void = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
123
|
+
const value: string = event.target.value;
|
|
124
|
+
setInputValue(value);
|
|
125
|
+
props.onChange?.(value);
|
|
126
|
+
setIsMenuVisible(true);
|
|
127
|
+
setHighlightedIndex(-1);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const handleInputFocus: () => void = () => {
|
|
131
|
+
clearBlurTimeout();
|
|
132
|
+
setIsMenuVisible(true);
|
|
133
|
+
props.onFocus?.();
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const handleInputBlur: () => void = () => {
|
|
137
|
+
clearBlurTimeout();
|
|
138
|
+
blurTimeoutRef.current = window.setTimeout(() => {
|
|
139
|
+
setIsMenuVisible(false);
|
|
140
|
+
setHighlightedIndex(-1);
|
|
141
|
+
props.onBlur?.();
|
|
142
|
+
}, 150);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const handleKeyDown: (
|
|
146
|
+
event: React.KeyboardEvent<HTMLInputElement>,
|
|
147
|
+
) => void = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
148
|
+
if (!showMenu) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (event.key === "ArrowDown") {
|
|
153
|
+
event.preventDefault();
|
|
154
|
+
setHighlightedIndex((previousIndex: number) => {
|
|
155
|
+
const nextIndex: number = previousIndex + 1;
|
|
156
|
+
if (nextIndex >= suggestions.length) {
|
|
157
|
+
return 0;
|
|
158
|
+
}
|
|
159
|
+
return nextIndex;
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (event.key === "ArrowUp") {
|
|
164
|
+
event.preventDefault();
|
|
165
|
+
setHighlightedIndex((previousIndex: number) => {
|
|
166
|
+
if (previousIndex <= 0) {
|
|
167
|
+
return suggestions.length - 1;
|
|
168
|
+
}
|
|
169
|
+
return previousIndex - 1;
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (event.key === "Enter") {
|
|
174
|
+
if (highlightedIndex >= 0 && highlightedIndex < suggestions.length) {
|
|
175
|
+
event.preventDefault();
|
|
176
|
+
handleSuggestionSelect(suggestions[highlightedIndex]!);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (event.key === "Escape") {
|
|
181
|
+
setIsMenuVisible(false);
|
|
182
|
+
setHighlightedIndex(-1);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
useEffect(() => {
|
|
187
|
+
if (highlightedIndex >= suggestions.length) {
|
|
188
|
+
setHighlightedIndex(suggestions.length - 1);
|
|
189
|
+
}
|
|
190
|
+
}, [suggestions, highlightedIndex]);
|
|
191
|
+
|
|
192
|
+
return (
|
|
193
|
+
<div
|
|
194
|
+
ref={containerRef}
|
|
195
|
+
className={props.outerDivClassName || "relative mt-2 mb-1 w-full"}
|
|
196
|
+
>
|
|
197
|
+
<input
|
|
198
|
+
autoFocus={props.autoFocus}
|
|
199
|
+
className={getInputClassName()}
|
|
200
|
+
data-testid={props.dataTestId}
|
|
201
|
+
disabled={props.disabled}
|
|
202
|
+
aria-autocomplete="list"
|
|
203
|
+
aria-controls={listboxIdRef.current}
|
|
204
|
+
aria-expanded={showMenu}
|
|
205
|
+
role="combobox"
|
|
206
|
+
onBlur={handleInputBlur}
|
|
207
|
+
onChange={handleInputChange}
|
|
208
|
+
onFocus={handleInputFocus}
|
|
209
|
+
onKeyDown={handleKeyDown}
|
|
210
|
+
placeholder={props.placeholder}
|
|
211
|
+
spellCheck={!props.disableSpellCheck}
|
|
212
|
+
type="text"
|
|
213
|
+
value={inputValue}
|
|
214
|
+
/>
|
|
215
|
+
{showMenu && (
|
|
216
|
+
<div
|
|
217
|
+
className={
|
|
218
|
+
props.menuClassName ||
|
|
219
|
+
"absolute z-10 mt-1 max-h-56 w-full overflow-auto rounded-md border border-gray-200 bg-white py-1 text-sm shadow-lg"
|
|
220
|
+
}
|
|
221
|
+
id={listboxIdRef.current}
|
|
222
|
+
role="listbox"
|
|
223
|
+
>
|
|
224
|
+
{suggestions.map((suggestion: string, index: number) => {
|
|
225
|
+
const isActive: boolean = index === highlightedIndex;
|
|
226
|
+
return (
|
|
227
|
+
<button
|
|
228
|
+
key={`${suggestion}-${index}`}
|
|
229
|
+
type="button"
|
|
230
|
+
role="option"
|
|
231
|
+
aria-selected={isActive}
|
|
232
|
+
className={`flex w-full items-center px-3 py-2 text-left hover:bg-indigo-50 ${isActive ? "bg-indigo-600 text-white hover:bg-indigo-500" : "text-gray-700"}`}
|
|
233
|
+
onMouseDown={(event: React.MouseEvent<HTMLButtonElement>) => {
|
|
234
|
+
event.preventDefault();
|
|
235
|
+
}}
|
|
236
|
+
onClick={() => {
|
|
237
|
+
handleSuggestionSelect(suggestion);
|
|
238
|
+
}}
|
|
239
|
+
>
|
|
240
|
+
{suggestion}
|
|
241
|
+
</button>
|
|
242
|
+
);
|
|
243
|
+
})}
|
|
244
|
+
</div>
|
|
245
|
+
)}
|
|
246
|
+
</div>
|
|
247
|
+
);
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
export default AutocompleteTextInput;
|