@oneuptime/common 10.0.26 → 10.0.28

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 (31) hide show
  1. package/Models/DatabaseModels/GlobalConfig.ts +18 -0
  2. package/Server/API/TelemetryAPI.ts +1 -1
  3. package/Server/Infrastructure/Postgres/SchemaMigrations/1772350000000-MigrationName.ts +23 -0
  4. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  5. package/Server/Services/LogAggregationService.ts +1 -1
  6. package/Server/Utils/AnalyticsDatabase/Statement.ts +3 -1
  7. package/Server/Utils/Monitor/MonitorLogUtil.ts +92 -23
  8. package/Server/Utils/Monitor/MonitorMetricUtil.ts +75 -12
  9. package/UI/Components/LogsViewer/LogsViewer.tsx +32 -26
  10. package/UI/Components/LogsViewer/components/FacetSection.tsx +45 -7
  11. package/build/dist/Models/DatabaseModels/GlobalConfig.js +19 -0
  12. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  13. package/build/dist/Server/API/TelemetryAPI.js +1 -1
  14. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  15. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1772350000000-MigrationName.js +14 -0
  16. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1772350000000-MigrationName.js.map +1 -0
  17. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  18. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  19. package/build/dist/Server/Services/LogAggregationService.js +1 -1
  20. package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
  21. package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js +3 -1
  22. package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js.map +1 -1
  23. package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js +69 -15
  24. package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js.map +1 -1
  25. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js +60 -12
  26. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js.map +1 -1
  27. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +22 -16
  28. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  29. package/build/dist/UI/Components/LogsViewer/components/FacetSection.js +26 -6
  30. package/build/dist/UI/Components/LogsViewer/components/FacetSection.js.map +1 -1
  31. package/package.json +1 -1
@@ -605,4 +605,22 @@ export default class GlobalConfig extends GlobalConfigModel {
605
605
  unique: true,
606
606
  })
607
607
  public monitorLogRetentionInDays?: number = undefined;
608
+
609
+ @ColumnAccessControl({
610
+ create: [],
611
+ read: [],
612
+ update: [],
613
+ })
614
+ @TableColumn({
615
+ type: TableColumnType.Number,
616
+ title: "Monitor Metric Retention Days",
617
+ description:
618
+ "Number of days to retain monitor metrics. Monitor metrics older than this will be automatically deleted. Default is 1 day.",
619
+ })
620
+ @Column({
621
+ type: ColumnType.Number,
622
+ nullable: true,
623
+ unique: true,
624
+ })
625
+ public monitorMetricRetentionInDays?: number = undefined;
608
626
  }
@@ -212,7 +212,7 @@ router.post(
212
212
  ? OneUptimeDate.fromString(body["endTime"] as string)
213
213
  : OneUptimeDate.getCurrentDate();
214
214
 
215
- const limit: number = (body["limit"] as number) || 10;
215
+ const limit: number = (body["limit"] as number) || 500;
216
216
 
217
217
  const serviceIds: Array<ObjectID> | undefined = body["serviceIds"]
218
218
  ? (body["serviceIds"] as Array<string>).map((id: string) => {
@@ -0,0 +1,23 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class MigrationName1772350000000 implements MigrationInterface {
4
+ public name = "MigrationName1772350000000";
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(
8
+ `ALTER TABLE "GlobalConfig" ADD "monitorMetricRetentionInDays" integer`,
9
+ );
10
+ await queryRunner.query(
11
+ `ALTER TABLE "GlobalConfig" ADD CONSTRAINT "UQ_monitor_metric_retention" UNIQUE ("monitorMetricRetentionInDays")`,
12
+ );
13
+ }
14
+
15
+ public async down(queryRunner: QueryRunner): Promise<void> {
16
+ await queryRunner.query(
17
+ `ALTER TABLE "GlobalConfig" DROP CONSTRAINT "UQ_monitor_metric_retention"`,
18
+ );
19
+ await queryRunner.query(
20
+ `ALTER TABLE "GlobalConfig" DROP COLUMN "monitorMetricRetentionInDays"`,
21
+ );
22
+ }
23
+ }
@@ -261,6 +261,7 @@ import { MigrationName1770834237090 } from "./1770834237090-MigrationName";
261
261
  import { MigrationName1770834237091 } from "./1770834237091-MigrationName";
262
262
  import { MigrationName1772111896988 } from "./1772111896988-MigrationName";
263
263
  import { MigrationName1772280000000 } from "./1772280000000-MigrationName";
264
+ import { MigrationName1772350000000 } from "./1772350000000-MigrationName";
264
265
 
265
266
  export default [
266
267
  InitialMigration,
@@ -526,4 +527,5 @@ export default [
526
527
  MigrationName1770834237091,
527
528
  MigrationName1772111896988,
528
529
  MigrationName1772280000000,
530
+ MigrationName1772350000000,
529
531
  ];
@@ -45,7 +45,7 @@ export interface FacetRequest {
45
45
  }
46
46
 
47
47
  export class LogAggregationService {
48
- private static readonly DEFAULT_FACET_LIMIT: number = 10;
48
+ private static readonly DEFAULT_FACET_LIMIT: number = 500;
49
49
  private static readonly TABLE_NAME: string = "LogItem";
50
50
  private static readonly TOP_LEVEL_COLUMNS: Set<string> = new Set([
51
51
  "severityText",
@@ -9,6 +9,7 @@ import LessThan from "../../../Types/BaseDatabase/LessThan";
9
9
  import LessThanOrEqual from "../../../Types/BaseDatabase/LessThanOrEqual";
10
10
  import LessThanOrNull from "../../../Types/BaseDatabase/LessThanOrNull";
11
11
  import GreaterThanOrNull from "../../../Types/BaseDatabase/GreaterThanOrNull";
12
+ import NotEqual from "../../../Types/BaseDatabase/NotEqual";
12
13
  import Search from "../../../Types/BaseDatabase/Search";
13
14
  import OneUptimeDate from "../../../Types/Date";
14
15
  import Dictionary from "../../../Types/Dictionary";
@@ -103,7 +104,8 @@ export class Statement implements BaseQueryParams {
103
104
  v.value instanceof GreaterThan ||
104
105
  v.value instanceof GreaterThanOrEqual ||
105
106
  v.value instanceof LessThanOrNull ||
106
- v.value instanceof GreaterThanOrNull
107
+ v.value instanceof GreaterThanOrNull ||
108
+ v.value instanceof NotEqual
107
109
  ) {
108
110
  finalValue = v.value.value;
109
111
  } else if (v.value instanceof Includes) {
@@ -1,4 +1,6 @@
1
1
  import MonitorLogService from "../../Services/MonitorLogService";
2
+ import GlobalConfigService from "../../Services/GlobalConfigService";
3
+ import GlobalConfig from "../../../Models/DatabaseModels/GlobalConfig";
2
4
  import logger from "../Logger";
3
5
  import OneUptimeDate from "../../../Types/Date";
4
6
  import ObjectID from "../../../Types/ObjectID";
@@ -6,6 +8,64 @@ import { JSONObject } from "../../../Types/JSON";
6
8
  import DataToProcess from "./DataToProcess";
7
9
 
8
10
  export default class MonitorLogUtil {
11
+ // Default retention in days if GlobalConfig is not set
12
+ private static readonly DEFAULT_RETENTION_DAYS: number = 1;
13
+
14
+ // Cached retention value to avoid querying GlobalConfig on every monitor check
15
+ private static cachedRetentionDays: number | null = null;
16
+ private static lastCacheRefresh: Date | null = null;
17
+ private static readonly CACHE_TTL_MS: number = 5 * 60 * 1000; // 5 minutes
18
+
19
+ private static async getRetentionDays(): Promise<number> {
20
+ const now: Date = OneUptimeDate.getCurrentDate();
21
+
22
+ // Return cached value if still fresh
23
+ if (
24
+ this.cachedRetentionDays !== null &&
25
+ this.lastCacheRefresh !== null &&
26
+ now.getTime() - this.lastCacheRefresh.getTime() < this.CACHE_TTL_MS
27
+ ) {
28
+ return this.cachedRetentionDays;
29
+ }
30
+
31
+ try {
32
+ const globalConfig: GlobalConfig | null =
33
+ await GlobalConfigService.findOneBy({
34
+ query: {
35
+ _id: ObjectID.getZeroObjectID().toString(),
36
+ },
37
+ props: {
38
+ isRoot: true,
39
+ },
40
+ select: {
41
+ monitorLogRetentionInDays: true,
42
+ },
43
+ });
44
+
45
+ if (
46
+ globalConfig &&
47
+ globalConfig.monitorLogRetentionInDays !== undefined &&
48
+ globalConfig.monitorLogRetentionInDays !== null &&
49
+ globalConfig.monitorLogRetentionInDays > 0
50
+ ) {
51
+ this.cachedRetentionDays = globalConfig.monitorLogRetentionInDays;
52
+ } else {
53
+ this.cachedRetentionDays = this.DEFAULT_RETENTION_DAYS;
54
+ }
55
+
56
+ this.lastCacheRefresh = now;
57
+ } catch (error) {
58
+ logger.error(
59
+ "Error fetching monitor log retention config, using default:",
60
+ );
61
+ logger.error(error);
62
+ this.cachedRetentionDays = this.DEFAULT_RETENTION_DAYS;
63
+ this.lastCacheRefresh = now;
64
+ }
65
+
66
+ return this.cachedRetentionDays;
67
+ }
68
+
9
69
  public static saveMonitorLog(data: {
10
70
  monitorId: ObjectID;
11
71
  projectId: ObjectID;
@@ -23,28 +83,37 @@ export default class MonitorLogUtil {
23
83
  return;
24
84
  }
25
85
 
26
- const logIngestionDate: Date = OneUptimeDate.getCurrentDate();
27
- const logTimestamp: string =
28
- OneUptimeDate.toClickhouseDateTime(logIngestionDate);
29
-
30
- const retentionDate: Date = OneUptimeDate.addRemoveDays(
31
- logIngestionDate,
32
- 15,
33
- );
34
-
35
- const monitorLogRow: JSONObject = {
36
- _id: ObjectID.generate().toString(),
37
- createdAt: logTimestamp,
38
- updatedAt: logTimestamp,
39
- projectId: data.projectId.toString(),
40
- monitorId: data.monitorId.toString(),
41
- time: logTimestamp,
42
- logBody: JSON.parse(JSON.stringify(data.dataToProcess)),
43
- retentionDate: OneUptimeDate.toClickhouseDateTime(retentionDate),
44
- };
45
-
46
- MonitorLogService.insertJsonRows([monitorLogRow]).catch((err: Error) => {
47
- logger.error(err);
48
- });
86
+ // Fire-and-forget: fetch retention config then insert
87
+ this.getRetentionDays()
88
+ .then((retentionDays: number) => {
89
+ const logIngestionDate: Date = OneUptimeDate.getCurrentDate();
90
+ const logTimestamp: string =
91
+ OneUptimeDate.toClickhouseDateTime(logIngestionDate);
92
+
93
+ const retentionDate: Date = OneUptimeDate.addRemoveDays(
94
+ logIngestionDate,
95
+ retentionDays,
96
+ );
97
+
98
+ const monitorLogRow: JSONObject = {
99
+ _id: ObjectID.generate().toString(),
100
+ createdAt: logTimestamp,
101
+ updatedAt: logTimestamp,
102
+ projectId: data.projectId.toString(),
103
+ monitorId: data.monitorId.toString(),
104
+ time: logTimestamp,
105
+ logBody: JSON.parse(JSON.stringify(data.dataToProcess)),
106
+ retentionDate: OneUptimeDate.toClickhouseDateTime(retentionDate),
107
+ };
108
+
109
+ MonitorLogService.insertJsonRows([monitorLogRow]).catch(
110
+ (err: Error) => {
111
+ logger.error(err);
112
+ },
113
+ );
114
+ })
115
+ .catch((err: Error) => {
116
+ logger.error(err);
117
+ });
49
118
  }
50
119
  }
@@ -2,6 +2,8 @@ import logger from "../Logger";
2
2
  import CaptureSpan from "../Telemetry/CaptureSpan";
3
3
  import TelemetryUtil from "../Telemetry/Telemetry";
4
4
  import MetricService from "../../Services/MetricService";
5
+ import GlobalConfigService from "../../Services/GlobalConfigService";
6
+ import GlobalConfig from "../../../Models/DatabaseModels/GlobalConfig";
5
7
  import DataToProcess from "./DataToProcess";
6
8
  import {
7
9
  MetricPointType,
@@ -20,6 +22,63 @@ import ObjectID from "../../../Types/ObjectID";
20
22
  import OneUptimeDate from "../../../Types/Date";
21
23
 
22
24
  export default class MonitorMetricUtil {
25
+ // Default retention in days if GlobalConfig is not set
26
+ private static readonly DEFAULT_RETENTION_DAYS: number = 1;
27
+
28
+ // Cached retention value to avoid querying GlobalConfig on every monitor check
29
+ private static cachedRetentionDays: number | null = null;
30
+ private static lastCacheRefresh: Date | null = null;
31
+ private static readonly CACHE_TTL_MS: number = 5 * 60 * 1000; // 5 minutes
32
+
33
+ private static async getRetentionDays(): Promise<number> {
34
+ const now: Date = OneUptimeDate.getCurrentDate();
35
+
36
+ // Return cached value if still fresh
37
+ if (
38
+ this.cachedRetentionDays !== null &&
39
+ this.lastCacheRefresh !== null &&
40
+ now.getTime() - this.lastCacheRefresh.getTime() < this.CACHE_TTL_MS
41
+ ) {
42
+ return this.cachedRetentionDays;
43
+ }
44
+
45
+ try {
46
+ const globalConfig: GlobalConfig | null =
47
+ await GlobalConfigService.findOneBy({
48
+ query: {
49
+ _id: ObjectID.getZeroObjectID().toString(),
50
+ },
51
+ props: {
52
+ isRoot: true,
53
+ },
54
+ select: {
55
+ monitorMetricRetentionInDays: true,
56
+ },
57
+ });
58
+
59
+ if (
60
+ globalConfig &&
61
+ globalConfig.monitorMetricRetentionInDays !== undefined &&
62
+ globalConfig.monitorMetricRetentionInDays !== null &&
63
+ globalConfig.monitorMetricRetentionInDays > 0
64
+ ) {
65
+ this.cachedRetentionDays = globalConfig.monitorMetricRetentionInDays;
66
+ } else {
67
+ this.cachedRetentionDays = this.DEFAULT_RETENTION_DAYS;
68
+ }
69
+
70
+ this.lastCacheRefresh = now;
71
+ } catch (error) {
72
+ logger.error(
73
+ "Error fetching monitor metric retention config, using default:",
74
+ );
75
+ logger.error(error);
76
+ this.cachedRetentionDays = this.DEFAULT_RETENTION_DAYS;
77
+ this.lastCacheRefresh = now;
78
+ }
79
+
80
+ return this.cachedRetentionDays;
81
+ }
23
82
  private static buildMonitorMetricAttributes(data: {
24
83
  monitorId: ObjectID;
25
84
  projectId: ObjectID;
@@ -47,14 +106,14 @@ export default class MonitorMetricUtil {
47
106
  return attributes;
48
107
  }
49
108
 
50
- private static buildMonitorMetricRow(data: {
109
+ private static async buildMonitorMetricRow(data: {
51
110
  projectId: ObjectID;
52
111
  monitorId: ObjectID;
53
112
  metricName: string;
54
113
  value: number | null | undefined;
55
114
  attributes: JSONObject;
56
115
  metricPointType?: MetricPointType;
57
- }): JSONObject {
116
+ }): Promise<JSONObject> {
58
117
  const ingestionDate: Date = OneUptimeDate.getCurrentDate();
59
118
  const ingestionTimestamp: string =
60
119
  OneUptimeDate.toClickhouseDateTime(ingestionDate);
@@ -65,7 +124,11 @@ export default class MonitorMetricUtil {
65
124
  const attributeKeys: Array<string> =
66
125
  TelemetryUtil.getAttributeKeys(attributes);
67
126
 
68
- const retentionDate: Date = OneUptimeDate.addRemoveDays(ingestionDate, 15);
127
+ const retentionDays: number = await this.getRetentionDays();
128
+ const retentionDate: Date = OneUptimeDate.addRemoveDays(
129
+ ingestionDate,
130
+ retentionDays,
131
+ );
69
132
 
70
133
  return {
71
134
  _id: ObjectID.generate().toString(),
@@ -149,7 +212,7 @@ export default class MonitorMetricUtil {
149
212
  probeName: data.probeName,
150
213
  });
151
214
 
152
- const metricRow: JSONObject = this.buildMonitorMetricRow({
215
+ const metricRow: JSONObject = await this.buildMonitorMetricRow({
153
216
  projectId: data.projectId,
154
217
  monitorId: data.monitorId,
155
218
  metricName: MonitorMetricType.IsOnline,
@@ -186,7 +249,7 @@ export default class MonitorMetricUtil {
186
249
  probeName: data.probeName,
187
250
  });
188
251
 
189
- const metricRow: JSONObject = this.buildMonitorMetricRow({
252
+ const metricRow: JSONObject = await this.buildMonitorMetricRow({
190
253
  projectId: data.projectId,
191
254
  monitorId: data.monitorId,
192
255
  metricName: MonitorMetricType.CPUUsagePercent,
@@ -214,7 +277,7 @@ export default class MonitorMetricUtil {
214
277
  probeName: data.probeName,
215
278
  });
216
279
 
217
- const metricRow: JSONObject = this.buildMonitorMetricRow({
280
+ const metricRow: JSONObject = await this.buildMonitorMetricRow({
218
281
  projectId: data.projectId,
219
282
  monitorId: data.monitorId,
220
283
  metricName: MonitorMetricType.MemoryUsagePercent,
@@ -250,7 +313,7 @@ export default class MonitorMetricUtil {
250
313
  extraAttributes: extraAttributes,
251
314
  });
252
315
 
253
- const metricRow: JSONObject = this.buildMonitorMetricRow({
316
+ const metricRow: JSONObject = await this.buildMonitorMetricRow({
254
317
  projectId: data.projectId,
255
318
  monitorId: data.monitorId,
256
319
  metricName: MonitorMetricType.DiskUsagePercent,
@@ -288,7 +351,7 @@ export default class MonitorMetricUtil {
288
351
  extraAttributes: extraAttributes,
289
352
  });
290
353
 
291
- const metricRow: JSONObject = this.buildMonitorMetricRow({
354
+ const metricRow: JSONObject = await this.buildMonitorMetricRow({
292
355
  projectId: data.projectId,
293
356
  monitorId: data.monitorId,
294
357
  metricName: MonitorMetricType.ExecutionTime,
@@ -345,7 +408,7 @@ export default class MonitorMetricUtil {
345
408
  extraAttributes: extraAttributes,
346
409
  });
347
410
 
348
- const metricRow: JSONObject = this.buildMonitorMetricRow({
411
+ const metricRow: JSONObject = await this.buildMonitorMetricRow({
349
412
  projectId: data.projectId,
350
413
  monitorId: data.monitorId,
351
414
  metricName: MonitorMetricType.ExecutionTime,
@@ -380,7 +443,7 @@ export default class MonitorMetricUtil {
380
443
  extraAttributes: extraAttributes,
381
444
  });
382
445
 
383
- const metricRow: JSONObject = this.buildMonitorMetricRow({
446
+ const metricRow: JSONObject = await this.buildMonitorMetricRow({
384
447
  projectId: data.projectId,
385
448
  monitorId: data.monitorId,
386
449
  metricName: MonitorMetricType.ResponseTime,
@@ -415,7 +478,7 @@ export default class MonitorMetricUtil {
415
478
  extraAttributes: extraAttributes,
416
479
  });
417
480
 
418
- const metricRow: JSONObject = this.buildMonitorMetricRow({
481
+ const metricRow: JSONObject = await this.buildMonitorMetricRow({
419
482
  projectId: data.projectId,
420
483
  monitorId: data.monitorId,
421
484
  metricName: MonitorMetricType.IsOnline,
@@ -449,7 +512,7 @@ export default class MonitorMetricUtil {
449
512
  extraAttributes: extraAttributes,
450
513
  });
451
514
 
452
- const metricRow: JSONObject = this.buildMonitorMetricRow({
515
+ const metricRow: JSONObject = await this.buildMonitorMetricRow({
453
516
  projectId: data.projectId,
454
517
  monitorId: data.monitorId,
455
518
  metricName: MonitorMetricType.ResponseStatusCode,
@@ -465,31 +465,10 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
465
465
  });
466
466
  }, [props.activeFilters, serviceMap]);
467
467
 
468
- if (isPageLoading) {
469
- return <PageLoader isVisible={true} />;
470
- }
471
-
472
- if (pageError) {
473
- return <ErrorMessage message={pageError} />;
474
- }
475
-
476
- const toolbarProps: LogsViewerToolbarProps = {
477
- resultCount: totalItems,
478
- currentPage,
479
- totalPages,
480
- ...(props.liveOptions ? { liveOptions: props.liveOptions } : {}),
481
- ...(props.timeRange && props.onTimeRangeChange
482
- ? {
483
- timeRange: props.timeRange,
484
- onTimeRangeChange: props.onTimeRangeChange,
485
- }
486
- : {}),
487
- };
488
-
489
- const showSidebar: boolean =
490
- props.showFacetSidebar !== false && Boolean(props.facetData);
491
-
492
- // Replace serviceId UUIDs with human-readable names in value suggestions
468
+ /*
469
+ * Replace serviceId UUIDs with human-readable names in value suggestions
470
+ * Must be before early returns to maintain consistent hook call order.
471
+ */
493
472
  const resolvedValueSuggestions: Record<string, Array<string>> | undefined =
494
473
  useMemo(() => {
495
474
  if (!props.valueSuggestions) {
@@ -512,7 +491,10 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
512
491
  return suggestions;
513
492
  }, [props.valueSuggestions, serviceMap]);
514
493
 
515
- // Wrap onFieldValueSelect to resolve service names back to UUIDs
494
+ /*
495
+ * Wrap onFieldValueSelect to resolve service names back to UUIDs
496
+ * Must be before early returns to maintain consistent hook call order.
497
+ */
516
498
  const handleFieldValueSelectWithServiceResolve:
517
499
  | ((fieldKey: string, value: string) => void)
518
500
  | undefined = useMemo(() => {
@@ -537,6 +519,30 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
537
519
  };
538
520
  }, [props.onFieldValueSelect, serviceMap]);
539
521
 
522
+ if (isPageLoading) {
523
+ return <PageLoader isVisible={true} />;
524
+ }
525
+
526
+ if (pageError) {
527
+ return <ErrorMessage message={pageError} />;
528
+ }
529
+
530
+ const toolbarProps: LogsViewerToolbarProps = {
531
+ resultCount: totalItems,
532
+ currentPage,
533
+ totalPages,
534
+ ...(props.liveOptions ? { liveOptions: props.liveOptions } : {}),
535
+ ...(props.timeRange && props.onTimeRangeChange
536
+ ? {
537
+ timeRange: props.timeRange,
538
+ onTimeRangeChange: props.onTimeRangeChange,
539
+ }
540
+ : {}),
541
+ };
542
+
543
+ const showSidebar: boolean =
544
+ props.showFacetSidebar !== false && Boolean(props.facetData);
545
+
540
546
  return (
541
547
  <div className="space-y-2">
542
548
  {props.showFilters && (
@@ -1,4 +1,9 @@
1
- import React, { FunctionComponent, ReactElement, useState } from "react";
1
+ import React, {
2
+ FunctionComponent,
3
+ ReactElement,
4
+ useState,
5
+ useMemo,
6
+ } from "react";
2
7
  import { FacetValue } from "../types";
3
8
  import FacetValueRow from "./FacetValueRow";
4
9
  import Icon from "../../Icon/Icon";
@@ -17,21 +22,40 @@ export interface FacetSectionProps {
17
22
  }
18
23
 
19
24
  const DEFAULT_VISIBLE_COUNT: number = 5;
25
+ const SEARCH_THRESHOLD: number = 6;
20
26
 
21
27
  const FacetSection: FunctionComponent<FacetSectionProps> = (
22
28
  props: FacetSectionProps,
23
29
  ): ReactElement => {
24
30
  const [isExpanded, setIsExpanded] = useState<boolean>(true);
25
31
  const [showAll, setShowAll] = useState<boolean>(false);
32
+ const [searchText, setSearchText] = useState<string>("");
33
+
34
+ const showSearch: boolean = props.values.length >= SEARCH_THRESHOLD;
35
+
36
+ const filteredValues: Array<FacetValue> = useMemo(() => {
37
+ if (!searchText.trim()) {
38
+ return props.values;
39
+ }
40
+ const query: string = searchText.toLowerCase().trim();
41
+ return props.values.filter((facet: FacetValue) => {
42
+ const displayName: string =
43
+ props.valueDisplayMap?.[facet.value] ?? facet.value;
44
+ return displayName.toLowerCase().includes(query);
45
+ });
46
+ }, [props.values, props.valueDisplayMap, searchText]);
26
47
 
27
48
  const visibleCount: number =
28
49
  props.initialVisibleCount ?? DEFAULT_VISIBLE_COUNT;
29
50
 
30
- const displayedValues: Array<FacetValue> = showAll
31
- ? props.values
32
- : props.values.slice(0, visibleCount);
51
+ const displayedValues: Array<FacetValue> = searchText.trim()
52
+ ? filteredValues
53
+ : showAll
54
+ ? filteredValues
55
+ : filteredValues.slice(0, visibleCount);
33
56
 
34
- const hasMore: boolean = props.values.length > visibleCount;
57
+ const hasMore: boolean =
58
+ !searchText.trim() && filteredValues.length > visibleCount;
35
59
 
36
60
  const maxCount: number =
37
61
  props.values.length > 0
@@ -71,6 +95,20 @@ const FacetSection: FunctionComponent<FacetSectionProps> = (
71
95
 
72
96
  {isExpanded && (
73
97
  <div className="mt-1 px-1">
98
+ {showSearch && (
99
+ <div className="mb-1 px-1">
100
+ <input
101
+ type="text"
102
+ placeholder={`Search ${props.title.toLowerCase()}...`}
103
+ value={searchText}
104
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
105
+ setSearchText(e.target.value);
106
+ }}
107
+ className="w-full rounded border border-gray-200 bg-gray-50 px-2 py-1 text-[11px] text-gray-700 placeholder-gray-400 outline-none focus:border-indigo-300 focus:bg-white focus:ring-1 focus:ring-indigo-200"
108
+ />
109
+ </div>
110
+ )}
111
+
74
112
  {displayedValues.map((facet: FacetValue) => {
75
113
  return (
76
114
  <FacetValueRow
@@ -91,9 +129,9 @@ const FacetSection: FunctionComponent<FacetSectionProps> = (
91
129
  );
92
130
  })}
93
131
 
94
- {props.values.length === 0 && (
132
+ {displayedValues.length === 0 && (
95
133
  <p className="px-1 py-2 text-[11px] text-gray-400">
96
- No values found
134
+ {searchText.trim() ? "No matches found" : "No values found"}
97
135
  </p>
98
136
  )}
99
137
 
@@ -65,6 +65,7 @@ let GlobalConfig = class GlobalConfig extends GlobalConfigModel {
65
65
  this.enterpriseLicenseExpiresAt = undefined;
66
66
  this.enterpriseLicenseToken = undefined;
67
67
  this.monitorLogRetentionInDays = undefined;
68
+ this.monitorMetricRetentionInDays = undefined;
68
69
  }
69
70
  };
70
71
  __decorate([
@@ -654,6 +655,24 @@ __decorate([
654
655
  }),
655
656
  __metadata("design:type", Number)
656
657
  ], GlobalConfig.prototype, "monitorLogRetentionInDays", void 0);
658
+ __decorate([
659
+ ColumnAccessControl({
660
+ create: [],
661
+ read: [],
662
+ update: [],
663
+ }),
664
+ TableColumn({
665
+ type: TableColumnType.Number,
666
+ title: "Monitor Metric Retention Days",
667
+ description: "Number of days to retain monitor metrics. Monitor metrics older than this will be automatically deleted. Default is 1 day.",
668
+ }),
669
+ Column({
670
+ type: ColumnType.Number,
671
+ nullable: true,
672
+ unique: true,
673
+ }),
674
+ __metadata("design:type", Number)
675
+ ], GlobalConfig.prototype, "monitorMetricRetentionInDays", void 0);
657
676
  GlobalConfig = __decorate([
658
677
  TableMetadata({
659
678
  tableName: "GlobalConfig",