diff --git a/streampipes-data-explorer/src/main/java/org/apache/streampipes/dataexplorer/param/model/WhereClauseParams.java b/streampipes-data-explorer/src/main/java/org/apache/streampipes/dataexplorer/param/model/WhereClauseParams.java index 3381983268..01613487a5 100644 --- a/streampipes-data-explorer/src/main/java/org/apache/streampipes/dataexplorer/param/model/WhereClauseParams.java +++ b/streampipes-data-explorer/src/main/java/org/apache/streampipes/dataexplorer/param/model/WhereClauseParams.java @@ -34,17 +34,21 @@ public class WhereClauseParams implements IQueryStatement { private final List filterConditions; - private WhereClauseParams(Long startTime, - Long endTime, - String whereConditions) { + private WhereClauseParams( + Long startTime, + Long endTime, + String whereConditions + ) { this(startTime, endTime); if (whereConditions != null) { buildConditions(whereConditions); } } - private WhereClauseParams(Long startTime, - Long endTime) { + private WhereClauseParams( + Long startTime, + Long endTime + ) { this.filterConditions = new ArrayList<>(); this.buildTimeConditions(startTime, endTime); } @@ -56,8 +60,10 @@ private WhereClauseParams(String whereConditions) { } } - public static WhereClauseParams from(Long startTime, - Long endTime) { + public static WhereClauseParams from( + Long startTime, + Long endTime + ) { return new WhereClauseParams(startTime, endTime); } @@ -65,14 +71,18 @@ public static WhereClauseParams from(String whereConditions) { return new WhereClauseParams(whereConditions); } - public static WhereClauseParams from(Long startTime, - Long endTime, - String whereConditions) { + public static WhereClauseParams from( + Long startTime, + Long endTime, + String whereConditions + ) { return new WhereClauseParams(startTime, endTime, whereConditions); } - private void buildTimeConditions(Long startTime, - Long endTime) { + private void buildTimeConditions( + Long startTime, + Long endTime + ) { if (startTime == null) { this.filterConditions.add(buildTimeBoundary(endTime, LT)); } else if (endTime == null) { @@ -98,7 +108,9 @@ private void buildConditions(String whereConditions) { } private Object returnCondition(String inputCondition) { - if (NumberUtils.isParsable(inputCondition)) { + if (isQuotedString(inputCondition)) { + return removeQuotes(inputCondition); + } else if (NumberUtils.isParsable(inputCondition)) { return Double.parseDouble(inputCondition); } else if (isBoolean(inputCondition)) { return Boolean.parseBoolean(inputCondition); @@ -107,6 +119,18 @@ private Object returnCondition(String inputCondition) { } } + private boolean isQuotedString(String input) { + if (input.startsWith("\"") && input.endsWith("\"")) { + String content = removeQuotes(input); + return NumberUtils.isParsable(content); + } + return false; + } + + private String removeQuotes(String input) { + return input.substring(1, input.length() - 1); + } + private boolean isBoolean(String input) { return "true".equalsIgnoreCase(input) || "false".equalsIgnoreCase(input); } diff --git a/ui/angular.json b/ui/angular.json index 17b7a423cc..384b9b88b4 100644 --- a/ui/angular.json +++ b/ui/angular.json @@ -189,7 +189,8 @@ }, "schematics": { "@schematics/angular:component": { - "style": "scss" + "style": "scss", + "standalone": false } }, "cli": { diff --git a/ui/cypress/fixtures/datalake/filterNumericalStringProperties.csv b/ui/cypress/fixtures/datalake/filterNumericalStringProperties.csv new file mode 100644 index 0000000000..3cf0902c26 --- /dev/null +++ b/ui/cypress/fixtures/datalake/filterNumericalStringProperties.csv @@ -0,0 +1,3 @@ +timestamp;dimensionKey;v1 +1737123058000;1.0;a +1737123059000;2.0;20 diff --git a/ui/cypress/support/utils/connect/ConnectUtils.ts b/ui/cypress/support/utils/connect/ConnectUtils.ts index 52d2ec6c88..40956ebffb 100644 --- a/ui/cypress/support/utils/connect/ConnectUtils.ts +++ b/ui/cypress/support/utils/connect/ConnectUtils.ts @@ -48,6 +48,8 @@ export class ConnectUtils { ConnectEventSchemaUtils.addTimestampProperty(); } + ConnectUtils.configureDimensionProperties(adapterConfiguration); + ConnectEventSchemaUtils.finishEventSchemaConfiguration(); ConnectUtils.startAdapter( @@ -66,6 +68,20 @@ export class ConnectUtils { ConnectUtils.configureAdapter(adapterConfiguration); + ConnectUtils.configureDimensionProperties(adapterConfiguration); + + if (adapterConfiguration.timestampProperty) { + ConnectEventSchemaUtils.markPropertyAsTimestamp( + adapterConfiguration.timestampProperty, + ); + } + + ConnectEventSchemaUtils.finishEventSchemaConfiguration(); + } + + private static configureDimensionProperties( + adapterConfiguration: AdapterInput, + ) { if (adapterConfiguration.dimensionProperties.length > 0) { adapterConfiguration.dimensionProperties.forEach( dimensionPropertyName => { @@ -75,14 +91,6 @@ export class ConnectUtils { }, ); } - - if (adapterConfiguration.timestampProperty) { - ConnectEventSchemaUtils.markPropertyAsTimestamp( - adapterConfiguration.timestampProperty, - ); - } - - ConnectEventSchemaUtils.finishEventSchemaConfiguration(); } public static addMachineDataSimulator( diff --git a/ui/cypress/support/utils/datalake/DataLakeUtils.ts b/ui/cypress/support/utils/datalake/DataLakeUtils.ts index 664c2ccd1c..e9b87103d5 100644 --- a/ui/cypress/support/utils/datalake/DataLakeUtils.ts +++ b/ui/cypress/support/utils/datalake/DataLakeUtils.ts @@ -69,7 +69,6 @@ export class DataLakeUtils { public static loadDataIntoDataLake( dataSet: string, - wait = true, format: 'csv' | 'json_array' = 'csv', ) { // Create adapter with dataset @@ -182,7 +181,9 @@ export class DataLakeUtils { } public static saveDataViewConfiguration() { - cy.dataCy('save-data-view-btn', { timeout: 10000 }).click(); + cy.dataCy('save-data-view-btn', { timeout: 10000 }).click({ + force: true, + }); } public static saveDashboardConfiguration() { @@ -279,6 +280,43 @@ export class DataLakeUtils { } } + /** + * This method validates that the defined filter options are available in the UI + * @param expectedFilterOptions + */ + public static validateFilterOptions( + expectedFilterOptions: ('=' | '<' | '<=' | '>=' | '>' | '!=')[], + ) { + cy.dataCy('design-panel-data-settings-filter-operator') + .click() + .dataCy('operator-', {}, true) + .should('have.length', expectedFilterOptions.length); + + expectedFilterOptions.forEach(option => { + const escapedOption = option.replace(/([=<>!])/g, '\\$1'); + cy.dataCy('operator-' + escapedOption).should('be.visible'); + }); + + cy.dataCy('design-panel-data-settings-filter-operator').click({ + force: true, + }); + } + + public static validateAutoCompleteOptions(options: string[]) { + cy.dataCy('design-panel-data-settings-filter-value') + .click({ force: true }) + .dataCy('autocomplete-value-', {}, true) + .should('have.length', options.length); + + options.forEach(option => { + cy.dataCy('autocomplete-value-' + option).should('be.visible'); + }); + + cy.dataCy('design-panel-data-settings-filter-value').click({ + force: true, + }); + } + /** * In the data set panel select all property fields */ @@ -310,7 +348,9 @@ export class DataLakeUtils { } public static dataConfigRemoveFilter() { - cy.dataCy('design-panel-data-settings-remove-filter').first().click(); + cy.dataCy('design-panel-data-settings-remove-filter') + .first() + .click({ force: true }); } public static clickGroupBy(propertyName: string) { diff --git a/ui/cypress/support/utils/datalake/DataLakeWidgetTableUtils.ts b/ui/cypress/support/utils/datalake/DataLakeWidgetTableUtils.ts index 733f8616a0..d09177c6ea 100644 --- a/ui/cypress/support/utils/datalake/DataLakeWidgetTableUtils.ts +++ b/ui/cypress/support/utils/datalake/DataLakeWidgetTableUtils.ts @@ -17,13 +17,17 @@ */ export class DataLakeWidgetTableUtils { + public static dataExplorerTableRowTimestamp() { + return cy.dataCy('data-explorer-table-row-timestamp', { + timeout: 10000, + }); + } + /** * Checks how many rows are visible within the table widget in the data explorer * @param amount of expected rows */ - public static checkRows(amount: number) { - cy.dataCy('data-explorer-table-row-timestamp', { - timeout: 10000, - }).should('have.length', amount); + public static checkAmountOfRows(amount: number) { + this.dataExplorerTableRowTimestamp().should('have.length', amount); } } diff --git a/ui/cypress/tests/datalake/deleteViewAndDashboard.spec.ts b/ui/cypress/tests/datalake/deleteViewAndDashboard.spec.ts index fb4526f187..620d5158dd 100644 --- a/ui/cypress/tests/datalake/deleteViewAndDashboard.spec.ts +++ b/ui/cypress/tests/datalake/deleteViewAndDashboard.spec.ts @@ -20,7 +20,7 @@ import { DataLakeUtils } from '../../support/utils/datalake/DataLakeUtils'; describe('Test Deletion of Data View and Dashboard', () => { beforeEach('Setup Test', () => { cy.initStreamPipesTest(); - DataLakeUtils.loadDataIntoDataLake('datalake/sample.csv', false); + DataLakeUtils.loadDataIntoDataLake('datalake/sample.csv'); }); it('Perform Test', () => { diff --git a/ui/cypress/tests/datalake/deleteWidget.ts b/ui/cypress/tests/datalake/deleteWidget.ts index ee5de09d95..a51db31f9a 100644 --- a/ui/cypress/tests/datalake/deleteWidget.ts +++ b/ui/cypress/tests/datalake/deleteWidget.ts @@ -20,7 +20,7 @@ import { DataLakeUtils } from '../../support/utils/datalake/DataLakeUtils'; describe('Test Table View in Data Explorer', () => { beforeEach('Setup Test', () => { cy.initStreamPipesTest(); - DataLakeUtils.loadDataIntoDataLake('datalake/sample.csv', false); + DataLakeUtils.loadDataIntoDataLake('datalake/sample.csv'); }); it('Perform Test', () => { diff --git a/ui/cypress/tests/datalake/filterNumericalStringProperties.spec.ts b/ui/cypress/tests/datalake/filterNumericalStringProperties.spec.ts new file mode 100644 index 0000000000..003e5c23c0 --- /dev/null +++ b/ui/cypress/tests/datalake/filterNumericalStringProperties.spec.ts @@ -0,0 +1,89 @@ +/* + * 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 { DataLakeUtils } from '../../support/utils/datalake/DataLakeUtils'; +import { DataLakeWidgetTableUtils } from '../../support/utils/datalake/DataLakeWidgetTableUtils'; +import { DataLakeFilterConfig } from '../../support/model/DataLakeFilterConfig'; +import { AdapterBuilder } from '../../support/builder/AdapterBuilder'; +import { ConnectBtns } from '../../support/utils/connect/ConnectBtns'; +import { ConnectUtils } from '../../support/utils/connect/ConnectUtils'; +import { FileManagementUtils } from '../../support/utils/FileManagementUtils'; + +describe('Validate that filter works for numerical dimension property', () => { + beforeEach('Setup Test', () => { + cy.initStreamPipesTest(); + + FileManagementUtils.addFile( + 'datalake/filterNumericalStringProperties.csv', + ); + const adapterInput = AdapterBuilder.create('File_Stream') + .setName('Test Adapter') + .setTimestampProperty('timestamp') + .addDimensionProperty('dimensionKey') + .setStoreInDataLake() + .setFormat('csv') + .addFormatInput('input', ConnectBtns.csvDelimiter(), ';') + .addFormatInput('checkbox', ConnectBtns.csvHeader(), 'check') + .build(); + ConnectUtils.testAdapter(adapterInput); + }); + + it('Perform Test', () => { + DataLakeUtils.goToDatalake(); + DataLakeUtils.createAndEditDataView(); + + // create table widget and select time range + const startDate = new Date(1737029442000); + const endDate = new Date(1742220659000); + + DataLakeUtils.clickOrderBy('descending'); + + DataLakeUtils.openVisualizationConfig(); + DataLakeUtils.selectVisualizationType('Table'); + DataLakeUtils.selectTimeRange(startDate, endDate); + cy.wait(1000); + + // validate data in table + DataLakeWidgetTableUtils.checkAmountOfRows(2); + + // select filter for tag + DataLakeUtils.selectDataConfig(); + var filterConfig = new DataLakeFilterConfig('dimensionKey', '1.0', '='); + DataLakeUtils.dataConfigAddFilter(filterConfig); + + // validate data in table is filtered + DataLakeWidgetTableUtils.checkAmountOfRows(1); + + // remove filter + DataLakeUtils.dataConfigRemoveFilter(); + + DataLakeUtils.selectDataConfig(); + + filterConfig = new DataLakeFilterConfig('v1', '20', '='); + DataLakeUtils.dataConfigAddFilter(filterConfig); + + // validate data in table is filtered + DataLakeWidgetTableUtils.checkAmountOfRows(1); + + // remove filter + DataLakeUtils.dataConfigRemoveFilter(); + + // validate data again + DataLakeWidgetTableUtils.checkAmountOfRows(2); + }); +}); diff --git a/ui/cypress/tests/datalake/missingDataInDataLake.spec.ts b/ui/cypress/tests/datalake/missingDataInDataLake.spec.ts index f547c61448..77e57710b1 100644 --- a/ui/cypress/tests/datalake/missingDataInDataLake.spec.ts +++ b/ui/cypress/tests/datalake/missingDataInDataLake.spec.ts @@ -34,13 +34,13 @@ describe('Test missing properties in data lake', () => { it('Test table with missing properties', () => { DataLakeUtils.addDataViewAndTableWidget(dataViewName, 'Persist'); - DataLakeWidgetTableUtils.checkRows(5); + DataLakeWidgetTableUtils.checkAmountOfRows(5); DataLakeUtils.selectDataConfig(); cy.dataCy('data-explorer-ignore-missing-values-checkbox') .children() .click(); - DataLakeWidgetTableUtils.checkRows(3); + DataLakeWidgetTableUtils.checkAmountOfRows(3); }); }); diff --git a/ui/cypress/tests/datalake/timeOrderDataView.spec.ts b/ui/cypress/tests/datalake/timeOrderDataView.spec.ts index 1b1ee0f222..17a19ae70f 100644 --- a/ui/cypress/tests/datalake/timeOrderDataView.spec.ts +++ b/ui/cypress/tests/datalake/timeOrderDataView.spec.ts @@ -22,7 +22,7 @@ import { DataLakeBtns } from '../../support/utils/datalake/DataLakeBtns'; describe('Test Time Order in Data Explorer', () => { beforeEach('Setup Test', () => { cy.initStreamPipesTest(); - DataLakeUtils.loadDataIntoDataLake('datalake/sample.csv', false); + DataLakeUtils.loadDataIntoDataLake('datalake/sample.csv'); DataLakeUtils.goToDatalake(); DataLakeUtils.createAndEditDataView(); }); diff --git a/ui/cypress/tests/datalake/timeRangeSelectors.spec.ts b/ui/cypress/tests/datalake/timeRangeSelectors.spec.ts index 71f2356ed9..3688e7f789 100644 --- a/ui/cypress/tests/datalake/timeRangeSelectors.spec.ts +++ b/ui/cypress/tests/datalake/timeRangeSelectors.spec.ts @@ -46,7 +46,7 @@ describe('Test Time Range Selectors in Data Explorer', () => { before('Setup Tests', () => { cy.initStreamPipesTest(); - DataLakeUtils.loadDataIntoDataLake('datalake/sample.csv', false); + DataLakeUtils.loadDataIntoDataLake('datalake/sample.csv'); }); it('Perform Test', () => { diff --git a/ui/cypress/tests/datalake/widgetDataConfiguration.smoke.spec.ts b/ui/cypress/tests/datalake/widgetDataConfiguration.smoke.spec.ts index 911cdc6b5a..3b9eb2f858 100644 --- a/ui/cypress/tests/datalake/widgetDataConfiguration.smoke.spec.ts +++ b/ui/cypress/tests/datalake/widgetDataConfiguration.smoke.spec.ts @@ -33,7 +33,7 @@ describe('Test Table View in Data Explorer', () => { DataLakeUtils.addDataViewAndTableWidget('NewWidget', 'Persist'); // Validate that X lines are available - DataLakeWidgetTableUtils.checkRows(10); + DataLakeWidgetTableUtils.checkAmountOfRows(10); // Go back to data configuration DataLakeUtils.selectDataConfig(); @@ -44,26 +44,30 @@ describe('Test Table View in Data Explorer', () => { // Test number let filterConfig = new DataLakeFilterConfig('randomnumber', '22', '='); DataLakeUtils.dataConfigAddFilter(filterConfig); - DataLakeWidgetTableUtils.checkRows(2); + DataLakeWidgetTableUtils.checkAmountOfRows(2); + DataLakeUtils.validateFilterOptions(['=', '<', '<=', '>=', '>', '!=']); DataLakeUtils.dataConfigRemoveFilter(); - DataLakeWidgetTableUtils.checkRows(10); + DataLakeWidgetTableUtils.checkAmountOfRows(10); // Test number greater then filterConfig = new DataLakeFilterConfig('randomnumber', '50', '>'); DataLakeUtils.dataConfigAddFilter(filterConfig); - DataLakeWidgetTableUtils.checkRows(5); + DataLakeWidgetTableUtils.checkAmountOfRows(5); + DataLakeUtils.validateFilterOptions(['=', '<', '<=', '>=', '>', '!=']); DataLakeUtils.dataConfigRemoveFilter(); // Test number smaller then filterConfig = new DataLakeFilterConfig('randomnumber', '50', '<'); DataLakeUtils.dataConfigAddFilter(filterConfig); - DataLakeWidgetTableUtils.checkRows(5); + DataLakeWidgetTableUtils.checkAmountOfRows(5); DataLakeUtils.dataConfigRemoveFilter(); // Test boolean filterConfig = new DataLakeFilterConfig('randombool', 'true', '='); DataLakeUtils.dataConfigAddFilter(filterConfig); - DataLakeWidgetTableUtils.checkRows(6); + DataLakeWidgetTableUtils.checkAmountOfRows(6); + DataLakeUtils.validateFilterOptions(['=', '!=']); + DataLakeUtils.validateAutoCompleteOptions(['true', 'false']); DataLakeUtils.dataConfigRemoveFilter(); // Test string & if filter is persisted correctly @@ -71,10 +75,12 @@ describe('Test Table View in Data Explorer', () => { DataLakeUtils.checkIfFilterIsSet(0); DataLakeUtils.dataConfigAddFilter(filterConfig); DataLakeUtils.checkIfFilterIsSet(1); - DataLakeWidgetTableUtils.checkRows(4); + DataLakeWidgetTableUtils.checkAmountOfRows(4); + DataLakeUtils.validateFilterOptions(['=', '!=']); + DataLakeUtils.validateAutoCompleteOptions(['a', 'b', 'c']); DataLakeUtils.saveAndReEditWidget('NewWidget'); DataLakeUtils.checkIfFilterIsSet(1); - DataLakeWidgetTableUtils.checkRows(4); + DataLakeWidgetTableUtils.checkAmountOfRows(4); DataLakeUtils.dataConfigRemoveFilter(); /** @@ -89,7 +95,7 @@ describe('Test Table View in Data Explorer', () => { cy.dataCy('data-explorer-table-row-randomtext', { timeout: 10000 }) .first({ timeout: 10000 }) .contains('c', { timeout: 10000 }); - DataLakeWidgetTableUtils.checkRows(10); + DataLakeWidgetTableUtils.checkAmountOfRows(10); DataLakeUtils.saveAndReEditWidget('NewWidget'); cy.dataCy('data-explorer-group-by-randomtext') .find('input') diff --git a/ui/cypress/tests/datalake/widgets/table.spec.ts b/ui/cypress/tests/datalake/widgets/table.spec.ts index 4648835e77..62c820f7b9 100644 --- a/ui/cypress/tests/datalake/widgets/table.spec.ts +++ b/ui/cypress/tests/datalake/widgets/table.spec.ts @@ -17,6 +17,7 @@ */ import { DataLakeUtils } from '../../../support/utils/datalake/DataLakeUtils'; +import { DataLakeWidgetTableUtils } from '../../../support/utils/datalake/DataLakeWidgetTableUtils'; describe('Test Table View in Data Explorer', () => { beforeEach('Setup Test', () => { @@ -27,8 +28,6 @@ describe('Test Table View in Data Explorer', () => { DataLakeUtils.addDataViewAndWidget('view', 'Persist', 'Table'); // Check if table is displayed correctly - cy.dataCy('data-explorer-table-row-timestamp', { - timeout: 10000, - }).should('have.length', 10); + DataLakeWidgetTableUtils.checkAmountOfRows(10); }); }); diff --git a/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel-row/escape-number-filter.service.ts b/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel-row/escape-number-filter.service.ts new file mode 100644 index 0000000000..78042aff72 --- /dev/null +++ b/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel-row/escape-number-filter.service.ts @@ -0,0 +1,62 @@ +/* + * 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 { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root', +}) +export class EscapeNumberFilterService { + // Method to remove enclosing double quotes + removeEnclosingQuotes(value: string): string { + return value?.replace(/^"|"$/g, ''); + } + + // Updates the filter value based on the field type and input value. + // Ensures that numeric values are wrapped in double quotes to prevent parsing issues on the backend. + // This check is necessary because the filter value is transmitted as a triple [field, operator, value], + // which causes the type information to be lost. Once the API is changed to retain type information, + // this service can be removed. + escapeIfNumberValue( + filter: any, + value: string, + tagValues: Map, + ): string { + const isTagValueKey = this.checkIfFilterOnTagValue(filter, tagValues); + const isNumericValue = this.checkIfNumericalValue(value); + + if (isNumericValue && (isTagValueKey || !filter?.field?.numeric)) { + return `"${value}"`; + } else { + return value; + } + } + + private checkIfFilterOnTagValue( + filter: any, + tagValues: Map, + ): boolean { + return ( + tagValues.has(filter?.field?.runtimeName) && + tagValues.get(filter.field.runtimeName)?.length > 0 + ); + } + + private checkIfNumericalValue(value: string): boolean { + return !isNaN(Number(value)); + } +} diff --git a/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel-row/filter-selection-panel-row.component.html b/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel-row/filter-selection-panel-row.component.html new file mode 100644 index 0000000000..4401ad9ccf --- /dev/null +++ b/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel-row/filter-selection-panel-row.component.html @@ -0,0 +1,58 @@ + + +
+
+ + + + + + + @if (!filter.field || !tagValues.has(filter.field.runtimeName)) { + + + } @else { + + + } + + +
+
diff --git a/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel-row/filter-selection-panel-row.component.ts b/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel-row/filter-selection-panel-row.component.ts new file mode 100644 index 0000000000..9e7cc75810 --- /dev/null +++ b/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel-row/filter-selection-panel-row.component.ts @@ -0,0 +1,51 @@ +/* + * 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, Input, Output, EventEmitter } from '@angular/core'; +import { FieldConfig, SelectedFilter } from '@streampipes/platform-services'; + +@Component({ + selector: 'sp-filter-selection-panel-row', + templateUrl: './filter-selection-panel-row.component.html', +}) +export class FilterSelectionPanelRowComponent { + @Input() + public filter: SelectedFilter; + + @Input() + public possibleFields: FieldConfig[]; + + @Input() + public tagValues: Map; + + @Output() + public update = new EventEmitter(); + + @Output() + public removeFilter = new EventEmitter(); + + constructor() {} + + updateParentComponent() { + this.update.emit(); + } + + remove() { + this.removeFilter.emit(); + } +} diff --git a/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel-row/panel-row-operation-selection/filter-selection-panel-row-operation-selection.component.html b/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel-row/panel-row-operation-selection/filter-selection-panel-row-operation-selection.component.html new file mode 100644 index 0000000000..3af11dd781 --- /dev/null +++ b/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel-row/panel-row-operation-selection/filter-selection-panel-row-operation-selection.component.html @@ -0,0 +1,46 @@ + + + + + = + + @if (filter.field && filter.field['numeric']) { + + < + + + <= + + + >= + + + > + + } + + != + + + diff --git a/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel-row/panel-row-operation-selection/filter-selection-panel-row-operation-selection.component.ts b/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel-row/panel-row-operation-selection/filter-selection-panel-row-operation-selection.component.ts new file mode 100644 index 0000000000..1ba9c766a1 --- /dev/null +++ b/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel-row/panel-row-operation-selection/filter-selection-panel-row-operation-selection.component.ts @@ -0,0 +1,36 @@ +/* + * 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, EventEmitter, Input, Output } from '@angular/core'; +import { SelectedFilter } from '@streampipes/platform-services'; + +@Component({ + selector: 'sp-filter-selection-panel-row-operation-selection', + templateUrl: + './filter-selection-panel-row-operation-selection.component.html', +}) +export class FilterSelectionPanelRowOperationSelectionComponent { + @Input() + public filter: SelectedFilter; + + @Output() + public update = new EventEmitter(); + + updateParentComponent() { + this.update.emit(); + } +} diff --git a/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel-row/panel-row-property-selection/filter-selection-panel-row-property-selection.component.html b/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel-row/panel-row-property-selection/filter-selection-panel-row-property-selection.component.html new file mode 100644 index 0000000000..23c7ba5c26 --- /dev/null +++ b/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel-row/panel-row-property-selection/filter-selection-panel-row-property-selection.component.html @@ -0,0 +1,31 @@ + + + Field + + @for (field of possibleFields; track field) { + {{ field.runtimeName }} + } + + diff --git a/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel-row/panel-row-property-selection/filter-selection-panel-row-property-selection.component.ts b/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel-row/panel-row-property-selection/filter-selection-panel-row-property-selection.component.ts new file mode 100644 index 0000000000..f1f874cfb3 --- /dev/null +++ b/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel-row/panel-row-property-selection/filter-selection-panel-row-property-selection.component.ts @@ -0,0 +1,43 @@ +/* + * 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, EventEmitter, Input, Output } from '@angular/core'; +import { FieldConfig, SelectedFilter } from '@streampipes/platform-services'; + +@Component({ + selector: 'sp-filter-selection-panel-row-property-selection', + templateUrl: + './filter-selection-panel-row-property-selection.component.html', +}) +export class FilterSelectionPanelRowPropertySelectionComponent { + @Input() + public filter: SelectedFilter; + + @Input() + public possibleFields: FieldConfig[]; + + @Output() + public update = new EventEmitter(); + + updateParentComponent() { + this.update.emit(); + } + + compare(available: FieldConfig, selected: FieldConfig) { + return available.runtimeName === selected.runtimeName; + } +} diff --git a/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel-row/panel-row-value-input-autocomplete/filter-selection-panel-row-value-autocomplete.component.html b/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel-row/panel-row-value-input-autocomplete/filter-selection-panel-row-value-autocomplete.component.html new file mode 100644 index 0000000000..81ff27305b --- /dev/null +++ b/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel-row/panel-row-value-input-autocomplete/filter-selection-panel-row-value-autocomplete.component.html @@ -0,0 +1,42 @@ + + + + + @for (option of tagValues.get(filter.field.runtimeName); track option) { + + {{ option }} + + } + + diff --git a/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel-row/panel-row-value-input-autocomplete/filter-selection-panel-row-value-autocomplete.component.ts b/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel-row/panel-row-value-input-autocomplete/filter-selection-panel-row-value-autocomplete.component.ts new file mode 100644 index 0000000000..aef2842318 --- /dev/null +++ b/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel-row/panel-row-value-input-autocomplete/filter-selection-panel-row-value-autocomplete.component.ts @@ -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. + * + */ +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { SelectedFilter } from '@streampipes/platform-services'; +import { EscapeNumberFilterService } from '../escape-number-filter.service'; + +@Component({ + selector: 'sp-filter-selection-panel-row-value-autocomplete', + templateUrl: + './filter-selection-panel-row-value-autocomplete.component.html', +}) +export class FilterSelectionPanelRowValueAutocompleteComponent + implements OnInit +{ + @Input() + public filter: SelectedFilter; + + @Input() + public tagValues: Map; + + @Output() + public update = new EventEmitter(); + + public value: string; + + constructor(private escapeNumberFilterService: EscapeNumberFilterService) {} + + ngOnInit(): void { + this.value = this.escapeNumberFilterService.removeEnclosingQuotes( + this.filter.value, + ); + } + + updateParentComponent() { + this.filter.value = this.escapeNumberFilterService.escapeIfNumberValue( + this.filter, + this.value, + this.tagValues, + ); + this.update.emit(); + } +} diff --git a/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel-row/panel-row-value-input/filter-selection-panel-row-value-input.component.html b/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel-row/panel-row-value-input/filter-selection-panel-row-value-input.component.html new file mode 100644 index 0000000000..8f7c507b79 --- /dev/null +++ b/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel-row/panel-row-value-input/filter-selection-panel-row-value-input.component.html @@ -0,0 +1,28 @@ + + + + diff --git a/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel-row/panel-row-value-input/filter-selection-panel-row-value-input.component.ts b/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel-row/panel-row-value-input/filter-selection-panel-row-value-input.component.ts new file mode 100644 index 0000000000..2d6031672b --- /dev/null +++ b/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel-row/panel-row-value-input/filter-selection-panel-row-value-input.component.ts @@ -0,0 +1,55 @@ +/* + * 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, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { SelectedFilter } from '@streampipes/platform-services'; +import { EscapeNumberFilterService } from '../escape-number-filter.service'; + +@Component({ + selector: 'sp-filter-selection-panel-row-value-input', + templateUrl: './filter-selection-panel-row-value-input.component.html', +}) +export class FilterSelectionPanelRowValueInputComponent implements OnInit { + @Input() + public filter: SelectedFilter; + + // This is only required to correctly escape numbers + @Input() + public tagValues: Map; + + @Output() + public update = new EventEmitter(); + + public value: string; + + constructor(private escapeNumberFilterService: EscapeNumberFilterService) {} + + ngOnInit(): void { + this.value = this.escapeNumberFilterService.removeEnclosingQuotes( + this.filter.value, + ); + } + + updateParentComponent() { + this.filter.value = this.escapeNumberFilterService.escapeIfNumberValue( + this.filter, + this.value, + this.tagValues, + ); + this.update.emit(); + } +} diff --git a/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel.component.html b/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel.component.html index 2d5242eaf3..f2dd8b0d27 100644 --- a/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel.component.html +++ b/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel.component.html @@ -31,134 +31,19 @@
-
-
-
- - Field - - {{ field.runtimeName }} - - - - - - - = - - - < - - - <= - - - >= - - - > - - - != - - - - - Value - - - - - - - {{ value }} - - - - -
-
-
+ @for ( + filter of sourceConfig.queryConfig.selectedFilters; + track filter; + let i = $index + ) { + + + }
diff --git a/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel.component.ts b/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel.component.ts index 042d2515e2..fc251508c5 100644 --- a/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel.component.ts +++ b/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel.component.ts @@ -19,7 +19,6 @@ import { Component, Input, OnInit } from '@angular/core'; import { DatalakeRestService, - FieldConfig, SelectedFilter, SourceConfig, } from '@streampipes/platform-services'; @@ -88,8 +87,8 @@ export class FilterSelectionPanelComponent implements OnInit { this.updateWidget(); } - remove(sourceConfig: any, index: number) { - sourceConfig.queryConfig.selectedFilters.splice(index, 1); + remove(index: number) { + this.sourceConfig.queryConfig.selectedFilters.splice(index, 1); this.widgetConfigService.notify({ refreshData: true, @@ -113,8 +112,4 @@ export class FilterSelectionPanelComponent implements OnInit { }); } } - - compare(available: FieldConfig, selected: FieldConfig) { - return available.runtimeName === selected.runtimeName; - } } diff --git a/ui/src/app/data-explorer/data-explorer.module.ts b/ui/src/app/data-explorer/data-explorer.module.ts index 32991008ed..f296389868 100644 --- a/ui/src/app/data-explorer/data-explorer.module.ts +++ b/ui/src/app/data-explorer/data-explorer.module.ts @@ -125,6 +125,11 @@ import { DataExplorerDataViewPreviewComponent } from './components/dashboard/das import { DataExplorerDashboardToolbarComponent } from './components/dashboard/dashboard-toolbar/dashboard-toolbar.component'; import { OrderSelectionPanelComponent } from './components/data-view/data-view-designer-panel/data-settings/order-selection-panel/order-selection-panel.component'; import { GaugeWidgetConfigComponent } from './components/widgets/gauge/config/gauge-widget-config.component'; +import { FilterSelectionPanelRowComponent } from './components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel-row/filter-selection-panel-row.component'; +import { FilterSelectionPanelRowPropertySelectionComponent } from './components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel-row/panel-row-property-selection/filter-selection-panel-row-property-selection.component'; +import { FilterSelectionPanelRowOperationSelectionComponent } from './components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel-row/panel-row-operation-selection/filter-selection-panel-row-operation-selection.component'; +import { FilterSelectionPanelRowValueInputComponent } from './components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel-row/panel-row-value-input/filter-selection-panel-row-value-input.component'; +import { FilterSelectionPanelRowValueAutocompleteComponent } from './components/data-view/data-view-designer-panel/data-settings/filter-selection-panel/filter-selection-panel-row/panel-row-value-input-autocomplete/filter-selection-panel-row-value-autocomplete.component'; @NgModule({ imports: [ @@ -224,6 +229,7 @@ import { GaugeWidgetConfigComponent } from './components/widgets/gauge/config/ga FieldSelectionPanelComponent, FieldSelectionComponent, FilterSelectionPanelComponent, + FilterSelectionPanelRowComponent, GaugeWidgetConfigComponent, GroupConfigurationComponent, ImageWidgetComponent, @@ -268,6 +274,10 @@ import { GaugeWidgetConfigComponent } from './components/widgets/gauge/config/ga SpEchartsWidgetAppearanceConfigComponent, SpTimeSeriesAppearanceConfigComponent, SpDataZoomConfigComponent, + FilterSelectionPanelRowPropertySelectionComponent, + FilterSelectionPanelRowOperationSelectionComponent, + FilterSelectionPanelRowValueInputComponent, + FilterSelectionPanelRowValueAutocompleteComponent, ], exports: [], })