@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
738 lines (733 loc) • 97 kB
JavaScript
import * as i0 from '@angular/core';
import { Injectable, Pipe, Component, Input, EventEmitter, Output, ViewChild, NgModule } from '@angular/core';
import * as i5 from '@angular/forms';
import { FormControl, ControlContainer, NgForm, FormsModule as FormsModule$1, ReactiveFormsModule } from '@angular/forms';
import * as i2 from '@c8y/client';
import { Severity, AlarmStatus, SEVERITY_LABELS, ALARM_STATUS_LABELS } from '@c8y/client';
import * as i2$1 from '@c8y/ngx-components';
import { gettext, IntervalBasedReload, CountdownIntervalComponent, DismissAlertStrategy, DynamicComponentAlert, globalAutoRefreshLoading, AlarmRealtimeService, AlarmWithChildrenRealtimeService, CommonModule, CountdownIntervalModule, DocsModule, FormsModule } from '@c8y/ngx-components';
import * as i3 from '@c8y/ngx-components/alarms';
import { ALARM_STATUS_ICON, DEFAULT_STATUS_VALUES, DEFAULT_SEVERITY_VALUES, AlarmsModule } from '@c8y/ngx-components/alarms';
import * as i6 from '@ngx-translate/core';
import { omit, isEqual, isEmpty } from 'lodash-es';
import { BehaviorSubject, Subject } from 'rxjs';
import { startWith, pairwise, debounceTime, tap, takeUntil, skip } from 'rxjs/operators';
import * as i7 from '@angular/common';
import { CommonModule as CommonModule$1 } from '@angular/common';
import * as i8 from 'ngx-bootstrap/popover';
import { PopoverModule } from 'ngx-bootstrap/popover';
import * as i5$1 from 'ngx-bootstrap/tooltip';
import { TooltipModule } from 'ngx-bootstrap/tooltip';
import { RouterModule } from '@angular/router';
const ALARM_ORDER_VALUES = {
BY_ACTIVE: 'BY_ACTIVE',
BY_DATE_ASCENDING: 'BY_DATE_ASCENDING',
BY_DATE_DESCENDING: 'BY_DATE_DESCENDING',
BY_SEVERITY: 'BY_SEVERITY'
};
const ALARM_ORDER_LABELS = {
BY_ACTIVE: gettext('By active status'),
BY_DATE_ASCENDING: gettext('By date (ascending)'),
BY_DATE_DESCENDING: gettext('By date (descending)'),
BY_SEVERITY: gettext('By severity')
};
const ASSET_ALARMS_WIDGET_ID = 'Asset Alarms';
const RECENT_ALARMS_WIDGET_ID = 'Recent Alarms';
const GLOBAL_INTERVAL_OPTION = 'global-interval';
var DATE_SELECTION;
(function (DATE_SELECTION) {
DATE_SELECTION["CONFIG"] = "config";
DATE_SELECTION["VIEW_AND_CONFIG"] = "view_and_config";
DATE_SELECTION["DASHBOARD_CONTEXT"] = "dashboard_context";
})(DATE_SELECTION || (DATE_SELECTION = {}));
const DEFAULT_PAGE_SIZE = 20;
class AlarmWidgetService {
constructor(alarmsViewService) {
this.alarmsViewService = alarmsViewService;
}
/**
* Checks if the provided data follows the LegacyAlarmConfig structure.
*
* This function determines if a given data object is an instance of LegacyAlarmConfig
* by checking for the presence of the 'options' property.
*
* @param data - The data object to be checked.
* @returns - Returns `true` if the data object is a LegacyAlarmConfig, otherwise `false`.
*/
isOldAlarmConfigStructure(data) {
return data !== null && typeof data === 'object' && 'options' in data;
}
/**
* Creates predefined widget configuration object.
*
* This method creates a new configuration object based on
* a widgets ID (that determines if is a legacy Recent or Critical alarms widget).
*
* @param isIntervalRefresh - determines a type of a refresh.
* @param widgetId - determines if a config should be done for Recent or Critical alarms widget.
* @returns The new, predefined configuration object.
*/
getPredefinedConfiguration(isIntervalRefresh, widgetId) {
return {
order: ALARM_ORDER_VALUES.BY_ACTIVE,
severities: widgetId === RECENT_ALARMS_WIDGET_ID
? {
[Severity.CRITICAL]: true,
[Severity.MAJOR]: true,
[Severity.MINOR]: true,
[Severity.WARNING]: true
}
: {
[Severity.CRITICAL]: true,
[Severity.MAJOR]: false,
[Severity.MINOR]: false,
[Severity.WARNING]: false
},
status: widgetId === RECENT_ALARMS_WIDGET_ID
? {
[AlarmStatus.ACKNOWLEDGED]: true,
[AlarmStatus.ACTIVE]: true,
[AlarmStatus.CLEARED]: true
}
: {
[AlarmStatus.ACKNOWLEDGED]: false,
[AlarmStatus.ACTIVE]: true,
[AlarmStatus.CLEARED]: false
},
types: [''],
isRealtime: !isIntervalRefresh,
isAutoRefreshEnabled: true,
refreshInterval: isIntervalRefresh ? this.alarmsViewService.DEFAULT_INTERVAL_VALUE : undefined
};
}
/**
* Transforms a LegacyAlarmConfig object into an AlarmListWidgetConfig object.
*
* This function maps the properties from an old configuration structure (LegacyAlarmConfig)
* to a new configuration structure (AlarmListWidgetConfig).
*
* @param oldConfig - The old configuration object to be transformed.
* @returns - The new configuration object mapped from the old one.
*/
mapToNewConfigStructure(oldConfig, isIntervalRefresh) {
const order = oldConfig.options.orderMode === 'ACTIVE_FIRST'
? ALARM_ORDER_VALUES.BY_ACTIVE
: ALARM_ORDER_VALUES.BY_SEVERITY;
if (!this.isContainingAllSeverityTypes(oldConfig.options.severity)) {
oldConfig.options.severity = this.addAllMissingSeverityTypes(oldConfig.options.severity);
}
return {
order: order,
isRealtime: oldConfig.realtime,
device: oldConfig.device,
showAlarmsForChildren: false,
severities: oldConfig.options.severity,
status: this.allValuesFalse(oldConfig.options.status)
? {
[AlarmStatus.ACKNOWLEDGED]: true,
[AlarmStatus.ACTIVE]: true,
[AlarmStatus.CLEARED]: true
}
: oldConfig.options.status,
types: oldConfig.options.types?.length ? oldConfig.options.types : [''],
isAutoRefreshEnabled: true,
refreshInterval: isIntervalRefresh ? this.alarmsViewService.DEFAULT_INTERVAL_VALUE : undefined
};
}
/**
* Checks if the provided severity object contains all the predefined severity types.
*
* @param severity - A record object where keys are severity type strings and values are boolean.
* - This object is checked against the predefined severity types.
* @returns `true` if all predefined severity types are present in the severity object; otherwise, `false`.
*/
isContainingAllSeverityTypes(severity) {
return Object.keys(SEVERITY_LABELS).every(severityType => severityType in severity);
}
/**
* Adds any missing severity types to the provided severity object with a default value of `false`.
*
* @param severity - A record object where keys are severity type strings and values are boolean.
* - Missing severity types will be added to this object.
* @returns The modified severity object, which includes all predefined severity types, adding any
* that were missing with a value of `false`.
*/
addAllMissingSeverityTypes(severity) {
Object.keys(SEVERITY_LABELS).forEach(severityType => {
if (!(severityType in severity)) {
severity[severityType] = false;
}
});
return severity;
}
/**
* Maps an AlarmListWidgetConfig object to an AlarmQueryFilter.
*
* This function converts the provided AlarmListWidgetConfig into a format suitable for querying alarms.
*
* @param config - The configuration object for the alarm list widget.
* @param pageSize - Optional number specifying the size of the pages to be returned in the query.
* @returns - The query filter object constructed from the provided configuration.
*/
mapConfigToQueryFilter(config, pageSize) {
const filter = {
pageSize: pageSize || DEFAULT_PAGE_SIZE,
query: this.getOrderParameters(config.order),
severity: this.extractFilterParams(config.severities || {}),
status: this.extractFilterParams(config.status || {}),
type: (config.types || []).join(','),
withTotalPages: true
};
if (config.dateFilter) {
filter.lastUpdatedFrom =
typeof config.dateFilter[0] === 'string'
? config.dateFilter[0]
: config.dateFilter[0].toISOString();
filter.createdTo =
typeof config.dateFilter[1] === 'string'
? config.dateFilter[1]
: config.dateFilter[1].toISOString();
}
if (config.device) {
filter.source = config.device.id;
filter.withSourceAssets = true;
filter.withSourceDevices = true;
filter.withSourceAssets = config.showAlarmsForChildren ?? true;
filter.withSourceDevices = config.showAlarmsForChildren ?? true;
}
return filter;
}
/**
* Extracts and concatenates filter parameters from a given object.
*
* This function takes an object containing filter settings (either SeverityFilter
* or AlarmStatusSettings) and returns a string of all keys where the corresponding value is true.
* If the object is empty or null, an empty string is returned.
*
* @param obj - The object containing filter settings.
* @returns - A concatenated string of keys with true values, separated by commas.
*/
extractFilterParams(obj) {
if (!obj) {
return '';
}
return Object.entries(obj)
.filter(([, value]) => value)
.map(([key]) => key)
.join(',');
}
/**
* Determines if an incoming real-time alarm has a different status than an existing alarm.
*
* This function checks if the provided incoming real-time alarm's status differs
* from that of an existing alarm with the same ID in the given array of alarms.
*
* @param existingAlarms - The array of existing alarms.
* @param incomingRealtimeAlarm - The incoming real-time alarm to check.
* @returns - True if the existing alarm's status has changed, otherwise false.
*/
hasExistingAlarmChangedStatus(existingAlarms, incomingRealtimeAlarm) {
const existingAlarm = existingAlarms.find(alarm => alarm.id === incomingRealtimeAlarm.id);
return !!existingAlarm && existingAlarm.status !== incomingRealtimeAlarm.status;
}
/**
* Filters alarms based on their status, severity, and type.
*
* This method determines if a given alarm, identified either by a numeric ID or an `IAlarm` object,
* matches specific criteria defined in `alarms` and optionally `config`.
*
* @param alarm - The alarm to check, represented either by a numeric ID or an `IAlarm` object.
* @param alarms - An array of `IAlarm` objects against which the given alarm is evaluated.
* @param config - Optional. Configuration for the alarm list widget, used to define additional
* filtering criteria.
*
* @returns `true` if the alarm matches the specified criteria; otherwise, `false`.
* If `alarm` is a number, it always returns `false`.
* If `config` is not provided, it uses a legacy filter for critical alarms.
*
* @remarks
* - When `alarm` is a numeric ID, the function returns `false` as it cannot match against type and severity.
* - If `config` is not provided, the function assumes a legacy scenario for filtering all critical alarms.
*/
filterAlarmsByStatusSeverityAndType(alarm, alarms, config) {
if (typeof alarm === 'number') {
return false;
}
return this.isAlarmMatchedByConfig(alarm, alarms, config);
}
/**
* Determines if all values in the given object are false.
*
* This function checks every value in the provided object to see if they are all false.
*
* @param obj - An object with boolean values.
* @returns - Returns `true` if all values in the object are false, otherwise `false`.
*/
allValuesFalse(obj) {
return Object.values(obj).every(value => !value);
}
/**
* Constructs a string of order parameters for a query based on the specified alarm order.
*
* This function takes an alarm order and maps it to a corresponding set of order parameters.
* It supports different ordering types, such as BY_ACTIVE, BY_SEVERITY, and BY_DATE_ASCENDING.
* The order parameters are used to construct a query string that determines the order
* in which alarms are retrieved or displayed.
*
* @param order - The specified order for sorting alarms (e.g., BY_ACTIVE).
* @returns - A string of order parameters to be used in a query, or an empty string if the order type is unrecognized.
*/
getOrderParameters(order) {
let orderParams;
switch (order) {
case ALARM_ORDER_VALUES.BY_ACTIVE:
orderParams = ['status asc', 'severity asc', 'time.date desc', 'text asc'];
return this.buildOrderParameters(orderParams);
case ALARM_ORDER_VALUES.BY_SEVERITY:
orderParams = ['severity asc', 'time.date desc', 'text asc'];
return this.buildOrderParameters(orderParams);
case ALARM_ORDER_VALUES.BY_DATE_ASCENDING:
orderParams = ['time.date asc', 'text asc'];
return this.buildOrderParameters(orderParams);
default:
orderParams = ['time.date desc', 'text asc'];
return this.buildOrderParameters(orderParams);
}
}
/**
* Determines if an alarm is matched by the specified widget configuration.
*
* This function evaluates whether a given alarm should be included based on the severity,
* status, and type filters defined in the AlarmListWidgetConfig. It checks if the alarm's
* severity and status match the configuration settings and if the alarm's type is included
* in the configuration's types (if specified).
*
* @ignore
* @param alarm - The alarm to evaluate.
* @param alarms - An array of existing alarms, used for status matching.
* @param config - The configuration settings to match against.
* @returns - Returns `true` if the alarm matches the configuration criteria; otherwise, `false`.
*/
isAlarmMatchedByConfig(alarm, alarms, config) {
const isSeverityMatched = this.isSeverityMatching(alarm.severity, config);
const isStatusMatched = this.isStatusMatching(alarm, alarms, config);
const isTypeMatched = this.isTypesMatching(config, alarm);
return isSeverityMatched && isStatusMatched && isTypeMatched;
}
/**
* Checks if the severity of an alarm matches the configuration setting.
*
* This function determines whether the severity of an alarm is included in the
* severity settings defined in the AlarmListWidgetConfig.
*
* @ignore
* @param severity - The severity of the alarm to check.
* @param config - The configuration with severity settings.
* @returns - Returns `true` if the alarm's severity matches the configuration; otherwise, `false`.
*/
isSeverityMatching(severity, config) {
return !!config.severities[severity];
}
/**
* Evaluates if the status of an alarm matches the configuration setting or has changed.
*
* This function checks if the status of an alarm is included in the status settings defined in
* the AlarmListWidgetConfig, or if the alarm's status has changed based on the existing alarms.
*
* @ignore
* @param alarm - The alarm whose status is to be evaluated.
* @param alarms - An array of existing alarms to compare against for status changes.
* @param config - The configuration with status settings.
* @returns - Returns `true` if the alarm's status matches or has changed as per the configuration; otherwise, `false`.
*/
isStatusMatching(alarm, alarms, config) {
return !!config.status[alarm.status] || this.hasExistingAlarmChangedStatus(alarms, alarm);
}
/**
* Checks if the configuration's types array contains only empty string or includes a specific alarm type.
*
* @param config - The configuration object with a `types` property.
* @param alarm - The alarm object with a `type` property to check against the config's types.
* @returns `true` if the config's types array contains only empty string or includes the alarm's type, otherwise `false`.
*/
isTypesMatching(config, alarm) {
return Array.isArray(config.types) && config.types.length === 1 && config.types[0] === ''
? true
: config.types?.includes(alarm.type) ?? false;
}
/**
* Constructs a query string from an array of order parameters.
*
* This function takes an array of ordering parameters and constructs a query string
* for use in alarm ordering queries. The parameters are concatenated into a single string,
* prefixed with '$orderby='.
*
* @ignore
* @private
* @param orderParams - The order parameters to be included in the query.
* @returns - A query string representing the order parameters.
*/
buildOrderParameters(orderParams) {
return `$orderby=${orderParams.join(',')}`;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AlarmWidgetService, deps: [{ token: i3.AlarmsViewService }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AlarmWidgetService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AlarmWidgetService, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}], ctorParameters: () => [{ type: i3.AlarmsViewService }] });
class SeverityIconPipe {
transform(severity) {
let severityClassName = '';
let iconClassName = '';
switch (severity) {
case Severity.CRITICAL:
severityClassName = 'critical';
iconClassName = 'exclamation-circle';
break;
case Severity.MAJOR:
severityClassName = 'major';
iconClassName = 'warning';
break;
case Severity.MINOR:
severityClassName = 'minor';
iconClassName = 'high-priority';
break;
case Severity.WARNING:
severityClassName = 'warning';
iconClassName = 'circle';
break;
default:
return { iconClass: '', severityClass: '' };
}
return {
iconClass: `status icon-lg stroked-icon dlt-c8y-icon-${iconClassName} ${severityClassName}`,
c8yIcon: severityClassName
};
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: SeverityIconPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "18.2.13", ngImport: i0, type: SeverityIconPipe, name: "severityIcon" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: SeverityIconPipe, decorators: [{
type: Pipe,
args: [{
name: 'severityIcon'
}]
}] });
class SortingDescriptionPopoverMessagePipe {
transform(order) {
switch (order) {
case ALARM_ORDER_VALUES.BY_ACTIVE:
return gettext('Order alarms by active status, severity, and time.');
case ALARM_ORDER_VALUES.BY_DATE_ASCENDING:
return gettext('Order alarms by time, starting with the oldest ones.');
case ALARM_ORDER_VALUES.BY_DATE_DESCENDING:
return gettext('Order alarms by time, starting with the latest ones.');
case ALARM_ORDER_VALUES.BY_SEVERITY:
return gettext('Order alarms by severity and time.');
default:
return '';
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: SortingDescriptionPopoverMessagePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "18.2.13", ngImport: i0, type: SortingDescriptionPopoverMessagePipe, name: "sortingDescriptionPopoverMessage" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: SortingDescriptionPopoverMessagePipe, decorators: [{
type: Pipe,
args: [{
name: 'sortingDescriptionPopoverMessage'
}]
}] });
class AlarmListWidgetConfigComponent {
constructor(alarmListWidgetService, alarmService, alarmsViewService, alertService, form, formBuilder, translateService) {
this.alarmListWidgetService = alarmListWidgetService;
this.alarmService = alarmService;
this.alarmsViewService = alarmsViewService;
this.alertService = alertService;
this.form = form;
this.formBuilder = formBuilder;
this.translateService = translateService;
this.REFRESH_INTERVAL_IN_MILLISECONDS = this.alarmsViewService.DEFAULT_INTERVAL_VALUES;
this.SEVERITY_LABELS = SEVERITY_LABELS;
this.STATUS_LABELS = ALARM_STATUS_LABELS;
this.ACKNOWLEDGE_STATUS_VALUE = AlarmStatus.ACKNOWLEDGED;
this.CLEARED_STATUS_VALUE = AlarmStatus.CLEARED;
this.BELL_SLASH_ICON = ALARM_STATUS_ICON.BELL_SLASH;
this.BELL_ICON = ALARM_STATUS_ICON.BELL;
this.C8Y_ALERT_IDLE_ICON = ALARM_STATUS_ICON.ALERT_IDLE;
this.ALARM_ORDER_LABELS = ALARM_ORDER_LABELS;
this.GLOBAL_INTERVAL_OPTION = GLOBAL_INTERVAL_OPTION;
this.alarms$ = new BehaviorSubject(null);
this.orderList = Object.values(ALARM_ORDER_VALUES);
this.severityList = Object.keys(SEVERITY_LABELS);
this.showDateFilter = true;
this.DATE_SELECTION = DATE_SELECTION;
this.dateSelectionHelp = this.translateService.instant(gettext(`Choose how to select a date range, the available options are:
<ul class="m-l-0 p-l-8 m-t-8 m-b-0">
<li>
<b>Widget configuration:</b>
restricts the date selection only to the widget configuration
</li>
<li>
<b>Widget and widget configuration:</b>
restricts the date selection to the widget view and widget configuration only
</li>
<li>
<b>Dashboard time range:</b>
restricts date selection to the global dashboard configuration only
</li>
</ul>`));
/**
* Order does matter.
*/
this.statusList = [AlarmStatus.ACTIVE, AlarmStatus.ACKNOWLEDGED, AlarmStatus.CLEARED];
}
async ngOnInit() {
this.refreshTypeTitle = this.config.isRealtime
? this.translateService.instant(gettext('Realtime'))
: this.translateService.instant(gettext('Auto refresh'));
this.refreshTypePopoverMessage = this.config.isRealtime
? this.translateService.instant(gettext('Change the default state of realtime refresh.'))
: this.translateService.instant(gettext('Change the state of interval automatic refresh and set the refresh frequency.'));
this.initializeForm();
if (!this.config.isRealtime) {
this.updateConfigBasedOnRefreshOption();
}
this.config.isRealtime = !this.alarmsViewService.isIntervalRefresh();
const isWidgetWithExistingConfig = this.config.order && this.config.severities && this.config.status;
if (isWidgetWithExistingConfig) {
await this.getAlarms(this.config);
}
else {
/** Used when creating a new widget */
await this.getAlarms(this.formGroup.value);
}
this.handlePreviewSubscription();
}
ngOnChanges(changes) {
if (this.formGroup && changes.config) {
this.formGroup.get('device').patchValue(this.config.device);
}
}
ngOnDestroy() {
this.formChangesSubscription?.unsubscribe();
}
onBeforeSave(config) {
const { types } = this.formGroup.value;
if (types.length > 1) {
const stringTypes = types;
if (this.isContainingOnlyEmptyTypes(stringTypes)) {
this.formGroup.value.types = [''];
}
else {
this.formGroup.value.types = this.filterEmptyTypes(stringTypes);
}
}
/**
* Applies only to converted legacy Alarm list widget
*/
if (config['options']) {
delete config['options'];
}
Object.assign(config, this.formGroup.value);
return true;
}
onDateFilterChange(event) {
this.formGroup.patchValue({
dateFilter: event.selectedDates,
interval: event.interval
});
}
removeType(index) {
if (this.types.controls.length === 1) {
this.formGroup.get('types').reset();
}
else {
this.types.removeAt(index);
}
}
addType() {
this.types.push(this.formBuilder.control(''));
}
updateRefreshOption() {
this.config.refreshOption = this.refreshOption;
this.formGroup.controls.refreshOption.setValue(this.refreshOption);
const isGlobalInterval = this.refreshOption === GLOBAL_INTERVAL_OPTION;
this.config.widgetInstanceGlobalAutoRefreshContext = isGlobalInterval;
this.formGroup.controls.widgetInstanceGlobalAutoRefreshContext.setValue(isGlobalInterval);
this.updateConfigBasedOnRefreshOption();
}
get types() {
return this.formGroup.get('types');
}
dateSelectionChange(dateSelection) {
if (dateSelection === DATE_SELECTION.CONFIG) {
this.showDateFilter = true;
this.formGroup.patchValue({
displayDateSelection: false,
widgetInstanceGlobalTimeContext: false
});
}
else if (dateSelection === DATE_SELECTION.VIEW_AND_CONFIG) {
this.showDateFilter = true;
this.formGroup.patchValue({
displayDateSelection: true,
widgetInstanceGlobalTimeContext: false
});
}
else if (dateSelection === DATE_SELECTION.DASHBOARD_CONTEXT) {
this.showDateFilter = false;
this.formGroup.patchValue({
displayDateSelection: false,
widgetInstanceGlobalTimeContext: true
});
}
}
filterEmptyTypes(types) {
return types.filter((element) => element !== '' && element !== null);
}
isContainingOnlyEmptyTypes(types) {
return types.every((element) => element === '' || element === null);
}
async getAlarms(config) {
try {
this.isLoading = true;
const result = await this.alarmService.list(this.alarmListWidgetService.mapConfigToQueryFilter(config, DEFAULT_PAGE_SIZE));
this.alarms$.next(result);
}
catch (error) {
this.alertService.addServerFailure(error);
}
finally {
this.isLoading = false;
}
}
initializeForm() {
this.formGroup = this.createForm();
this.form.form.addControl('config', this.formGroup);
this.formGroup.patchValue(this.config);
this.initializeTypes(this.config.types);
this.initDateSelection();
this.refreshOption = this.config.refreshOption ?? 'interval';
}
createForm() {
return this.formBuilder.group({
status: this.formBuilder.group(DEFAULT_STATUS_VALUES, {
validators: this.minSelectedCheckboxes(1)
}),
showAlarmsForChildren: true,
types: this.formBuilder.array([]),
severities: this.formBuilder.group(DEFAULT_SEVERITY_VALUES, {
validators: this.minSelectedCheckboxes(1)
}),
order: ALARM_ORDER_VALUES.BY_ACTIVE,
isAutoRefreshEnabled: [true],
refreshInterval: !this.config.isRealtime
? new FormControl({
value: this.alarmsViewService.DEFAULT_INTERVAL_VALUE,
disabled: this.isAutorefershDisabled()
})
: new FormControl(undefined),
device: this.config.device ? new FormControl(this.config.device) : new FormControl(undefined),
dateFilter: this.config.dateFilter
? new FormControl(this.config.dateFilter)
: new FormControl([new Date(0), new Date()]),
displayDateSelection: this.config.displayDateSelection || false,
widgetInstanceGlobalTimeContext: this.config.widgetInstanceGlobalTimeContext || false,
interval: this.config.interval || 'none',
refreshOption: this.config.isRealtime ? null : 'interval',
widgetInstanceGlobalAutoRefreshContext: this.config.refreshOption === GLOBAL_INTERVAL_OPTION
});
}
isAutorefershDisabled() {
/**This check is required on widget creation */
if (this.config.isAutoRefreshEnabled === undefined) {
return false;
}
return !this.config.isAutoRefreshEnabled;
}
minSelectedCheckboxes(min = 1) {
const validator = (formGroup) => {
const totalSelected = Object.values(formGroup.controls).reduce((prev, next) => (next.value ? prev + next.value : prev), 0);
return totalSelected >= min ? null : { required: true };
};
return validator;
}
initializeTypes(types) {
const typesControl = this.formGroup.get('types');
if (types) {
types.forEach(type => {
typesControl.push(this.formBuilder.control(type));
});
}
else {
typesControl.push(this.formBuilder.control(''));
}
}
initDateSelection() {
this.dateSelection = this.config?.widgetInstanceGlobalTimeContext
? DATE_SELECTION.DASHBOARD_CONTEXT
: this.config?.displayDateSelection
? DATE_SELECTION.VIEW_AND_CONFIG
: DATE_SELECTION.CONFIG;
this.dateSelectionChange(this.dateSelection);
const interval = this.config?.interval ?? 'none';
if (interval === 'none' || interval === 'custom') {
return;
}
this.config.dateFilter = this.alarmsViewService.getDateTimeContextByInterval(this.config.interval);
this.formGroup.patchValue({
dateFilter: this.config.dateFilter
});
}
/**
* Initializes and handles the form change subscription for the Alarm list preview at Widget's configuration.
*
* This method subscribes to form value changes with a debounce time of 500ms.
* It compares the previous and current form values to determine if the changes are relevant.
* If the only change is in the `isAutoRefreshEnabled` property, the change is ignored.
*/
handlePreviewSubscription() {
this.formChangesSubscription = this.formGroup.valueChanges
.pipe(startWith(this.formGroup.value), pairwise(), debounceTime(500))
.subscribe(([previousConfig, currentConfig]) => {
const typedPreviousConfig = previousConfig;
const typedCurrentConfig = currentConfig;
if (this.isOnlyAutoRefreshOrIntervalChanged(typedPreviousConfig, typedCurrentConfig)) {
return;
}
if (this.alarmListWidgetService.allValuesFalse(typedCurrentConfig.severities) ||
this.alarmListWidgetService.allValuesFalse(typedCurrentConfig.status)) {
this.alarms$.next({ data: [], res: null });
return;
}
this.getAlarms(typedCurrentConfig);
});
}
/**
* Determines if the only changes between two `AlarmListWidgetConfig` objects are in the `isAutoRefreshEnabled` and `refreshInterval` properties.
*
* This method compares two configurations by omitting `isAutoRefreshEnabled` and `refreshInterval`.
* It returns true if these are the only properties that differ between the previous and current configurations.
* Note that `refreshInterval` is relevant only when the refresh type is set to an interval.
*
* @param previousConfig - The previous configuration of `AlarmListWidgetConfig`.
* @param currentConfig - The current configuration of `AlarmListWidgetConfig`.
* @returns `true` if the differences are confined to `isAutoRefreshEnabled` and `refreshInterval`, `false` otherwise.
*/
isOnlyAutoRefreshOrIntervalChanged(previousConfig, currentConfig) {
const prevConfigCopy = omit(previousConfig, 'isAutoRefreshEnabled', 'refreshInterval');
const currentConfigCopy = omit(currentConfig, 'isAutoRefreshEnabled', 'refreshInterval');
return isEqual(prevConfigCopy, currentConfigCopy);
}
updateConfigBasedOnRefreshOption() {
const isInterval = this.refreshOption === 'interval';
this.config.isAutoRefreshEnabled = isInterval;
this.formGroup.controls.isAutoRefreshEnabled.setValue(isInterval);
isInterval
? this.formGroup.get('refreshInterval').enable()
: this.formGroup.get('refreshInterval').disable();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AlarmListWidgetConfigComponent, deps: [{ token: AlarmWidgetService }, { token: i2.AlarmService }, { token: i3.AlarmsViewService }, { token: i2$1.AlertService }, { token: i5.NgForm }, { token: i5.FormBuilder }, { token: i6.TranslateService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: AlarmListWidgetConfigComponent, selector: "c8y-alarm-list-widget-config", inputs: { config: "config" }, usesOnChanges: true, ngImport: i0, template: "<div\n class=\"p-l-24 p-r-24\"\n [style.pointer-events]=\"'auto'\"\n [style.opacity]=\"1\"\n></div>\n\n<form\n class=\"row\"\n [formGroup]=\"formGroup\"\n>\n <div class=\"form-group col-md-12\">\n <div class=\"d-flex a-i-center\">\n <label\n class=\"m-b-0\"\n translate\n >\n Date selection\n </label>\n <button\n class=\"btn-help btn-help--sm\"\n [attr.aria-label]=\"'Help' | translate\"\n [popover]=\"dateSelectionHelpTemplate\"\n placement=\"bottom\"\n triggers=\"focus\"\n container=\"body\"\n [adaptivePosition]=\"false\"\n ></button>\n </div>\n <div class=\"c8y-select-wrapper\">\n <select\n class=\"form-control input-sm\"\n [ngModel]=\"dateSelection\"\n (ngModelChange)=\"dateSelectionChange($event)\"\n [ngModelOptions]=\"{ standalone: true }\"\n >\n <option\n title=\"{{ 'Widget configuration' | translate }}\"\n [value]=\"DATE_SELECTION.CONFIG\"\n >\n {{ 'Widget configuration' | translate }}\n </option>\n <option\n title=\"{{ 'Widget and widget configuration' | translate }}\"\n [value]=\"DATE_SELECTION.VIEW_AND_CONFIG\"\n >\n {{ 'Widget and widget configuration' | translate }}\n </option>\n <option\n title=\"{{ 'Dashboard time range' | translate }}\"\n [value]=\"DATE_SELECTION.DASHBOARD_CONTEXT\"\n >\n {{ 'Dashboard time range' | translate }}\n </option>\n </select>\n <span></span>\n </div>\n </div>\n <div class=\"col-md-6\">\n <fieldset class=\"c8y-fieldset\">\n <legend>{{ 'Order`of items on a list, noun`' | translate }}</legend>\n <c8y-form-group class=\"m-b-0\">\n <div\n class=\"d-flex m-b-8 a-i-center\"\n *ngFor=\"let order of orderList; let i = index\"\n >\n <label\n class=\"c8y-radio gap-4\"\n title=\"{{ ALARM_ORDER_LABELS[order] | translate }}\"\n >\n <input\n type=\"radio\"\n [value]=\"order\"\n formControlName=\"order\"\n />\n <span class=\"a-s-center\"></span>\n <span class=\"text-truncate\">{{ ALARM_ORDER_LABELS[order] | translate }}</span>\n </label>\n <button\n class=\"btn-help btn-help--sm\"\n [attr.aria-label]=\"'Help' | translate\"\n [popover]=\"order | sortingDescriptionPopoverMessage | translate\"\n placement=\"right\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n [adaptivePosition]=\"true\"\n ></button>\n </div>\n </c8y-form-group>\n </fieldset>\n <fieldset\n class=\"c8y-fieldset\"\n formArrayName=\"severities\"\n >\n <legend>{{ 'Severities' | translate }}</legend>\n <c8y-form-group\n [hasError]=\"formGroup.controls.severities.hasError('required')\"\n [ngClass]=\"{\n 'm-b-8': !formGroup.controls.severities.hasError('required')\n }\"\n >\n <ng-container *ngFor=\"let severityOption of severityList\">\n <label\n class=\"c8y-checkbox m-t-0 gap-4\"\n [title]=\"SEVERITY_LABELS[severityOption] | translate\"\n >\n <input\n type=\"checkbox\"\n [formControlName]=\"severityOption\"\n [name]=\"severityOption\"\n />\n <span class=\"a-s-center\"></span>\n <ng-container *ngIf=\"{ result: severityOption | severityIcon } as severityIcon\">\n <i\n class=\"a-s-center m-r-4 icon-20 {{ severityIcon.result.iconClass }}\"\n [c8yIcon]=\"severityIcon.result.c8yIcon\"\n ></i>\n </ng-container>\n <span>{{ SEVERITY_LABELS[severityOption] | translate }}</span>\n </label>\n </ng-container>\n <c8y-messages>\n <c8y-message *ngIf=\"formGroup.controls.severities.hasError('required')\">\n {{ 'Select at least one severity.' | translate }}\n </c8y-message>\n </c8y-messages>\n </c8y-form-group>\n </fieldset>\n <ng-container *ngIf=\"showDateFilter\">\n <fieldset class=\"c8y-fieldset\">\n <legend>{{ 'Date filter' | translate }}</legend>\n <c8y-form-group class=\"m-b-8\">\n <c8y-alarms-date-filter\n [updateQueryParams]=\"false\"\n [DEFAULT_INTERVAL]=\"config.interval || 'none'\"\n formControlName=\"dateFilter\"\n (dateFilterChange)=\"onDateFilterChange($event)\"\n ></c8y-alarms-date-filter>\n </c8y-form-group>\n </fieldset>\n </ng-container>\n </div>\n\n <div class=\"col-md-6\">\n <fieldset\n class=\"c8y-fieldset\"\n formArrayName=\"status\"\n >\n <legend>{{ 'Status' | translate }}</legend>\n <c8y-form-group\n [hasError]=\"formGroup.controls.status.hasError('required')\"\n [ngClass]=\"{\n 'm-b-8': !formGroup.controls.status.hasError('required')\n }\"\n >\n <ng-container *ngFor=\"let statusOption of statusList\">\n <label\n class=\"c8y-checkbox m-t-0 gap-4\"\n [title]=\"STATUS_LABELS[statusOption] | translate\"\n >\n <input\n type=\"checkbox\"\n [formControlName]=\"statusOption\"\n [name]=\"statusOption\"\n />\n <span class=\"a-s-center\"></span>\n <i\n class=\"a-s-center m-l-4 m-r-4 text-gray-dark c8y-icon icon-20\"\n [c8yIcon]=\"\n statusOption === CLEARED_STATUS_VALUE\n ? C8Y_ALERT_IDLE_ICON\n : statusOption === ACKNOWLEDGE_STATUS_VALUE\n ? BELL_SLASH_ICON\n : BELL_ICON\n \"\n ></i>\n <span>{{ STATUS_LABELS[statusOption] | translate }}</span>\n </label>\n </ng-container>\n <c8y-messages>\n <c8y-message *ngIf=\"formGroup.controls.status.hasError('required')\">\n {{ 'Select at least one status.' | translate }}\n </c8y-message>\n </c8y-messages>\n </c8y-form-group>\n </fieldset>\n\n <fieldset class=\"c8y-fieldset\">\n <legend>{{ 'Child devices' | translate }}</legend>\n <c8y-form-group class=\"m-b-8\">\n <label\n class=\"c8y-switch\"\n [title]=\"'Show alarms from child devices' | translate\"\n >\n <input\n type=\"checkbox\"\n formControlName=\"showAlarmsForChildren\"\n />\n <span></span>\n <span>{{ 'Show alarms' | translate }}</span>\n <span class=\"sr-only\">{{ 'Show alarms' | translate }}</span>\n </label>\n </c8y-form-group>\n </fieldset>\n\n <fieldset class=\"c8y-fieldset\">\n <legend>{{ 'Types' | translate }}</legend>\n <c8y-form-group\n class=\"m-b-8\"\n formArrayName=\"types\"\n >\n <div\n class=\"input-group\"\n *ngFor=\"let type of types.controls; let i = index\"\n >\n <input\n class=\"form-control\"\n type=\"text\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 'c8y_Alarm' }\"\n [formControlName]=\"i\"\n />\n <div class=\"input-group-btn\">\n <button\n class=\"btn btn-dot btn-dot--danger m-l-auto\"\n [title]=\"'Remove' | translate\"\n type=\"button\"\n (click)=\"removeType(i)\"\n [disabled]=\"types.controls?.length === 1 && !type.value\"\n >\n <i c8yIcon=\"minus-circle\"></i>\n </button>\n </div>\n <div\n class=\"input-group-btn\"\n *ngIf=\"i === types.controls.length - 1\"\n >\n <button\n class=\"btn btn-dot btn-dot--primary m-l-auto\"\n [title]=\"'Add alarm type' | translate\"\n type=\"button\"\n (click)=\"addType()\"\n >\n <i c8yIcon=\"plus-circle\"></i>\n </button>\n </div>\n </div>\n </c8y-form-group>\n </fieldset>\n <fieldset class=\"c8y-fieldset\">\n <legend class=\"d-flex\">\n {{ refreshTypeTitle }}\n <button\n class=\"btn-help btn-help--sm\"\n [attr.aria-label]=\"'Help' | translate\"\n [popover]=\"refreshTypePopoverMessage\"\n placement=\"top\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n [adaptivePosition]=\"true\"\n ></button>\n </legend>\n <c8y-form-group class=\"m-b-8 form-group-sm\">\n <div class=\"d-flex gap-8 a-i-center\">\n <ng-template #realtime>\n <label class=\"c8y-switch\">\n <input\n id=\"refreshToggle\"\n name=\"isAutoRefreshEnabled\"\n type=\"checkbox\"\n formControlName=\"isAutoRefreshEnabled\"\n />\n <span></span>\n <span class=\"sr-only\">{{ 'Realtime refresh' | translate }}</span>\n </label>\n </ng-template>\n <div\n class=\"c8y-select-wrapper\"\n *ngIf=\"!config.isRealtime; else realtime\"\n >\n <select\n class=\"form-control\"\n title=\"{{ 'Refresh options`options for refreshing a view`' | translate }}\"\n [(ngModel)]=\"refreshOption\"\n [ngModelOptions]=\"{ standalone: true }\"\n (change)=\"updateRefreshOption()\"\n >\n <option value=\"none\">\n {{ 'No automatic refresh' | translate }}\n </option>\n <option\n [title]=\"'Refreshing after the given interval' | translate\"\n value=\"interval\"\n >\n {{ 'Use refresh interval' | translate }}\n </option>\n <option\n [title]=\"'Refreshing after the given interval, synchronized globally' | translate\"\n value=\"global-interval\"\n >\n {{ 'Use global refresh interval' | translate }}\n </option>\n </select>\n </div>\n <ng-container\n *ngIf=\"!config.isRealtime && config.refreshOption !== GLOBAL_INTERVAL_OPTION\"\n >\n <label\n class=\"m-b-0\"\n for=\"refreshInterval\"\n >\n {{ 'Interval' | translate }}\n </label>\n <div class=\"c8y-select-wrapper flex-grow\">\n <select\n class=\"form-control text-12\"\n [title]=\"'Refresh interval in seconds' | translate\"\n id=\"refreshInterval\"\n formControlName=\"refreshInterval\"\n >\n <option\n *ngFor=\"let refreshInterval of REFRESH_INTERVAL_IN_MILLISECONDS\"\n [ngValue]=\"refreshInterval\"\n >\n {{ '{{ seconds }} s' | translate: { seconds: refreshInterval / 1000 } }}\n </option>\n </select>\n </div>\n </ng-container>\n </div>\n </c8y-form-group>\n </fieldset>\n </div>\n</form>\n\n<div class=\"legend form-block m-b-0\">{{ 'Preview`of an alarm list`' | translate }}</div>\n<c8y-alarms-list\n [alarms]=\"alarms$ | async\"\n [isInitialLoading]=\"isLoading\"\n [navigationOptions]=\"{\n allowNavigationToAlarmsView: false,\n alwaysNavigateToAllAlarms: false,\n includeClearedQueryParams: false,\n queryParamsHandling: 'merge'\n }\"\n></c8y-alarms-list>\n\n<ng-template #dateSelectionHelpTemplate>\n <div [innerHTML]=\"dateSelectionHelp\"></div>\n</ng-template>\n", dependencies: [{ kind: "component", type: i3.AlarmsListComponent, selector: "c8y-alarms-list", inputs: ["alarms", "hasPermissions", "typeFilters", "loadMoreMode", "navigationOptions", "isInitialLoading", "splitView"], outputs: ["onSelectedAlarm", "onScrollingStateChange"] }, { kind: "component", type: i3.AlarmsDateFilterComponent, selector: "c8y-alarms-date-filter", inputs: ["DEFAULT_INTERVAL", "updateQueryParams", "date"], outputs: ["dateFilterChange"] }, { kind: "directive", type: i2$1.IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: i2$1.C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: i7.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i7.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i7.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i5.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i5.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i5.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i5.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: i5.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i5.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i5.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i5.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i5.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i5.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i2$1.FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "directive", type: i2$1.MessageDirective, selector: "c8y-message", inputs: ["name", "text"] }, { kind: "component", type: i2$1.MessagesCo