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

SvenO3 pushed a commit to branch 4566-add-details-view-for-data-sets
in repository https://gitbox.apache.org/repos/asf/streampipes.git


The following commit(s) were added to 
refs/heads/4566-add-details-view-for-data-sets by this push:
     new 241b0d29c9 Add details view for datasets
241b0d29c9 is described below

commit 241b0d29c9ab3b7094e8552e18634f98f49128d4
Author: Sven Oehler <[email protected]>
AuthorDate: Thu Jun 18 17:07:13 2026 +0200

    Add details view for datasets
---
 .../data-settings/chart-data-settings.component.ts |  15 +-
 .../datalake-configuration.component.html          |  10 +
 .../datalake-configuration.component.ts            |   6 +
 .../abstract-dataset-details.directive.ts          |  69 +++++++
 .../dataset-details-events.component.html          | 213 +++++++++++++++++++++
 .../dataset-details-events.component.scss          | 149 ++++++++++++++
 .../dataset-details-events.component.ts            | 198 +++++++++++++++++++
 .../dataset-details-schema.component.html          | 110 +++++++++++
 .../dataset-details-schema.component.scss}         |  22 +--
 .../dataset-details-schema.component.ts            | 124 ++++++++++++
 .../dataset-details/dataset-details-tabs.ts}       |  26 +--
 .../dataset-feature-card.component.ts              |   6 +-
 ui/src/app/dataset/dataset.routes.ts               |  21 ++
 13 files changed, 943 insertions(+), 26 deletions(-)

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..a6fce58ee0 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"
             >
@@ -274,6 +276,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..ba8b1522e0
--- /dev/null
+++ 
b/ui/src/app/dataset/components/dataset-details/dataset-details-events/dataset-details-events.component.html
@@ -0,0 +1,213 @@
+<!--
+~ 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>
+                    <mat-select
+                        [(ngModel)]="eventLimit"
+                        (selectionChange)="loadLatestEvents()"
+                        data-cy="dataset-details-event-limit"
+                    >
+                        @for (option of eventLimitOptions; track option) {
+                            <mat-option [value]="option">
+                                {{ option }}
+                            </mat-option>
+                        }
+                    </mat-select>
+                </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"
+                    >
+                        <div
+                            class="preview-header"
+                            fxLayout="row"
+                            fxLayoutAlign="space-between center"
+                        >
+                            <div class="preview-title" fxFlex>
+                                {{ 'Latest events' | translate }}
+                            </div>
+                            <div
+                                class="preview-header-actions"
+                                fxLayout="row"
+                                fxLayoutAlign="end center"
+                            >
+                                <div class="preview-meta">
+                                    {{ totalRows }} {{ 'rows' | translate }}
+                                </div>
+                            </div>
+                        </div>
+
+                        @if (rows.length > 0 && columns.length > 0) {
+                            <div
+                                class="preview-table-scroll"
+                                fxFlex
+                                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..cd671cc786
--- /dev/null
+++ 
b/ui/src/app/dataset/components/dataset-details/dataset-details-events/dataset-details-events.component.scss
@@ -0,0 +1,149 @@
+/*!
+ * 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 {
+    flex: 1 1 auto;
+    min-height: 0;
+}
+
+.preview-shell {
+    display: flex;
+    flex: 1 1 auto;
+    min-height: 0;
+    box-sizing: border-box;
+    border-top: 1px solid var(--color-bg-2);
+    border-radius: 0;
+    background: var(--color-bg-0);
+    overflow: hidden;
+}
+
+.create-chart-action {
+    padding-top: var(--space-sm);
+    flex: 0 0 auto;
+}
+
+.preview-header {
+    gap: var(--space-sm);
+    padding: var(--padding-panel-header-y) var(--padding-panel-header-x);
+    border-bottom: 1px solid var(--color-tab-border);
+    background: var(--color-panel-header-accent);
+    min-height: var(--size-panel-header-compact);
+    box-sizing: border-box;
+}
+
+.preview-title {
+    font-size: var(--font-size-sm);
+    font-weight: var(--font-weight-semibold);
+    line-height: var(--line-height-normal);
+}
+
+.preview-header-actions {
+    gap: var(--space-2xs);
+}
+
+.preview-meta {
+    font-size: var(--font-size-xs);
+    color: var(--color-secondary-text);
+    white-space: nowrap;
+}
+
+.preview-table-scroll {
+    flex: 1 1 auto;
+    min-height: 0;
+    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..04b394315e
--- /dev/null
+++ 
b/ui/src/app/dataset/components/dataset-details/dataset-details-events/dataset-details-events.component.ts
@@ -0,0 +1,198 @@
+/*
+ * 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 { MatOption, MatSelect } from '@angular/material/select';
+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,
+        MatSelect,
+        MatOption,
+        FormsModule,
+        TranslatePipe,
+    ],
+})
+export class DatasetDetailsEventsComponent
+    extends SpAbstractDatasetDetailsDirective
+    implements OnInit
+{
+    private router = inject(Router);
+    private datePipe = new DatePipe('en-US');
+
+    eventLimit = 10;
+    eventLimitOptions = [10, 25, 50];
+    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));
+    }
+
+    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..45905c5ee7
--- /dev/null
+++ 
b/ui/src/app/dataset/components/dataset-details/dataset-details-schema/dataset-details-schema.component.html
@@ -0,0 +1,110 @@
+<!--
+~ 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) {
+                <table
+                    mat-table
+                    [dataSource]="schemaRows"
+                    class="w-100"
+                    data-cy="dataset-details-schema-table"
+                >
+                    <ng-container matColumnDef="runtimeName">
+                        <th mat-header-cell *matHeaderCellDef>
+                            {{ 'Runtime Name' | translate }}
+                        </th>
+                        <td mat-cell *matCellDef="let row">
+                            {{ row.runtimeName }}
+                        </td>
+                    </ng-container>
+
+                    <ng-container matColumnDef="label">
+                        <th mat-header-cell *matHeaderCellDef>
+                            {{ 'Field Name' | translate }}
+                        </th>
+                        <td mat-cell *matCellDef="let row">
+                            {{ row.label }}
+                        </td>
+                    </ng-container>
+
+                    <ng-container matColumnDef="dataType">
+                        <th mat-header-cell *matHeaderCellDef>
+                            {{ 'Data type' | translate }}
+                        </th>
+                        <td mat-cell *matCellDef="let row">
+                            {{ row.dataType }}
+                        </td>
+                    </ng-container>
+
+                    <ng-container matColumnDef="propertyScope">
+                        <th mat-header-cell *matHeaderCellDef>
+                            {{ 'Property scope' | translate }}
+                        </th>
+                        <td mat-cell *matCellDef="let row">
+                            <sp-property-scope-badge
+                                [propertyScope]="row.propertyScope"
+                            />
+                        </td>
+                    </ng-container>
+
+                    <ng-container matColumnDef="description">
+                        <th mat-header-cell *matHeaderCellDef>
+                            {{ 'Description' | translate }}
+                        </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>
+            } @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 67%
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..e76c1e7326 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,13 @@
  *
  */
 
-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,
-            },
-        ],
-    },
-];
+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..4e1f0cacca
--- /dev/null
+++ 
b/ui/src/app/dataset/components/dataset-details/dataset-details-schema/dataset-details-schema.component.ts
@@ -0,0 +1,124 @@
+/*
+ * 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',
+        'propertyScope',
+        '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[] {
+        return (this.dataset.eventSchema?.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',
+            }),
+        );
+    }
+}
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