Browse Source

Merge pull request #6555 from thingsboard/feature/widget-data-page-size

[3.4] Add 'Maximum entities per datasource' parameter to widget configuration
pull/6537/head
Igor Kulikov 4 years ago
committed by GitHub
parent
commit
2d2824fa4a
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      ui-ngx/src/app/core/api/alias-controller.ts
  2. 4
      ui-ngx/src/app/core/api/widget-api.models.ts
  3. 8
      ui-ngx/src/app/core/api/widget-subscription.ts
  4. 6
      ui-ngx/src/app/core/http/entity.service.ts
  5. 8
      ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts
  6. 1
      ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts
  7. 6
      ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.ts
  8. 8
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.html
  9. 2
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts
  10. 3
      ui-ngx/src/app/modules/home/components/widget/widget.component.ts
  11. 1
      ui-ngx/src/app/shared/models/widget.models.ts
  12. 1
      ui-ngx/src/assets/locale/locale.constant-en_US.json

10
ui-ngx/src/app/core/api/alias-controller.ts

@ -17,14 +17,15 @@
import { AliasInfo, IAliasController, StateControllerHolder, StateEntityInfo } from '@core/api/widget-api.models'; import { AliasInfo, IAliasController, StateControllerHolder, StateEntityInfo } from '@core/api/widget-api.models';
import { forkJoin, Observable, of, ReplaySubject, Subject } from 'rxjs'; import { forkJoin, Observable, of, ReplaySubject, Subject } from 'rxjs';
import { Datasource, DatasourceType, datasourceTypeTranslationMap } from '@app/shared/models/widget.models'; import { Datasource, DatasourceType, datasourceTypeTranslationMap } from '@app/shared/models/widget.models';
import { deepClone, isEqual } from '@core/utils'; import { deepClone, isDefinedAndNotNull, isEqual } from '@core/utils';
import { EntityService } from '@core/http/entity.service'; import { EntityService } from '@core/http/entity.service';
import { UtilsService } from '@core/services/utils.service'; import { UtilsService } from '@core/services/utils.service';
import { AliasFilterType, EntityAliases, SingleEntityFilter } from '@shared/models/alias.models'; import { AliasFilterType, EntityAliases, SingleEntityFilter } from '@shared/models/alias.models';
import { EntityInfo } from '@shared/models/entity.models'; import { EntityInfo } from '@shared/models/entity.models';
import { map, mergeMap } from 'rxjs/operators'; import { map, mergeMap } from 'rxjs/operators';
import { import {
defaultEntityDataPageLink, Filter, FilterInfo, filterInfoToKeyFilters, Filters, KeyFilter, singleEntityDataPageLink, createDefaultEntityDataPageLink,
Filter, FilterInfo, filterInfoToKeyFilters, Filters, KeyFilter, singleEntityDataPageLink,
updateDatasourceFromEntityInfo updateDatasourceFromEntityInfo
} from '@shared/models/query/query.models'; } from '@shared/models/query/query.models';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
@ -322,7 +323,7 @@ export class AliasController implements IAliasController {
); );
} }
resolveDatasources(datasources: Array<Datasource>, singleEntity?: boolean): Observable<Array<Datasource>> { resolveDatasources(datasources: Array<Datasource>, singleEntity?: boolean, pageSize = 1024): Observable<Array<Datasource>> {
if (!datasources || !datasources.length) { if (!datasources || !datasources.length) {
return of([]); return of([]);
} }
@ -360,7 +361,8 @@ export class AliasController implements IAliasController {
if (singleEntity) { if (singleEntity) {
datasource.pageLink = deepClone(singleEntityDataPageLink); datasource.pageLink = deepClone(singleEntityDataPageLink);
} else if (!datasource.pageLink) { } else if (!datasource.pageLink) {
datasource.pageLink = deepClone(defaultEntityDataPageLink); pageSize = isDefinedAndNotNull(pageSize) && pageSize > 0 ? pageSize : 1024;
datasource.pageLink = createDefaultEntityDataPageLink(pageSize);
} }
} }
}); });

4
ui-ngx/src/app/core/api/widget-api.models.ts

@ -119,7 +119,7 @@ export interface IAliasController {
getEntityAliasId(aliasName: string): string; getEntityAliasId(aliasName: string): string;
getInstantAliasInfo(aliasId: string): AliasInfo; getInstantAliasInfo(aliasId: string): AliasInfo;
resolveSingleEntityInfo(aliasId: string): Observable<EntityInfo>; resolveSingleEntityInfo(aliasId: string): Observable<EntityInfo>;
resolveDatasources(datasources: Array<Datasource>, singleEntity?: boolean): Observable<Array<Datasource>>; resolveDatasources(datasources: Array<Datasource>, singleEntity?: boolean, pageSize?: number): Observable<Array<Datasource>>;
resolveAlarmSource(alarmSource: Datasource): Observable<Datasource>; resolveAlarmSource(alarmSource: Datasource): Observable<Datasource>;
getEntityAliases(): EntityAliases; getEntityAliases(): EntityAliases;
getFilters(): Filters; getFilters(): Filters;
@ -184,6 +184,7 @@ export interface SubscriptionInfo {
deviceName?: string; deviceName?: string;
deviceNamePrefix?: string; deviceNamePrefix?: string;
deviceIds?: Array<string>; deviceIds?: Array<string>;
pageSize?: number;
} }
export class WidgetSubscriptionContext { export class WidgetSubscriptionContext {
@ -242,6 +243,7 @@ export interface WidgetSubscriptionOptions {
datasourcesOptional?: boolean; datasourcesOptional?: boolean;
hasDataPageLink?: boolean; hasDataPageLink?: boolean;
singleEntity?: boolean; singleEntity?: boolean;
pageSize?: number;
warnOnPageDataOverflow?: boolean; warnOnPageDataOverflow?: boolean;
ignoreDataUpdateOnIntervalTick?: boolean; ignoreDataUpdateOnIntervalTick?: boolean;
targetDeviceAliasIds?: Array<string>; targetDeviceAliasIds?: Array<string>;

8
ui-ngx/src/app/core/api/widget-subscription.ts

@ -97,6 +97,7 @@ export class WidgetSubscription implements IWidgetSubscription {
hasDataPageLink: boolean; hasDataPageLink: boolean;
singleEntity: boolean; singleEntity: boolean;
pageSize: number;
warnOnPageDataOverflow: boolean; warnOnPageDataOverflow: boolean;
ignoreDataUpdateOnIntervalTick: boolean; ignoreDataUpdateOnIntervalTick: boolean;
@ -229,6 +230,7 @@ export class WidgetSubscription implements IWidgetSubscription {
this.entityDataListeners = []; this.entityDataListeners = [];
this.hasDataPageLink = options.hasDataPageLink; this.hasDataPageLink = options.hasDataPageLink;
this.singleEntity = options.singleEntity; this.singleEntity = options.singleEntity;
this.pageSize = options.pageSize;
this.warnOnPageDataOverflow = options.warnOnPageDataOverflow; this.warnOnPageDataOverflow = options.warnOnPageDataOverflow;
this.ignoreDataUpdateOnIntervalTick = options.ignoreDataUpdateOnIntervalTick; this.ignoreDataUpdateOnIntervalTick = options.ignoreDataUpdateOnIntervalTick;
this.datasourcePages = []; this.datasourcePages = [];
@ -387,7 +389,7 @@ export class WidgetSubscription implements IWidgetSubscription {
} }
); );
} else { } else {
this.ctx.aliasController.resolveDatasources(this.configuredDatasources, this.singleEntity).subscribe( this.ctx.aliasController.resolveDatasources(this.configuredDatasources, this.singleEntity, this.pageSize).subscribe(
(datasources) => { (datasources) => {
this.configuredDatasources = datasources; this.configuredDatasources = datasources;
this.prepareDataSubscriptions().subscribe( this.prepareDataSubscriptions().subscribe(
@ -1132,7 +1134,7 @@ export class WidgetSubscription implements IWidgetSubscription {
} }
); );
} else { } else {
this.ctx.aliasController.resolveDatasources(this.configuredDatasources, this.singleEntity).subscribe( this.ctx.aliasController.resolveDatasources(this.configuredDatasources, this.singleEntity, this.pageSize).subscribe(
(datasources) => { (datasources) => {
this.configuredDatasources = datasources; this.configuredDatasources = datasources;
this.prepareDataSubscriptions().subscribe( this.prepareDataSubscriptions().subscribe(
@ -1271,7 +1273,7 @@ export class WidgetSubscription implements IWidgetSubscription {
totalPages: pageData.totalPages totalPages: pageData.totalPages
}; };
if (datasource.type === DatasourceType.entity && if (datasource.type === DatasourceType.entity &&
pageData.hasNext && pageLink.pageSize > 1) { pageData.hasNext && !this.singleEntity) {
if (this.warnOnPageDataOverflow) { if (this.warnOnPageDataOverflow) {
const message = this.ctx.translate.instant('widget.data-overflow', const message = this.ctx.translate.instant('widget.data-overflow',
{count: pageData.data.length, total: pageData.totalElements}); {count: pageData.data.length, total: pageData.totalElements});

6
ui-ngx/src/app/core/http/entity.service.ts

@ -1319,7 +1319,8 @@ export class EntityService {
pageLink = deepClone(singleEntityDataPageLink); pageLink = deepClone(singleEntityDataPageLink);
} else { } else {
nameFilter = subscriptionInfo.entityNamePrefix; nameFilter = subscriptionInfo.entityNamePrefix;
pageLink = deepClone(defaultEntityDataPageLink); const pageSize = isDefinedAndNotNull(subscriptionInfo.pageSize) && subscriptionInfo.pageSize > 0 ? subscriptionInfo.pageSize : 1024;
pageLink = createDefaultEntityDataPageLink(pageSize);
} }
datasource.entityFilter = { datasource.entityFilter = {
type: AliasFilterType.entityName, type: AliasFilterType.entityName,
@ -1333,7 +1334,8 @@ export class EntityService {
entityType: subscriptionInfo.entityType, entityType: subscriptionInfo.entityType,
entityList: subscriptionInfo.entityIds entityList: subscriptionInfo.entityIds
}; };
datasource.pageLink = deepClone(defaultEntityDataPageLink); const pageSize = isDefinedAndNotNull(subscriptionInfo.pageSize) && subscriptionInfo.pageSize > 0 ? subscriptionInfo.pageSize : 1024;
datasource.pageLink = createDefaultEntityDataPageLink(pageSize);
} }
} }

8
ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts

@ -45,7 +45,7 @@ import { TranslateService } from '@ngx-translate/core';
import { UtilsService } from '@core/services/utils.service'; import { UtilsService } from '@core/services/utils.service';
import { EntityDataPageLink } from '@shared/models/query/query.models'; import { EntityDataPageLink } from '@shared/models/query/query.models';
import { providerClass } from '@home/components/widget/lib/maps/providers'; import { providerClass } from '@home/components/widget/lib/maps/providers';
import { isDefined, parseFunction } from '@core/utils'; import { isDefined, isDefinedAndNotNull, parseFunction } from '@core/utils';
import L from 'leaflet'; import L from 'leaflet';
import { forkJoin, Observable, of } from 'rxjs'; import { forkJoin, Observable, of } from 'rxjs';
import { AttributeService } from '@core/http/attribute.service'; import { AttributeService } from '@core/http/attribute.service';
@ -87,9 +87,13 @@ export class MapWidgetController implements MapWidgetInterface {
this.map.saveMarkerLocation = this.setMarkerLocation.bind(this); this.map.saveMarkerLocation = this.setMarkerLocation.bind(this);
this.map.savePolygonLocation = this.savePolygonLocation.bind(this); this.map.savePolygonLocation = this.savePolygonLocation.bind(this);
this.map.saveLocation = this.saveLocation.bind(this); this.map.saveLocation = this.saveLocation.bind(this);
let pageSize = this.settings.mapPageSize;
if (isDefinedAndNotNull(this.ctx.widgetConfig.pageSize)) {
pageSize = Math.max(pageSize, this.ctx.widgetConfig.pageSize);
}
this.pageLink = { this.pageLink = {
page: 0, page: 0,
pageSize: this.settings.mapPageSize, pageSize,
textSearch: null, textSearch: null,
dynamic: true dynamic: true
}; };

1
ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts

@ -88,6 +88,7 @@ export class ImageMap extends LeafletMap {
const imageUrlSubscriptionOptions: WidgetSubscriptionOptions = { const imageUrlSubscriptionOptions: WidgetSubscriptionOptions = {
datasources, datasources,
hasDataPageLink: true, hasDataPageLink: true,
singleEntity: true,
useDashboardTimewindow: false, useDashboardTimewindow: false,
type: widgetType.latest, type: widgetType.latest,
callbacks: { callbacks: {

6
ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.ts

@ -26,7 +26,7 @@ import {
fillDataPattern, fillDataPattern,
flatFormattedData, flatFormattedData,
formattedDataFormDatasourceData, formattedDataFormDatasourceData,
hashCode, hashCode, isDefinedAndNotNull,
isNotEmptyStr, isNotEmptyStr,
parseFunction, processDataPattern, parseFunction, processDataPattern,
safeExecute safeExecute
@ -83,9 +83,11 @@ export class MarkdownWidgetComponent extends PageComponent implements OnInit {
cssParser.cssPreviewNamespace = this.markdownClass; cssParser.cssPreviewNamespace = this.markdownClass;
cssParser.createStyleElement(this.markdownClass, cssString); cssParser.createStyleElement(this.markdownClass, cssString);
} }
const pageSize = isDefinedAndNotNull(this.ctx.widgetConfig.pageSize) &&
this.ctx.widgetConfig.pageSize > 0 ? this.ctx.widgetConfig.pageSize : 16384;
const pageLink: EntityDataPageLink = { const pageLink: EntityDataPageLink = {
page: 0, page: 0,
pageSize: 16384, pageSize,
textSearch: null, textSearch: null,
dynamic: true dynamic: true
}; };

8
ui-ngx/src/app/modules/home/components/widget/widget-config.component.html

@ -317,6 +317,14 @@
<mat-panel-title translate>widget-config.data-settings</mat-panel-title> <mat-panel-title translate>widget-config.data-settings</mat-panel-title>
</mat-expansion-panel-header> </mat-expansion-panel-header>
<ng-template matExpansionPanelContent> <ng-template matExpansionPanelContent>
<div fxLayout="row" *ngIf="widgetType !== widgetTypes.rpc &&
widgetType !== widgetTypes.alarm &&
modelValue?.isDataEnabled && !modelValue?.typeParameters?.singleEntity">
<mat-form-field fxFlex>
<mat-label translate>widget-config.data-page-size</mat-label>
<input matInput formControlName="pageSize" type="number" min="1" step="1">
</mat-form-field>
</div>
<div fxLayout.xs="column" fxLayout="row" fxLayoutGap="8px"> <div fxLayout.xs="column" fxLayout="row" fxLayoutGap="8px">
<mat-form-field fxFlex> <mat-form-field fxFlex>
<mat-label translate>widget-config.units</mat-label> <mat-label translate>widget-config.units</mat-label>

2
ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts

@ -213,6 +213,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
widgetStyle: [null, []], widgetStyle: [null, []],
widgetCss: [null, []], widgetCss: [null, []],
titleStyle: [null, []], titleStyle: [null, []],
pageSize: [1024, [Validators.min(1), Validators.pattern(/^\d*$/)]],
units: [null, []], units: [null, []],
decimals: [null, [Validators.min(0), Validators.max(15), Validators.pattern(/^\d*$/)]], decimals: [null, [Validators.min(0), Validators.max(15), Validators.pattern(/^\d*$/)]],
noDataDisplayMessage: [null, []], noDataDisplayMessage: [null, []],
@ -420,6 +421,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
fontSize: '16px', fontSize: '16px',
fontWeight: 400 fontWeight: 400
}, },
pageSize: isDefined(config.pageSize) ? config.pageSize : 1024,
units: config.units, units: config.units,
decimals: config.decimals, decimals: config.decimals,
noDataDisplayMessage: isDefined(config.noDataDisplayMessage) ? config.noDataDisplayMessage : '', noDataDisplayMessage: isDefined(config.noDataDisplayMessage) ? config.noDataDisplayMessage : '',

3
ui-ngx/src/app/modules/home/components/widget/widget.component.ts

@ -977,7 +977,8 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
ignoreDataUpdateOnIntervalTick: this.typeParameters.ignoreDataUpdateOnIntervalTick, ignoreDataUpdateOnIntervalTick: this.typeParameters.ignoreDataUpdateOnIntervalTick,
comparisonEnabled: comparisonSettings.comparisonEnabled, comparisonEnabled: comparisonSettings.comparisonEnabled,
timeForComparison: comparisonSettings.timeForComparison, timeForComparison: comparisonSettings.timeForComparison,
comparisonCustomIntervalValue: comparisonSettings.comparisonCustomIntervalValue comparisonCustomIntervalValue: comparisonSettings.comparisonCustomIntervalValue,
pageSize: this.widget.config.pageSize
}; };
if (this.widget.type === widgetType.alarm) { if (this.widget.type === widgetType.alarm) {
options.alarmSource = deepClone(this.widget.config.alarmSource); options.alarmSource = deepClone(this.widget.config.alarmSource);

1
ui-ngx/src/app/shared/models/widget.models.ts

@ -554,6 +554,7 @@ export interface WidgetConfig {
units?: string; units?: string;
decimals?: number; decimals?: number;
noDataDisplayMessage?: string; noDataDisplayMessage?: string;
pageSize?: number;
actions?: {[actionSourceId: string]: Array<WidgetActionDescriptor>}; actions?: {[actionSourceId: string]: Array<WidgetActionDescriptor>};
settings?: WidgetSettings; settings?: WidgetSettings;
alarmSource?: Datasource; alarmSource?: Datasource;

1
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -3246,6 +3246,7 @@
"advanced-settings": "Advanced settings", "advanced-settings": "Advanced settings",
"data-settings": "Data settings", "data-settings": "Data settings",
"no-data-display-message": "\"No data to display\" alternative message", "no-data-display-message": "\"No data to display\" alternative message",
"data-page-size": "Maximum entities per datasource",
"settings-component-not-found": "Settings form component not found for selector '{{selector}}'" "settings-component-not-found": "Settings form component not found for selector '{{selector}}'"
}, },
"widget-type": { "widget-type": {

Loading…
Cancel
Save