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

zehnder pushed a commit to branch improve-tests
in repository https://gitbox.apache.org/repos/asf/streampipes.git

commit 2a6e1abc341c92c4841bc5b095f536ad8d43ee7b
Author: Philipp Zehnder <[email protected]>
AuthorDate: Thu Feb 26 16:10:33 2026 +0100

    feat: enhance e2e tests
---
 ui/cypress/AGENTS.md                               |  53 ++++++++++
 ui/cypress/support/utils/asset/AssetUtils.ts       |   4 +-
 .../support/utils/dataExplorer/DataExplorerBtns.ts |  48 +++++++++
 .../utils/dataExplorer/DataExplorerUtils.ts        |  25 ++---
 ui/cypress/support/utils/pipeline/PipelineBtns.ts  |  40 ++++++++
 .../advancedFilterExpressions.smoke.spec.ts        |  39 ++++----
 .../tests/pipeline/pipelineMultiSelect.spec.ts     | 110 +++++++--------------
 .../components/chart-view/chart-view.component.ts  |  67 ++++++++++++-
 8 files changed, 275 insertions(+), 111 deletions(-)

diff --git a/ui/cypress/AGENTS.md b/ui/cypress/AGENTS.md
new file mode 100644
index 0000000000..9bedcd1a9a
--- /dev/null
+++ b/ui/cypress/AGENTS.md
@@ -0,0 +1,53 @@
+# AGENTS Guide (Cypress E2E)
+
+## Scope
+
+Applies to everything under `ui/cypress/`.
+
+## Inheritance
+
+- Also follow `AGENTS.md` at repository root.
+- Also follow `ui/AGENTS.md`.
+
+## Primary Goal
+
+Keep tests readable and maintainable by centralizing selectors and common 
flows in `support/` classes.
+
+## Authoring Rules For New/Generated Tests
+
+- Prefer existing helpers from `ui/cypress/support/utils/**` and 
`ui/cypress/support/builder/**`.
+- Do not introduce new inline selector strings in spec files when a selector 
can be reused.
+- If a new selector is needed, add it to a fitting support class first, then 
consume it from the spec.
+- Keep specs focused on scenario intent and assertions, not low-level UI 
wiring.
+
+## Selector Placement Rules
+
+- Put `data-cy` element accessors in domain `*Btns` classes (for example 
`PipelineBtns`, `ConnectBtns`, `DataExplorerBtns`).
+- Put multi-step user flows in domain `*Utils` classes (for example 
`PipelineUtils`, `ConnectUtils`, `DataExplorerUtils`).
+- For dynamic selectors, use typed helper methods with parameters instead of 
string concatenation in specs.
+- Reuse existing selector constants/patterns when already present (for example 
`SiteUtils` constants).
+- Keep direct `cy.get(...)` in specs to a minimum; if reused, move it behind a 
support helper.
+
+## Spec Structure
+
+- Initialize test state with `cy.initStreamPipesTest()` unless a test 
explicitly requires different setup.
+- Reuse builders for test objects (`AdapterBuilder`, `PipelineBuilder`, 
`PipelineElementBuilder`, ...).
+- Avoid fixed `cy.wait(...)` where possible; prefer state-based 
waits/assertions via helpers.
+- Keep each test independent: no inter-test dependencies.
+
+## Refactoring Expectations
+
+- When touching an existing spec with many inline selectors, opportunistically 
move touched selectors to the matching `*Btns`/`*Utils` class.
+- Do not do broad cross-module rewrites; keep refactors scoped to the test 
area being changed.
+
+## Naming And Organization
+
+- Follow existing naming style:
+  - UI accessors: verb/noun methods in `*Btns` returning Cypress chains.
+  - Flows/assertions: descriptive methods in `*Utils`.
+- Place new helpers in the closest domain folder under `support/utils/` 
(connect, pipeline, dataExplorer, ...).
+
+## Validation
+
+- For Cypress changes, run targeted specs when feasible (for example via smoke 
selection).
+- If execution is not possible, document what was not run.
diff --git a/ui/cypress/support/utils/asset/AssetUtils.ts 
b/ui/cypress/support/utils/asset/AssetUtils.ts
index 8444e391bd..a408bd8418 100644
--- a/ui/cypress/support/utils/asset/AssetUtils.ts
+++ b/ui/cypress/support/utils/asset/AssetUtils.ts
@@ -158,9 +158,7 @@ export class AssetUtils {
 
     public static editAsset(assetName: string) {
         GeneralUtils.openMenuForRow(assetName);
-        cy.contains('button', 'Edit').click({ force: true });
-        //This is the old version and there in case above does not work for 
all tests
-        //AssetBtns.editAssetBtn(assetName).click({ force: true });
+        AssetBtns.editAssetBtn(assetName).click({ force: true });
     }
 
     public static addAssetWithOneAdapter(assetName: string) {
diff --git a/ui/cypress/support/utils/dataExplorer/DataExplorerBtns.ts 
b/ui/cypress/support/utils/dataExplorer/DataExplorerBtns.ts
index f644453904..ee99646fe0 100644
--- a/ui/cypress/support/utils/dataExplorer/DataExplorerBtns.ts
+++ b/ui/cypress/support/utils/dataExplorer/DataExplorerBtns.ts
@@ -157,4 +157,52 @@ export class DataExplorerBtns {
     public static closeDashboardCreate() {
         return cy.dataCy('close-data-view');
     }
+
+    public static advancedFilterBtn() {
+        return cy.dataCy('design-panel-data-settings-advanced-filter');
+    }
+
+    public static advancedFilterAddConditionBtn() {
+        return cy.dataCy('advanced-filter-add-condition');
+    }
+
+    public static advancedFilterAddGroupBtn() {
+        return cy.dataCy('advanced-filter-add-group');
+    }
+
+    public static advancedFilterGroupOperator() {
+        return cy.dataCy('advanced-filter-group-operator', {}, true);
+    }
+
+    public static advancedFilterPreviewBanner() {
+        return cy.dataCy('advanced-filter-preview-banner');
+    }
+
+    public static advancedFilterApplyBtn() {
+        return cy.dataCy('advanced-filter-apply');
+    }
+
+    public static filterAlertBanner() {
+        return cy.dataCy('filter-alert-banner', { timeout: 2000 });
+    }
+
+    public static filterFieldSelect() {
+        return cy.dataCy('design-panel-data-settings-filter-field', {}, true);
+    }
+
+    public static filterOperatorSelect() {
+        return cy.dataCy(
+            'design-panel-data-settings-filter-operator',
+            {},
+            true,
+        );
+    }
+
+    public static filterValueInput() {
+        return cy.dataCy('design-panel-data-settings-filter-value', {}, true);
+    }
+
+    public static matOptionByText(text: string | RegExp) {
+        return cy.get('mat-option').contains(text);
+    }
 }
diff --git a/ui/cypress/support/utils/dataExplorer/DataExplorerUtils.ts 
b/ui/cypress/support/utils/dataExplorer/DataExplorerUtils.ts
index e918092423..5528d03f5b 100644
--- a/ui/cypress/support/utils/dataExplorer/DataExplorerUtils.ts
+++ b/ui/cypress/support/utils/dataExplorer/DataExplorerUtils.ts
@@ -24,7 +24,6 @@ import { FileManagementUtils } from '../FileManagementUtils';
 import { ConnectUtils } from '../connect/ConnectUtils';
 import { ConnectBtns } from '../connect/ConnectBtns';
 import { AdapterBuilder } from '../../builder/AdapterBuilder';
-import { differenceInMonths } from 'date-fns';
 import { GeneralUtils } from '../GeneralUtils';
 import { DataExplorerBtns } from './DataExplorerBtns';
 import { SharedBtns } from '../shared/SharedBtns';
@@ -101,7 +100,7 @@ export class DataExplorerUtils {
                 'speed',
                 'fastest_\\(ignore_original_time\\)',
             )
-            .setStartAdapter(true);
+            .setStartAdapter(false);
 
         if (format === 'csv') {
             adapterBuilder
@@ -659,19 +658,21 @@ export class DataExplorerUtils {
     }
 
     public static selectTimeRange(from: Date, to: Date) {
-        DataExplorerUtils.openTimeSelectorMenu();
-        const monthsBack = Math.abs(differenceInMonths(from, new Date())) + 1;
-        DataExplorerUtils.navigateCalendar('previous', monthsBack);
-        DataExplorerUtils.selectDay(from.getDate());
+        cy.location('hash').then(hash => {
+            const [route, queryString] = hash.split('?');
+            const searchParams = new URLSearchParams(queryString ?? '');
 
-        const monthsForward = Math.abs(differenceInMonths(from, to));
-        DataExplorerUtils.navigateCalendar('next', monthsForward);
+            searchParams.set('startDate', from.getTime().toString());
+            searchParams.set('endDate', to.getTime().toString());
 
-        DataExplorerUtils.selectDay(to.getDate());
+            const updatedHash = `${route}?${searchParams.toString()}`;
+            cy.window().then(win => {
+                win.location.hash = updatedHash;
+            });
+        });
 
-        DataExplorerUtils.setTimeInput('time-selector-start-time', from);
-        DataExplorerUtils.setTimeInput('time-selector-end-time', to);
-        DataExplorerUtils.applyCustomTimeSelection();
+        cy.location('hash').should('contain', `startDate=${from.getTime()}`);
+        cy.location('hash').should('contain', `endDate=${to.getTime()}`);
     }
 
     public static navigateCalendar(direction: string, numberOfMonths: number) {
diff --git a/ui/cypress/support/utils/pipeline/PipelineBtns.ts 
b/ui/cypress/support/utils/pipeline/PipelineBtns.ts
index 1ba9c36db7..8f57da5995 100644
--- a/ui/cypress/support/utils/pipeline/PipelineBtns.ts
+++ b/ui/cypress/support/utils/pipeline/PipelineBtns.ts
@@ -101,4 +101,44 @@ export class PipelineBtns {
     public static pipelineHelpBtn() {
         return cy.dataCy('help-button-icon-stand');
     }
+
+    public static selectionToolbar() {
+        return cy.dataCy('sp-table-selection-toolbar');
+    }
+
+    public static rowCheckbox() {
+        return cy.dataCy('sp-table-row-checkbox');
+    }
+
+    public static rowCheckboxInput(index: number) {
+        return PipelineBtns.rowCheckbox()
+            .eq(index)
+            .find('input[type="checkbox"]');
+    }
+
+    public static multiActionExecute() {
+        return cy.dataCy('sp-table-multi-action-execute');
+    }
+
+    public static selectNone() {
+        return cy.dataCy('sp-table-select-none');
+    }
+
+    public static multiActionSelect() {
+        return cy.dataCy('sp-table-multi-action-select');
+    }
+
+    public static multiActionOptionStop() {
+        return cy.dataCy('sp-table-multi-action-option-stop');
+    }
+
+    public static selectVisible() {
+        return cy.dataCy('sp-table-select-visible');
+    }
+
+    public static selectAllCheckboxInput() {
+        return cy
+            .dataCy('sp-table-select-all-checkbox')
+            .find('input[type="checkbox"]');
+    }
 }
diff --git 
a/ui/cypress/tests/dataExplorer/advancedFilterExpressions.smoke.spec.ts 
b/ui/cypress/tests/dataExplorer/advancedFilterExpressions.smoke.spec.ts
index 2231c63e5b..07e618ef30 100644
--- a/ui/cypress/tests/dataExplorer/advancedFilterExpressions.smoke.spec.ts
+++ b/ui/cypress/tests/dataExplorer/advancedFilterExpressions.smoke.spec.ts
@@ -17,6 +17,7 @@
  */
 
 import { DataExplorerUtils } from 
'../../support/utils/dataExplorer/DataExplorerUtils';
+import { DataExplorerBtns } from 
'../../support/utils/dataExplorer/DataExplorerBtns';
 import { DataExplorerWidgetTableUtils } from 
'../../support/utils/dataExplorer/DataExplorerWidgetTableUtils';
 
 describe('Advanced Filter Expressions in Data Explorer', () => {
@@ -34,21 +35,21 @@ describe('Advanced Filter Expressions in Data Explorer', () 
=> {
         DataExplorerWidgetTableUtils.checkAmountOfRows(10);
         DataExplorerUtils.selectDataConfig();
 
-        cy.dataCy('design-panel-data-settings-advanced-filter').click();
+        DataExplorerBtns.advancedFilterBtn().click();
 
         // Root condition: randomtext = a
-        cy.dataCy('advanced-filter-add-condition').first().click();
+        DataExplorerBtns.advancedFilterAddConditionBtn().first().click();
         // Root nested group
-        cy.dataCy('advanced-filter-add-group').first().click();
+        DataExplorerBtns.advancedFilterAddGroupBtn().first().click();
         // Two conditions in nested group
-        cy.dataCy('advanced-filter-add-condition').last().click();
-        cy.dataCy('advanced-filter-add-condition').last().click();
+        DataExplorerBtns.advancedFilterAddConditionBtn().last().click();
+        DataExplorerBtns.advancedFilterAddConditionBtn().last().click();
 
         // Set nested group operator to OR
-        cy.dataCy('advanced-filter-group-operator', {}, true)
+        DataExplorerBtns.advancedFilterGroupOperator()
             .last()
             .click({ force: true });
-        cy.get('mat-option').contains(/^OR$/).click();
+        DataExplorerBtns.matOptionByText(/^OR$/).click();
 
         setAdvancedCondition(0, 'randomtext', '=', 'a');
         setAdvancedCondition(1, 'randomnumber', '=', '22');
@@ -57,13 +58,13 @@ describe('Advanced Filter Expressions in Data Explorer', () 
=> {
         // Value inputs update the model on change; blur the active input 
before checking the preview.
         cy.focused().blur();
 
-        cy.dataCy('advanced-filter-preview-banner')
+        DataExplorerBtns.advancedFilterPreviewBanner()
             .should('contain.text', 'randomtext = a')
             .and('contain.text', 'randomnumber = 22')
             .and('contain.text', 'randomnumber = 56')
             .and('contain.text', 'OR');
 
-        cy.dataCy('advanced-filter-apply').click();
+        DataExplorerBtns.advancedFilterApplyBtn().click();
 
         // a AND (22 OR 56) => 2 rows in sample.csv
         DataExplorerWidgetTableUtils.checkAmountOfRows(2);
@@ -72,10 +73,8 @@ describe('Advanced Filter Expressions in Data Explorer', () 
=> {
         DataExplorerWidgetTableUtils.checkAmountOfRows(2);
 
         DataExplorerUtils.selectDataConfig();
-        cy.dataCy('design-panel-data-settings-advanced-filter').should(
-            'be.visible',
-        );
-        cy.dataCy('filter-alert-banner', { timeout: 2000 })
+        DataExplorerBtns.advancedFilterBtn().should('be.visible');
+        DataExplorerBtns.filterAlertBanner()
             .should('be.visible')
             .within(() => {
                 cy.contains('randomtext = a');
@@ -89,17 +88,13 @@ function setAdvancedCondition(
     operator: '=' | '!=' | '<' | '<=' | '>' | '>=',
     value: string,
 ) {
-    cy.dataCy('design-panel-data-settings-filter-field', {}, true)
-        .eq(index)
-        .click({ force: true });
-    cy.get('mat-option').contains(field).click();
+    DataExplorerBtns.filterFieldSelect().eq(index).click({ force: true });
+    DataExplorerBtns.matOptionByText(field).click();
 
-    cy.dataCy('design-panel-data-settings-filter-operator', {}, true)
-        .eq(index)
-        .click({ force: true });
-    cy.get('mat-option').contains(operator).click();
+    DataExplorerBtns.filterOperatorSelect().eq(index).click({ force: true });
+    DataExplorerBtns.matOptionByText(operator).click();
 
-    cy.dataCy('design-panel-data-settings-filter-value', {}, true)
+    DataExplorerBtns.filterValueInput()
         .eq(index)
         .clear({ force: true })
         .type(value, { force: true });
diff --git a/ui/cypress/tests/pipeline/pipelineMultiSelect.spec.ts 
b/ui/cypress/tests/pipeline/pipelineMultiSelect.spec.ts
index eacdb8e435..5caf26ac11 100644
--- a/ui/cypress/tests/pipeline/pipelineMultiSelect.spec.ts
+++ b/ui/cypress/tests/pipeline/pipelineMultiSelect.spec.ts
@@ -18,6 +18,7 @@
 
 import { ConnectUtils } from '../../support/utils/connect/ConnectUtils';
 import { PipelineUtils } from '../../support/utils/pipeline/PipelineUtils';
+import { PipelineBtns } from '../../support/utils/pipeline/PipelineBtns';
 import { PipelineBuilder } from '../../support/builder/PipelineBuilder';
 import { PipelineElementBuilder } from 
'../../support/builder/PipelineElementBuilder';
 
@@ -54,78 +55,41 @@ describe('Pipeline Overview Multi Select', () => {
     });
 
     it('supports selecting rows and bulk action state changes', () => {
-        cy.dataCy('sp-table-selection-toolbar').should('be.visible');
-        cy.dataCy('sp-table-row-checkbox').should('have.length', 2);
-
-        cy.dataCy('sp-table-multi-action-execute').should('be.disabled');
-        cy.dataCy('sp-table-select-none').should('be.disabled');
-
-        cy.dataCy('sp-table-row-checkbox')
-            .eq(0)
-            .find('input[type="checkbox"]')
-            .check({ force: true });
-        cy.dataCy('sp-table-select-none').should('not.be.disabled');
-        cy.dataCy('sp-table-multi-action-execute').should('be.disabled');
-
-        cy.dataCy('sp-table-multi-action-select').click();
-        cy.dataCy('sp-table-multi-action-option-stop').click();
-        cy.dataCy('sp-table-multi-action-execute').should('not.be.disabled');
-
-        cy.dataCy('sp-table-row-checkbox')
-            .eq(0)
-            .find('input')
-            .should('be.checked');
-        cy.dataCy('sp-table-row-checkbox')
-            .eq(1)
-            .find('input')
-            .should('not.be.checked');
-
-        cy.dataCy('sp-table-select-visible').click();
-        cy.dataCy('sp-table-row-checkbox')
-            .eq(0)
-            .find('input')
-            .should('be.checked');
-        cy.dataCy('sp-table-row-checkbox')
-            .eq(1)
-            .find('input')
-            .should('be.checked');
-        cy.dataCy('sp-table-multi-action-execute').should('not.be.disabled');
-
-        cy.dataCy('sp-table-select-none').click();
-        cy.dataCy('sp-table-row-checkbox')
-            .eq(0)
-            .find('input')
-            .should('not.be.checked');
-        cy.dataCy('sp-table-row-checkbox')
-            .eq(1)
-            .find('input')
-            .should('not.be.checked');
-        cy.dataCy('sp-table-multi-action-execute').should('be.disabled');
-
-        cy.dataCy('sp-table-select-all-checkbox')
-            .find('input[type="checkbox"]')
-            .check({ force: true });
-        cy.dataCy('sp-table-row-checkbox')
-            .eq(0)
-            .find('input')
-            .should('be.checked');
-        cy.dataCy('sp-table-row-checkbox')
-            .eq(1)
-            .find('input')
-            .should('be.checked');
-        cy.dataCy('sp-table-multi-action-execute').should('not.be.disabled');
-
-        cy.dataCy('sp-table-select-all-checkbox')
-            .find('input[type="checkbox"]')
-            .uncheck({ force: true });
-        cy.dataCy('sp-table-row-checkbox')
-            .eq(0)
-            .find('input')
-            .should('not.be.checked');
-        cy.dataCy('sp-table-row-checkbox')
-            .eq(1)
-            .find('input')
-            .should('not.be.checked');
-        cy.dataCy('sp-table-multi-action-execute').should('be.disabled');
+        PipelineBtns.selectionToolbar().should('be.visible');
+        PipelineBtns.rowCheckbox().should('have.length', 2);
+
+        PipelineBtns.multiActionExecute().should('be.disabled');
+        PipelineBtns.selectNone().should('be.disabled');
+
+        PipelineBtns.rowCheckboxInput(0).check({ force: true });
+        PipelineBtns.selectNone().should('not.be.disabled');
+        PipelineBtns.multiActionExecute().should('be.disabled');
+
+        PipelineBtns.multiActionSelect().click();
+        PipelineBtns.multiActionOptionStop().click();
+        PipelineBtns.multiActionExecute().should('not.be.disabled');
+
+        PipelineBtns.rowCheckboxInput(0).should('be.checked');
+        PipelineBtns.rowCheckboxInput(1).should('not.be.checked');
+
+        PipelineBtns.selectVisible().click();
+        PipelineBtns.rowCheckboxInput(0).should('be.checked');
+        PipelineBtns.rowCheckboxInput(1).should('be.checked');
+        PipelineBtns.multiActionExecute().should('not.be.disabled');
+
+        PipelineBtns.selectNone().click();
+        PipelineBtns.rowCheckboxInput(0).should('not.be.checked');
+        PipelineBtns.rowCheckboxInput(1).should('not.be.checked');
+        PipelineBtns.multiActionExecute().should('be.disabled');
+
+        PipelineBtns.selectAllCheckboxInput().check({ force: true });
+        PipelineBtns.rowCheckboxInput(0).should('be.checked');
+        PipelineBtns.rowCheckboxInput(1).should('be.checked');
+        PipelineBtns.multiActionExecute().should('not.be.disabled');
+
+        PipelineBtns.selectAllCheckboxInput().uncheck({ force: true });
+        PipelineBtns.rowCheckboxInput(0).should('not.be.checked');
+        PipelineBtns.rowCheckboxInput(1).should('not.be.checked');
+        PipelineBtns.multiActionExecute().should('be.disabled');
     });
 });
diff --git a/ui/src/app/chart/components/chart-view/chart-view.component.ts 
b/ui/src/app/chart/components/chart-view/chart-view.component.ts
index 43a3461383..10c442c48b 100644
--- a/ui/src/app/chart/components/chart-view/chart-view.component.ts
+++ b/ui/src/app/chart/components/chart-view/chart-view.component.ts
@@ -31,6 +31,7 @@ import {
     EventPropertyUnion,
     FieldConfig,
     LinkageData,
+    TimeSelectionConstants,
     TimeSettings,
 } from '@streampipes/platform-services';
 import {
@@ -130,6 +131,7 @@ export class ChartViewComponent
     private assetSaveService = inject(AssetSaveService);
 
     currentUser$: Subscription;
+    queryParams$: Subscription;
 
     chartNotFound = false;
 
@@ -153,10 +155,16 @@ export class ChartViewComponent
             this.loadDataView(dataViewId);
         } else {
             this.createWidget();
-            this.timeSettings = this.makeDefaultTimeSettings();
+            this.timeSettings =
+                this.getTimeSettingsFromQueryParams() ??
+                this.makeDefaultTimeSettings();
             this.dataView.timeSettings = this.timeSettings;
             this.afterDataViewLoaded();
         }
+
+        this.queryParams$ = this.route.queryParams.subscribe(queryParams => {
+            this.applyTimeSettingsFromQueryParams(queryParams);
+        });
     }
 
     onAddWidget(event: Tuple2<DataLakeMeasure, DataExplorerWidgetModel>) {
@@ -225,11 +233,67 @@ export class ChartViewComponent
                         this.dataExplorerSharedService.makeChartTimeSettings(
                             this.dataView,
                         );
+                    this.timeSettings =
+                        this.getTimeSettingsFromQueryParams() ??
+                        this.timeSettings;
                     this.afterDataViewLoaded();
                 }
             });
     }
 
+    private applyTimeSettingsFromQueryParams(queryParams: {
+        [key: string]: any;
+    }): void {
+        if (!this.timeSettings) {
+            return;
+        }
+
+        const startDate = Number(queryParams.startDate);
+        const endDate = Number(queryParams.endDate);
+        if (
+            !Number.isFinite(startDate) ||
+            !Number.isFinite(endDate) ||
+            startDate >= endDate
+        ) {
+            return;
+        }
+
+        if (
+            this.timeSettings.startTime === startDate &&
+            this.timeSettings.endTime === endDate
+        ) {
+            return;
+        }
+
+        this.timeSettings = {
+            ...this.timeSettings,
+            startTime: startDate,
+            endTime: endDate,
+            timeSelectionId: TimeSelectionConstants.CUSTOM,
+        };
+        this.timeSelectionService.notify(this.timeSettings);
+    }
+
+    private getTimeSettingsFromQueryParams(): TimeSettings | undefined {
+        const startDate = Number(this.route.snapshot.queryParams.startDate);
+        const endDate = Number(this.route.snapshot.queryParams.endDate);
+
+        if (
+            !Number.isFinite(startDate) ||
+            !Number.isFinite(endDate) ||
+            startDate >= endDate
+        ) {
+            return undefined;
+        }
+
+        return {
+            startTime: startDate,
+            endTime: endDate,
+            dynamicSelection: -1,
+            timeSelectionId: TimeSelectionConstants.CUSTOM,
+        };
+    }
+
     afterDataViewLoaded(): void {
         this.dataViewLoaded = true;
         setTimeout(() => {
@@ -452,5 +516,6 @@ export class ChartViewComponent
 
     ngOnDestroy() {
         this.currentUser$?.unsubscribe();
+        this.queryParams$?.unsubscribe();
     }
 }

Reply via email to