From 19cdd22432f33a6b7320b65a4c659e7919d3bc5d Mon Sep 17 00:00:00 2001 From: Marcelfrueh <78954450+Marcelfrueh@users.noreply.github.com> Date: Sat, 14 Sep 2024 09:03:17 +0200 Subject: [PATCH] Add status widget to data explorer (#3226) --- .../status-widget-config.component.html | 158 +++++++++++++++++ .../status-widget-config.component.scss | 38 ++++ .../config/status-widget-config.component.ts | 91 ++++++++++ .../status/model/status-widget.model.ts | 37 ++++ .../status/status-widget.component.html | 60 +++++++ .../status/status-widget.component.scss | 57 ++++++ .../widgets/status/status-widget.component.ts | 164 ++++++++++++++++++ .../traffic-light-widget.component.ts | 25 +-- .../app/data-explorer/data-explorer.module.ts | 4 + .../registry/data-explorer-widget-registry.ts | 8 + 10 files changed, 632 insertions(+), 10 deletions(-) create mode 100644 ui/src/app/data-explorer/components/widgets/status/config/status-widget-config.component.html create mode 100644 ui/src/app/data-explorer/components/widgets/status/config/status-widget-config.component.scss create mode 100644 ui/src/app/data-explorer/components/widgets/status/config/status-widget-config.component.ts create mode 100644 ui/src/app/data-explorer/components/widgets/status/model/status-widget.model.ts create mode 100644 ui/src/app/data-explorer/components/widgets/status/status-widget.component.html create mode 100644 ui/src/app/data-explorer/components/widgets/status/status-widget.component.scss create mode 100644 ui/src/app/data-explorer/components/widgets/status/status-widget.component.ts diff --git a/ui/src/app/data-explorer/components/widgets/status/config/status-widget-config.component.html b/ui/src/app/data-explorer/components/widgets/status/config/status-widget-config.component.html new file mode 100644 index 0000000000..518f3fc085 --- /dev/null +++ b/ui/src/app/data-explorer/components/widgets/status/config/status-widget-config.component.html @@ -0,0 +1,158 @@ + + + + +
+
+ Select Value Type + + Numeric Value + Boolean Value + +
+ +
+
+ Interval [sec] + + + +
+
+ Show Last Seen Timestamp + + +
+
+ +
+
+ Field + +
+ +
+ Select Mapping + + + True + + False + + + True + + False + + +
+
+
+
+
diff --git a/ui/src/app/data-explorer/components/widgets/status/config/status-widget-config.component.scss b/ui/src/app/data-explorer/components/widgets/status/config/status-widget-config.component.scss new file mode 100644 index 0000000000..c3105ba0e4 --- /dev/null +++ b/ui/src/app/data-explorer/components/widgets/status/config/status-widget-config.component.scss @@ -0,0 +1,38 @@ +/* + * 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. + */ + +.color-box { + display: inline-block; + width: 12px; + height: 12px; + margin-right: 5px; + border-radius: 50%; +} +.spacing { + margin-left: 15px; +} +.green-box { + background-color: green; +} + +.red-box { + background-color: red; +} + +.checkbox-container { + margin-top: 20px; +} diff --git a/ui/src/app/data-explorer/components/widgets/status/config/status-widget-config.component.ts b/ui/src/app/data-explorer/components/widgets/status/config/status-widget-config.component.ts new file mode 100644 index 0000000000..f671616d16 --- /dev/null +++ b/ui/src/app/data-explorer/components/widgets/status/config/status-widget-config.component.ts @@ -0,0 +1,91 @@ +/* + * 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 { Component } from '@angular/core'; +import { BaseWidgetConfig } from '../../base/base-widget-config'; +import { WidgetConfigurationService } from '../../../../services/widget-configuration.service'; +import { + StatusWidgetModel, + StatusVisConfig, +} from '../model/status-widget.model'; +import { DataExplorerFieldProviderService } from '../../../../services/data-explorer-field-provider-service'; +import { DataExplorerField } from '@streampipes/platform-services'; + +@Component({ + selector: 'sp-data-explorer-status-widget-config', + templateUrl: './status-widget-config.component.html', + styleUrls: ['./status-widget-config.component.scss'], +}) +export class StatusWidgetConfigComponent extends BaseWidgetConfig< + StatusWidgetModel, + StatusVisConfig +> { + constructor( + widgetConfigurationService: WidgetConfigurationService, + fieldService: DataExplorerFieldProviderService, + ) { + super(widgetConfigurationService, fieldService); + } + + selectDataType(selectedDataType: string): void { + this.currentlyConfiguredWidget.visualizationConfig.selectedDataType = + selectedDataType; + this.triggerViewRefresh(); + } + + selectInterval(selectedInterval: number): void { + this.currentlyConfiguredWidget.visualizationConfig.selectedInterval = + selectedInterval; + this.triggerViewRefresh(); + } + + showLastSeen(selectedLastSeen: boolean): void { + this.currentlyConfiguredWidget.visualizationConfig.showLastSeen = + selectedLastSeen; + this.triggerViewRefresh(); + } + + selectBooleanFieldToObserve( + selectedBooleanFieldToObserve: DataExplorerField, + ): void { + this.currentlyConfiguredWidget.visualizationConfig.selectedBooleanFieldToObserve = + selectedBooleanFieldToObserve; + this.triggerViewRefresh(); + } + + selectMappingGreenTrue(selectedMappingGreenTrue: boolean): void { + this.currentlyConfiguredWidget.visualizationConfig.selectedMappingGreenTrue = + selectedMappingGreenTrue; + this.triggerViewRefresh(); + } + + protected applyWidgetConfig(config: StatusVisConfig): void { + config.selectedBooleanFieldToObserve = + this.fieldService.getSelectedField( + config.selectedBooleanFieldToObserve, + this.fieldProvider.allFields, + () => this.fieldProvider.allFields[0], + ); + this.currentlyConfiguredWidget.visualizationConfig.selectedInterval ??= 5; + this.currentlyConfiguredWidget.visualizationConfig.selectedMappingGreenTrue ??= + true; + } + protected requiredFieldsForChartPresent(): boolean { + return true; + } +} diff --git a/ui/src/app/data-explorer/components/widgets/status/model/status-widget.model.ts b/ui/src/app/data-explorer/components/widgets/status/model/status-widget.model.ts new file mode 100644 index 0000000000..73bb53e69d --- /dev/null +++ b/ui/src/app/data-explorer/components/widgets/status/model/status-widget.model.ts @@ -0,0 +1,37 @@ +/* + * 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 { + DataExplorerDataConfig, + DataExplorerWidgetModel, + DataExplorerField, +} from '@streampipes/platform-services'; +import { DataExplorerVisConfig } from '../../../../models/dataview-dashboard.model'; + +export interface StatusVisConfig extends DataExplorerVisConfig { + selectedDataType: string; + selectedInterval: number; + showLastSeen: boolean; + selectedBooleanFieldToObserve: DataExplorerField; + selectedMappingGreenTrue: boolean; +} + +export interface StatusWidgetModel extends DataExplorerWidgetModel { + dataConfig: DataExplorerDataConfig; + visualizationConfig: StatusVisConfig; +} diff --git a/ui/src/app/data-explorer/components/widgets/status/status-widget.component.html b/ui/src/app/data-explorer/components/widgets/status/status-widget.component.html new file mode 100644 index 0000000000..ad01626a9f --- /dev/null +++ b/ui/src/app/data-explorer/components/widgets/status/status-widget.component.html @@ -0,0 +1,60 @@ + + +
+ + + +
+
+
+
+
+
+
Last seen: {{ lastTimestamp | date: 'short' }}
+
+
diff --git a/ui/src/app/data-explorer/components/widgets/status/status-widget.component.scss b/ui/src/app/data-explorer/components/widgets/status/status-widget.component.scss new file mode 100644 index 0000000000..ac656663e5 --- /dev/null +++ b/ui/src/app/data-explorer/components/widgets/status/status-widget.component.scss @@ -0,0 +1,57 @@ +/* + * 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. + */ + +.h-100 { + height: 100%; +} + +.tl-container { + background-color: #222; + display: flex; + align-items: center; + flex-direction: column; + padding: 20px; + border-radius: 10px; +} + +.light { + border-radius: 50%; + background-color: #3d3535; + background: repeating-linear-gradient(#333, #443 5px); +} + +.light-red, +.light-green { + box-shadow: 0 0 40px; + z-index: 1; +} + +.light-red { + background: repeating-linear-gradient(#f00, #e00 5px); + box-shadow: 0 0 40px #f00; +} + +.light-green { + background: repeating-linear-gradient(#0d0, #0c0 5px); + box-shadow: 0 0 40px #0d0; +} + +.title-panel { + font-size: 20px; + height: 30px; + margin: 10px 0; +} diff --git a/ui/src/app/data-explorer/components/widgets/status/status-widget.component.ts b/ui/src/app/data-explorer/components/widgets/status/status-widget.component.ts new file mode 100644 index 0000000000..abb41e549c --- /dev/null +++ b/ui/src/app/data-explorer/components/widgets/status/status-widget.component.ts @@ -0,0 +1,164 @@ +/* + * 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 { Component, OnInit } from '@angular/core'; +import { BaseDataExplorerWidgetDirective } from '../base/base-data-explorer-widget.directive'; +import { StatusWidgetModel } from './model/status-widget.model'; +import { + DataExplorerField, + SpQueryResult, +} from '@streampipes/platform-services'; + +@Component({ + selector: 'sp-data-explorer-status-widget', + templateUrl: './status-widget.component.html', + styleUrls: ['./status-widget.component.scss'], +}) +export class StatusWidgetComponent + extends BaseDataExplorerWidgetDirective + implements OnInit +{ + width: number; + height: number; + + row: any[][]; + header: string[]; + fieldIndex = -1; + + containerWidth: number; + containerHeight: number; + + lightWidth: number; + lightHeight: number; + + selectedDataType: string; + selectedInterval: number; + showLastSeen: boolean; + selectedBooleanFieldToObserve: DataExplorerField; + selectedMappingGreenTrue: boolean; + + lastTimestamp = 0; + active: boolean; + + ngOnInit(): void { + super.ngOnInit(); + this.onResize( + this.gridsterItemComponent.width - this.widthOffset, + this.gridsterItemComponent.height - this.heightOffset, + ); + this.updateSettings(); + } + + updateSettings(): void { + this.selectedDataType = + this.dataExplorerWidget.visualizationConfig.selectedDataType; + this.selectedInterval = + this.dataExplorerWidget.visualizationConfig.selectedInterval; + this.showLastSeen = + this.dataExplorerWidget.visualizationConfig.showLastSeen; + this.selectedBooleanFieldToObserve = + this.dataExplorerWidget.visualizationConfig.selectedBooleanFieldToObserve; + this.selectedMappingGreenTrue = + this.dataExplorerWidget.visualizationConfig.selectedMappingGreenTrue; + } + + getNumericalStatus(): void { + const timestamp = new Date().getTime(); + this.active = + this.lastTimestamp >= timestamp - this.selectedInterval * 1000; + } + + getBooleanStatus(): void { + if (this.selectedMappingGreenTrue) { + this.active = this.row[0][this.fieldIndex]; + } else { + this.active = !this.row[0][this.fieldIndex]; + } + } + + booleanFieldToObserve(): void { + this.fieldIndex = this.header.indexOf( + this.selectedBooleanFieldToObserve.runtimeName, + ); + } + + refreshView(): void { + this.updateSettings(); + if (this.row !== undefined && this.row.length > 0) { + if (this.selectedDataType == 'boolean') { + this.getBooleanStatus(); + } else { + this.getNumericalStatus(); + } + } + } + + beforeDataFetched(): void { + this.setShownComponents(false, false, true, false); + } + + onDataReceived(spQueryResult: SpQueryResult[]): void { + if ( + spQueryResult.length > 0 && + spQueryResult[0].allDataSeries.length > 0 + ) { + this.header = spQueryResult[0].allDataSeries[0].headers; + this.row = spQueryResult[0].allDataSeries[0].rows; + this.lastTimestamp = spQueryResult[0].allDataSeries[0].rows[0][0]; + + if (this.selectedDataType == 'number') { + this.getNumericalStatus(); + } else if (this.selectedDataType == 'boolean') { + this.booleanFieldToObserve(); + this.getBooleanStatus(); + } + this.setShownComponents(false, true, false, false); + } else { + this.setShownComponents(true, false, false, false); + } + } + + onResize(width: number, heigth: number): void { + this.containerHeight = heigth * 0.3; + this.containerWidth = this.containerHeight; + this.lightWidth = this.containerHeight; + this.lightHeight = this.lightWidth; + } + + handleUpdatedFields( + addedFields: DataExplorerField[], + removedFields: DataExplorerField[], + ) { + const updatedFields = this.fieldUpdateService.updateFieldSelection( + [ + this.dataExplorerWidget.visualizationConfig + .selectedBooleanFieldToObserve, + ], + { + addedFields, + removedFields, + fieldProvider: this.fieldProvider, + }, + () => true, + ); + + this.selectedBooleanFieldToObserve = updatedFields[0]; + this.booleanFieldToObserve(); + this.refreshView(); + } +} diff --git a/ui/src/app/data-explorer/components/widgets/traffic-light/traffic-light-widget.component.ts b/ui/src/app/data-explorer/components/widgets/traffic-light/traffic-light-widget.component.ts index 80e3490ddc..99df13f09f 100644 --- a/ui/src/app/data-explorer/components/widgets/traffic-light/traffic-light-widget.component.ts +++ b/ui/src/app/data-explorer/components/widgets/traffic-light/traffic-light-widget.component.ts @@ -110,8 +110,6 @@ export class TrafficLightWidgetComponent (this.selectedWarningRange / 100) ); } else { - console.log(value); - return ( value <= this.selectedThreshold + @@ -125,13 +123,13 @@ export class TrafficLightWidgetComponent return !this.exceedsThreshold(value) && !this.isInWarningRange(value); } - public refreshView(): void { + refreshView(): void { this.updateSettings(); this.fieldToObserve(); this.getTrafficLightColor(); } - public beforeDataFetched(): void { + beforeDataFetched(): void { this.setShownComponents(false, false, true, false); } @@ -141,12 +139,19 @@ export class TrafficLightWidgetComponent ); } - public onDataReceived(spQueryResult: SpQueryResult[]): void { - this.header = spQueryResult[0].allDataSeries[0].headers; - this.row = spQueryResult[0].allDataSeries[0].rows; - this.fieldToObserve(); - this.getTrafficLightColor(); - this.setShownComponents(false, true, false, false); + onDataReceived(spQueryResult: SpQueryResult[]): void { + if ( + spQueryResult.length > 0 && + spQueryResult[0].allDataSeries.length > 0 + ) { + this.header = spQueryResult[0].allDataSeries[0].headers; + this.row = spQueryResult[0].allDataSeries[0].rows; + this.fieldToObserve(); + this.getTrafficLightColor(); + this.setShownComponents(false, true, false, false); + } else { + this.setShownComponents(true, false, false, false); + } } onResize(width: number, heigth: number) { diff --git a/ui/src/app/data-explorer/data-explorer.module.ts b/ui/src/app/data-explorer/data-explorer.module.ts index 09b8f33e75..c3070b45b5 100644 --- a/ui/src/app/data-explorer/data-explorer.module.ts +++ b/ui/src/app/data-explorer/data-explorer.module.ts @@ -48,6 +48,8 @@ import { DataExplorerDashboardWidgetComponent } from './components/widget/data-e import { ImageWidgetComponent } from './components/widgets/image/image-widget.component'; import { TrafficLightWidgetComponent } from './components/widgets/traffic-light/traffic-light-widget.component'; import { TrafficLightWidgetConfigComponent } from './components/widgets/traffic-light/config/traffic-light-widget-config.component'; +import { StatusWidgetComponent } from './components/widgets/status/status-widget.component'; +import { StatusWidgetConfigComponent } from './components/widgets/status/config/status-widget-config.component'; import { TableWidgetComponent } from './components/widgets/table/table-widget.component'; import { AggregateConfigurationComponent } from './components/widgets/utils/aggregate-configuration/aggregate-configuration.component'; import { LoadDataSpinnerComponent } from './components/widgets/utils/load-data-spinner/load-data-spinner.component'; @@ -251,6 +253,8 @@ import { GaugeWidgetConfigComponent } from './components/widgets/gauge/config/ga TableWidgetConfigComponent, TrafficLightWidgetComponent, TrafficLightWidgetConfigComponent, + StatusWidgetComponent, + StatusWidgetConfigComponent, MapWidgetConfigComponent, MapWidgetComponent, HeatmapWidgetConfigComponent, diff --git a/ui/src/app/data-explorer/registry/data-explorer-widget-registry.ts b/ui/src/app/data-explorer/registry/data-explorer-widget-registry.ts index c2d486bfd6..efb221b3b6 100644 --- a/ui/src/app/data-explorer/registry/data-explorer-widget-registry.ts +++ b/ui/src/app/data-explorer/registry/data-explorer-widget-registry.ts @@ -54,6 +54,8 @@ import { GaugeWidgetConfigComponent } from '../components/widgets/gauge/config/g import { GaugeWidgetModel } from '../components/widgets/gauge/model/gauge-widget.model'; import { TrafficLightWidgetConfigComponent } from '../components/widgets/traffic-light/config/traffic-light-widget-config.component'; import { TrafficLightWidgetComponent } from '../components/widgets/traffic-light/traffic-light-widget.component'; +import { StatusWidgetConfigComponent } from '../components/widgets/status/config/status-widget-config.component'; +import { StatusWidgetComponent } from '../components/widgets/status/status-widget.component'; @Injectable({ providedIn: 'root' }) export class DataExplorerWidgetRegistry { @@ -92,6 +94,12 @@ export class DataExplorerWidgetRegistry { widgetConfigurationComponent: TrafficLightWidgetConfigComponent, widgetComponent: TrafficLightWidgetComponent, }, + { + id: 'status', + label: 'Status', + widgetConfigurationComponent: StatusWidgetConfigComponent, + widgetComponent: StatusWidgetComponent, + }, { id: 'map', label: 'Map',