You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@streampipes.apache.org by ri...@apache.org on 2024/07/25 08:45:26 UTC

(streampipes) branch 3059-extend-capabilities-of-time-selection-bar created (now 7075d02814)

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

riemer pushed a change to branch 3059-extend-capabilities-of-time-selection-bar
in repository https://gitbox.apache.org/repos/asf/streampipes.git


      at 7075d02814 feat(#3059): Replace time selection bar with menu

This branch includes the following new commits:

     new 7075d02814 feat(#3059): Replace time selection bar with menu

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(streampipes) 01/01: feat(#3059): Replace time selection bar with menu

Posted by ri...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

riemer pushed a commit to branch 3059-extend-capabilities-of-time-selection-bar
in repository https://gitbox.apache.org/repos/asf/streampipes.git

commit 7075d028142c7e63827ed2c21644dc2cae746e09
Author: Dominik Riemer <do...@gmail.com>
AuthorDate: Thu Jul 25 10:45:02 2024 +0200

    feat(#3059): Replace time selection bar with menu
---
 ui/cypress/support/utils/datalake/DataLakeUtils.ts |  40 ++++-
 .../tests/datalake/timeRangeSelectors.spec.ts      | 160 +++++++++---------
 .../lib/apis/data-view-data-explorer.service.ts    |   4 +
 .../src/lib/model/datalake/DateRange.ts            |  31 ++++
 .../dashboard-toolbar.component.html               |   4 +-
 .../dashboard-toolbar.component.ts                 |   3 +
 .../data-view-preview.component.html               |   4 +-
 .../data-explorer-dashboard-panel.component.html   |   3 +
 .../data-explorer-dashboard-panel.component.ts     |  13 +-
 .../data-view/data-explorer-data-view.component.ts |   8 +-
 .../data-explorer-designer-panel.component.html    |   9 --
 .../data-explorer-designer-panel.component.ts      |  19 ---
 .../data-explorer-data-view-toolbar.component.html |   2 +-
 ...data-explorer-dashboard-overview.component.html |   2 +-
 .../data-explorer-dashboard-overview.component.ts  |   4 +-
 .../overview/data-explorer-overview.component.ts   |   6 +-
 .../time-range-selector.component.html             |  65 ++++++++
 ...ent.scss => time-range-selector.component.scss} |  15 ++
 .../time-selector/time-range-selector.component.ts | 143 ++++++++++++++++
 .../custom-time-range-selection.component.html     |  87 ++++++++++
 .../custom-time-range-selection.component.scss}    |  27 +---
 .../custom-time-range-selection.component.ts       | 107 ++++++++++++
 .../time-selector-menu.component.html              |  53 ++++++
 .../time-selector-menu.component.scss}             |  33 ++--
 .../time-selector-menu.component.ts                |  69 ++++++++
 .../time-selector/timeRangeSelector.component.html |  82 ----------
 .../time-selector/timeRangeSelector.component.ts   | 180 ---------------------
 .../widgets/table/table-widget.component.ts        |  12 +-
 ui/src/app/data-explorer/data-explorer.module.ts   |  18 ++-
 ...-explorer-edit-dashboard-dialog.component.html} |  47 +++---
 ...-explorer-edit-dashboard-dialog.component.scss} |   0
 ...ta-explorer-edit-dashboard-dialog.component.ts} |  18 ++-
 .../services/data-explorer-dashboard.service.ts    |  12 +-
 .../services/time-selection.service.ts             | 123 +++++++++++++-
 34 files changed, 938 insertions(+), 465 deletions(-)

diff --git a/ui/cypress/support/utils/datalake/DataLakeUtils.ts b/ui/cypress/support/utils/datalake/DataLakeUtils.ts
index dd52f048e4..0dee52c86d 100644
--- a/ui/cypress/support/utils/datalake/DataLakeUtils.ts
+++ b/ui/cypress/support/utils/datalake/DataLakeUtils.ts
@@ -24,6 +24,7 @@ import { FileManagementUtils } from '../FileManagementUtils';
 import { ConnectUtils } from '../connect/ConnectUtils';
 import { ConnectBtns } from '../connect/ConnectBtns';
 import { AdapterBuilder } from '../../builder/AdapterBuilder';
+import { differenceInMonths } from 'date-fns';
 
 export class DataLakeUtils {
     public static goToDatalake() {
@@ -349,8 +350,41 @@ export class DataLakeUtils {
     }
 
     public static selectTimeRange(from: Date, to: Date) {
-        DataLakeUtils.setTimeInput('time-range-from', from);
-        DataLakeUtils.setTimeInput('time-range-to', to);
+        DataLakeUtils.openTimeSelectorMenu();
+        const monthsBack = Math.abs(differenceInMonths(from, new Date()));
+        DataLakeUtils.navigateCalendar('previous', monthsBack);
+        DataLakeUtils.selectDay(from.getDay());
+
+        const monthsForward = Math.abs(differenceInMonths(from, to));
+        DataLakeUtils.navigateCalendar('next', monthsForward);
+
+        DataLakeUtils.selectDay(to.getDay());
+
+        DataLakeUtils.setTimeInput('time-selector-start-time', from);
+        DataLakeUtils.setTimeInput('time-selector-end-time', to);
+        DataLakeUtils.applyCustomTimeSelection();
+    }
+
+    public static navigateCalendar(direction: string, numberOfMonths: number) {
+        for (let i = 0; i < numberOfMonths; i++) {
+            cy.get(`button.mat-calendar-${direction}-button`).click();
+        }
+    }
+
+    public static selectDay(day: number) {
+        cy.get(
+            `button:has(span.mat-calendar-body-cell-content:contains("${day}"))`,
+        )
+            .first()
+            .click();
+    }
+
+    public static openTimeSelectorMenu() {
+        cy.dataCy('time-selector-menu').click();
+    }
+
+    public static applyCustomTimeSelection() {
+        cy.dataCy('apply-custom-time').click();
     }
 
     public static setTimeInput(field: string, date: Date) {
@@ -358,7 +392,7 @@ export class DataLakeUtils {
     }
 
     public static makeTimeString(date: Date) {
-        return date.toISOString().slice(0, 16);
+        return date.toTimeString().slice(0, 5);
     }
 
     public static getFutureDate() {
diff --git a/ui/cypress/tests/datalake/timeRangeSelectors.spec.ts b/ui/cypress/tests/datalake/timeRangeSelectors.spec.ts
index fd60b73af7..f83ae8ee6b 100644
--- a/ui/cypress/tests/datalake/timeRangeSelectors.spec.ts
+++ b/ui/cypress/tests/datalake/timeRangeSelectors.spec.ts
@@ -17,20 +17,29 @@
  */
 
 import { DataLakeUtils } from '../../support/utils/datalake/DataLakeUtils';
+import {
+    subDays,
+    subHours,
+    subMinutes,
+    subMonths,
+    subWeeks,
+    subYears,
+} from 'date-fns';
 
 describe('Test Time Range Selectors in Data Explorer', () => {
     const periods = [
-        { selector: '15_min', offset: 15 },
-        { selector: '1_hour', offset: 60 },
-        { selector: '1_day', offset: 1440 },
-        { selector: '1_week', offset: 10080 },
-        { selector: '1_month', offset: 43200 },
-        { selector: '1_year', offset: 525600 },
+        { selector: 1, start: (now: Date) => subMinutes(now, 15) },
+        { selector: 2, start: (now: Date) => subHours(now, 1) },
+        { selector: 4, start: (now: Date) => subDays(now, 1) },
+        { selector: 6, start: (now: Date) => subWeeks(now, 1) },
+        { selector: 8, start: (now: Date) => subMonths(now, 1) },
+        { selector: 10, start: (now: Date) => subYears(now, 1) },
     ];
 
-    const dateAttribute = 'ng-reflect-date';
-    const timeRangeFrom = 'time-range-from';
-    const timeRangeTo = 'time-range-to';
+    const timeRangeFrom = 'time-selector-start-time';
+    const timeRangeTo = 'time-selector-end-time';
+    const dateRangeFrom = 'time-selector-start-date';
+    const dateRangeTo = 'time-selector-end-date';
 
     before('Setup Tests', () => {
         cy.initStreamPipesTest();
@@ -42,74 +51,59 @@ describe('Test Time Range Selectors in Data Explorer', () => {
     it('Perform Test', () => {
         periods.forEach(period => {
             cy.log('Testing period: ' + period.selector);
+            DataLakeUtils.openTimeSelectorMenu();
             // Choosing time period and saving initial start and end dates
-            cy.dataCy(period.selector).click();
-            cy.dataCy(timeRangeTo)
-                .invoke('attr', dateAttribute)
-                .then(initialEndDateString => {
-                    cy.dataCy(timeRangeFrom)
-                        .invoke('attr', dateAttribute)
-                        .then(initialStartDateString => {
-                            const initialStartDate = parseDate(
-                                initialStartDateString,
-                            );
-                            const initialEndDate =
-                                parseDate(initialEndDateString);
-
-                            cy.wrap(initialStartDate).should(
-                                'deep.eq',
-                                getExpectedStartDate(
-                                    initialEndDate,
-                                    period.offset,
-                                ),
-                            );
-                            // Updating time range to previous one and checking start and end dates
-                            cy.dataCy('decrease-time-button').click({
-                                force: true,
-                            });
-                            cy.dataCy(timeRangeTo)
-                                .invoke('attr', dateAttribute)
-                                .then(updatedEndDateString => {
-                                    cy.dataCy(timeRangeFrom)
-                                        .invoke('attr', dateAttribute)
-                                        .then(updatedStartDateString => {
-                                            const updatedStartDate = parseDate(
-                                                updatedStartDateString,
-                                            );
-                                            const updatedEndDate =
-                                                parseDate(updatedEndDateString);
+            cy.dataCy(`time-selector-quick-${period.selector}`).click();
+            const expectedEndDate = new Date();
+            DataLakeUtils.openTimeSelectorMenu();
+            // check if dates can differ from the selected dates
+            const expectedStartDate = getExpectedStartDate(
+                expectedEndDate,
+                period.start,
+            );
+            cy.dataCy(dateRangeFrom).should(
+                'have.text',
+                getLocalizedDateString(expectedStartDate),
+            );
+            cy.dataCy(dateRangeTo).should(
+                'have.text',
+                getLocalizedDateString(expectedEndDate),
+            );
 
-                                            cy.wrap(updatedStartDate).should(
-                                                'deep.eq',
-                                                getExpectedStartDate(
-                                                    updatedEndDate,
-                                                    period.offset,
-                                                ),
-                                            );
-                                            cy.wrap(updatedEndDate).should(
-                                                'deep.eq',
-                                                initialStartDate,
-                                            );
-                                        });
-                                });
-                            // Updating time range to the next one and comparing start and end dates with initial values
-                            cy.dataCy('increase-time-button').click({
-                                force: true,
-                            });
-                            cy.dataCy(timeRangeFrom)
-                                .invoke('attr', dateAttribute)
-                                .should('eq', initialStartDateString);
-                            cy.dataCy(timeRangeTo)
-                                .invoke('attr', dateAttribute)
-                                .should('eq', initialEndDateString);
-                        });
+            cy.dataCy(timeRangeFrom)
+                .invoke('val')
+                .then(actualTime => {
+                    const expectedDate =
+                        getLocalizedTimeString(expectedStartDate);
+                    expect(
+                        isTimeWithinTolerance(
+                            actualTime as string,
+                            expectedDate,
+                            3,
+                        ),
+                    ).to.be.true;
+                });
+            cy.dataCy(timeRangeTo)
+                .invoke('val')
+                .then(actualTime => {
+                    const expectedDate =
+                        getLocalizedTimeString(expectedEndDate);
+                    expect(
+                        isTimeWithinTolerance(
+                            actualTime as string,
+                            expectedDate,
+                            3,
+                        ),
+                    ).to.be.true;
                 });
+
+            DataLakeUtils.applyCustomTimeSelection();
         });
     });
 });
 
-function getExpectedStartDate(endDate: Date, offset: number): Date {
-    const startDate = new Date(endDate.getTime() - offset * 60000);
+function getExpectedStartDate(endDate: Date, startFn: (Date) => Date): Date {
+    const startDate = startFn(endDate);
     startDate.setMinutes(
         startDate.getMinutes() + getTimezoneDifference(endDate, startDate),
     );
@@ -120,6 +114,28 @@ function getTimezoneDifference(endDate: Date, startDate: Date): number {
     return endDate.getTimezoneOffset() - startDate.getTimezoneOffset();
 }
 
-function parseDate(dateString: string): Date {
-    return new Date(Date.parse(dateString));
+function getLocalizedDateString(date: Date) {
+    return date.toLocaleDateString();
+}
+
+function getLocalizedTimeString(date: Date) {
+    return date.toLocaleTimeString();
+}
+
+function parseTimeStringToSeconds(timeString: string) {
+    const [hours, minutes, seconds] = timeString.split(':').map(Number);
+    return hours * 3600 + minutes * 60 + seconds || 0;
+}
+
+function isTimeWithinTolerance(
+    actualTimeString: string,
+    expectedTimeString: string,
+    toleranceInSeconds: number,
+) {
+    const actualTimeInSeconds = parseTimeStringToSeconds(actualTimeString);
+    const expectedTimeInSeconds = parseTimeStringToSeconds(expectedTimeString);
+    return (
+        Math.abs(actualTimeInSeconds - expectedTimeInSeconds) <=
+        toleranceInSeconds
+    );
 }
diff --git a/ui/projects/streampipes/platform-services/src/lib/apis/data-view-data-explorer.service.ts b/ui/projects/streampipes/platform-services/src/lib/apis/data-view-data-explorer.service.ts
index 68c1988cb4..af101d07a0 100644
--- a/ui/projects/streampipes/platform-services/src/lib/apis/data-view-data-explorer.service.ts
+++ b/ui/projects/streampipes/platform-services/src/lib/apis/data-view-data-explorer.service.ts
@@ -40,6 +40,10 @@ export class DataViewDataExplorerService {
         return this.sharedDatalakeRestService.getDashboards(this.dashboardUrl);
     }
 
+    getDashboard(dashboardId: string): Observable<Dashboard> {
+        return this.http.get(`${this.dashboardUrl}/${dashboardId}`);
+    }
+
     updateDashboard(dashboard: Dashboard): Observable<Dashboard> {
         return this.sharedDatalakeRestService.updateDashboard(
             this.dashboardUrl,
diff --git a/ui/projects/streampipes/platform-services/src/lib/model/datalake/DateRange.ts b/ui/projects/streampipes/platform-services/src/lib/model/datalake/DateRange.ts
index 87151909ca..0a3eb650f1 100644
--- a/ui/projects/streampipes/platform-services/src/lib/model/datalake/DateRange.ts
+++ b/ui/projects/streampipes/platform-services/src/lib/model/datalake/DateRange.ts
@@ -20,6 +20,37 @@ export interface TimeSettings {
     startTime: number;
     endTime: number;
     dynamicSelection: 15 | 60 | 1440 | 10080 | 43800 | 525600 | -1;
+    timeSelectionId?: TimeSelectionId;
+}
+
+export interface TimeString {
+    startDate: string;
+    startTime: string;
+    endDate: string;
+    endTime: string;
+}
+
+export enum TimeSelectionId {
+    CUSTOM,
+    LAST_15_MINUTES,
+    LAST_HOUR,
+    CURRENT_HOUR,
+    LAST_DAY,
+    CURRENT_DAY,
+    LAST_WEEK,
+    CURRENT_WEEK,
+    LAST_MONTH,
+    CURRENT_MONTH,
+    LAST_YEAR,
+    CURRENT_YEAR,
+}
+
+export interface QuickTimeSelection {
+    label: string;
+    timeSelectionId: TimeSelectionId;
+    startTime: (now: Date) => Date;
+    endTime: (now: Date) => Date;
+    addDividerAfter?: boolean;
 }
 
 export class DateRange {
diff --git a/ui/src/app/data-explorer/components/dashboard/dashboard-toolbar/dashboard-toolbar.component.html b/ui/src/app/data-explorer/components/dashboard/dashboard-toolbar/dashboard-toolbar.component.html
index ff1b2ee7b6..56ad0025db 100644
--- a/ui/src/app/data-explorer/components/dashboard/dashboard-toolbar/dashboard-toolbar.component.html
+++ b/ui/src/app/data-explorer/components/dashboard/dashboard-toolbar/dashboard-toolbar.component.html
@@ -78,9 +78,9 @@
     fxLayout="row"
 >
     <sp-time-range-selector
-        *ngIf="editMode || timeRangeVisible"
+        *ngIf="(editMode || timeRangeVisible) && globalTimeEnabled"
         (dateRangeEmitter)="updateDateRangeEmitter.emit($event)"
-        [dateRange]="timeSettings"
+        [timeSettings]="timeSettings"
     >
     </sp-time-range-selector>
 
diff --git a/ui/src/app/data-explorer/components/dashboard/dashboard-toolbar/dashboard-toolbar.component.ts b/ui/src/app/data-explorer/components/dashboard/dashboard-toolbar/dashboard-toolbar.component.ts
index e2da2c238d..e0e64fa740 100644
--- a/ui/src/app/data-explorer/components/dashboard/dashboard-toolbar/dashboard-toolbar.component.ts
+++ b/ui/src/app/data-explorer/components/dashboard/dashboard-toolbar/dashboard-toolbar.component.ts
@@ -43,6 +43,9 @@ export class DataExplorerDashboardToolbarComponent {
     @Input()
     timeSettings: TimeSettings;
 
+    @Input()
+    globalTimeEnabled = true;
+
     @Output()
     viewModeChange: EventEmitter<string> = new EventEmitter<string>();
 
diff --git a/ui/src/app/data-explorer/components/dashboard/dashboard-widget-selection-panel/data-view-selection/data-view-preview/data-view-preview.component.html b/ui/src/app/data-explorer/components/dashboard/dashboard-widget-selection-panel/data-view-selection/data-view-preview/data-view-preview.component.html
index dc77d00e81..e6727f9e1b 100644
--- a/ui/src/app/data-explorer/components/dashboard/dashboard-widget-selection-panel/data-view-selection/data-view-preview/data-view-preview.component.html
+++ b/ui/src/app/data-explorer/components/dashboard/dashboard-widget-selection-panel/data-view-selection/data-view-preview/data-view-preview.component.html
@@ -21,7 +21,7 @@
         <h5>{{ dataView.baseAppearanceConfig.widgetTitle }}</h5>
         <div fxLayout="row" fxLayoutGap="15px" class="data-view-preview-info">
             <span fxLayoutAlign="start center"
-                ><i class="material-icons color-primary">insert_chart</i></span
+                ><mat-icon color="primary">insert_chart</mat-icon></span
             >
             <span fxFlex fxLayoutAlign="start center">{{
                 widgetTypeLabel
@@ -29,7 +29,7 @@
         </div>
         <div fxLayout="row" fxLayoutGap="15px" class="data-view-preview-info">
             <span fxLayoutAlign="start center"
-                ><i class="material-icons">folder</i></span
+                ><mat-icon color="primary">folder</mat-icon></span
             >
             <span fxFlex fxLayoutAlign="start center">{{
                 dataView.dataConfig.sourceConfigs[0].measureName
diff --git a/ui/src/app/data-explorer/components/dashboard/data-explorer-dashboard-panel.component.html b/ui/src/app/data-explorer/components/dashboard/data-explorer-dashboard-panel.component.html
index f5e4d0e2be..0a02874ae2 100644
--- a/ui/src/app/data-explorer/components/dashboard/data-explorer-dashboard-panel.component.html
+++ b/ui/src/app/data-explorer/components/dashboard/data-explorer-dashboard-panel.component.html
@@ -23,6 +23,9 @@
 >
     <div nav fxFlex="100" class="ml-10">
         <sp-data-explorer-dashboard-toolbar
+            [globalTimeEnabled]="
+                dashboard.dashboardGeneralSettings.globalTimeEnabled
+            "
             [editMode]="editMode"
             [(viewMode)]="viewMode"
             [hasDataExplorerDeletePrivileges]="hasDataExplorerDeletePrivileges"
diff --git a/ui/src/app/data-explorer/components/dashboard/data-explorer-dashboard-panel.component.ts b/ui/src/app/data-explorer/components/dashboard/data-explorer-dashboard-panel.component.ts
index d29b19a2bd..55b6557584 100644
--- a/ui/src/app/data-explorer/components/dashboard/data-explorer-dashboard-panel.component.ts
+++ b/ui/src/app/data-explorer/components/dashboard/data-explorer-dashboard-panel.component.ts
@@ -179,10 +179,8 @@ export class DataExplorerDashboardPanelComponent implements OnInit, OnDestroy {
     }
 
     getDashboard(dashboardId: string, startTime: number, endTime: number) {
-        this.dataViewService.getDataViews().subscribe(data => {
-            this.dashboard = data.filter(
-                dashboard => dashboard.elementId === dashboardId,
-            )[0];
+        this.dataViewService.getDashboard(dashboardId).subscribe(dashboard => {
+            this.dashboard = dashboard;
             this.breadcrumbService.updateBreadcrumb(
                 this.breadcrumbService.makeRoute(
                     [SpDataExplorerRoutes.BASE],
@@ -192,6 +190,13 @@ export class DataExplorerDashboardPanelComponent implements OnInit, OnDestroy {
             this.viewMode =
                 this.dashboard.dashboardGeneralSettings.defaultViewMode ||
                 'grid';
+            if (
+                this.dashboard.dashboardGeneralSettings.globalTimeEnabled ===
+                undefined
+            ) {
+                this.dashboard.dashboardGeneralSettings.globalTimeEnabled =
+                    true;
+            }
             this.timeSettings =
                 startTime && endTime
                     ? this.overrideTime(+startTime, +endTime)
diff --git a/ui/src/app/data-explorer/components/data-view/data-explorer-data-view.component.ts b/ui/src/app/data-explorer/components/data-view/data-explorer-data-view.component.ts
index e7c6225ff4..1530403e25 100644
--- a/ui/src/app/data-explorer/components/data-view/data-explorer-data-view.component.ts
+++ b/ui/src/app/data-explorer/components/data-view/data-explorer-data-view.component.ts
@@ -26,7 +26,6 @@ import {
 import { ActivatedRoute } from '@angular/router';
 import { TimeSelectionService } from '../../services/time-selection.service';
 import { DataExplorerRoutingService } from '../../services/data-explorer-routing.service';
-import { DialogService } from '@streampipes/shared-ui';
 import { DataExplorerDashboardService } from '../../services/data-explorer-dashboard.service';
 
 @Component({
@@ -46,7 +45,6 @@ export class DataExplorerDataViewComponent implements OnInit {
     @ViewChild('panel', { static: false }) outerPanel: ElementRef;
 
     constructor(
-        private dialogService: DialogService,
         private dashboardService: DataExplorerDashboardService,
         private route: ActivatedRoute,
         private routingService: DataExplorerRoutingService,
@@ -95,11 +93,7 @@ export class DataExplorerDataViewComponent implements OnInit {
     }
 
     makeDefaultTimeSettings(): TimeSettings {
-        return {
-            dynamicSelection: 1440,
-            endTime: new Date().getTime(),
-            startTime: new Date().getTime() - 1000 * 24 * 60 * 60,
-        };
+        return this.timeSelectionService.getDefaultTimeSettings();
     }
 
     createWidget() {
diff --git a/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-explorer-designer-panel.component.html b/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-explorer-designer-panel.component.html
index 6aa928dba7..7a13a6b959 100644
--- a/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-explorer-designer-panel.component.html
+++ b/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-explorer-designer-panel.component.html
@@ -32,15 +32,6 @@
                     }}
                 </span>
             </div>
-            <div fxFlex fxLayoutAlign="end end">
-                <button
-                    mat-icon-button
-                    matTooltip="Close Widget Configuration"
-                    (click)="closeDesignerPanel()"
-                >
-                    <mat-icon>close</mat-icon>
-                </button>
-            </div>
         </div>
         <div fxFlex="100" fxLayout="column" class="no-overflow">
             <mat-tab-group
diff --git a/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-explorer-designer-panel.component.ts b/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-explorer-designer-panel.component.ts
index f2400158ac..901f51f188 100644
--- a/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-explorer-designer-panel.component.ts
+++ b/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-explorer-designer-panel.component.ts
@@ -74,25 +74,6 @@ export class DataExplorerDesignerPanelComponent {
         });
     }
 
-    modifyWidgetMode(widget: DataExplorerWidgetModel, newWidgetMode: boolean) {
-        this.currentlyConfiguredWidget = widget;
-        this.newWidgetMode = newWidgetMode;
-        if (this.dataSettingsPanel) {
-            setTimeout(() => {
-                this.dataSettingsPanel.checkSourceTypes();
-            });
-        }
-    }
-
-    closeDesignerPanel() {
-        this.closeDesignerPanelEmitter.emit();
-    }
-
-    resetIndex() {
-        this.selectedIndex = 0;
-        this.newWidgetMode = true;
-    }
-
     @ViewChild('dataSettingsPanel')
     public set content(
         dataSettingsPanel: DataExplorerWidgetDataSettingsComponent,
diff --git a/ui/src/app/data-explorer/components/data-view/data-view-toolbar/data-explorer-data-view-toolbar.component.html b/ui/src/app/data-explorer/components/data-view/data-view-toolbar/data-explorer-data-view-toolbar.component.html
index 04ae11e225..3f54d2b57b 100644
--- a/ui/src/app/data-explorer/components/data-view/data-view-toolbar/data-explorer-data-view-toolbar.component.html
+++ b/ui/src/app/data-explorer/components/data-view/data-view-toolbar/data-explorer-data-view-toolbar.component.html
@@ -65,7 +65,7 @@
             <sp-time-range-selector
                 *ngIf="editMode || timeRangeVisible"
                 (dateRangeEmitter)="updateDateRangeEmitter.emit($event)"
-                [dateRange]="timeSettings"
+                [timeSettings]="timeSettings"
             >
             </sp-time-range-selector>
         </div>
diff --git a/ui/src/app/data-explorer/components/overview/data-explorer-dashboard-overview/data-explorer-dashboard-overview.component.html b/ui/src/app/data-explorer/components/overview/data-explorer-dashboard-overview/data-explorer-dashboard-overview.component.html
index 4ada6cdaed..b4a789b851 100644
--- a/ui/src/app/data-explorer/components/overview/data-explorer-dashboard-overview/data-explorer-dashboard-overview.component.html
+++ b/ui/src/app/data-explorer/components/overview/data-explorer-dashboard-overview/data-explorer-dashboard-overview.component.html
@@ -83,7 +83,7 @@
                             color="accent"
                             matTooltip="Edit data view settings"
                             *ngIf="hasDataExplorerWritePrivileges"
-                            (click)="openEditDataViewDialog(element)"
+                            (click)="openEditDashboardDialog(element)"
                         >
                             <i class="material-icons">settings</i>
                         </button>
diff --git a/ui/src/app/data-explorer/components/overview/data-explorer-dashboard-overview/data-explorer-dashboard-overview.component.ts b/ui/src/app/data-explorer/components/overview/data-explorer-dashboard-overview/data-explorer-dashboard-overview.component.ts
index 1ccaeb0f4d..4d6b8ca7dd 100644
--- a/ui/src/app/data-explorer/components/overview/data-explorer-dashboard-overview/data-explorer-dashboard-overview.component.ts
+++ b/ui/src/app/data-explorer/components/overview/data-explorer-dashboard-overview/data-explorer-dashboard-overview.component.ts
@@ -82,9 +82,9 @@ export class SpDataExplorerDashboardOverviewComponent extends SpDataExplorerOver
         });
     }
 
-    openEditDataViewDialog(dashboard: Dashboard) {
+    openEditDashboardDialog(dashboard: Dashboard) {
         const dialogRef =
-            this.dataExplorerDashboardService.openDataViewModificationDialog(
+            this.dataExplorerDashboardService.openDashboardModificationDialog(
                 false,
                 dashboard,
             );
diff --git a/ui/src/app/data-explorer/components/overview/data-explorer-overview.component.ts b/ui/src/app/data-explorer/components/overview/data-explorer-overview.component.ts
index 45d057adc4..2d872f7561 100644
--- a/ui/src/app/data-explorer/components/overview/data-explorer-overview.component.ts
+++ b/ui/src/app/data-explorer/components/overview/data-explorer-overview.component.ts
@@ -62,16 +62,16 @@ export class DataExplorerOverviewComponent extends SpDataExplorerOverviewDirecti
         dataViewDashboard.widgets = [];
         dataViewDashboard.name = '';
 
-        this.openDataViewModificationDialog(true, dataViewDashboard);
+        this.openDashboardModificationDialog(true, dataViewDashboard);
     }
 
     createNewDataView(): void {
         this.routingService.navigateToDataView(true);
     }
 
-    openDataViewModificationDialog(createMode: boolean, dashboard: Dashboard) {
+    openDashboardModificationDialog(createMode: boolean, dashboard: Dashboard) {
         const dialogRef =
-            this.dataExplorerDashboardService.openDataViewModificationDialog(
+            this.dataExplorerDashboardService.openDashboardModificationDialog(
                 createMode,
                 dashboard,
             );
diff --git a/ui/src/app/data-explorer/components/time-selector/time-range-selector.component.html b/ui/src/app/data-explorer/components/time-selector/time-range-selector.component.html
new file mode 100644
index 0000000000..4ca8900256
--- /dev/null
+++ b/ui/src/app/data-explorer/components/time-selector/time-range-selector.component.html
@@ -0,0 +1,65 @@
+<!--
+  ~ 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 fxLayoutAlign="center center">
+    <button mat-icon-button color="accent" (click)="applyPreviousInterval()">
+        <i class="material-icons">navigate_before</i>
+    </button>
+    <button
+        mat-button
+        color="accent"
+        [matMenuTriggerFor]="menu"
+        #menuTrigger="matMenuTrigger"
+        matTooltip="Modify time"
+        data-cy="time-selector-menu"
+        (menuClosed)="menuTrigger.closeMenu()"
+    >
+        <div *ngIf="timeStringMode === 'advanced'">
+            <div class="formatted-datetime">
+                <span class="formatted-date">{{ timeString.startDate }}</span
+                >&nbsp;
+                <span class="formatted-time">{{ timeString.startTime }}</span>
+            </div>
+            -
+            <div class="formatted-datetime">
+                <span class="formatted-date">{{ timeString.endDate }}</span
+                >&nbsp;
+                <span class="formatted-time">{{ timeString.endTime }}</span>
+            </div>
+        </div>
+        <div class="formatted-datetime" *ngIf="timeStringMode === 'simple'">
+            {{ simpleTimeString }}
+        </div>
+    </button>
+    <button mat-icon-button color="accent" (click)="applyNextInterval()">
+        <i class="material-icons">navigate_next</i>
+    </button>
+
+    <mat-menu #menu="matMenu" fxFlex="100" class="large-menu">
+        <sp-time-selector-menu
+            [timeSettings]="timeSettings"
+            (timeSettingsEmitter)="applyCurrentDateRange($event)"
+            class="w-100"
+        >
+        </sp-time-selector-menu>
+    </mat-menu>
+
+    <button mat-icon-button color="accent" (click)="refreshData()">
+        <i class="material-icons">autorenew</i>
+    </button>
+</div>
diff --git a/ui/src/app/data-explorer/components/time-selector/timeRangeSelector.component.scss b/ui/src/app/data-explorer/components/time-selector/time-range-selector.component.scss
similarity index 87%
rename from ui/src/app/data-explorer/components/time-selector/timeRangeSelector.component.scss
rename to ui/src/app/data-explorer/components/time-selector/time-range-selector.component.scss
index fb24a7dc6f..aef8593a37 100644
--- a/ui/src/app/data-explorer/components/time-selector/timeRangeSelector.component.scss
+++ b/ui/src/app/data-explorer/components/time-selector/time-range-selector.component.scss
@@ -55,3 +55,18 @@
 .mr--5 {
     margin-right: -5px;
 }
+
+.formatted-date {
+    font-weight: bolder;
+}
+
+.formatted-time {
+    font-weight: lighter;
+}
+
+.formatted-datetime {
+    display: inline;
+    background: var(--color-bg-2);
+    border-radius: 5px;
+    padding: 5px;
+}
diff --git a/ui/src/app/data-explorer/components/time-selector/time-range-selector.component.ts b/ui/src/app/data-explorer/components/time-selector/time-range-selector.component.ts
new file mode 100644
index 0000000000..dafc89cc0f
--- /dev/null
+++ b/ui/src/app/data-explorer/components/time-selector/time-range-selector.component.ts
@@ -0,0 +1,143 @@
+/*
+ * 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,
+    OnChanges,
+    OnInit,
+    Output,
+    SimpleChanges,
+    ViewChild,
+    ViewEncapsulation,
+} from '@angular/core';
+import {
+    TimeSelectionId,
+    TimeSettings,
+    TimeString,
+} from '@streampipes/platform-services';
+import { MatMenuTrigger } from '@angular/material/menu';
+import { TimeSelectionService } from '../../services/time-selection.service';
+
+@Component({
+    selector: 'sp-time-range-selector',
+    templateUrl: 'time-range-selector.component.html',
+    styleUrls: ['./time-range-selector.component.scss'],
+    encapsulation: ViewEncapsulation.None,
+})
+export class TimeRangeSelectorComponent implements OnInit, OnChanges {
+    @ViewChild('menuTrigger') menu: MatMenuTrigger;
+
+    @Output() dateRangeEmitter = new EventEmitter<TimeSettings>();
+
+    @Input()
+    timeSettings: TimeSettings;
+
+    simpleTimeString: string = '';
+    timeString: TimeString;
+    timeStringMode: 'simple' | 'advanced' = 'simple';
+
+    constructor(private timeSelectionService: TimeSelectionService) {}
+
+    ngOnInit() {
+        this.createDateString();
+    }
+
+    ngOnChanges(changes: SimpleChanges) {
+        if (changes.timeSettings) {
+            this.createDateString();
+        }
+    }
+
+    applyPreviousInterval(): void {
+        this.changeTimeByInterval((a, b) => a - b);
+    }
+
+    applyNextInterval(): void {
+        this.changeTimeByInterval((a, b) => a + b);
+    }
+
+    compare(newDateRange: TimeSettings, oldDateRange: TimeSettings): boolean {
+        return (
+            newDateRange &&
+            oldDateRange &&
+            newDateRange.startTime === oldDateRange.startTime &&
+            newDateRange.endTime === oldDateRange.endTime &&
+            newDateRange.dynamicSelection === oldDateRange.dynamicSelection
+        );
+    }
+
+    reloadData() {
+        this.dateRangeEmitter.emit(this.timeSettings);
+    }
+
+    refreshData() {
+        const difference =
+            this.timeSettings.endTime - this.timeSettings.startTime;
+
+        const current = new Date().getTime();
+        this.timeSettings = {
+            startTime: current - difference,
+            endTime: current,
+            dynamicSelection: this.timeSettings.dynamicSelection,
+            timeSelectionId: this.timeSettings.timeSelectionId,
+        };
+
+        this.reloadData();
+    }
+
+    private changeTimeByInterval(func) {
+        const difference =
+            this.timeSettings.endTime - this.timeSettings.startTime;
+        const newStartTime = func(this.timeSettings.startTime, difference);
+        const newEndTime = func(this.timeSettings.endTime, difference);
+
+        this.timeSettings.startTime = newStartTime;
+        this.timeSettings.endTime = newEndTime;
+        this.timeSettings.timeSelectionId = TimeSelectionId.CUSTOM;
+        this.createDateString();
+        this.reloadData();
+    }
+
+    applyCurrentDateRange(timeSettings: TimeSettings) {
+        this.timeSettings = timeSettings;
+        this.createDateString();
+        this.menu.closeMenu();
+        this.reloadData();
+    }
+
+    createDateString(): void {
+        if (this.timeSettings.timeSelectionId !== TimeSelectionId.CUSTOM) {
+            this.simpleTimeString = this.timeSelectionService.getTimeSelection(
+                this.timeSettings.timeSelectionId,
+            ).label;
+            this.timeStringMode = 'simple';
+        } else {
+            const startDate = new Date(this.timeSettings.startTime);
+            const endDate = new Date(this.timeSettings.endTime);
+            this.timeString = {
+                startDate: startDate.toLocaleDateString(),
+                endDate: endDate.toLocaleDateString(),
+                startTime: startDate.toLocaleTimeString(),
+                endTime: endDate.toLocaleTimeString(),
+            };
+            this.timeStringMode = 'advanced';
+        }
+    }
+}
diff --git a/ui/src/app/data-explorer/components/time-selector/time-selector-menu/custom-time-range-selection/custom-time-range-selection.component.html b/ui/src/app/data-explorer/components/time-selector/time-selector-menu/custom-time-range-selection/custom-time-range-selection.component.html
new file mode 100644
index 0000000000..9e277f0ca6
--- /dev/null
+++ b/ui/src/app/data-explorer/components/time-selector/time-selector-menu/custom-time-range-selection/custom-time-range-selection.component.html
@@ -0,0 +1,87 @@
+<!--
+  ~ 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 fxLayout="column" fxFlex="100" class="w-100">
+    <div fxLayout="column" fxFlex="100" class="mt-10">
+        <mat-card style="width: 100%">
+            <mat-calendar
+                [(selected)]="currentDateRange"
+                (selectedChange)="onDateChange($event)"
+            >
+            </mat-calendar>
+        </mat-card>
+
+        <div fxLayout="column" class="mt-10 mr-5">
+            <div fxLayout="row" fxLayoutGap="10px">
+                <div fxFlex="30">
+                    <span
+                        class="time-selector"
+                        data-cy="time-selector-start-date"
+                        >{{ currentStartDate }}</span
+                    >
+                </div>
+                <div fxFlex>
+                    <input
+                        matNativeControl
+                        data-cy="time-selector-start-time"
+                        class="w-100 time-selector"
+                        type="time"
+                        step="1"
+                        [(ngModel)]="currentStartTime"
+                        (ngModelChange)="
+                            updateDateTime(currentDateRange.start, $event)
+                        "
+                    />
+                </div>
+            </div>
+            <div fxLayout="row" fxLayoutGap="10px" class="mt-10">
+                <div fxFlex="30">
+                    <span
+                        class="time-selector"
+                        data-cy="time-selector-end-date"
+                        >{{ currentEndDate }}</span
+                    >
+                </div>
+                <div fxFlex>
+                    <input
+                        matNativeControl
+                        data-cy="time-selector-end-time"
+                        class="w-100 time-selector"
+                        type="time"
+                        step="1"
+                        [(ngModel)]="currentEndTime"
+                        (ngModelChange)="
+                            updateDateTime(currentDateRange.end, $event)
+                        "
+                    />
+                </div>
+            </div>
+        </div>
+    </div>
+    <div fxLayout="row" fxLayoutAlign="end center" class="mt-10">
+        <button
+            mat-raised-button
+            color="accent"
+            data-cy="apply-custom-time"
+            (click)="saveSelection()"
+            [disabled]="!dateSelectionComplete"
+        >
+            Apply
+        </button>
+    </div>
+</div>
diff --git a/ui/src/app/data-explorer/dialogs/edit-dashboard/data-explorer-edit-data-view-dialog.component.scss b/ui/src/app/data-explorer/components/time-selector/time-selector-menu/custom-time-range-selection/custom-time-range-selection.component.scss
similarity index 69%
copy from ui/src/app/data-explorer/dialogs/edit-dashboard/data-explorer-edit-data-view-dialog.component.scss
copy to ui/src/app/data-explorer/components/time-selector/time-selector-menu/custom-time-range-selection/custom-time-range-selection.component.scss
index 92f0108de0..ce53cf59ea 100644
--- a/ui/src/app/data-explorer/dialogs/edit-dashboard/data-explorer-edit-data-view-dialog.component.scss
+++ b/ui/src/app/data-explorer/components/time-selector/time-selector-menu/custom-time-range-selection/custom-time-range-selection.component.scss
@@ -16,28 +16,7 @@
  *
  */
 
-@import '../../../../scss/sp/sp-dialog.scss';
-
-.element-margin {
-    margin-top: 10px;
-    margin-bottom: 10px;
-}
-
-.mat-radio-button {
-    padding: 10px;
-}
-
-.mr-10 {
-    margin-right: 10px;
-}
-
-.view-radio-group {
-    display: flex;
-    flex-direction: column;
-    margin: 15px 0;
-    align-items: flex-start;
-}
-
-.view-radio-button {
-    margin: 5px;
+.time-selector {
+    font-family: inherit;
+    font-size: 11pt;
 }
diff --git a/ui/src/app/data-explorer/components/time-selector/time-selector-menu/custom-time-range-selection/custom-time-range-selection.component.ts b/ui/src/app/data-explorer/components/time-selector/time-selector-menu/custom-time-range-selection/custom-time-range-selection.component.ts
new file mode 100644
index 0000000000..a0ad73dab2
--- /dev/null
+++ b/ui/src/app/data-explorer/components/time-selector/time-selector-menu/custom-time-range-selection/custom-time-range-selection.component.ts
@@ -0,0 +1,107 @@
+/*
+ * 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 { TimeSelectionId, TimeSettings } from '@streampipes/platform-services';
+import {
+    DateRange,
+    DefaultMatCalendarRangeStrategy,
+    MatRangeDateSelectionModel,
+} from '@angular/material/datepicker';
+
+@Component({
+    selector: 'sp-custom-time-range-selection',
+    templateUrl: 'custom-time-range-selection.component.html',
+    styleUrls: ['./custom-time-range-selection.component.scss'],
+})
+export class CustomTimeRangeSelectionComponent implements OnInit {
+    @Input() timeSettings: TimeSettings;
+    @Output() timeSettingsEmitter = new EventEmitter<TimeSettings>();
+
+    currentStartDate: string;
+    currentEndDate: string;
+    currentStartTime: string;
+    currentEndTime: string;
+    currentDateRange: DateRange<Date>;
+    dateSelectionComplete = false;
+
+    constructor(
+        private readonly selectionModel: MatRangeDateSelectionModel<Date>,
+        private readonly selectionStrategy: DefaultMatCalendarRangeStrategy<Date>,
+    ) {}
+
+    ngOnInit(): void {
+        this.initializeDateRange();
+        this.updateTimeStrings();
+        this.updateDateStrings();
+        this.dateSelectionComplete = true;
+    }
+
+    initializeDateRange(): void {
+        this.currentDateRange = new DateRange(
+            new Date(this.timeSettings.startTime),
+            new Date(this.timeSettings.endTime),
+        );
+    }
+
+    updateTimeStrings(): void {
+        this.currentStartTime = this.formatTime(this.currentDateRange.start);
+        this.currentEndTime = this.formatTime(this.currentDateRange.end);
+    }
+
+    formatTime(date: Date): string {
+        return date.toLocaleTimeString(); //.slice(0, 5);
+    }
+
+    updateDateStrings(): void {
+        this.currentStartDate = this.formatDate(this.currentDateRange.start);
+        this.currentEndDate = this.formatDate(this.currentDateRange.end);
+    }
+
+    formatDate(date: Date): string {
+        return date?.toLocaleDateString() || '-';
+    }
+
+    onDateChange(selectedDate: Date): void {
+        const newSelection = this.selectionStrategy.selectionFinished(
+            selectedDate,
+            this.selectionModel.selection,
+        );
+        this.selectionModel.updateSelection(newSelection, this);
+        this.currentDateRange = new DateRange<Date>(
+            newSelection.start,
+            newSelection.end,
+        );
+        this.dateSelectionComplete = this.selectionModel.isComplete();
+        this.updateDateStrings();
+    }
+
+    saveSelection(): void {
+        this.updateDateTime(this.currentDateRange.start, this.currentStartTime);
+        this.updateDateTime(this.currentDateRange.end, this.currentEndTime);
+        this.timeSettings.startTime = this.currentDateRange.start.getTime();
+        this.timeSettings.endTime = this.currentDateRange.end.getTime();
+        this.timeSettings.timeSelectionId = TimeSelectionId.CUSTOM;
+        this.timeSettingsEmitter.emit(this.timeSettings);
+    }
+
+    updateDateTime(date: Date, time: string): void {
+        const [hours, minutes, seconds] = time.split(':').map(Number);
+        date.setHours(hours, minutes, seconds || 0);
+    }
+}
diff --git a/ui/src/app/data-explorer/components/time-selector/time-selector-menu/time-selector-menu.component.html b/ui/src/app/data-explorer/components/time-selector/time-selector-menu/time-selector-menu.component.html
new file mode 100644
index 0000000000..70ded63676
--- /dev/null
+++ b/ui/src/app/data-explorer/components/time-selector/time-selector-menu/time-selector-menu.component.html
@@ -0,0 +1,53 @@
+<!--
+  ~ 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 class="menu-content" (click)="$event.stopPropagation()">
+    <div fxLayout="column" fxLayoutGap="10px">
+        <div fxLayout="row" fxFlex="100" fxLayoutGap="10px">
+            <div fxLayout="column" fxFlex="50" fxLayoutGap="10px">
+                <div class="filter-header">Quick selection</div>
+                <div fxLayout="column" fxLayoutGap="10px" fxFlex="100">
+                    <div *ngFor="let quickSelection of quickSelections">
+                        <span
+                            class="quick-link"
+                            [attr.data-cy]="
+                                'time-selector-quick-' +
+                                quickSelection.timeSelectionId
+                            "
+                            (click)="applyQuickSelection(quickSelection)"
+                            >{{ quickSelection.label }}</span
+                        >
+                        <mat-divider
+                            *ngIf="quickSelection.addDividerAfter"
+                        ></mat-divider>
+                    </div>
+                </div>
+            </div>
+            <div fxLayout="column" fxFlex="50">
+                <div class="filter-header">Custom</div>
+                <sp-custom-time-range-selection
+                    #timeRangeSelection
+                    [timeSettings]="timeSettings"
+                    (timeSettingsEmitter)="timeSettingsEmitter.emit($event)"
+                    class="w-100"
+                >
+                </sp-custom-time-range-selection>
+            </div>
+        </div>
+    </div>
+</div>
diff --git a/ui/src/app/data-explorer/dialogs/edit-dashboard/data-explorer-edit-data-view-dialog.component.scss b/ui/src/app/data-explorer/components/time-selector/time-selector-menu/time-selector-menu.component.scss
similarity index 68%
copy from ui/src/app/data-explorer/dialogs/edit-dashboard/data-explorer-edit-data-view-dialog.component.scss
copy to ui/src/app/data-explorer/components/time-selector/time-selector-menu/time-selector-menu.component.scss
index 92f0108de0..b9c614c4d3 100644
--- a/ui/src/app/data-explorer/dialogs/edit-dashboard/data-explorer-edit-data-view-dialog.component.scss
+++ b/ui/src/app/data-explorer/components/time-selector/time-selector-menu/time-selector-menu.component.scss
@@ -16,28 +16,29 @@
  *
  */
 
-@import '../../../../scss/sp/sp-dialog.scss';
-
-.element-margin {
-    margin-top: 10px;
-    margin-bottom: 10px;
+.menu-content {
+    padding: 10px;
+    min-width: 550px;
 }
 
-.mat-radio-button {
-    padding: 10px;
+.filter-header {
+    font-weight: bold;
+    white-space: nowrap;
+    font-size: 12pt;
 }
 
-.mr-10 {
-    margin-right: 10px;
+.filter-section {
+    padding: 5px 10px 5px 5px;
+    border: 1px solid var(--color-bg-3);
+    background: var(--color-bg-1);
 }
 
-.view-radio-group {
-    display: flex;
-    flex-direction: column;
-    margin: 15px 0;
-    align-items: flex-start;
+.quick-link {
+    color: var(--color-accent);
+    cursor: pointer;
+    font-size: 11pt;
 }
 
-.view-radio-button {
-    margin: 5px;
+.quick-link:hover {
+    font-weight: bold;
 }
diff --git a/ui/src/app/data-explorer/components/time-selector/time-selector-menu/time-selector-menu.component.ts b/ui/src/app/data-explorer/components/time-selector/time-selector-menu/time-selector-menu.component.ts
new file mode 100644
index 0000000000..ff9e4e166d
--- /dev/null
+++ b/ui/src/app/data-explorer/components/time-selector/time-selector-menu/time-selector-menu.component.ts
@@ -0,0 +1,69 @@
+/*
+ * 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,
+    ViewChild,
+} from '@angular/core';
+import {
+    QuickTimeSelection,
+    TimeSettings,
+} from '@streampipes/platform-services';
+import { TimeSelectionService } from '../../../services/time-selection.service';
+import { CustomTimeRangeSelectionComponent } from './custom-time-range-selection/custom-time-range-selection.component';
+
+@Component({
+    selector: 'sp-time-selector-menu',
+    templateUrl: 'time-selector-menu.component.html',
+    styleUrls: ['./time-selector-menu.component.scss'],
+})
+export class TimeRangeSelectorMenuComponent implements OnInit {
+    @Input()
+    timeSettings: TimeSettings;
+
+    @Output()
+    timeSettingsEmitter: EventEmitter<TimeSettings> =
+        new EventEmitter<TimeSettings>();
+
+    quickSelections: QuickTimeSelection[] = [];
+
+    @ViewChild('timeRangeSelection')
+    timeRangeSelection: CustomTimeRangeSelectionComponent;
+
+    constructor(private timeSelectionService: TimeSelectionService) {}
+
+    ngOnInit(): void {
+        this.quickSelections = this.timeSelectionService.quickTimeSelections;
+    }
+
+    applyQuickSelection(quickSelection: QuickTimeSelection): void {
+        const selectedDateRange =
+            this.timeSelectionService.getDateRange(quickSelection);
+        this.timeSettings.timeSelectionId = quickSelection.timeSelectionId;
+        this.timeSettings.startTime = selectedDateRange.startDate.getTime();
+        this.timeSettings.endTime = selectedDateRange.endDate.getTime();
+        this.timeRangeSelection.initializeDateRange();
+        this.timeRangeSelection.updateTimeStrings();
+        this.timeRangeSelection.updateDateStrings();
+        this.timeSettingsEmitter.emit(this.timeSettings);
+    }
+}
diff --git a/ui/src/app/data-explorer/components/time-selector/timeRangeSelector.component.html b/ui/src/app/data-explorer/components/time-selector/timeRangeSelector.component.html
deleted file mode 100644
index 29073a02a0..0000000000
--- a/ui/src/app/data-explorer/components/time-selector/timeRangeSelector.component.html
+++ /dev/null
@@ -1,82 +0,0 @@
-<!--
-  ~ 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 fxLayout="row" fxLayoutAlign="start center" class="form-field-small">
-    <div class="time-range-wrapper" fxFlex fxLayoutAlign="center center">
-        <button
-            mat-button
-            mat-raised-button
-            *ngFor="let item of possibleTimeButtons"
-            [color]="selectedTimeButton.value === item.value ? 'accent' : ''"
-            [attr.data-cy]="item.value.replace(' ', '_')"
-            [ngClass]="'button-margin smaller-button-font-size'"
-            (click)="this.setCurrentDateRange(item)"
-        >
-            {{ item.value }}
-        </button>
-    </div>
-    <div class="time-wrapper" fxFlex fxLayoutAlign="center center">
-        <button
-            mat-button
-            class="button-icon icon-button-font-size mr--15"
-            color="accent"
-            (click)="decreaseTime()"
-            data-cy="decrease-time-button"
-        >
-            <mat-icon>navigate_before</mat-icon>
-        </button>
-        <sp-date-input
-            class="start-date-margin"
-            [(date)]="startDate"
-            (dateChanged)="changeCustomDateRange()"
-            data-cy="time-range-from"
-        >
-        </sp-date-input>
-
-        <sp-date-input
-            [(date)]="endDate"
-            (dateChanged)="changeCustomDateRange()"
-            data-cy="time-range-to"
-        >
-        </sp-date-input>
-
-        <button
-            mat-button
-            class="button-icon icon-button-font-size ml--5 mr--5"
-            color="accent"
-            (click)="increaseTime()"
-            data-cy="increase-time-button"
-        >
-            <mat-icon>navigate_next</mat-icon>
-        </button>
-    </div>
-    <div class="time-range-wrapper" fxFlex fxLayoutAlign="center center">
-        <button
-            mat-button
-            mat-raised-button
-            color="accent"
-            class="button-margin smaller-button-font-size"
-            matTooltip="Reload"
-            matTooltipPosition="above"
-            (click)="reloadData()"
-            data-cy="reload-data-view-button"
-        >
-            <i class="material-icons mr-0">autorenew</i>
-        </button>
-    </div>
-</div>
diff --git a/ui/src/app/data-explorer/components/time-selector/timeRangeSelector.component.ts b/ui/src/app/data-explorer/components/time-selector/timeRangeSelector.component.ts
deleted file mode 100644
index 6dcea2232c..0000000000
--- a/ui/src/app/data-explorer/components/time-selector/timeRangeSelector.component.ts
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * 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,
-    ViewEncapsulation,
-} from '@angular/core';
-import { TimeSettings } from '@streampipes/platform-services';
-
-@Component({
-    selector: 'sp-time-range-selector',
-    templateUrl: 'timeRangeSelector.component.html',
-    styleUrls: ['./timeRangeSelector.component.scss'],
-    encapsulation: ViewEncapsulation.None,
-})
-export class TimeRangeSelectorComponent implements OnInit {
-    @Output() dateRangeEmitter = new EventEmitter<TimeSettings>();
-
-    _dateRange: TimeSettings;
-
-    startDate: Date;
-    endDate: Date;
-
-    public possibleTimeButtons = [
-        { value: '15 min', offset: 15 },
-        { value: '1 hour', offset: 60 },
-        { value: '1 day', offset: 1440 },
-        { value: '1 week', offset: 10080 },
-        { value: '1 month', offset: 43200 },
-        { value: '1 year', offset: 525600 },
-        { value: 'custom', offset: -1 },
-    ];
-
-    public selectedTimeButton;
-
-    constructor() {}
-
-    ngOnInit() {
-        if (!this.dateRange.startTime) {
-            this.setCurrentDateRange(this.possibleTimeButtons[0]);
-        } else if (this.dateRange.dynamicSelection !== -1) {
-            this.setCurrentDateRange(
-                this.possibleTimeButtons.find(
-                    tb => tb.offset === this.dateRange.dynamicSelection,
-                ),
-            );
-        } else {
-            this.startDate = new Date(this._dateRange.startTime);
-            this.endDate = new Date(this._dateRange.endTime);
-        }
-    }
-
-    @Input()
-    set dateRange(dateRange: TimeSettings) {
-        if (!this.compare(dateRange, this._dateRange)) {
-            this._dateRange = dateRange;
-            this.updateTimeSettings();
-        }
-    }
-
-    get dateRange(): TimeSettings {
-        return this._dateRange;
-    }
-
-    compare(newDateRange: TimeSettings, oldDateRange: TimeSettings): boolean {
-        return (
-            newDateRange &&
-            oldDateRange &&
-            newDateRange.startTime === oldDateRange.startTime &&
-            newDateRange.endTime === oldDateRange.endTime &&
-            newDateRange.dynamicSelection === oldDateRange.dynamicSelection
-        );
-    }
-
-    updateTimeSettings() {
-        this.startDate = new Date(this.dateRange.startTime);
-        this.endDate = new Date(this.dateRange.endTime);
-        this.selectedTimeButton = this.findOffset(
-            this.dateRange.dynamicSelection,
-        );
-        this.reloadData();
-    }
-
-    findOffset(dynamicSelection: number) {
-        return (
-            this.possibleTimeButtons.find(
-                el => el.offset === dynamicSelection,
-            ) || this.possibleTimeButtons[0]
-        );
-    }
-
-    reloadData() {
-        this.dateRangeEmitter.emit(this.dateRange);
-    }
-
-    increaseTime() {
-        this.changeTimeByInterval((a, b) => a + b);
-    }
-
-    decreaseTime() {
-        this.changeTimeByInterval((a, b) => a - b);
-    }
-
-    refreshData() {
-        const difference = this.endDate.getTime() - this.startDate.getTime();
-
-        const current = new Date().getTime();
-        this.dateRange = {
-            startTime: current - difference,
-            endTime: current,
-            dynamicSelection: this.dateRange.dynamicSelection,
-        };
-
-        this.reloadData();
-    }
-
-    private changeTimeByInterval(func) {
-        const difference = this.endDate.getTime() - this.startDate.getTime();
-        const newStartTime = func(this.startDate.getTime(), difference);
-        const newEndTime = func(this.endDate.getTime(), difference);
-
-        this.startDate = new Date(newStartTime);
-        this.endDate = new Date(newEndTime);
-        this.selectedTimeButton =
-            this.possibleTimeButtons[this.possibleTimeButtons.length - 1];
-        this.dateRange = {
-            startTime: newStartTime,
-            endTime: newEndTime,
-            dynamicSelection: -1,
-        };
-    }
-
-    changeCustomDateRange() {
-        this.selectedTimeButton =
-            this.possibleTimeButtons[this.possibleTimeButtons.length - 1];
-        const newStartTime = this.startDate.getTime();
-        const newEndTime = this.endDate.getTime();
-
-        this.dateRange = {
-            startTime: newStartTime,
-            endTime: newEndTime,
-            dynamicSelection: -1,
-        };
-    }
-
-    /**
-     * Sets the current date range from now to the value of offset in the past
-     * @param offset in minutes
-     */
-    setCurrentDateRange(item) {
-        this.selectedTimeButton = item;
-        const current = new Date().getTime();
-        this.startDate = new Date(current - item.offset * 60000);
-        this.endDate = new Date(current);
-        this.dateRange = {
-            startTime: this.startDate.getTime(),
-            endTime: this.endDate.getTime(),
-            dynamicSelection: item.offset,
-        };
-    }
-}
diff --git a/ui/src/app/data-explorer/components/widgets/table/table-widget.component.ts b/ui/src/app/data-explorer/components/widgets/table/table-widget.component.ts
index a156b11b14..40acdf5a48 100644
--- a/ui/src/app/data-explorer/components/widgets/table/table-widget.component.ts
+++ b/ui/src/app/data-explorer/components/widgets/table/table-widget.component.ts
@@ -51,7 +51,9 @@ export class TableWidgetComponent
     }
 
     regenerateColumnNames(): void {
-        this.groupByColumnNames = this.makeGroupByColumns();
+        this.groupByColumnNames = this.makeGroupByColumns(
+            this.dataExplorerWidget.visualizationConfig.selectedColumns,
+        );
         this.columnNames = ['time'].concat(
             this.dataExplorerWidget.visualizationConfig.selectedColumns.map(
                 c => c.fullDbName,
@@ -60,10 +62,16 @@ export class TableWidgetComponent
         );
     }
 
-    makeGroupByColumns(): string[] {
+    makeGroupByColumns(selectedColumns: DataExplorerField[]): string[] {
         return this.dataExplorerWidget.dataConfig.sourceConfigs.flatMap(sc => {
             return sc.queryConfig.groupBy
                 .filter(g => g.selected)
+                .filter(
+                    g =>
+                        selectedColumns.find(
+                            column => column.runtimeName === g.runtimeName,
+                        ) === undefined,
+                )
                 .map(g => g.runtimeName);
         });
     }
diff --git a/ui/src/app/data-explorer/data-explorer.module.ts b/ui/src/app/data-explorer/data-explorer.module.ts
index 62dc60706a..02cdad7b9b 100644
--- a/ui/src/app/data-explorer/data-explorer.module.ts
+++ b/ui/src/app/data-explorer/data-explorer.module.ts
@@ -23,7 +23,11 @@ import { FlexLayoutModule } from '@ngbracket/ngx-layout';
 import { FormsModule, ReactiveFormsModule } from '@angular/forms';
 import { MatChipsModule } from '@angular/material/chips';
 import { MatNativeDateModule } from '@angular/material/core';
-import { MatDatepickerModule } from '@angular/material/datepicker';
+import {
+    DefaultMatCalendarRangeStrategy,
+    MatDatepickerModule,
+    MatRangeDateSelectionModel,
+} from '@angular/material/datepicker';
 import { MatGridListModule } from '@angular/material/grid-list';
 import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
 import { MatSliderModule } from '@angular/material/slider';
@@ -39,7 +43,7 @@ import { CoreUiModule } from '../core-ui/core-ui.module';
 import { DataExplorerDashboardGridComponent } from './components/widget-view/grid-view/data-explorer-dashboard-grid.component';
 import { DataExplorerOverviewComponent } from './components/overview/data-explorer-overview.component';
 import { DataExplorerDashboardPanelComponent } from './components/dashboard/data-explorer-dashboard-panel.component';
-import { TimeRangeSelectorComponent } from './components/time-selector/timeRangeSelector.component';
+import { TimeRangeSelectorComponent } from './components/time-selector/time-range-selector.component';
 import { DataExplorerDashboardWidgetComponent } from './components/widget/data-explorer-dashboard-widget.component';
 import { ImageWidgetComponent } from './components/widgets/image/image-widget.component';
 import { TableWidgetComponent } from './components/widgets/table/table-widget.component';
@@ -48,7 +52,7 @@ import { LoadDataSpinnerComponent } from './components/widgets/utils/load-data-s
 import { NoDataInDateRangeComponent } from './components/widgets/utils/no-data/no-data-in-date-range.component';
 import { SelectPropertiesComponent } from './components/widgets/utils/select-properties/select-properties.component';
 import { SelectColorPropertiesComponent } from './components/widgets/utils/select-color-properties/select-color-properties.component';
-import { DataExplorerEditDataViewDialogComponent } from './dialogs/edit-dashboard/data-explorer-edit-data-view-dialog.component';
+import { DataExplorerEditDashboardDialogComponent } from './dialogs/edit-dashboard/data-explorer-edit-dashboard-dialog.component';
 import { GroupConfigurationComponent } from './components/widgets/utils/group-configuration/group-configuration.component';
 import { MatSlideToggleModule } from '@angular/material/slide-toggle';
 import { DataExplorerDesignerPanelComponent } from './components/data-view/data-view-designer-panel/data-explorer-designer-panel.component';
@@ -121,6 +125,8 @@ import { DataExplorerDataViewSelectionComponent } from './components/dashboard/d
 import { DataExplorerDashboardWidgetSelectionPanelComponent } from './components/dashboard/dashboard-widget-selection-panel/dashboard-widget-selection-panel.component';
 import { DataExplorerDataViewPreviewComponent } from './components/dashboard/dashboard-widget-selection-panel/data-view-selection/data-view-preview/data-view-preview.component';
 import { DataExplorerDashboardToolbarComponent } from './components/dashboard/dashboard-toolbar/dashboard-toolbar.component';
+import { TimeRangeSelectorMenuComponent } from './components/time-selector/time-selector-menu/time-selector-menu.component';
+import { CustomTimeRangeSelectionComponent } from './components/time-selector/time-selector-menu/custom-time-range-selection/custom-time-range-selection.component';
 
 @NgModule({
     imports: [
@@ -201,6 +207,7 @@ import { DataExplorerDashboardToolbarComponent } from './components/dashboard/da
     ],
     declarations: [
         AggregateConfigurationComponent,
+        CustomTimeRangeSelectionComponent,
         DataExplorerDashboardGridComponent,
         DataExplorerOverviewComponent,
         DataExplorerDashboardPanelComponent,
@@ -211,7 +218,7 @@ import { DataExplorerDashboardToolbarComponent } from './components/dashboard/da
         DataExplorerDataViewPreviewComponent,
         DataExplorerDataViewSelectionComponent,
         DataExplorerDesignerPanelComponent,
-        DataExplorerEditDataViewDialogComponent,
+        DataExplorerEditDashboardDialogComponent,
         DataExplorerWidgetAppearanceSettingsComponent,
         DataExplorerWidgetDataSettingsComponent,
         DataExplorerDataViewComponent,
@@ -239,6 +246,7 @@ import { DataExplorerDashboardToolbarComponent } from './components/dashboard/da
         HeatmapWidgetConfigComponent,
         ImageViewerComponent,
         TimeRangeSelectorComponent,
+        TimeRangeSelectorMenuComponent,
         DataExplorerVisualisationSettingsComponent,
         GroupSelectionPanelComponent,
         DataExplorerVisualisationSettingsComponent,
@@ -260,7 +268,7 @@ import { DataExplorerDashboardToolbarComponent } from './components/dashboard/da
         SpTimeSeriesAppearanceConfigComponent,
         SpDataZoomConfigComponent,
     ],
-    providers: [],
+    providers: [DefaultMatCalendarRangeStrategy, MatRangeDateSelectionModel],
     exports: [],
 })
 export class DataExplorerModule {
diff --git a/ui/src/app/data-explorer/dialogs/edit-dashboard/data-explorer-edit-data-view-dialog.component.html b/ui/src/app/data-explorer/dialogs/edit-dashboard/data-explorer-edit-dashboard-dialog.component.html
similarity index 66%
rename from ui/src/app/data-explorer/dialogs/edit-dashboard/data-explorer-edit-data-view-dialog.component.html
rename to ui/src/app/data-explorer/dialogs/edit-dashboard/data-explorer-edit-dashboard-dialog.component.html
index b65bc1e047..de73ccaeff 100644
--- a/ui/src/app/data-explorer/dialogs/edit-dashboard/data-explorer-edit-data-view-dialog.component.html
+++ b/ui/src/app/data-explorer/dialogs/edit-dashboard/data-explorer-edit-dashboard-dialog.component.html
@@ -40,25 +40,36 @@
                     <mat-label>Description</mat-label>
                     <input matInput [(ngModel)]="dashboard.description" />
                 </mat-form-field>
-                <label>Default view mode</label>
-                <mat-radio-group
-                    [(ngModel)]="
-                        dashboard.dashboardGeneralSettings.defaultViewMode
-                    "
-                >
-                    <mat-radio-button
-                        class="view-radio-button"
-                        [value]="'grid'"
+                <div class="mt-10" fxLayout="column">
+                    <label>Default view mode</label>
+                    <mat-radio-group
+                        [(ngModel)]="
+                            dashboard.dashboardGeneralSettings.defaultViewMode
+                        "
                     >
-                        Grid View
-                    </mat-radio-button>
-                    <mat-radio-button
-                        class="view-radio-button"
-                        [value]="'slide'"
-                    >
-                        Slide View
-                    </mat-radio-button>
-                </mat-radio-group>
+                        <mat-radio-button
+                            class="view-radio-button"
+                            [value]="'grid'"
+                        >
+                            Grid View
+                        </mat-radio-button>
+                        <mat-radio-button
+                            class="view-radio-button"
+                            [value]="'slide'"
+                        >
+                            Slide View
+                        </mat-radio-button>
+                    </mat-radio-group>
+                </div>
+                <div class="mt-10" fxLayout="column">
+                    <label>Time Settings</label>
+                    <mat-checkbox
+                        [(ngModel)]="
+                            dashboard.dashboardGeneralSettings.globalTimeEnabled
+                        "
+                        >Use global time instead of widget time settings
+                    </mat-checkbox>
+                </div>
                 <!--<mat-checkbox [(ngModel)]="dashboard.displayHeader">Show name and description in dashboard</mat-checkbox>-->
             </div>
         </div>
diff --git a/ui/src/app/data-explorer/dialogs/edit-dashboard/data-explorer-edit-data-view-dialog.component.scss b/ui/src/app/data-explorer/dialogs/edit-dashboard/data-explorer-edit-dashboard-dialog.component.scss
similarity index 100%
rename from ui/src/app/data-explorer/dialogs/edit-dashboard/data-explorer-edit-data-view-dialog.component.scss
rename to ui/src/app/data-explorer/dialogs/edit-dashboard/data-explorer-edit-dashboard-dialog.component.scss
diff --git a/ui/src/app/data-explorer/dialogs/edit-dashboard/data-explorer-edit-data-view-dialog.component.ts b/ui/src/app/data-explorer/dialogs/edit-dashboard/data-explorer-edit-dashboard-dialog.component.ts
similarity index 77%
rename from ui/src/app/data-explorer/dialogs/edit-dashboard/data-explorer-edit-data-view-dialog.component.ts
rename to ui/src/app/data-explorer/dialogs/edit-dashboard/data-explorer-edit-dashboard-dialog.component.ts
index 5fdce655c6..b0e8a66a6d 100644
--- a/ui/src/app/data-explorer/dialogs/edit-dashboard/data-explorer-edit-data-view-dialog.component.ts
+++ b/ui/src/app/data-explorer/dialogs/edit-dashboard/data-explorer-edit-dashboard-dialog.component.ts
@@ -18,22 +18,22 @@
 
 import { Component, Input, OnInit } from '@angular/core';
 import {
-    DataViewDataExplorerService,
     Dashboard,
+    DataViewDataExplorerService,
 } from '@streampipes/platform-services';
 import { DialogRef } from '@streampipes/shared-ui';
 
 @Component({
-    selector: 'sp-data-explorer-edit-data-view-dialog-component',
-    templateUrl: './data-explorer-edit-data-view-dialog.component.html',
-    styleUrls: ['./data-explorer-edit-data-view-dialog.component.scss'],
+    selector: 'sp-data-explorer-edit-dashboard-dialog-component',
+    templateUrl: './data-explorer-edit-dashboard-dialog.component.html',
+    styleUrls: ['./data-explorer-edit-dashboard-dialog.component.scss'],
 })
-export class DataExplorerEditDataViewDialogComponent implements OnInit {
+export class DataExplorerEditDashboardDialogComponent implements OnInit {
     @Input() createMode: boolean;
     @Input() dashboard: Dashboard;
 
     constructor(
-        private dialogRef: DialogRef<DataExplorerEditDataViewDialogComponent>,
+        private dialogRef: DialogRef<DataExplorerEditDashboardDialogComponent>,
         private dashboardService: DataViewDataExplorerService,
     ) {}
 
@@ -41,6 +41,12 @@ export class DataExplorerEditDataViewDialogComponent implements OnInit {
         if (!this.dashboard.dashboardGeneralSettings.defaultViewMode) {
             this.dashboard.dashboardGeneralSettings.defaultViewMode = 'grid';
         }
+        if (
+            this.dashboard.dashboardGeneralSettings.globalTimeEnabled ===
+            undefined
+        ) {
+            this.dashboard.dashboardGeneralSettings.globalTimeEnabled = true;
+        }
     }
 
     onCancel(): void {
diff --git a/ui/src/app/data-explorer/services/data-explorer-dashboard.service.ts b/ui/src/app/data-explorer/services/data-explorer-dashboard.service.ts
index e7177e9fa1..dcc5bba228 100644
--- a/ui/src/app/data-explorer/services/data-explorer-dashboard.service.ts
+++ b/ui/src/app/data-explorer/services/data-explorer-dashboard.service.ts
@@ -24,7 +24,7 @@ import {
     DateRange,
     TimeSettings,
 } from '@streampipes/platform-services';
-import { DataExplorerEditDataViewDialogComponent } from '../dialogs/edit-dashboard/data-explorer-edit-data-view-dialog.component';
+import { DataExplorerEditDashboardDialogComponent } from '../dialogs/edit-dashboard/data-explorer-edit-dashboard-dialog.component';
 import { DialogService, PanelType } from '@streampipes/shared-ui';
 import { DataDownloadDialogComponent } from '../../core-ui/data-download-dialog/data-download-dialog.component';
 
@@ -32,13 +32,13 @@ import { DataDownloadDialogComponent } from '../../core-ui/data-download-dialog/
 export class DataExplorerDashboardService {
     constructor(private dialogService: DialogService) {}
 
-    openDataViewModificationDialog(createMode: boolean, dashboard: Dashboard) {
+    openDashboardModificationDialog(createMode: boolean, dashboard: Dashboard) {
         return this.dialogService.open(
-            DataExplorerEditDataViewDialogComponent,
+            DataExplorerEditDashboardDialogComponent,
             {
-                panelType: PanelType.STANDARD_PANEL,
-                title: createMode ? 'New Data View' : 'Edit Data View',
-                width: '70vw',
+                panelType: PanelType.SLIDE_IN_PANEL,
+                title: createMode ? 'New Dashboard' : 'Edit Dashboard',
+                width: '60vw',
                 data: {
                     createMode: createMode,
                     dashboard: dashboard,
diff --git a/ui/src/app/data-explorer/services/time-selection.service.ts b/ui/src/app/data-explorer/services/time-selection.service.ts
index 81090a04d2..7ac09f0c3c 100644
--- a/ui/src/app/data-explorer/services/time-selection.service.ts
+++ b/ui/src/app/data-explorer/services/time-selection.service.ts
@@ -18,10 +18,131 @@
 
 import { Injectable } from '@angular/core';
 import { Subject } from 'rxjs';
-import { TimeSettings } from '@streampipes/platform-services';
+import {
+    DateRange,
+    QuickTimeSelection,
+    TimeSelectionId,
+    TimeSettings,
+} from '@streampipes/platform-services';
+import {
+    startOfDay,
+    startOfHour,
+    startOfMonth,
+    startOfWeek,
+    startOfYear,
+    subDays,
+    subHours,
+    subMinutes,
+    subMonths,
+    subWeeks,
+    subYears,
+} from 'date-fns';
 
 @Injectable({ providedIn: 'root' })
 export class TimeSelectionService {
+    quickTimeSelections: QuickTimeSelection[] = [
+        {
+            label: 'Last 15 min',
+            timeSelectionId: TimeSelectionId.LAST_15_MINUTES,
+            startTime: now => subMinutes(now, 15),
+            endTime: now => now,
+        },
+        {
+            label: 'Last 1 hour',
+            timeSelectionId: TimeSelectionId.LAST_HOUR,
+            startTime: now => subHours(now, 1),
+            endTime: now => now,
+        },
+        {
+            label: 'Last 1 day',
+            timeSelectionId: TimeSelectionId.LAST_DAY,
+            startTime: now => subDays(now, 1),
+            endTime: now => now,
+        },
+        {
+            label: 'Last 1 week',
+            timeSelectionId: TimeSelectionId.LAST_WEEK,
+            startTime: now => subWeeks(now, 1),
+            endTime: now => now,
+        },
+        {
+            label: 'Last 1 month',
+            timeSelectionId: TimeSelectionId.LAST_MONTH,
+            startTime: now => subMonths(now, 1),
+            endTime: now => now,
+        },
+        {
+            label: 'Last 1 year',
+            timeSelectionId: TimeSelectionId.LAST_YEAR,
+            startTime: now => subYears(now, 1),
+            endTime: now => now,
+        },
+        {
+            label: 'Current day',
+            timeSelectionId: TimeSelectionId.CURRENT_DAY,
+            startTime: now => startOfDay(now),
+            endTime: now => now,
+        },
+        {
+            label: 'Current hour',
+            timeSelectionId: TimeSelectionId.CURRENT_HOUR,
+            startTime: now => startOfHour(now),
+            endTime: now => now,
+        },
+        {
+            label: 'Current week',
+            timeSelectionId: TimeSelectionId.CURRENT_WEEK,
+            startTime: now => startOfWeek(now),
+            endTime: now => now,
+        },
+        {
+            label: 'Current month',
+            timeSelectionId: TimeSelectionId.CURRENT_MONTH,
+            startTime: now => startOfMonth(now),
+            endTime: now => now,
+        },
+        {
+            label: 'Current year',
+            timeSelectionId: TimeSelectionId.CURRENT_YEAR,
+            startTime: now => startOfYear(now),
+            endTime: now => now,
+        },
+    ];
+
+    public getDateRange(quickSelection: QuickTimeSelection): DateRange {
+        const now = new Date();
+        return {
+            startDate: quickSelection.startTime(now),
+            endDate: quickSelection.endTime(now),
+        };
+    }
+
+    public getDefaultTimeSettings(): TimeSettings {
+        return this.getTimeSettings(
+            TimeSelectionId.LAST_15_MINUTES,
+            new Date(),
+        );
+    }
+
+    public getTimeSettings(
+        timeSelectionId: TimeSelectionId,
+        now: Date,
+    ): TimeSettings {
+        const selection = this.getTimeSelection(timeSelectionId);
+        return {
+            startTime: selection.startTime(now).getTime(),
+            endTime: selection.endTime(now).getTime(),
+            dynamicSelection: -1,
+            timeSelectionId: timeSelectionId,
+        };
+    }
+
+    public getTimeSelection(timeSelectionId: TimeSelectionId) {
+        return this.quickTimeSelections.find(
+            s => s.timeSelectionId === timeSelectionId,
+        );
+    }
+
     public timeSelectionChangeSubject: Subject<TimeSettings> =
         new Subject<TimeSettings>();