Commit 961455b4af0c0656304ded3a5c9551633dd692fe
Committed by
GitHub
Merge pull request #4096 from vvlladd28/improvement/timeseries-table/load
UI: Improvement load and update time into time series table
Showing
11 changed files
with
156 additions
and
118 deletions
@@ -47,7 +47,7 @@ | @@ -47,7 +47,7 @@ | ||
47 | "resources": [], | 47 | "resources": [], |
48 | "templateHtml": "<tb-timeseries-table-widget \n [ctx]=\"ctx\">\n</tb-timeseries-table-widget>", | 48 | "templateHtml": "<tb-timeseries-table-widget \n [ctx]=\"ctx\">\n</tb-timeseries-table-widget>", |
49 | "templateCss": "", | 49 | "templateCss": "", |
50 | - "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeseriesTableWidget.onDataUpdated();\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}", | 50 | + "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeseriesTableWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n ignoreDataUpdateOnIntervalTick: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}", |
51 | "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"TimeseriesTableSettings\",\n \"properties\": {\n \"showTimestamp\": {\n \"title\": \"Display timestamp column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showMilliseconds\": {\n \"title\": \"Display timestamp milliseconds\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n }, \n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"hideEmptyLines\": {\n \"title\": \"Hide empty lines\",\n \"type\": \"boolean\",\n \"default\": false\n }\n },\n \"required\": []\n },\n \"form\": [\n \"showTimestamp\",\n \"showMilliseconds\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"hideEmptyLines\"\n ]\n}", | 51 | "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"TimeseriesTableSettings\",\n \"properties\": {\n \"showTimestamp\": {\n \"title\": \"Display timestamp column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showMilliseconds\": {\n \"title\": \"Display timestamp milliseconds\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n }, \n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"hideEmptyLines\": {\n \"title\": \"Hide empty lines\",\n \"type\": \"boolean\",\n \"default\": false\n }\n },\n \"required\": []\n },\n \"form\": [\n \"showTimestamp\",\n \"showMilliseconds\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"hideEmptyLines\"\n ]\n}", |
52 | "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", | 52 | "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", |
53 | "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = (value + 60)/120 * 100;\\n var color = tinycolor.mix('blue', 'red', amount = percent);\\n color.setAlpha(.5);\\n return {\\n paddingLeft: '20px',\\n color: '#ffffff',\\n background: color.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\"},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = value;\\n var backgroundColor = tinycolor('blue');\\n backgroundColor.setAlpha(value/100);\\n var color = 'blue';\\n if (value > 50) {\\n color = 'white';\\n }\\n \\n return {\\n paddingLeft: '20px',\\n color: color,\\n background: backgroundColor.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showTimestamp\":true,\"displayPagination\":true,\"defaultPageSize\":10},\"title\":\"Timeseries table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\"}" | 53 | "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = (value + 60)/120 * 100;\\n var color = tinycolor.mix('blue', 'red', amount = percent);\\n color.setAlpha(.5);\\n return {\\n paddingLeft: '20px',\\n color: '#ffffff',\\n background: color.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\"},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = value;\\n var backgroundColor = tinycolor('blue');\\n backgroundColor.setAlpha(value/100);\\n var color = 'blue';\\n if (value > 50) {\\n color = 'white';\\n }\\n \\n return {\\n paddingLeft: '20px',\\n color: color,\\n background: backgroundColor.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showTimestamp\":true,\"displayPagination\":true,\"defaultPageSize\":10},\"title\":\"Timeseries table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\"}" |
@@ -134,4 +134,4 @@ | @@ -134,4 +134,4 @@ | ||
134 | } | 134 | } |
135 | } | 135 | } |
136 | ] | 136 | ] |
137 | -} | ||
137 | +} |
@@ -71,6 +71,7 @@ export class DataAggregator { | @@ -71,6 +71,7 @@ export class DataAggregator { | ||
71 | 71 | ||
72 | private dataReceived = false; | 72 | private dataReceived = false; |
73 | private resetPending = false; | 73 | private resetPending = false; |
74 | + private updatedData = false; | ||
74 | 75 | ||
75 | private noAggregation = this.aggregationType === AggregationType.NONE; | 76 | private noAggregation = this.aggregationType === AggregationType.NONE; |
76 | private aggregationTimeout = Math.max(this.interval, 1000); | 77 | private aggregationTimeout = Math.max(this.interval, 1000); |
@@ -90,7 +91,8 @@ export class DataAggregator { | @@ -90,7 +91,8 @@ export class DataAggregator { | ||
90 | private timeWindow: number, | 91 | private timeWindow: number, |
91 | private interval: number, | 92 | private interval: number, |
92 | private stateData: boolean, | 93 | private stateData: boolean, |
93 | - private utils: UtilsService) { | 94 | + private utils: UtilsService, |
95 | + private ignoreDataUpdateOnIntervalTick: boolean) { | ||
94 | this.tsKeyNames.forEach((key) => { | 96 | this.tsKeyNames.forEach((key) => { |
95 | this.dataBuffer[key] = []; | 97 | this.dataBuffer[key] = []; |
96 | }); | 98 | }); |
@@ -140,6 +142,7 @@ export class DataAggregator { | @@ -140,6 +142,7 @@ export class DataAggregator { | ||
140 | this.elapsed = 0; | 142 | this.elapsed = 0; |
141 | this.aggregationTimeout = Math.max(this.interval, 1000); | 143 | this.aggregationTimeout = Math.max(this.interval, 1000); |
142 | this.resetPending = true; | 144 | this.resetPending = true; |
145 | + this.updatedData = false; | ||
143 | this.intervalTimeoutHandle = setTimeout(this.onInterval.bind(this), this.aggregationTimeout); | 146 | this.intervalTimeoutHandle = setTimeout(this.onInterval.bind(this), this.aggregationTimeout); |
144 | } | 147 | } |
145 | 148 | ||
@@ -180,6 +183,7 @@ export class DataAggregator { | @@ -180,6 +183,7 @@ export class DataAggregator { | ||
180 | this.onInterval(history, detectChanges); | 183 | this.onInterval(history, detectChanges); |
181 | } | 184 | } |
182 | } | 185 | } |
186 | + this.updatedData = true; | ||
183 | } | 187 | } |
184 | 188 | ||
185 | private onInterval(history?: boolean, detectChanges?: boolean) { | 189 | private onInterval(history?: boolean, detectChanges?: boolean) { |
@@ -201,8 +205,9 @@ export class DataAggregator { | @@ -201,8 +205,9 @@ export class DataAggregator { | ||
201 | } else { | 205 | } else { |
202 | this.data = this.updateData(); | 206 | this.data = this.updateData(); |
203 | } | 207 | } |
204 | - if (this.onDataCb) { | 208 | + if (this.onDataCb && (!this.ignoreDataUpdateOnIntervalTick || this.updatedData)) { |
205 | this.onDataCb(this.data, detectChanges); | 209 | this.onDataCb(this.data, detectChanges); |
210 | + this.updatedData = false; | ||
206 | } | 211 | } |
207 | if (!history) { | 212 | if (!history) { |
208 | this.intervalTimeoutHandle = setTimeout(this.onInterval.bind(this), this.aggregationTimeout); | 213 | this.intervalTimeoutHandle = setTimeout(this.onInterval.bind(this), this.aggregationTimeout); |
@@ -223,6 +228,7 @@ export class DataAggregator { | @@ -223,6 +228,7 @@ export class DataAggregator { | ||
223 | this.lastPrevKvPairData[key] = [aggTimestamp, aggData.aggValue]; | 228 | this.lastPrevKvPairData[key] = [aggTimestamp, aggData.aggValue]; |
224 | } | 229 | } |
225 | aggKeyData.delete(aggTimestamp); | 230 | aggKeyData.delete(aggTimestamp); |
231 | + this.updatedData = true; | ||
226 | } else if (aggTimestamp <= this.endTs) { | 232 | } else if (aggTimestamp <= this.endTs) { |
227 | const kvPair: [number, any] = [aggTimestamp, aggData.aggValue]; | 233 | const kvPair: [number, any] = [aggTimestamp, aggData.aggValue]; |
228 | keyData.push(kvPair); | 234 | keyData.push(kvPair); |
@@ -69,6 +69,7 @@ export interface EntityDataSubscriptionOptions { | @@ -69,6 +69,7 @@ export interface EntityDataSubscriptionOptions { | ||
69 | type: widgetType; | 69 | type: widgetType; |
70 | entityFilter?: EntityFilter; | 70 | entityFilter?: EntityFilter; |
71 | isPaginatedDataSubscription?: boolean; | 71 | isPaginatedDataSubscription?: boolean; |
72 | + ignoreDataUpdateOnIntervalTick?: boolean; | ||
72 | pageLink?: EntityDataPageLink; | 73 | pageLink?: EntityDataPageLink; |
73 | keyFilters?: Array<KeyFilter>; | 74 | keyFilters?: Array<KeyFilter>; |
74 | additionalKeyFilters?: Array<KeyFilter>; | 75 | additionalKeyFilters?: Array<KeyFilter>; |
@@ -750,7 +751,8 @@ export class EntityDataSubscription { | @@ -750,7 +751,8 @@ export class EntityDataSubscription { | ||
750 | subsTw.aggregation.timeWindow, | 751 | subsTw.aggregation.timeWindow, |
751 | subsTw.aggregation.interval, | 752 | subsTw.aggregation.interval, |
752 | subsTw.aggregation.stateData, | 753 | subsTw.aggregation.stateData, |
753 | - this.utils | 754 | + this.utils, |
755 | + this.entityDataSubscriptionOptions.ignoreDataUpdateOnIntervalTick | ||
754 | ); | 756 | ); |
755 | } | 757 | } |
756 | 758 |
@@ -60,7 +60,8 @@ export class EntityDataService { | @@ -60,7 +60,8 @@ export class EntityDataService { | ||
60 | constructor(private telemetryService: TelemetryWebsocketService, | 60 | constructor(private telemetryService: TelemetryWebsocketService, |
61 | private utils: UtilsService) {} | 61 | private utils: UtilsService) {} |
62 | 62 | ||
63 | - public prepareSubscription(listener: EntityDataListener): Observable<EntityDataLoadResult> { | 63 | + public prepareSubscription(listener: EntityDataListener, |
64 | + ignoreDataUpdateOnIntervalTick = false): Observable<EntityDataLoadResult> { | ||
64 | const datasource = listener.configDatasource; | 65 | const datasource = listener.configDatasource; |
65 | listener.subscriptionOptions = this.createSubscriptionOptions( | 66 | listener.subscriptionOptions = this.createSubscriptionOptions( |
66 | datasource, | 67 | datasource, |
@@ -68,7 +69,8 @@ export class EntityDataService { | @@ -68,7 +69,8 @@ export class EntityDataService { | ||
68 | datasource.pageLink, | 69 | datasource.pageLink, |
69 | datasource.keyFilters, | 70 | datasource.keyFilters, |
70 | null, | 71 | null, |
71 | - false); | 72 | + false, |
73 | + ignoreDataUpdateOnIntervalTick); | ||
72 | if (datasource.type === DatasourceType.entity && (!datasource.entityFilter || !datasource.pageLink)) { | 74 | if (datasource.type === DatasourceType.entity && (!datasource.entityFilter || !datasource.pageLink)) { |
73 | return of(null); | 75 | return of(null); |
74 | } | 76 | } |
@@ -87,7 +89,8 @@ export class EntityDataService { | @@ -87,7 +89,8 @@ export class EntityDataService { | ||
87 | 89 | ||
88 | public subscribeForPaginatedData(listener: EntityDataListener, | 90 | public subscribeForPaginatedData(listener: EntityDataListener, |
89 | pageLink: EntityDataPageLink, | 91 | pageLink: EntityDataPageLink, |
90 | - keyFilters: KeyFilter[]): Observable<EntityDataLoadResult> { | 92 | + keyFilters: KeyFilter[], |
93 | + ignoreDataUpdateOnIntervalTick = false): Observable<EntityDataLoadResult> { | ||
91 | const datasource = listener.configDatasource; | 94 | const datasource = listener.configDatasource; |
92 | listener.subscriptionOptions = this.createSubscriptionOptions( | 95 | listener.subscriptionOptions = this.createSubscriptionOptions( |
93 | datasource, | 96 | datasource, |
@@ -95,7 +98,8 @@ export class EntityDataService { | @@ -95,7 +98,8 @@ export class EntityDataService { | ||
95 | pageLink, | 98 | pageLink, |
96 | datasource.keyFilters, | 99 | datasource.keyFilters, |
97 | keyFilters, | 100 | keyFilters, |
98 | - true); | 101 | + true, |
102 | + ignoreDataUpdateOnIntervalTick); | ||
99 | if (datasource.type === DatasourceType.entity && (!datasource.entityFilter || !pageLink)) { | 103 | if (datasource.type === DatasourceType.entity && (!datasource.entityFilter || !pageLink)) { |
100 | listener.dataLoaded(emptyPageData<EntityData>(), [], | 104 | listener.dataLoaded(emptyPageData<EntityData>(), [], |
101 | listener.configDatasourceIndex, listener.subscriptionOptions.pageLink); | 105 | listener.configDatasourceIndex, listener.subscriptionOptions.pageLink); |
@@ -119,7 +123,8 @@ export class EntityDataService { | @@ -119,7 +123,8 @@ export class EntityDataService { | ||
119 | pageLink: EntityDataPageLink, | 123 | pageLink: EntityDataPageLink, |
120 | keyFilters: KeyFilter[], | 124 | keyFilters: KeyFilter[], |
121 | additionalKeyFilters: KeyFilter[], | 125 | additionalKeyFilters: KeyFilter[], |
122 | - isPaginatedDataSubscription: boolean): EntityDataSubscriptionOptions { | 126 | + isPaginatedDataSubscription: boolean, |
127 | + ignoreDataUpdateOnIntervalTick: boolean): EntityDataSubscriptionOptions { | ||
123 | const subscriptionDataKeys: Array<SubscriptionDataKey> = []; | 128 | const subscriptionDataKeys: Array<SubscriptionDataKey> = []; |
124 | datasource.dataKeys.forEach((dataKey) => { | 129 | datasource.dataKeys.forEach((dataKey) => { |
125 | const subscriptionDataKey: SubscriptionDataKey = { | 130 | const subscriptionDataKey: SubscriptionDataKey = { |
@@ -145,6 +150,7 @@ export class EntityDataService { | @@ -145,6 +150,7 @@ export class EntityDataService { | ||
145 | } | 150 | } |
146 | } | 151 | } |
147 | entityDataSubscriptionOptions.isPaginatedDataSubscription = isPaginatedDataSubscription; | 152 | entityDataSubscriptionOptions.isPaginatedDataSubscription = isPaginatedDataSubscription; |
153 | + entityDataSubscriptionOptions.ignoreDataUpdateOnIntervalTick = ignoreDataUpdateOnIntervalTick; | ||
148 | return entityDataSubscriptionOptions; | 154 | return entityDataSubscriptionOptions; |
149 | } | 155 | } |
150 | } | 156 | } |
@@ -226,6 +226,7 @@ export interface WidgetSubscriptionOptions { | @@ -226,6 +226,7 @@ export interface WidgetSubscriptionOptions { | ||
226 | hasDataPageLink?: boolean; | 226 | hasDataPageLink?: boolean; |
227 | singleEntity?: boolean; | 227 | singleEntity?: boolean; |
228 | warnOnPageDataOverflow?: boolean; | 228 | warnOnPageDataOverflow?: boolean; |
229 | + ignoreDataUpdateOnIntervalTick?: boolean; | ||
229 | targetDeviceAliasIds?: Array<string>; | 230 | targetDeviceAliasIds?: Array<string>; |
230 | targetDeviceIds?: Array<string>; | 231 | targetDeviceIds?: Array<string>; |
231 | useDashboardTimewindow?: boolean; | 232 | useDashboardTimewindow?: boolean; |
@@ -83,6 +83,7 @@ export class WidgetSubscription implements IWidgetSubscription { | @@ -83,6 +83,7 @@ export class WidgetSubscription implements IWidgetSubscription { | ||
83 | hasDataPageLink: boolean; | 83 | hasDataPageLink: boolean; |
84 | singleEntity: boolean; | 84 | singleEntity: boolean; |
85 | warnOnPageDataOverflow: boolean; | 85 | warnOnPageDataOverflow: boolean; |
86 | + ignoreDataUpdateOnIntervalTick: boolean; | ||
86 | 87 | ||
87 | datasourcePages: PageData<Datasource>[]; | 88 | datasourcePages: PageData<Datasource>[]; |
88 | dataPages: PageData<Array<DatasourceData>>[]; | 89 | dataPages: PageData<Array<DatasourceData>>[]; |
@@ -200,6 +201,7 @@ export class WidgetSubscription implements IWidgetSubscription { | @@ -200,6 +201,7 @@ export class WidgetSubscription implements IWidgetSubscription { | ||
200 | this.hasDataPageLink = options.hasDataPageLink; | 201 | this.hasDataPageLink = options.hasDataPageLink; |
201 | this.singleEntity = options.singleEntity; | 202 | this.singleEntity = options.singleEntity; |
202 | this.warnOnPageDataOverflow = options.warnOnPageDataOverflow; | 203 | this.warnOnPageDataOverflow = options.warnOnPageDataOverflow; |
204 | + this.ignoreDataUpdateOnIntervalTick = options.ignoreDataUpdateOnIntervalTick; | ||
203 | this.datasourcePages = []; | 205 | this.datasourcePages = []; |
204 | this.datasources = []; | 206 | this.datasources = []; |
205 | this.dataPages = []; | 207 | this.dataPages = []; |
@@ -423,7 +425,7 @@ export class WidgetSubscription implements IWidgetSubscription { | @@ -423,7 +425,7 @@ export class WidgetSubscription implements IWidgetSubscription { | ||
423 | } | 425 | } |
424 | }; | 426 | }; |
425 | this.entityDataListeners.push(listener); | 427 | this.entityDataListeners.push(listener); |
426 | - return this.ctx.entityDataService.prepareSubscription(listener); | 428 | + return this.ctx.entityDataService.prepareSubscription(listener, this.ignoreDataUpdateOnIntervalTick); |
427 | }); | 429 | }); |
428 | return forkJoin(resolveResultObservables).pipe( | 430 | return forkJoin(resolveResultObservables).pipe( |
429 | map((resolveResults) => { | 431 | map((resolveResults) => { |
@@ -815,7 +817,8 @@ export class WidgetSubscription implements IWidgetSubscription { | @@ -815,7 +817,8 @@ export class WidgetSubscription implements IWidgetSubscription { | ||
815 | } | 817 | } |
816 | }; | 818 | }; |
817 | this.entityDataListeners[datasourceIndex] = entityDataListener; | 819 | this.entityDataListeners[datasourceIndex] = entityDataListener; |
818 | - return this.ctx.entityDataService.subscribeForPaginatedData(entityDataListener, pageLink, keyFilters); | 820 | + return this.ctx.entityDataService.subscribeForPaginatedData(entityDataListener, pageLink, keyFilters, |
821 | + this.ignoreDataUpdateOnIntervalTick); | ||
819 | } else { | 822 | } else { |
820 | return of(null); | 823 | return of(null); |
821 | } | 824 | } |
@@ -39,73 +39,75 @@ | @@ -39,73 +39,75 @@ | ||
39 | </mat-toolbar> | 39 | </mat-toolbar> |
40 | <mat-tab-group [ngClass]="{'tb-headless': sources.length === 1}" fxFlex | 40 | <mat-tab-group [ngClass]="{'tb-headless': sources.length === 1}" fxFlex |
41 | [(selectedIndex)]="sourceIndex" (selectedIndexChange)="onSourceIndexChanged()"> | 41 | [(selectedIndex)]="sourceIndex" (selectedIndexChange)="onSourceIndexChanged()"> |
42 | - <mat-tab *ngFor="let source of sources; trackBy: trackBySourcesIndex" label="{{ source.datasource.name }}"> | ||
43 | - <div fxFlex class="table-container"> | ||
44 | - <table mat-table [dataSource]="source.timeseriesDatasource" [trackBy]="trackByRowTimestamp" | ||
45 | - matSort [matSortActive]="source.pageLink.sortOrder.property" [matSortDirection]="source.pageLink.sortDirection()" matSortDisableClear> | ||
46 | - <ng-container *ngIf="showTimestamp" [matColumnDef]="'0'"> | ||
47 | - <mat-header-cell *matHeaderCellDef mat-sort-header>Timestamp</mat-header-cell> | ||
48 | - <mat-cell *matCellDef="let row;" | ||
49 | - [innerHTML]="cellContent(source, 0, row, row[0])" | ||
50 | - [ngStyle]="cellStyle(source, 0, row[0])"> | ||
51 | - </mat-cell> | ||
52 | - </ng-container> | ||
53 | - <ng-container [matColumnDef]="h.index + ''" *ngFor="let h of source.header; trackBy: trackByColumnIndex;"> | ||
54 | - <mat-header-cell *matHeaderCellDef mat-sort-header> {{ h.dataKey.label }} </mat-header-cell> | ||
55 | - <mat-cell *matCellDef="let row;" | ||
56 | - [innerHTML]="cellContent(source, h.index, row, row[h.index])" | ||
57 | - [ngStyle]="cellStyle(source, h.index, row[h.index])"> | ||
58 | - </mat-cell> | ||
59 | - </ng-container> | ||
60 | - <ng-container matColumnDef="actions" stickyEnd> | ||
61 | - <mat-header-cell *matHeaderCellDef [ngStyle.gt-md]="{ minWidth: (actionCellDescriptors.length * 40) + 'px', | ||
62 | - maxWidth: (actionCellDescriptors.length * 40) + 'px', | ||
63 | - width: (actionCellDescriptors.length * 40) + 'px' }"> | ||
64 | - </mat-header-cell> | ||
65 | - <mat-cell *matCellDef="let row" [ngStyle.gt-md]="{ minWidth: (actionCellDescriptors.length * 40) + 'px', | ||
66 | - maxWidth: (actionCellDescriptors.length * 40) + 'px', | ||
67 | - width: (actionCellDescriptors.length * 40) + 'px' }"> | ||
68 | - <div fxHide fxShow.gt-md fxFlex fxLayout="row" fxLayoutAlign="end"> | ||
69 | - <button mat-button mat-icon-button [disabled]="isLoading$ | async" | ||
70 | - *ngFor="let actionDescriptor of actionCellDescriptors; trackBy: trackByActionCellDescriptionId" | ||
71 | - matTooltip="{{ actionDescriptor.displayName }}" | ||
72 | - matTooltipPosition="above" | ||
73 | - (click)="onActionButtonClick($event, row, actionDescriptor)"> | ||
74 | - <mat-icon>{{actionDescriptor.icon}}</mat-icon> | ||
75 | - </button> | ||
76 | - </div> | ||
77 | - <div fxHide fxShow.lt-lg *ngIf="actionCellDescriptors.length"> | ||
78 | - <button mat-button mat-icon-button | ||
79 | - (click)="$event.stopPropagation(); ctx.detectChanges();" | ||
80 | - [matMenuTriggerFor]="cellActionsMenu"> | ||
81 | - <mat-icon class="material-icons">more_vert</mat-icon> | ||
82 | - </button> | ||
83 | - <mat-menu #cellActionsMenu="matMenu" xPosition="before"> | ||
84 | - <button mat-menu-item *ngFor="let actionDescriptor of actionCellDescriptors; trackBy: trackByActionCellDescriptionId" | ||
85 | - [disabled]="isLoading$ | async" | 42 | + <mat-tab *ngFor="let source of sources; trackBy: trackBySourcesIndex; let index = index;" label="{{ source.datasource.name }}"> |
43 | + <ng-template [ngIf]="isActiveTab(index)"> | ||
44 | + <div fxFlex class="table-container"> | ||
45 | + <table mat-table [dataSource]="source.timeseriesDatasource" [trackBy]="trackByRowTimestamp" | ||
46 | + matSort [matSortActive]="source.pageLink.sortOrder.property" [matSortDirection]="source.pageLink.sortDirection()" matSortDisableClear> | ||
47 | + <ng-container *ngIf="showTimestamp" [matColumnDef]="'0'"> | ||
48 | + <mat-header-cell *matHeaderCellDef mat-sort-header>Timestamp</mat-header-cell> | ||
49 | + <mat-cell *matCellDef="let row;" | ||
50 | + [innerHTML]="cellContent(source, 0, row, row[0])" | ||
51 | + [ngStyle]="cellStyle(source, 0, row[0])"> | ||
52 | + </mat-cell> | ||
53 | + </ng-container> | ||
54 | + <ng-container [matColumnDef]="h.index + ''" *ngFor="let h of source.header; trackBy: trackByColumnIndex;"> | ||
55 | + <mat-header-cell *matHeaderCellDef mat-sort-header> {{ h.dataKey.label }} </mat-header-cell> | ||
56 | + <mat-cell *matCellDef="let row;" | ||
57 | + [innerHTML]="cellContent(source, h.index, row, row[h.index])" | ||
58 | + [ngStyle]="cellStyle(source, h.index, row[h.index])"> | ||
59 | + </mat-cell> | ||
60 | + </ng-container> | ||
61 | + <ng-container matColumnDef="actions" stickyEnd> | ||
62 | + <mat-header-cell *matHeaderCellDef [ngStyle.gt-md]="{ minWidth: (actionCellDescriptors.length * 40) + 'px', | ||
63 | + maxWidth: (actionCellDescriptors.length * 40) + 'px', | ||
64 | + width: (actionCellDescriptors.length * 40) + 'px' }"> | ||
65 | + </mat-header-cell> | ||
66 | + <mat-cell *matCellDef="let row" [ngStyle.gt-md]="{ minWidth: (actionCellDescriptors.length * 40) + 'px', | ||
67 | + maxWidth: (actionCellDescriptors.length * 40) + 'px', | ||
68 | + width: (actionCellDescriptors.length * 40) + 'px' }"> | ||
69 | + <div fxHide fxShow.gt-md fxFlex fxLayout="row" fxLayoutAlign="end"> | ||
70 | + <button mat-button mat-icon-button [disabled]="isLoading$ | async" | ||
71 | + *ngFor="let actionDescriptor of actionCellDescriptors; trackBy: trackByActionCellDescriptionId" | ||
72 | + matTooltip="{{ actionDescriptor.displayName }}" | ||
73 | + matTooltipPosition="above" | ||
86 | (click)="onActionButtonClick($event, row, actionDescriptor)"> | 74 | (click)="onActionButtonClick($event, row, actionDescriptor)"> |
87 | <mat-icon>{{actionDescriptor.icon}}</mat-icon> | 75 | <mat-icon>{{actionDescriptor.icon}}</mat-icon> |
88 | - <span>{{ actionDescriptor.displayName }}</span> | ||
89 | </button> | 76 | </button> |
90 | - </mat-menu> | ||
91 | - </div> | ||
92 | - </mat-cell> | ||
93 | - </ng-container> | ||
94 | - <mat-header-row *matHeaderRowDef="source.displayedColumns; sticky: true"></mat-header-row> | ||
95 | - <mat-row *matRowDef="let row; columns: source.displayedColumns;" | ||
96 | - (click)="onRowClick($event, row)"></mat-row> | ||
97 | - </table> | ||
98 | - <span [fxShow]="source.timeseriesDatasource.isEmpty() | async" | ||
99 | - fxLayoutAlign="center center" | ||
100 | - class="no-data-found" translate>widget.no-data-found</span> | ||
101 | - </div> | ||
102 | - <mat-divider *ngIf="displayPagination"></mat-divider> | ||
103 | - <mat-paginator *ngIf="displayPagination" | ||
104 | - [length]="source.timeseriesDatasource.total() | async" | ||
105 | - [pageIndex]="source.pageLink.page" | ||
106 | - [pageSize]="source.pageLink.pageSize" | ||
107 | - [pageSizeOptions]="pageSizeOptions" | ||
108 | - showFirstLastButtons></mat-paginator> | 77 | + </div> |
78 | + <div fxHide fxShow.lt-lg *ngIf="actionCellDescriptors.length"> | ||
79 | + <button mat-button mat-icon-button | ||
80 | + (click)="$event.stopPropagation(); ctx.detectChanges();" | ||
81 | + [matMenuTriggerFor]="cellActionsMenu"> | ||
82 | + <mat-icon class="material-icons">more_vert</mat-icon> | ||
83 | + </button> | ||
84 | + <mat-menu #cellActionsMenu="matMenu" xPosition="before"> | ||
85 | + <button mat-menu-item *ngFor="let actionDescriptor of actionCellDescriptors; trackBy: trackByActionCellDescriptionId" | ||
86 | + [disabled]="isLoading$ | async" | ||
87 | + (click)="onActionButtonClick($event, row, actionDescriptor)"> | ||
88 | + <mat-icon>{{actionDescriptor.icon}}</mat-icon> | ||
89 | + <span>{{ actionDescriptor.displayName }}</span> | ||
90 | + </button> | ||
91 | + </mat-menu> | ||
92 | + </div> | ||
93 | + </mat-cell> | ||
94 | + </ng-container> | ||
95 | + <mat-header-row *matHeaderRowDef="source.displayedColumns; sticky: true"></mat-header-row> | ||
96 | + <mat-row *matRowDef="let row; columns: source.displayedColumns;" | ||
97 | + (click)="onRowClick($event, row)"></mat-row> | ||
98 | + </table> | ||
99 | + <span [fxShow]="source.timeseriesDatasource.isEmpty() | async" | ||
100 | + fxLayoutAlign="center center" | ||
101 | + class="no-data-found" translate>widget.no-data-found</span> | ||
102 | + </div> | ||
103 | + <mat-divider *ngIf="displayPagination"></mat-divider> | ||
104 | + <mat-paginator *ngIf="displayPagination" | ||
105 | + [length]="source.timeseriesDatasource.total() | async" | ||
106 | + [pageIndex]="source.pageLink.page" | ||
107 | + [pageSize]="source.pageLink.pageSize" | ||
108 | + [pageSizeOptions]="pageSizeOptions" | ||
109 | + showFirstLastButtons></mat-paginator> | ||
110 | + </ng-template> | ||
109 | </mat-tab> | 111 | </mat-tab> |
110 | </mat-tab-group> | 112 | </mat-tab-group> |
111 | </div> | 113 | </div> |
@@ -40,12 +40,12 @@ import { | @@ -40,12 +40,12 @@ import { | ||
40 | } from '@shared/models/widget.models'; | 40 | } from '@shared/models/widget.models'; |
41 | import { UtilsService } from '@core/services/utils.service'; | 41 | import { UtilsService } from '@core/services/utils.service'; |
42 | import { TranslateService } from '@ngx-translate/core'; | 42 | import { TranslateService } from '@ngx-translate/core'; |
43 | -import {hashCode, isDefined, isDefinedAndNotNull, isNumber} from '@core/utils'; | 43 | +import { hashCode, isDefined, isNumber } from '@core/utils'; |
44 | import cssjs from '@core/css/css'; | 44 | import cssjs from '@core/css/css'; |
45 | import { PageLink } from '@shared/models/page/page-link'; | 45 | import { PageLink } from '@shared/models/page/page-link'; |
46 | import { Direction, SortOrder, sortOrderFromString } from '@shared/models/page/sort-order'; | 46 | import { Direction, SortOrder, sortOrderFromString } from '@shared/models/page/sort-order'; |
47 | import { CollectionViewer, DataSource } from '@angular/cdk/collections'; | 47 | import { CollectionViewer, DataSource } from '@angular/cdk/collections'; |
48 | -import { BehaviorSubject, fromEvent, merge, Observable, of } from 'rxjs'; | 48 | +import { BehaviorSubject, fromEvent, merge, Observable, of, Subscription } from 'rxjs'; |
49 | import { emptyPageData, PageData } from '@shared/models/page/page-data'; | 49 | import { emptyPageData, PageData } from '@shared/models/page/page-data'; |
50 | import { catchError, debounceTime, distinctUntilChanged, map, tap } from 'rxjs/operators'; | 50 | import { catchError, debounceTime, distinctUntilChanged, map, tap } from 'rxjs/operators'; |
51 | import { MatPaginator } from '@angular/material/paginator'; | 51 | import { MatPaginator } from '@angular/material/paginator'; |
@@ -129,6 +129,8 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI | @@ -129,6 +129,8 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI | ||
129 | public showTimestamp = true; | 129 | public showTimestamp = true; |
130 | private dateFormatFilter: string; | 130 | private dateFormatFilter: string; |
131 | 131 | ||
132 | + private subscriptions: Subscription[] = []; | ||
133 | + | ||
132 | private searchAction: WidgetAction = { | 134 | private searchAction: WidgetAction = { |
133 | name: 'action.search', | 135 | name: 'action.search', |
134 | show: true, | 136 | show: true, |
@@ -166,40 +168,27 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI | @@ -166,40 +168,27 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI | ||
166 | debounceTime(150), | 168 | debounceTime(150), |
167 | distinctUntilChanged(), | 169 | distinctUntilChanged(), |
168 | tap(() => { | 170 | tap(() => { |
169 | - if (this.displayPagination) { | ||
170 | - this.paginators.forEach((paginator) => { | ||
171 | - paginator.pageIndex = 0; | ||
172 | - }); | ||
173 | - } | ||
174 | this.sources.forEach((source) => { | 171 | this.sources.forEach((source) => { |
175 | source.pageLink.textSearch = this.textSearch; | 172 | source.pageLink.textSearch = this.textSearch; |
173 | + if (this.displayPagination) { | ||
174 | + source.pageLink.page = 0; | ||
175 | + } | ||
176 | }); | 176 | }); |
177 | - this.updateAllData(); | 177 | + this.loadCurrentSourceRow(); |
178 | + this.ctx.detectChanges(); | ||
178 | }) | 179 | }) |
179 | ) | 180 | ) |
180 | .subscribe(); | 181 | .subscribe(); |
181 | 182 | ||
182 | - if (this.displayPagination) { | ||
183 | - this.sorts.forEach((sort, index) => { | ||
184 | - sort.sortChange.subscribe(() => this.paginators.toArray()[index].pageIndex = 0); | ||
185 | - }); | ||
186 | - } | ||
187 | - this.sorts.forEach((sort, index) => { | ||
188 | - const paginator = this.displayPagination ? this.paginators.toArray()[index] : null; | ||
189 | - sort.sortChange.subscribe(() => this.paginators.toArray()[index].pageIndex = 0); | ||
190 | - ((this.displayPagination ? merge(sort.sortChange, paginator.page) : sort.sortChange) as Observable<any>) | ||
191 | - .pipe( | ||
192 | - tap(() => this.updateData(sort, paginator, index)) | ||
193 | - ) | ||
194 | - .subscribe(); | 183 | + this.sorts.changes.subscribe(() => { |
184 | + this.initSubscriptionsToSortAndPaginator(); | ||
195 | }); | 185 | }); |
196 | - this.updateAllData(); | 186 | + |
187 | + this.initSubscriptionsToSortAndPaginator(); | ||
197 | } | 188 | } |
198 | 189 | ||
199 | public onDataUpdated() { | 190 | public onDataUpdated() { |
200 | - this.sources.forEach((source) => { | ||
201 | - source.timeseriesDatasource.dataUpdated(this.data); | ||
202 | - }); | 191 | + this.updateCurrentSourceData(); |
203 | } | 192 | } |
204 | 193 | ||
205 | private initialize() { | 194 | private initialize() { |
@@ -305,7 +294,27 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI | @@ -305,7 +294,27 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI | ||
305 | this.ctx.activeEntityInfo = activeEntityInfo; | 294 | this.ctx.activeEntityInfo = activeEntityInfo; |
306 | } | 295 | } |
307 | 296 | ||
297 | + private initSubscriptionsToSortAndPaginator() { | ||
298 | + this.subscriptions.forEach(subscription => subscription.unsubscribe()); | ||
299 | + this.sorts.forEach((sort, index) => { | ||
300 | + let paginator = null; | ||
301 | + const observables = [sort.sortChange]; | ||
302 | + if (this.displayPagination) { | ||
303 | + paginator = this.paginators.toArray()[index]; | ||
304 | + this.subscriptions.push( | ||
305 | + sort.sortChange.subscribe(() => paginator.pageIndex = 0) | ||
306 | + ); | ||
307 | + observables.push(paginator.page); | ||
308 | + } | ||
309 | + this.updateData(sort, paginator); | ||
310 | + this.subscriptions.push(merge(...observables).pipe( | ||
311 | + tap(() => this.updateData(sort, paginator)) | ||
312 | + ).subscribe()); | ||
313 | + }); | ||
314 | + } | ||
315 | + | ||
308 | onSourceIndexChanged() { | 316 | onSourceIndexChanged() { |
317 | + this.updateCurrentSourceData(); | ||
309 | this.updateActiveEntityInfo(); | 318 | this.updateActiveEntityInfo(); |
310 | } | 319 | } |
311 | 320 | ||
@@ -326,30 +335,19 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI | @@ -326,30 +335,19 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI | ||
326 | exitFilterMode() { | 335 | exitFilterMode() { |
327 | this.textSearchMode = false; | 336 | this.textSearchMode = false; |
328 | this.textSearch = null; | 337 | this.textSearch = null; |
329 | - this.sources.forEach((source, index) => { | 338 | + this.sources.forEach((source) => { |
330 | source.pageLink.textSearch = this.textSearch; | 339 | source.pageLink.textSearch = this.textSearch; |
331 | - const sort = this.sorts.toArray()[index]; | ||
332 | - let paginator = null; | ||
333 | if (this.displayPagination) { | 340 | if (this.displayPagination) { |
334 | - paginator = this.paginators.toArray()[index]; | ||
335 | - paginator.pageIndex = 0; | 341 | + source.pageLink.page = 0; |
336 | } | 342 | } |
337 | - this.updateData(sort, paginator, index); | ||
338 | }); | 343 | }); |
344 | + this.loadCurrentSourceRow(); | ||
339 | this.ctx.hideTitlePanel = false; | 345 | this.ctx.hideTitlePanel = false; |
340 | this.ctx.detectChanges(true); | 346 | this.ctx.detectChanges(true); |
341 | } | 347 | } |
342 | 348 | ||
343 | - private updateAllData() { | ||
344 | - this.sources.forEach((source, index) => { | ||
345 | - const sort = this.sorts.toArray()[index]; | ||
346 | - const paginator = this.displayPagination ? this.paginators.toArray()[index] : null; | ||
347 | - this.updateData(sort, paginator, index); | ||
348 | - }); | ||
349 | - } | ||
350 | - | ||
351 | - private updateData(sort: MatSort, paginator: MatPaginator, index: number) { | ||
352 | - const source = this.sources[index]; | 349 | + private updateData(sort: MatSort, paginator: MatPaginator) { |
350 | + const source = this.sources[this.sourceIndex]; | ||
353 | if (this.displayPagination) { | 351 | if (this.displayPagination) { |
354 | source.pageLink.page = paginator.pageIndex; | 352 | source.pageLink.page = paginator.pageIndex; |
355 | source.pageLink.pageSize = paginator.pageSize; | 353 | source.pageLink.pageSize = paginator.pageSize; |
@@ -418,7 +416,6 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI | @@ -418,7 +416,6 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI | ||
418 | 416 | ||
419 | if (!isDefined(content)) { | 417 | if (!isDefined(content)) { |
420 | return ''; | 418 | return ''; |
421 | - | ||
422 | } else { | 419 | } else { |
423 | switch (typeof content) { | 420 | switch (typeof content) { |
424 | case 'string': | 421 | case 'string': |
@@ -462,6 +459,18 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI | @@ -462,6 +459,18 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI | ||
462 | } | 459 | } |
463 | this.ctx.actionsApi.handleWidgetAction($event, actionDescriptor, entityId, entityName, row, entityLabel); | 460 | this.ctx.actionsApi.handleWidgetAction($event, actionDescriptor, entityId, entityName, row, entityLabel); |
464 | } | 461 | } |
462 | + | ||
463 | + public isActiveTab(index: number): boolean { | ||
464 | + return index === this.sourceIndex; | ||
465 | + } | ||
466 | + | ||
467 | + private updateCurrentSourceData() { | ||
468 | + this.sources[this.sourceIndex].timeseriesDatasource.dataUpdated(this.data); | ||
469 | + } | ||
470 | + | ||
471 | + private loadCurrentSourceRow() { | ||
472 | + this.sources[this.sourceIndex].timeseriesDatasource.loadRows(); | ||
473 | + } | ||
465 | } | 474 | } |
466 | 475 | ||
467 | class TimeseriesDatasource implements DataSource<TimeseriesRow> { | 476 | class TimeseriesDatasource implements DataSource<TimeseriesRow> { |
@@ -482,6 +491,10 @@ class TimeseriesDatasource implements DataSource<TimeseriesRow> { | @@ -482,6 +491,10 @@ class TimeseriesDatasource implements DataSource<TimeseriesRow> { | ||
482 | } | 491 | } |
483 | 492 | ||
484 | connect(collectionViewer: CollectionViewer): Observable<TimeseriesRow[] | ReadonlyArray<TimeseriesRow>> { | 493 | connect(collectionViewer: CollectionViewer): Observable<TimeseriesRow[] | ReadonlyArray<TimeseriesRow>> { |
494 | + if (this.rowsSubject.isStopped) { | ||
495 | + this.rowsSubject.isStopped = false; | ||
496 | + this.pageDataSubject.isStopped = false; | ||
497 | + } | ||
485 | return this.rowsSubject.asObservable(); | 498 | return this.rowsSubject.asObservable(); |
486 | } | 499 | } |
487 | 500 |
@@ -485,6 +485,9 @@ export class WidgetComponentService { | @@ -485,6 +485,9 @@ export class WidgetComponentService { | ||
485 | if (isUndefined(result.typeParameters.warnOnPageDataOverflow)) { | 485 | if (isUndefined(result.typeParameters.warnOnPageDataOverflow)) { |
486 | result.typeParameters.warnOnPageDataOverflow = true; | 486 | result.typeParameters.warnOnPageDataOverflow = true; |
487 | } | 487 | } |
488 | + if (isUndefined(result.typeParameters.ignoreDataUpdateOnIntervalTick)) { | ||
489 | + result.typeParameters.ignoreDataUpdateOnIntervalTick = false; | ||
490 | + } | ||
488 | if (isUndefined(result.typeParameters.dataKeysOptional)) { | 491 | if (isUndefined(result.typeParameters.dataKeysOptional)) { |
489 | result.typeParameters.dataKeysOptional = false; | 492 | result.typeParameters.dataKeysOptional = false; |
490 | } | 493 | } |
@@ -895,6 +895,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI | @@ -895,6 +895,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI | ||
895 | hasDataPageLink: this.typeParameters.hasDataPageLink, | 895 | hasDataPageLink: this.typeParameters.hasDataPageLink, |
896 | singleEntity: this.typeParameters.singleEntity, | 896 | singleEntity: this.typeParameters.singleEntity, |
897 | warnOnPageDataOverflow: this.typeParameters.warnOnPageDataOverflow, | 897 | warnOnPageDataOverflow: this.typeParameters.warnOnPageDataOverflow, |
898 | + ignoreDataUpdateOnIntervalTick: this.typeParameters.ignoreDataUpdateOnIntervalTick, | ||
898 | comparisonEnabled: comparisonSettings.comparisonEnabled, | 899 | comparisonEnabled: comparisonSettings.comparisonEnabled, |
899 | timeForComparison: comparisonSettings.timeForComparison | 900 | timeForComparison: comparisonSettings.timeForComparison |
900 | }; | 901 | }; |
@@ -154,6 +154,7 @@ export interface WidgetTypeParameters { | @@ -154,6 +154,7 @@ export interface WidgetTypeParameters { | ||
154 | hasDataPageLink?: boolean; | 154 | hasDataPageLink?: boolean; |
155 | singleEntity?: boolean; | 155 | singleEntity?: boolean; |
156 | warnOnPageDataOverflow?: boolean; | 156 | warnOnPageDataOverflow?: boolean; |
157 | + ignoreDataUpdateOnIntervalTick?: boolean; | ||
157 | } | 158 | } |
158 | 159 | ||
159 | export interface WidgetControllerDescriptor { | 160 | export interface WidgetControllerDescriptor { |