Merge branch 'master' into feature/time-series-chart
This commit is contained in:
commit
ddb0c0fd2f
@ -159,6 +159,7 @@ export interface IStateController {
|
||||
dashboardCtrl: IDashboardController;
|
||||
getStateParams(): StateParams;
|
||||
stateChanged(): Observable<string>;
|
||||
stateId(): Observable<string>;
|
||||
getStateParamsByStateId(stateId: string): StateParams;
|
||||
openState(id: string, params?: StateParams, openRightLayout?: boolean): void;
|
||||
updateState(id?: string, params?: StateParams, openRightLayout?: boolean): void;
|
||||
|
||||
@ -270,6 +270,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
|
||||
state: null,
|
||||
stateController: null,
|
||||
stateChanged: null,
|
||||
stateId: null,
|
||||
aliasController: null,
|
||||
runChangeDetection: this.runChangeDetection.bind(this)
|
||||
};
|
||||
|
||||
@ -39,6 +39,7 @@ export interface DashboardContext {
|
||||
aliasController: IAliasController;
|
||||
stateController: IStateController;
|
||||
stateChanged: Observable<string>;
|
||||
stateId: Observable<string>;
|
||||
runChangeDetection: () => void;
|
||||
}
|
||||
|
||||
|
||||
@ -240,6 +240,7 @@ export class DefaultStateControllerComponent extends StateControllerComponent im
|
||||
private gotoState(stateId: string, update: boolean, openRightLayout?: boolean) {
|
||||
if (this.dashboardCtrl.dashboardCtx.state !== stateId) {
|
||||
this.dashboardCtrl.openDashboardState(stateId, openRightLayout);
|
||||
this.stateIdSubject.next(stateId);
|
||||
if (this.syncStateWithQueryParam && stateId && this.statesValue[stateId]) {
|
||||
this.mobileService.handleDashboardStateName(this.getStateName(stateId, this.statesValue[stateId]));
|
||||
}
|
||||
|
||||
@ -280,6 +280,9 @@ export class EntityStateControllerComponent extends StateControllerComponent imp
|
||||
private gotoState(stateId: string, update: boolean, openRightLayout?: boolean) {
|
||||
const isStateIdChanged = this.dashboardCtrl.dashboardCtx.state !== stateId;
|
||||
this.dashboardCtrl.openDashboardState(stateId, openRightLayout);
|
||||
if (isStateIdChanged) {
|
||||
this.stateIdSubject.next(stateId);
|
||||
}
|
||||
if (this.syncStateWithQueryParam) {
|
||||
this.mobileService.handleDashboardStateName(this.getStateName(this.stateObject.length - 1));
|
||||
}
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
import { IStateControllerComponent, StateControllerState } from '@home/components/dashboard-page/states/state-controller.models';
|
||||
import { IDashboardController } from '../dashboard-page.models';
|
||||
import { DashboardState } from '@app/shared/models/dashboard.models';
|
||||
import { Observable, Subject, Subscription } from 'rxjs';
|
||||
import { BehaviorSubject, Observable, Subject, Subscription } from 'rxjs';
|
||||
import { NgZone, OnDestroy, OnInit, Directive } from '@angular/core';
|
||||
import { ActivatedRoute, Params, Router } from '@angular/router';
|
||||
import { StatesControllerService } from '@home/components/dashboard-page/states/states-controller.service';
|
||||
@ -28,6 +28,7 @@ import { StateObject, StateParams } from '@app/core/api/widget-api.models';
|
||||
export abstract class StateControllerComponent implements IStateControllerComponent, OnInit, OnDestroy {
|
||||
|
||||
private stateChangedSubject = new Subject<string>();
|
||||
protected stateIdSubject = new Subject<string>();
|
||||
stateObject: StateControllerState = [];
|
||||
dashboardCtrl: IDashboardController;
|
||||
preservedState: any;
|
||||
@ -126,6 +127,7 @@ export abstract class StateControllerComponent implements IStateControllerCompon
|
||||
subscription.unsubscribe();
|
||||
});
|
||||
this.rxSubscriptions.length = 0;
|
||||
this.stateIdSubject.complete();
|
||||
this.stateChangedSubject.complete();
|
||||
}
|
||||
|
||||
@ -152,6 +154,10 @@ export abstract class StateControllerComponent implements IStateControllerCompon
|
||||
return this.stateChangedSubject.asObservable();
|
||||
}
|
||||
|
||||
public stateId(): Observable<string> {
|
||||
return this.stateIdSubject.asObservable();
|
||||
}
|
||||
|
||||
public openRightLayout(): void {
|
||||
this.dashboardCtrl.openRightLayout();
|
||||
}
|
||||
|
||||
@ -28,7 +28,7 @@ import { DashboardState } from '@shared/models/dashboard.models';
|
||||
import { IDashboardController } from '@home/components/dashboard-page/dashboard-page.models';
|
||||
import { StatesControllerService } from '@home/components/dashboard-page/states/states-controller.service';
|
||||
import { IStateControllerComponent } from '@home/components/dashboard-page/states/state-controller.models';
|
||||
import { Subject } from 'rxjs';
|
||||
import { BehaviorSubject, Subject } from 'rxjs';
|
||||
|
||||
@Directive({
|
||||
// eslint-disable-next-line @angular-eslint/directive-selector
|
||||
@ -64,18 +64,21 @@ export class StatesComponentDirective implements OnInit, OnDestroy, OnChanges {
|
||||
stateControllerComponent: IStateControllerComponent;
|
||||
|
||||
private stateChangedSubject = new Subject<string>();
|
||||
private stateIdSubject: BehaviorSubject<string>;
|
||||
|
||||
constructor(private viewContainerRef: ViewContainerRef,
|
||||
private statesControllerService: StatesControllerService) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.stateIdSubject = new BehaviorSubject<string>(this.dashboardCtrl.dashboardCtx.state);
|
||||
this.init();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy();
|
||||
this.stateChangedSubject.complete();
|
||||
this.stateIdSubject.complete();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
@ -128,9 +131,13 @@ export class StatesComponentDirective implements OnInit, OnDestroy, OnChanges {
|
||||
this.stateControllerComponent = this.stateControllerComponentRef.instance;
|
||||
this.dashboardCtrl.dashboardCtx.stateController = this.stateControllerComponent;
|
||||
this.dashboardCtrl.dashboardCtx.stateChanged = this.stateChangedSubject.asObservable();
|
||||
this.dashboardCtrl.dashboardCtx.stateId = this.stateIdSubject.asObservable();
|
||||
this.stateControllerComponent.stateChanged().subscribe((state) => {
|
||||
this.stateChangedSubject.next(state);
|
||||
});
|
||||
this.stateControllerComponent.stateId().subscribe((stateId) => {
|
||||
this.stateIdSubject.next(stateId);
|
||||
});
|
||||
this.stateControllerComponent.preservedState = preservedState;
|
||||
this.stateControllerComponent.dashboardCtrl = this.dashboardCtrl;
|
||||
this.stateControllerComponent.stateControllerInstanceId = stateControllerInstanceId;
|
||||
|
||||
@ -23,8 +23,8 @@ import {
|
||||
telemetryTypeTranslationsShort
|
||||
} from '@shared/models/telemetry/telemetry.models';
|
||||
import { WidgetContext } from '@home/models/widget-component.models';
|
||||
import { BehaviorSubject, forkJoin, Observable, Observer, of, throwError } from 'rxjs';
|
||||
import { catchError, delay, map, share, take } from 'rxjs/operators';
|
||||
import { BehaviorSubject, forkJoin, Observable, Observer, of, Subscription, throwError } from 'rxjs';
|
||||
import { catchError, delay, map, share, take, tap } from 'rxjs/operators';
|
||||
import { UtilsService } from '@core/services/utils.service';
|
||||
import { AfterViewInit, ChangeDetectorRef, Directive, Input, OnDestroy, OnInit, TemplateRef } from '@angular/core';
|
||||
import {
|
||||
@ -186,8 +186,14 @@ export class DataToValueConverter<V> {
|
||||
case DataToValueType.FUNCTION:
|
||||
result = data;
|
||||
try {
|
||||
result = this.dataToValueFunction(!!data ? JSON.parse(data) : data);
|
||||
} catch (e) {}
|
||||
let input = data;
|
||||
if (!!data) {
|
||||
try {
|
||||
input = JSON.parse(data);
|
||||
} catch (_e) {}
|
||||
}
|
||||
result = this.dataToValueFunction(input);
|
||||
} catch (_e) {}
|
||||
break;
|
||||
case DataToValueType.NONE:
|
||||
result = data;
|
||||
@ -233,12 +239,16 @@ export abstract class ValueGetter<V> extends ValueAction {
|
||||
return new AttributeValueGetter<V>(ctx, settings, valueType, valueObserver);
|
||||
case GetValueAction.GET_TIME_SERIES:
|
||||
return new TimeSeriesValueGetter<V>(ctx, settings, valueType, valueObserver);
|
||||
case GetValueAction.GET_DASHBOARD_STATE:
|
||||
return new DashboardStateGetter<V>(ctx, settings, valueType, valueObserver);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly isSimulated: boolean;
|
||||
private readonly dataConverter: DataToValueConverter<V>;
|
||||
|
||||
private getValueSubscription: Subscription;
|
||||
|
||||
protected constructor(protected ctx: WidgetContext,
|
||||
protected settings: GetValueSettings<V>,
|
||||
protected valueType: ValueType,
|
||||
@ -263,7 +273,10 @@ export abstract class ValueGetter<V> extends ValueAction {
|
||||
throw this.handleError(err);
|
||||
})
|
||||
);
|
||||
valueObservable.subscribe({
|
||||
if (this.getValueSubscription) {
|
||||
this.getValueSubscription.unsubscribe();
|
||||
}
|
||||
this.getValueSubscription = valueObservable.subscribe({
|
||||
next: (value) => {
|
||||
this.valueObserver.next(value);
|
||||
},
|
||||
@ -277,6 +290,9 @@ export abstract class ValueGetter<V> extends ValueAction {
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.getValueSubscription) {
|
||||
this.getValueSubscription.unsubscribe();
|
||||
}
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
@ -504,6 +520,19 @@ export class TimeSeriesValueGetter<V> extends TelemetryValueGetter<V, TelemetryV
|
||||
}
|
||||
}
|
||||
|
||||
export class DashboardStateGetter<V> extends ValueGetter<V> {
|
||||
constructor(protected ctx: WidgetContext,
|
||||
protected settings: GetValueSettings<V>,
|
||||
protected valueType: ValueType,
|
||||
protected valueObserver: Partial<Observer<V>>) {
|
||||
super(ctx, settings, valueType, valueObserver);
|
||||
}
|
||||
|
||||
protected doGetValue(): Observable<string> {
|
||||
return this.ctx.stateController.dashboardCtrl.dashboardCtx.stateId;
|
||||
}
|
||||
}
|
||||
|
||||
export class ExecuteRpcValueSetter<V> extends ValueSetter<V> {
|
||||
|
||||
private readonly executeRpcSettings: RpcSettings;
|
||||
|
||||
@ -41,7 +41,7 @@ export const commandButtonDefaultSettings: CommandButtonWidgetSettings = {
|
||||
},
|
||||
setAttribute: {
|
||||
key: 'state',
|
||||
scope: AttributeScope.SHARED_SCOPE
|
||||
scope: AttributeScope.SERVER_SCOPE
|
||||
},
|
||||
putTimeSeries: {
|
||||
key: 'state'
|
||||
|
||||
@ -92,7 +92,7 @@ export const toggleButtonDefaultSettings: ToggleButtonWidgetSettings = {
|
||||
},
|
||||
setAttribute: {
|
||||
key: 'state',
|
||||
scope: AttributeScope.SHARED_SCOPE
|
||||
scope: AttributeScope.SERVER_SCOPE
|
||||
},
|
||||
putTimeSeries: {
|
||||
key: 'state'
|
||||
@ -113,7 +113,7 @@ export const toggleButtonDefaultSettings: ToggleButtonWidgetSettings = {
|
||||
},
|
||||
setAttribute: {
|
||||
key: 'state',
|
||||
scope: AttributeScope.SHARED_SCOPE
|
||||
scope: AttributeScope.SERVER_SCOPE
|
||||
},
|
||||
putTimeSeries: {
|
||||
key: 'state'
|
||||
|
||||
@ -126,7 +126,7 @@ export const powerButtonDefaultSettings: PowerButtonWidgetSettings = {
|
||||
},
|
||||
setAttribute: {
|
||||
key: 'state',
|
||||
scope: AttributeScope.SHARED_SCOPE
|
||||
scope: AttributeScope.SERVER_SCOPE
|
||||
},
|
||||
putTimeSeries: {
|
||||
key: 'state'
|
||||
@ -147,7 +147,7 @@ export const powerButtonDefaultSettings: PowerButtonWidgetSettings = {
|
||||
},
|
||||
setAttribute: {
|
||||
key: 'state',
|
||||
scope: AttributeScope.SHARED_SCOPE
|
||||
scope: AttributeScope.SERVER_SCOPE
|
||||
},
|
||||
putTimeSeries: {
|
||||
key: 'state'
|
||||
|
||||
@ -131,7 +131,7 @@ export const singleSwitchDefaultSettings: SingleSwitchWidgetSettings = {
|
||||
},
|
||||
setAttribute: {
|
||||
key: 'state',
|
||||
scope: AttributeScope.SHARED_SCOPE
|
||||
scope: AttributeScope.SERVER_SCOPE
|
||||
},
|
||||
putTimeSeries: {
|
||||
key: 'state'
|
||||
@ -152,7 +152,7 @@ export const singleSwitchDefaultSettings: SingleSwitchWidgetSettings = {
|
||||
},
|
||||
setAttribute: {
|
||||
key: 'state',
|
||||
scope: AttributeScope.SHARED_SCOPE
|
||||
scope: AttributeScope.SERVER_SCOPE
|
||||
},
|
||||
putTimeSeries: {
|
||||
key: 'state'
|
||||
|
||||
@ -132,7 +132,7 @@ export const sliderWidgetDefaultSettings: SliderWidgetSettings = {
|
||||
},
|
||||
setAttribute: {
|
||||
key: 'state',
|
||||
scope: AttributeScope.SHARED_SCOPE
|
||||
scope: AttributeScope.SERVER_SCOPE
|
||||
},
|
||||
putTimeSeries: {
|
||||
key: 'state'
|
||||
|
||||
@ -125,6 +125,8 @@
|
||||
<div class="fixed-title-width fixed-title-height">{{ 'widgets.value-action.state-when-result-is' | translate:{state: (stateLabel | translate)} }}</div>
|
||||
<tb-value-input
|
||||
fxFlex
|
||||
[valueType]="(getValueSettingsFormGroup.get('action').value === getValueAction.GET_DASHBOARD_STATE &&
|
||||
getValueSettingsFormGroup.get('dataToValue').get('type').value === dataToValueType.NONE) ? ValueType.STRING : null"
|
||||
layout="column"
|
||||
formControlName="compareToValue">
|
||||
</tb-value-input>
|
||||
|
||||
@ -28,7 +28,7 @@ import {
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { MatButton } from '@angular/material/button';
|
||||
import { TbPopoverService } from '@shared/components/popover.service';
|
||||
import { GetValueAction, GetValueSettings } from '@shared/models/action-widget-settings.models';
|
||||
import { DataToValueType, GetValueAction, GetValueSettings } from '@shared/models/action-widget-settings.models';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { ValueType } from '@shared/models/constants';
|
||||
import {
|
||||
@ -170,6 +170,18 @@ export class GetValueActionSettingsComponent implements OnInit, ControlValueAcce
|
||||
case GetValueAction.GET_TIME_SERIES:
|
||||
this.displayValue = this.translate.instant('widgets.value-action.get-time-series-text', {key: this.modelValue.getTimeSeries.key});
|
||||
break;
|
||||
case GetValueAction.GET_DASHBOARD_STATE:
|
||||
if (this.valueType === ValueType.BOOLEAN) {
|
||||
const state = this.modelValue.dataToValue?.compareToValue;
|
||||
if (this.modelValue.dataToValue?.type === DataToValueType.FUNCTION) {
|
||||
this.displayValue = this.translate.instant('widgets.value-action.when-dashboard-state-function-is-text', {state});
|
||||
} else {
|
||||
this.displayValue = this.translate.instant('widgets.value-action.when-dashboard-state-is-text', {state});
|
||||
}
|
||||
} else {
|
||||
this.displayValue = this.translate.instant('widgets.value-action.get-dashboard-state-text');
|
||||
}
|
||||
break;
|
||||
}
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
|
||||
@ -14,7 +14,17 @@
|
||||
/// limitations under the License.
|
||||
///
|
||||
|
||||
import { Component, forwardRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||
import {
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
forwardRef,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
SimpleChanges,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR, NgForm } from '@angular/forms';
|
||||
import { resolveBreakpoint, ValueType, valueTypesMap } from '@shared/models/constants';
|
||||
import { isObject } from '@core/utils';
|
||||
@ -45,7 +55,7 @@ export interface ValueInputLayout {
|
||||
}
|
||||
]
|
||||
})
|
||||
export class ValueInputComponent implements OnInit, OnDestroy, ControlValueAccessor {
|
||||
export class ValueInputComponent implements OnInit, OnDestroy, OnChanges, ControlValueAccessor {
|
||||
|
||||
@Input()
|
||||
disabled: boolean;
|
||||
@ -85,6 +95,7 @@ export class ValueInputComponent implements OnInit, OnDestroy, ControlValueAcces
|
||||
|
||||
constructor(
|
||||
private breakpointObserver: BreakpointObserver,
|
||||
private cd: ChangeDetectorRef,
|
||||
public dialog: MatDialog,
|
||||
) {
|
||||
|
||||
@ -104,6 +115,23 @@ export class ValueInputComponent implements OnInit, OnDestroy, ControlValueAcces
|
||||
}
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
for (const propName of Object.keys(changes)) {
|
||||
const change = changes[propName];
|
||||
if (!change.firstChange) {
|
||||
if (propName === 'valueType') {
|
||||
this.showValueType = !this.valueType;
|
||||
if (this.valueType) {
|
||||
this.updateModelToValueType();
|
||||
} else {
|
||||
this.detectValueType();
|
||||
}
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._subscription.unsubscribe();
|
||||
}
|
||||
@ -144,19 +172,12 @@ export class ValueInputComponent implements OnInit, OnDestroy, ControlValueAcces
|
||||
writeValue(value: any): void {
|
||||
this.modelValue = value;
|
||||
if (this.showValueType) {
|
||||
if (this.modelValue === true || this.modelValue === false) {
|
||||
this.valueType = ValueType.BOOLEAN;
|
||||
} else if (typeof this.modelValue === 'number') {
|
||||
if (this.modelValue.toString().indexOf('.') === -1) {
|
||||
this.valueType = ValueType.INTEGER;
|
||||
} else {
|
||||
this.valueType = ValueType.DOUBLE;
|
||||
}
|
||||
} else if (isObject(this.modelValue)) {
|
||||
this.valueType = ValueType.JSON;
|
||||
} else {
|
||||
this.valueType = ValueType.STRING;
|
||||
}
|
||||
this.detectValueType();
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
this.updateModelToValueType();
|
||||
this.cd.markForCheck();
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -185,6 +206,39 @@ export class ValueInputComponent implements OnInit, OnDestroy, ControlValueAcces
|
||||
this.updateView();
|
||||
}
|
||||
|
||||
private detectValueType() {
|
||||
if (this.modelValue === true || this.modelValue === false) {
|
||||
this.valueType = ValueType.BOOLEAN;
|
||||
} else if (typeof this.modelValue === 'number') {
|
||||
if (this.modelValue.toString().indexOf('.') === -1) {
|
||||
this.valueType = ValueType.INTEGER;
|
||||
} else {
|
||||
this.valueType = ValueType.DOUBLE;
|
||||
}
|
||||
} else if (isObject(this.modelValue)) {
|
||||
this.valueType = ValueType.JSON;
|
||||
} else {
|
||||
this.valueType = ValueType.STRING;
|
||||
}
|
||||
}
|
||||
|
||||
private updateModelToValueType() {
|
||||
if (this.valueType === ValueType.BOOLEAN && typeof this.modelValue !== 'boolean') {
|
||||
this.modelValue = !!this.modelValue;
|
||||
this.updateView();
|
||||
} else if (this.valueType === ValueType.STRING && typeof this.modelValue !== 'string') {
|
||||
this.modelValue = null;
|
||||
this.updateView();
|
||||
} else if ([ValueType.DOUBLE, ValueType.INTEGER].includes(this.valueType) && typeof this.modelValue !== 'number') {
|
||||
this.modelValue = null;
|
||||
this.updateView();
|
||||
} else if (this.valueType === ValueType.JSON && typeof this.modelValue !== 'object') {
|
||||
this.modelValue = {};
|
||||
this.inputForm.form.get('value').patchValue({});
|
||||
this.updateView();
|
||||
}
|
||||
}
|
||||
|
||||
private _computeLayout(): Layout {
|
||||
if (typeof this.layout !== 'object') {
|
||||
return this.layout;
|
||||
|
||||
@ -21,7 +21,8 @@ export enum GetValueAction {
|
||||
DO_NOTHING = 'DO_NOTHING',
|
||||
EXECUTE_RPC = 'EXECUTE_RPC',
|
||||
GET_ATTRIBUTE = 'GET_ATTRIBUTE',
|
||||
GET_TIME_SERIES = 'GET_TIME_SERIES'
|
||||
GET_TIME_SERIES = 'GET_TIME_SERIES',
|
||||
GET_DASHBOARD_STATE = 'GET_DASHBOARD_STATE'
|
||||
}
|
||||
|
||||
export const getValueActions = Object.keys(GetValueAction) as GetValueAction[];
|
||||
@ -39,7 +40,8 @@ export const getValueActionTranslations = new Map<GetValueAction, string>(
|
||||
[GetValueAction.DO_NOTHING, 'widgets.value-action.do-nothing'],
|
||||
[GetValueAction.EXECUTE_RPC, 'widgets.value-action.execute-rpc'],
|
||||
[GetValueAction.GET_ATTRIBUTE, 'widgets.value-action.get-attribute'],
|
||||
[GetValueAction.GET_TIME_SERIES, 'widgets.value-action.get-time-series']
|
||||
[GetValueAction.GET_TIME_SERIES, 'widgets.value-action.get-time-series'],
|
||||
[GetValueAction.GET_DASHBOARD_STATE, 'widgets.value-action.get-dashboard-state']
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
@ -6638,10 +6638,14 @@
|
||||
"get-attribute": "Get attribute",
|
||||
"set-attribute": "Set attribute",
|
||||
"get-time-series": "Get time-series",
|
||||
"get-dashboard-state": "Get dashboard state",
|
||||
"add-time-series": "Add time-series",
|
||||
"execute-rpc-text": "Execute RPC method '{{methodName}}'",
|
||||
"get-attribute-text": "Use attribute '{{key}}'",
|
||||
"get-time-series-text": "Use time-series '{{key}}'",
|
||||
"get-dashboard-state-text": "Use dashboard state",
|
||||
"when-dashboard-state-is-text": "When dashboard state is '{{state}}'",
|
||||
"when-dashboard-state-function-is-text": "When f(dashboard state) is '{{state}}'",
|
||||
"set-attribute-to-value-text": "Set '{{key}}' attribute to: {{value}}",
|
||||
"add-time-series-value-text": "Add '{{key}}' time-series value: {{value}}",
|
||||
"set-attribute-text": "Set '{{key}}' attribute",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user