@oneuptime/common 12.0.34 → 13.0.0
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/GlobalConfig.ts +12 -12
- package/Models/DatabaseModels/InventoryItem.ts +44 -1
- package/Models/DatabaseModels/NetworkDevice.ts +126 -0
- package/Server/EnvironmentConfig.ts +47 -16
- package/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Infrastructure/Redis.ts +8 -8
- package/Server/Infrastructure/Status.ts +10 -4
- package/Server/Services/DatabaseService.ts +65 -13
- package/Server/Services/InventoryItemService.ts +31 -1
- package/Server/Services/MonitorTemplateService.ts +140 -19
- package/Server/Services/NetworkDeviceAutoImportRuleEngineService.ts +349 -41
- package/Server/Services/NetworkDeviceDiscoveryScanService.ts +87 -2
- package/Server/Services/NetworkDeviceService.ts +69 -0
- package/Server/Services/UserNotificationRuleService.ts +76 -25
- package/Server/Types/Database/Permissions/ReadPermission.ts +45 -5
- package/Server/Types/Database/QueryUtil.ts +56 -0
- package/Server/Types/Markdown.ts +93 -1
- package/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.ts +82 -17
- package/Server/Utils/EmailRollup/EmailRollupFlushRunner.ts +10 -1
- package/Server/Utils/EmailRollup/EmailRollupRenderer.ts +2 -2
- package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +82 -35
- package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +31 -5
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +202 -37
- package/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.ts +82 -9
- package/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.ts +16 -7
- package/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.ts +29 -17
- package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +107 -48
- package/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.ts +158 -0
- package/Server/Utils/Monitor/NetworkInventoryUtil.ts +27 -0
- package/Server/Utils/Telemetry/EntityRegistry.ts +11 -1
- package/Server/Utils/Telemetry/TelemetryEntity.ts +83 -9
- package/Tests/App/Dashboard/AddNeighborToMonitoringModal.test.tsx +32 -0
- package/Tests/App/Dashboard/AdoptedDeviceDrawer.test.tsx +374 -0
- package/Tests/App/Dashboard/DashboardLabelVariables.test.tsx +580 -0
- package/Tests/App/Dashboard/DashboardMonitorLabelVariable.test.tsx +438 -0
- package/Tests/App/Dashboard/DashboardVariableAllContract.test.tsx +5 -2
- package/Tests/App/Dashboard/DeviceAttachmentCard.test.tsx +273 -0
- package/Tests/App/Dashboard/DiscoveryReviewInventoryRefresh.test.tsx +505 -0
- package/Tests/App/Dashboard/EntityDetailPanel.test.tsx +163 -0
- package/Tests/App/Dashboard/InfrastructureExplorer.test.tsx +401 -0
- package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +232 -0
- package/Tests/App/Dashboard/InventoryItems.test.tsx +201 -0
- package/Tests/App/Dashboard/InventoryTypeAndStatusFacets.test.tsx +798 -0
- package/Tests/App/Dashboard/NetworkTopologyLiveView.test.tsx +320 -0
- package/Tests/App/Dashboard/NetworkTopologyToolbar.test.tsx +275 -0
- package/Tests/App/Dashboard/OnCallCalendarFeedEmptyState.test.tsx +975 -0
- package/Tests/App/Dashboard/OnCallCalendarFeedPlanGate.test.tsx +63 -0
- package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +626 -0
- package/Tests/App/Dashboard/TopologyDataLoading.test.tsx +348 -0
- package/Tests/App/Dashboard/TopologyPageNavigation.test.tsx +331 -0
- package/Tests/App/Dashboard/UserSettingsEmailPreferences.test.tsx +603 -0
- package/Tests/App/Dashboard/UserSettingsNotificationSettings.test.tsx +324 -0
- package/Tests/App/PublicDashboard/DashboardVariableSelector.test.tsx +13 -4
- package/Tests/App/StatusPage/StatusPageOidcOrigin.test.tsx +223 -0
- package/Tests/Models/InventoryItemStatus.test.ts +147 -0
- package/Tests/Models/NetworkDeviceMacAddressColumn.test.ts +294 -0
- package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +129 -0
- package/Tests/Server/EnvironmentConfigValkey.test.ts +227 -0
- package/Tests/Server/Infrastructure/Postgres/AddMacAddressToNetworkDeviceMigration.test.ts +365 -0
- package/Tests/Server/Infrastructure/Postgres/CustomFieldValueMappingMigration.test.ts +62 -23
- package/Tests/Server/Infrastructure/ValkeyDeploymentConfig.test.ts +265 -0
- package/Tests/Server/Middleware/HttpMetricsMiddleware.test.ts +382 -0
- package/Tests/Server/Middleware/SCIMAuthorization.test.ts +525 -0
- package/Tests/Server/Services/DatabaseServiceAggregateBy.test.ts +291 -56
- package/Tests/Server/Services/DatabaseServiceSortTiebreaker.test.ts +151 -0
- package/Tests/Server/Services/InventoryItemManualCreate.test.ts +105 -0
- package/Tests/Server/Services/InventoryItemPromotionGate.test.ts +178 -0
- package/Tests/Server/Services/MonitorTemplateServiceCustomFieldSync.test.ts +502 -0
- package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +1133 -81
- package/Tests/Server/Services/NetworkDeviceDiscoveryScanRegistration.test.ts +520 -0
- package/Tests/Server/Services/NetworkDeviceMacAddressNormalization.test.ts +572 -0
- package/Tests/Server/Types/Database/InventoryStatusPostgres.test.ts +368 -0
- package/Tests/Server/Types/Database/Permissions/ReadBlockPermission.test.ts +261 -14
- package/Tests/Server/Types/Database/QueryUtilIncludesAnyOfGroups.test.ts +169 -0
- package/Tests/Server/Types/Database/QueryUtilIncludesAnyOfGroupsPostgres.test.ts +297 -0
- package/Tests/Server/Types/Markdown.test.ts +194 -1
- package/Tests/Server/Types/Workflow/Components/IncomingWebhookUtils.test.ts +279 -0
- package/Tests/Server/Types/Workflow/Components/TextToJson.test.ts +212 -0
- package/Tests/Server/Utils/CustomField/CustomFieldMappingRegistry.test.ts +436 -0
- package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +214 -0
- package/Tests/Server/Utils/EmailRollup/EmailRollupFlushRunnerBehaviour.test.ts +1 -1
- package/Tests/Server/Utils/EmailRollup/EmailRollupRenderer.test.ts +10 -3
- package/Tests/Server/Utils/EventLoop.test.ts +359 -0
- package/Tests/Server/Utils/JsonWebToken.test.ts +30 -0
- package/Tests/Server/Utils/Monitor/Criteria/CompareCriteria.test.ts +242 -0
- package/Tests/Server/Utils/Monitor/Criteria/CompareCriteriaAggregation.test.ts +13 -7
- package/Tests/Server/Utils/Monitor/Criteria/DatabaseMonitorCriteria.test.ts +29 -0
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.ts +30 -6
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +246 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEmailEvalLogAgreement.test.ts +280 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +224 -10
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorMetricUnits.test.ts +570 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaExpectationBuilderUnits.test.ts +30 -5
- package/Tests/Server/Utils/Monitor/MonitorCriteriaMessageBuilderUnits.test.ts +68 -21
- package/Tests/Server/Utils/Monitor/MonitorCriteriaObservationBuilderUnits.test.ts +36 -19
- package/Tests/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.test.ts +573 -0
- package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +317 -0
- package/Tests/Server/Utils/Telemetry/ContextSpanProcessor.test.ts +281 -0
- package/Tests/Server/Utils/Telemetry/InventoryDuplicateIdentity.test.ts +316 -0
- package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +111 -8
- package/Tests/Types/Database/IncludesAnyOfGroups.test.ts +106 -0
- package/Tests/Types/JSONFunctions.test.ts +40 -0
- package/Tests/Types/NetworkAutomation/AutoImportRunMerge.test.ts +235 -0
- package/Tests/Types/NetworkAutomation/RuleRunResult.test.ts +66 -0
- package/Tests/Types/NetworkAutomation/RuleRunResultDescribe.test.ts +309 -1
- package/Tests/Types/Telemetry/InventoryLiveness.test.ts +99 -0
- package/Tests/UI/Components/CustomFields/CustomFieldsDetail.test.tsx +160 -0
- package/Tests/UI/ReactRouterSingletonBuild.test.ts +133 -0
- package/Tests/UI/Rum/ReplayHeader.test.tsx +10 -2
- package/Tests/UI/Rum/ReplayPlayerChrome.test.tsx +514 -0
- package/Tests/UI/Rum/ReplayTimeFormat.test.ts +94 -0
- package/Tests/UI/Rum/ReplayUi.test.tsx +559 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +5 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +1 -0
- package/Tests/Utils/Dashboard/Components/DashboardMonitorListComponent.test.ts +11 -1
- package/Tests/Utils/Dashboard/LabelVariable.test.ts +257 -0
- package/Tests/Utils/Dashboard/VariableUrlState.test.ts +144 -14
- package/Tests/Utils/MetricUnitUtil.test.ts +81 -0
- package/Tests/Utils/Monitor/DeviceMacLearningUtil.test.ts +497 -0
- package/Tests/Utils/Monitor/MetricValueFormatter.test.ts +546 -0
- package/Tests/Utils/Monitor/MonitorTemplateCustomFieldUtil.test.ts +218 -0
- package/Tests/Utils/Monitor/NetworkTopologyEndpointAdoption.test.ts +1755 -0
- package/Tests/Utils/NetworkAutomation/AutoImportRunChain.test.ts +320 -0
- package/Types/BaseDatabase/IncludesAnyOfGroups.ts +77 -0
- package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardMonitorListComponent.ts +1 -0
- package/Types/Dashboard/DashboardVariable.ts +8 -0
- package/Types/JSON.ts +4 -0
- package/Types/JSONFunctions.ts +16 -0
- package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +13 -0
- package/Types/NetworkAutomation/RuleRunResult.ts +402 -13
- package/Types/SerializableObjectDictionary.ts +4 -0
- package/Types/Telemetry/InventoryLiveness.ts +56 -0
- package/UI/Components/CSVFileUpload/CSVFileUpload.tsx +9 -2
- package/UI/Components/CustomFields/CustomFieldsDetail.tsx +46 -21
- package/UI/Components/Dashboard/DashboardVariableControl.tsx +285 -0
- package/UI/esbuild-config.js +6 -0
- package/Utils/Dashboard/Components/DashboardMonitorListComponent.ts +12 -1
- package/Utils/Dashboard/LabelVariable.ts +152 -0
- package/Utils/Dashboard/VariableUrlState.ts +27 -5
- package/Utils/MetricUnitUtil.ts +34 -0
- package/Utils/Monitor/DeviceMacLearningUtil.ts +177 -0
- package/Utils/Monitor/MetricValueFormatter.ts +342 -0
- package/Utils/Monitor/MonitorTemplateCustomFieldUtil.ts +124 -0
- package/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.ts +7 -2
- package/Utils/Monitor/NetworkTopologyUtil.ts +693 -0
- package/Utils/NetworkAutomation/AutoImportRunChain.ts +138 -0
- package/build/dist/Models/DatabaseModels/GlobalConfig.js +12 -12
- package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
- package/build/dist/Models/DatabaseModels/InventoryItem.js +34 -1
- package/build/dist/Models/DatabaseModels/InventoryItem.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +128 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +44 -12
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.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/Redis.js +8 -8
- package/build/dist/Server/Infrastructure/Redis.js.map +1 -1
- package/build/dist/Server/Infrastructure/Status.js +10 -4
- package/build/dist/Server/Infrastructure/Status.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +58 -13
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/InventoryItemService.js +28 -1
- package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
- package/build/dist/Server/Services/MonitorTemplateService.js +116 -17
- package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js +270 -37
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +65 -0
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceService.js +54 -0
- package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js +60 -25
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/ReadPermission.js +29 -4
- package/build/dist/Server/Types/Database/Permissions/ReadPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryUtil.js +36 -0
- package/build/dist/Server/Types/Database/QueryUtil.js.map +1 -1
- package/build/dist/Server/Types/Markdown.js +75 -0
- package/build/dist/Server/Types/Markdown.js.map +1 -1
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js +53 -18
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js.map +1 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js +10 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js.map +1 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js +2 -2
- package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +58 -35
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +29 -5
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +166 -35
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js +58 -9
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.js +9 -3
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.js +26 -12
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +74 -33
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.js +114 -0
- package/build/dist/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +25 -0
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +9 -1
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +70 -13
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
- package/build/dist/Types/BaseDatabase/IncludesAnyOfGroups.js +54 -0
- package/build/dist/Types/BaseDatabase/IncludesAnyOfGroups.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardVariable.js +1 -0
- package/build/dist/Types/Dashboard/DashboardVariable.js.map +1 -1
- package/build/dist/Types/JSON.js +1 -0
- package/build/dist/Types/JSON.js.map +1 -1
- package/build/dist/Types/JSONFunctions.js +13 -0
- package/build/dist/Types/JSONFunctions.js.map +1 -1
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js +291 -6
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js.map +1 -1
- package/build/dist/Types/SerializableObjectDictionary.js +4 -0
- package/build/dist/Types/SerializableObjectDictionary.js.map +1 -1
- package/build/dist/Types/Telemetry/InventoryLiveness.js +37 -0
- package/build/dist/Types/Telemetry/InventoryLiveness.js.map +1 -0
- package/build/dist/UI/Components/CSVFileUpload/CSVFileUpload.js +7 -0
- package/build/dist/UI/Components/CSVFileUpload/CSVFileUpload.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +9 -2
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
- package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js +137 -0
- package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js +9 -1
- package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js.map +1 -1
- package/build/dist/Utils/Dashboard/LabelVariable.js +96 -0
- package/build/dist/Utils/Dashboard/LabelVariable.js.map +1 -0
- package/build/dist/Utils/Dashboard/VariableUrlState.js +19 -4
- package/build/dist/Utils/Dashboard/VariableUrlState.js.map +1 -1
- package/build/dist/Utils/MetricUnitUtil.js +26 -0
- package/build/dist/Utils/MetricUnitUtil.js.map +1 -1
- package/build/dist/Utils/Monitor/DeviceMacLearningUtil.js +112 -0
- package/build/dist/Utils/Monitor/DeviceMacLearningUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/MetricValueFormatter.js +276 -0
- package/build/dist/Utils/Monitor/MetricValueFormatter.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitorTemplateCustomFieldUtil.js +101 -0
- package/build/dist/Utils/Monitor/MonitorTemplateCustomFieldUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js +7 -2
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +484 -0
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
- package/build/dist/Utils/NetworkAutomation/AutoImportRunChain.js +59 -0
- package/build/dist/Utils/NetworkAutomation/AutoImportRunChain.js.map +1 -0
- package/jest.config.json +2 -0
- package/package.json +1 -1
- package/test-setup.sh +9 -9
- package/tsconfig.json +51 -11
- package/Tests/App/Dashboard/NotificationEmailPreferences.test.tsx +0 -399
|
@@ -5,7 +5,7 @@ import PermissionGate, {
|
|
|
5
5
|
PermissionGateResult,
|
|
6
6
|
} from "../../Utils/PermissionGate";
|
|
7
7
|
import { ButtonStyleType } from "../Button/Button";
|
|
8
|
-
import Card from "../Card/Card";
|
|
8
|
+
import Card, { CardButtonSchema } from "../Card/Card";
|
|
9
9
|
import ComponentLoader from "../ComponentLoader/ComponentLoader";
|
|
10
10
|
import Detail from "../Detail/Detail";
|
|
11
11
|
import { DropdownOption } from "../Dropdown/Dropdown";
|
|
@@ -79,6 +79,22 @@ export interface ComponentProps {
|
|
|
79
79
|
* real reason.
|
|
80
80
|
*/
|
|
81
81
|
hideIfEmpty?: boolean | undefined;
|
|
82
|
+
/*
|
|
83
|
+
* Card buttons that belong beside "Edit Fields" rather than on a card of
|
|
84
|
+
* their own — the Monitor Template's "Sync Custom Fields to Linked Monitors"
|
|
85
|
+
* being the one that exists. They render after the edit button and are not
|
|
86
|
+
* touched by the gating above: whether a push to other records is offered is
|
|
87
|
+
* the caller's decision, not this card's.
|
|
88
|
+
*/
|
|
89
|
+
additionalButtons?: Array<CardButtonSchema> | undefined;
|
|
90
|
+
/*
|
|
91
|
+
* The record's stored bag, handed back after every load — including the
|
|
92
|
+
* reload that follows a save — so a caller whose extra button acts on those
|
|
93
|
+
* values can enable and word it from what is actually stored, rather than
|
|
94
|
+
* re-reading the record behind this card's back and going stale the moment
|
|
95
|
+
* somebody edits it here.
|
|
96
|
+
*/
|
|
97
|
+
onValuesLoaded?: ((customFields: JSONObject) => void) | undefined;
|
|
82
98
|
}
|
|
83
99
|
|
|
84
100
|
const CustomFieldsDetail: FunctionComponent<ComponentProps> = (
|
|
@@ -219,6 +235,12 @@ const CustomFieldsDetail: FunctionComponent<ComponentProps> = (
|
|
|
219
235
|
setSchemaList(schemaList.data);
|
|
220
236
|
setModel(item);
|
|
221
237
|
|
|
238
|
+
if (props.onValuesLoaded) {
|
|
239
|
+
props.onValuesLoaded(
|
|
240
|
+
((item as any)?.["customFields"] as JSONObject) || {},
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
222
244
|
setIsLoading(false);
|
|
223
245
|
} catch (err) {
|
|
224
246
|
setIsLoading(false);
|
|
@@ -313,30 +335,33 @@ const CustomFieldsDetail: FunctionComponent<ComponentProps> = (
|
|
|
313
335
|
const showEditButton: boolean =
|
|
314
336
|
canEdit && (updateGate.isAllowed || Boolean(updateGate.disabledReason));
|
|
315
337
|
|
|
338
|
+
const cardButtons: Array<CardButtonSchema> = [
|
|
339
|
+
...(showEditButton
|
|
340
|
+
? [
|
|
341
|
+
{
|
|
342
|
+
title: "Edit Fields",
|
|
343
|
+
buttonStyle: ButtonStyleType.NORMAL,
|
|
344
|
+
disabled: !updateGate.isAllowed,
|
|
345
|
+
tooltip: updateGate.disabledReason,
|
|
346
|
+
onClick: () => {
|
|
347
|
+
if (!updateGate.isAllowed) {
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
setShowModelForm(true);
|
|
352
|
+
},
|
|
353
|
+
icon: IconProp.Edit,
|
|
354
|
+
} as CardButtonSchema,
|
|
355
|
+
]
|
|
356
|
+
: []),
|
|
357
|
+
...(props.additionalButtons || []),
|
|
358
|
+
];
|
|
359
|
+
|
|
316
360
|
return (
|
|
317
361
|
<Card
|
|
318
362
|
title={props.title}
|
|
319
363
|
description={props.description}
|
|
320
|
-
buttons={
|
|
321
|
-
showEditButton
|
|
322
|
-
? [
|
|
323
|
-
{
|
|
324
|
-
title: "Edit Fields",
|
|
325
|
-
buttonStyle: ButtonStyleType.NORMAL,
|
|
326
|
-
disabled: !updateGate.isAllowed,
|
|
327
|
-
tooltip: updateGate.disabledReason,
|
|
328
|
-
onClick: () => {
|
|
329
|
-
if (!updateGate.isAllowed) {
|
|
330
|
-
return;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
setShowModelForm(true);
|
|
334
|
-
},
|
|
335
|
-
icon: IconProp.Edit,
|
|
336
|
-
},
|
|
337
|
-
]
|
|
338
|
-
: []
|
|
339
|
-
}
|
|
364
|
+
buttons={cardButtons}
|
|
340
365
|
>
|
|
341
366
|
<div className="border-t border-gray-200 px-4 py-5 sm:px-6 -m-6 -mt-2">
|
|
342
367
|
{isLoading && !loadError && <ComponentLoader />}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
FunctionComponent,
|
|
3
|
+
ReactElement,
|
|
4
|
+
useEffect,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
} from "react";
|
|
8
|
+
import DashboardVariable, {
|
|
9
|
+
DashboardVariableType,
|
|
10
|
+
DashboardVariableOption,
|
|
11
|
+
} from "../../../Types/Dashboard/DashboardVariable";
|
|
12
|
+
|
|
13
|
+
export interface VariableValueChange {
|
|
14
|
+
selectedValue?: string | undefined;
|
|
15
|
+
selectedValues?: Array<string> | undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ComponentProps {
|
|
19
|
+
variable: DashboardVariable;
|
|
20
|
+
dynamicOptions?: Array<string> | undefined;
|
|
21
|
+
isLoadingOptions?: boolean | undefined;
|
|
22
|
+
onVariableValueChange: (
|
|
23
|
+
variableId: string,
|
|
24
|
+
change: VariableValueChange,
|
|
25
|
+
) => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const MultiSelectPopover: FunctionComponent<{
|
|
29
|
+
options: Array<DashboardVariableOption>;
|
|
30
|
+
selected: Array<string>;
|
|
31
|
+
label: string;
|
|
32
|
+
isLoading: boolean;
|
|
33
|
+
maxSelections?: number | undefined;
|
|
34
|
+
unavailableLabel?: string | undefined;
|
|
35
|
+
onChange: (next: Array<string>) => void;
|
|
36
|
+
}> = ({
|
|
37
|
+
options,
|
|
38
|
+
selected,
|
|
39
|
+
label,
|
|
40
|
+
isLoading,
|
|
41
|
+
maxSelections,
|
|
42
|
+
unavailableLabel,
|
|
43
|
+
onChange,
|
|
44
|
+
}: {
|
|
45
|
+
options: Array<DashboardVariableOption>;
|
|
46
|
+
selected: Array<string>;
|
|
47
|
+
label: string;
|
|
48
|
+
isLoading: boolean;
|
|
49
|
+
maxSelections?: number | undefined;
|
|
50
|
+
unavailableLabel?: string | undefined;
|
|
51
|
+
onChange: (next: Array<string>) => void;
|
|
52
|
+
}): ReactElement => {
|
|
53
|
+
const [open, setOpen] = useState<boolean>(false);
|
|
54
|
+
const wrapRef: React.RefObject<HTMLDivElement> = useRef<HTMLDivElement>(null);
|
|
55
|
+
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (!open) {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
const onDocClick: (e: MouseEvent) => void = (e: MouseEvent): void => {
|
|
61
|
+
if (wrapRef.current && !wrapRef.current.contains(e.target as Node)) {
|
|
62
|
+
setOpen(false);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
document.addEventListener("mousedown", onDocClick);
|
|
66
|
+
return () => {
|
|
67
|
+
document.removeEventListener("mousedown", onDocClick);
|
|
68
|
+
};
|
|
69
|
+
}, [open]);
|
|
70
|
+
|
|
71
|
+
const buttonText: string =
|
|
72
|
+
selected.length === 0
|
|
73
|
+
? isLoading
|
|
74
|
+
? "Loading…"
|
|
75
|
+
: "All"
|
|
76
|
+
: selected.length === 1
|
|
77
|
+
? options.find((option: DashboardVariableOption) => {
|
|
78
|
+
return option.value === selected[0];
|
|
79
|
+
})?.label ||
|
|
80
|
+
unavailableLabel ||
|
|
81
|
+
(selected[0] as string)
|
|
82
|
+
: `${selected.length} selected`;
|
|
83
|
+
|
|
84
|
+
const toggle: (value: string) => void = (value: string): void => {
|
|
85
|
+
if (selected.includes(value)) {
|
|
86
|
+
onChange(
|
|
87
|
+
selected.filter((v: string) => {
|
|
88
|
+
return v !== value;
|
|
89
|
+
}),
|
|
90
|
+
);
|
|
91
|
+
} else {
|
|
92
|
+
onChange([...selected, value]);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<div className="relative" ref={wrapRef}>
|
|
98
|
+
<button
|
|
99
|
+
type="button"
|
|
100
|
+
className="text-xs border border-gray-200 rounded-md px-2.5 py-1.5 bg-white text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-100 focus:border-blue-300 transition-colors flex items-center gap-1"
|
|
101
|
+
disabled={isLoading}
|
|
102
|
+
onClick={() => {
|
|
103
|
+
setOpen(!open);
|
|
104
|
+
}}
|
|
105
|
+
title={label}
|
|
106
|
+
aria-label={`${label}: ${buttonText}`}
|
|
107
|
+
aria-expanded={open}
|
|
108
|
+
>
|
|
109
|
+
<span className="truncate max-w-[10rem]">{buttonText}</span>
|
|
110
|
+
<span className="text-gray-400">▾</span>
|
|
111
|
+
</button>
|
|
112
|
+
{open && (
|
|
113
|
+
<div className="absolute z-30 mt-1 right-0 w-56 max-h-72 overflow-auto rounded-md border border-gray-200 bg-white shadow-lg py-1">
|
|
114
|
+
<div className="flex items-center justify-between px-3 py-1.5 border-b border-gray-100 text-[11px] text-gray-500">
|
|
115
|
+
<span>{selected.length} selected</span>
|
|
116
|
+
<button
|
|
117
|
+
type="button"
|
|
118
|
+
className="text-blue-600 hover:underline disabled:text-gray-300"
|
|
119
|
+
disabled={selected.length === 0}
|
|
120
|
+
onClick={() => {
|
|
121
|
+
onChange([]);
|
|
122
|
+
}}
|
|
123
|
+
>
|
|
124
|
+
Clear
|
|
125
|
+
</button>
|
|
126
|
+
</div>
|
|
127
|
+
{maxSelections !== undefined && selected.length >= maxSelections && (
|
|
128
|
+
<p className="px-3 py-1 text-xs text-gray-500">
|
|
129
|
+
Choose up to {maxSelections} labels.
|
|
130
|
+
</p>
|
|
131
|
+
)}
|
|
132
|
+
{options.length === 0 ? (
|
|
133
|
+
<div className="px-3 py-2 text-xs text-gray-400">
|
|
134
|
+
{isLoading ? "Loading options…" : "No options available"}
|
|
135
|
+
</div>
|
|
136
|
+
) : (
|
|
137
|
+
options.map((option: DashboardVariableOption) => {
|
|
138
|
+
const checked: boolean = selected.includes(option.value);
|
|
139
|
+
return (
|
|
140
|
+
<label
|
|
141
|
+
key={option.value}
|
|
142
|
+
className="flex items-center gap-2 px-3 py-1.5 text-xs text-gray-700 hover:bg-gray-50 cursor-pointer"
|
|
143
|
+
>
|
|
144
|
+
<input
|
|
145
|
+
type="checkbox"
|
|
146
|
+
className="h-3.5 w-3.5"
|
|
147
|
+
checked={checked}
|
|
148
|
+
disabled={
|
|
149
|
+
!checked &&
|
|
150
|
+
maxSelections !== undefined &&
|
|
151
|
+
selected.length >= maxSelections
|
|
152
|
+
}
|
|
153
|
+
onChange={() => {
|
|
154
|
+
toggle(option.value);
|
|
155
|
+
}}
|
|
156
|
+
/>
|
|
157
|
+
<span className="truncate">{option.label}</span>
|
|
158
|
+
</label>
|
|
159
|
+
);
|
|
160
|
+
})
|
|
161
|
+
)}
|
|
162
|
+
</div>
|
|
163
|
+
)}
|
|
164
|
+
</div>
|
|
165
|
+
);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const DashboardVariableControl: FunctionComponent<ComponentProps> = (
|
|
169
|
+
props: ComponentProps,
|
|
170
|
+
): ReactElement => {
|
|
171
|
+
const { variable } = props;
|
|
172
|
+
const isTelemetryAttribute: boolean =
|
|
173
|
+
variable.type === DashboardVariableType.TelemetryAttribute;
|
|
174
|
+
const isCustomList: boolean =
|
|
175
|
+
variable.type === DashboardVariableType.CustomList ||
|
|
176
|
+
Boolean(variable.customListValues);
|
|
177
|
+
|
|
178
|
+
const customListOptions: Array<string> = variable.customListValues
|
|
179
|
+
? variable.customListValues.split(",").map((v: string) => {
|
|
180
|
+
return v.trim();
|
|
181
|
+
})
|
|
182
|
+
: [];
|
|
183
|
+
|
|
184
|
+
const isProjectLabel: boolean =
|
|
185
|
+
variable.type === DashboardVariableType.ProjectLabel;
|
|
186
|
+
const options: Array<DashboardVariableOption> = isProjectLabel
|
|
187
|
+
? variable.labelOptions || []
|
|
188
|
+
: (isTelemetryAttribute
|
|
189
|
+
? props.dynamicOptions || []
|
|
190
|
+
: customListOptions
|
|
191
|
+
).map((value: string) => {
|
|
192
|
+
return { label: value, value };
|
|
193
|
+
});
|
|
194
|
+
const selectedValue: string =
|
|
195
|
+
variable.selectedValue ?? variable.defaultValue ?? "";
|
|
196
|
+
const isUnavailableSelection: boolean = Boolean(
|
|
197
|
+
isProjectLabel &&
|
|
198
|
+
selectedValue &&
|
|
199
|
+
!options.some((option: DashboardVariableOption) => {
|
|
200
|
+
return option.value === selectedValue;
|
|
201
|
+
}),
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
const useSelect: boolean =
|
|
205
|
+
isTelemetryAttribute || isCustomList || isProjectLabel;
|
|
206
|
+
const label: string = variable.label || variable.name;
|
|
207
|
+
|
|
208
|
+
return (
|
|
209
|
+
<div className="flex items-center gap-1.5">
|
|
210
|
+
<label className="text-xs font-medium text-gray-400 uppercase tracking-wide">
|
|
211
|
+
{label}
|
|
212
|
+
</label>
|
|
213
|
+
{useSelect && variable.isMultiSelect ? (
|
|
214
|
+
/*
|
|
215
|
+
* `selectedValues` only — never defaultValue. The popover renders
|
|
216
|
+
* "All" for an empty list, and DashboardVariableInterpolation
|
|
217
|
+
* resolves an empty list to no predicate, so the two agree. Seeding
|
|
218
|
+
* this from defaultValue would put a filter behind a control that
|
|
219
|
+
* reads "All".
|
|
220
|
+
*/
|
|
221
|
+
<MultiSelectPopover
|
|
222
|
+
options={options}
|
|
223
|
+
selected={variable.selectedValues || []}
|
|
224
|
+
label={label}
|
|
225
|
+
isLoading={Boolean(props.isLoadingOptions)}
|
|
226
|
+
maxSelections={isProjectLabel ? 100 : undefined}
|
|
227
|
+
unavailableLabel={isProjectLabel ? "Unavailable label" : undefined}
|
|
228
|
+
onChange={(next: Array<string>) => {
|
|
229
|
+
props.onVariableValueChange(variable.id, { selectedValues: next });
|
|
230
|
+
}}
|
|
231
|
+
/>
|
|
232
|
+
) : useSelect ? (
|
|
233
|
+
<select
|
|
234
|
+
aria-label={label}
|
|
235
|
+
className="text-xs border border-gray-200 rounded-md px-2.5 py-1.5 bg-white text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-100 focus:border-blue-300 transition-colors"
|
|
236
|
+
/*
|
|
237
|
+
* `??`, not `||`: picking "All" stores selectedValue as "", which is
|
|
238
|
+
* a real selection and must not collapse back to defaultValue. Only
|
|
239
|
+
* an unset (undefined) selection falls through to the default — the
|
|
240
|
+
* same rule the query side applies in
|
|
241
|
+
* Common/Utils/Dashboard/VariableInterpolation.ts, so what the
|
|
242
|
+
* toolbar shows and what the widgets are filtered by stay in step.
|
|
243
|
+
*/
|
|
244
|
+
value={variable.selectedValue ?? variable.defaultValue ?? ""}
|
|
245
|
+
disabled={props.isLoadingOptions}
|
|
246
|
+
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
247
|
+
props.onVariableValueChange(variable.id, {
|
|
248
|
+
selectedValue: e.target.value,
|
|
249
|
+
});
|
|
250
|
+
}}
|
|
251
|
+
>
|
|
252
|
+
<option value="">
|
|
253
|
+
{props.isLoadingOptions ? "Loading…" : "All"}
|
|
254
|
+
</option>
|
|
255
|
+
{isUnavailableSelection && (
|
|
256
|
+
<option value={selectedValue}>Unavailable label</option>
|
|
257
|
+
)}
|
|
258
|
+
{options.map((option: DashboardVariableOption) => {
|
|
259
|
+
return (
|
|
260
|
+
<option key={option.value} value={option.value}>
|
|
261
|
+
{option.label}
|
|
262
|
+
</option>
|
|
263
|
+
);
|
|
264
|
+
})}
|
|
265
|
+
</select>
|
|
266
|
+
) : (
|
|
267
|
+
<input
|
|
268
|
+
type="text"
|
|
269
|
+
aria-label={label}
|
|
270
|
+
className="text-xs border border-gray-200 rounded-md px-2.5 py-1.5 bg-white text-gray-700 w-28 focus:outline-none focus:ring-2 focus:ring-blue-100 focus:border-blue-300 transition-colors"
|
|
271
|
+
// `??` so clearing the box stays cleared. See the select above.
|
|
272
|
+
value={variable.selectedValue ?? variable.defaultValue ?? ""}
|
|
273
|
+
placeholder={variable.name}
|
|
274
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
275
|
+
props.onVariableValueChange(variable.id, {
|
|
276
|
+
selectedValue: e.target.value,
|
|
277
|
+
});
|
|
278
|
+
}}
|
|
279
|
+
/>
|
|
280
|
+
)}
|
|
281
|
+
</div>
|
|
282
|
+
);
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
export default DashboardVariableControl;
|
package/UI/esbuild-config.js
CHANGED
|
@@ -304,6 +304,8 @@ function createConfig(options) {
|
|
|
304
304
|
const isDev = process.env.NODE_ENV !== "production";
|
|
305
305
|
const isAnalyze = process.env.analyze === "true";
|
|
306
306
|
const reactRoot = resolvePackageRoot("react");
|
|
307
|
+
const reactRouterRoot = resolvePackageRoot("react-router");
|
|
308
|
+
const reactRouterDomRoot = resolvePackageRoot("react-router-dom");
|
|
307
309
|
const reactI18nextRoot = resolvePackageRoot("react-i18next");
|
|
308
310
|
const i18nextRoot = resolvePackageRoot("i18next");
|
|
309
311
|
|
|
@@ -356,6 +358,10 @@ function createConfig(options) {
|
|
|
356
358
|
react: reactRoot,
|
|
357
359
|
"react/jsx-runtime": path.join(reactRoot, "jsx-runtime.js"),
|
|
358
360
|
"react/jsx-dev-runtime": path.join(reactRoot, "jsx-dev-runtime.js"),
|
|
361
|
+
// Public dashboards reuse widgets from the private dashboard package.
|
|
362
|
+
// Their links must share the router context installed by the entry app.
|
|
363
|
+
"react-router": reactRouterRoot,
|
|
364
|
+
"react-router-dom": reactRouterDomRoot,
|
|
359
365
|
// Force a single instance of i18next/react-i18next so that translations
|
|
360
366
|
// initialized in the service entry are visible to Common UI components.
|
|
361
367
|
// Without this, Common's own node_modules copy gets a separate, never-
|
|
@@ -74,7 +74,8 @@ export default class DashboardMonitorListComponentUtil extends DashboardBaseComp
|
|
|
74
74
|
|
|
75
75
|
componentArguments.push({
|
|
76
76
|
name: "Title",
|
|
77
|
-
description:
|
|
77
|
+
description:
|
|
78
|
+
"Header shown above the monitor list. Use {{UNIT}} to show a dashboard variable’s selected label; configure Label Variable separately to filter monitors.",
|
|
78
79
|
required: false,
|
|
79
80
|
type: ComponentInputType.Text,
|
|
80
81
|
id: "title",
|
|
@@ -142,6 +143,16 @@ export default class DashboardMonitorListComponentUtil extends DashboardBaseComp
|
|
|
142
143
|
entityFilterModelType: EntityFilterModelType.Label,
|
|
143
144
|
});
|
|
144
145
|
|
|
146
|
+
componentArguments.push({
|
|
147
|
+
name: "Label Variable",
|
|
148
|
+
description:
|
|
149
|
+
"Filter monitors using a Project Labels dashboard variable. This is combined with the fixed filters above. All removes only the variable filter.",
|
|
150
|
+
required: false,
|
|
151
|
+
type: ComponentInputType.ProjectLabelVariable,
|
|
152
|
+
id: "labelVariableId",
|
|
153
|
+
section: FiltersSection,
|
|
154
|
+
});
|
|
155
|
+
|
|
145
156
|
return componentArguments;
|
|
146
157
|
}
|
|
147
158
|
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import Includes from "../../Types/BaseDatabase/Includes";
|
|
2
|
+
import IncludesAnyOfGroups from "../../Types/BaseDatabase/IncludesAnyOfGroups";
|
|
3
|
+
import DashboardVariable, {
|
|
4
|
+
DashboardVariableOption,
|
|
5
|
+
DashboardVariableType,
|
|
6
|
+
} from "../../Types/Dashboard/DashboardVariable";
|
|
7
|
+
import BadDataException from "../../Types/Exception/BadDataException";
|
|
8
|
+
import ObjectID from "../../Types/ObjectID";
|
|
9
|
+
import DashboardVariableInterpolation, {
|
|
10
|
+
ResolvedVariableValue,
|
|
11
|
+
} from "./VariableInterpolation";
|
|
12
|
+
|
|
13
|
+
export interface DashboardLabelFilterData {
|
|
14
|
+
labelIds?: Array<string> | undefined;
|
|
15
|
+
labelVariableId?: string | undefined;
|
|
16
|
+
variables?: Array<DashboardVariable> | undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default class DashboardLabelVariable {
|
|
20
|
+
public static getOptions(
|
|
21
|
+
variable: DashboardVariable,
|
|
22
|
+
): Array<DashboardVariableOption> {
|
|
23
|
+
const options: Array<DashboardVariableOption> | undefined =
|
|
24
|
+
variable.labelOptions;
|
|
25
|
+
if (
|
|
26
|
+
!Array.isArray(options) ||
|
|
27
|
+
options.length === 0 ||
|
|
28
|
+
options.length > 1000
|
|
29
|
+
) {
|
|
30
|
+
throw new BadDataException(
|
|
31
|
+
"A project label variable must offer between 1 and 1000 labels.",
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const seen: Set<string> = new Set<string>();
|
|
36
|
+
for (const option of options) {
|
|
37
|
+
if (
|
|
38
|
+
!option ||
|
|
39
|
+
typeof option.value !== "string" ||
|
|
40
|
+
!ObjectID.isValidUUID(option.value) ||
|
|
41
|
+
typeof option.label !== "string" ||
|
|
42
|
+
option.label.trim().length === 0 ||
|
|
43
|
+
option.label.length > 1024 ||
|
|
44
|
+
seen.has(option.value)
|
|
45
|
+
) {
|
|
46
|
+
throw new BadDataException(
|
|
47
|
+
"Project label variable choices must have unique label IDs and nonempty display names.",
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
seen.add(option.value);
|
|
51
|
+
}
|
|
52
|
+
return options;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public static getFilter(
|
|
56
|
+
data: DashboardLabelFilterData,
|
|
57
|
+
): Includes | IncludesAnyOfGroups | undefined {
|
|
58
|
+
const fixed: Array<string> = [...new Set(data.labelIds || [])];
|
|
59
|
+
if (!data.labelVariableId) {
|
|
60
|
+
return fixed.length > 0 ? new Includes(fixed) : undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const matching: Array<DashboardVariable> = (data.variables || []).filter(
|
|
64
|
+
(variable: DashboardVariable) => {
|
|
65
|
+
return variable.id === data.labelVariableId;
|
|
66
|
+
},
|
|
67
|
+
);
|
|
68
|
+
const variable: DashboardVariable | undefined = matching[0];
|
|
69
|
+
if (
|
|
70
|
+
matching.length !== 1 ||
|
|
71
|
+
!variable ||
|
|
72
|
+
variable.type !== DashboardVariableType.ProjectLabel
|
|
73
|
+
) {
|
|
74
|
+
throw new BadDataException(
|
|
75
|
+
"The monitor widget's label variable is missing or is not a project label variable. Edit the widget to select a label variable.",
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const allowed: Set<string> = new Set(
|
|
80
|
+
DashboardLabelVariable.getOptions(variable).map(
|
|
81
|
+
(option: DashboardVariableOption) => {
|
|
82
|
+
return option.value;
|
|
83
|
+
},
|
|
84
|
+
),
|
|
85
|
+
);
|
|
86
|
+
const resolved: ResolvedVariableValue | undefined =
|
|
87
|
+
DashboardVariableInterpolation.resolveValue(variable);
|
|
88
|
+
const selected: Array<string> =
|
|
89
|
+
resolved?.multi ||
|
|
90
|
+
(resolved?.scalar !== undefined ? [resolved.scalar] : []);
|
|
91
|
+
if (
|
|
92
|
+
selected.length > 100 ||
|
|
93
|
+
selected.some((value: string) => {
|
|
94
|
+
return !allowed.has(value);
|
|
95
|
+
})
|
|
96
|
+
) {
|
|
97
|
+
throw new BadDataException(
|
|
98
|
+
"Choose up to 100 labels offered by this dashboard variable, or select All.",
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (selected.length === 0) {
|
|
103
|
+
return fixed.length > 0 ? new Includes(fixed) : undefined;
|
|
104
|
+
}
|
|
105
|
+
const dynamic: Array<string> = [...new Set(selected)];
|
|
106
|
+
return fixed.length > 0
|
|
107
|
+
? new IncludesAnyOfGroups([fixed, dynamic])
|
|
108
|
+
: new Includes(dynamic);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
public static interpolateTitle(
|
|
112
|
+
title: string | undefined,
|
|
113
|
+
variables: Array<DashboardVariable> | undefined,
|
|
114
|
+
): string | undefined {
|
|
115
|
+
if (!title || !variables?.length) {
|
|
116
|
+
return title;
|
|
117
|
+
}
|
|
118
|
+
return title.replace(
|
|
119
|
+
/\{\{([a-zA-Z_][a-zA-Z0-9_]*)\}\}/g,
|
|
120
|
+
(placeholder: string, name: string): string => {
|
|
121
|
+
const variable: DashboardVariable | undefined = variables.find(
|
|
122
|
+
(item: DashboardVariable) => {
|
|
123
|
+
return item.name === name;
|
|
124
|
+
},
|
|
125
|
+
);
|
|
126
|
+
if (!variable) {
|
|
127
|
+
return placeholder;
|
|
128
|
+
}
|
|
129
|
+
const resolved: ResolvedVariableValue | undefined =
|
|
130
|
+
DashboardVariableInterpolation.resolveValue(variable);
|
|
131
|
+
if (!resolved) {
|
|
132
|
+
return "All";
|
|
133
|
+
}
|
|
134
|
+
const values: Array<string> = resolved.multi || [
|
|
135
|
+
resolved.scalar as string,
|
|
136
|
+
];
|
|
137
|
+
return values
|
|
138
|
+
.map((value: string): string => {
|
|
139
|
+
if (variable.type !== DashboardVariableType.ProjectLabel) {
|
|
140
|
+
return value;
|
|
141
|
+
}
|
|
142
|
+
return (
|
|
143
|
+
variable.labelOptions?.find((option: DashboardVariableOption) => {
|
|
144
|
+
return option.value === value;
|
|
145
|
+
})?.label || "Unavailable label"
|
|
146
|
+
);
|
|
147
|
+
})
|
|
148
|
+
.join(", ");
|
|
149
|
+
},
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -65,10 +65,25 @@ export default class DashboardVariableUrlState {
|
|
|
65
65
|
if (!fromName) {
|
|
66
66
|
return variable;
|
|
67
67
|
}
|
|
68
|
+
|
|
69
|
+
/*
|
|
70
|
+
* The URL's comma syntax cannot distinguish one multi-select choice
|
|
71
|
+
* from a scalar. Resolve that ambiguity using the saved definition.
|
|
72
|
+
*/
|
|
73
|
+
if (variable.isMultiSelect) {
|
|
74
|
+
return {
|
|
75
|
+
...variable,
|
|
76
|
+
selectedValue: undefined,
|
|
77
|
+
selectedValues:
|
|
78
|
+
fromName.selectedValues ||
|
|
79
|
+
(fromName.selectedValue ? [fromName.selectedValue] : []),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
68
82
|
return {
|
|
69
83
|
...variable,
|
|
70
|
-
selectedValue:
|
|
71
|
-
|
|
84
|
+
selectedValue:
|
|
85
|
+
fromName.selectedValue ?? fromName.selectedValues?.join(","),
|
|
86
|
+
selectedValues: undefined,
|
|
72
87
|
};
|
|
73
88
|
});
|
|
74
89
|
}
|
|
@@ -110,11 +125,18 @@ export default class DashboardVariableUrlState {
|
|
|
110
125
|
continue;
|
|
111
126
|
}
|
|
112
127
|
const paramKey: string = `${VAR_PREFIX}${name}`;
|
|
113
|
-
if (variable.
|
|
114
|
-
|
|
128
|
+
if (variable.isMultiSelect) {
|
|
129
|
+
if (Array.isArray(variable.selectedValues)) {
|
|
130
|
+
params.set(paramKey, variable.selectedValues.join(","));
|
|
131
|
+
}
|
|
115
132
|
continue;
|
|
116
133
|
}
|
|
117
|
-
|
|
134
|
+
|
|
135
|
+
/*
|
|
136
|
+
* Keep an explicit All as `var-name=`. Omitting it would restore the
|
|
137
|
+
* saved selection or default when this URL is reopened.
|
|
138
|
+
*/
|
|
139
|
+
if (typeof variable.selectedValue === "string") {
|
|
118
140
|
params.set(paramKey, variable.selectedValue);
|
|
119
141
|
}
|
|
120
142
|
}
|
package/Utils/MetricUnitUtil.ts
CHANGED
|
@@ -297,6 +297,40 @@ export default class MetricUnitUtil {
|
|
|
297
297
|
return (value * fromDef.toCanonical) / metricDef.toCanonical;
|
|
298
298
|
}
|
|
299
299
|
|
|
300
|
+
/*
|
|
301
|
+
* The canonical base of the family this unit belongs to — "B" for any
|
|
302
|
+
* data unit, "sec" for any time unit, "%" for percent, "bit" for bits.
|
|
303
|
+
* Null when the unit belongs to no known family.
|
|
304
|
+
*
|
|
305
|
+
* Distinct from getCanonicalUnitValue, which answers "what should the
|
|
306
|
+
* threshold dropdown default to for this metric" and returns the unit
|
|
307
|
+
* itself for a known family member. This answers "what is the one unit
|
|
308
|
+
* every member of this family can be converted into", which is what a
|
|
309
|
+
* formatter needs before it can pick a human-readable scale: a value
|
|
310
|
+
* carrying the unit "GB" has to become bytes before a bytes ladder can
|
|
311
|
+
* decide that 2500 of them read best as "2.5 TB".
|
|
312
|
+
*/
|
|
313
|
+
public static getFamilyBaseUnit(
|
|
314
|
+
metricUnit: string | undefined,
|
|
315
|
+
): string | null {
|
|
316
|
+
if (!metricUnit || !metricUnit.trim()) {
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const family: Array<UnitDefinition> | null = findFamily(metricUnit);
|
|
321
|
+
if (!family) {
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const base: UnitDefinition | undefined = family.find(
|
|
326
|
+
(u: UnitDefinition) => {
|
|
327
|
+
return u.toCanonical === 1;
|
|
328
|
+
},
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
return base?.value || null;
|
|
332
|
+
}
|
|
333
|
+
|
|
300
334
|
/*
|
|
301
335
|
* Convenience: does the metric unit belong to a family we can offer
|
|
302
336
|
* conversions for? When false, the UI should still render a dropdown,
|