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

tenthe 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 a476bf14d2 feat: Make dashboard panel collapsible (#4406)
a476bf14d2 is described below

commit a476bf14d2d2384eef8f4c08d48ebeab7134eb91
Author: Dominik Riemer <[email protected]>
AuthorDate: Wed May 20 13:40:02 2026 +0200

    feat: Make dashboard panel collapsible (#4406)
    
    Co-authored-by: Philipp Zehnder <[email protected]>
---
 .../influx/DataLakeInfluxQueryBuilder.java         |  2 +-
 .../influx/sanitize/InfluxNameSanitizer.java       |  2 +-
 .../dataexplorer/influx/SelectQueryParamsTest.java | 68 ++++++++++++++++++-
 .../utils/ProvidedQueryParameterBuilder.java       |  6 ++
 .../dataexplorer}/InfluxDbReservedKeywords.java    |  2 +-
 .../param/ProvidedRestQueryParamConverter.java     |  8 +--
 .../param/model/GroupByTagsClauseParams.java       |  4 +-
 .../param/model/GroupByTimeClauseParams.java       |  2 +-
 .../param/model/InfluxQueryParameterValidator.java | 50 ++++++++++++++
 .../model/InfluxQueryParameterValidatorTest.java   | 78 ++++++++++++++++++++++
 .../panel/dashboard-panel.component.html           | 37 +++++++++-
 .../panel/dashboard-panel.component.scss           | 17 +++++
 .../components/panel/dashboard-panel.component.ts  | 14 +++-
 13 files changed, 273 insertions(+), 17 deletions(-)

diff --git 
a/streampipes-data-explorer-influx/src/main/java/org/apache/streampipes/dataexplorer/influx/DataLakeInfluxQueryBuilder.java
 
b/streampipes-data-explorer-influx/src/main/java/org/apache/streampipes/dataexplorer/influx/DataLakeInfluxQueryBuilder.java
index 3c2ca1422d..74ffa2428a 100644
--- 
a/streampipes-data-explorer-influx/src/main/java/org/apache/streampipes/dataexplorer/influx/DataLakeInfluxQueryBuilder.java
+++ 
b/streampipes-data-explorer-influx/src/main/java/org/apache/streampipes/dataexplorer/influx/DataLakeInfluxQueryBuilder.java
@@ -216,7 +216,7 @@ public class DataLakeInfluxQueryBuilder implements 
IDataLakeQueryBuilder<Query>
   @Override
   public DataLakeInfluxQueryBuilder withGroupBy(String column) {
 
-    this.groupByClauses.add(new RawTextClause(column));
+    this.groupByClauses.add(new RawTextClause("\"" + column + "\""));
 
     return this;
   }
diff --git 
a/streampipes-data-explorer-influx/src/main/java/org/apache/streampipes/dataexplorer/influx/sanitize/InfluxNameSanitizer.java
 
b/streampipes-data-explorer-influx/src/main/java/org/apache/streampipes/dataexplorer/influx/sanitize/InfluxNameSanitizer.java
index 1d468e7703..61b50946a2 100644
--- 
a/streampipes-data-explorer-influx/src/main/java/org/apache/streampipes/dataexplorer/influx/sanitize/InfluxNameSanitizer.java
+++ 
b/streampipes-data-explorer-influx/src/main/java/org/apache/streampipes/dataexplorer/influx/sanitize/InfluxNameSanitizer.java
@@ -18,7 +18,7 @@
 
 package org.apache.streampipes.dataexplorer.influx.sanitize;
 
-import org.apache.streampipes.dataexplorer.influx.InfluxDbReservedKeywords;
+import org.apache.streampipes.dataexplorer.InfluxDbReservedKeywords;
 
 public class InfluxNameSanitizer {
 
diff --git 
a/streampipes-data-explorer-influx/src/test/java/org/apache/streampipes/dataexplorer/influx/SelectQueryParamsTest.java
 
b/streampipes-data-explorer-influx/src/test/java/org/apache/streampipes/dataexplorer/influx/SelectQueryParamsTest.java
index 87743970bb..b79f55d98d 100644
--- 
a/streampipes-data-explorer-influx/src/test/java/org/apache/streampipes/dataexplorer/influx/SelectQueryParamsTest.java
+++ 
b/streampipes-data-explorer-influx/src/test/java/org/apache/streampipes/dataexplorer/influx/SelectQueryParamsTest.java
@@ -28,6 +28,7 @@ import java.util.Arrays;
 import java.util.List;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 
 public class SelectQueryParamsTest {
 
@@ -186,7 +187,7 @@ public class SelectQueryParamsTest {
     String query = 
qp.toQuery(DataLakeInfluxQueryBuilder.create("abc")).getCommand();
 
     assertEquals("SELECT MEAN(p1) AS p1_mean,COUNT(p2) AS p2_count FROM 
\"abc\" WHERE (time < 2000000 AND"
-        + " time > 1000000) GROUP BY sensorId;", query);
+        + " time > 1000000) GROUP BY \"sensorId\";", query);
   }
 
   @Test
@@ -203,7 +204,70 @@ public class SelectQueryParamsTest {
     String query = 
qp.toQuery(DataLakeInfluxQueryBuilder.create("abc")).getCommand();
 
     assertEquals("SELECT MEAN(p1) AS p1_mean,COUNT(p2) AS p2_count FROM 
\"abc\" WHERE (time < 2000000 AND"
-        + " time > 1000000) GROUP BY sensorId,sensorId2;", query);
+        + " time > 1000000) GROUP BY \"sensorId\",\"sensorId2\";", query);
+  }
+
+  @Test
+  public void testGroupByTime() {
+    var params = ProvidedQueryParameterBuilder.create("abc")
+        .withSimpleColumns(List.of("value"))
+        .withTimeInterval("1h")
+        .build();
+
+    SelectQueryParams qp = 
ProvidedRestQueryParamConverter.getSelectQueryParams(params);
+
+    String query = 
qp.toQuery(DataLakeInfluxQueryBuilder.create("abc")).getCommand();
+
+    assertEquals("SELECT value FROM \"abc\" GROUP BY time(1h) fill(none);", 
query);
+  }
+
+  @Test
+  public void testGroupByTimeAndTags() {
+    var params = ProvidedQueryParameterBuilder.create("abc")
+        .withSimpleColumns(List.of("value"))
+        .withTimeInterval("1ms")
+        .withGroupBy(List.of("sensorId"))
+        .build();
+
+    SelectQueryParams qp = 
ProvidedRestQueryParamConverter.getSelectQueryParams(params);
+
+    String query = 
qp.toQuery(DataLakeInfluxQueryBuilder.create("abc")).getCommand();
+
+    assertEquals("SELECT value FROM \"abc\" GROUP BY time(1ms),\"sensorId\" 
fill(none);", query);
+  }
+
+  @Test
+  public void testGroupByTimeRejectsUnsafeInterval() {
+    var params = ProvidedQueryParameterBuilder.create("abc")
+        .withSimpleColumns(List.of("value"))
+        .withTimeInterval("1h); SHOW MEASUREMENTS --")
+        .build();
+
+    assertThrows(IllegalArgumentException.class,
+        () -> ProvidedRestQueryParamConverter.getSelectQueryParams(params));
+  }
+
+  @Test
+  public void testGroupByRejectsUnsafeIdentifier() {
+    var params = ProvidedQueryParameterBuilder.create("abc")
+        .withSimpleColumns(List.of("value"))
+        .withGroupBy(List.of("sensorId;SHOW"))
+        .build();
+
+    assertThrows(IllegalArgumentException.class,
+        () -> ProvidedRestQueryParamConverter.getSelectQueryParams(params));
+  }
+
+  @Test
+  public void testGroupByTimeAndTagsRejectUnsafeInterval() {
+    var params = ProvidedQueryParameterBuilder.create("abc")
+        .withSimpleColumns(List.of("value"))
+        .withGroupBy(List.of("sensorId"))
+        .withTimeInterval("1h) INVALID_TOKEN --")
+        .build();
+
+    assertThrows(IllegalArgumentException.class,
+        () -> ProvidedRestQueryParamConverter.getSelectQueryParams(params));
   }
 
   @Test
diff --git 
a/streampipes-data-explorer-influx/src/test/java/org/apache/streampipes/dataexplorer/influx/utils/ProvidedQueryParameterBuilder.java
 
b/streampipes-data-explorer-influx/src/test/java/org/apache/streampipes/dataexplorer/influx/utils/ProvidedQueryParameterBuilder.java
index c5ad9bd2d2..944b4f6a66 100644
--- 
a/streampipes-data-explorer-influx/src/test/java/org/apache/streampipes/dataexplorer/influx/utils/ProvidedQueryParameterBuilder.java
+++ 
b/streampipes-data-explorer-influx/src/test/java/org/apache/streampipes/dataexplorer/influx/utils/ProvidedQueryParameterBuilder.java
@@ -68,6 +68,12 @@ public class ProvidedQueryParameterBuilder {
     return this;
   }
 
+  public ProvidedQueryParameterBuilder withTimeInterval(String timeInterval) {
+    this.queryParams.put(SupportedRestQueryParams.QP_TIME_INTERVAL, 
timeInterval);
+
+    return this;
+  }
+
   public ProvidedQueryParameterBuilder withFilter(String filter) {
     this.queryParams.put(SupportedRestQueryParams.QP_FILTER, filter);
 
diff --git 
a/streampipes-data-explorer-influx/src/main/java/org/apache/streampipes/dataexplorer/influx/InfluxDbReservedKeywords.java
 
b/streampipes-data-explorer/src/main/java/org/apache/streampipes/dataexplorer/InfluxDbReservedKeywords.java
similarity index 97%
rename from 
streampipes-data-explorer-influx/src/main/java/org/apache/streampipes/dataexplorer/influx/InfluxDbReservedKeywords.java
rename to 
streampipes-data-explorer/src/main/java/org/apache/streampipes/dataexplorer/InfluxDbReservedKeywords.java
index df4357eb6e..98cf8af664 100644
--- 
a/streampipes-data-explorer-influx/src/main/java/org/apache/streampipes/dataexplorer/influx/InfluxDbReservedKeywords.java
+++ 
b/streampipes-data-explorer/src/main/java/org/apache/streampipes/dataexplorer/InfluxDbReservedKeywords.java
@@ -16,7 +16,7 @@
  *
  */
 
-package org.apache.streampipes.dataexplorer.influx;
+package org.apache.streampipes.dataexplorer;
 
 import java.util.Arrays;
 import java.util.List;
diff --git 
a/streampipes-data-explorer/src/main/java/org/apache/streampipes/dataexplorer/param/ProvidedRestQueryParamConverter.java
 
b/streampipes-data-explorer/src/main/java/org/apache/streampipes/dataexplorer/param/ProvidedRestQueryParamConverter.java
index f767d1b79b..90569a26d2 100644
--- 
a/streampipes-data-explorer/src/main/java/org/apache/streampipes/dataexplorer/param/ProvidedRestQueryParamConverter.java
+++ 
b/streampipes-data-explorer/src/main/java/org/apache/streampipes/dataexplorer/param/ProvidedRestQueryParamConverter.java
@@ -77,13 +77,7 @@ public class ProvidedRestQueryParamConverter {
 
     if (params.has(SupportedRestQueryParams.QP_TIME_INTERVAL)) {
       String timeInterval = 
params.getAsString(SupportedRestQueryParams.QP_TIME_INTERVAL);
-      if (!params.has(SupportedRestQueryParams.QP_GROUP_BY)) {
-        
queryParameters.withGroupByTimeParams(GroupByTimeClauseParams.from(timeInterval));
-      } else {
-        params.update(SupportedRestQueryParams.QP_GROUP_BY,
-                      params.getAsString(SupportedRestQueryParams.QP_GROUP_BY) 
+ ",time(" + timeInterval + ")");
-      }
-
+      
queryParameters.withGroupByTimeParams(GroupByTimeClauseParams.from(timeInterval));
       queryParameters.withFillParams(FillClauseParams.from());
     }
 
diff --git 
a/streampipes-data-explorer/src/main/java/org/apache/streampipes/dataexplorer/param/model/GroupByTagsClauseParams.java
 
b/streampipes-data-explorer/src/main/java/org/apache/streampipes/dataexplorer/param/model/GroupByTagsClauseParams.java
index 3a4c8fd84d..55238384fa 100644
--- 
a/streampipes-data-explorer/src/main/java/org/apache/streampipes/dataexplorer/param/model/GroupByTagsClauseParams.java
+++ 
b/streampipes-data-explorer/src/main/java/org/apache/streampipes/dataexplorer/param/model/GroupByTagsClauseParams.java
@@ -30,7 +30,9 @@ public class GroupByTagsClauseParams implements 
IQueryStatement {
 
   public GroupByTagsClauseParams(String groupingTagsSeparatedByComma) {
     this.groupingTags = new ArrayList<>();
-    
this.groupingTags.addAll(Arrays.asList(groupingTagsSeparatedByComma.split(",")));
+    Arrays.stream(groupingTagsSeparatedByComma.split(","))
+        .map(InfluxQueryParameterValidator::requireSafeIdentifier)
+        .forEach(this.groupingTags::add);
   }
 
   public static GroupByTagsClauseParams from(String 
groupingTagsSeparatedByComma) {
diff --git 
a/streampipes-data-explorer/src/main/java/org/apache/streampipes/dataexplorer/param/model/GroupByTimeClauseParams.java
 
b/streampipes-data-explorer/src/main/java/org/apache/streampipes/dataexplorer/param/model/GroupByTimeClauseParams.java
index aa3f3f9422..be1d2338c8 100644
--- 
a/streampipes-data-explorer/src/main/java/org/apache/streampipes/dataexplorer/param/model/GroupByTimeClauseParams.java
+++ 
b/streampipes-data-explorer/src/main/java/org/apache/streampipes/dataexplorer/param/model/GroupByTimeClauseParams.java
@@ -25,7 +25,7 @@ public class GroupByTimeClauseParams implements 
IQueryStatement {
   private final String timeInterval;
 
   public GroupByTimeClauseParams(String timeInterval) {
-    this.timeInterval = timeInterval;
+    this.timeInterval = 
InfluxQueryParameterValidator.requireSafeTimeInterval(timeInterval);
   }
 
   public static GroupByTimeClauseParams from(String timeInterval) {
diff --git 
a/streampipes-data-explorer/src/main/java/org/apache/streampipes/dataexplorer/param/model/InfluxQueryParameterValidator.java
 
b/streampipes-data-explorer/src/main/java/org/apache/streampipes/dataexplorer/param/model/InfluxQueryParameterValidator.java
new file mode 100644
index 0000000000..750d1b3ac8
--- /dev/null
+++ 
b/streampipes-data-explorer/src/main/java/org/apache/streampipes/dataexplorer/param/model/InfluxQueryParameterValidator.java
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ *
+ */
+
+package org.apache.streampipes.dataexplorer.param.model;
+
+import org.apache.streampipes.dataexplorer.InfluxDbReservedKeywords;
+
+import java.util.regex.Pattern;
+
+final class InfluxQueryParameterValidator {
+
+  private static final Pattern SAFE_TIME_INTERVAL = 
Pattern.compile("^\\d+(ms|s|m|h|d|w)$");
+  private static final Pattern SAFE_IDENTIFIER = 
Pattern.compile("^[^\\s,;()\"']+$");
+
+  private InfluxQueryParameterValidator() {
+  }
+
+  static String requireSafeTimeInterval(String timeInterval) {
+    if (timeInterval == null || 
!SAFE_TIME_INTERVAL.matcher(timeInterval).matches()) {
+      throw new IllegalArgumentException("Invalid time interval format: " + 
timeInterval);
+    }
+
+    return timeInterval;
+  }
+
+  static String requireSafeIdentifier(String identifier) {
+    if (identifier == null
+        || !SAFE_IDENTIFIER.matcher(identifier).matches()
+        || InfluxDbReservedKeywords.KEYWORD_LIST.stream().anyMatch(k -> 
k.equalsIgnoreCase(identifier))) {
+      throw new IllegalArgumentException("Invalid group by identifier: " + 
identifier);
+    }
+
+    return identifier;
+  }
+}
diff --git 
a/streampipes-data-explorer/src/test/java/org/apache/streampipes/dataexplorer/param/model/InfluxQueryParameterValidatorTest.java
 
b/streampipes-data-explorer/src/test/java/org/apache/streampipes/dataexplorer/param/model/InfluxQueryParameterValidatorTest.java
new file mode 100644
index 0000000000..e97674432b
--- /dev/null
+++ 
b/streampipes-data-explorer/src/test/java/org/apache/streampipes/dataexplorer/param/model/InfluxQueryParameterValidatorTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ *
+ */
+
+package org.apache.streampipes.dataexplorer.param.model;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class InfluxQueryParameterValidatorTest {
+
+  @Test
+  public void testAcceptsSafeTimeIntervals() {
+    assertDoesNotThrow(() -> 
InfluxQueryParameterValidator.requireSafeTimeInterval("1ms"));
+    assertDoesNotThrow(() -> 
InfluxQueryParameterValidator.requireSafeTimeInterval("1s"));
+    assertDoesNotThrow(() -> 
InfluxQueryParameterValidator.requireSafeTimeInterval("1m"));
+    assertDoesNotThrow(() -> 
InfluxQueryParameterValidator.requireSafeTimeInterval("1h"));
+    assertDoesNotThrow(() -> 
InfluxQueryParameterValidator.requireSafeTimeInterval("1d"));
+    assertDoesNotThrow(() -> 
InfluxQueryParameterValidator.requireSafeTimeInterval("1w"));
+  }
+
+  @Test
+  public void testRejectsUnsafeTimeIntervals() {
+    assertThrows(IllegalArgumentException.class,
+        () -> InfluxQueryParameterValidator.requireSafeTimeInterval("1 h"));
+    assertThrows(IllegalArgumentException.class,
+        () -> InfluxQueryParameterValidator.requireSafeTimeInterval("1H"));
+    assertThrows(IllegalArgumentException.class,
+        () -> InfluxQueryParameterValidator.requireSafeTimeInterval("-1h"));
+    assertThrows(IllegalArgumentException.class,
+        () -> InfluxQueryParameterValidator.requireSafeTimeInterval("1month"));
+    assertThrows(IllegalArgumentException.class,
+        () -> InfluxQueryParameterValidator.requireSafeTimeInterval("1h)"));
+    assertThrows(IllegalArgumentException.class,
+        () -> 
InfluxQueryParameterValidator.requireSafeTimeInterval("1h;SHOW"));
+  }
+
+  @Test
+  public void testAcceptsSafeIdentifiers() {
+    assertDoesNotThrow(() -> 
InfluxQueryParameterValidator.requireSafeIdentifier("sensorId"));
+    assertDoesNotThrow(() -> 
InfluxQueryParameterValidator.requireSafeIdentifier("_sensorId"));
+    assertDoesNotThrow(() -> 
InfluxQueryParameterValidator.requireSafeIdentifier("sensor_id_2"));
+    assertDoesNotThrow(() -> 
InfluxQueryParameterValidator.requireSafeIdentifier("sensor-id"));
+    assertDoesNotThrow(() -> 
InfluxQueryParameterValidator.requireSafeIdentifier("sensor:id"));
+    assertDoesNotThrow(() -> 
InfluxQueryParameterValidator.requireSafeIdentifier("sensor$id"));
+    assertDoesNotThrow(() -> 
InfluxQueryParameterValidator.requireSafeIdentifier("sensor$id"));
+  }
+
+  @Test
+  public void testRejectsUnsafeIdentifiers() {
+    assertThrows(IllegalArgumentException.class,
+        () -> InfluxQueryParameterValidator.requireSafeIdentifier("time(1h)"));
+    assertThrows(IllegalArgumentException.class,
+        () -> InfluxQueryParameterValidator.requireSafeIdentifier("sensor 
id"));
+    assertThrows(IllegalArgumentException.class,
+        () -> InfluxQueryParameterValidator.requireSafeIdentifier("SHOW"));
+    assertThrows(IllegalArgumentException.class,
+        () -> InfluxQueryParameterValidator.requireSafeIdentifier("show"));
+    assertThrows(IllegalArgumentException.class,
+        () -> 
InfluxQueryParameterValidator.requireSafeIdentifier("sensorId;SHOW"));
+  }
+}
diff --git 
a/ui/src/app/dashboard/components/panel/dashboard-panel.component.html 
b/ui/src/app/dashboard/components/panel/dashboard-panel.component.html
index e7daf7b908..d524dee1cd 100644
--- a/ui/src/app/dashboard/components/panel/dashboard-panel.component.html
+++ b/ui/src/app/dashboard/components/panel/dashboard-panel.component.html
@@ -68,7 +68,7 @@
                     class="designer-panel-container h-100 dashboard-grid"
                 >
                     <mat-drawer
-                        [opened]="editMode"
+                        [opened]="editMode && chartSelectionPanelExpanded"
                         mode="side"
                         position="end"
                         class="designer-panel"
@@ -85,7 +85,40 @@
                             }
                         </div>
                     </mat-drawer>
-                    <mat-drawer-content class="h-100 dashboard-grid">
+                    <mat-drawer-content
+                        class="h-100 dashboard-grid dashboard-content"
+                    >
+                        @if (editMode) {
+                            <button
+                                mat-icon-button
+                                type="button"
+                                class="panel-toggle-button"
+                                [class.panel-toggle-button-collapsed]="
+                                    !chartSelectionPanelExpanded
+                                "
+                                (click)="toggleChartSelectionPanel()"
+                                [attr.aria-label]="
+                                    (chartSelectionPanelExpanded
+                                        ? 'Collapse chart selection panel'
+                                        : 'Expand chart selection panel'
+                                    ) | translate
+                                "
+                                [matTooltip]="
+                                    (chartSelectionPanelExpanded
+                                        ? 'Collapse chart selection panel'
+                                        : 'Expand chart selection panel'
+                                    ) | translate
+                                "
+                            >
+                                <mat-icon>
+                                    {{
+                                        chartSelectionPanelExpanded
+                                            ? 'chevron_right'
+                                            : 'chevron_left'
+                                    }}
+                                </mat-icon>
+                            </button>
+                        }
                         @if (dashboard.widgets.length === 0) {
                             <div
                                 fxFlex="100"
diff --git 
a/ui/src/app/dashboard/components/panel/dashboard-panel.component.scss 
b/ui/src/app/dashboard/components/panel/dashboard-panel.component.scss
index 6b94dd13a0..127f137aa2 100644
--- a/ui/src/app/dashboard/components/panel/dashboard-panel.component.scss
+++ b/ui/src/app/dashboard/components/panel/dashboard-panel.component.scss
@@ -34,6 +34,10 @@
     flex: 1 1 100%;
 }
 
+.dashboard-content {
+    position: relative;
+}
+
 .designer-panel-container {
     width: 100%;
     height: 100%;
@@ -43,6 +47,19 @@
     width: 350px;
 }
 
+.panel-toggle-button {
+    position: absolute;
+    top: 1rem;
+    right: 0.5rem;
+    z-index: 2;
+    background: var(--mat-sys-surface, #fff);
+    box-shadow: 0 0.125rem 0.5rem rgb(0 0 0 / 15%);
+}
+
+.panel-toggle-button-collapsed {
+    right: 1rem;
+}
+
 .edit-menu-btn {
     margin-right: 5px;
 }
diff --git a/ui/src/app/dashboard/components/panel/dashboard-panel.component.ts 
b/ui/src/app/dashboard/components/panel/dashboard-panel.component.ts
index 1c7fa97363..b508838153 100644
--- a/ui/src/app/dashboard/components/panel/dashboard-panel.component.ts
+++ b/ui/src/app/dashboard/components/panel/dashboard-panel.component.ts
@@ -62,6 +62,9 @@ import {
     MatDrawerContainer,
     MatDrawerContent,
 } from '@angular/material/sidenav';
+import { MatIcon } from '@angular/material/icon';
+import { MatIconButton } from '@angular/material/button';
+import { MatTooltip } from '@angular/material/tooltip';
 import { ChartSelectionPanelComponent } from 
'./chart-selection-panel/chart-selection-panel.component';
 import {
     FlexDirective,
@@ -81,6 +84,9 @@ import {
         DashboardToolbarComponent,
         MatDrawerContainer,
         MatDrawer,
+        MatIcon,
+        MatIconButton,
+        MatTooltip,
         ChartSelectionPanelComponent,
         MatDrawerContent,
         DashboardGridViewComponent,
@@ -104,6 +110,7 @@ export class DashboardPanelComponent
     viewMode = 'grid';
 
     editMode = false;
+    chartSelectionPanelExpanded = false;
     timeRangeVisible = true;
 
     _dashboardGrid: DashboardGridViewComponent;
@@ -159,7 +166,7 @@ export class DashboardPanelComponent
                 UserPrivilege.PRIVILEGE_WRITE_DASHBOARD,
             );
             if (queryParams.editMode && this.hasDashboardWritePrivileges) {
-                this.editMode = true;
+                this.triggerEditMode();
             }
         });
     }
@@ -270,6 +277,11 @@ export class DashboardPanelComponent
 
     triggerEditMode() {
         this.editMode = true;
+        this.chartSelectionPanelExpanded = true;
+    }
+
+    toggleChartSelectionPanel() {
+        this.chartSelectionPanelExpanded = !this.chartSelectionPanelExpanded;
     }
 
     deleteDashboard() {

Reply via email to