blob: b8e86ffdb9c5af627fb68e1892038e97d02c7fce [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import {
Directive,
EventEmitter,
HostBinding,
inject,
Input,
OnInit,
Output,
} from '@angular/core';
import { GridsterItem, GridsterItemComponent } from 'angular-gridster2';
import { WidgetConfigurationService } from '../../../services/widget-configuration.service';
import {
DashboardItem,
DataExplorerDataConfig,
DataExplorerField,
DataExplorerWidgetModel,
DatalakeRestService,
DataViewQueryGeneratorService,
SpLogMessage,
SpQueryResult,
TimeSettings,
} from '@streampipes/platform-services';
import { ResizeService } from '../../../services/resize.service';
import {
BaseWidgetData,
FieldProvider,
} from '../../../models/dataview-dashboard.model';
import { Observable, Subject, Subscription, zip } from 'rxjs';
import { DataExplorerFieldProviderService } from '../../../services/data-explorer-field-provider-service';
import { TimeSelectionService } from '../../../services/time-selection.service';
import { catchError, switchMap } from 'rxjs/operators';
import { DataExplorerWidgetRegistry } from '../../../registry/data-explorer-widget-registry';
import { SpFieldUpdateService } from '../../../services/field-update.service';
@Directive()
export abstract class BaseDataExplorerWidgetDirective<
T extends DataExplorerWidgetModel,
>
implements BaseWidgetData<T>, OnInit
{
private static TOO_MUCH_DATA_PARAMETER = 10000;
@Output()
removeWidgetCallback: EventEmitter<boolean> = new EventEmitter();
@Output()
timerCallback: EventEmitter<boolean> = new EventEmitter();
@Output()
errorCallback: EventEmitter<SpLogMessage> =
new EventEmitter<SpLogMessage>();
@Input() gridsterItem: GridsterItem;
@Input() gridsterItemComponent: GridsterItemComponent;
@Input() editMode: boolean;
@Input() timeSettings: TimeSettings;
@Input()
previewMode = false;
@Input()
gridMode = true;
@Input() dataViewDashboardItem: DashboardItem;
@Input() dataExplorerWidget: T;
@HostBinding('class') className = 'h-100';
public selectedProperties: string[];
public showNoDataInDateRange: boolean;
public showData: boolean;
public showIsLoadingData: boolean;
public showTooMuchData: boolean;
public showInvalidConfiguration = false;
public amountOfTooMuchEvents: number;
fieldProvider: FieldProvider;
widgetConfigurationSub: Subscription;
resizeSub: Subscription;
timeSelectionSub: Subscription;
widthOffset: number;
heightOffset: number;
requestQueue$: Subject<Observable<SpQueryResult>[]> = new Subject<
Observable<SpQueryResult>[]
>();
// inject services to avoid constructor overload
protected dataLakeRestService = inject(DatalakeRestService);
protected widgetConfigurationService = inject(WidgetConfigurationService);
protected resizeService = inject(ResizeService);
protected dataViewQueryGeneratorService = inject(
DataViewQueryGeneratorService,
);
protected timeSelectionService = inject(TimeSelectionService);
protected widgetRegistryService = inject(DataExplorerWidgetRegistry);
protected fieldUpdateService = inject(SpFieldUpdateService);
public fieldService = inject(DataExplorerFieldProviderService);
ngOnInit(): void {
this.heightOffset = this.gridMode ? 70 : 65;
this.widthOffset = this.gridMode ? 10 : 10;
this.showData = true;
const sourceConfigs = this.dataExplorerWidget.dataConfig.sourceConfigs;
this.fieldProvider =
this.fieldService.generateFieldLists(sourceConfigs);
this.requestQueue$
.pipe(
switchMap(observables => {
this.errorCallback.emit(undefined);
return zip(...observables).pipe(
catchError(err => {
this.timerCallback.emit(false);
this.errorCallback.emit(err.error);
return [];
}),
);
}),
)
.subscribe(results => {
results.forEach(
(result, index) => (result.sourceIndex = index),
);
this.timerCallback.emit(false);
setTimeout(() => {
this.validateReceivedData(results);
});
});
this.widgetConfigurationSub =
this.widgetConfigurationService.configurationChangedSubject.subscribe(
refreshMessage => {
if (refreshMessage.refreshData) {
const newFieldsProvider =
this.fieldService.generateFieldLists(sourceConfigs);
const addedFields = this.fieldService.getAddedFields(
this.fieldProvider.allFields,
newFieldsProvider.allFields,
);
const removedFields =
this.fieldService.getRemovedFields(
this.fieldProvider.allFields,
newFieldsProvider.allFields,
);
this.fieldProvider =
this.fieldService.generateFieldLists(sourceConfigs);
this.handleUpdatedFields(addedFields, removedFields);
this.updateData();
}
if (refreshMessage.refreshView) {
this.refreshView();
}
},
);
if (!this.previewMode) {
this.resizeSub = this.resizeService.resizeSubject.subscribe(
info => {
if (
info.gridsterItem.id ===
this.dataExplorerWidget.elementId
) {
this.onResize(
this.gridsterItemComponent.width - this.widthOffset,
this.gridsterItemComponent.height -
this.heightOffset,
);
}
},
);
}
this.timeSelectionSub =
this.timeSelectionService.timeSelectionChangeSubject.subscribe(
ts => {
if (ts) {
this.timeSettings = ts;
} else {
this.timeSelectionService.updateTimeSettings(
this.timeSettings,
new Date(),
);
}
this.updateData();
},
);
this.updateData();
this.onResize(
this.gridsterItemComponent.width - this.widthOffset,
this.gridsterItemComponent.height - this.heightOffset,
);
}
public cleanupSubscriptions(): void {
this.widgetConfigurationSub?.unsubscribe();
this.resizeSub?.unsubscribe();
this.timeSelectionSub?.unsubscribe();
this.requestQueue$?.unsubscribe();
}
public setShownComponents(
showNoDataInDateRange: boolean,
showData: boolean,
showIsLoadingData: boolean,
showTooMuchData: boolean = false,
) {
this.showNoDataInDateRange = showNoDataInDateRange;
this.showData = showData;
this.showIsLoadingData = showIsLoadingData;
this.showTooMuchData = showTooMuchData;
}
public updateData(includeTooMuchEventsParameter: boolean = true) {
this.beforeDataFetched();
this.loadData(includeTooMuchEventsParameter);
}
private loadData(includeTooMuchEventsParameter: boolean) {
let observables: Observable<SpQueryResult>[];
if (
includeTooMuchEventsParameter &&
!this.dataExplorerWidget.dataConfig.ignoreTooMuchDataWarning
) {
observables =
this.dataViewQueryGeneratorService.generateObservables(
this.timeSettings.startTime,
this.timeSettings.endTime,
this.dataExplorerWidget
.dataConfig as DataExplorerDataConfig,
BaseDataExplorerWidgetDirective.TOO_MUCH_DATA_PARAMETER,
);
} else {
observables =
this.dataViewQueryGeneratorService.generateObservables(
this.timeSettings.startTime,
this.timeSettings.endTime,
this.dataExplorerWidget
.dataConfig as DataExplorerDataConfig,
);
}
this.timerCallback.emit(true);
this.requestQueue$.next(observables);
}
validateReceivedData(spQueryResults: SpQueryResult[]) {
const spQueryResult = spQueryResults[0];
if (spQueryResult.total === 0) {
this.setShownComponents(true, false, false, false);
} else if (spQueryResult['spQueryStatus'] === 'TOO_MUCH_DATA') {
this.amountOfTooMuchEvents = spQueryResult.total;
this.setShownComponents(false, false, false, true);
} else {
this.onDataReceived(spQueryResults);
}
}
loadDataWithTooManyEvents() {
this.updateData(false);
}
getColumnIndex(field: DataExplorerField, data: SpQueryResult) {
return data.headers.indexOf(field.fullDbName);
}
public abstract refreshView(): void;
public abstract beforeDataFetched(): void;
public abstract onDataReceived(spQueryResult: SpQueryResult[]): void;
public abstract onResize(width: number, height: number): void;
protected abstract handleUpdatedFields(
addedFields: DataExplorerField[],
removedFields: DataExplorerField[],
): void;
}