You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@streampipes.apache.org by ri...@apache.org on 2022/08/23 08:08:45 UTC
[incubator-streampipes] 02/02: [STREAMPIPES-579] Fetch dashboard data in a single request
This is an automated email from the ASF dual-hosted git repository.
riemer pushed a commit to branch STREAMPIPES-579
in repository https://gitbox.apache.org/repos/asf/incubator-streampipes.git
commit 324d15384603a2aa5beeb78616002007e8be835f
Author: Dominik Riemer <do...@gmail.com>
AuthorDate: Tue Aug 23 10:08:33 2022 +0200
[STREAMPIPES-579] Fetch dashboard data in a single request
---
.../dataexplorer/DataLakeManagementV4.java | 9 +-
.../dataexplorer/v4/query/DataExplorerQueryV4.java | 15 ++
.../streampipes/model/datalake/SpQueryResult.java | 9 +
.../apache/streampipes/ps/DataLakeResourceV4.java | 15 ++
.../src/lib/apis/datalake-rest.service.ts | 5 +
.../lib/model/datalake/DatalakeQueryParameters.ts | 3 +
.../src/lib/model/gen/streampipes-model.ts | 13 +-
.../components/grid/dashboard-grid.component.html | 4 +-
.../components/grid/dashboard-grid.component.ts | 187 ++++++++++++++-------
.../panel/dashboard-panel.component.html | 3 +-
.../components/panel/dashboard-panel.component.ts | 14 +-
.../widget/dashboard-widget.component.html | 42 +++--
.../widget/dashboard-widget.component.ts | 46 ++++-
.../components/widgets/base/base-widget.ts | 42 +++--
.../edit-dashboard-dialog.component.html | 11 ++
15 files changed, 311 insertions(+), 107 deletions(-)
diff --git a/streampipes-data-explorer/src/main/java/org/apache/streampipes/dataexplorer/DataLakeManagementV4.java b/streampipes-data-explorer/src/main/java/org/apache/streampipes/dataexplorer/DataLakeManagementV4.java
index 8f6a858ff..b58ebf5c5 100644
--- a/streampipes-data-explorer/src/main/java/org/apache/streampipes/dataexplorer/DataLakeManagementV4.java
+++ b/streampipes-data-explorer/src/main/java/org/apache/streampipes/dataexplorer/DataLakeManagementV4.java
@@ -66,6 +66,8 @@ import static org.apache.streampipes.dataexplorer.v4.SupportedDataLakeQueryParam
public class DataLakeManagementV4 {
+ public static final String FOR_ID_KEY = "forId";
+
private static final DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.appendPattern("uuuu[-MM[-dd]]['T'HH[:mm[:ss[.SSSSSSSSS][.SSSSSSSS][.SSSSSSS][.SSSSSS][.SSSSS][.SSSS][.SSS][.SS][.S]]]][XXX]")
.parseDefaulting(ChronoField.NANO_OF_SECOND, 0)
@@ -87,7 +89,12 @@ public class DataLakeManagementV4 {
return new DataExplorerQueryV4(queryParts, maximumAmountOfEvents).executeQuery();
}
- return new DataExplorerQueryV4(queryParts).executeQuery();
+ if (queryParams.getProvidedParams().containsKey(FOR_ID_KEY)) {
+ String forWidgetId = queryParams.getProvidedParams().get(FOR_ID_KEY);
+ return new DataExplorerQueryV4(queryParts, forWidgetId).executeQuery();
+ } else {
+ return new DataExplorerQueryV4(queryParts).executeQuery();
+ }
}
public void getDataAsStream(ProvidedQueryParams params, String format, OutputStream outputStream) throws IOException {
diff --git a/streampipes-data-explorer/src/main/java/org/apache/streampipes/dataexplorer/v4/query/DataExplorerQueryV4.java b/streampipes-data-explorer/src/main/java/org/apache/streampipes/dataexplorer/v4/query/DataExplorerQueryV4.java
index a26c4b357..30a87d483 100644
--- a/streampipes-data-explorer/src/main/java/org/apache/streampipes/dataexplorer/v4/query/DataExplorerQueryV4.java
+++ b/streampipes-data-explorer/src/main/java/org/apache/streampipes/dataexplorer/v4/query/DataExplorerQueryV4.java
@@ -44,10 +44,20 @@ public class DataExplorerQueryV4 {
protected int maximumAmountOfEvents;
+ private boolean appendId = false;
+ private String forId;
+
public DataExplorerQueryV4() {
}
+ public DataExplorerQueryV4(Map<String, QueryParamsV4> params,
+ String forId) {
+ this(params);
+ this.appendId = true;
+ this.forId = forId;
+ }
+
public DataExplorerQueryV4(Map<String, QueryParamsV4> params) {
this.params = params;
this.maximumAmountOfEvents = -1;
@@ -134,6 +144,11 @@ public class DataExplorerQueryV4 {
result.addDataResult(series);
});
}
+
+ if (this.appendId) {
+ result.setForId(this.forId);
+ }
+
return result;
}
diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/datalake/SpQueryResult.java b/streampipes-model/src/main/java/org/apache/streampipes/model/datalake/SpQueryResult.java
index 8a2a54c8b..c3c9ef7e8 100644
--- a/streampipes-model/src/main/java/org/apache/streampipes/model/datalake/SpQueryResult.java
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/datalake/SpQueryResult.java
@@ -31,6 +31,7 @@ public class SpQueryResult {
private List<DataSeries> allDataSeries;
private int sourceIndex;
private SpQueryStatus spQueryStatus;
+ private String forId;
public SpQueryResult() {
this.total = 0;
@@ -92,4 +93,12 @@ public class SpQueryResult {
public void setSpQueryStatus(SpQueryStatus spQueryStatus) {
this.spQueryStatus = spQueryStatus;
}
+
+ public String getForId() {
+ return forId;
+ }
+
+ public void setForId(String forId) {
+ this.forId = forId;
+ }
}
diff --git a/streampipes-platform-services/src/main/java/org/apache/streampipes/ps/DataLakeResourceV4.java b/streampipes-platform-services/src/main/java/org/apache/streampipes/ps/DataLakeResourceV4.java
index 92397a221..f825ae622 100644
--- a/streampipes-platform-services/src/main/java/org/apache/streampipes/ps/DataLakeResourceV4.java
+++ b/streampipes-platform-services/src/main/java/org/apache/streampipes/ps/DataLakeResourceV4.java
@@ -40,6 +40,7 @@ import javax.ws.rs.core.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.stream.Collectors;
import static org.apache.streampipes.dataexplorer.v4.SupportedDataLakeQueryParameters.*;
@@ -165,6 +166,20 @@ public class DataLakeResourceV4 extends AbstractRestResource {
}
}
+ @POST
+ @Path("/query")
+ @Produces(MediaType.APPLICATION_JSON)
+ @Consumes(MediaType.APPLICATION_JSON)
+ public Response getData(List<Map<String, String>> queryParams) {
+ var results = queryParams
+ .stream()
+ .map(qp -> new ProvidedQueryParams(qp.get("measureName"), qp))
+ .map(params -> this.dataLakeManagement.getData(params))
+ .collect(Collectors.toList());
+
+ return ok(results);
+ }
+
@GET
@Path("/measurements/{measurementID}/download")
@Produces(MediaType.APPLICATION_OCTET_STREAM)
diff --git a/ui/projects/streampipes/platform-services/src/lib/apis/datalake-rest.service.ts b/ui/projects/streampipes/platform-services/src/lib/apis/datalake-rest.service.ts
index f83c5a2d7..6bcd51196 100644
--- a/ui/projects/streampipes/platform-services/src/lib/apis/datalake-rest.service.ts
+++ b/ui/projects/streampipes/platform-services/src/lib/apis/datalake-rest.service.ts
@@ -45,6 +45,11 @@ export class DatalakeRestService {
}));
}
+ performMultiQuery(queryParams: DatalakeQueryParameters[]): Observable<SpQueryResult[]> {
+ return this.http.post(`${this.dataLakeUrl}/query`, queryParams, {headers: {ignoreLoadingBar: ''}})
+ .pipe(map(response => response as SpQueryResult[]));
+ }
+
getData(index: string,
queryParams: DatalakeQueryParameters,
ignoreLoadingBar?: boolean): Observable<SpQueryResult> {
diff --git a/ui/projects/streampipes/platform-services/src/lib/model/datalake/DatalakeQueryParameters.ts b/ui/projects/streampipes/platform-services/src/lib/model/datalake/DatalakeQueryParameters.ts
index 3cfd937a5..d69493548 100644
--- a/ui/projects/streampipes/platform-services/src/lib/model/datalake/DatalakeQueryParameters.ts
+++ b/ui/projects/streampipes/platform-services/src/lib/model/datalake/DatalakeQueryParameters.ts
@@ -32,5 +32,8 @@ export class DatalakeQueryParameters {
public filter: string;
public maximumAmountOfEvents: number;
+ // should be only used for multi-query requests
+ public measureName: string;
+ public forId: string;
}
diff --git a/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model.ts b/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model.ts
index 2708db878..b6e2425c5 100644
--- a/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model.ts
+++ b/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model.ts
@@ -15,10 +15,11 @@
* limitations under the License.
*/
+
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
-// Generated using typescript-generator version 2.27.744 on 2022-08-19 14:38:41.
+// Generated using typescript-generator version 2.27.744 on 2022-08-23 09:28:28.
export class AbstractStreamPipesEntity {
"@class": "org.apache.streampipes.model.base.AbstractStreamPipesEntity" | "org.apache.streampipes.model.base.NamedStreamPipesEntity" | "org.apache.streampipes.model.connect.adapter.AdapterDescription" | "org.apache.streampipes.model.connect.adapter.AdapterSetDescription" | "org.apache.streampipes.model.connect.adapter.GenericAdapterSetDescription" | "org.apache.streampipes.model.connect.adapter.SpecificAdapterSetDescription" | "org.apache.streampipes.model.connect.adapter.AdapterStre [...]
@@ -192,8 +193,8 @@ export class AdapterDescription extends NamedStreamPipesEntity {
instance.selectedEndpointUrl = data.selectedEndpointUrl;
instance.correspondingServiceGroup = data.correspondingServiceGroup;
instance.correspondingDataStreamElementId = data.correspondingDataStreamElementId;
- instance.streamRules = __getCopyArrayFn(TransformationRuleDescription.fromDataUnion)(data.streamRules);
instance.valueRules = __getCopyArrayFn(TransformationRuleDescription.fromDataUnion)(data.valueRules);
+ instance.streamRules = __getCopyArrayFn(TransformationRuleDescription.fromDataUnion)(data.streamRules);
instance.schemaRules = __getCopyArrayFn(TransformationRuleDescription.fromDataUnion)(data.schemaRules);
return instance;
}
@@ -1790,9 +1791,9 @@ export class GenericAdapterSetDescription extends AdapterSetDescription implemen
}
const instance = target || new GenericAdapterSetDescription();
super.fromData(data, instance);
- instance.eventSchema = EventSchema.fromData(data.eventSchema);
instance.formatDescription = FormatDescription.fromData(data.formatDescription);
instance.protocolDescription = ProtocolDescription.fromData(data.protocolDescription);
+ instance.eventSchema = EventSchema.fromData(data.eventSchema);
return instance;
}
}
@@ -1809,9 +1810,9 @@ export class GenericAdapterStreamDescription extends AdapterStreamDescription im
}
const instance = target || new GenericAdapterStreamDescription();
super.fromData(data, instance);
- instance.eventSchema = EventSchema.fromData(data.eventSchema);
instance.formatDescription = FormatDescription.fromData(data.formatDescription);
instance.protocolDescription = ProtocolDescription.fromData(data.protocolDescription);
+ instance.eventSchema = EventSchema.fromData(data.eventSchema);
return instance;
}
}
@@ -2641,9 +2642,9 @@ export class PipelineTemplateDescription extends NamedStreamPipesEntity {
const instance = target || new PipelineTemplateDescription();
super.fromData(data, instance);
instance.boundTo = __getCopyArrayFn(BoundPipelineElement.fromData)(data.boundTo);
+ instance.pipelineTemplateId = data.pipelineTemplateId;
instance.pipelineTemplateDescription = data.pipelineTemplateDescription;
instance.pipelineTemplateName = data.pipelineTemplateName;
- instance.pipelineTemplateId = data.pipelineTemplateId;
return instance;
}
}
@@ -3052,6 +3053,7 @@ export class SpDataSet extends SpDataStream {
export class SpQueryResult {
allDataSeries: DataSeries[];
+ forId: string;
headers: string[];
sourceIndex: number;
spQueryStatus: SpQueryStatus;
@@ -3067,6 +3069,7 @@ export class SpQueryResult {
instance.allDataSeries = __getCopyArrayFn(DataSeries.fromData)(data.allDataSeries);
instance.sourceIndex = data.sourceIndex;
instance.spQueryStatus = data.spQueryStatus;
+ instance.forId = data.forId;
return instance;
}
}
diff --git a/ui/src/app/dashboard/components/grid/dashboard-grid.component.html b/ui/src/app/dashboard/components/grid/dashboard-grid.component.html
index 94baf0cbc..de987ce73 100644
--- a/ui/src/app/dashboard/components/grid/dashboard-grid.component.html
+++ b/ui/src/app/dashboard/components/grid/dashboard-grid.component.html
@@ -23,10 +23,12 @@
<gridster [options]="options" [ngClass]="editMode ? 'edit' : ''">
<ng-container *ngFor="let item of dashboard.widgets;let i=index">
<gridster-item [item]="item" #gridsterItemComponent>
- <dashboard-widget (updateCallback)="propagateItemUpdate($event)"
+ <dashboard-widget (updateCallback)="propagateItemUpdate($event)" #dashboardWidgetComponent
(deleteCallback)="propagateItemRemoval($event)"
[widget]="item"
[editMode]="editMode"
+ [allMeasurements]="allMeasurements"
+ [globalRefresh]="dashboard.dashboardGeneralSettings.globalRefresh"
[headerVisible]="headerVisible"
[gridsterItemComponent]="gridsterItemComponent"
[itemWidth]="gridsterItemComponent.width"
diff --git a/ui/src/app/dashboard/components/grid/dashboard-grid.component.ts b/ui/src/app/dashboard/components/grid/dashboard-grid.component.ts
index 942e80157..443aa4999 100644
--- a/ui/src/app/dashboard/components/grid/dashboard-grid.component.ts
+++ b/ui/src/app/dashboard/components/grid/dashboard-grid.component.ts
@@ -17,88 +17,145 @@
*/
import {
- Component,
- EventEmitter,
- Input,
- OnChanges,
- OnInit,
- Output,
- QueryList,
- SimpleChanges,
- ViewChildren
+ AfterContentInit,
+ Component,
+ EventEmitter,
+ Input,
+ OnChanges, OnDestroy,
+ OnInit,
+ Output,
+ QueryList,
+ SimpleChanges,
+ ViewChildren
} from '@angular/core';
-import {Dashboard, DashboardConfig, DashboardItem, DashboardWidgetModel} from '@streampipes/platform-services';
-import {ResizeService} from '../../services/resize.service';
-import {GridsterItemComponent, GridType} from 'angular-gridster2';
-import {GridsterInfo} from "../../models/gridster-info.model";
+import {
+ Dashboard,
+ DashboardConfig,
+ DashboardItem,
+ DashboardWidgetModel, DataLakeMeasure,
+ DatalakeRestService, SpQueryResult
+} from '@streampipes/platform-services';
+import { ResizeService } from '../../services/resize.service';
+import { GridsterItemComponent, GridType } from 'angular-gridster2';
+import { GridsterInfo } from "../../models/gridster-info.model";
+import { DashboardWidgetComponent } from '../widget/dashboard-widget.component';
+import { exhaustMap } from 'rxjs/operators';
+import { Observable, of, Subscription, timer } from 'rxjs';
@Component({
- selector: 'dashboard-grid',
- templateUrl: './dashboard-grid.component.html',
- styleUrls: ['./dashboard-grid.component.css']
+ selector: 'dashboard-grid',
+ templateUrl: './dashboard-grid.component.html',
+ styleUrls: ['./dashboard-grid.component.css']
})
-export class DashboardGridComponent implements OnInit, OnChanges {
+export class DashboardGridComponent implements OnInit, OnChanges, AfterContentInit, OnDestroy {
- @Input() editMode: boolean;
- @Input() headerVisible: boolean;
- @Input() dashboard: Dashboard;
+ @Input() editMode: boolean;
+ @Input() headerVisible: boolean;
+ @Input() dashboard: Dashboard;
+ @Input() allMeasurements: DataLakeMeasure[];
- @Output() deleteCallback: EventEmitter<DashboardItem> = new EventEmitter<DashboardItem>();
- @Output() updateCallback: EventEmitter<DashboardWidgetModel> = new EventEmitter<DashboardWidgetModel>();
+ @Output() deleteCallback: EventEmitter<DashboardItem> = new EventEmitter<DashboardItem>();
+ @Output() updateCallback: EventEmitter<DashboardWidgetModel> = new EventEmitter<DashboardWidgetModel>();
- options: DashboardConfig;
- loaded = false;
+ options: DashboardConfig;
+ loaded = false;
- @ViewChildren(GridsterItemComponent) gridsterItemComponents: QueryList<GridsterItemComponent>;
+ subscription: Subscription;
- constructor(private resizeService: ResizeService) {
+ @ViewChildren(GridsterItemComponent) gridsterItemComponents: QueryList<GridsterItemComponent>;
+ @ViewChildren(DashboardWidgetComponent) dashboardWidgetComponents: QueryList<DashboardWidgetComponent>;
- }
+ constructor(private resizeService: ResizeService,
+ private datalakeRestService: DatalakeRestService) {
+
+ }
- ngOnInit(): void {
- this.options = {
- disablePushOnDrag: true,
- draggable: {enabled: this.editMode},
- gridType: GridType.VerticalFixed,
- minCols: 12,
- maxCols: 12,
- minRows: 4,
- fixedRowHeight: 50,
- fixedColWidth: 50,
- margin: 5,
- resizable: {enabled: this.editMode},
- displayGrid: this.editMode ? 'always' : 'none',
- itemResizeCallback: ((item, itemComponent) => {
- this.resizeService.notify({
- gridsterItem: item,
- gridsterItemComponent: itemComponent
- } as GridsterInfo);
- }),
- itemInitCallback: ((item, itemComponent) => {
- this.resizeService.notify({
- gridsterItem: item,
- gridsterItemComponent: itemComponent
- } as GridsterInfo);
- //window.dispatchEvent(new Event('resize'));
- })
- };
+ ngOnInit(): void {
+ this.options = {
+ disablePushOnDrag: true,
+ draggable: {enabled: this.editMode},
+ gridType: GridType.VerticalFixed,
+ minCols: 12,
+ maxCols: 12,
+ minRows: 4,
+ fixedRowHeight: 50,
+ fixedColWidth: 50,
+ margin: 5,
+ resizable: {enabled: this.editMode},
+ displayGrid: this.editMode ? 'always' : 'none',
+ itemResizeCallback: ((item, itemComponent) => {
+ this.resizeService.notify({
+ gridsterItem: item,
+ gridsterItemComponent: itemComponent
+ } as GridsterInfo);
+ }),
+ itemInitCallback: ((item, itemComponent) => {
+ this.resizeService.notify({
+ gridsterItem: item,
+ gridsterItemComponent: itemComponent
+ } as GridsterInfo);
+ //window.dispatchEvent(new Event('resize'));
+ })
+ };
+ }
+
+ ngOnDestroy() {
+ if (this.subscription) {
+ this.subscription.unsubscribe();
}
+ }
- ngOnChanges(changes: SimpleChanges): void {
- if (changes['editMode'] && this.options) {
- this.options.draggable.enabled = this.editMode;
- this.options.resizable.enabled = this.editMode;
- this.options.displayGrid = this.editMode ? 'always' : 'none';
- this.options.api.optionsChanged();
- }
+ ngOnChanges(changes: SimpleChanges): void {
+ if (changes['editMode'] && this.options) {
+ this.options.draggable.enabled = this.editMode;
+ this.options.resizable.enabled = this.editMode;
+ this.options.displayGrid = this.editMode ? 'always' : 'none';
+ this.options.api.optionsChanged();
}
+ }
- propagateItemRemoval(widget: DashboardItem) {
- this.deleteCallback.emit(widget);
+ propagateItemRemoval(widget: DashboardItem) {
+ this.deleteCallback.emit(widget);
+ }
+
+ propagateItemUpdate(dashboardWidget: DashboardWidgetModel) {
+ this.updateCallback.emit(dashboardWidget);
+ }
+
+ ngAfterContentInit(): void {
+ if (this.dashboard.dashboardGeneralSettings.globalRefresh) {
+ this.checkWidgetsReady();
}
+ }
- propagateItemUpdate(dashboardWidget: DashboardWidgetModel) {
- this.updateCallback.emit(dashboardWidget);
+ checkWidgetsReady() {
+ if (this.dashboardWidgetComponents) {
+ this.createQuerySubscription();
+ } else {
+ setTimeout(() => this.checkWidgetsReady(), 1000);
}
+ }
+
+ createQuerySubscription() {
+ this.subscription = timer(0, this.dashboard.dashboardGeneralSettings.refreshIntervalInSeconds * 1000)
+ .pipe(exhaustMap(() => this.makeQueryObservable()))
+ .subscribe(res => {
+ if (res.length > 0) {
+ this.dashboardWidgetComponents.forEach((widget, index) => {
+ const widgetId = widget.getWidgetId();
+ const queryResult = res.find(r => r.forId === widgetId);
+ if (queryResult) {
+ widget.processQueryResponse(queryResult);
+ }
+ });
+ }
+ });
+ }
+ makeQueryObservable(): Observable<SpQueryResult[]> {
+ const queries = this.dashboardWidgetComponents
+ .map(dw => dw.getWidgetQuery())
+ .filter(query => query !== undefined);
+ return this.datalakeRestService.performMultiQuery(queries);
+ }
}
diff --git a/ui/src/app/dashboard/components/panel/dashboard-panel.component.html b/ui/src/app/dashboard/components/panel/dashboard-panel.component.html
index 2b09d951f..1e368cfb3 100644
--- a/ui/src/app/dashboard/components/panel/dashboard-panel.component.html
+++ b/ui/src/app/dashboard/components/panel/dashboard-panel.component.html
@@ -56,9 +56,10 @@
<div fxFlex="100" fxLayout="column">
<dashboard-grid [editMode]="editMode" [dashboard]="dashboard"
[headerVisible]="headerVisible"
+ [allMeasurements]="allMeasurements"
(updateCallback)="updateAndQueueItemForDeletion($event)"
(deleteCallback)="removeAndQueueItemForDeletion($event)"
- *ngIf="dashboard"
+ *ngIf="dashboard && allMeasurements"
class="h-100 dashboard-grid"></dashboard-grid>
</div>
</sp-basic-view>
diff --git a/ui/src/app/dashboard/components/panel/dashboard-panel.component.ts b/ui/src/app/dashboard/components/panel/dashboard-panel.component.ts
index b244e59ca..11d0cfbb9 100644
--- a/ui/src/app/dashboard/components/panel/dashboard-panel.component.ts
+++ b/ui/src/app/dashboard/components/panel/dashboard-panel.component.ts
@@ -17,7 +17,13 @@
*/
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
-import { ClientDashboardItem, Dashboard, DashboardService, DashboardWidgetModel } from '@streampipes/platform-services';
+import {
+ ClientDashboardItem,
+ Dashboard,
+ DashboardService,
+ DashboardWidgetModel, DataLakeMeasure,
+ DatalakeRestService
+} from '@streampipes/platform-services';
import { forkJoin, Observable, of, Subscription } from 'rxjs';
import { AddVisualizationDialogComponent } from '../../dialogs/add-widget/add-visualization-dialog.component';
import { RefreshDashboardService } from '../../services/refresh-dashboard.service';
@@ -46,10 +52,12 @@ export class DashboardPanelComponent implements OnInit {
widgetIdsToRemove: string[] = [];
widgetsToUpdate: Map<string, DashboardWidgetModel> = new Map<string, DashboardWidgetModel>();
+ allMeasurements: DataLakeMeasure[];
headerVisible = true;
constructor(private dashboardService: DashboardService,
+ private datalakeRestService: DatalakeRestService,
private dialogService: DialogService,
private dialog: MatDialog,
private refreshDashboardService: RefreshDashboardService,
@@ -72,6 +80,7 @@ export class DashboardPanelComponent implements OnInit {
});
this.getDashboard(params.id);
+ this.getAllMeasurements();
}
@@ -84,6 +93,9 @@ export class DashboardPanelComponent implements OnInit {
});
}
+ getAllMeasurements(): void {
+ this.datalakeRestService.getAllMeasurementSeries().subscribe(res => this.allMeasurements = res);
+ }
addWidget(): void {
const dialogRef = this.dialogService.open(AddVisualizationDialogComponent, {
diff --git a/ui/src/app/dashboard/components/widget/dashboard-widget.component.html b/ui/src/app/dashboard/components/widget/dashboard-widget.component.html
index 282e5df45..d7a4ec7d6 100644
--- a/ui/src/app/dashboard/components/widget/dashboard-widget.component.html
+++ b/ui/src/app/dashboard/components/widget/dashboard-widget.component.html
@@ -51,113 +51,127 @@
</div>
<div *ngIf="widgetLoaded && pipelineRunning" class="h-100">
<div *ngIf="configuredWidget.widgetType === 'number'" class="h-100 p-0">
- <number-widget [itemWidth]="itemWidth"
+ <number-widget [itemWidth]="itemWidth" #activeWidget
[itemHeight]="itemHeight"
[editMode]="editMode"
+ [globalRefresh]="globalRefresh"
[gridsterItemComponent]="gridsterItemComponent"
[widgetConfig]="configuredWidget" [widgetDataConfig]="widgetDataConfig"
class="h-100"></number-widget>
</div>
<div *ngIf="configuredWidget.widgetType === 'line'" class="h-100 p-0">
- <line-widget [itemWidth]="itemWidth"
+ <line-widget [itemWidth]="itemWidth" #activeWidget
[itemHeight]="itemHeight"
[editMode]="editMode"
+ [globalRefresh]="globalRefresh"
[gridsterItemComponent]="gridsterItemComponent"
[widgetConfig]="configuredWidget"
[widgetDataConfig]="widgetDataConfig" class="h-100"></line-widget>
</div>
<div *ngIf="configuredWidget.widgetType === 'area'" class="h-100 p-0">
- <area-widget [itemWidth]="itemWidth"
+ <area-widget [itemWidth]="itemWidth" #activeWidget
[itemHeight]="itemHeight"
[editMode]="editMode"
+ [globalRefresh]="globalRefresh"
[gridsterItemComponent]="gridsterItemComponent"
[widgetConfig]="configuredWidget"
[widgetDataConfig]="widgetDataConfig" class="h-100"></area-widget>
</div>
<div *ngIf="configuredWidget.widgetType === 'table'" class="h-100 p-0">
- <table-widget [itemWidth]="itemWidth"
+ <table-widget [itemWidth]="itemWidth" #activeWidget
[itemHeight]="itemHeight"
[editMode]="editMode"
+ [globalRefresh]="globalRefresh"
[gridsterItemComponent]="gridsterItemComponent"
[widgetConfig]="configuredWidget"
[widgetDataConfig]="widgetDataConfig" class="h-100"></table-widget>
</div>
<div *ngIf="configuredWidget.widgetType === 'gauge'" class="h-100 p-0">
- <gauge-widget [itemWidth]="itemWidth"
+ <gauge-widget [itemWidth]="itemWidth" #activeWidget
[itemHeight]="itemHeight"
[editMode]="editMode"
+ [globalRefresh]="globalRefresh"
[gridsterItemComponent]="gridsterItemComponent"
[widgetConfig]="configuredWidget"
[widgetDataConfig]="widgetDataConfig" class="h-100"></gauge-widget>
</div>
<div *ngIf="configuredWidget.widgetType === 'image'" class="h-100 p-0">
- <image-widget [itemWidth]="itemWidth"
+ <image-widget [itemWidth]="itemWidth" #activeWidget
[itemHeight]="itemHeight"
[editMode]="editMode"
+ [globalRefresh]="globalRefresh"
[gridsterItemComponent]="gridsterItemComponent"
[widgetConfig]="configuredWidget"
[widgetDataConfig]="widgetDataConfig" class="h-100"></image-widget>
</div>
<div *ngIf="configuredWidget.widgetType === 'map'" class="h-100 p-0">
- <map-widget [itemWidth]="itemWidth"
+ <map-widget [itemWidth]="itemWidth" #activeWidget
[itemHeight]="itemHeight"
[editMode]="editMode"
+ [globalRefresh]="globalRefresh"
[gridsterItemComponent]="gridsterItemComponent"
[widgetConfig]="configuredWidget"
[widgetDataConfig]="widgetDataConfig" class="h-100"></map-widget>
</div>
<div *ngIf="configuredWidget.widgetType === 'raw'" class="h-100 p-0">
- <raw-widget [itemWidth]="itemWidth"
+ <raw-widget [itemWidth]="itemWidth" #activeWidget
[itemHeight]="itemHeight"
[editMode]="editMode"
+ [globalRefresh]="globalRefresh"
[gridsterItemComponent]="gridsterItemComponent"
[widgetConfig]="configuredWidget"
[widgetDataConfig]="widgetDataConfig" class="h-100"></raw-widget>
</div>
<div *ngIf="configuredWidget.widgetType === 'html'" class="h-100 p-0">
- <html-widget [itemWidth]="itemWidth"
+ <html-widget [itemWidth]="itemWidth" #activeWidget
[itemHeight]="itemHeight"
[editMode]="editMode"
+ [globalRefresh]="globalRefresh"
[gridsterItemComponent]="gridsterItemComponent"
[widgetConfig]="configuredWidget"
[widgetDataConfig]="widgetDataConfig" class="h-100"></html-widget>
</div>
<div *ngIf="configuredWidget.widgetType === 'trafficlight'" class="h-100 p-0">
- <traffic-light-widget [itemWidth]="itemWidth"
+ <traffic-light-widget [itemWidth]="itemWidth" #activeWidget
[itemHeight]="itemHeight"
[editMode]="editMode"
+ [globalRefresh]="globalRefresh"
[gridsterItemComponent]="gridsterItemComponent"
[widgetConfig]="configuredWidget"
[widgetDataConfig]="widgetDataConfig" class="h-100"></traffic-light-widget>
</div>
<div *ngIf="configuredWidget.widgetType === 'wordcloud'" class="h-100 p-0">
- <wordcloud-widget [itemWidth]="itemWidth"
+ <wordcloud-widget [itemWidth]="itemWidth" #activeWidget
[itemHeight]="itemHeight"
[editMode]="editMode"
+ [globalRefresh]="globalRefresh"
[gridsterItemComponent]="gridsterItemComponent"
[widgetConfig]="configuredWidget"
[widgetDataConfig]="widgetDataConfig" class="h-100"></wordcloud-widget>
</div>
<div *ngIf="configuredWidget.widgetType === 'status'" class="h-100 p-0">
- <status-widget [itemWidth]="itemWidth"
+ <status-widget [itemWidth]="itemWidth" #activeWidget
[itemHeight]="itemHeight"
[editMode]="editMode"
+ [globalRefresh]="globalRefresh"
[gridsterItemComponent]="gridsterItemComponent"
[widgetConfig]="configuredWidget"
[widgetDataConfig]="widgetDataConfig" class="h-100"></status-widget>
</div>
<div *ngIf="configuredWidget.widgetType === 'bar-race'" class="h-100 p-0">
- <bar-race-widget [itemWidth]="itemWidth"
+ <bar-race-widget [itemWidth]="itemWidth" #activeWidget
[itemHeight]="itemHeight"
[editMode]="editMode"
+ [globalRefresh]="globalRefresh"
[gridsterItemComponent]="gridsterItemComponent"
[widgetConfig]="configuredWidget"
[widgetDataConfig]="widgetDataConfig" class="h-100"></bar-race-widget>
</div>
<div *ngIf="configuredWidget.widgetType === 'stacked-line-chart'" class="h-100 p-0">
- <stacked-line-chart-widget [itemWidth]="itemWidth"
+ <stacked-line-chart-widget [itemWidth]="itemWidth" #activeWidget
[itemHeight]="itemHeight"
[editMode]="editMode"
+ [globalRefresh]="globalRefresh"
[gridsterItemComponent]="gridsterItemComponent"
[widgetConfig]="configuredWidget"
[widgetDataConfig]="widgetDataConfig"
diff --git a/ui/src/app/dashboard/components/widget/dashboard-widget.component.ts b/ui/src/app/dashboard/components/widget/dashboard-widget.component.ts
index b35f46003..85b12ec7b 100644
--- a/ui/src/app/dashboard/components/widget/dashboard-widget.component.ts
+++ b/ui/src/app/dashboard/components/widget/dashboard-widget.component.ts
@@ -16,17 +16,26 @@
*
*/
-import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
+import {
+ Component,
+ EventEmitter,
+ Input,
+ OnInit,
+ Output,
+ QueryList, ViewChild,
+ ViewChildren,
+ ViewContainerRef
+} from '@angular/core';
import { AddVisualizationDialogComponent } from '../../dialogs/add-widget/add-visualization-dialog.component';
import {
DashboardItem,
DashboardService,
DashboardWidgetModel,
- DataLakeMeasure,
+ DataLakeMeasure, DatalakeQueryParameters,
DatalakeRestService,
DataViewDataExplorerService,
Pipeline,
- PipelineService
+ PipelineService, SpQueryResult
} from '@streampipes/platform-services';
import { DialogService, PanelType } from '@streampipes/shared-ui';
import { EditModeService } from '../../services/edit-mode.service';
@@ -35,6 +44,8 @@ import { zip } from 'rxjs';
import { GridsterItemComponent } from 'angular-gridster2';
import { ResizeService } from '../../services/resize.service';
import { GridsterInfo } from '../../models/gridster-info.model';
+import { BaseDataExplorerWidgetDirective } from '../../../data-explorer/components/widgets/base/base-data-explorer-widget.directive';
+import { BaseStreamPipesWidget } from '../widgets/base/base-widget';
@Component({
selector: 'dashboard-widget',
@@ -49,6 +60,8 @@ export class DashboardWidgetComponent implements OnInit {
@Input() itemWidth: number;
@Input() itemHeight: number;
@Input() gridsterItemComponent: GridsterItemComponent;
+ @Input() globalRefresh: boolean;
+ @Input() allMeasurements: DataLakeMeasure[];
@Output() deleteCallback: EventEmitter<DashboardItem> = new EventEmitter<DashboardItem>();
@Output() updateCallback: EventEmitter<DashboardWidgetModel> = new EventEmitter<DashboardWidgetModel>();
@@ -61,6 +74,8 @@ export class DashboardWidgetComponent implements OnInit {
pipelineRunning = false;
widgetNotAvailable = false;
+ _activeWidget: BaseStreamPipesWidget;
+
constructor(private dashboardService: DashboardService,
private dialogService: DialogService,
private pipelineService: PipelineService,
@@ -86,10 +101,10 @@ export class DashboardWidgetComponent implements OnInit {
}
loadVisualizablePipeline() {
- zip(this.dataExplorerService.getPersistedDataStream(this.configuredWidget.pipelineId, this.configuredWidget.visualizationName), this.dataLakeRestService.getAllMeasurementSeries())
+ zip(this.dataExplorerService.getPersistedDataStream(this.configuredWidget.pipelineId, this.configuredWidget.visualizationName))
.subscribe(res => {
const vizPipeline = res[0];
- const measurement = res[1].find(m => m.measureName === vizPipeline.measureName);
+ const measurement = this.allMeasurements.find(m => m.measureName === vizPipeline.measureName);
vizPipeline.eventSchema = measurement.eventSchema;
this.widgetDataConfig = vizPipeline;
this.dashboardService.getPipelineById(vizPipeline.pipelineId).subscribe(pipeline => {
@@ -151,4 +166,25 @@ export class DashboardWidgetComponent implements OnInit {
}
});
}
+
+ @ViewChild('activeWidget')
+ set activeWidget(activeWidget: BaseStreamPipesWidget) {
+ this._activeWidget = activeWidget;
+ }
+
+ getWidgetQuery(): DatalakeQueryParameters {
+ if (this._activeWidget) {
+ return this._activeWidget.buildQuery(true);
+ } else {
+ return undefined;
+ }
+ }
+
+ processQueryResponse(res: SpQueryResult) {
+ this._activeWidget.processQueryResult(res);
+ }
+
+ getWidgetId(): string {
+ return this.widget.id;
+ }
}
diff --git a/ui/src/app/dashboard/components/widgets/base/base-widget.ts b/ui/src/app/dashboard/components/widgets/base/base-widget.ts
index 4a202c2b0..9a801a474 100644
--- a/ui/src/app/dashboard/components/widgets/base/base-widget.ts
+++ b/ui/src/app/dashboard/components/widgets/base/base-widget.ts
@@ -24,7 +24,7 @@ import { ResizeService } from '../../../services/resize.service';
import {
DashboardWidgetModel,
DataLakeMeasure,
- DatalakeQueryParameterBuilder,
+ DatalakeQueryParameterBuilder, DatalakeQueryParameters,
DatalakeRestService, EventPropertyPrimitive, EventPropertyUnion, FieldConfig,
SpQueryResult
} from '@streampipes/platform-services';
@@ -50,6 +50,7 @@ export abstract class BaseStreamPipesWidget implements OnInit, OnChanges, OnDest
@Input() itemWidth: number;
@Input() itemHeight: number;
@Input() editMode: boolean;
+ @Input() globalRefresh: boolean;
subscription: Subscription;
intervalSubject: BehaviorSubject<number>;
@@ -83,15 +84,17 @@ export abstract class BaseStreamPipesWidget implements OnInit, OnChanges, OnDest
this.prepareConfigExtraction();
- this.fireQuery().subscribe(result => this.processQueryResult(result));
+ if (!(this.globalRefresh)) {
+ this.fireQuery().subscribe(result => this.processQueryResult(result));
- this.intervalSubject = new BehaviorSubject<number>(this.refreshIntervalInSeconds);
- this.subscription = this.intervalSubject.pipe(
- switchMap(val => interval(val * 1000)))
- .pipe(exhaustMap(() => this.fireQuery()))
- .subscribe((result) => {
- this.processQueryResult(result);
- });
+ this.intervalSubject = new BehaviorSubject<number>(this.refreshIntervalInSeconds);
+ this.subscription = this.intervalSubject.pipe(
+ switchMap(val => interval(val * 1000)))
+ .pipe(exhaustMap(() => this.fireQuery()))
+ .subscribe((result) => {
+ this.processQueryResult(result);
+ });
+ }
}
prepareConfigExtraction() {
@@ -121,9 +124,13 @@ export abstract class BaseStreamPipesWidget implements OnInit, OnChanges, OnDest
}
ngOnDestroy(): void {
- this.subscription.unsubscribe();
- this.intervalSubject.unsubscribe();
- this.resizeSub.unsubscribe();
+ if (this.subscription) {
+ this.subscription.unsubscribe();
+ this.intervalSubject.unsubscribe();
+ }
+ if (this.resizeSub) {
+ this.resizeSub.unsubscribe();
+ }
}
computeCurrentWidth(width: number): number {
@@ -183,7 +190,7 @@ export abstract class BaseStreamPipesWidget implements OnInit, OnChanges, OnDest
}
}
- buildQuery() {
+ public buildQuery(includeMeasure = false): DatalakeQueryParameters {
const queryBuilder = DatalakeQueryParameterBuilder.create();
const columns = this.getFieldsToQuery();
if (columns) {
@@ -197,10 +204,17 @@ export abstract class BaseStreamPipesWidget implements OnInit, OnChanges, OnDest
queryBuilder.withColumnFilter(fields, false);
}
- return queryBuilder
+ const queryParams = queryBuilder
.withLimit(this.queryLimit)
.withOrdering('DESC')
.build();
+
+ if (includeMeasure) {
+ queryParams.measureName = this.widgetDataConfig.measureName;
+ queryParams.forId = this.widgetConfig._id;
+ }
+
+ return queryParams;
}
getAnyMeasurementField(eventProperties: EventPropertyUnion[]): string {
diff --git a/ui/src/app/dashboard/dialogs/edit-dashboard/edit-dashboard-dialog.component.html b/ui/src/app/dashboard/dialogs/edit-dashboard/edit-dashboard-dialog.component.html
index a653ab005..d45e60a30 100644
--- a/ui/src/app/dashboard/dialogs/edit-dashboard/edit-dashboard-dialog.component.html
+++ b/ui/src/app/dashboard/dialogs/edit-dashboard/edit-dashboard-dialog.component.html
@@ -33,7 +33,18 @@
</mat-form-field>
<mat-checkbox [(ngModel)]="dashboard.displayHeader">Show name and description in dashboard
</mat-checkbox>
+ <div fxLayout="column" class="mt-10">
+ <mat-checkbox [(ngModel)]="dashboard.dashboardGeneralSettings.globalRefresh">
+ Use same update frequency for all widgets (will override widget-specific intervals)
+ </mat-checkbox>
+ <mat-form-field class="full-width mt-10"
+ color="accent"
+ *ngIf="dashboard.dashboardGeneralSettings.globalRefresh">
+ <mat-label>Refresh interval (seconds)</mat-label>
+ <input matInput [(ngModel)]="dashboard.dashboardGeneralSettings.refreshIntervalInSeconds">
+ </mat-form-field>
+ </div>
</div>
</div>
</div>