This is an automated email from the ASF dual-hosted git repository.

riemer pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/streampipes.git


The following commit(s) were added to refs/heads/dev by this push:
     new 1605f6b47b fix(#3430): filtering for numerical dimension properties in 
data explorer (#3436)
1605f6b47b is described below

commit 1605f6b47bce582b3e3dafa63cacf543ec320fde
Author: Philipp Zehnder <[email protected]>
AuthorDate: Thu Jan 23 16:57:47 2025 +0100

    fix(#3430): filtering for numerical dimension properties in data explorer 
(#3436)
    
    * fix(#3430): Remove unused parameter in DataLakeUtils.ts
    
    * fix(#3430): Add method to validate amount of table results in data 
explorer
    
    * fix(#3430): Add e2e test to validate functionality of filter dimension 
property numbers
    
    * fix(#3430): Refactor filter selection panel row and filter operator 
according to data type
    
    * fix(#3430): Numerical values are now handled as expected by filters
    
    * fix(#3430): Numerical values are now handled as expected by filters
    
    * fix(#3430): Change directory name to pass windows CI builds
    
    * fix(#3430): Rename component to meet windows file name length restrictions
---
 .../param/model/WhereClauseParams.java             |  50 +++++--
 ui/angular.json                                    |   3 +-
 .../datalake/filterNumericalStringProperties.csv   |   3 +
 ui/cypress/support/utils/connect/ConnectUtils.ts   |  24 ++--
 ui/cypress/support/utils/datalake/DataLakeUtils.ts |  46 ++++++-
 .../utils/datalake/DataLakeWidgetTableUtils.ts     |  12 +-
 .../tests/datalake/deleteViewAndDashboard.spec.ts  |   2 +-
 ui/cypress/tests/datalake/deleteWidget.ts          |   2 +-
 .../filterNumericalStringProperties.spec.ts        |  89 +++++++++++++
 .../tests/datalake/missingDataInDataLake.spec.ts   |   4 +-
 .../tests/datalake/timeOrderDataView.spec.ts       |   2 +-
 .../tests/datalake/timeRangeSelectors.spec.ts      |   2 +-
 .../datalake/widgetDataConfiguration.smoke.spec.ts |  24 ++--
 ui/cypress/tests/datalake/widgets/table.spec.ts    |   5 +-
 .../escape-number-filter.service.ts                |  62 +++++++++
 .../filter-selection-panel-row.component.html      |  58 +++++++++
 .../filter-selection-panel-row.component.ts}       |  49 ++++---
 ...on-panel-row-operation-selection.component.html |  46 +++++++
 ...ion-panel-row-operation-selection.component.ts} |  30 +++--
 ...ion-panel-row-property-selection.component.html |  31 +++++
 ...tion-panel-row-property-selection.component.ts} |  35 +++--
 ...ion-panel-row-value-autocomplete.component.html |  42 ++++++
 ...ction-panel-row-value-autocomplete.component.ts |  57 ++++++++
 ...-selection-panel-row-value-input.component.html |  28 ++++
 ...er-selection-panel-row-value-input.component.ts |  55 ++++++++
 .../filter-selection-panel.component.html          | 143 ++-------------------
 .../filter-selection-panel.component.ts            |   9 +-
 ui/src/app/data-explorer/data-explorer.module.ts   |  10 ++
 28 files changed, 697 insertions(+), 226 deletions(-)

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<FilterCondition> 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 @@ public class WhereClauseParams implements IQueryStatement {
     }
   }
 
-  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 class WhereClauseParams implements IQueryStatement {
     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 @@ public class WhereClauseParams implements IQueryStatement {
   }
 
   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 @@ public class WhereClauseParams implements IQueryStatement {
     }
   }
 
+  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, string[]>,
+    ): 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<string, string[]>,
+    ): 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 @@
+<!--
+  ~ 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.
+  ~
+  -->
+
+<div fxFlex="100" fxLayout="column" class="form-field-small">
+    <div fxFlex="100" fxLayout="row" fxLayoutAlign="start center">
+        <sp-filter-selection-panel-row-property-selection
+            [filter]="filter"
+            [possibleFields]="possibleFields"
+            (update)="updateParentComponent()"
+        >
+        </sp-filter-selection-panel-row-property-selection>
+
+        <sp-filter-selection-panel-row-operation-selection
+            [filter]="filter"
+            (update)="updateParentComponent()"
+        >
+        </sp-filter-selection-panel-row-operation-selection>
+
+        @if (!filter.field || !tagValues.has(filter.field.runtimeName)) {
+            <sp-filter-selection-panel-row-value-input
+                [filter]="filter"
+                (update)="updateParentComponent()"
+            >
+            </sp-filter-selection-panel-row-value-input>
+        } @else {
+            <sp-filter-selection-panel-row-value-autocomplete
+                [filter]="filter"
+                [tagValues]="tagValues"
+                (update)="updateParentComponent()"
+            >
+            </sp-filter-selection-panel-row-value-autocomplete>
+        }
+
+        <button
+            mat-icon-button
+            color="accent"
+            (click)="remove()"
+            data-cy="design-panel-data-settings-remove-filter"
+        >
+            <i class="material-icons">remove</i>
+        </button>
+    </div>
+</div>
diff --git a/ui/cypress/tests/datalake/widgets/table.spec.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
similarity index 51%
copy from ui/cypress/tests/datalake/widgets/table.spec.ts
copy to 
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
index 4648835e77..9e7cc75810 100644
--- a/ui/cypress/tests/datalake/widgets/table.spec.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
@@ -16,19 +16,36 @@
  *
  */
 
-import { DataLakeUtils } from '../../../support/utils/datalake/DataLakeUtils';
-
-describe('Test Table View in Data Explorer', () => {
-    beforeEach('Setup Test', () => {
-        DataLakeUtils.initDataLakeTests();
-    });
-
-    it('Perform Test', () => {
-        DataLakeUtils.addDataViewAndWidget('view', 'Persist', 'Table');
-
-        // Check if table is displayed correctly
-        cy.dataCy('data-explorer-table-row-timestamp', {
-            timeout: 10000,
-        }).should('have.length', 10);
-    });
-});
+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<string, string[]>;
+
+    @Output()
+    public update = new EventEmitter<void>();
+
+    @Output()
+    public removeFilter = new EventEmitter<number>();
+
+    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 @@
+<!--
+  ~ 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.
+  ~
+  -->
+<mat-form-field color="accent" class="w-80-px mr-5" appearance="outline">
+    <mat-select
+        [(value)]="filter.operator"
+        panelClass="form-field-small min-w-100"
+        (selectionChange)="updateParentComponent()"
+        data-cy="design-panel-data-settings-filter-operator"
+    >
+        <mat-option [value]="'='" data-cy="operator-=">
+            <span class="pipeline-name">=</span>
+        </mat-option>
+        @if (filter.field && filter.field['numeric']) {
+            <mat-option [value]="'<'" data-cy="operator-<">
+                <span class="pipeline-name"><</span>
+            </mat-option>
+            <mat-option [value]="'<='" data-cy="operator-<=">
+                <span class="pipeline-name"><=</span>
+            </mat-option>
+            <mat-option [value]="'>='" data-cy="operator->=">
+                <span class="pipeline-name">>=</span>
+            </mat-option>
+            <mat-option [value]="'>'" data-cy="operator->">
+                <span class="pipeline-name">></span>
+            </mat-option>
+        }
+        <mat-option [value]="'!='" data-cy="operator-!=">
+            <span class="pipeline-name">!=</span>
+        </mat-option>
+    </mat-select>
+</mat-form-field>
diff --git a/ui/cypress/tests/datalake/widgets/table.spec.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
similarity index 59%
copy from ui/cypress/tests/datalake/widgets/table.spec.ts
copy to 
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
index 4648835e77..1ba9c766a1 100644
--- a/ui/cypress/tests/datalake/widgets/table.spec.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
@@ -15,20 +15,22 @@
  * limitations under the License.
  *
  */
+import { Component, EventEmitter, Input, Output } from '@angular/core';
+import { SelectedFilter } from '@streampipes/platform-services';
 
-import { DataLakeUtils } from '../../../support/utils/datalake/DataLakeUtils';
+@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;
 
-describe('Test Table View in Data Explorer', () => {
-    beforeEach('Setup Test', () => {
-        DataLakeUtils.initDataLakeTests();
-    });
+    @Output()
+    public update = new EventEmitter<void>();
 
-    it('Perform Test', () => {
-        DataLakeUtils.addDataViewAndWidget('view', 'Persist', 'Table');
-
-        // Check if table is displayed correctly
-        cy.dataCy('data-explorer-table-row-timestamp', {
-            timeout: 10000,
-        }).should('have.length', 10);
-    });
-});
+    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 @@
+<!--
+  ~ 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.
+  ~
+  -->
+<mat-form-field color="accent" class="w-140-px mr-5" appearance="outline">
+    <mat-label>Field</mat-label>
+    <mat-select
+        [(value)]="filter.field"
+        (selectionChange)="updateParentComponent()"
+        [compareWith]="compare"
+        panelClass="form-field-small min-w-200"
+        data-cy="design-panel-data-settings-filter-field"
+    >
+        @for (field of possibleFields; track field) {
+            <mat-option [value]="field">{{ field.runtimeName }} </mat-option>
+        }
+    </mat-select>
+</mat-form-field>
diff --git a/ui/cypress/tests/datalake/widgets/table.spec.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
similarity index 51%
copy from ui/cypress/tests/datalake/widgets/table.spec.ts
copy to 
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
index 4648835e77..f1f874cfb3 100644
--- a/ui/cypress/tests/datalake/widgets/table.spec.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
@@ -15,20 +15,29 @@
  * limitations under the License.
  *
  */
+import { Component, EventEmitter, Input, Output } from '@angular/core';
+import { FieldConfig, SelectedFilter } from '@streampipes/platform-services';
 
-import { DataLakeUtils } from '../../../support/utils/datalake/DataLakeUtils';
+@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;
 
-describe('Test Table View in Data Explorer', () => {
-    beforeEach('Setup Test', () => {
-        DataLakeUtils.initDataLakeTests();
-    });
+    @Input()
+    public possibleFields: FieldConfig[];
 
-    it('Perform Test', () => {
-        DataLakeUtils.addDataViewAndWidget('view', 'Persist', 'Table');
+    @Output()
+    public update = new EventEmitter<void>();
 
-        // Check if table is displayed correctly
-        cy.dataCy('data-explorer-table-row-timestamp', {
-            timeout: 10000,
-        }).should('have.length', 10);
-    });
-});
+    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 @@
+<!--
+  ~ 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.
+  ~
+  -->
+<mat-form-field color="accent" appearance="outline" class="w-140-px mr-5">
+    <input
+        type="text"
+        placeholder="Value"
+        aria-label="Number"
+        matInput
+        [(ngModel)]="value"
+        (change)="updateParentComponent()"
+        data-cy="design-panel-data-settings-filter-value"
+        [matAutocomplete]="auto"
+    />
+    <mat-autocomplete
+        #auto="matAutocomplete"
+        (optionSelected)="updateParentComponent()"
+    >
+        @for (option of tagValues.get(filter.field.runtimeName); track option) 
{
+            <mat-option
+                [attr.data-cy]="'autocomplete-value-' + option"
+                [value]="option"
+            >
+                {{ option }}
+            </mat-option>
+        }
+    </mat-autocomplete>
+</mat-form-field>
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<string, string[]>;
+
+    @Output()
+    public update = new EventEmitter<void>();
+
+    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 @@
+<!--
+  ~ 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.
+  ~
+  -->
+<mat-form-field color="accent" appearance="outline" class="w-140-px mr-5">
+    <input
+        type="text"
+        placeholder="Value"
+        aria-label="Number"
+        matInput
+        [(ngModel)]="value"
+        (change)="updateParentComponent()"
+        data-cy="design-panel-data-settings-filter-value"
+    />
+</mat-form-field>
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<string, string[]>;
+
+    @Output()
+    public update = new EventEmitter<void>();
+
+    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 @@
         </button>
     </div>
     <div fxLayout="column">
-        <div
-            *ngFor="
-                let filter of sourceConfig.queryConfig.selectedFilters;
-                let i = index
-            "
-            fxFlex="100"
-            fxLayout="column"
-        >
-            <div fxFlex="100" fxLayout="column" class="form-field-small">
-                <div fxFlex="100" fxLayout="row" fxLayoutAlign="start center">
-                    <mat-form-field
-                        color="accent"
-                        class="w-140-px mr-5"
-                        appearance="outline"
-                    >
-                        <mat-label>Field</mat-label>
-                        <mat-select
-                            [(value)]="filter.field"
-                            (selectionChange)="updateWidget()"
-                            [compareWith]="compare"
-                            panelClass="form-field-small min-w-200"
-                            data-cy="design-panel-data-settings-filter-field"
-                        >
-                            <mat-option
-                                *ngFor="
-                                    let field of 
sourceConfig.queryConfig.fields
-                                "
-                                [value]="field"
-                                >{{ field.runtimeName }}
-                            </mat-option>
-                        </mat-select>
-                    </mat-form-field>
-                    <mat-form-field
-                        color="accent"
-                        class="w-80-px mr-5"
-                        appearance="outline"
-                    >
-                        <mat-select
-                            [(value)]="filter.operator"
-                            panelClass="form-field-small min-w-100"
-                            (selectionChange)="updateWidget()"
-                            
data-cy="design-panel-data-settings-filter-operator"
-                        >
-                            <mat-option [value]="'='">
-                                <span class="pipeline-name">=</span>
-                            </mat-option>
-                            <mat-option [value]="'<'">
-                                <span class="pipeline-name"><</span>
-                            </mat-option>
-                            <mat-option [value]="'<='">
-                                <span class="pipeline-name"><=</span>
-                            </mat-option>
-                            <mat-option [value]="'>='">
-                                <span class="pipeline-name">>=</span>
-                            </mat-option>
-                            <mat-option [value]="'>'">
-                                <span class="pipeline-name">></span>
-                            </mat-option>
-                            <mat-option [value]="'!='">
-                                <span class="pipeline-name">!=</span>
-                            </mat-option>
-                        </mat-select>
-                    </mat-form-field>
-                    <mat-form-field
-                        color="accent"
-                        appearance="outline"
-                        class="w-140-px mr-5"
-                        *ngIf="
-                            !filter.field ||
-                            !tagValues.has(filter.field.runtimeName)
-                        "
-                    >
-                        <mat-label>Value</mat-label>
-                        <input
-                            type="text"
-                            placeholder="Value"
-                            aria-label="Number"
-                            matInput
-                            [(ngModel)]="filter.value"
-                            (change)="updateWidget()"
-                            data-cy="design-panel-data-settings-filter-value"
-                        />
-                    </mat-form-field>
-                    <mat-form-field
-                        color="accent"
-                        appearance="outline"
-                        class="w-140-px mr-5"
-                        *ngIf="
-                            filter.field &&
-                            tagValues.has(filter.field.runtimeName)
-                        "
-                    >
-                        <input
-                            type="text"
-                            placeholder="Value"
-                            aria-label="Number"
-                            matInput
-                            [(ngModel)]="filter.value"
-                            (change)="updateWidget()"
-                            data-cy="design-panel-data-settings-filter-value"
-                            [matAutocomplete]="auto"
-                        />
-                        <mat-autocomplete
-                            #auto="matAutocomplete"
-                            (optionSelected)="updateWidget()"
-                        >
-                            <mat-option
-                                *ngFor="
-                                    let value of tagValues.get(
-                                        filter.field.runtimeName
-                                    )
-                                "
-                                [value]="value"
-                            >
-                                {{ value }}
-                            </mat-option>
-                        </mat-autocomplete>
-                    </mat-form-field>
-                    <button
-                        mat-icon-button
-                        color="accent"
-                        (click)="remove(sourceConfig, i)"
-                        data-cy="design-panel-data-settings-remove-filter"
-                    >
-                        <i class="material-icons">remove</i>
-                    </button>
-                </div>
-            </div>
-        </div>
+        @for (
+            filter of sourceConfig.queryConfig.selectedFilters;
+            track filter;
+            let i = $index
+        ) {
+            <sp-filter-selection-panel-row
+                [filter]="filter"
+                [possibleFields]="sourceConfig.queryConfig.fields"
+                [tagValues]="tagValues"
+                (update)="updateWidget()"
+                (removeFilter)="remove(i)"
+            >
+            </sp-filter-selection-panel-row>
+        }
     </div>
 </sp-configuration-box>
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: [],
 })

Reply via email to