@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
564 lines (562 loc) • 89.6 kB
JavaScript
import * as i0 from '@angular/core';
import { signal, inject, ViewChild, Input, Optional, Component } from '@angular/core';
import * as i3 from '@angular/forms';
import { FormBuilder, NgForm, Validators } from '@angular/forms';
import * as i2 from '@c8y/ngx-components';
import { GainsightService, CommonModule, CoreModule, FormsModule, WidgetTimeContextDateRangeService, AGGREGATION_ICONS, AGGREGATION_TEXTS, SelectComponent, SelectItemDirective, SelectedItemsDirective } from '@c8y/ngx-components';
import * as i7 from '@c8y/ngx-components/alarm-event-selector';
import { AlarmEventSelectorModule } from '@c8y/ngx-components/alarm-event-selector';
import * as i1 from '@c8y/ngx-components/context-dashboard';
import { WidgetConfigService } from '@c8y/ngx-components/context-dashboard';
import * as i6 from '@c8y/ngx-components/datapoint-selector';
import { DatapointSelectorModule } from '@c8y/ngx-components/datapoint-selector';
import { ChartHelpersService, CHART_VIEW_CONTEXT, LEGEND_DISPLAY_OPTIONS, PRODUCT_EXPERIENCE_DATA_EXPLORER_AND_GRAPH, ChartsComponent, ChartEventsService, ChartAlarmsService } from '@c8y/ngx-components/echart';
import * as i4$1 from '@c8y/ngx-components/global-context';
import { GlobalContextWidgetWrapperComponent, REFRESH_OPTION, GLOBAL_CONTEXT_DISPLAY_MODE, GLOBAL_CONTEXT_SOURCE, GLOBAL_CONTEXT_EVENTS } from '@c8y/ngx-components/global-context';
import { defaultWidgetIds } from '@c8y/ngx-components/widgets/definitions';
import * as i5 from 'ngx-bootstrap/popover';
import { PopoverModule } from 'ngx-bootstrap/popover';
import * as i4 from 'ngx-bootstrap/tooltip';
import { TooltipModule } from 'ngx-bootstrap/tooltip';
import { Subject, takeUntil, BehaviorSubject } from 'rxjs';
import { A11yModule } from '@angular/cdk/a11y';
import * as i5$1 from '@angular/common';
import { CommonModule as CommonModule$1 } from '@angular/common';
import { ALARM_STATUS_LABELS } from '@c8y/client';
import * as i9 from '@c8y/ngx-components/alarms';
import { AlarmsModule } from '@c8y/ngx-components/alarms';
import { gettext } from '@c8y/ngx-components/gettext';
import * as i1$1 from '@ngx-translate/core';
import { merge, cloneDeep } from 'lodash-es';
import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
class DatapointsGraphWidgetConfigComponent {
set config(value) {
if (!value)
return;
if (this.isFirstConfigSet) {
this._config.set({ ...value });
this.isFirstConfigSet = false;
}
else {
this._config.set({
...this._config(),
dateFrom: value.dateFrom,
dateTo: value.dateTo,
interval: value.interval,
refreshInterval: value.refreshInterval,
isAutoRefreshEnabled: value.isAutoRefreshEnabled,
aggregation: value.aggregation,
realtime: value.realtime,
displayMode: value.displayMode,
refreshOption: value.refreshOption,
dateTimeContext: value.dateTimeContext
});
}
}
get config() {
return this._config();
}
set previewMapSet(template) {
if (template) {
this.widgetConfigService.setPreview(template);
return;
}
this.widgetConfigService.setPreview(null);
}
constructor(widgetConfig, dashboardContextComponent, dynamicComponentService) {
this.widgetConfig = widgetConfig;
this.dashboardContextComponent = dashboardContextComponent;
this.dynamicComponentService = dynamicComponentService;
this._config = signal({}, ...(ngDevMode ? [{ debugName: "_config" }] : []));
this.isFirstConfigSet = true;
this.formBuilder = inject(FormBuilder);
this.form = inject(NgForm);
this.widgetConfigService = inject(WidgetConfigService);
this.chartHelpersService = inject(ChartHelpersService);
this.gainsightService = inject(GainsightService);
this.datapointSelectDefaultFormOptions = {
showRange: true,
showChart: true
};
this.datapointSelectionConfig = {};
this.activeDatapointsExists = false;
this.alarmsOrEventsHaveNoMatchingDps = false;
this.chartViewContext = CHART_VIEW_CONTEXT.WIDGET_CONFIG;
this.legendDisplayOptions = LEGEND_DISPLAY_OPTIONS;
this.destroy$ = new Subject();
this.isInitialized = false;
this.formGroup = this.initForm();
}
async ngOnInit() {
this.widgetControls =
(await this.dynamicComponentService.getById(defaultWidgetIds.DATAPOINTS_GRAPH_NEW)).data
?.widgetControls || {};
const currentConfig = this._config();
currentConfig.datapoints?.forEach(dp => this.assignContextFromContextDashboard(dp));
this.form.form.addControl('config', this.formGroup);
const alarms = currentConfig.alarmsEventsConfigs?.filter(ae => ae.timelineType === 'ALARM') || [];
const events = currentConfig.alarmsEventsConfigs?.filter(ae => ae.timelineType === 'EVENT') || [];
this.formGroup.patchValue({ ...currentConfig, alarms, events }, { emitEvent: false });
this.isInitialized = true;
this.formGroup.valueChanges.pipe(takeUntil(this.destroy$)).subscribe(formValue => {
const { alarms, events, datapoints } = formValue;
this._config.set({
...this._config(),
datapoints: datapoints || [],
dataPointLegendDisplay: formValue.dataPointLegendDisplay,
alarmsEventsConfigs: [...(alarms || []), ...(events || [])],
displayMarkedLine: formValue.displayMarkedLine,
displayMarkedPoint: formValue.displayMarkedPoint,
mergeMatchingDatapoints: formValue.mergeMatchingDatapoints,
forceMergeDatapoints: formValue.forceMergeDatapoints,
showLabelAndUnit: formValue.showLabelAndUnit,
showSlider: formValue.showSlider,
yAxisSplitLines: formValue.yAxisSplitLines,
xAxisSplitLines: formValue.xAxisSplitLines,
numberOfDecimalPlaces: formValue.numberOfDecimalPlaces,
aggregatedDatapoint: formValue.aggregatedDatapoint
});
this.setActiveDatapointsExists();
this.checkForMatchingDatapoints();
});
}
ngOnChanges(changes) {
if (!changes.config || changes.config.isFirstChange() || !this.isInitialized)
return;
this.setActiveDatapointsExists();
this.checkForMatchingDatapoints();
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
onBeforeSave(config) {
if (!this.formGroup.valid || !config)
return false;
const snapshot = { ...this._config() };
for (const key in snapshot) {
if (snapshot.hasOwnProperty(key)) {
try {
config[key] = snapshot[key];
}
catch (e) {
// do nothing
}
}
}
if (snapshot.alarmsEventsConfigs) {
config.alarmsEventsConfigs = [...snapshot.alarmsEventsConfigs];
}
if (snapshot.datapoints) {
config.datapoints = [...snapshot.datapoints];
}
this.widgetConfigService.updateConfig(snapshot, true);
const configSummaryGS = this.chartHelpersService.getConfigSummaryForGainsight(config);
this.gainsightService.triggerEvent(PRODUCT_EXPERIENCE_DATA_EXPLORER_AND_GRAPH.EVENTS.DATA_EXPLORER_AND_GRAPH, {
component: PRODUCT_EXPERIENCE_DATA_EXPLORER_AND_GRAPH.COMPONENTS.DATA_EXPLORER_DETAILS,
action: PRODUCT_EXPERIENCE_DATA_EXPLORER_AND_GRAPH.ACTIONS.DATA_GRAPH_WIDGET_CONFIG,
...configSummaryGS
});
return true;
}
updateTimeRangeOnRealtime(timeRange) {
const current = this._config();
this._config.set({
...current,
...timeRange,
interval: current.interval || 'hours'
});
}
updateDashboardTimeContext(timeProps) {
const dateTimeContext = {
dateFrom: new Date(timeProps.dateFrom).toISOString(),
dateTo: new Date(timeProps.dateTo).toISOString(),
interval: timeProps.interval
};
this._config.set({
...this._config(),
dateTimeContext
});
}
updateAggregatedSliderDatapoint(selectedDatapoint) {
const aggregatedDatapoint = this.chartHelpersService.findMatchingDatapoint(this._config().datapoints || [], selectedDatapoint);
this._config.set({
...this._config(),
aggregatedDatapoint
});
}
assignContextFromContextDashboard(datapoint) {
if (!this.dashboardContextComponent?.isDeviceTypeDashboard) {
return;
}
const context = this.widgetConfig?.context;
if (context?.id) {
const { name, id } = context;
datapoint.__target = { name, id };
this.datapointSelectionConfig.contextAsset = { id };
}
}
checkForMatchingDatapoints() {
const config = this._config();
const alarmsEventsConfigs = config.alarmsEventsConfigs || [];
const datapoints = config.datapoints || [];
const allMatch = alarmsEventsConfigs.every(ae => datapoints.some(dp => dp.__target?.id === ae.__target?.id));
queueMicrotask(() => {
this.alarmsOrEventsHaveNoMatchingDps = !allMatch;
});
}
initForm() {
const initialState = this._config();
const form = this.formBuilder.group({
datapoints: [
[],
[Validators.required, Validators.minLength(1)]
],
dataPointLegendDisplay: ['auto', []],
alarms: [[]],
events: [[]],
displayMarkedLine: [true, []],
displayMarkedPoint: [true, []],
mergeMatchingDatapoints: [true, []],
forceMergeDatapoints: [false, []],
showLabelAndUnit: [true, []],
displayAggregationSelection: [false, []],
canDecoupleGlobalTimeContext: [false, []],
showSlider: [true, [Validators.required]],
yAxisSplitLines: [false, [Validators.required]],
xAxisSplitLines: [false, [Validators.required]],
numberOfDecimalPlaces: [2, [Validators.required, Validators.min(0), Validators.max(10)]],
aggregatedDatapoint: [initialState?.aggregatedDatapoint || null]
});
return form;
}
setActiveDatapointsExists() {
const datapoints = this._config().datapoints || [];
this.activeDatapointsExists = datapoints.filter(dp => dp.__active).length > 0;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: DatapointsGraphWidgetConfigComponent, deps: [{ token: i1.WidgetConfigComponent, optional: true }, { token: i1.ContextDashboardComponent, optional: true }, { token: i2.DynamicComponentService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: DatapointsGraphWidgetConfigComponent, isStandalone: true, selector: "c8y-datapoints-graph-widget-config", inputs: { config: "config" }, host: { classAttribute: "d-contents" }, providers: [
ChartEventsService,
ChartAlarmsService,
ChartHelpersService,
WidgetTimeContextDateRangeService
], viewQueries: [{ propertyName: "previewMapSet", first: true, predicate: ["dataPointsGraphPreview"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<form [formGroup]=\"formGroup\">\n <c8y-datapoint-selection-list\n class=\"bg-component separator-bottom d-block\"\n name=\"datapoints\"\n [minActiveCount]=\"1\"\n [defaultFormOptions]=\"datapointSelectDefaultFormOptions\"\n [config]=\"datapointSelectionConfig\"\n formControlName=\"datapoints\"\n ></c8y-datapoint-selection-list>\n\n <c8y-alarm-event-selection-list\n class=\"bg-component separator-bottom d-block\"\n name=\"alarms\"\n formControlName=\"alarms\"\n [timelineType]=\"'ALARM'\"\n [datapoints]=\"config?.datapoints\"\n ></c8y-alarm-event-selection-list>\n\n <c8y-alarm-event-selection-list\n class=\"bg-inherit\"\n name=\"events\"\n formControlName=\"events\"\n [timelineType]=\"'EVENT'\"\n [datapoints]=\"config?.datapoints\"\n ></c8y-alarm-event-selection-list>\n\n <div class=\"form-group form-group-sm\">\n <label\n [title]=\"'Number of decimal places' | translate\"\n translate\n >\n Number of decimal places\n </label>\n <input\n class=\"form-control\"\n name=\"numberOfDecimalPlaces\"\n type=\"number\"\n formControlName=\"numberOfDecimalPlaces\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 1 }\"\n />\n <c8y-messages\n [show]=\"\n formGroup.controls?.numberOfDecimalPlaces?.touched &&\n formGroup?.controls?.numberOfDecimalPlaces?.errors\n \"\n ></c8y-messages>\n </div>\n <c8y-form-group class=\"form-group-sm\">\n <label>\n {{ 'Data point legend display' | translate }}\n </label>\n <div\n class=\"c8y-select-wrapper\"\n [formGroup]=\"formGroup\"\n >\n <select\n class=\"form-control\"\n [title]=\"'Data point legend display' | translate\"\n id=\"dataPointLegendDisplay\"\n formControlName=\"dataPointLegendDisplay\"\n >\n @for (option of legendDisplayOptions; track option) {\n <option [ngValue]=\"option.value\">\n {{ option.label | translate }}\n </option>\n }\n </select>\n </div>\n </c8y-form-group>\n</form>\n\n<form\n class=\"d-block p-t-8\"\n [formGroup]=\"formGroup\"\n>\n <label>{{ 'Display options' | translate }}</label>\n <fieldset class=\"c8y-fieldset m-b-24 m-t-0\">\n <legend>{{ 'Axis' | translate }}</legend>\n <c8y-form-group class=\"p-b-16 m-b-0 p-t-8 form-group-sm\">\n <label\n class=\"c8y-checkbox\"\n [title]=\"'Y-axis helper lines' | translate\"\n >\n <input\n name=\"yAxisSplitLines\"\n type=\"checkbox\"\n formControlName=\"yAxisSplitLines\"\n />\n <span></span>\n <span translate>Y-axis helper lines</span>\n </label>\n <label\n class=\"c8y-checkbox\"\n [title]=\"'X-axis helper lines' | translate\"\n >\n <input\n name=\"xAxisSplitLines\"\n type=\"checkbox\"\n formControlName=\"xAxisSplitLines\"\n />\n <span></span>\n <span translate>X-axis helper lines</span>\n </label>\n <label\n class=\"c8y-checkbox\"\n [title]=\"'Merge matching data points into single axis' | translate\"\n >\n <input\n name=\"mergeMatchingDatapoints\"\n type=\"checkbox\"\n formControlName=\"mergeMatchingDatapoints\"\n />\n <span></span>\n <span translate>Merge matching data points into single axis</span>\n <button\n class=\"btn-help\"\n [attr.aria-label]=\"\n 'Data points with the same min and max values will be merged into a single axis. The values must be defined in the data point configuration.'\n | translate\n \"\n [popover]=\"\n 'Data points with the same min and max values will be merged into a single axis. The values must be defined in the data point configuration.'\n | translate\n \"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n (click)=\"$event.stopPropagation()\"\n [adaptivePosition]=\"false\"\n ></button>\n </label>\n <label\n class=\"c8y-checkbox\"\n [title]=\"'Force merge all data points into single axis' | translate\"\n >\n <input\n name=\"forceMergeDatapoints\"\n type=\"checkbox\"\n formControlName=\"forceMergeDatapoints\"\n />\n <span></span>\n <span translate>Force merge all datapoints into a single axis</span>\n <button\n class=\"btn-help\"\n [attr.aria-label]=\"\n 'All axes will be force merged to a single axis with the scale being set to the max and min value of all axes. It\\'s recommended to use this option for data points with similar values.'\n | translate\n \"\n [popover]=\"\n 'All axes will be force merged to a single axis with the scale being set to the max and min value of all axes. It\\'s recommended to use this option for data points with similar values.'\n | translate\n \"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n (click)=\"$event.stopPropagation()\"\n [adaptivePosition]=\"false\"\n ></button>\n </label>\n </c8y-form-group>\n </fieldset>\n <fieldset class=\"c8y-fieldset m-b-24 m-t-0\">\n <legend>{{ 'Alarms & events' | translate }}</legend>\n <c8y-form-group class=\"p-b-16 m-b-0 p-t-8 form-group-sm\">\n <label\n class=\"c8y-checkbox\"\n [title]=\"'Show vertical line on every occurrence' | translate\"\n >\n <input\n name=\"displayMarkedLine\"\n type=\"checkbox\"\n formControlName=\"displayMarkedLine\"\n />\n <span></span>\n <span translate>Show vertical line on every occurrence</span>\n </label>\n <label\n class=\"c8y-checkbox\"\n [title]=\"'Show icon when triggered' | translate\"\n >\n <input\n name=\"displayMarkedPoint\"\n type=\"checkbox\"\n formControlName=\"displayMarkedPoint\"\n />\n <span></span>\n <span translate>Show icon when triggered</span>\n @if (alarmsOrEventsHaveNoMatchingDps) {\n <button\n class=\"btn-clean m-l-8\"\n [attr.aria-label]=\"\n 'Some alarms or events have no matching data points. No icons will be shown for them.'\n | translate\n \"\n [tooltip]=\"\n 'Some alarms or events have no matching data points. No icons will be shown for them.'\n | translate\n \"\n container=\"body\"\n type=\"button\"\n (click)=\"$event.stopPropagation()\"\n [adaptivePosition]=\"false\"\n >\n <i\n class=\"text-warning\"\n c8yIcon=\"exclamation-triangle\"\n ></i>\n </button>\n }\n </label>\n </c8y-form-group>\n </fieldset>\n <fieldset class=\"c8y-fieldset m-b-24 m-t-0\">\n <legend>{{ 'Chart' | translate }}</legend>\n <c8y-form-group class=\"p-b-16 m-b-0 p-t-8 form-group-sm\">\n <label\n class=\"c8y-checkbox\"\n [title]=\"'Show labels and units' | translate\"\n >\n <input\n name=\"showLabelAndUnit\"\n type=\"checkbox\"\n formControlName=\"showLabelAndUnit\"\n />\n <span></span>\n <span translate>Display labels and units on Y-axis</span>\n </label>\n <label\n class=\"c8y-checkbox\"\n [title]=\"'Show slider' | translate\"\n >\n <input\n name=\"showSlider\"\n type=\"checkbox\"\n formControlName=\"showSlider\"\n />\n <span></span>\n <span translate>Show slider</span>\n </label>\n </c8y-form-group>\n </fieldset>\n</form>\n\n<ng-template #dataPointsGraphPreview>\n @if (widgetControls) {\n <c8y-global-context-widget-wrapper\n [config]=\"config\"\n [displayMode]=\"'preview'\"\n [widgetControls]=\"widgetControls\"\n ></c8y-global-context-widget-wrapper>\n }\n\n @if (activeDatapointsExists) {\n <c8y-charts\n class=\"d-block p-relative\"\n [config]=\"config\"\n [alerts]=\"alerts\"\n [chartViewContext]=\"chartViewContext\"\n (timeRangeChangeOnRealtime)=\"updateTimeRangeOnRealtime($event)\"\n (configChangeOnZoomOut)=\"updateDashboardTimeContext($event)\"\n (updateAggregatedSliderDatapoint)=\"updateAggregatedSliderDatapoint($event)\"\n ></c8y-charts>\n }\n\n @if (!activeDatapointsExists) {\n <c8y-ui-empty-state\n class=\"d-block m-t-24\"\n [icon]=\"'search'\"\n [title]=\"'No data points selected' | translate\"\n [subtitle]=\"'Select data point to render content' | translate\"\n data-cy=\"datapoints-graph-list--empty-state-no-data-point-selected\"\n >\n <p c8y-guide-docs>\n <small translate>\n Find out more in the\n <a c8y-guide-href=\"/docs/cockpit/widgets-collection/#data-point-graph\">\n user documentation\n </a>\n .\n </small>\n </p>\n </c8y-ui-empty-state>\n }\n</ng-template>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: i2.EmptyStateComponent, selector: "c8y-ui-empty-state", inputs: ["icon", "title", "subtitle", "horizontal"] }, { kind: "directive", type: i2.IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: i2.C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "ngmodule", type: CoreModule }, { kind: "directive", type: i3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i3.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i3.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i3.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "component", type: i2.FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "component", type: i2.MessagesComponent, selector: "c8y-messages", inputs: ["show", "defaults", "helpMessage"] }, { kind: "directive", type: i2.RequiredInputPlaceholderDirective, selector: "input[required], input[formControlName]" }, { kind: "directive", type: i2.GuideHrefDirective, selector: "[c8y-guide-href]", inputs: ["c8y-guide-href"] }, { kind: "component", type: i2.GuideDocsComponent, selector: "[c8y-guide-docs]" }, { kind: "directive", type: i3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i3.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i4.TooltipDirective, selector: "[tooltip], [tooltipHtml]", inputs: ["adaptivePosition", "tooltip", "placement", "triggers", "container", "containerClass", "boundariesElement", "isOpen", "isDisabled", "delay", "tooltipHtml", "tooltipPlacement", "tooltipIsOpen", "tooltipEnable", "tooltipAppendToBody", "tooltipAnimation", "tooltipClass", "tooltipContext", "tooltipPopupDelay", "tooltipFadeDuration", "tooltipTrigger"], outputs: ["tooltipChange", "onShown", "onHidden", "tooltipStateChanged"], exportAs: ["bs-tooltip"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "directive", type: i5.PopoverDirective, selector: "[popover]", inputs: ["adaptivePosition", "boundariesElement", "popover", "popoverContext", "popoverTitle", "placement", "outsideClick", "triggers", "container", "containerClass", "isOpen", "delay"], outputs: ["onShown", "onHidden"], exportAs: ["bs-popover"] }, { kind: "component", type: ChartsComponent, selector: "c8y-charts", inputs: ["config", "alerts", "chartViewContext"], outputs: ["configChangeOnZoomOut", "timeRangeChangeOnRealtime", "datapointOutOfSync", "updateAlarmsAndEvents", "isMarkedAreaEnabled", "finishLoading", "updateActiveDatapoints", "updateAggregatedSliderDatapoint"] }, { kind: "ngmodule", type: DatapointSelectorModule }, { kind: "component", type: i6.DatapointSelectionListComponent, selector: "c8y-datapoint-selection-list", inputs: ["actions", "allowDragAndDrop", "config", "defaultFormOptions", "maxActiveCount", "minActiveCount", "resolveContext", "listTitle"], outputs: ["isValid", "change"] }, { kind: "ngmodule", type: AlarmEventSelectorModule }, { kind: "component", type: i7.AlarmEventSelectionListComponent, selector: "c8y-alarm-event-selection-list", inputs: ["timelineType", "canRemove", "canEdit", "canDragAndDrop", "title", "addButtonLabel", "hideSource", "inline", "activeToggleAsSwitch", "omitProperties", "datapoints", "config"] }, { kind: "component", type: GlobalContextWidgetWrapperComponent, selector: "c8y-global-context-widget-wrapper", inputs: ["isLoading", "displayMode", "widgetControls", "controlLinks", "dashboardChildForLegacy", "config"], outputs: ["globalContextChange"] }, { kind: "pipe", type: i2.C8yTranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: DatapointsGraphWidgetConfigComponent, decorators: [{
type: Component,
args: [{ selector: 'c8y-datapoints-graph-widget-config', host: { class: 'd-contents' }, standalone: true, imports: [
CommonModule,
CoreModule,
FormsModule,
TooltipModule,
PopoverModule,
ChartsComponent,
DatapointSelectorModule,
AlarmEventSelectorModule,
GlobalContextWidgetWrapperComponent
], providers: [
ChartEventsService,
ChartAlarmsService,
ChartHelpersService,
WidgetTimeContextDateRangeService
], template: "<form [formGroup]=\"formGroup\">\n <c8y-datapoint-selection-list\n class=\"bg-component separator-bottom d-block\"\n name=\"datapoints\"\n [minActiveCount]=\"1\"\n [defaultFormOptions]=\"datapointSelectDefaultFormOptions\"\n [config]=\"datapointSelectionConfig\"\n formControlName=\"datapoints\"\n ></c8y-datapoint-selection-list>\n\n <c8y-alarm-event-selection-list\n class=\"bg-component separator-bottom d-block\"\n name=\"alarms\"\n formControlName=\"alarms\"\n [timelineType]=\"'ALARM'\"\n [datapoints]=\"config?.datapoints\"\n ></c8y-alarm-event-selection-list>\n\n <c8y-alarm-event-selection-list\n class=\"bg-inherit\"\n name=\"events\"\n formControlName=\"events\"\n [timelineType]=\"'EVENT'\"\n [datapoints]=\"config?.datapoints\"\n ></c8y-alarm-event-selection-list>\n\n <div class=\"form-group form-group-sm\">\n <label\n [title]=\"'Number of decimal places' | translate\"\n translate\n >\n Number of decimal places\n </label>\n <input\n class=\"form-control\"\n name=\"numberOfDecimalPlaces\"\n type=\"number\"\n formControlName=\"numberOfDecimalPlaces\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 1 }\"\n />\n <c8y-messages\n [show]=\"\n formGroup.controls?.numberOfDecimalPlaces?.touched &&\n formGroup?.controls?.numberOfDecimalPlaces?.errors\n \"\n ></c8y-messages>\n </div>\n <c8y-form-group class=\"form-group-sm\">\n <label>\n {{ 'Data point legend display' | translate }}\n </label>\n <div\n class=\"c8y-select-wrapper\"\n [formGroup]=\"formGroup\"\n >\n <select\n class=\"form-control\"\n [title]=\"'Data point legend display' | translate\"\n id=\"dataPointLegendDisplay\"\n formControlName=\"dataPointLegendDisplay\"\n >\n @for (option of legendDisplayOptions; track option) {\n <option [ngValue]=\"option.value\">\n {{ option.label | translate }}\n </option>\n }\n </select>\n </div>\n </c8y-form-group>\n</form>\n\n<form\n class=\"d-block p-t-8\"\n [formGroup]=\"formGroup\"\n>\n <label>{{ 'Display options' | translate }}</label>\n <fieldset class=\"c8y-fieldset m-b-24 m-t-0\">\n <legend>{{ 'Axis' | translate }}</legend>\n <c8y-form-group class=\"p-b-16 m-b-0 p-t-8 form-group-sm\">\n <label\n class=\"c8y-checkbox\"\n [title]=\"'Y-axis helper lines' | translate\"\n >\n <input\n name=\"yAxisSplitLines\"\n type=\"checkbox\"\n formControlName=\"yAxisSplitLines\"\n />\n <span></span>\n <span translate>Y-axis helper lines</span>\n </label>\n <label\n class=\"c8y-checkbox\"\n [title]=\"'X-axis helper lines' | translate\"\n >\n <input\n name=\"xAxisSplitLines\"\n type=\"checkbox\"\n formControlName=\"xAxisSplitLines\"\n />\n <span></span>\n <span translate>X-axis helper lines</span>\n </label>\n <label\n class=\"c8y-checkbox\"\n [title]=\"'Merge matching data points into single axis' | translate\"\n >\n <input\n name=\"mergeMatchingDatapoints\"\n type=\"checkbox\"\n formControlName=\"mergeMatchingDatapoints\"\n />\n <span></span>\n <span translate>Merge matching data points into single axis</span>\n <button\n class=\"btn-help\"\n [attr.aria-label]=\"\n 'Data points with the same min and max values will be merged into a single axis. The values must be defined in the data point configuration.'\n | translate\n \"\n [popover]=\"\n 'Data points with the same min and max values will be merged into a single axis. The values must be defined in the data point configuration.'\n | translate\n \"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n (click)=\"$event.stopPropagation()\"\n [adaptivePosition]=\"false\"\n ></button>\n </label>\n <label\n class=\"c8y-checkbox\"\n [title]=\"'Force merge all data points into single axis' | translate\"\n >\n <input\n name=\"forceMergeDatapoints\"\n type=\"checkbox\"\n formControlName=\"forceMergeDatapoints\"\n />\n <span></span>\n <span translate>Force merge all datapoints into a single axis</span>\n <button\n class=\"btn-help\"\n [attr.aria-label]=\"\n 'All axes will be force merged to a single axis with the scale being set to the max and min value of all axes. It\\'s recommended to use this option for data points with similar values.'\n | translate\n \"\n [popover]=\"\n 'All axes will be force merged to a single axis with the scale being set to the max and min value of all axes. It\\'s recommended to use this option for data points with similar values.'\n | translate\n \"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n (click)=\"$event.stopPropagation()\"\n [adaptivePosition]=\"false\"\n ></button>\n </label>\n </c8y-form-group>\n </fieldset>\n <fieldset class=\"c8y-fieldset m-b-24 m-t-0\">\n <legend>{{ 'Alarms & events' | translate }}</legend>\n <c8y-form-group class=\"p-b-16 m-b-0 p-t-8 form-group-sm\">\n <label\n class=\"c8y-checkbox\"\n [title]=\"'Show vertical line on every occurrence' | translate\"\n >\n <input\n name=\"displayMarkedLine\"\n type=\"checkbox\"\n formControlName=\"displayMarkedLine\"\n />\n <span></span>\n <span translate>Show vertical line on every occurrence</span>\n </label>\n <label\n class=\"c8y-checkbox\"\n [title]=\"'Show icon when triggered' | translate\"\n >\n <input\n name=\"displayMarkedPoint\"\n type=\"checkbox\"\n formControlName=\"displayMarkedPoint\"\n />\n <span></span>\n <span translate>Show icon when triggered</span>\n @if (alarmsOrEventsHaveNoMatchingDps) {\n <button\n class=\"btn-clean m-l-8\"\n [attr.aria-label]=\"\n 'Some alarms or events have no matching data points. No icons will be shown for them.'\n | translate\n \"\n [tooltip]=\"\n 'Some alarms or events have no matching data points. No icons will be shown for them.'\n | translate\n \"\n container=\"body\"\n type=\"button\"\n (click)=\"$event.stopPropagation()\"\n [adaptivePosition]=\"false\"\n >\n <i\n class=\"text-warning\"\n c8yIcon=\"exclamation-triangle\"\n ></i>\n </button>\n }\n </label>\n </c8y-form-group>\n </fieldset>\n <fieldset class=\"c8y-fieldset m-b-24 m-t-0\">\n <legend>{{ 'Chart' | translate }}</legend>\n <c8y-form-group class=\"p-b-16 m-b-0 p-t-8 form-group-sm\">\n <label\n class=\"c8y-checkbox\"\n [title]=\"'Show labels and units' | translate\"\n >\n <input\n name=\"showLabelAndUnit\"\n type=\"checkbox\"\n formControlName=\"showLabelAndUnit\"\n />\n <span></span>\n <span translate>Display labels and units on Y-axis</span>\n </label>\n <label\n class=\"c8y-checkbox\"\n [title]=\"'Show slider' | translate\"\n >\n <input\n name=\"showSlider\"\n type=\"checkbox\"\n formControlName=\"showSlider\"\n />\n <span></span>\n <span translate>Show slider</span>\n </label>\n </c8y-form-group>\n </fieldset>\n</form>\n\n<ng-template #dataPointsGraphPreview>\n @if (widgetControls) {\n <c8y-global-context-widget-wrapper\n [config]=\"config\"\n [displayMode]=\"'preview'\"\n [widgetControls]=\"widgetControls\"\n ></c8y-global-context-widget-wrapper>\n }\n\n @if (activeDatapointsExists) {\n <c8y-charts\n class=\"d-block p-relative\"\n [config]=\"config\"\n [alerts]=\"alerts\"\n [chartViewContext]=\"chartViewContext\"\n (timeRangeChangeOnRealtime)=\"updateTimeRangeOnRealtime($event)\"\n (configChangeOnZoomOut)=\"updateDashboardTimeContext($event)\"\n (updateAggregatedSliderDatapoint)=\"updateAggregatedSliderDatapoint($event)\"\n ></c8y-charts>\n }\n\n @if (!activeDatapointsExists) {\n <c8y-ui-empty-state\n class=\"d-block m-t-24\"\n [icon]=\"'search'\"\n [title]=\"'No data points selected' | translate\"\n [subtitle]=\"'Select data point to render content' | translate\"\n data-cy=\"datapoints-graph-list--empty-state-no-data-point-selected\"\n >\n <p c8y-guide-docs>\n <small translate>\n Find out more in the\n <a c8y-guide-href=\"/docs/cockpit/widgets-collection/#data-point-graph\">\n user documentation\n </a>\n .\n </small>\n </p>\n </c8y-ui-empty-state>\n }\n</ng-template>\n" }]
}], ctorParameters: () => [{ type: i1.WidgetConfigComponent, decorators: [{
type: Optional
}] }, { type: i1.ContextDashboardComponent, decorators: [{
type: Optional
}] }, { type: i2.DynamicComponentService }], propDecorators: { config: [{
type: Input
}], previewMapSet: [{
type: ViewChild,
args: ['dataPointsGraphPreview']
}] } });
class DatapointsGraphWidgetViewComponent {
set config(value) {
// Special case: sync the config with migrated values.
// This is needed to reflect migrated states in the widget config.
const migratedConfig = this.widgetConfigMigrationService.migrateWidgetConfig(value);
const newConfig = merge(value, migratedConfig);
this.displayConfig = cloneDeep(newConfig);
}
get config() {
throw Error('"config" property should not be referenced in view component to avoid mutating data.');
}
constructor(translate, dashboardContextComponent, dynamicComponentService, widgetConfigMigrationService, widgetTimeContextDateRangeService, cdr) {
this.translate = translate;
this.dashboardContextComponent = dashboardContextComponent;
this.dynamicComponentService = dynamicComponentService;
this.widgetConfigMigrationService = widgetConfigMigrationService;
this.widgetTimeContextDateRangeService = widgetTimeContextDateRangeService;
this.cdr = cdr;
this.events = [];
this.alarms = [];
this.AGGREGATION_ICONS = AGGREGATION_ICONS;
this.AGGREGATION_TEXTS = AGGREGATION_TEXTS;
this.datapointsOutOfSync = new Map();
this.hasAtLeastOneDatapointActive = true;
this.hasAtLeastOneAlarmActive = true;
this.isMarkedAreaEnabled = false;
this.loadedDatapoints = [];
this.loadedAlarmsOrEvents = [];
this.isLoading$ = new BehaviorSubject(false);
this.isSliderBeingDragged$ = new BehaviorSubject(false);
this.chartViewContext = CHART_VIEW_CONTEXT.WIDGET_VIEW;
this.globalContextState = null;
/** Combined number of datapoints, alarms, and events */
this.totalLegendItems = 0;
/** Selectable items for the datapoints, alarms, events dropdown */
this.selectableItems = [];
/** Selected items from the dropdown */
this.selectedItems = [];
this.legendHelp = this.translate.instant(gettext(`<ul class="m-l-0 p-l-8 m-t-8 m-b-0">
<li>
<b>Visibility:</b>
use visibility icon to toggle datapoint, alarm or event visibility on chart. At least one datapoint is required to display chart.
</li>
<li>
<b>Alarm details</b>
Click alarm legend item to highlight area between alarm raised timestamp and alarm cleared timestamp.
You can also click alarm markline on chart to highlight alarm and to pause tooltip. Click on highlighted area or legend item to cancel highlighting.
</li>
</ul>`));
this.disableZoomInLabel = gettext('Disable zoom in');
this.enableZoomInLabel = gettext('Click to enable zoom, then click and drag on the desired area in the chart.');
this.hideDatapointLabel = gettext('Hide data point');
this.showDatapointLabel = gettext('Show data point');
this.destroy$ = new Subject();
this.widgetInstanceId = crypto.randomUUID();
}
async ngOnInit() {
this.displayConfig?.datapoints?.forEach(dp => this.assignContextFromContextDashboard(dp));
this.displayConfig.isRealtimeEnabled = this.displayConfig.realtime;
this.totalLegendItems =
(this.displayConfig?.datapoints?.length || 0) +
(this.displayConfig?.alarmsEventsConfigs?.length || 0);
this.loadedDatapoints = (this.displayConfig?.datapoints || []).filter(dp => dp.__active);
this.loadedAlarmsOrEvents = (this.displayConfig?.alarmsEventsConfigs || []).filter(aOrE => aOrE.__active && !aOrE.__hidden);
if (this.totalLegendItems > 5 || this.displayConfig?.dataPointLegendDisplay === 'dropdown') {
this.selectableItems = this.buildSelectableItems();
// Initialize selectedItems with the currently active items
this.selectedItems = this.selectableItems.filter(item => {
if (item.value.type === 'DATAPOINT') {
return !!item.value.original.__active;
}
else {
return item.value.original.__active && !item.value.original.__hidden;
}
});
}
this.widgetControls =
(await this.dynamicComponentService.getById(defaultWidgetIds.DATAPOINTS_GRAPH_NEW)).data
?.widgetControls || {};
}
onGlobalContextChange(event) {
const { context, diff } = event;
const { dateTimeContext } = context;
const { dateFrom, dateTo, interval } = dateTimeContext;
const isRealtimeEnabled = context.refreshOption === REFRESH_OPTION.LIVE &&
context.isAutoRefreshEnabled &&
context.refreshInterval === 5000;
if (this.displayConfig.displayMode !== GLOBAL_CONTEXT_DISPLAY_MODE.DASHBOARD) {
this.displayConfig = {
...this.displayConfig,
...structuredClone(context),
dateFrom: new Date(dateFrom),
dateTo: new Date(dateTo),
interval
};
this.globalContextState = {
...this.displayConfig,
realtime: isRealtimeEnabled,
dateTimeContext: { ...this.displayConfig.dateTimeContext }
};
if (this.isSliderBeingDragged$.value === true) {
this.isSliderBeingDragged$.next(false);
return;
}
this.widgetTimeContextDateRangeService.updateInitialTimeRange(null);
return;
}
if (this.displayConfig?.realtime !== isRealtimeEnabled) {
// Sync dates to avoid jumps in the chart
this.displayConfig = merge(this.displayConfig, {
dateTimeContext: structuredClone(context),
dateFrom: new Date(dateFrom),
dateTo: new Date(dateTo),
interval
});
// Update global context state when realtime state changes
this.globalContextState = merge({}, this.globalContextState, this.displayConfig);
this.displayConfig = {
...this.displayConfig,
realtime: isRealtimeEnabled
};
this.globalContextState = { ...this.globalContextState, realtime: isRealtimeEnabled };
}
// Realtime is a special case, we need to block "automatic" emissions from the global context
// GC in auto mode emits every 5 seconds. We only want to react to user changes.
if (isRealtimeEnabled &&
diff?.dateTimeContext &&
Object.keys(diff).length === 1 &&
Object.keys(diff.dateTimeContext).length === 2) {
// Sync dates to avoid jumps in the chart
this.displayConfig = merge(this.displayConfig, {
dateTimeContext: structuredClone(context),
dateFrom: new Date(dateFrom),
dateTo: new Date(dateTo),
interval
});
this.globalContextState = merge({}, this.globalContextState, this.displayConfig);
return;
}
this.displayConfig = {
...this.displayConfig,
...structuredClone(context),
dateFrom: new Date(dateFrom),
dateTo: new Date(dateTo),
interval
};
this.globalContextState = {
...this.displayConfig,
dateTimeContext: { ...this.displayConfig.dateTimeContext }
};
if (context.source === GLOBAL_CONTEXT_SOURCE.DASHBOARD &&
this.displayConfig.displayMode === GLOBAL_CONTEXT_DISPLAY_MODE.DASHBOARD) {
this.widgetTimeContextDateRangeService.updateInitialTimeRange(null);
}
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
updateDashboardTimeContext(timeProps) {
const chartOptions = this.chartComponent?.echartsInstance?.getOption();
const isEndZoomChanged = chartOptions?.dataZoom[0]?.end !== 100;
if (this.displayConfig.displayMode === GLOBAL_CONTEXT_DISPLAY_MODE.DASHBOARD) {
const dateTimeContext = isEndZoomChanged
? {
dateFrom: new Date(timeProps.dateFrom).toISOString(),
dateTo: new Date(timeProps.dateTo).toISOString(),
interval: timeProps.interval
}
: { dateFrom: new Date(timeProps.dateFrom).toISOString(), interval: timeProps.interval };
window.dispatchEvent(new CustomEvent(GLOBAL_CONTEXT_EVENTS.UPDATE_GLOBAL_CONTEXT_HISTORY, {
detail: {
type: GLOBAL_CONTEXT_EVENTS.UPDATE_GLOBAL_CONTEXT_HISTORY,
payload: {
dateTimeContext,
isAutoRefreshEnabled: false,
aggregation: this.displayConfig.aggregation,
source: GLOBAL_CONTEXT_SOURCE.WIDGET,
eventSourceId: this.widgetInstanceId
},
timestamp: Date.now()
}
}));
}
else {
this.isSliderBeingDragged$.next(true);
const dateTimeContext = {
dateFrom: new Date(timeProps.dateFrom),
dateTo: new Date(timeProps.dateTo),
interval: timeProps.interval
};
// Update displayConfig for internal state
this.displayConfig = {
...this.displayConfig,
dateTimeContext: structuredClone(dateTimeContext),
dateFrom: new Date(timeProps.dateFrom),
dateTo: new Date(timeProps.dateTo),
interval: timeProps.interval,
isAutoRefreshEnabled: false
};
this.globalContextState = {
...this.displayConfig,
dateTimeContext: { ...dateTimeContext }
};
// Trigger wrapper to update its internal state
this.wrapper.pauseAutoRefresh();
this.wrapper.updateDateTimeContext(dateTimeContext);
}
}
buildSelectableItems() {
const dpItems = (this.displayConfig.datapoints || []).map(dp => {
return {
label: dp.label,
value: {
type: 'DATAPOINT',
original: dp
}
};
});
const alarmItems = (this.displayConfig.alarmsEventsConfigs || [])
.filter(ae => ae.timelineType === 'ALARM')
.map(alarm => {
return {
label: alarm.label,
value: {
type: 'ALARM',
original: alarm
}
};
});
const eventItems = (this.displayConfig.alarmsEventsConfigs || [])
.filter(ae => ae.timelineType === 'EVENT')
.map(event => {
return {
label: event.label,
value: {
type: 'EVENT',
original: event
}
};
});
return [...dpItems, ...alarmItems, ...eventItems];
}
onItemSelected(item) {
if (item.value.type === 'DATAPOINT') {
this.toggleChart(item.value.original);
}
else {
this.toggleAlarmEventType(item.value.original);
}
}
onItemDeselected(item) {
if (item.value.type === 'DATAPOINT') {
this.toggleChart(item.value.original);
}
else {
this.toggleAlarmEventType(item.value.original);
}
}
toggleChart(datapoint) {
if (this.displayConfig?.datapoints?.filter(dp => dp.__active).length === 1 &&
datapoint.__active) {
// at least 1 datapoint should be active
this.hasAtLeastOneDatapointActive = false;
this.updateSelectedItems();
return;
}
datapoint.__active = !datapoint.__active;
this.hasAtLeastOneDatapointActive = true;
this.updateSelectedItems();
if (!this.loadedDatapoints.find(dp => dp.label === datapoint.label)) {
this.loadedDatapoints.push(datapoint);
this.displayConfig = { ...this.displayConfig };
return;
}
this.chartComponent.toggleDatapointSeriesVisibility(datapoint);
}
handleDatapointOutOfSync(dpOutOfSync) {
const key = (dp) => dp.__target?.id + dp.fragment + dp.series;
const dpMatch = this.displayConfig?.datapoints?.find(dp => key(dp) === key(dpOutOfSync));
if (!dpMatch) {
return;
}
this.datapointsOutOfSync.set(dpMatch, true);
}
toggleMarkedArea(alarm) {
this.enabledMarkedAreaAlarmType = alarm.filters.type;
const params = {
data: {
itemType: alarm