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 <[email protected]>
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>();
 

Reply via email to