diff --git a/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html
index ec475e1c3e..a98262d5a2 100644
--- a/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html
+++ b/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html
@@ -54,6 +54,17 @@
+
+ {{ 'widget-config.use-show-widget-action-function' | translate }}
+
+
widget-config.action-type
diff --git a/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts
index d8633c9aff..44687a3e8d 100644
--- a/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts
@@ -38,7 +38,12 @@ import {
WidgetActionsData
} from '@home/components/widget/action/manage-widget-actions.component.models';
import { UtilsService } from '@core/services/utils.service';
-import { WidgetActionSource, WidgetActionType, widgetActionTypeTranslationMap } from '@shared/models/widget.models';
+import {
+ WidgetActionSource,
+ widgetActionSources,
+ WidgetActionType,
+ widgetActionTypeTranslationMap
+} from '@shared/models/widget.models';
import { map, mergeMap, startWith, tap } from 'rxjs/operators';
import { DashboardService } from '@core/http/dashboard.service';
import { Dashboard } from '@shared/models/dashboard.models';
@@ -124,17 +129,41 @@ export class WidgetActionDialogComponent extends DialogComponent {
this.updateActionTypeFormGroup(type);
});
this.widgetActionFormGroup.get('actionSourceId').valueChanges.subscribe(() => {
this.widgetActionFormGroup.get('name').updateValueAndValidity();
+ this.updateShowWidgetActionForm();
+ });
+ this.widgetActionFormGroup.get('useShowWidgetActionFunction').valueChanges.subscribe(() => {
+ this.updateShowWidgetActionForm();
});
}
+ displayShowWidgetActionForm(): boolean {
+ return this.widgetActionFormGroup.get('actionSourceId').value === widgetActionSources.headerButton.value;
+ }
+
+ private updateShowWidgetActionForm() {
+ const actionSourceId = this.widgetActionFormGroup.get('actionSourceId').value;
+ const useShowWidgetActionFunction = this.widgetActionFormGroup.get('useShowWidgetActionFunction').value;
+ if (actionSourceId === widgetActionSources.headerButton.value && useShowWidgetActionFunction) {
+ this.widgetActionFormGroup.get('showWidgetActionFunction').setValidators([Validators.required]);
+ } else {
+ this.widgetActionFormGroup.get('showWidgetActionFunction').clearValidators();
+ }
+ this.widgetActionFormGroup.get('showWidgetActionFunction').updateValueAndValidity();
+ }
+
private updateActionTypeFormGroup(type?: WidgetActionType, action?: WidgetActionDescriptorInfo) {
this.actionTypeFormGroup = this.fb.group({});
if (type) {
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/common-maps-utils.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/common-maps-utils.ts
index 6b2bb5fafe..d9b0da46d8 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/common-maps-utils.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/common-maps-utils.ts
@@ -330,7 +330,7 @@ export function parseData(input: DatasourceData[]): FormattedData[] {
deviceType: null
};
entityArray.filter(el => el.data.length).forEach(el => {
- const indexDate = el.data.length ? el.data.length - 1 : 0;
+ const indexDate = el.data.length - 1;
if (!obj.hasOwnProperty(el.dataKey.label) || el.data[indexDate][1] !== '') {
obj[el.dataKey.label] = el.data[indexDate][1];
obj[el.dataKey.label + '|ts'] = el.data[indexDate][0];
diff --git a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts
index 456a1e3dee..420e481bd4 100644
--- a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts
@@ -55,9 +55,17 @@ import { AppState } from '@core/core.state';
import { WidgetService } from '@core/http/widget.service';
import { UtilsService } from '@core/services/utils.service';
import { forkJoin, Observable, of, ReplaySubject, Subscription, throwError } from 'rxjs';
-import { deepClone, insertVariable, isDefined, objToBase64, objToBase64URI, validateEntityId } from '@core/utils';
import {
- IDynamicWidgetComponent,
+ deepClone,
+ insertVariable,
+ isDefined,
+ isNotEmptyStr,
+ objToBase64,
+ objToBase64URI,
+ validateEntityId
+} from '@core/utils';
+import {
+ IDynamicWidgetComponent, ShowWidgetHeaderActionFunction,
WidgetContext,
WidgetHeaderAction,
WidgetInfo,
@@ -290,12 +298,25 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
this.widgetContext.customHeaderActions = [];
const headerActionsDescriptors = this.getActionDescriptors(widgetActionSources.headerButton.value);
- headerActionsDescriptors.forEach((descriptor) => {
+ headerActionsDescriptors.forEach((descriptor) =>
+ {
+ let useShowWidgetHeaderActionFunction = descriptor.useShowWidgetActionFunction || false;
+ let showWidgetHeaderActionFunction: ShowWidgetHeaderActionFunction = null;
+ if (useShowWidgetHeaderActionFunction && isNotEmptyStr(descriptor.showWidgetActionFunction)) {
+ try {
+ showWidgetHeaderActionFunction =
+ new Function('widgetContext', 'data', descriptor.showWidgetActionFunction) as ShowWidgetHeaderActionFunction;
+ } catch (e) {
+ useShowWidgetHeaderActionFunction = false;
+ }
+ }
const headerAction: WidgetHeaderAction = {
name: descriptor.name,
displayName: descriptor.displayName,
icon: descriptor.icon,
descriptor,
+ useShowWidgetHeaderActionFunction,
+ showWidgetHeaderActionFunction,
onAction: $event => {
const entityInfo = this.getActiveEntityInfo();
const entityId = entityInfo ? entityInfo.entityId : null;
@@ -507,6 +528,9 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
this.widgetInstanceInited = true;
if (this.dataUpdatePending) {
this.widgetTypeInstance.onDataUpdated();
+ setTimeout(() => {
+ this.dashboardWidget.updateCustomHeaderActions(true);
+ }, 0);
this.dataUpdatePending = false;
}
if (this.pendingMessage) {
@@ -844,6 +868,9 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
if (this.displayWidgetInstance()) {
if (this.widgetInstanceInited) {
this.widgetTypeInstance.onDataUpdated();
+ setTimeout(() => {
+ this.dashboardWidget.updateCustomHeaderActions(true);
+ }, 0);
} else {
this.dataUpdatePending = true;
}
diff --git a/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts b/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts
index 7c80219da3..fe32c2df1b 100644
--- a/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts
+++ b/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts
@@ -25,6 +25,8 @@ import { IterableDiffer, KeyValueDiffer } from '@angular/core';
import { IAliasController, IStateController } from '@app/core/api/widget-api.models';
import { enumerable } from '@shared/decorators/enumerable';
import { UtilsService } from '@core/services/utils.service';
+import { FormattedData } from '@home/components/widget/lib/maps/map-models';
+import { parseData } from '@home/components/widget/lib/maps/common-maps-utils';
export interface WidgetsData {
widgets: Array;
@@ -438,13 +440,45 @@ export class DashboardWidget implements GridsterItem, IDashboardWidget {
this.showWidgetActions = !this.widgetContext.hideTitlePanel;
- this.customHeaderActions = this.widgetContext.customHeaderActions ? this.widgetContext.customHeaderActions : [];
+ this.updateCustomHeaderActions();
this.widgetActions = this.widgetContext.widgetActions ? this.widgetContext.widgetActions : [];
if (detectChanges) {
this.widgetContext.detectContainerChanges();
}
}
+ updateCustomHeaderActions(detectChanges = false) {
+ let customHeaderActions: Array;
+ if (this.widgetContext.customHeaderActions) {
+ let data: FormattedData[] = [];
+ if (this.widgetContext.customHeaderActions.some(action => action.useShowWidgetHeaderActionFunction)) {
+ data = parseData(this.widgetContext.data || []);
+ }
+ customHeaderActions = this.widgetContext.customHeaderActions.filter(action => this.filterCustomHeaderAction(action, data));
+ } else {
+ customHeaderActions = [];
+ }
+ if (!isEqual(this.customHeaderActions, customHeaderActions)) {
+ this.customHeaderActions = customHeaderActions;
+ if (detectChanges) {
+ this.widgetContext.detectContainerChanges();
+ }
+ }
+ }
+
+ private filterCustomHeaderAction(action: WidgetHeaderAction, data: FormattedData[]): boolean {
+ if (action.useShowWidgetHeaderActionFunction) {
+ try {
+ return action.showWidgetHeaderActionFunction(this.widgetContext, data);
+ } catch (e) {
+ console.warn('Failed to execute showWidgetHeaderActionFunction', e);
+ return false;
+ }
+ } else {
+ return true;
+ }
+ }
+
@enumerable(true)
get x(): number {
let res;
diff --git a/ui-ngx/src/app/modules/home/models/widget-component.models.ts b/ui-ngx/src/app/modules/home/models/widget-component.models.ts
index 46fce30684..c0dc2aedf6 100644
--- a/ui-ngx/src/app/modules/home/models/widget-component.models.ts
+++ b/ui-ngx/src/app/modules/home/models/widget-component.models.ts
@@ -79,6 +79,7 @@ import { SortOrder } from '@shared/models/page/sort-order';
import { DomSanitizer } from '@angular/platform-browser';
import { Router } from '@angular/router';
import { catchError, map, mergeMap, switchMap } from 'rxjs/operators';
+import { FormattedData } from '@home/components/widget/lib/maps/map-models';
export interface IWidgetAction {
name: string;
@@ -86,9 +87,13 @@ export interface IWidgetAction {
onAction: ($event: Event) => void;
}
+export type ShowWidgetHeaderActionFunction = (ctx: WidgetContext, data: FormattedData[]) => boolean;
+
export interface WidgetHeaderAction extends IWidgetAction {
displayName: string;
descriptor: WidgetActionDescriptor;
+ useShowWidgetHeaderActionFunction: boolean;
+ showWidgetHeaderActionFunction: ShowWidgetHeaderActionFunction;
}
export interface WidgetAction extends IWidgetAction {
diff --git a/ui-ngx/src/app/shared/models/ace/widget-completion.models.ts b/ui-ngx/src/app/shared/models/ace/widget-completion.models.ts
index fcbdd2d1ca..6622c26fb1 100644
--- a/ui-ngx/src/app/shared/models/ace/widget-completion.models.ts
+++ b/ui-ngx/src/app/shared/models/ace/widget-completion.models.ts
@@ -390,6 +390,63 @@ export const widgetContextCompletions: TbEditorCompletions = {
meta: 'property',
type: 'number'
},
+ currentUser: {
+ description: 'Current user object.',
+ meta: 'property',
+ type: 'AuthUser',
+ children: {
+ sub: {
+ description: 'User subject (email).',
+ meta: 'property',
+ type: 'string'
+ },
+ scopes: {
+ description: 'User security scopes.',
+ meta: 'property',
+ type: 'Array'
+ },
+ userId: {
+ description: 'User id.',
+ meta: 'property',
+ type: 'string'
+ },
+ firstName: {
+ description: 'User first name.',
+ meta: 'property',
+ type: 'string'
+ },
+ lastName: {
+ description: 'User last name.',
+ meta: 'property',
+ type: 'string'
+ },
+ enabled: {
+ description: 'Whether is user enabled.',
+ meta: 'property',
+ type: 'boolean'
+ },
+ tenantId: {
+ description: 'Tenant id of the user.',
+ meta: 'property',
+ type: 'string'
+ },
+ customerId: {
+ description: 'Customer id of the user (available when user belongs to specific customer).',
+ meta: 'property',
+ type: 'string'
+ },
+ isPublic: {
+ description: 'Special flag indicating public user.',
+ meta: 'property',
+ type: 'boolean'
+ },
+ authority: {
+ description: 'User authority. Possible values: SYS_ADMIN, TENANT_ADMIN, CUSTOMER_USER',
+ meta: 'property',
+ type: 'Authority'
+ }
+ }
+ },
hideTitlePanel: {
description: 'Manages visibility of widget title panel. Useful for widget with custom title panels or different states. updateWidgetParams() function must be called after this property change.',
meta: 'property',
diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts
index 781db1d8dd..9f58f5e264 100644
--- a/ui-ngx/src/app/shared/models/widget.models.ts
+++ b/ui-ngx/src/app/shared/models/widget.models.ts
@@ -462,6 +462,8 @@ export interface WidgetActionDescriptor extends CustomActionDescriptor {
setEntityId?: boolean;
stateEntityParamName?: string;
mobileAction?: WidgetMobileActionDescriptor;
+ useShowWidgetActionFunction?: boolean;
+ showWidgetActionFunction?: string;
}
export interface WidgetComparisonSettings {
diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json
index 9b887f156c..d37b9ce8e9 100644
--- a/ui-ngx/src/assets/locale/locale.constant-en_US.json
+++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json
@@ -3038,6 +3038,7 @@
"action-name-required": "Action name is required.",
"action-name-not-unique": "Another action with the same name already exists.
Action name should be unique within the same action source.",
"action-icon": "Icon",
+ "use-show-widget-action-function": "Use show widget action function",
"action-type": "Type",
"action-type-required": "Action type is required.",
"edit-action": "Edit action",