@oneuptime/common 10.0.86 → 10.0.88
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/DatabaseModels/EnterpriseLicense.ts +54 -0
- package/Models/DatabaseModels/GlobalConfig.ts +51 -0
- package/Server/API/EnterpriseLicenseAPI.ts +83 -0
- package/Server/API/GlobalConfigAPI.ts +59 -0
- package/Server/API/TelemetryAPI.ts +24 -0
- package/Server/EnvironmentConfig.ts +10 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1777629313843-MigrationName.ts +59 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Infrastructure/Queue.ts +4 -4
- package/Server/Services/TelemetryAttributeService.ts +37 -3
- package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +174 -7
- package/Tests/Types/Date.test.ts +46 -0
- package/Types/Date.ts +9 -4
- package/UI/Components/AutocompleteTextInput/AutocompleteTextInput.tsx +60 -21
- package/UI/Components/Dictionary/Dictionary.tsx +188 -26
- package/UI/Components/Dictionary/DictionaryFilterOperator.ts +357 -0
- package/UI/Components/Dictionary/DictionaryOfStrings.tsx +12 -7
- package/UI/Components/EditionLabel/EditionLabel.tsx +224 -10
- package/UI/Components/Filters/FilterViewer.tsx +81 -16
- package/UI/Components/Filters/FiltersForm.tsx +18 -3
- package/UI/Components/Filters/JSONFilter.tsx +11 -2
- package/UI/Components/Filters/Types/Filter.ts +3 -0
- package/UI/Components/Forms/Fields/FormField.tsx +6 -1
- package/UI/Components/Forms/Types/Field.ts +5 -0
- package/UI/Components/LogsViewer/LogsViewer.tsx +73 -4
- package/UI/Components/LogsViewer/components/LogSearchBar.tsx +77 -31
- package/UI/Components/LogsViewer/components/LogSearchSuggestions.tsx +44 -1
- package/UI/Components/LogsViewer/components/LogsFilterCard.tsx +7 -5
- package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +6 -0
- package/UI/Components/TelemetryViewer/components/TelemetrySearchBar.tsx +84 -25
- package/UI/Components/TelemetryViewer/components/TelemetrySearchSuggestions.tsx +44 -1
- package/build/dist/Models/DatabaseModels/EnterpriseLicense.js +57 -0
- package/build/dist/Models/DatabaseModels/EnterpriseLicense.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalConfig.js +54 -0
- package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
- package/build/dist/Server/API/EnterpriseLicenseAPI.js +64 -1
- package/build/dist/Server/API/EnterpriseLicenseAPI.js.map +1 -1
- package/build/dist/Server/API/GlobalConfigAPI.js +47 -0
- package/build/dist/Server/API/GlobalConfigAPI.js.map +1 -1
- package/build/dist/Server/API/TelemetryAPI.js +9 -0
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +3 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1777629313843-MigrationName.js +26 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1777629313843-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/Queue.js +3 -3
- package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
- package/build/dist/Server/Services/TelemetryAttributeService.js +36 -7
- package/build/dist/Server/Services/TelemetryAttributeService.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +135 -5
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
- package/build/dist/Tests/Types/Date.test.js +40 -0
- package/build/dist/Tests/Types/Date.test.js.map +1 -1
- package/build/dist/Types/Date.js +7 -2
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js +21 -10
- package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js.map +1 -1
- package/build/dist/UI/Components/Dictionary/Dictionary.js +109 -16
- package/build/dist/UI/Components/Dictionary/Dictionary.js.map +1 -1
- package/build/dist/UI/Components/Dictionary/DictionaryFilterOperator.js +263 -0
- package/build/dist/UI/Components/Dictionary/DictionaryFilterOperator.js.map +1 -0
- package/build/dist/UI/Components/Dictionary/DictionaryOfStrings.js +10 -6
- package/build/dist/UI/Components/Dictionary/DictionaryOfStrings.js.map +1 -1
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js +124 -6
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
- package/build/dist/UI/Components/Filters/FilterViewer.js +50 -12
- package/build/dist/UI/Components/Filters/FilterViewer.js.map +1 -1
- package/build/dist/UI/Components/Filters/FiltersForm.js +5 -4
- package/build/dist/UI/Components/Filters/FiltersForm.js.map +1 -1
- package/build/dist/UI/Components/Filters/JSONFilter.js +1 -1
- package/build/dist/UI/Components/Filters/JSONFilter.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js +1 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +54 -5
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogSearchBar.js +59 -29
- package/build/dist/UI/Components/LogsViewer/components/LogSearchBar.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogSearchSuggestions.js +10 -2
- package/build/dist/UI/Components/LogsViewer/components/LogSearchSuggestions.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsFilterCard.js +2 -5
- package/build/dist/UI/Components/LogsViewer/components/LogsFilterCard.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +1 -1
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js +59 -22
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchSuggestions.js +10 -2
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchSuggestions.js.map +1 -1
- package/package.json +1 -1
|
@@ -15,6 +15,7 @@ import AnalyticsTableColumn, {
|
|
|
15
15
|
SkipIndexType,
|
|
16
16
|
} from "../../../Types/AnalyticsDatabase/TableColumn";
|
|
17
17
|
import TableColumnType from "../../../Types/AnalyticsDatabase/TableColumnType";
|
|
18
|
+
import EqualTo from "../../../Types/BaseDatabase/EqualTo";
|
|
18
19
|
import GreaterThan from "../../../Types/BaseDatabase/GreaterThan";
|
|
19
20
|
import GreaterThanOrEqual from "../../../Types/BaseDatabase/GreaterThanOrEqual";
|
|
20
21
|
import InBetween from "../../../Types/BaseDatabase/InBetween";
|
|
@@ -25,7 +26,11 @@ import LessThanOrEqual from "../../../Types/BaseDatabase/LessThanOrEqual";
|
|
|
25
26
|
import GreaterThanOrNull from "../../../Types/BaseDatabase/GreaterThanOrNull";
|
|
26
27
|
import LessThanOrNull from "../../../Types/BaseDatabase/LessThanOrNull";
|
|
27
28
|
import NotEqual from "../../../Types/BaseDatabase/NotEqual";
|
|
29
|
+
import NotContains from "../../../Types/BaseDatabase/NotContains";
|
|
30
|
+
import NotNull from "../../../Types/BaseDatabase/NotNull";
|
|
28
31
|
import Search from "../../../Types/BaseDatabase/Search";
|
|
32
|
+
import StartsWith from "../../../Types/BaseDatabase/StartsWith";
|
|
33
|
+
import EndsWith from "../../../Types/BaseDatabase/EndsWith";
|
|
29
34
|
import SortOrder from "../../../Types/BaseDatabase/SortOrder";
|
|
30
35
|
import OneUptimeDate from "../../../Types/Date";
|
|
31
36
|
import BadDataException from "../../../Types/Exception/BadDataException";
|
|
@@ -447,33 +452,195 @@ export default class StatementGenerator<TBaseModel extends AnalyticsBaseModel> {
|
|
|
447
452
|
tableColumn.type === TableColumnType.MapStringString &&
|
|
448
453
|
typeof value === "object"
|
|
449
454
|
) {
|
|
450
|
-
const mapValue: Record<string,
|
|
451
|
-
|
|
455
|
+
const mapValue: Record<string, unknown> = value as Record<
|
|
456
|
+
string,
|
|
457
|
+
unknown
|
|
458
|
+
>;
|
|
452
459
|
for (const mapKey in mapValue) {
|
|
453
|
-
|
|
460
|
+
const mapEntry: unknown = mapValue[mapKey];
|
|
461
|
+
if (mapEntry === undefined || mapEntry === null) {
|
|
454
462
|
continue;
|
|
455
463
|
}
|
|
456
|
-
|
|
464
|
+
|
|
465
|
+
/*
|
|
466
|
+
* ClickHouse Map columns return the value type's default for
|
|
467
|
+
* missing keys (empty string for String values), so to express
|
|
468
|
+
* "is empty" we have to cover both the missing-key and the
|
|
469
|
+
* empty-string case explicitly.
|
|
470
|
+
*/
|
|
471
|
+
if (mapEntry instanceof IsNull) {
|
|
472
|
+
whereStatement.append(
|
|
473
|
+
SQL`AND ((NOT mapContains(${key}, ${{
|
|
474
|
+
value: mapKey,
|
|
475
|
+
type: TableColumnType.Text,
|
|
476
|
+
}})) OR ${key}[${{
|
|
477
|
+
value: mapKey,
|
|
478
|
+
type: TableColumnType.Text,
|
|
479
|
+
}}] = '')`,
|
|
480
|
+
);
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (mapEntry instanceof NotNull) {
|
|
485
|
+
whereStatement.append(
|
|
486
|
+
SQL`AND mapContains(${key}, ${{
|
|
487
|
+
value: mapKey,
|
|
488
|
+
type: TableColumnType.Text,
|
|
489
|
+
}}) AND ${key}[${{
|
|
490
|
+
value: mapKey,
|
|
491
|
+
type: TableColumnType.Text,
|
|
492
|
+
}}] != ''`,
|
|
493
|
+
);
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (mapEntry instanceof Search) {
|
|
498
|
+
whereStatement.append(
|
|
499
|
+
SQL`AND ${key}[${{
|
|
500
|
+
value: mapKey,
|
|
501
|
+
type: TableColumnType.Text,
|
|
502
|
+
}}] ILIKE ${{
|
|
503
|
+
value: mapEntry as Search<string>,
|
|
504
|
+
type: TableColumnType.Text,
|
|
505
|
+
}}`,
|
|
506
|
+
);
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (mapEntry instanceof NotContains) {
|
|
511
|
+
const literalValue: string = `%${(mapEntry.value as string) || ""}%`;
|
|
512
|
+
whereStatement.append(
|
|
513
|
+
SQL`AND ${key}[${{
|
|
514
|
+
value: mapKey,
|
|
515
|
+
type: TableColumnType.Text,
|
|
516
|
+
}}] NOT ILIKE ${{
|
|
517
|
+
value: literalValue,
|
|
518
|
+
type: TableColumnType.Text,
|
|
519
|
+
}}`,
|
|
520
|
+
);
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
if (mapEntry instanceof StartsWith) {
|
|
525
|
+
const literalValue: string = `${(mapEntry.value as string) || ""}%`;
|
|
526
|
+
whereStatement.append(
|
|
527
|
+
SQL`AND ${key}[${{
|
|
528
|
+
value: mapKey,
|
|
529
|
+
type: TableColumnType.Text,
|
|
530
|
+
}}] ILIKE ${{
|
|
531
|
+
value: literalValue,
|
|
532
|
+
type: TableColumnType.Text,
|
|
533
|
+
}}`,
|
|
534
|
+
);
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if (mapEntry instanceof EndsWith) {
|
|
539
|
+
const literalValue: string = `%${(mapEntry.value as string) || ""}`;
|
|
457
540
|
whereStatement.append(
|
|
458
541
|
SQL`AND ${key}[${{
|
|
459
542
|
value: mapKey,
|
|
460
543
|
type: TableColumnType.Text,
|
|
461
544
|
}}] ILIKE ${{
|
|
462
|
-
value:
|
|
545
|
+
value: literalValue,
|
|
463
546
|
type: TableColumnType.Text,
|
|
464
547
|
}}`,
|
|
465
548
|
);
|
|
466
|
-
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
if (mapEntry instanceof NotEqual) {
|
|
553
|
+
whereStatement.append(
|
|
554
|
+
SQL`AND ${key}[${{
|
|
555
|
+
value: mapKey,
|
|
556
|
+
type: TableColumnType.Text,
|
|
557
|
+
}}] != ${{
|
|
558
|
+
value: String((mapEntry as NotEqual<string>).value ?? ""),
|
|
559
|
+
type: TableColumnType.Text,
|
|
560
|
+
}}`,
|
|
561
|
+
);
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if (mapEntry instanceof EqualTo) {
|
|
467
566
|
whereStatement.append(
|
|
468
567
|
SQL`AND ${key}[${{
|
|
469
568
|
value: mapKey,
|
|
470
569
|
type: TableColumnType.Text,
|
|
471
570
|
}}] = ${{
|
|
472
|
-
value:
|
|
571
|
+
value: String((mapEntry as EqualTo<any>).value ?? ""),
|
|
473
572
|
type: TableColumnType.Text,
|
|
474
573
|
}}`,
|
|
475
574
|
);
|
|
575
|
+
continue;
|
|
476
576
|
}
|
|
577
|
+
|
|
578
|
+
/*
|
|
579
|
+
* Map values are stored as text; cast to Float64 for numeric
|
|
580
|
+
* comparisons and skip rows where the cast fails (non-numeric).
|
|
581
|
+
*/
|
|
582
|
+
if (mapEntry instanceof GreaterThan) {
|
|
583
|
+
whereStatement.append(
|
|
584
|
+
SQL`AND toFloat64OrNull(${key}[${{
|
|
585
|
+
value: mapKey,
|
|
586
|
+
type: TableColumnType.Text,
|
|
587
|
+
}}]) > ${{
|
|
588
|
+
value: Number((mapEntry as GreaterThan<any>).value),
|
|
589
|
+
type: TableColumnType.Number,
|
|
590
|
+
}}`,
|
|
591
|
+
);
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
if (mapEntry instanceof GreaterThanOrEqual) {
|
|
596
|
+
whereStatement.append(
|
|
597
|
+
SQL`AND toFloat64OrNull(${key}[${{
|
|
598
|
+
value: mapKey,
|
|
599
|
+
type: TableColumnType.Text,
|
|
600
|
+
}}]) >= ${{
|
|
601
|
+
value: Number((mapEntry as GreaterThanOrEqual<any>).value),
|
|
602
|
+
type: TableColumnType.Number,
|
|
603
|
+
}}`,
|
|
604
|
+
);
|
|
605
|
+
continue;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
if (mapEntry instanceof LessThan) {
|
|
609
|
+
whereStatement.append(
|
|
610
|
+
SQL`AND toFloat64OrNull(${key}[${{
|
|
611
|
+
value: mapKey,
|
|
612
|
+
type: TableColumnType.Text,
|
|
613
|
+
}}]) < ${{
|
|
614
|
+
value: Number((mapEntry as LessThan<any>).value),
|
|
615
|
+
type: TableColumnType.Number,
|
|
616
|
+
}}`,
|
|
617
|
+
);
|
|
618
|
+
continue;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
if (mapEntry instanceof LessThanOrEqual) {
|
|
622
|
+
whereStatement.append(
|
|
623
|
+
SQL`AND toFloat64OrNull(${key}[${{
|
|
624
|
+
value: mapKey,
|
|
625
|
+
type: TableColumnType.Text,
|
|
626
|
+
}}]) <= ${{
|
|
627
|
+
value: Number((mapEntry as LessThanOrEqual<any>).value),
|
|
628
|
+
type: TableColumnType.Number,
|
|
629
|
+
}}`,
|
|
630
|
+
);
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// Bare string/number/boolean — back-compat with existing data.
|
|
635
|
+
whereStatement.append(
|
|
636
|
+
SQL`AND ${key}[${{
|
|
637
|
+
value: mapKey,
|
|
638
|
+
type: TableColumnType.Text,
|
|
639
|
+
}}] = ${{
|
|
640
|
+
value: String(mapEntry),
|
|
641
|
+
type: TableColumnType.Text,
|
|
642
|
+
}}`,
|
|
643
|
+
);
|
|
477
644
|
}
|
|
478
645
|
} else if (
|
|
479
646
|
(tableColumn.type === TableColumnType.JSON ||
|
package/Tests/Types/Date.test.ts
CHANGED
|
@@ -250,4 +250,50 @@ describe("class OneUptimeDate", () => {
|
|
|
250
250
|
expect(result[0]).not.toContain("EST");
|
|
251
251
|
});
|
|
252
252
|
});
|
|
253
|
+
|
|
254
|
+
describe("getDateAsFormattedArrayInMultipleTimezones default timezones", () => {
|
|
255
|
+
test("defaults include UTC, London, New York, LA, Kolkata, Sydney", () => {
|
|
256
|
+
const result: Array<string> =
|
|
257
|
+
OneUptimeDate.getDateAsFormattedArrayInMultipleTimezones({
|
|
258
|
+
date: new Date("2026-07-15T17:00:00Z"),
|
|
259
|
+
use12HourFormat: true,
|
|
260
|
+
});
|
|
261
|
+
expect(result).toHaveLength(6);
|
|
262
|
+
expect(result[0]).toContain("UTC");
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
test("summer (DST) event shows EDT and BST in defaults, not EST or GMT", () => {
|
|
266
|
+
/*
|
|
267
|
+
* Apr 27 2026 21:30 UTC — both US and UK are in DST. Use word
|
|
268
|
+
* boundaries so AEST/AEDT (Sydney) doesn't match EST/EDT.
|
|
269
|
+
*/
|
|
270
|
+
const result: Array<string> =
|
|
271
|
+
OneUptimeDate.getDateAsFormattedArrayInMultipleTimezones({
|
|
272
|
+
date: new Date("2026-04-27T21:30:00Z"),
|
|
273
|
+
use12HourFormat: true,
|
|
274
|
+
});
|
|
275
|
+
const joined: string = result.join("\n");
|
|
276
|
+
expect(joined).toMatch(/\bEDT\b/);
|
|
277
|
+
expect(joined).toMatch(/\bBST\b/);
|
|
278
|
+
expect(joined).not.toMatch(/\bEST\b/);
|
|
279
|
+
expect(joined).not.toMatch(/\bGMT\b/);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test("winter (no DST) event shows EST and GMT in defaults, not EDT or BST", () => {
|
|
283
|
+
/*
|
|
284
|
+
* Jan 15 2026 17:00 UTC — both US and UK are on standard time. Use
|
|
285
|
+
* word boundaries so AEDT (Sydney) doesn't match EDT.
|
|
286
|
+
*/
|
|
287
|
+
const result: Array<string> =
|
|
288
|
+
OneUptimeDate.getDateAsFormattedArrayInMultipleTimezones({
|
|
289
|
+
date: new Date("2026-01-15T17:00:00Z"),
|
|
290
|
+
use12HourFormat: true,
|
|
291
|
+
});
|
|
292
|
+
const joined: string = result.join("\n");
|
|
293
|
+
expect(joined).toMatch(/\bEST\b/);
|
|
294
|
+
expect(joined).toMatch(/\bGMT\b/);
|
|
295
|
+
expect(joined).not.toMatch(/\bEDT\b/);
|
|
296
|
+
expect(joined).not.toMatch(/\bBST\b/);
|
|
297
|
+
});
|
|
298
|
+
});
|
|
253
299
|
});
|
package/Types/Date.ts
CHANGED
|
@@ -1385,13 +1385,18 @@ export default class OneUptimeDate {
|
|
|
1385
1385
|
formatstring = "MMM DD, YYYY";
|
|
1386
1386
|
}
|
|
1387
1387
|
|
|
1388
|
-
// convert this date into GMT, EST, PST, IST, ACT with moment
|
|
1389
1388
|
const timezoneDates: Array<string> = [];
|
|
1390
1389
|
|
|
1391
1390
|
if (!timezones || timezones.length === 0) {
|
|
1391
|
+
/*
|
|
1392
|
+
* Use IANA region zones (not fixed-offset zones like "EST") so the
|
|
1393
|
+
* abbreviation reflects DST at the event's date — e.g. EST/EDT for
|
|
1394
|
+
* America/New_York and GMT/BST for Europe/London.
|
|
1395
|
+
*/
|
|
1392
1396
|
timezones = [
|
|
1393
1397
|
Timezone.UTC,
|
|
1394
|
-
Timezone.
|
|
1398
|
+
Timezone.EuropeLondon,
|
|
1399
|
+
Timezone.AmericaNew_York,
|
|
1395
1400
|
Timezone.AmericaLos_Angeles,
|
|
1396
1401
|
Timezone.AsiaKolkata,
|
|
1397
1402
|
Timezone.AustraliaSydney,
|
|
@@ -1416,7 +1421,7 @@ export default class OneUptimeDate {
|
|
|
1416
1421
|
public static getDateAsFormattedHTMLInMultipleTimezones(data: {
|
|
1417
1422
|
date: string | Date;
|
|
1418
1423
|
onlyShowDate?: boolean;
|
|
1419
|
-
timezones?: Array<Timezone> | undefined; // if
|
|
1424
|
+
timezones?: Array<Timezone> | undefined; // if skipped, defaults to UTC, Europe/London, America/New_York, America/Los_Angeles, Asia/Kolkata, Australia/Sydney (DST-aware)
|
|
1420
1425
|
use12HourFormat?: boolean | undefined;
|
|
1421
1426
|
}): string {
|
|
1422
1427
|
const date: string | Date = data.date;
|
|
@@ -1435,7 +1440,7 @@ export default class OneUptimeDate {
|
|
|
1435
1440
|
public static getDateAsFormattedStringInMultipleTimezones(data: {
|
|
1436
1441
|
date: string | Date;
|
|
1437
1442
|
onlyShowDate?: boolean | undefined;
|
|
1438
|
-
timezones?: Array<Timezone> | undefined; // if
|
|
1443
|
+
timezones?: Array<Timezone> | undefined; // if skipped, defaults to UTC, Europe/London, America/New_York, America/Los_Angeles, Asia/Kolkata, Australia/Sydney (DST-aware)
|
|
1439
1444
|
use12HourFormat?: boolean | undefined;
|
|
1440
1445
|
}): string {
|
|
1441
1446
|
const date: string | Date = data.date;
|
|
@@ -21,6 +21,9 @@ export interface ComponentProps {
|
|
|
21
21
|
onBlur?: (() => void) | undefined;
|
|
22
22
|
outerDivClassName?: string | undefined;
|
|
23
23
|
disableSpellCheck?: boolean | undefined;
|
|
24
|
+
isLoadingSuggestions?: boolean | undefined;
|
|
25
|
+
loadingMessage?: string | undefined;
|
|
26
|
+
noSuggestionsMessage?: string | undefined;
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
const BASE_INPUT_CLASS: string =
|
|
@@ -90,7 +93,9 @@ const AutocompleteTextInput: FunctionComponent<ComponentProps> = (
|
|
|
90
93
|
.slice(0, MAX_SUGGESTIONS);
|
|
91
94
|
}, [inputValue, props.suggestions]);
|
|
92
95
|
|
|
93
|
-
const
|
|
96
|
+
const isLoadingSuggestions: boolean = Boolean(props.isLoadingSuggestions);
|
|
97
|
+
const showMenu: boolean =
|
|
98
|
+
isMenuVisible && (suggestions.length > 0 || isLoadingSuggestions);
|
|
94
99
|
|
|
95
100
|
const getInputClassName: () => string = (): string => {
|
|
96
101
|
let className: string = props.className || BASE_INPUT_CLASS;
|
|
@@ -145,7 +150,7 @@ const AutocompleteTextInput: FunctionComponent<ComponentProps> = (
|
|
|
145
150
|
const handleKeyDown: (
|
|
146
151
|
event: React.KeyboardEvent<HTMLInputElement>,
|
|
147
152
|
) => void = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
148
|
-
if (!showMenu) {
|
|
153
|
+
if (!showMenu || suggestions.length === 0) {
|
|
149
154
|
return;
|
|
150
155
|
}
|
|
151
156
|
|
|
@@ -221,26 +226,60 @@ const AutocompleteTextInput: FunctionComponent<ComponentProps> = (
|
|
|
221
226
|
id={listboxIdRef.current}
|
|
222
227
|
role="listbox"
|
|
223
228
|
>
|
|
224
|
-
{
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
aria-
|
|
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
|
-
}}
|
|
229
|
+
{isLoadingSuggestions && (
|
|
230
|
+
<div className="flex w-full items-center px-3 py-2 text-left text-gray-500">
|
|
231
|
+
<svg
|
|
232
|
+
className="animate-spin -ml-0.5 mr-2 h-4 w-4 text-indigo-500"
|
|
233
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
234
|
+
fill="none"
|
|
235
|
+
viewBox="0 0 24 24"
|
|
236
|
+
aria-hidden="true"
|
|
239
237
|
>
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
238
|
+
<circle
|
|
239
|
+
className="opacity-25"
|
|
240
|
+
cx="12"
|
|
241
|
+
cy="12"
|
|
242
|
+
r="10"
|
|
243
|
+
stroke="currentColor"
|
|
244
|
+
strokeWidth="4"
|
|
245
|
+
></circle>
|
|
246
|
+
<path
|
|
247
|
+
className="opacity-75"
|
|
248
|
+
fill="currentColor"
|
|
249
|
+
d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"
|
|
250
|
+
></path>
|
|
251
|
+
</svg>
|
|
252
|
+
<span>{props.loadingMessage || "Loading..."}</span>
|
|
253
|
+
</div>
|
|
254
|
+
)}
|
|
255
|
+
{!isLoadingSuggestions &&
|
|
256
|
+
suggestions.length === 0 &&
|
|
257
|
+
props.noSuggestionsMessage && (
|
|
258
|
+
<div className="flex w-full items-center px-3 py-2 text-left text-gray-500">
|
|
259
|
+
{props.noSuggestionsMessage}
|
|
260
|
+
</div>
|
|
261
|
+
)}
|
|
262
|
+
{!isLoadingSuggestions &&
|
|
263
|
+
suggestions.map((suggestion: string, index: number) => {
|
|
264
|
+
const isActive: boolean = index === highlightedIndex;
|
|
265
|
+
return (
|
|
266
|
+
<button
|
|
267
|
+
key={`${suggestion}-${index}`}
|
|
268
|
+
type="button"
|
|
269
|
+
role="option"
|
|
270
|
+
aria-selected={isActive}
|
|
271
|
+
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"}`}
|
|
272
|
+
onMouseDown={(event: React.MouseEvent<HTMLButtonElement>) => {
|
|
273
|
+
event.preventDefault();
|
|
274
|
+
}}
|
|
275
|
+
onClick={() => {
|
|
276
|
+
handleSuggestionSelect(suggestion);
|
|
277
|
+
}}
|
|
278
|
+
>
|
|
279
|
+
{suggestion}
|
|
280
|
+
</button>
|
|
281
|
+
);
|
|
282
|
+
})}
|
|
244
283
|
</div>
|
|
245
284
|
)}
|
|
246
285
|
</div>
|