UNPKG

@c8y/ngx-components

Version:

Angular modules for Cumulocity IoT applications

1 lines • 165 kB
{"version":3,"file":"c8y-ngx-components-widgets-implementations-datapoints-table.mjs","sources":["../../widgets/implementations/datapoints-table/datapoints-table-widget.model.ts","../../widgets/implementations/datapoints-table/datapoints-table.service.ts","../../widgets/implementations/datapoints-table/date-range-picker.component.ts","../../widgets/implementations/datapoints-table/date-range-picker.component.html","../../widgets/implementations/datapoints-table/datapoints-table-config/datapoints-table-config.component.ts","../../widgets/implementations/datapoints-table/datapoints-table-config/datapoints-table-config.component.html","../../widgets/implementations/datapoints-table/datapoints-table-view/adjust-aggregated-time-range.pipe.ts","../../widgets/implementations/datapoints-table/datapoints-table-view/apply-range-class.pipe.ts","../../widgets/implementations/datapoints-table/datapoints-table-view/datapoints-reload/datapoints-reload.component.ts","../../widgets/implementations/datapoints-table/datapoints-table-view/datapoints-reload/datapoints-reload.component.html","../../widgets/implementations/datapoints-table/datapoints-table-view/datapoints-table-view.service.ts","../../widgets/implementations/datapoints-table/datapoints-table-view/column-title.pipe.ts","../../widgets/implementations/datapoints-table/datapoints-table-view/virtual-scroll-listener.directive.ts","../../widgets/implementations/datapoints-table/datapoints-table-view/datapoints-table/dynamic-column.directive.ts","../../widgets/implementations/datapoints-table/datapoints-table-view/datapoints-table/datapoints-table.component.ts","../../widgets/implementations/datapoints-table/datapoints-table-view/datapoints-table/datapoints-table.component.html","../../widgets/implementations/datapoints-table/datapoints-table-view/datapoints-table-view.component.ts","../../widgets/implementations/datapoints-table/datapoints-table-view/datapoints-table-view.component.html","../../widgets/implementations/datapoints-table/c8y-ngx-components-widgets-implementations-datapoints-table.ts"],"sourcesContent":["import { IFetchResponse, ISeries } from '@c8y/client';\nimport { AggregationOption, gettext } from '@c8y/ngx-components';\nimport { KPIDetails } from '@c8y/ngx-components/datapoint-selector';\nimport { MinMaxValues, SourceId, TimeStamp } from '@c8y/ngx-components/datapoints-export-selector';\nimport { Interval, INTERVAL_VALUES } from '@c8y/ngx-components/interval-picker';\n\nexport const DEFAULT_DPT_REFRESH_INTERVAL_VALUE = 30_000;\n\nexport type DatapointTableMapKey = `${TimeStamp}_${DeviceName}`;\n/**\n * Represents a mapping where the key is a datapoint identifier containing the date and device name, and the value is an array of data point table items or null.\n */\nexport type DataPointsTableMap = Map<DatapointTableMapKey, (DatapointTableItem | null)[]>;\n\ntype DeviceName = string;\n\n/**\n * Represents a map of datapoints series data.\n * The key of the map is a source, and the value is the data for requested series.\n */\nexport type DatapointsSeriesDataMap = Map<SourceId, ISeries>;\n/**\n * Determines which values will be displayed as values for datapoints.\n * e.g. if user chose 'min', only the minimum values will be displayed.\n */\nexport type RenderType = keyof typeof RENDER_TYPES_LABELS;\n/**\n * Represents an object where key is a timestamp and value is an array\n * where first record contains min and max values for a corresponding timestamp.\n */\nexport type MeasurementRanges = {\n [key: TimeStamp]: Array<MinMaxValues>;\n};\n/**\n * Represents a value object where key is an object with min and max properties with its corresponding values.\n */\nexport type Value = {\n [key in keyof MinMaxValues]: number;\n};\n\nexport const DATE_SELECTION_VALUES = {\n dashboard_context: 'dashboard_context',\n config: 'config',\n view_and_config: 'view_and_config'\n} as const;\n\nexport const DATE_SELECTION_VALUES_ARR = [\n DATE_SELECTION_VALUES.dashboard_context,\n DATE_SELECTION_VALUES.config,\n DATE_SELECTION_VALUES.view_and_config\n] as const;\n\nexport const DATE_SELECTION_LABELS = {\n config: gettext('Widget configuration') as 'Widget configuration',\n view_and_config: gettext('Widget and widget configuration') as 'Widget and widget configuration',\n dashboard_context: gettext('Dashboard time range') as 'Dashboard time range'\n} as const;\n\nexport const REFRESH_INTERVAL_VALUES_ARR = [5_000, 10_000, 15_000, 30_000, 60_000];\n\nexport const RENDER_TYPES_LABELS = {\n min: gettext('Minimum') as 'Minimum',\n max: gettext('Maximum') as 'Maximum',\n area: gettext('Area') as 'Area'\n} as const;\n\nexport const INTERVAL_VALUES_ARR = [\n INTERVAL_VALUES.minutes,\n INTERVAL_VALUES.hours,\n INTERVAL_VALUES.days,\n INTERVAL_VALUES.weeks,\n INTERVAL_VALUES.months,\n INTERVAL_VALUES.custom\n] as const;\n\nexport const TIME_RANGE_INTERVAL_LABELS = {\n minutes: gettext('Last minute') as 'Last minute',\n hours: gettext('Last hour') as 'Last hour',\n days: gettext('Last day') as 'Last day',\n weeks: gettext('Last week') as 'Last week',\n months: gettext('Last month') as 'Last month',\n custom: gettext('Custom') as 'Custom'\n} as const;\n\nexport const DURATION_OPTIONS = [\n {\n id: INTERVAL_VALUES.minutes,\n label: TIME_RANGE_INTERVAL_LABELS.minutes,\n unit: INTERVAL_VALUES.minutes,\n amount: 1\n },\n {\n id: INTERVAL_VALUES.hours,\n label: TIME_RANGE_INTERVAL_LABELS.hours,\n unit: INTERVAL_VALUES.hours,\n amount: 1\n },\n {\n id: INTERVAL_VALUES.days,\n label: TIME_RANGE_INTERVAL_LABELS.days,\n unit: INTERVAL_VALUES.days,\n amount: 1\n },\n {\n id: INTERVAL_VALUES.weeks,\n label: TIME_RANGE_INTERVAL_LABELS.weeks,\n unit: INTERVAL_VALUES.weeks,\n amount: 1\n },\n {\n id: INTERVAL_VALUES.months,\n label: TIME_RANGE_INTERVAL_LABELS.months,\n unit: INTERVAL_VALUES.months,\n amount: 1\n },\n { id: INTERVAL_VALUES.custom, label: TIME_RANGE_INTERVAL_LABELS.custom }\n];\n\nexport interface ColorRangeBoundaries {\n yellowRangeMin: number;\n yellowRangeMax: number;\n redRangeMin: number;\n redRangeMax: number;\n}\n\nexport interface DatapointWithValues extends KPIDetails {\n seriesUnit?: string;\n values: MeasurementRanges;\n}\n\nexport interface Duration {\n id: string;\n label: string;\n unit?: string;\n amount?: number;\n}\n\nexport interface TableColumnHeader {\n deviceName: string;\n label: string;\n renderType: string;\n unit: string;\n}\n\nexport interface DateRange {\n dateFrom: string;\n dateTo: string;\n}\n\nexport interface DatapointsTableConfig {\n aggregation?: AggregationOption | null;\n context?: number;\n datapoints: KPIDetails[];\n /**\n * Array that contains global time context dateFrom and dateTo.\n */\n date?: string[];\n dateFrom: string;\n dateTo: string;\n decimalPlaces?: number;\n displayDateSelection: boolean;\n displaySettings: {\n globalTimeContext: boolean;\n globalRealtimeContext: boolean;\n globalAggregationContext: boolean;\n globalAutoRefreshContext: boolean;\n };\n interval: Interval['id'];\n isAutoRefreshEnabled: boolean;\n realtime: boolean;\n refreshInterval?: number;\n selected?: object | null;\n widgetInstanceGlobalTimeContext?: boolean | null;\n widgetInstanceGlobalAutoRefreshContext: boolean;\n globalDateSelector?: keyof typeof DATE_SELECTION_VALUES;\n sliderChange?: boolean | null;\n}\n\nexport interface DatapointTableItem {\n dateAndTime: string;\n deviceName: string;\n fragment: string;\n label: string;\n redRangeMax?: number;\n redRangeMin?: number;\n renderType: string;\n series: string;\n value: Value;\n yellowRangeMax?: number;\n yellowRangeMin?: number;\n}\n\nexport interface GroupedDatapointTableItem {\n dateAndTime: string;\n deviceName: string;\n rowItems: ({\n fragment: string;\n label: string;\n redRangeMax?: number;\n redRangeMin?: number;\n renderType: string;\n series: string;\n value: Value;\n yellowRangeMax?: number;\n yellowRangeMin?: number;\n } | null)[];\n}\n\nexport interface SeriesDataWithResponse {\n source: SourceId;\n data: ISeries;\n res: IFetchResponse;\n}\n","import { Injectable } from '@angular/core';\nimport { DataFetchingService } from '@c8y/ngx-components/datapoints-export-selector';\nimport { INTERVAL_VALUES, Interval } from '@c8y/ngx-components/interval-picker';\nimport { DateRange } from './datapoints-table-widget.model';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class DatapointsTableService {\n constructor(private dataFetchingService: DataFetchingService) {}\n\n calculateDateRange(interval: Interval['id']): DateRange {\n const now = new Date();\n const nowString = now.toISOString();\n\n let dateFrom: string;\n\n switch (interval) {\n case INTERVAL_VALUES.minutes:\n dateFrom = this.dataFetchingService.adjustDate(nowString, -1, true);\n break;\n case INTERVAL_VALUES.hours:\n const minutesInAnHourNegative = -60;\n dateFrom = this.dataFetchingService.adjustDate(nowString, minutesInAnHourNegative, true);\n break;\n case INTERVAL_VALUES.days:\n const minutesInADayNegative = -24 * 60;\n dateFrom = this.dataFetchingService.adjustDate(nowString, minutesInADayNegative, true);\n break;\n case INTERVAL_VALUES.weeks:\n const minutesInAWeekNegative = -7 * 24 * 60;\n dateFrom = this.dataFetchingService.adjustDate(nowString, minutesInAWeekNegative, true);\n break;\n case INTERVAL_VALUES.months:\n const oneMonthAgo = new Date(now);\n oneMonthAgo.setMonth(oneMonthAgo.getMonth() - 1);\n dateFrom = this.dataFetchingService.adjustDate(oneMonthAgo.toISOString(), 0, true);\n break;\n default:\n throw new Error('Invalid time interval');\n }\n\n return {\n dateFrom: dateFrom,\n dateTo: this.dataFetchingService.adjustDate(nowString, 0, true)\n };\n }\n}\n","import { CommonModule } from '@angular/common';\nimport {\n ChangeDetectionStrategy,\n Component,\n EventEmitter,\n inject,\n Input,\n Output\n} from '@angular/core';\nimport { ControlContainer, FormGroup, ReactiveFormsModule } from '@angular/forms';\nimport { CoreModule, gettext } from '@c8y/ngx-components';\n\n@Component({\n selector: 'c8y-date-range-picker',\n templateUrl: './date-range-picker.component.html',\n standalone: true,\n imports: [CoreModule, CommonModule, ReactiveFormsModule],\n changeDetection: ChangeDetectionStrategy.OnPush,\n viewProviders: [\n {\n provide: ControlContainer,\n useFactory: () => inject(ControlContainer, { skipSelf: true })\n }\n ]\n})\nexport class DateRangePickerComponent {\n /**\n * If set to true, the component will be reactive and will emit the updatedDate on every change.\n * Otherwise, the component will use a non emitting variant of a template.\n */\n @Input() isEmittingDateChange = false;\n /**\n * Determines the display of from and to date picker labels.\n */\n @Input() showLabel = true;\n @Output() updatedDate: EventEmitter<{\n dateFrom?: string | null;\n dateTo?: string | null;\n }> = new EventEmitter<{\n dateFrom?: string | null;\n dateTo?: string | null;\n }>();\n\n readonly DATE_FROM = 'dateFrom';\n readonly DATE_TO = 'dateTo';\n readonly FROM_DATE = gettext('From`date`');\n readonly HAS_ERROR = 'has-error';\n readonly INVALID_DATE_TIME = 'invalidDateTime';\n readonly THIS_DATE_IS_INVALID = gettext('This date is invalid.');\n readonly THIS_DATE_IS_AFTER_THE_LAST_ALLOWED_DATE = gettext(\n 'This date is after the latest allowed date.'\n );\n readonly THIS_DATE_IS_BEFORE_THE_EARLIEST_ALLOWED_DATE = gettext(\n 'This date is before the earliest allowed date.'\n );\n readonly TO_DATE = gettext('To`date`');\n private parentContainer = inject(ControlContainer);\n\n onDateFromChange(dateFrom: string) {\n this.updatedDate.emit({\n dateFrom\n });\n }\n\n onDateToChange(dateTo: string) {\n this.updatedDate.emit({\n dateTo\n });\n }\n\n get parentFormGroup() {\n return this.parentContainer.control as FormGroup;\n }\n}\n","<div class=\"d-flex gap-8 a-i-center flex-wrap\">\n <c8y-form-group\n [ngClass]=\"[\n parentFormGroup?.controls.dateFrom.errors ? HAS_ERROR : '',\n isEmittingDateChange ? 'd-flex a-i-center gap-4 m-b-0' : ''\n ]\"\n class=\"form-group-sm\"\n >\n <ng-container *ngIf=\"showLabel\">\n <label\n [title]=\"FROM_DATE | translate\"\n [for]=\"DATE_FROM\"\n >\n {{ FROM_DATE | translate }}\n </label>\n </ng-container>\n <c8y-date-time-picker\n id=\"DATE_FROM\"\n [maxDate]=\"parentFormGroup?.value.dateTo\"\n [placeholder]=\"FROM_DATE | translate\"\n [formControl]=\"parentFormGroup?.controls.dateFrom\"\n [ngClass]=\"parentFormGroup?.controls.dateFrom.errors ? HAS_ERROR : ''\"\n (ngModelChange)=\"onDateFromChange($event)\"\n ></c8y-date-time-picker>\n <c8y-messages\n class=\"text-nowrap\"\n [show]=\"parentFormGroup?.controls.dateFrom.errors\"\n >\n <c8y-message\n name=\"dateAfterRangeMax\"\n [text]=\"THIS_DATE_IS_AFTER_THE_LAST_ALLOWED_DATE | translate\"\n ></c8y-message>\n <c8y-message\n name=\"INVALID_DATE_TIME\"\n [text]=\"THIS_DATE_IS_INVALID | translate\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n <c8y-form-group\n [ngClass]=\"[\n parentFormGroup?.controls.dateTo.errors ? HAS_ERROR : '',\n isEmittingDateChange ? 'd-flex a-i-center gap-4 m-b-0' : ''\n ]\"\n class=\"form-group-sm\"\n >\n <ng-container *ngIf=\"showLabel\">\n <label\n [title]=\"TO_DATE | translate\"\n [for]=\"DATE_TO\"\n >\n {{ TO_DATE | translate }}\n </label>\n </ng-container>\n <c8y-date-time-picker\n id=\"DATE_TO\"\n [minDate]=\"parentFormGroup?.value.dateFrom\"\n [placeholder]=\"TO_DATE | translate\"\n [formControl]=\"parentFormGroup?.controls.dateTo\"\n [ngClass]=\"parentFormGroup?.controls.dateTo.errors ? HAS_ERROR : ''\"\n (ngModelChange)=\"onDateToChange($event)\"\n ></c8y-date-time-picker>\n <c8y-messages [show]=\"parentFormGroup?.controls.dateTo.errors\">\n <c8y-message\n name=\"dateBeforeRangeMin\"\n [text]=\"THIS_DATE_IS_BEFORE_THE_EARLIEST_ALLOWED_DATE | translate\"\n ></c8y-message>\n <c8y-message\n name=\"INVALID_DATE_TIME\"\n [text]=\"THIS_DATE_IS_INVALID | translate\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n</div>\n","import { Component, Input, OnDestroy, OnInit } from '@angular/core';\nimport {\n AbstractControl,\n FormBuilder,\n FormControl,\n NgForm,\n ReactiveFormsModule,\n ValidationErrors,\n ValidatorFn,\n Validators\n} from '@angular/forms';\nimport {\n AGGREGATION_LABELS,\n AGGREGATION_VALUES,\n AGGREGATION_VALUES_ARR,\n AggregationOption,\n AggregationOptionStatus,\n AggregationService,\n CoreModule\n} from '@c8y/ngx-components';\nimport { WidgetConfigComponent } from '@c8y/ngx-components/context-dashboard';\nimport {\n DatapointAttributesFormConfig,\n DatapointSelectorModalOptions,\n DatapointSelectorModule,\n KPIDetails\n} from '@c8y/ngx-components/datapoint-selector';\nimport { dateRangeValidator } from '@c8y/ngx-components/datapoints-export-selector';\nimport { Interval, INTERVAL_VALUES } from '@c8y/ngx-components/interval-picker';\nimport { PopoverModule } from 'ngx-bootstrap/popover';\nimport { merge, Subject, takeUntil } from 'rxjs';\nimport {\n DatapointsTableConfig,\n DATE_SELECTION_LABELS,\n DATE_SELECTION_VALUES,\n DATE_SELECTION_VALUES_ARR,\n DateRange,\n DEFAULT_DPT_REFRESH_INTERVAL_VALUE,\n INTERVAL_VALUES_ARR,\n REFRESH_INTERVAL_VALUES_ARR,\n TIME_RANGE_INTERVAL_LABELS\n} from '../datapoints-table-widget.model';\nimport { DatapointsTableService } from '../datapoints-table.service';\nimport { DateRangePickerComponent } from '../date-range-picker.component';\n\nexport function minOneDatapointActive(): ValidatorFn {\n return (control: AbstractControl): ValidationErrors | null => {\n const datapoints: KPIDetails[] = control.value;\n\n if (!datapoints || !datapoints.length) {\n return null;\n }\n\n const activeDatapoints = datapoints.filter(datapoint => datapoint.__active);\n\n if (activeDatapoints.length >= 1) {\n return null;\n }\n return { exactlyOneDatapointNeedsToBeActive: true };\n };\n}\n\n@Component({\n selector: 'c8y-datapoints-table-view-config',\n templateUrl: './datapoints-table-config.component.html',\n standalone: true,\n imports: [\n CoreModule,\n DatapointSelectorModule,\n DateRangePickerComponent,\n PopoverModule,\n ReactiveFormsModule\n ]\n})\nexport class DatapointsTableWidgetConfigComponent implements OnInit, OnDestroy {\n /**\n * Data points table widget config.\n */\n @Input() config: DatapointsTableConfig;\n\n readonly AGGREGATION_LABELS = AGGREGATION_LABELS;\n readonly DATE_SELECTION_LABELS = DATE_SELECTION_LABELS;\n readonly DEFAULT_DATE_SELECTOR_VALUE = DATE_SELECTION_VALUES.dashboard_context;\n readonly DEFAULT_INTERVAL_VALUE = INTERVAL_VALUES.hours;\n readonly TIME_RANGE_INTERVAL_LABELS = TIME_RANGE_INTERVAL_LABELS;\n\n readonly AGGREGATION_VALUES_ARR = AGGREGATION_VALUES_ARR;\n readonly DATE_SELECTION_VALUES_ARR = DATE_SELECTION_VALUES_ARR;\n readonly INTERVAL_VALUES_ARR = INTERVAL_VALUES_ARR;\n readonly REFRESH_INTERVAL_VALUES_ARR = REFRESH_INTERVAL_VALUES_ARR;\n\n datapointSelectionConfig: Partial<DatapointSelectorModalOptions> = {};\n disabledAggregationOptions: AggregationOptionStatus = {};\n defaultFormOptions: Partial<DatapointAttributesFormConfig> = {\n selectableChartLineTypes: [],\n selectableAxisTypes: [],\n showRedRange: true,\n showYellowRange: true\n };\n formGroup: ReturnType<DatapointsTableWidgetConfigComponent['createForm']>;\n isWidgetLinkedToGlobalTimeContext: boolean;\n\n private decimalLimits = {\n numberOfDecimalPlacesMin: 0,\n numberOfDecimalPlacesMax: 10\n } as const;\n\n /**\n * Indicate when the time interval selector item has been changed programmatically.\n *\n * This property is used to track changes in the time interval selector\n * that are not triggered by direct user interaction, but by the application itself.\n *\n * In our case, the date selector and the interval selector are linked.\n * So, when one of them changes, it affects the other.\n * For example, selecting \"Last hour\" in the interval selector should automatically update the date range to the last hour.\n * But without this flag, changing the date range would also update the interval selector, resulting in \"Custom date\" being selected.\n * This happens because the system would interpret the behavior this way.\n */\n private isIntervalSelectorChangedProgrammatically = false;\n private destroy$ = new Subject<void>();\n\n constructor(\n private aggregationService: AggregationService,\n private datapointsTableService: DatapointsTableService,\n private formBuilder: FormBuilder,\n private form: NgForm,\n private widgetConfig: WidgetConfigComponent\n ) {}\n\n ngOnInit(): void {\n if (this.widgetConfig.context?.id) {\n this.datapointSelectionConfig.contextAsset = this.widgetConfig?.context;\n }\n\n this.isWidgetLinkedToGlobalTimeContext =\n this.config.widgetInstanceGlobalAutoRefreshContext ?? true;\n\n this.initForm();\n\n this.handleAutoRefreshToggleChanges();\n this.handleIntervalSelectorChanges();\n this.handleDateSelectorChanges();\n this.handleGlobalDateSelectorChanges();\n\n this.updateDisabledAggregationOptions();\n }\n\n ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n }\n\n onBeforeSave(config?: DatapointsTableConfig): void {\n if (\n this.formGroup.value.aggregation === AGGREGATION_VALUES.none ||\n (!this.formGroup.value.aggregation && config.aggregation === AGGREGATION_VALUES.none)\n ) {\n // The 'NONE' aggregation type is not handled by a backend, so it simply needs to be deleted to get data without aggregation.\n this.deleteAggregationProperty(config);\n }\n this.updateTimeContext(config, this.formGroup.value.globalDateSelector);\n Object.assign(config, this.formGroup.value);\n }\n\n toggleRefreshIntervalControl(): void {\n this.formGroup.controls.isAutoRefreshEnabled.value\n ? this.formGroup.controls.refreshInterval.disable()\n : this.formGroup.controls.refreshInterval.enable();\n }\n\n private updateTimeContext(config: DatapointsTableConfig, selectedDateContext: string): void {\n switch (selectedDateContext) {\n case DATE_SELECTION_VALUES.dashboard_context:\n config.displayDateSelection = false;\n config.widgetInstanceGlobalAutoRefreshContext = true;\n if (config.aggregation !== 'NONE') {\n this.deleteAggregationProperty(config);\n }\n break;\n case DATE_SELECTION_VALUES.view_and_config:\n config.displayDateSelection = true;\n config.widgetInstanceGlobalAutoRefreshContext = false;\n break;\n case DATE_SELECTION_VALUES.config:\n default:\n config.displayDateSelection = false;\n config.widgetInstanceGlobalAutoRefreshContext = false;\n }\n }\n\n private initForm(): void {\n this.formGroup = this.createForm();\n // When the 'NONE' aggregation is removed at the 'onBeforeSave' block,\n // and a Global Date Context forces widgets auto refresh to be enabled\n // then, the aggregation selector will be empty on initial load.\n this.config.aggregation = this.config.aggregation ?? AGGREGATION_VALUES.none;\n\n this.form.form.addControl('config', this.formGroup);\n this.formGroup.patchValue(this.config);\n }\n\n private createForm() {\n const { dateFrom, dateTo } = this.datapointsTableService.calculateDateRange(\n this.DEFAULT_INTERVAL_VALUE\n );\n const isLegacyWidget =\n this.config.hasOwnProperty('widgetInstanceGlobalTimeContext') &&\n !this.config.hasOwnProperty('decimalPlaces');\n\n return this.formBuilder.group(\n {\n aggregation: new FormControl({\n value: AGGREGATION_VALUES.none as AggregationOption,\n disabled: !this.isAutoRefershDisabled()\n }),\n datapoints: this.formBuilder.control(new Array<KPIDetails>(), [\n Validators.required,\n Validators.minLength(1),\n minOneDatapointActive()\n ]),\n globalDateSelector: new FormControl(\n isLegacyWidget\n ? this.determineGlobalDateSelectorValueForLegacyWidget()\n : this.DEFAULT_DATE_SELECTOR_VALUE\n ),\n dateFrom: new FormControl(dateFrom),\n dateTo: new FormControl(dateTo),\n decimalPlaces: [\n 2,\n [\n Validators.required,\n Validators.min(this.decimalLimits.numberOfDecimalPlacesMin),\n Validators.max(this.decimalLimits.numberOfDecimalPlacesMax),\n Validators.pattern('^[0-9]+$')\n ]\n ],\n interval: new FormControl(this.DEFAULT_INTERVAL_VALUE as Interval['id']),\n isAutoRefreshEnabled: isLegacyWidget ? this.config.realtime : [true],\n refreshInterval: new FormControl({\n value: DEFAULT_DPT_REFRESH_INTERVAL_VALUE,\n disabled: this.isAutoRefershDisabled()\n })\n },\n { validators: dateRangeValidator }\n );\n }\n\n private determineGlobalDateSelectorValueForLegacyWidget(): string {\n const { displayDateSelection, widgetInstanceGlobalAutoRefreshContext } = this.config;\n\n // State of widgetInstanceGlobalTimeContext was converted to widgetInstanceGlobalAutoRefreshContext.\n // That is why it use here in a legacy widget context.\n if (widgetInstanceGlobalAutoRefreshContext) {\n return DATE_SELECTION_VALUES.dashboard_context;\n }\n\n return displayDateSelection\n ? DATE_SELECTION_VALUES.view_and_config\n : DATE_SELECTION_VALUES.config;\n }\n\n private isAutoRefershDisabled(): boolean {\n const isInitialWidgetCreation =\n Object.keys(this.config).length === 1 && this.config['settings'];\n\n if (isInitialWidgetCreation) {\n return false;\n }\n\n const isLegacyWidgetRealtimeNotActive =\n !this.config.realtime &&\n !this.config.hasOwnProperty('decimalPlaces') &&\n !this.config.hasOwnProperty('refreshInterval') &&\n !this.config.hasOwnProperty('isAutoRefreshEnabled');\n\n if (isLegacyWidgetRealtimeNotActive) {\n return true;\n }\n\n return !this.config.isAutoRefreshEnabled;\n }\n\n /**\n * Handles changes to the auto-refresh toggle control and updates the aggregation control accordingly.\n *\n * This method subscribes to the value changes of the auto-refresh toggle form control.\n * When auto-refresh is enabled, the aggregation control's value is set to 'none' and the control is\n * visually disabled (but not programmatically disabled to ensure the value is saved).\n * When auto-refresh is disabled, the aggregation control is enabled.\n */\n private handleAutoRefreshToggleChanges(): void {\n this.formGroup.controls.isAutoRefreshEnabled.valueChanges\n .pipe(takeUntil(this.destroy$))\n .subscribe((isAutoRefreshEnabled: boolean) => {\n const aggregationControl = this.formGroup.controls.aggregation;\n if (isAutoRefreshEnabled) {\n aggregationControl.setValue(AGGREGATION_VALUES.none);\n // Do not disable the control programmatically to ensure the value is saved.\n } else {\n aggregationControl.enable();\n }\n });\n }\n\n /**\n * Handles changes in the interval selector form control.\n */\n private handleIntervalSelectorChanges(): void {\n this.formGroup.controls.interval.valueChanges\n .pipe(takeUntil(this.destroy$))\n .subscribe((selectedInterval: Interval['id']) => {\n if (selectedInterval === INTERVAL_VALUES.custom) {\n this.updateDisabledAggregationOptions();\n return;\n }\n\n this.handleNonCustomInterval(selectedInterval);\n });\n }\n\n private handleNonCustomInterval(selectedInterval: Interval['id']): void {\n this.isIntervalSelectorChangedProgrammatically = true;\n this.updateDateRange(selectedInterval);\n this.updateDisabledAggregationOptions();\n this.updateAggregationIfAutoRefreshDisabled(selectedInterval);\n }\n\n private updateDateRange(selectedInterval: Interval['id']): void {\n const dateRange: DateRange = this.datapointsTableService.calculateDateRange(selectedInterval);\n this.formGroup.controls.dateTo.setValue(dateRange.dateTo);\n\n // MTM-61351\n // Use requestAnimationFrame to queue the dateFrom update.\n // This prevents timing issues where rapid setValue calls might\n // cause the view to go out of sync with form control values,\n // especially during the first change after initialization.\n // requestAnimationFrame(() => {\n /**\n * Without this requestAnimationFrame or setTimeout, dateFrom won't change to a correct value after first selector change.\n * When form is saved it saves with a correct value, even without with this fix.\n * How to reproduce:\n * 1. set date values to e.g.: 01.05.2024-30.05.2024 and save it\n * 2. reopen a config and switch interval to last month\n */\n // This fix brakes a logic behind disabling non available aggregations.\n // Form will be still saved with correct date value, only view is out of a sync.\n this.formGroup.controls.dateFrom.setValue(dateRange.dateFrom);\n this.isIntervalSelectorChangedProgrammatically = false;\n // });\n }\n\n private updateDisabledAggregationOptions(): void {\n this.disabledAggregationOptions = this.aggregationService.getDisabledAggregationOptions(\n this.formGroup.controls.dateFrom.value,\n this.formGroup.controls.dateTo.value\n );\n }\n\n private updateAggregationIfAutoRefreshDisabled(selectedInterval: Interval['id']): void {\n const isAutoRefreshDisabled = !this.formGroup.controls.isAutoRefreshEnabled.value;\n if (isAutoRefreshDisabled) {\n this.setAggregationValue(selectedInterval);\n }\n }\n\n private setAggregationValue(interval: Interval['id']): void {\n const aggregationControl = this.formGroup.controls.aggregation;\n\n const newAggregationValue = this.aggregationService.determineAggregationValue(interval);\n aggregationControl.setValue(newAggregationValue);\n }\n\n /**\n * Handles changes in the date selector form control.\n */\n private handleDateSelectorChanges(): void {\n merge(\n this.formGroup.controls.dateFrom.valueChanges,\n this.formGroup.controls.dateTo.valueChanges\n )\n .pipe(takeUntil(this.destroy$))\n .subscribe(() => this.handleDateChange());\n }\n\n private handleDateChange(): void {\n if (this.isIntervalSelectorChangedProgrammatically) {\n return;\n }\n\n const intervalControl = this.formGroup.controls.interval;\n if (intervalControl.value === INTERVAL_VALUES.custom) {\n this.handleCustomIntervalDateChange();\n } else {\n this.setIntervalToCustom(intervalControl);\n }\n this.setToFirstAvailableAggregationOptionIfCurrentAggregationIsDisabled();\n }\n\n private handleCustomIntervalDateChange(): void {\n this.updateDisabledAggregationOptions();\n }\n\n private setIntervalToCustom(intervalControl: AbstractControl): void {\n intervalControl.setValue(INTERVAL_VALUES.custom);\n }\n\n /**\n * Handles changes in the global date selector form control.\n */\n private handleGlobalDateSelectorChanges(): void {\n this.formGroup.controls.globalDateSelector.valueChanges\n .pipe(takeUntil(this.destroy$))\n .subscribe((selected: string) => {\n if (selected === DATE_SELECTION_VALUES.dashboard_context) {\n this.handleDashboardContext();\n } else {\n this.handleNonDashboardContext();\n }\n });\n }\n\n private handleDashboardContext(): void {\n this.isWidgetLinkedToGlobalTimeContext = true;\n }\n\n private handleNonDashboardContext(): void {\n this.isWidgetLinkedToGlobalTimeContext = false;\n\n if (this.isConfigSavedWithoutDashboardTimeOption()) {\n return;\n }\n\n this.updateAggregationIfNecessary();\n this.setIntervalToCustom(this.formGroup.controls.interval);\n }\n\n private isConfigSavedWithoutDashboardTimeOption(): boolean {\n return !this.config.widgetInstanceGlobalAutoRefreshContext;\n }\n\n private updateAggregationIfNecessary(): void {\n const aggregationControl = this.formGroup.controls.aggregation;\n const isDashboardTimeOptionSetAggregationToNull = !aggregationControl.value;\n\n if (isDashboardTimeOptionSetAggregationToNull) {\n aggregationControl.setValue(AGGREGATION_VALUES.none);\n }\n }\n\n /**\n * Sets the aggregation control to the first available (non-disabled) option if the current option is disabled.\n *\n * This method:\n * - Retrieves the current value of the aggregation control.\n * - Checks if the current aggregation option is disabled.\n * - If the current option is disabled, sets the control to the first available (non-disabled) option based on the following order:\n * - If the current value is `DAILY`, it switches to `HOURLY` if it's not disabled, otherwise to `MINUTELY` if `HOURLY` is also disabled.\n * - If the current value is `HOURLY`, it switches to `MINUTELY` if it's not disabled.\n * - If all options are disabled, it sets the value to `NONE`.\n *\n * The disabled state is stored in the `disabledAggregationOptions` object,\n * where the key is the aggregation value and the value is a boolean indicating whether the option is disabled.\n *\n * The `AGGREGATION_VALUES` object defines the possible aggregation values.\n */\n private setToFirstAvailableAggregationOptionIfCurrentAggregationIsDisabled(): void {\n const aggregationControl = this.formGroup.controls.aggregation;\n const currentValue = aggregationControl.value;\n\n const newAggregationValue = this.aggregationService.determineFirstNewAvailableAggregationValue(\n currentValue,\n this.disabledAggregationOptions\n );\n\n if (newAggregationValue !== currentValue) {\n aggregationControl.setValue(newAggregationValue);\n }\n }\n\n private deleteAggregationProperty(config: DatapointsTableConfig): void {\n delete this.formGroup.value.aggregation;\n delete config.aggregation;\n }\n}\n","<form\n class=\"no-card-context\"\n [formGroup]=\"formGroup\"\n>\n<fieldset class=\"c8y-fieldset\">\n <legend>{{ 'Date' | translate }}</legend>\n <!-- global-time-context-selector -->\n <div class=\"form-group form-group-sm m-b-16\">\n <label\n class=\"d-flex a-i-center p-t-4\"\n for=\"dateSelection\"\n >\n {{ 'Date selection' | translate }}\n <button\n class=\"btn-help btn-help--sm\"\n [attr.aria-label]=\"'Help' | translate\"\n [popover]=\"popoverTemplate\"\n placement=\"right\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n [adaptivePosition]=\"true\"\n ></button>\n </label>\n <ng-template #popoverTemplate>\n <span translate>\n Choose how to select a date range, the available options are:\n <ul class=\"m-l-0 p-l-8 m-t-8 m-b-0\">\n <li>\n <b>Dashboard time range:</b>\n restricts date selection to the global dashboard configuration only\n </li>\n <li>\n <b>Widget configuration:</b>\n restricts the date selection only to the widget configuration\n </li>\n <li>\n <b>Widget and widget configuration:</b>\n restricts the date selection to the widget view and widget configuration only\n </li>\n </ul>\n </span>\n </ng-template>\n <div class=\"c8y-select-wrapper\">\n <select\n class=\"form-control text-12\"\n [title]=\"'Date selection' | translate\"\n [attr.aria-label]=\"'Date selection' | translate\"\n id=\"globalDateSelector\"\n formControlName=\"globalDateSelector\"\n >\n <option\n *ngFor=\"let dataSelectionValue of DATE_SELECTION_VALUES_ARR\"\n [ngValue]=\"dataSelectionValue\"\n >\n {{ DATE_SELECTION_LABELS[dataSelectionValue] | translate }}\n </option>\n </select>\n </div>\n </div>\n <div class=\"row tight-grid d-flex flex-wrap\" *ngIf=\"!isWidgetLinkedToGlobalTimeContext\">\n <div class=\"col-md-6\" >\n <c8y-form-group class=\"form-group-sm\">\n <label>{{ 'Date filter' | translate }}</label>\n <div class=\"c8y-select-wrapper\">\n <select\n class=\"form-control text-12\"\n [title]=\"'Interval' | translate\"\n id=\"interval\"\n formControlName=\"interval\"\n >\n <option\n *ngFor=\"let intervalValue of INTERVAL_VALUES_ARR\"\n [ngValue]=\"intervalValue\"\n >\n {{ TIME_RANGE_INTERVAL_LABELS[intervalValue] | translate }}\n </option>\n </select>\n </div>\n </c8y-form-group>\n </div>\n <div class=\"col-md-6\" >\n <c8y-date-range-picker *ngIf=\"formGroup.value.interval === 'custom'\"></c8y-date-range-picker>\n </div>\n <div class=\"col-md-6\">\n <c8y-form-group class=\"form-group-sm\">\n <label>\n {{ 'Aggregation' | translate }}\n </label>\n <div class=\"c8y-select-wrapper\">\n <!-- Setting below [attr.disabled] ensures that the control is visually disabled and user interaction is prevented,\n while still allowing the value to be updated and saved correctly in the form.\n This solution covers the case where the user enables auto-refresh, in which case aggregation must be set to NONE. -->\n <select\n class=\"form-control text-12\"\n [title]=\"'Aggregation' | translate\"\n id=\"aggregation\"\n formControlName=\"aggregation\"\n [attr.disabled]=\"formGroup.value.isAutoRefreshEnabled ? true : null\"\n >\n <option\n *ngFor=\"let aggregationValue of AGGREGATION_VALUES_ARR\"\n [ngValue]=\"aggregationValue\"\n [disabled]=\"disabledAggregationOptions[aggregationValue]\"\n >\n {{ AGGREGATION_LABELS[aggregationValue] | translate }}\n </option>\n </select>\n </div>\n </c8y-form-group>\n </div>\n <div class=\"col-md-6\">\n <!-- interval selector -->\n <c8y-form-group class=\"form-group-sm\">\n <label class=\"d-flex a-i-center\">\n {{ 'Auto refresh' | translate }}\n <button\n class=\"btn-help btn-help--sm\"\n [attr.aria-label]=\"'Help' | translate\"\n [popover]=\"\n 'Change the state of interval automatic refresh and set the refresh frequency.'\n | translate\n \"\n placement=\"top\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n [adaptivePosition]=\"true\"\n ></button>\n </label>\n <div class=\"d-flex gap-4 a-i-center\">\n <label class=\"c8y-switch c8y-switch--inline\">\n <input\n id=\"refreshToggle\"\n name=\"isAutoRefreshEnabled\"\n type=\"checkbox\"\n formControlName=\"isAutoRefreshEnabled\"\n (click)=\"toggleRefreshIntervalControl()\"\n />\n <span></span>\n <span class=\"sr-only\">{{ 'Auto refresh' | translate }}</span>\n </label>\n <div class=\"c8y-select-wrapper\">\n <select\n class=\"form-control\"\n [title]=\"'Refresh interval in seconds' | translate\"\n id=\"refreshInterval\"\n formControlName=\"refreshInterval\"\n >\n <option\n *ngFor=\"let refreshInterval of REFRESH_INTERVAL_VALUES_ARR\"\n [ngValue]=\"refreshInterval\"\n >\n {{ '{{ seconds }} s' | translate: { seconds: refreshInterval / 1000 } }}\n </option>\n </select>\n </div>\n </div>\n </c8y-form-group>\n </div>\n </div>\n</fieldset>\n\n<!-- decimal input -->\n<fieldset class=\"c8y-fieldset\">\n <legend>\n {{ 'Decimal places' | translate }}\n </legend>\n <c8y-form-group class=\"p-t-8\">\n <input\n class=\"form-control\"\n name=\"decimalPlaces\"\n type=\"number\"\n formControlName=\"decimalPlaces\"\n step=\"1\"\n />\n </c8y-form-group>\n</fieldset>\n\n\n <!-- <datapoints-selector> -->\n <c8y-datapoint-selection-list\n class=\"bg-inherit\"\n listTitle=\"{{ 'Data points' | translate }}\"\n name=\"datapoints\"\n [defaultFormOptions]=\"defaultFormOptions\"\n [config]=\"datapointSelectionConfig\"\n [minActiveCount]=\"1\"\n formControlName=\"datapoints\"\n ></c8y-datapoint-selection-list>\n \n</form>\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { aggregationType as AggregationTypeEnum } from '@c8y/client';\nimport { AggregationOption } from '@c8y/ngx-components';\n\n/**\n * A pipe that adjusts the aggregated time range based on the aggregation type.\n *\n * ```html\n * '9:00' | adjustAggregatedTimeRange: config.aggregation (e.g.:HOURLY)\n * ```\n * The output will be '9:00-10:00'.\n */\n@Pipe({\n name: 'adjustAggregatedTimeRange',\n standalone: true\n})\nexport class AdjustAggregatedTimeRangePipe implements PipeTransform {\n /**\n * Transforms the input time based on the aggregation type.\n * @param inputTime The input time string.\n * @param aggregationType The type of aggregation (optional).\n * @returns The transformed time string.\n */\n transform(inputTime: string, aggregationType?: AggregationOption): string {\n if (!aggregationType) {\n return inputTime;\n }\n\n if (aggregationType === AggregationTypeEnum.DAILY) {\n return '';\n }\n\n const date = this.createDateFromInput(inputTime);\n const isTwelveHoursFormat = this.isTwelveHoursFormat(inputTime);\n\n switch (aggregationType) {\n case AggregationTypeEnum.HOURLY:\n return this.getHourlyTimeRange(date, isTwelveHoursFormat);\n case AggregationTypeEnum.MINUTELY:\n return this.getMinutelyTimeRange(date, isTwelveHoursFormat);\n default:\n throw new Error('Unsupported aggregation type');\n }\n }\n\n /**\n * Creates a date object from the input time string.\n * @param inputTime The input time string.\n * @returns The created Date object.\n */\n private createDateFromInput(inputTime: string): Date {\n const defaultDate = '1970-01-01 ';\n const isPM = /PM/i.test(inputTime);\n const cleanedTime = inputTime.replace(/AM|PM/i, '').trim();\n\n this.validateTimeFormat(cleanedTime, inputTime);\n\n const dateTimeString = `${defaultDate}${cleanedTime}`;\n const date = new Date(dateTimeString);\n\n if (isNaN(date.getTime())) {\n throw new Error('Invalid input time');\n }\n\n return this.adjustForPMTime(date, isPM);\n }\n\n /**\n * Validates if the time string matches the required format and has valid values.\n * @param time The time string to validate.\n * @param originalInput The original input string (including AM/PM if present).\n * @throws Error if the time format is invalid or values are out of range.\n */\n private validateTimeFormat(time: string, originalInput: string): void {\n const parts = time.split(':');\n this.validateTimeParts(parts);\n\n const [hoursStr, minutesStr, secondsStr] = parts;\n this.validateTimeDigits(hoursStr, minutesStr, secondsStr);\n\n const { hours, minutes, seconds } = this.parseTimeComponents(hoursStr, minutesStr, secondsStr);\n this.validateTimeRanges(hours, minutes, seconds);\n this.validateTimeFormat24Hour(hours, originalInput);\n }\n\n private validateTimeParts(parts: string[]): void {\n if (parts.length < 2 || parts.length > 3) {\n throw new Error('Invalid input time');\n }\n }\n\n private validateTimeDigits(hoursStr: string, minutesStr: string, secondsStr?: string): void {\n if (\n !this.isValidNumberString(hoursStr) ||\n !this.isValidNumberString(minutesStr) ||\n (secondsStr !== undefined && !this.isValidNumberString(secondsStr))\n ) {\n throw new Error('Invalid input time');\n }\n }\n\n private parseTimeComponents(hoursStr: string, minutesStr: string, secondsStr?: string) {\n return {\n hours: Number(hoursStr),\n minutes: Number(minutesStr),\n seconds: secondsStr ? Number(secondsStr) : 0\n };\n }\n\n private validateTimeRanges(hours: number, minutes: number, seconds: number): void {\n if (hours > 23 || hours < 0 || minutes > 59 || minutes < 0 || seconds > 59 || seconds < 0) {\n throw new Error('Invalid input time');\n }\n }\n\n private validateTimeFormat24Hour(hours: number, originalInput: string): void {\n if (hours > 12 && this.hasAmPm(originalInput)) {\n throw new Error('Invalid input time');\n }\n }\n\n /**\n * Checks if string contains only digits and is 1-2 characters long.\n * @param value String to check\n * @returns boolean indicating if string is valid\n */\n private isValidNumberString(value: string): boolean {\n return (\n value.length > 0 &&\n value.length <= 2 &&\n value.split('').every(char => char >= '0' && char <= '9')\n );\n }\n\n /**\n * Checks if the input time has AM/PM markers.\n * @param input The input time string to check.\n * @returns boolean indicating if the input contains AM/PM.\n */\n private hasAmPm(input: string): boolean {\n return /AM|PM/i.test(input);\n }\n\n /**\n * Adjusts the date for PM times by adding 12 hours when necessary.\n * @param date The date object to adjust.\n * @param isPM Boolean indicating if the time is PM.\n * @returns The adjusted Date object.\n */\n private adjustForPMTime(date: Date, isPM: boolean): Date {\n const hours = date.getHours();\n if (isPM && hours < 12) {\n date.setHours(hours + 12);\n } else if (!isPM && hours === 12) {\n date.setHours(0);\n }\n return date;\n }\n\n /**\n * Checks if the input time is in twelve hours format.\n * @param inputTime The input time string.\n * @returns True if the input time is in twelve hours format, false otherwise.\n */\n private isTwelveHoursFormat(inputTime: string): boolean {\n return /AM|PM/i.test(inputTime);\n }\n\n /**\n * Gets the hourly time range for the given date.\n * @param date The date object.\n * @param twelveHoursFormat Indicates whether to use twelve hours format.\n * @returns The hourly time range string.\n */\n private getHourlyTimeRange(date: Date, twelveHoursFormat: boolean): string {\n const nextHour = new Date(date.getTime());\n nextHour.setHours(date.getHours() + 1);\n return `${this.formatTime(date, twelveHoursFormat, true)}-${this.formatTime(nextHour, twelveHoursFormat, true)}`;\n }\n\n /**\n * Gets the minutely time range for the given date.\n * @param date The date object.\n * @param twelveHoursFormat Indicates whether to use twelve hours format.\n * @returns The minutely time range string.\n */\n private getMinutelyTimeRange(date: Date, twelveHoursFormat: boolean): string {\n const nextMinute = new Date(date.getTime());\n nextMinute.setMinutes(date.getMinutes() + 1);\n return `${this.formatTime(date, twelveHoursFormat, false)}-${this.formatTime(nextMinute, twelveHoursFormat, false)}`;\n }\n\n /**\n * Formats the given date into a time string.\n * @param date The date to format.\n * @param usePeriod Indicates whether to include the period (AM/PM) in the formatted time.\n * @param useHourOnly Indicates whether to include only the hour part in the formatted time.\n * @returns The formatted time string.\n */\n private formatTime(date: Date, usePeriod: boolean, useHourOnly: boolean): string {\n const hours = date.getHours();\n const minutes = date.getMinutes().toString().padStart(2, '0');\n if (usePeriod) {\n const period = hours >= 12 ? 'PM' : 'AM';\n const formattedHours = hours % 12 === 0 ? 12 : hours % 12;\n return `${formattedHours}:${useHourOnly ? '00' : minutes} ${period}`;\n } else {\n return `${hours.toString().padStart(2, '0')}:${useHourOnly ? '00' : minutes}`;\n }\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { ColorRangeBoundaries } from '../datapoints-table-widget.model';\n\n/**\n * Applies CSS classes based on the value's range.\n */\n@Pipe({\n name: 'applyRangeClass',\n standalone: true\n})\nexport class ApplyRangeClassPipe implements PipeTransform {\n /**\n * Transforms the input value based on the specified ranges.\n *\n * @param value - Initial value used to determine the CSS class.\n * @param ranges - An object containing the min and max range values for yellow and red colors.\n * @returns The CSS class to be applied.\n */\n transform(value: number, ranges: ColorRangeBoundaries): string {\n if (value == null) {\n return;\n }\n\n if (value >= ranges.yellowRangeMin && value < ranges.yellowRangeMax) {\n return 'text-warning';\n } else if (value >= ranges.redRangeMin && value <= ranges.redRangeMax) {\n return 'text-danger';\n }\n return 'default';\n }\n}\n","import {\n AfterViewInit,\n ChangeDetectorRef,\n Component,\n EventEmitter,\n Input,\n OnChanges,\n OnDestroy,\n Output,\n SimpleChanges,\n ViewChild\n} from '@angular/core';\nimport {\n CommonModule,\n CountdownIntervalComponent,\n CountdownIntervalModule,\n GainsightService,\n IntervalBasedReload,\n ListGroupModule,\n WIDGET_TYPE_VALUES,\n WidgetGlobalAutoRefreshService,\n gettext\n} from '@c8y/ngx-components';\nimport { TranslateService } from '@ngx-translate/core';\nimport { TooltipModule } from 'ngx-bootstrap/tooltip';\nimport { BehaviorSubject, Subject, takeUntil, tap } from 'rxjs';\n\n@Component({\n selector: 'c8y-datapoints-reload',\n templateUrl: './datapoints-reload.component.html',\n standalone: true,\n imports: [CommonModule, CountdownIntervalModule, ListGroupModule, TooltipModule]\n})\nexport class DatapointsReloadComponent\n extends IntervalBasedReload\n implements OnChanges, AfterViewInit, OnDestroy\n{\n readonly WIDGET_TYPE_VALUES = WIDGET_TYPE_VALUES;\n\n @ViewChild(CountdownIntervalComponent, { static: false })\n countdownIntervalComponent: CountdownIntervalComponent;\n /**\n * @inheritdoc\n */\n @Input() isAutoRefreshEnabled: boolean;\n /**\n * @inheritdoc\n */\n @Input() isRefreshDisabled = false;\n /**\n * @inheritdoc\n */\n @Input() isLoading: BehaviorSubject<boolean>;\n\n @Input() isScrolling: boolean;\n\n @Input() isExportModalOpen: boolean;\n /**\n * @inheritdoc\n */\n @Input() refreshInterval: number;\n\n @Input() widgetInstanceGlobalAutoRefreshContext: boolean;\n /**\n * @inheritdoc\n */\n @Output() onCountdownEnded = new EventEmitter<void>();\n /**\n * @inheritdoc\n */\n isIntervalRefreshToggleOn: boolean;\n\n toggleCountdownButtonTooltipText: string;\n /**\n * @inheritdoc\n */\n protected manuallyDisabledCountdown = false;\n /**\n * @inheritdoc\n */\n protected hideCountdown: boolean;\n\n protected destroy$ = new Subject<void>();\n\n constructor(\n