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

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


The following commit(s) were added to refs/heads/dev by this push:
     new bd607e3943 feat(#4566): Add details view for data sets (#4592)
bd607e3943 is described below

commit bd607e3943c75dc8904b89cdf47f87457e51c69b
Author: Sven Oehler <[email protected]>
AuthorDate: Mon Jun 22 13:05:48 2026 +0200

    feat(#4566): Add details view for data sets (#4592)
    
    Co-authored-by: Dominik Riemer <[email protected]>
---
 ui/cypress/support/utils/GeneralUtils.ts           |   2 +-
 ui/cypress/support/utils/dataset/DatasetBtns.ts    |  34 ++++
 ui/cypress/support/utils/dataset/DatasetUtils.ts   |  61 +++++++
 ui/cypress/tests/dataset/datasetDetails.spec.ts    |  58 ++++++
 ui/deployment/i18n/de.json                         |   4 +
 ui/deployment/i18n/en.json                         |   4 +
 ui/deployment/i18n/pl.json                         |   4 +
 .../data-settings/chart-data-settings.component.ts |  15 +-
 .../datalake-configuration.component.html          |  26 ++-
 .../datalake-configuration.component.ts            |   6 +
 .../abstract-dataset-details.directive.ts          |  69 +++++++
 .../dataset-details-events.component.html          | 191 ++++++++++++++++++++
 .../dataset-details-events.component.scss          | 119 ++++++++++++
 .../dataset-details-events.component.ts            | 201 +++++++++++++++++++++
 .../dataset-details-schema.component.html          | 122 +++++++++++++
 .../dataset-details-schema.component.scss}         |  45 +++--
 .../dataset-details-schema.component.ts            | 134 ++++++++++++++
 .../dataset-details/dataset-details-tabs.ts}       |  26 +--
 .../dataset-feature-card.component.ts              |   6 +-
 ui/src/app/dataset/dataset.routes.ts               |  21 +++
 20 files changed, 1117 insertions(+), 31 deletions(-)

diff --git a/ui/cypress/support/utils/GeneralUtils.ts 
b/ui/cypress/support/utils/GeneralUtils.ts
index e8ace9468a..88a3476332 100644
--- a/ui/cypress/support/utils/GeneralUtils.ts
+++ b/ui/cypress/support/utils/GeneralUtils.ts
@@ -18,7 +18,7 @@
 
 export class GeneralUtils {
     public static tab(identifier: string) {
-        return cy.dataCy(`tab-${identifier}`).click();
+        return cy.get(`[data-cy="tab-${identifier}"]`).click();
     }
 
     public static openMenuForRow(rowText: string) {
diff --git a/ui/cypress/support/utils/dataset/DatasetBtns.ts 
b/ui/cypress/support/utils/dataset/DatasetBtns.ts
index a3ea274575..4cd6a5b4d6 100644
--- a/ui/cypress/support/utils/dataset/DatasetBtns.ts
+++ b/ui/cypress/support/utils/dataset/DatasetBtns.ts
@@ -111,6 +111,40 @@ export class DatasetBtns {
         });
     }
 
+    public static datasetDetailsSchemaTable() {
+        return cy.dataCy('dataset-details-schema-table', { timeout: 10000 });
+    }
+
+    public static datasetDetailsSchemaField(runtimeName: string) {
+        return cy.dataCy(`dataset-details-schema-field-${runtimeName}`, {
+            timeout: 10000,
+        });
+    }
+
+    public static datasetDetailsSchemaType(runtimeName: string) {
+        return cy.dataCy(`dataset-details-schema-type-${runtimeName}`, {
+            timeout: 10000,
+        });
+    }
+
+    public static datasetDetailsEventLimit() {
+        return cy.dataCy('dataset-details-event-limit', { timeout: 10000 });
+    }
+
+    public static datasetDetailsEventsTable() {
+        return cy.dataCy('dataset-details-events-table', { timeout: 30000 });
+    }
+
+    public static datasetDetailsEventCell(columnName: string) {
+        return cy.dataCy(`dataset-details-event-cell-${columnName}`, {
+            timeout: 30000,
+        });
+    }
+
+    public static datasetDetailsCreateChart() {
+        return cy.dataCy('dataset-details-create-chart', { timeout: 10000 });
+    }
+
     public static datasetTotalCountCell(name: string) {
         return this.datasetRow(name).find(
             '[data-cy="datalake-number-of-events"]',
diff --git a/ui/cypress/support/utils/dataset/DatasetUtils.ts 
b/ui/cypress/support/utils/dataset/DatasetUtils.ts
index 8ff9ce4b71..e5ef33302c 100644
--- a/ui/cypress/support/utils/dataset/DatasetUtils.ts
+++ b/ui/cypress/support/utils/dataset/DatasetUtils.ts
@@ -17,6 +17,7 @@
  */
 
 import { PermissionUtils } from '../user/PermissionUtils';
+import { GeneralUtils } from '../GeneralUtils';
 import { DatasetBtns } from './DatasetBtns';
 
 export class DatasetUtils {
@@ -150,6 +151,66 @@ export class DatasetUtils {
             .click();
     }
 
+    public static openDatasetDetails(datasetName: string) {
+        DatasetUtils.goToDatasets();
+        DatasetUtils.waitForTotalEvents(datasetName);
+        DatasetBtns.datasetRow(datasetName).click();
+        cy.url().should('include', '#/datasets/');
+    }
+
+    public static waitForTotalEvents(datasetName: string, attempts = 30) {
+        DatasetBtns.datasetTotalCountButton(datasetName).click({
+            force: true,
+        });
+        DatasetBtns.datasetTotalCountCell(datasetName)
+            .find('[data-cy="datalake-number-of-events-spinner"]')
+            .should('not.exist');
+        DatasetBtns.datasetTotalCountCell(datasetName).then($cell => {
+            const eventCount = DatasetUtils.parseEventCount($cell.text());
+
+            if (eventCount > 0) {
+                expect(eventCount).to.be.greaterThan(0);
+            } else if (attempts > 0) {
+                cy.wait(1000);
+                DatasetUtils.waitForTotalEvents(datasetName, attempts - 1);
+            } else {
+                expect(eventCount).to.be.greaterThan(0);
+            }
+        });
+    }
+
+    private static parseEventCount(text: string) {
+        return Number(text.trim().replace(/[^\d.-]/g, ''));
+    }
+
+    public static openLatestEventsTab() {
+        GeneralUtils.tab('Latest events');
+    }
+
+    public static setLatestEventsLimit(limit: number) {
+        DatasetBtns.datasetDetailsEventLimit().clear().type(`${limit}`);
+        DatasetBtns.datasetDetailsEventLimit().blur();
+    }
+
+    public static expectSchemaField(runtimeName: string, expectedType: string) 
{
+        
DatasetBtns.datasetDetailsSchemaField(runtimeName).should('be.visible');
+        DatasetBtns.datasetDetailsSchemaType(runtimeName).should(
+            'contain.text',
+            expectedType,
+        );
+    }
+
+    public static expectLatestEventsForColumn(columnName: string) {
+        DatasetBtns.datasetDetailsEventsTable().should('be.visible');
+        DatasetBtns.datasetDetailsEventCell(columnName)
+            .should('exist')
+            .and('have.length.at.least', 1);
+    }
+
+    public static createChartFromDatasetDetails() {
+        DatasetBtns.datasetDetailsCreateChart().click();
+    }
+
     public static expectDatasetPreviewDoesNotContainKey(key: string) {
         cy.dataCy('dataset-preview-table', { timeout: 10000 }).should(
             'be.visible',
diff --git a/ui/cypress/tests/dataset/datasetDetails.spec.ts 
b/ui/cypress/tests/dataset/datasetDetails.spec.ts
new file mode 100644
index 0000000000..b17838fcdc
--- /dev/null
+++ b/ui/cypress/tests/dataset/datasetDetails.spec.ts
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { ConnectUtils } from '../../support/utils/connect/ConnectUtils';
+import { DatasetBtns } from '../../support/utils/dataset/DatasetBtns';
+import { DatasetUtils } from '../../support/utils/dataset/DatasetUtils';
+
+describe('Test Data Set Details', () => {
+    beforeEach('Setup Test', () => {
+        cy.initStreamPipesTest();
+    });
+
+    it('Shows schema and latest events and opens chart creation', () => {
+        const adapterName = 'Machine Data Simulator Dataset Details';
+
+        ConnectUtils.addMachineDataSimulator(adapterName, true, '100');
+        DatasetUtils.openDatasetDetails(adapterName);
+
+        DatasetBtns.datasetDetailsSchemaTable().should('be.visible');
+        DatasetUtils.expectSchemaField('density', 'Number');
+        DatasetUtils.expectSchemaField('mass_flow', 'Number');
+        DatasetUtils.expectSchemaField('sensorId', 'Text');
+        DatasetUtils.expectSchemaField('sensor_fault_flags', 'Boolean');
+
+        DatasetUtils.openLatestEventsTab();
+        DatasetUtils.expectLatestEventsForColumn('density');
+        DatasetUtils.expectLatestEventsForColumn('mass_flow');
+        DatasetUtils.setLatestEventsLimit(5);
+        DatasetUtils.expectLatestEventsForColumn('density');
+        DatasetUtils.expectLatestEventsForColumn('mass_flow');
+
+        DatasetUtils.createChartFromDatasetDetails();
+
+        cy.url().should('include', '#/chart/create');
+        cy.url().should(
+            'include',
+            `measureName=${encodeURIComponent(adapterName)}`,
+        );
+        cy.dataCy('data-explorer-select-data-set').should(
+            'have.value',
+            adapterName,
+        );
+    });
+});
diff --git a/ui/deployment/i18n/de.json b/ui/deployment/i18n/de.json
index a5d72cf6a9..b8f936636e 100644
--- a/ui/deployment/i18n/de.json
+++ b/ui/deployment/i18n/de.json
@@ -247,6 +247,7 @@
   "Create adapter": "Adapter erstellen",
   "Create asset": "Asset erstellen",
   "Create asset links": "Asset-Links erstellen",
+  "Create chart": "Diagramm erstellen",
   "Create chart in new tab": "Diagramm in neuem Tab erstellen",
   "Create link": "Link erstellen",
   "Create new API key": "Neuen API-Schlüssel erstellen",
@@ -425,6 +426,7 @@
   "Error: {{message}} with cause {{cause}}": "Fehler: {{message}} mit Ursache 
{{cause}}",
   "Event Aggregation": "Aggregation",
   "Event Transformation Configuration has changed": "Die Transformation von 
Events wurde geändert",
+  "Events": "Ereignisse",
   "Everything older than": "Älter als",
   "Exact location of the site": "Genaue Lage des Standorts",
   "Excel template": "Excel Vorlage",
@@ -729,6 +731,7 @@
   "No datasets available": "Keine Datensätze verfügbar",
   "No datasets match your search.": "Keine Datensätze entsprechen deiner 
Suche.",
   "No entries available.": "Keine Einträge vorhanden.",
+  "No event schema available.": "Kein Ereignisschema verfügbar.",
   "No export providers found": "Keine Exportanbieter gefunden",
   "No groups configured": "Keine Gruppen konfiguriert",
   "No labels available - Click 'Manage Labels'": "Keine Labels verfügbar - 
Klicken Sie auf 'Labels verwalten'",
@@ -1102,6 +1105,7 @@
   "The desired adapter was not found!": "Der gewünschte Adapter wurde nicht 
gefunden!",
   "The desired chart was not found!": "Die gewünschte Karte wurde nicht 
gefunden!",
   "The desired dashboard was not found!": "Das gewünschte Dashboard wurde 
nicht gefunden!",
+  "The desired dataset was not found!": "Das gewünschte Dataset wurde nicht 
gefunden!",
   "The desired pipeline was not found!": "Die gewünschte Pipeline wurde nicht 
gefunden",
   "The following fields used by this chart no longer exist in the dataset:": 
"Die folgenden von diesem Diagramm verwendeten Felder existieren nicht mehr im 
Dataset:",
   "The following files already exist. Please rename them.": "Die folgenden 
Dateien existieren bereits. Bitte umbenennen.",
diff --git a/ui/deployment/i18n/en.json b/ui/deployment/i18n/en.json
index f4b2ed0f2d..afd3952a86 100644
--- a/ui/deployment/i18n/en.json
+++ b/ui/deployment/i18n/en.json
@@ -247,6 +247,7 @@
   "Create adapter": null,
   "Create asset": null,
   "Create asset links": null,
+  "Create chart": null,
   "Create chart in new tab": null,
   "Create link": null,
   "Create new API key": null,
@@ -425,6 +426,7 @@
   "Error: {{message}} with cause {{cause}}": "Error: {{message}} with cause 
{{cause}}",
   "Event Aggregation": null,
   "Event Transformation Configuration has changed": null,
+  "Events": null,
   "Everything older than": null,
   "Exact location of the site": null,
   "Excel template": null,
@@ -729,6 +731,7 @@
   "No datasets available": null,
   "No datasets match your search.": null,
   "No entries available.": null,
+  "No event schema available.": null,
   "No export providers found": null,
   "No groups configured": null,
   "No labels available - Click 'Manage Labels'": null,
@@ -1102,6 +1105,7 @@
   "The desired adapter was not found!": null,
   "The desired chart was not found!": null,
   "The desired dashboard was not found!": null,
+  "The desired dataset was not found!": null,
   "The desired pipeline was not found!": null,
   "The following fields used by this chart no longer exist in the dataset:": 
null,
   "The following files already exist. Please rename them.": null,
diff --git a/ui/deployment/i18n/pl.json b/ui/deployment/i18n/pl.json
index 4ff88dd0b2..4a2e1b3513 100644
--- a/ui/deployment/i18n/pl.json
+++ b/ui/deployment/i18n/pl.json
@@ -247,6 +247,7 @@
   "Create adapter": "Utwórz adapter",
   "Create asset": "Utwórz zasób",
   "Create asset links": "Utwórz linki zasobu",
+  "Create chart": "Utwórz wykres",
   "Create chart in new tab": "Utwórz wykres w nowej karcie",
   "Create link": "Utwórz link",
   "Create new API key": "Utwórz nowy klucz API",
@@ -425,6 +426,7 @@
   "Error: {{message}} with cause {{cause}}": "Błąd: {{message}} z powodu 
{{cause}}",
   "Event Aggregation": "Agregacja",
   "Event Transformation Configuration has changed": "Konfiguracja 
transformacji zdarzeń została zmieniona",
+  "Events": "Zdarzenia",
   "Everything older than": "Wszystko starsze niż",
   "Exact location of the site": "Dokładne położenie lokalizacji",
   "Excel template": "Szablon Excela",
@@ -729,6 +731,7 @@
   "No datasets available": "Brak dostępnych zbiorów danych",
   "No datasets match your search.": "Żadne zbiory danych nie pasują do 
wyszukiwania.",
   "No entries available.": "Brak dostępnych wpisów.",
+  "No event schema available.": "Brak dostępnego schematu zdarzeń.",
   "No export providers found": "Nie znaleziono exportera",
   "No groups configured": "Nie skonfigurowano grup",
   "No labels available - Click 'Manage Labels'": "Brak dostępnych etykiet - 
kliknij 'Zarządzaj etykietami'",
@@ -1102,6 +1105,7 @@
   "The desired adapter was not found!": "Nie znaleziono żądanego adaptera!",
   "The desired chart was not found!": "Nie znaleziono żądanego wykresu!",
   "The desired dashboard was not found!": "Nie znaleziono żądanego pulpitu!",
+  "The desired dataset was not found!": "Nie znaleziono żądanego zbioru 
danych!",
   "The desired pipeline was not found!": "Nie znaleziono żądanego strumienia!",
   "The following fields used by this chart no longer exist in the dataset:": 
"Następujące pola używane przez ten wykres nie istnieją już w zbiorze danych:",
   "The following files already exist. Please rename them.": "Następujące pliki 
już istnieją. Zmień ich nazwy.",
diff --git 
a/ui/src/app/chart/components/chart-view/designer-panel/data-settings/chart-data-settings.component.ts
 
b/ui/src/app/chart/components/chart-view/designer-panel/data-settings/chart-data-settings.component.ts
index 2875af0354..a441cd8fb4 100644
--- 
a/ui/src/app/chart/components/chart-view/designer-panel/data-settings/chart-data-settings.component.ts
+++ 
b/ui/src/app/chart/components/chart-view/designer-panel/data-settings/chart-data-settings.component.ts
@@ -34,7 +34,7 @@ import {
     SourceConfig,
 } from '@streampipes/platform-services';
 import { Tuple2 } from '../../../../../core-model/base/Tuple2';
-import { Router } from '@angular/router';
+import { ActivatedRoute, Router } from '@angular/router';
 import { ChartConfigurationService } from 
'../../../../../chart-shared/services/chart-configuration.service';
 import { FieldSelectionPanelComponent } from 
'./field-selection-panel/field-selection-panel.component';
 import { GroupSelectionPanelComponent } from 
'./group-selection-panel/group-selection-panel.component';
@@ -133,6 +133,7 @@ export class ChartDataSettingsComponent implements OnInit {
     private fieldProviderService = inject(ChartFieldProviderService);
     private widgetTypeService = inject(ChartTypeService);
     private router = inject(Router);
+    private route = inject(ActivatedRoute);
 
     @Input() dataConfig: DataExplorerDataConfig;
     @Input() dataLakeMeasure: DataLakeMeasure;
@@ -205,6 +206,18 @@ export class ChartDataSettingsComponent implements OnInit {
     findDefaultConfig(): {
         measureName: string | undefined;
     } {
+        const measureNameFromQueryParams =
+            this.route.snapshot.queryParams.measureName;
+        const matchingMeasurement = this.availableMeasurements.find(
+            measurement =>
+                measurement.measureName === measureNameFromQueryParams,
+        );
+        if (matchingMeasurement) {
+            return {
+                measureName: matchingMeasurement.measureName,
+            };
+        }
+
         if (this.availableMeasurements.length > 0) {
             return {
                 measureName: this.availableMeasurements[0].measureName,
diff --git 
a/ui/src/app/dataset/components/datalake-configuration/datalake-configuration.component.html
 
b/ui/src/app/dataset/components/datalake-configuration/datalake-configuration.component.html
index d8792a53a4..72c6816298 100644
--- 
a/ui/src/app/dataset/components/datalake-configuration/datalake-configuration.component.html
+++ 
b/ui/src/app/dataset/components/datalake-configuration/datalake-configuration.component.html
@@ -67,6 +67,8 @@
                 }"
                 [assetContextConfig]="assetContextConfig"
                 [showActionsMenu]="true"
+                [rowsClickable]="true"
+                (rowClicked)="openDatasetDetails($event.elementId)"
                 matSort
                 data-cy="datalake-settings"
             >
@@ -92,7 +94,11 @@
                     <th mat-header-cell mat-sort-header *matHeaderCellDef>
                         {{ '# Events (7d)' | translate }}
                     </th>
-                    <td mat-cell *matCellDef="let configurationEntry">
+                    <td
+                        mat-cell
+                        data-cy="datalake-number-of-events-latest"
+                        *matCellDef="let configurationEntry"
+                    >
                         @if (configurationEntry.eventsLatestLoading) {
                             <mat-spinner
                                 [diameter]="20"
@@ -137,6 +143,7 @@
                                     <sp-label
                                         tone="neutral"
                                         class="cursor-pointer"
+                                        data-cy="datalake-total-count-button"
                                         minWidth="100px"
                                         [labelText]="
                                             configurationEntry.eventsTotal
@@ -146,7 +153,8 @@
                                         (click)="
                                             receiveTotalMeasurementSize(
                                                 configurationEntry
-                                            )
+                                            );
+                                            $event.stopPropagation()
                                         "
                                     >
                                     </sp-label>
@@ -163,7 +171,8 @@
                                         (click)="
                                             receiveTotalMeasurementSize(
                                                 configurationEntry
-                                            )
+                                            );
+                                            $event.stopPropagation()
                                         "
                                     >
                                     </sp-label>
@@ -197,7 +206,8 @@
                                         (click)="
                                             openRetentionDialog(
                                                 configurationEntry.elementId
-                                            )
+                                            );
+                                            $event.stopPropagation()
                                         "
                                     >
                                         <i
@@ -274,6 +284,14 @@
                 </ng-container>
 
                 <ng-template spTableActions let-element>
+                    <button
+                        mat-menu-item
+                        data-cy="dataset-details-menu-link"
+                        (click)="openDatasetDetails(element.elementId)"
+                    >
+                        <mat-icon>visibility</mat-icon>
+                        <span>{{ 'Show' | translate }}</span>
+                    </button>
                     <button
                         mat-menu-item
                         data-cy="datalake-download-btn"
diff --git 
a/ui/src/app/dataset/components/datalake-configuration/datalake-configuration.component.ts
 
b/ui/src/app/dataset/components/datalake-configuration/datalake-configuration.component.ts
index fcfc0472aa..cf53cd6441 100644
--- 
a/ui/src/app/dataset/components/datalake-configuration/datalake-configuration.component.ts
+++ 
b/ui/src/app/dataset/components/datalake-configuration/datalake-configuration.component.ts
@@ -24,6 +24,7 @@ import {
     OnInit,
     ViewChild,
 } from '@angular/core';
+import { Router } from '@angular/router';
 import {
     MatCell,
     MatCellDef,
@@ -148,6 +149,7 @@ export class DatalakeConfigurationComponent
     private translateService = inject(TranslateService);
     private currentUserService = inject(CurrentUserService);
     private assetFilterService = inject(SpAssetBrowserService);
+    private router = inject(Router);
 
     dataSource: MatTableDataSource<DataLakeConfigurationEntry> =
         new MatTableDataSource([]);
@@ -392,6 +394,10 @@ export class DatalakeConfigurationComponent
         });
     }
 
+    openDatasetDetails(elementId: string): void {
+        this.router.navigate(['datasets', elementId]);
+    }
+
     openRetentionDialog(measurementId: string): void {
         const dialogRef: DialogRef<DataRetentionDialogComponent> =
             this.dialogService.open(DataRetentionDialogComponent, {
diff --git 
a/ui/src/app/dataset/components/dataset-details/abstract-dataset-details.directive.ts
 
b/ui/src/app/dataset/components/dataset-details/abstract-dataset-details.directive.ts
new file mode 100644
index 0000000000..3bea3e3b53
--- /dev/null
+++ 
b/ui/src/app/dataset/components/dataset-details/abstract-dataset-details.directive.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 { Directive, inject } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import {
+    DataLakeMeasure,
+    DatalakeRestService,
+} from '@streampipes/platform-services';
+import { SpBreadcrumbService, SpNavigationItem } from '@streampipes/shared-ui';
+import { catchError, of } from 'rxjs';
+import { SpDatasetDetailsTabs } from './dataset-details-tabs';
+
+@Directive()
+export abstract class SpAbstractDatasetDetailsDirective {
+    protected activatedRoute = inject(ActivatedRoute);
+    protected datalakeRestService = inject(DatalakeRestService);
+    protected breadcrumbService = inject(SpBreadcrumbService);
+
+    currentDatasetId: string;
+    tabs: SpNavigationItem[] = [];
+    dataset: DataLakeMeasure;
+    datasetNotFound = false;
+
+    onInit(): void {
+        const elementId = this.activatedRoute.snapshot.params.elementId;
+        if (elementId) {
+            this.currentDatasetId = elementId;
+            this.tabs = new SpDatasetDetailsTabs().getTabs(elementId);
+            this.loadDataset();
+        }
+    }
+
+    loadDataset(): void {
+        this.datalakeRestService
+            .getMeasurement(this.currentDatasetId)
+            .pipe(
+                catchError(() => {
+                    this.datasetNotFound = true;
+                    return of(null);
+                }),
+            )
+            .subscribe(dataset => {
+                if (!dataset) {
+                    return;
+                }
+
+                this.dataset = DataLakeMeasure.fromData(dataset);
+                this.onDatasetLoaded();
+            });
+    }
+
+    abstract onDatasetLoaded(): void;
+}
diff --git 
a/ui/src/app/dataset/components/dataset-details/dataset-details-events/dataset-details-events.component.html
 
b/ui/src/app/dataset/components/dataset-details/dataset-details-events/dataset-details-events.component.html
new file mode 100644
index 0000000000..d647754863
--- /dev/null
+++ 
b/ui/src/app/dataset/components/dataset-details/dataset-details-events/dataset-details-events.component.html
@@ -0,0 +1,191 @@
+<!--
+~ 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.
+~
+-->
+
+<sp-basic-nav-tabs
+    [spNavigationItems]="tabs"
+    [activeLink]="'events'"
+    [showBackLink]="true"
+    [backLinkTarget]="['datasets']"
+>
+    @if (datasetNotFound) {
+        <div class="text-xl" fxFlex="100" fxLayoutAlign="center center">
+            {{ 'The desired dataset was not found!' | translate }}
+        </div>
+    } @else if (dataset) {
+        <div
+            fxLayout="column"
+            fxFlex="100"
+            fxLayoutGap="1rem"
+            class="events-tab-content"
+        >
+            <div fxLayout="row" fxLayoutAlign="start center">
+                <sp-basic-header-title-component
+                    [title]="dataset.measureName + ' - Latest events'"
+                ></sp-basic-header-title-component>
+                <span fxFlex></span>
+                <sp-element-id
+                    fxLayoutAlign="end start"
+                    [value]="dataset.elementId"
+                >
+                </sp-element-id>
+            </div>
+
+            <div fxLayout="row" fxLayoutAlign="end center">
+                <mat-form-field
+                    appearance="outline"
+                    subscriptSizing="dynamic"
+                    class="event-limit-select"
+                >
+                    <mat-label>{{ 'Events' | translate }}</mat-label>
+                    <input
+                        matInput
+                        type="number"
+                        min="1"
+                        max="1000"
+                        [(ngModel)]="eventLimit"
+                        (change)="onEventLimitChange(eventLimit)"
+                        data-cy="dataset-details-event-limit"
+                    />
+                </mat-form-field>
+            </div>
+
+            <div fxLayout="column" class="events-preview-section">
+                @if (loadingEvents) {
+                    <div
+                        fxLayout="row"
+                        fxLayoutAlign="center center"
+                        class="loading-state"
+                    >
+                        <mat-spinner [diameter]="32" color="accent">
+                            {{ 'Loading' | translate }}
+                        </mat-spinner>
+                    </div>
+                } @else {
+                    <div
+                        class="preview-shell"
+                        fxLayout="column"
+                        data-cy="dataset-details-events-preview"
+                    >
+                        @if (rows.length > 0 && columns.length > 0) {
+                            <div
+                                class="preview-table-scroll"
+                                data-cy="dataset-details-events-table-scroll"
+                            >
+                                <table
+                                    class="preview-table"
+                                    data-cy="dataset-details-events-table"
+                                >
+                                    <thead>
+                                        <tr>
+                                            <th class="index-col">#</th>
+                                            @for (
+                                                column of columns;
+                                                track trackColumn(
+                                                    $index,
+                                                    column
+                                                )
+                                            ) {
+                                                <th
+                                                    [title]="
+                                                        displayColumnName(
+                                                            column
+                                                        )
+                                                    "
+                                                >
+                                                    {{
+                                                        displayColumnName(
+                                                            column
+                                                        )
+                                                    }}
+                                                </th>
+                                            }
+                                        </tr>
+                                    </thead>
+                                    <tbody>
+                                        @for (
+                                            row of rows;
+                                            track trackRow($index)
+                                        ) {
+                                            <tr>
+                                                <td class="index-col">
+                                                    {{ $index + 1 }}
+                                                </td>
+                                                @for (
+                                                    column of columns;
+                                                    track trackColumn(
+                                                        $index,
+                                                        column
+                                                    )
+                                                ) {
+                                                    <td
+                                                        [title]="
+                                                            stringify(
+                                                                row[column]
+                                                            )
+                                                        "
+                                                        [attr.data-cy]="
+                                                            
'dataset-details-event-cell-' +
+                                                            column
+                                                        "
+                                                    >
+                                                        {{
+                                                            formatPreviewValue(
+                                                                column,
+                                                                row[column]
+                                                            )
+                                                        }}
+                                                    </td>
+                                                }
+                                            </tr>
+                                        }
+                                    </tbody>
+                                </table>
+                            </div>
+                        } @else {
+                            <div
+                                class="preview-empty"
+                                fxFlex
+                                fxLayout
+                                fxLayoutAlign="center center"
+                                data-cy="dataset-details-events-empty"
+                            >
+                                {{ 'No data available.' | translate }}
+                            </div>
+                        }
+                    </div>
+                }
+
+                <div
+                    fxLayout="row"
+                    fxLayoutAlign="center center"
+                    class="create-chart-action"
+                >
+                    <button
+                        mat-flat-button
+                        color="accent"
+                        data-cy="dataset-details-create-chart"
+                        (click)="navigateToCreateChart()"
+                    >
+                        <mat-icon>insert_chart</mat-icon>
+                        <span>{{ 'Create chart' | translate }}</span>
+                    </button>
+                </div>
+            </div>
+        </div>
+    }
+</sp-basic-nav-tabs>
diff --git 
a/ui/src/app/dataset/components/dataset-details/dataset-details-events/dataset-details-events.component.scss
 
b/ui/src/app/dataset/components/dataset-details/dataset-details-events/dataset-details-events.component.scss
new file mode 100644
index 0000000000..16526330a0
--- /dev/null
+++ 
b/ui/src/app/dataset/components/dataset-details/dataset-details-events/dataset-details-events.component.scss
@@ -0,0 +1,119 @@
+/*!
+ * 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.
+ *
+ */
+
+.event-limit-select {
+    width: 8rem;
+}
+
+.loading-state {
+    min-height: 12rem;
+}
+
+.events-tab-content {
+    min-height: 0;
+}
+
+.events-preview-section {
+    align-items: stretch;
+}
+
+.preview-shell {
+    display: flex;
+    box-sizing: border-box;
+    border-radius: 0;
+    background: var(--color-bg-0);
+    overflow: hidden;
+}
+
+.create-chart-action {
+    padding-top: var(--space-sm);
+    flex: 0 0 auto;
+}
+
+.preview-table-scroll {
+    max-height: calc(100vh - 300px);
+    overflow: auto;
+}
+
+.preview-table {
+    width: max-content;
+    min-width: 100%;
+    border-collapse: separate;
+    border-spacing: 0;
+    font-size: var(--font-size-xs);
+    line-height: var(--line-height-tight);
+    background: var(--color-bg-0);
+}
+
+.preview-table th,
+.preview-table td {
+    padding: var(--padding-table-cell-compact-y)
+        var(--padding-table-cell-compact-x);
+    text-align: left;
+    border-bottom: 1px solid var(--color-border-subtle);
+    border-right: 1px solid var(--color-border-subtle);
+    white-space: nowrap;
+    max-width: 16rem;
+    overflow: hidden;
+    text-overflow: ellipsis;
+}
+
+.preview-table th {
+    position: sticky;
+    top: 0;
+    z-index: 1;
+    background: var(--color-bg-0);
+    color: var(--color-default-text);
+    font-weight: var(--font-weight-semibold);
+}
+
+.preview-table tbody tr:nth-child(even) td {
+    background: var(--color-surface-subtle);
+}
+
+.preview-table tbody tr:hover td {
+    background: var(--color-surface-selected-subtle);
+}
+
+.preview-table th.index-col,
+.preview-table td.index-col {
+    position: sticky;
+    left: 0;
+    z-index: 2;
+    min-width: var(--size-table-index-column);
+    max-width: var(--size-table-index-column);
+    text-align: right;
+    color: var(--color-secondary-text);
+    background: var(--color-bg-0);
+}
+
+.preview-table tbody tr:nth-child(even) td.index-col {
+    background: var(--color-surface-subtle);
+}
+
+.preview-table tbody tr:hover td.index-col {
+    background: var(--color-surface-selected-subtle);
+}
+
+.preview-empty {
+    min-height: 12rem;
+    padding: var(--space-md);
+    text-align: center;
+    color: var(--color-secondary-text);
+    font-size: var(--font-size-xs);
+}
diff --git 
a/ui/src/app/dataset/components/dataset-details/dataset-details-events/dataset-details-events.component.ts
 
b/ui/src/app/dataset/components/dataset-details/dataset-details-events/dataset-details-events.component.ts
new file mode 100644
index 0000000000..7c19543d72
--- /dev/null
+++ 
b/ui/src/app/dataset/components/dataset-details/dataset-details-events/dataset-details-events.component.ts
@@ -0,0 +1,201 @@
+/*
+ * 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, OnInit, inject } from '@angular/core';
+import { Router } from '@angular/router';
+import { SpQueryResult } from '@streampipes/platform-services';
+import {
+    SpBasicHeaderTitleComponent,
+    SpBasicNavTabsComponent,
+    SpElementIdComponent,
+} from '@streampipes/shared-ui';
+import { SpAbstractDatasetDetailsDirective } from 
'../abstract-dataset-details.directive';
+import {
+    FlexDirective,
+    LayoutAlignDirective,
+    LayoutDirective,
+    LayoutGapDirective,
+} from '@ngbracket/ngx-layout/flex';
+import { MatButton } from '@angular/material/button';
+import { MatIcon } from '@angular/material/icon';
+import { MatProgressSpinner } from '@angular/material/progress-spinner';
+import { MatFormField, MatLabel } from '@angular/material/form-field';
+import { MatInput } from '@angular/material/input';
+import { FormsModule } from '@angular/forms';
+import { TranslatePipe } from '@ngx-translate/core';
+import { catchError, finalize, of } from 'rxjs';
+import { SpConfigurationRoutes } from 
'../../../../configuration/configuration.breadcrumb';
+import { DatePipe } from '@angular/common';
+
+type PreviewRow = Record<string, unknown>;
+
+@Component({
+    selector: 'sp-dataset-details-events',
+    templateUrl: './dataset-details-events.component.html',
+    styleUrls: ['./dataset-details-events.component.scss'],
+    imports: [
+        SpBasicNavTabsComponent,
+        SpBasicHeaderTitleComponent,
+        SpElementIdComponent,
+        LayoutDirective,
+        LayoutAlignDirective,
+        LayoutGapDirective,
+        FlexDirective,
+        MatButton,
+        MatIcon,
+        MatProgressSpinner,
+        MatFormField,
+        MatLabel,
+        MatInput,
+        FormsModule,
+        TranslatePipe,
+    ],
+})
+export class DatasetDetailsEventsComponent
+    extends SpAbstractDatasetDetailsDirective
+    implements OnInit
+{
+    private router = inject(Router);
+    private datePipe = new DatePipe('en-US');
+
+    eventLimit = 10;
+    columns: string[] = [];
+    rows: PreviewRow[] = [];
+    totalRows = 0;
+    loadingEvents = false;
+
+    ngOnInit(): void {
+        super.onInit();
+    }
+
+    onDatasetLoaded(): void {
+        this.breadcrumbService.updateBreadcrumb([
+            SpConfigurationRoutes.BASE,
+            { label: 'Datasets', link: ['datasets'] },
+            { label: this.dataset.measureName },
+            { label: 'Latest events' },
+        ]);
+        this.loadLatestEvents();
+    }
+
+    loadLatestEvents(): void {
+        if (!this.dataset) {
+            return;
+        }
+
+        this.loadingEvents = true;
+        this.datalakeRestService
+            .getData(this.dataset.measureName, {
+                endDate: new Date().getTime(),
+                startDate: 0,
+                limit: this.eventLimit,
+                order: 'DESC',
+                missingValueBehaviour: 'empty',
+                columns: this.getRuntimeNames().toString(),
+            })
+            .pipe(
+                catchError(() => {
+                    return of(new SpQueryResult());
+                }),
+                finalize(() => {
+                    this.loadingEvents = false;
+                }),
+            )
+            .subscribe(result => this.applyPreviewResult(result));
+    }
+
+    onEventLimitChange(value: number): void {
+        this.eventLimit = Math.min(Math.max(Number(value) || 1, 1), 1000);
+        this.loadLatestEvents();
+    }
+
+    navigateToCreateChart(): void {
+        this.router.navigate(['chart', 'create'], {
+            queryParams: {
+                editMode: true,
+                measureName: this.dataset.measureName,
+            },
+        });
+    }
+
+    formatPreviewValue(name: string, value: unknown): string {
+        if (name === 'time' && this.isValidDateValue(value)) {
+            return (
+                this.datePipe.transform(
+                    value as string | number | Date,
+                    'yyyy-MM-dd HH:mm:ss.SSS',
+                ) ?? this.stringify(value)
+            );
+        }
+
+        return this.stringify(value);
+    }
+
+    stringify(value: unknown): string {
+        if (value === null || value === undefined) {
+            return '-';
+        }
+
+        return String(value);
+    }
+
+    isValidDateValue(value: unknown): boolean {
+        if (value === null || value === undefined || value === '') {
+            return false;
+        }
+
+        const date = new Date(value as string | number);
+        return !Number.isNaN(date.getTime());
+    }
+
+    displayColumnName(column: string): string {
+        return column;
+    }
+
+    trackColumn(_index: number, column: string): string {
+        return column;
+    }
+
+    trackRow(index: number): number {
+        return index;
+    }
+
+    private getRuntimeNames(): string[] {
+        return (this.dataset.eventSchema?.eventProperties ?? []).map(
+            property => property.runtimeName,
+        );
+    }
+
+    private applyPreviewResult(result: SpQueryResult): void {
+        this.columns = this.orderHeaderColumns(result?.headers ?? []);
+        const rows = result?.allDataSeries?.[0]?.rows ?? [];
+        this.totalRows = rows.length;
+        this.rows = rows.map(row =>
+            this.columns.reduce<PreviewRow>((previewRow, column) => {
+                previewRow[column] = row[result.headers.indexOf(column)];
+                return previewRow;
+            }, {}),
+        );
+    }
+
+    private orderHeaderColumns(columns: string[]): string[] {
+        const timeColumns = columns.filter(column => column === 'time');
+        const otherColumns = columns.filter(column => column !== 'time');
+        return [...timeColumns, ...otherColumns];
+    }
+}
diff --git 
a/ui/src/app/dataset/components/dataset-details/dataset-details-schema/dataset-details-schema.component.html
 
b/ui/src/app/dataset/components/dataset-details/dataset-details-schema/dataset-details-schema.component.html
new file mode 100644
index 0000000000..fc1ddca160
--- /dev/null
+++ 
b/ui/src/app/dataset/components/dataset-details/dataset-details-schema/dataset-details-schema.component.html
@@ -0,0 +1,122 @@
+<!--
+~ 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.
+~
+-->
+
+<sp-basic-nav-tabs
+    [spNavigationItems]="tabs"
+    [activeLink]="'schema'"
+    [showBackLink]="true"
+    [backLinkTarget]="['datasets']"
+>
+    @if (datasetNotFound) {
+        <div class="text-xl" fxFlex="100" fxLayoutAlign="center center">
+            {{ 'The desired dataset was not found!' | translate }}
+        </div>
+    } @else if (dataset) {
+        <div fxLayout="column" fxFlex="100">
+            <div fxLayout="row" fxLayoutAlign="start center">
+                <sp-basic-header-title-component
+                    [title]="dataset.measureName + ' - Event schema'"
+                ></sp-basic-header-title-component>
+                <span fxFlex></span>
+                <sp-element-id
+                    fxLayoutAlign="end start"
+                    [value]="dataset.elementId"
+                >
+                </sp-element-id>
+            </div>
+
+            @if (schemaRows.length > 0) {
+                <div data-cy="dataset-details-schema-table">
+                    <table mat-table [dataSource]="schemaRows" class="w-100">
+                        <ng-container matColumnDef="runtimeName">
+                            <th mat-header-cell *matHeaderCellDef>
+                                <strong>{{
+                                    'Runtime Name' | translate
+                                }}</strong>
+                            </th>
+                            <td mat-cell *matCellDef="let row">
+                                <div class="runtime-name-row">
+                                    <div
+                                        class="runtime-name"
+                                        [attr.data-cy]="
+                                            'dataset-details-schema-field-' +
+                                            row.runtimeName
+                                        "
+                                    >
+                                        {{ row.runtimeName }}
+                                    </div>
+                                    <sp-property-scope-badge
+                                        [propertyScope]="row.propertyScope"
+                                    />
+                                </div>
+                            </td>
+                        </ng-container>
+
+                        <ng-container matColumnDef="label">
+                            <th mat-header-cell *matHeaderCellDef>
+                                <strong>{{ 'Field Name' | translate }}</strong>
+                            </th>
+                            <td mat-cell *matCellDef="let row">
+                                {{ row.label }}
+                            </td>
+                        </ng-container>
+
+                        <ng-container matColumnDef="dataType">
+                            <th mat-header-cell *matHeaderCellDef>
+                                <strong>{{ 'Data type' | translate }}</strong>
+                            </th>
+                            <td mat-cell *matCellDef="let row">
+                                <div
+                                    class="value"
+                                    [attr.data-cy]="
+                                        'dataset-details-schema-type-' +
+                                        row.runtimeName
+                                    "
+                                >
+                                    {{ row.dataType }}
+                                </div>
+                            </td>
+                        </ng-container>
+
+                        <ng-container matColumnDef="description">
+                            <th mat-header-cell *matHeaderCellDef>
+                                <strong>{{ 'Description' | translate 
}}</strong>
+                            </th>
+                            <td mat-cell *matCellDef="let row">
+                                {{ row.description }}
+                            </td>
+                        </ng-container>
+
+                        <tr
+                            mat-header-row
+                            *matHeaderRowDef="schemaColumns"
+                        ></tr>
+                        <tr
+                            mat-row
+                            *matRowDef="let row; columns: schemaColumns"
+                        ></tr>
+                    </table>
+                </div>
+            } @else {
+                <div class="empty-state">
+                    {{ 'No event schema available.' | translate }}
+                </div>
+            }
+        </div>
+    }
+</sp-basic-nav-tabs>
diff --git a/ui/src/app/dataset/dataset.routes.ts 
b/ui/src/app/dataset/components/dataset-details/dataset-details-schema/dataset-details-schema.component.scss
similarity index 60%
copy from ui/src/app/dataset/dataset.routes.ts
copy to 
ui/src/app/dataset/components/dataset-details/dataset-details-schema/dataset-details-schema.component.scss
index 1bd06b8b40..32e33a0f81 100644
--- a/ui/src/app/dataset/dataset.routes.ts
+++ 
b/ui/src/app/dataset/components/dataset-details/dataset-details-schema/dataset-details-schema.component.scss
@@ -16,17 +16,36 @@
  *
  */
 
-import { Routes } from '@angular/router';
-import { DatalakeConfigurationComponent } from 
'./components/datalake-configuration/datalake-configuration.component';
+.empty-state {
+    padding: var(--space-md);
+    border: 1px solid var(--color-bg-3);
+    background: var(--color-bg-1);
+}
 
-export const DATASET_ROUTES: Routes = [
-    {
-        path: '',
-        children: [
-            {
-                path: '',
-                component: DatalakeConfigurationComponent,
-            },
-        ],
-    },
-];
+.runtime-name-row {
+    display: inline-flex;
+    align-items: center;
+    gap: var(--space-xs);
+}
+
+.runtime-name {
+    font-weight: bold;
+    border-radius: 3px;
+    padding: 3px 5px;
+    background: var(--color-bg-3);
+    display: inline-flex;
+}
+
+.mat-mdc-row:nth-child(even) {
+    background: var(--color-bg-1);
+}
+
+.value {
+    border-radius: 3px;
+    padding: 3px 5px;
+}
+
+td,
+th {
+    white-space: nowrap;
+}
diff --git 
a/ui/src/app/dataset/components/dataset-details/dataset-details-schema/dataset-details-schema.component.ts
 
b/ui/src/app/dataset/components/dataset-details/dataset-details-schema/dataset-details-schema.component.ts
new file mode 100644
index 0000000000..a5df2a76de
--- /dev/null
+++ 
b/ui/src/app/dataset/components/dataset-details/dataset-details-schema/dataset-details-schema.component.ts
@@ -0,0 +1,134 @@
+/*
+ * 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, OnInit, inject } from '@angular/core';
+import {
+    PipelineElementSchemaService,
+    PropertyScopeBadgeComponent,
+    SpBasicHeaderTitleComponent,
+    SpBasicNavTabsComponent,
+    SpElementIdComponent,
+} from '@streampipes/shared-ui';
+import { SpAbstractDatasetDetailsDirective } from 
'../abstract-dataset-details.directive';
+import {
+    FlexDirective,
+    LayoutAlignDirective,
+    LayoutDirective,
+} from '@ngbracket/ngx-layout/flex';
+import {
+    MatCell,
+    MatCellDef,
+    MatColumnDef,
+    MatHeaderCell,
+    MatHeaderCellDef,
+    MatHeaderRow,
+    MatHeaderRowDef,
+    MatRow,
+    MatRowDef,
+    MatTable,
+} from '@angular/material/table';
+import { TranslatePipe } from '@ngx-translate/core';
+import { SpConfigurationRoutes } from 
'../../../../configuration/configuration.breadcrumb';
+
+interface SchemaRow {
+    runtimeName: string;
+    label: string;
+    dataType: string;
+    propertyScope: string;
+    description: string;
+}
+
+@Component({
+    selector: 'sp-dataset-details-schema',
+    templateUrl: './dataset-details-schema.component.html',
+    styleUrls: ['./dataset-details-schema.component.scss'],
+    imports: [
+        SpBasicNavTabsComponent,
+        SpBasicHeaderTitleComponent,
+        SpElementIdComponent,
+        PropertyScopeBadgeComponent,
+        LayoutDirective,
+        LayoutAlignDirective,
+        FlexDirective,
+        MatTable,
+        MatColumnDef,
+        MatHeaderCellDef,
+        MatHeaderCell,
+        MatCellDef,
+        MatCell,
+        MatHeaderRowDef,
+        MatHeaderRow,
+        MatRowDef,
+        MatRow,
+        TranslatePipe,
+    ],
+})
+export class DatasetDetailsSchemaComponent
+    extends SpAbstractDatasetDetailsDirective
+    implements OnInit
+{
+    private pipelineElementSchemaService = 
inject(PipelineElementSchemaService);
+
+    schemaRows: SchemaRow[] = [];
+    schemaColumns = ['runtimeName', 'label', 'dataType', 'description'];
+
+    ngOnInit(): void {
+        super.onInit();
+    }
+
+    onDatasetLoaded(): void {
+        this.schemaRows = this.makeSchemaRows();
+        this.breadcrumbService.updateBreadcrumb([
+            SpConfigurationRoutes.BASE,
+            { label: 'Datasets', link: ['datasets'] },
+            { label: this.dataset.measureName },
+            { label: 'Event schema' },
+        ]);
+    }
+
+    private makeSchemaRows(): SchemaRow[] {
+        const eventProperties = this.dataset.eventSchema?.eventProperties ?? 
[];
+        const schemaRows = eventProperties.map(property => ({
+            runtimeName: property.runtimeName || 'n/a',
+            label: property.label || 'n/a',
+            dataType:
+                this.pipelineElementSchemaService.getFriendlyRuntimeType(
+                    property,
+                ),
+            propertyScope: property.propertyScope,
+            description: property.description || 'n/a',
+        }));
+
+        return [this.makeTimestampRow(), ...schemaRows];
+    }
+
+    private makeTimestampRow(): SchemaRow {
+        return {
+            runtimeName: this.getTimestampFieldName(),
+            label: 'n/a',
+            dataType: 'Timestamp',
+            propertyScope: 'HEADER_PROPERTY',
+            description:
+                'Timestamp field used for storing and querying this dataset',
+        };
+    }
+
+    private getTimestampFieldName(): string {
+        return this.dataset.timestampField?.split('::').pop() || 'time';
+    }
+}
diff --git a/ui/src/app/dataset/dataset.routes.ts 
b/ui/src/app/dataset/components/dataset-details/dataset-details-tabs.ts
similarity index 62%
copy from ui/src/app/dataset/dataset.routes.ts
copy to ui/src/app/dataset/components/dataset-details/dataset-details-tabs.ts
index 1bd06b8b40..db4637ac90 100644
--- a/ui/src/app/dataset/dataset.routes.ts
+++ b/ui/src/app/dataset/components/dataset-details/dataset-details-tabs.ts
@@ -16,17 +16,21 @@
  *
  */
 
-import { Routes } from '@angular/router';
-import { DatalakeConfigurationComponent } from 
'./components/datalake-configuration/datalake-configuration.component';
+import { SpNavigationItem } from '@streampipes/shared-ui';
 
-export const DATASET_ROUTES: Routes = [
-    {
-        path: '',
-        children: [
+export class SpDatasetDetailsTabs {
+    public getTabs(elementId: string): SpNavigationItem[] {
+        return [
             {
-                path: '',
-                component: DatalakeConfigurationComponent,
+                itemId: 'schema',
+                itemTitle: 'Event schema',
+                itemLink: ['datasets', elementId, 'schema'],
             },
-        ],
-    },
-];
+            {
+                itemId: 'events',
+                itemTitle: 'Latest events',
+                itemLink: ['datasets', elementId, 'events'],
+            },
+        ];
+    }
+}
diff --git 
a/ui/src/app/dataset/components/dataset-feature-card/dataset-feature-card.component.ts
 
b/ui/src/app/dataset/components/dataset-feature-card/dataset-feature-card.component.ts
index 99faeba446..f14d2f9332 100644
--- 
a/ui/src/app/dataset/components/dataset-feature-card/dataset-feature-card.component.ts
+++ 
b/ui/src/app/dataset/components/dataset-feature-card/dataset-feature-card.component.ts
@@ -17,6 +17,7 @@
  */
 
 import { Component, inject, Input, OnInit } from '@angular/core';
+import { Router } from '@angular/router';
 import { FlexFillDirective } from '@ngbracket/ngx-layout';
 import {
     DateFormatService,
@@ -76,6 +77,7 @@ export class DatasetFeatureCardComponent implements OnInit {
     private datalakeRestService = inject(DatalakeRestService);
     private genericStorageService = inject(GenericStorageService);
     private dateFormatService = inject(DateFormatService);
+    private router = inject(Router);
 
     ngOnInit() {
         forkJoin([
@@ -162,7 +164,9 @@ export class DatasetFeatureCardComponent implements OnInit {
         );
     }
 
-    navigateToChartView(): void {}
+    navigateToChartView(): void {
+        this.router.navigate(['datasets', this.resourceId, 'schema']);
+    }
 }
 
 interface PreviewRow {
diff --git a/ui/src/app/dataset/dataset.routes.ts 
b/ui/src/app/dataset/dataset.routes.ts
index 1bd06b8b40..b1d8417bb0 100644
--- a/ui/src/app/dataset/dataset.routes.ts
+++ b/ui/src/app/dataset/dataset.routes.ts
@@ -18,6 +18,8 @@
 
 import { Routes } from '@angular/router';
 import { DatalakeConfigurationComponent } from 
'./components/datalake-configuration/datalake-configuration.component';
+import { DatasetDetailsSchemaComponent } from 
'./components/dataset-details/dataset-details-schema/dataset-details-schema.component';
+import { DatasetDetailsEventsComponent } from 
'./components/dataset-details/dataset-details-events/dataset-details-events.component';
 
 export const DATASET_ROUTES: Routes = [
     {
@@ -25,8 +27,27 @@ export const DATASET_ROUTES: Routes = [
         children: [
             {
                 path: '',
+                pathMatch: 'full',
                 component: DatalakeConfigurationComponent,
             },
+            {
+                path: ':elementId',
+                children: [
+                    {
+                        path: '',
+                        pathMatch: 'full',
+                        redirectTo: 'schema',
+                    },
+                    {
+                        path: 'schema',
+                        component: DatasetDetailsSchemaComponent,
+                    },
+                    {
+                        path: 'events',
+                        component: DatasetDetailsEventsComponent,
+                    },
+                ],
+            },
         ],
     },
 ];

Reply via email to