Commit 11db772ea2d753f178faa14e6eee0e2ac830b424

Authored by ArtemHalushko
Committed by GitHub
1 parent c6cf5c43

bugfixes (#2705)

... ... @@ -17,7 +17,7 @@
17 17 import { AliasInfo, IAliasController, StateControllerHolder, StateEntityInfo } from '@core/api/widget-api.models';
18 18 import { forkJoin, Observable, of, ReplaySubject, Subject } from 'rxjs';
19 19 import { DataKey, Datasource, DatasourceType } from '@app/shared/models/widget.models';
20   -import { deepClone, isEqual } from '@core/utils';
  20 +import { deepClone, isEqual, createLabelFromDatasource } from '@core/utils';
21 21 import { EntityService } from '@core/http/entity.service';
22 22 import { UtilsService } from '@core/services/utils.service';
23 23 import { EntityAliases } from '@shared/models/alias.models';
... ... @@ -329,7 +329,7 @@ export class AliasController implements IAliasController {
329 329 if (!dataKey.pattern) {
330 330 dataKey.pattern = deepClone(dataKey.label);
331 331 }
332   - dataKey.label = this.utils.createLabelFromDatasource(datasource, dataKey.pattern);
  332 + dataKey.label = createLabelFromDatasource(datasource, dataKey.pattern);
333 333 }
334 334
335 335 getInstantAliasInfo(aliasId: string): AliasInfo {
... ...
... ... @@ -20,7 +20,7 @@
20 20 import { Inject, Injectable, NgZone } from '@angular/core';
21 21 import { WINDOW } from '@core/services/window.service';
22 22 import { ExceptionData } from '@app/shared/models/error.models';
23   -import { deepClone, deleteNullProperties, guid, isDefined, isDefinedAndNotNull, isUndefined } from '@core/utils';
  23 +import { deepClone, deleteNullProperties, guid, isDefined, isDefinedAndNotNull, isUndefined, createLabelFromDatasource } from '@core/utils';
24 24 import { WindowMessage } from '@shared/models/window-message.model';
25 25 import { TranslateService } from '@ngx-translate/core';
26 26 import { customTranslationsPrefix } from '@app/shared/models/constants';
... ... @@ -36,7 +36,7 @@ import { Observable, of, ReplaySubject } from 'rxjs';
36 36
37 37 const varsRegex = /\$\{([^}]*)\}/g;
38 38
39   -const predefinedFunctions: {[func: string]: string} = {
  39 +const predefinedFunctions: { [func: string]: string } = {
40 40 Sin: 'return Math.round(1000*Math.sin(time/5000));',
41 41 Cos: 'return Math.round(1000*Math.cos(time/5000));',
42 42 Random: 'var value = prevValue + Math.random() * 100 - 50;\n' +
... ... @@ -63,12 +63,12 @@ const defaultAlarmFields: Array<string> = [
63 63 alarmFields.status.keyName
64 64 ];
65 65
66   -const commonMaterialIcons: Array<string> = [ 'more_horiz', 'more_vert', 'open_in_new',
  66 +const commonMaterialIcons: Array<string> = ['more_horiz', 'more_vert', 'open_in_new',
67 67 'visibility', 'play_arrow', 'arrow_back', 'arrow_downward',
68 68 'arrow_forward', 'arrow_upwards', 'close', 'refresh', 'menu', 'show_chart', 'multiline_chart', 'pie_chart', 'insert_chart', 'people',
69 69 'person', 'domain', 'devices_other', 'now_widgets', 'dashboards', 'map', 'pin_drop', 'my_location', 'extension', 'search',
70 70 'settings', 'notifications', 'notifications_active', 'info', 'info_outline', 'warning', 'list', 'file_download', 'import_export',
71   - 'share', 'add', 'edit', 'done' ];
  71 + 'share', 'add', 'edit', 'done'];
72 72
73 73 // @dynamic
74 74 @Injectable({
... ... @@ -101,8 +101,8 @@ export class UtilsService {
101 101 materialIcons: Array<string> = [];
102 102
103 103 constructor(@Inject(WINDOW) private window: Window,
104   - private zone: NgZone,
105   - private translate: TranslateService) {
  104 + private zone: NgZone,
  105 + private translate: TranslateService) {
106 106 let frame: Element = null;
107 107 try {
108 108 frame = window.frameElement;
... ... @@ -302,10 +302,10 @@ export class UtilsService {
302 302 .split('\n')
303 303 .filter((codepoint) => codepoint && codepoint.length);
304 304 codepointsArray.forEach((codepoint) => {
305   - const values = codepoint.split(' ');
306   - if (values && values.length === 2) {
307   - this.materialIcons.push(values[0]);
308   - }
  305 + const values = codepoint.split(' ');
  306 + if (values && values.length === 2) {
  307 + this.materialIcons.push(values[0]);
  308 + }
309 309 });
310 310 materialIconsSubject.next(this.materialIcons);
311 311 });
... ... @@ -360,12 +360,12 @@ export class UtilsService {
360 360 }
361 361
362 362 public createAdditionalDataKey(dataKey: DataKey, datasource: Datasource, timeUnit: string,
363   - datasources: Datasource[], additionalKeysNumber: number): DataKey {
  363 + datasources: Datasource[], additionalKeysNumber: number): DataKey {
364 364 const additionalDataKey = deepClone(dataKey);
365 365 if (dataKey.settings.comparisonSettings.comparisonValuesLabel) {
366   - additionalDataKey.label = this.createLabelFromDatasource(datasource, dataKey.settings.comparisonSettings.comparisonValuesLabel);
  366 + additionalDataKey.label = createLabelFromDatasource(datasource, dataKey.settings.comparisonSettings.comparisonValuesLabel);
367 367 } else {
368   - additionalDataKey.label = dataKey.label + ' ' + this.translate.instant('legend.comparison-time-ago.'+timeUnit);
  368 + additionalDataKey.label = dataKey.label + ' ' + this.translate.instant('legend.comparison-time-ago.' + timeUnit);
369 369 }
370 370 additionalDataKey.pattern = additionalDataKey.label;
371 371 if (dataKey.settings.comparisonSettings.color) {
... ... @@ -380,30 +380,7 @@ export class UtilsService {
380 380 }
381 381
382 382 public createLabelFromDatasource(datasource: Datasource, pattern: string) {
383   - let label = pattern;
384   - if (!datasource) {
385   - return label;
386   - }
387   - let match = varsRegex.exec(pattern);
388   - while (match !== null) {
389   - const variable = match[0];
390   - const variableName = match[1];
391   - if (variableName === 'dsName') {
392   - label = label.split(variable).join(datasource.name);
393   - } else if (variableName === 'entityName') {
394   - label = label.split(variable).join(datasource.entityName);
395   - } else if (variableName === 'deviceName') {
396   - label = label.split(variable).join(datasource.entityName);
397   - } else if (variableName === 'entityLabel') {
398   - label = label.split(variable).join(datasource.entityLabel || datasource.entityName);
399   - } else if (variableName === 'aliasName') {
400   - label = label.split(variable).join(datasource.aliasName);
401   - } else if (variableName === 'entityDescription') {
402   - label = label.split(variable).join(datasource.entityDescription);
403   - }
404   - match = varsRegex.exec(pattern);
405   - }
406   - return label;
  383 + return createLabelFromDatasource(datasource, pattern);
407 384 }
408 385
409 386 public generateColors(datasources: Array<Datasource>) {
... ... @@ -456,7 +433,7 @@ export class UtilsService {
456 433 params = urlQueryString + '&' + newParam;
457 434 }
458 435 } else if (newParam) {
459   - params = '?' + newParam;
  436 + params = '?' + newParam;
460 437 }
461 438 this.window.history.replaceState({}, '', baseUrl + params);
462 439 }
... ...
... ... @@ -18,6 +18,7 @@ import _ from 'lodash';
18 18 import { Observable, Subject, fromEvent, of } from 'rxjs';
19 19 import { finalize, share, map } from 'rxjs/operators';
20 20 import base64js from 'base64-js';
  21 +import { Datasource } from '@app/shared/models/widget.models';
21 22
22 23 export function onParentScrollOrWindowResize(el: Node): Observable<Event> {
23 24 const scrollSubject = new Subject<Event>();
... ... @@ -435,6 +436,34 @@ export function imageLoader(imageUrl: string): Observable<HTMLImageElement> {
435 436 return imageLoad$;
436 437 }
437 438
  439 +export function createLabelFromDatasource(datasource: Datasource, pattern: string) {
  440 + const varsRegex = /\$\{([^}]*)\}/g;
  441 + let label = pattern;
  442 + if (!datasource) {
  443 + return label;
  444 + }
  445 + let match = varsRegex.exec(pattern);
  446 + while (match !== null) {
  447 + const variable = match[0];
  448 + const variableName = match[1];
  449 + if (variableName === 'dsName') {
  450 + label = label.split(variable).join(datasource.name);
  451 + } else if (variableName === 'entityName') {
  452 + label = label.split(variable).join(datasource.entityName);
  453 + } else if (variableName === 'deviceName') {
  454 + label = label.split(variable).join(datasource.entityName);
  455 + } else if (variableName === 'entityLabel') {
  456 + label = label.split(variable).join(datasource.entityLabel || datasource.entityName);
  457 + } else if (variableName === 'aliasName') {
  458 + label = label.split(variable).join(datasource.aliasName);
  459 + } else if (variableName === 'entityDescription') {
  460 + label = label.split(variable).join(datasource.entityDescription);
  461 + }
  462 + match = varsRegex.exec(pattern);
  463 + }
  464 + return label;
  465 +}
  466 +
438 467 const imageAspectMap = {};
439 468
440 469 export function aspectCache(imageUrl: string): Observable<number> {
... ... @@ -452,7 +481,6 @@ export function aspectCache(imageUrl: string): Observable<number> {
452 481 }
453 482 }
454 483
455   -
456 484 export function parseArray(input: any[]): any[] {
457 485 return _(input).groupBy(el => el?.datasource?.entityName)
458 486 .values().value().map((entityArray, dsIndex) =>
... ... @@ -523,15 +551,18 @@ export function parseFunction(source: any, params: string[] = ['def']): Function
523 551 return res;
524 552 }
525 553
526   -export function parseTemplate(template: string, data: object, translateFn?: (key: string) => string) {
  554 +export function parseTemplate(template: string, data: { $datasource?: Datasource, [key: string]: any },
  555 + translateFn?: (key: string) => string) {
527 556 let res = '';
528 557 try {
529 558 if (template.match(/<link-act/g)) {
530   - template = template.replace(/<link-act/g, '<a').replace(/link-act>/g, 'a>').replace(/name=(\'|")(.*?)(\'|")/g, `class='tb-custom-action' id='$2'`);
  559 + template = template.replace(/<link-act/g, '<a').replace(/link-act>/g, 'a>')
  560 + .replace(/name=(\'|")(.*?)(\'|")/g, `class='tb-custom-action' id='$2'`);
531 561 }
532 562 if (translateFn) {
533 563 template = translateFn(template);
534 564 }
  565 + template = createLabelFromDatasource(data.$datasource, template);
535 566 const formatted = template.match(/\$\{([^}]*)\:\d*\}/g);
536 567 if (formatted)
537 568 formatted.forEach(value => {
... ...
... ... @@ -33,7 +33,7 @@ import { Datasource, WidgetActionDescriptor, WidgetConfig } from '@shared/models
33 33 import { IWidgetSubscription } from '@core/api/widget-api.models';
34 34 import { UtilsService } from '@core/services/utils.service';
35 35 import { TranslateService } from '@ngx-translate/core';
36   -import { deepClone, isDefined, isNumber } from '@core/utils';
  36 +import { deepClone, isDefined, isNumber, createLabelFromDatasource } from '@core/utils';
37 37 import cssjs from '@core/css/css';
38 38 import { PageLink } from '@shared/models/page/page-link';
39 39 import { Direction, SortOrder, sortOrderFromString } from '@shared/models/page/sort-order';
... ... @@ -282,7 +282,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
282 282 alarmsTitle = this.translate.instant('alarm.alarms');
283 283 }
284 284
285   - this.ctx.widgetTitle = this.utils.createLabelFromDatasource(this.alarmSource, alarmsTitle);
  285 + this.ctx.widgetTitle = createLabelFromDatasource(this.alarmSource, alarmsTitle);
286 286
287 287 this.enableSelection = isDefined(this.settings.enableSelection) ? this.settings.enableSelection : true;
288 288 if (!this.allowAcknowledgment && !this.allowClear) {
... ...
... ... @@ -39,7 +39,7 @@ import {
39 39 import { IWidgetSubscription } from '@core/api/widget-api.models';
40 40 import { UtilsService } from '@core/services/utils.service';
41 41 import { TranslateService } from '@ngx-translate/core';
42   -import { deepClone, isDefined, isNumber } from '@core/utils';
  42 +import { deepClone, isDefined, isNumber, createLabelFromDatasource } from '@core/utils';
43 43 import cssjs from '@core/css/css';
44 44 import { PageLink } from '@shared/models/page/page-link';
45 45 import { Direction, SortOrder, sortOrderFromString } from '@shared/models/page/sort-order';
... ... @@ -210,7 +210,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
210 210 }
211 211
212 212 const datasource = this.subscription.datasources[0];
213   - this.ctx.widgetTitle = this.utils.createLabelFromDatasource(datasource, entitiesTitle);
  213 + this.ctx.widgetTitle = createLabelFromDatasource(datasource, entitiesTitle);
214 214
215 215 this.searchAction.show = isDefined(this.settings.enableSearch) ? this.settings.enableSearch : true;
216 216 this.displayPagination = isDefined(this.settings.displayPagination) ? this.settings.displayPagination : true;
... ...
... ... @@ -26,6 +26,7 @@ import { filter } from 'rxjs/operators';
26 26 import { Polyline } from './polyline';
27 27 import { Polygon } from './polygon';
28 28 import { DatasourceData } from '@app/shared/models/widget.models';
  29 +import { safeExecute } from '@app/core/utils';
29 30
30 31 export default abstract class LeafletMap {
31 32
... ... @@ -87,12 +88,14 @@ export default abstract class LeafletMap {
87 88 if (this.options.draggableMarker) {
88 89 let mousePositionOnMap: L.LatLng;
89 90 let addMarker: L.Control;
90   - this.map.on('mouseup', (e: L.LeafletMouseEvent) => {
  91 + this.map.on('mousemove', (e: L.LeafletMouseEvent) => {
91 92 mousePositionOnMap = e.latlng;
92 93 });
93 94 const dragListener = (e: L.DragEndEvent) => {
94 95 if (e.type === 'dragend' && mousePositionOnMap) {
95   - const newMarker = L.marker(mousePositionOnMap).addTo(this.map);
  96 + const icon = new L.Icon.Default();
  97 + icon.options.shadowSize = [0, 0];
  98 + const newMarker = L.marker(mousePositionOnMap, { icon }).addTo(this.map);
96 99 const datasourcesList = document.createElement('div');
97 100 const customLatLng = this.convertToCustomFormat(mousePositionOnMap);
98 101 this.datasources.forEach(ds => {
... ... @@ -195,15 +198,18 @@ export default abstract class LeafletMap {
195 198
196 199 fitBounds(bounds: LatLngBounds, useDefaultZoom = false, padding?: LatLngTuple) {
197 200 if (bounds.isValid()) {
198   - if ((!this.options.fitMapBounds || useDefaultZoom) && this.options.defaultZoomLevel) {
  201 + if ((!this.options.fitMapBounds || this.options.useDefaultCenterPosition) && this.options.defaultZoomLevel) {
199 202 this.map.setZoom(this.options.defaultZoomLevel, { animate: false });
200   - this.map.panTo(bounds.getCenter(), { animate: false });
  203 + this.map.panTo(this.options.defaultCenterPosition, { animate: false });
201 204 } else {
202 205 this.map.once('zoomend', () => {
203 206 if (!this.options.defaultZoomLevel && this.map.getZoom() > this.options.minZoomLevel) {
204 207 this.map.setZoom(this.options.minZoomLevel, { animate: false });
205 208 }
206 209 });
  210 + if (this.options.useDefaultCenterPosition) {
  211 + bounds = bounds.extend(this.options.defaultCenterPosition);
  212 + }
207 213 this.map.fitBounds(bounds, { padding: padding || [50, 50], animate: false });
208 214 }
209 215 this.bounds = bounds;
... ... @@ -231,8 +237,16 @@ export default abstract class LeafletMap {
231 237 updateMarkers(markersData) {
232 238 markersData.filter(mdata => !!this.convertPosition(mdata)).forEach(data => {
233 239 if (data.rotationAngle || data.rotationAngle === 0) {
  240 + const currentImage = this.options.useMarkerImageFunction ?
  241 + safeExecute(this.options.markerImageFunction,
  242 + [data, this.options.markerImages, markersData, data.dsIndex]) : this.options.currentImage;
  243 + const style = currentImage ? 'background-image: url(' + currentImage.url + ');' : '';
234 244 this.options.icon = L.divIcon({
235   - html: `<div class="arrow" style="transform: translate(-10px, -10px) rotate(${data.rotationAngle}deg);"><div>`
  245 + html: `<div class="arrow"
  246 + style="transform: translate(-10px, -10px);
  247 + ${style}
  248 + rotate(${data.rotationAngle}deg);
  249 + "><div>`
236 250 })
237 251 }
238 252 else {
... ... @@ -335,31 +349,28 @@ export default abstract class LeafletMap {
335 349 data.data = JSON.parse(data.data[0][1]) as LatLngTuple[];
336 350 }
337 351 if (this.polygons.get(data.datasource.entityName)) {
338   - this.updatePolygon(data.datasource.entityName, data.data, polyData, this.options);
  352 + this.updatePolygon(data, polyData, this.options);
339 353 }
340 354 else {
341   - this.createPolygon(data.datasource.entityName, data.data, polyData, this.options);
  355 + this.createPolygon(data, polyData, this.options);
342 356 }
343 357 }
344 358 });
345 359 }
346 360
347   - createPolygon(key: string, data: LatLngTuple[], dataSources: DatasourceData[], settings: PolygonSettings) {
  361 + createPolygon(polyData: DatasourceData, dataSources: DatasourceData[], settings: PolygonSettings) {
348 362 this.ready$.subscribe(() => {
349   - const polygon = new Polygon(this.map, data, dataSources, settings);
  363 + const polygon = new Polygon(this.map, polyData, dataSources, settings);
350 364 const bounds = this.bounds.extend(polygon.leafletPoly.getBounds());
351   - if (bounds.isValid()) {
352   - this.map.fitBounds(bounds);
353   - this.bounds = bounds;
354   - }
355   - this.polygons.set(key, polygon);
  365 + this.fitBounds(bounds);
  366 + this.polygons.set(polyData.datasource.entityName, polygon);
356 367 });
357 368 }
358 369
359   - updatePolygon(key: string, data: LatLngTuple[], dataSources: DatasourceData[], settings: PolygonSettings) {
  370 + updatePolygon(polyData: DatasourceData, dataSources: DatasourceData[], settings: PolygonSettings) {
360 371 this.ready$.subscribe(() => {
361   - const poly = this.polygons.get(key);
362   - poly.updatePolygon(data, dataSources, settings);
  372 + const poly = this.polygons.get(polyData.datasource.entityName);
  373 + poly.updatePolygon(polyData.data, dataSources, settings);
363 374 this.fitBounds(poly.leafletPoly.getBounds());
364 375 });
365 376 }
... ...
... ... @@ -111,9 +111,13 @@ export type PolygonSettings = {
111 111 polygonStrokeWeight: number;
112 112 polygonStrokeColor: string;
113 113 polygonColor: string;
  114 + showPolygonTooltip: boolean;
114 115 autocloseTooltip: boolean;
  116 + tooltipFunction: GenericFunction;
115 117 showTooltipAction: string;
116 118 tooltipAction: object;
  119 + tooltipPattern: string;
  120 + useTooltipFunction: boolean;
117 121 polygonClick: { [name: string]: actionsHandler };
118 122 polygonColorFunction?: GenericFunction;
119 123 }
... ...
... ... @@ -16,8 +16,9 @@
16 16
17 17 import L, { LatLngExpression, LatLngTuple } from 'leaflet';
18 18 import { createTooltip } from './maps-utils';
19   -import { PolygonSettings } from './map-models';
  19 +import { PolygonSettings, FormattedData } from './map-models';
20 20 import { DatasourceData } from '@app/shared/models/widget.models';
  21 +import { safeExecute, parseWithTranslation } from '@app/core/utils';
21 22
22 23 export class Polygon {
23 24
... ... @@ -26,8 +27,8 @@ export class Polygon {
26 27 data;
27 28 dataSources;
28 29
29   - constructor(public map, coordinates, dataSources, settings: PolygonSettings, onClickListener?) {
30   - this.leafletPoly = L.polygon(coordinates, {
  30 + constructor(public map, polyData: DatasourceData, dataSources, private settings: PolygonSettings, onClickListener?) {
  31 + this.leafletPoly = L.polygon(polyData.data, {
31 32 fill: true,
32 33 fillColor: settings.polygonColor,
33 34 color: settings.polygonStrokeColor,
... ... @@ -35,19 +36,29 @@ export class Polygon {
35 36 fillOpacity: settings.polygonOpacity,
36 37 opacity: settings.polygonStrokeOpacity
37 38 }).addTo(this.map);
38   -
39   - if (settings.showTooltip) {
  39 + this.dataSources = dataSources;
  40 + this.data = polyData;
  41 + if (settings.showPolygonTooltip) {
40 42 this.tooltip = createTooltip(this.leafletPoly, settings);
  43 + this.updateTooltip(polyData);
41 44 }
42 45 if (onClickListener) {
43 46 this.leafletPoly.on('click', onClickListener);
44 47 }
45 48 }
46 49
  50 + updateTooltip(data: DatasourceData) {
  51 + const pattern = this.settings.useTooltipFunction ?
  52 + safeExecute(this.settings.tooltipFunction, [this.data, this.dataSources, this.data.dsIndex]) : this.settings.tooltipPattern;
  53 + this.tooltip.setContent(parseWithTranslation.parseTemplate(pattern, data, true));
  54 + }
  55 +
47 56 updatePolygon(data: LatLngTuple[], dataSources: DatasourceData[], settings: PolygonSettings) {
48 57 this.data = data;
49 58 this.dataSources = dataSources;
50 59 this.leafletPoly.setLatLngs(data);
  60 + if (settings.showPolygonTooltip)
  61 + this.updateTooltip(this.data);
51 62 this.updatePolygonColor(settings);
52 63 }
53 64
... ...
... ... @@ -477,6 +477,11 @@ export const mapPolygonSchema =
477 477 type: 'number',
478 478 default: 1
479 479 },
  480 + showPolygonTooltip: {
  481 + title: 'Show polygon tooltip',
  482 + type: 'boolean',
  483 + default: false
  484 + },
480 485 usePolygonColorFunction: {
481 486 title: 'Use polygon color function',
482 487 type: 'boolean',
... ... @@ -501,7 +506,7 @@ export const mapPolygonSchema =
501 506 key: 'polygonStrokeColor',
502 507 type: 'color'
503 508 },
504   - 'polygonStrokeOpacity', 'polygonStrokeWeight', 'usePolygonColorFunction',
  509 + 'polygonStrokeOpacity', 'polygonStrokeWeight', 'usePolygonColorFunction', 'showPolygonTooltip',
505 510 {
506 511 key: 'polygonColorFunction',
507 512 type: 'javascript'
... ... @@ -1137,7 +1142,7 @@ export const tripAnimationSchema = {
1137 1142 rotationAngle: {
1138 1143 title: 'Set additional rotation angle for marker (deg)',
1139 1144 type: 'number',
1140   - default: 180
  1145 + default: 0
1141 1146 },
1142 1147 useMarkerImageFunction: {
1143 1148 title: 'Use marker image function',
... ...
... ... @@ -24,7 +24,7 @@ import { UtilsService } from '@core/services/utils.service';
24 24 import { TranslateService } from '@ngx-translate/core';
25 25 import { DataKey, Datasource, DatasourceData, DatasourceType, WidgetConfig } from '@shared/models/widget.models';
26 26 import { IWidgetSubscription } from '@core/api/widget-api.models';
27   -import { isDefined, isEqual, isUndefined } from '@core/utils';
  27 +import { isDefined, isEqual, isUndefined, createLabelFromDatasource } from '@core/utils';
28 28 import { EntityType } from '@shared/models/entity-type.models';
29 29 import * as _moment from 'moment';
30 30 import { FormBuilder, FormGroup, ValidatorFn, Validators } from '@angular/forms';
... ... @@ -331,7 +331,7 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni
331 331 }
332 332
333 333 public getGroupTitle(datasource: Datasource): string {
334   - return this.utils.createLabelFromDatasource(datasource, this.settings.groupTitle);
  334 + return createLabelFromDatasource(datasource, this.settings.groupTitle);
335 335 }
336 336
337 337 public visibleKeys(source: MultipleInputWidgetSource): MultipleInputWidgetDataKey[] {
... ...