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 8e68cbbfb0 feat: Harmonize resource creation workflow for assets 
(#4590)
8e68cbbfb0 is described below

commit 8e68cbbfb0fea2d04bdb02b7316666e485ee74a9
Author: Jacqueline Höllig <[email protected]>
AuthorDate: Fri Jun 19 18:37:32 2026 +0200

    feat: Harmonize resource creation workflow for assets (#4590)
---
 ui/deployment/i18n/de.json                         |   3 +-
 ui/deployment/i18n/en.json                         |   3 +-
 ui/deployment/i18n/pl.json                         |   3 +-
 ui/src/app/assets/assets.routes.ts                 |   2 +
 .../edit-asset/asset-details.component.html        |  29 ++-
 .../edit-asset/asset-details.component.ts          | 225 ++++++++++++++++++++-
 .../asset-overview/asset-overview.component.html   |   8 +-
 .../asset-overview/asset-overview.component.ts     |  72 +++++--
 8 files changed, 314 insertions(+), 31 deletions(-)

diff --git a/ui/deployment/i18n/de.json b/ui/deployment/i18n/de.json
index dfc76c4696..46bfc43003 100644
--- a/ui/deployment/i18n/de.json
+++ b/ui/deployment/i18n/de.json
@@ -638,6 +638,7 @@
   "Mail Template": "Mail-Vorlage",
   "Make available to all users with appropriate role": "Allen Benutzern mit 
entsprechender Rolle zur Verfügung stellen",
   "Manage": "Verwalten",
+  "Manage Asset ": "Asset verwalten ",
   "Manage Chart ": "Diagramm verwalten",
   "Manage Dashboard ": "Dashboard verwalten",
   "Manage Labels": "Labels verwalten",
@@ -649,7 +650,6 @@
   "Manage links": "Verwalten von Links",
   "Manage permissions": "Berechtigungen verwalten",
   "Manage permissions for adapter ": "Berechtigungen für Adapter verwalten",
-  "Manage permissions for asset ": "Berechtigungen für Assets verwalten",
   "Manage permissions for dataset ": "Berechtigungen für Datensatz verwalten",
   "Manage permissions for pipeline element {{name}}": "Berechtigungen für 
Pipeline-Element {{name}} verwalten",
   "Manage roles": "Rollen verwalten",
@@ -1179,6 +1179,7 @@
   "Update": "Aktualisieren",
   "Update Adapter": "Adapter aktualisieren",
   "Update adapter and migrate pipelines": "Adapter aktualisieren und Pipelines 
migrieren",
+  "Update all changes to asset or discard current changes.": "Alle Änderungen 
am Asset übernehmen oder die aktuellen Änderungen verwerfen.",
   "Update all changes to chart or discard current changes.": "Aktualisieren 
Sie alle Änderungen im Diagramm oder verwerfen Sie die aktuellen Änderungen.",
   "Update all changes to dashboard charts or discard current changes.": 
"Aktualisieren Sie alle Änderungen der Diagramme oder verwerfen Sie aktuelle 
Änderungen.",
   "Update asset links": "Asset-Links aktualisieren",
diff --git a/ui/deployment/i18n/en.json b/ui/deployment/i18n/en.json
index 8cc64c0bb6..66c48b1178 100644
--- a/ui/deployment/i18n/en.json
+++ b/ui/deployment/i18n/en.json
@@ -638,6 +638,7 @@
   "Mail Template": null,
   "Make available to all users with appropriate role": null,
   "Manage": null,
+  "Manage Asset ": null,
   "Manage Chart ": null,
   "Manage Dashboard ": null,
   "Manage Labels": null,
@@ -649,7 +650,6 @@
   "Manage links": null,
   "Manage permissions": null,
   "Manage permissions for adapter ": null,
-  "Manage permissions for asset ": null,
   "Manage permissions for dataset ": null,
   "Manage permissions for pipeline element {{name}}": "Manage permissions for 
pipeline element {{name}}",
   "Manage roles": null,
@@ -1179,6 +1179,7 @@
   "Update": null,
   "Update Adapter": null,
   "Update adapter and migrate pipelines": null,
+  "Update all changes to asset or discard current changes.": null,
   "Update all changes to chart or discard current changes.": null,
   "Update all changes to dashboard charts or discard current changes.": null,
   "Update asset links": null,
diff --git a/ui/deployment/i18n/pl.json b/ui/deployment/i18n/pl.json
index eba340d80e..b998c2d4c2 100644
--- a/ui/deployment/i18n/pl.json
+++ b/ui/deployment/i18n/pl.json
@@ -638,6 +638,7 @@
   "Mail Template": "Szablon wiadomości",
   "Make available to all users with appropriate role": "Udostępnij wszystkim 
użytkownikom z odpowiednią rolą",
   "Manage": "Zarządzanie",
+  "Manage Asset ": "Zarządzanie aktywami ",
   "Manage Chart ": "Zarządzaj wykresem",
   "Manage Dashboard ": "Zarządzaj pulpitem nawigacyjnym",
   "Manage Labels": "Zarządzaj etykietami",
@@ -649,7 +650,6 @@
   "Manage links": "Zarządzaj linkami",
   "Manage permissions": "Zarządzaj uprawnieniami",
   "Manage permissions for adapter ": "Zarządzaj uprawnieniami dla adaptera ",
-  "Manage permissions for asset ": "Zarządzaj uprawnieniami dla zasobu ",
   "Manage permissions for dataset ": "Zarządzaj uprawnieniami dla zbioru 
danych ",
   "Manage permissions for pipeline element {{name}}": "Zarządzaj uprawnieniami 
dla elementu strumienia {{name}}",
   "Manage roles": "Zarządzaj rolami",
@@ -1179,6 +1179,7 @@
   "Update": "Aktualizuj",
   "Update Adapter": "Aktualizuj adapter",
   "Update adapter and migrate pipelines": "Zaktualizuj adapter i zmigruj 
strumień",
+  "Update all changes to asset or discard current changes.": "Zapisz wszystkie 
zmiany w zasobie lub odrzuć bieżące zmiany.",
   "Update all changes to chart or discard current changes.": "Zapisz wszystkie 
zmiany wykresu lub odrzuć bieżące zmiany.",
   "Update all changes to dashboard charts or discard current changes.": 
"Zapisz wszystkie zmiany wykresów pulpitu lub odrzuć bieżące zmiany.",
   "Update asset links": "Zaktualizuj linki zasobu",
diff --git a/ui/src/app/assets/assets.routes.ts 
b/ui/src/app/assets/assets.routes.ts
index 34f0e5173a..4df4520b9d 100644
--- a/ui/src/app/assets/assets.routes.ts
+++ b/ui/src/app/assets/assets.routes.ts
@@ -22,6 +22,7 @@ import { SpViewAssetComponent } from 
'./components/asset-details/view-asset/view
 import { SpAssetDetailsComponent } from 
'./components/asset-details/edit-asset/asset-details.component';
 import { UserPrivilege } from '../core/auth/user-privilege.enum';
 import { PageAuthGuard } from 
'../core/auth/guards/page-auth.can-activate.guard';
+import { ChartPanelCanDeactivateGuard } from 
'../chart-shared/services/chart-panel-can-deactivate-guard.service';
 
 export const ASSET_ROUTES: Routes = [
     {
@@ -47,6 +48,7 @@ export const ASSET_ROUTES: Routes = [
                     privileges: [UserPrivilege.PRIVILEGE_WRITE_ASSETS],
                 },
                 canActivate: [PageAuthGuard],
+                canDeactivate: [ChartPanelCanDeactivateGuard],
             },
         ],
     },
diff --git 
a/ui/src/app/assets/components/asset-details/edit-asset/asset-details.component.html
 
b/ui/src/app/assets/components/asset-details/edit-asset/asset-details.component.html
index a31ba0520f..3edca7e368 100644
--- 
a/ui/src/app/assets/components/asset-details/edit-asset/asset-details.component.html
+++ 
b/ui/src/app/assets/components/asset-details/edit-asset/asset-details.component.html
@@ -49,7 +49,34 @@
                         </button>
                     </div>
                 </div>
-                <div fxFlex fxLayout="row" fxLayoutAlign="end center"></div>
+                <div fxFlex fxLayout="row" fxLayoutAlign="end center">
+                    <button
+                        mat-icon-button
+                        [matMenuTriggerFor]="optMenu"
+                        [attr.aria-label]="'Options' | translate"
+                        data-cy="options-asset"
+                    >
+                        <mat-icon>more_vert</mat-icon>
+                    </button>
+                    <mat-menu #optMenu="matMenu">
+                        <button
+                            mat-menu-item
+                            (click)="manageAsset()"
+                            data-cy="manage-asset-btn"
+                        >
+                            <mat-icon>settings</mat-icon>
+                            <span>{{ 'Manage' | translate }}</span>
+                        </button>
+                        <button
+                            mat-menu-item
+                            (click)="deleteAsset()"
+                            data-cy="delete-asset-btn"
+                        >
+                            <mat-icon>clear</mat-icon>
+                            <span>{{ 'Delete' | translate }}</span>
+                        </button>
+                    </mat-menu>
+                </div>
             </div>
             @if (asset) {
                 <div fxFlex="100" fxLayout="column" class="p-10">
diff --git 
a/ui/src/app/assets/components/asset-details/edit-asset/asset-details.component.ts
 
b/ui/src/app/assets/components/asset-details/edit-asset/asset-details.component.ts
index 6f7ea67db3..2a4276160e 100644
--- 
a/ui/src/app/assets/components/asset-details/edit-asset/asset-details.component.ts
+++ 
b/ui/src/app/assets/components/asset-details/edit-asset/asset-details.component.ts
@@ -18,10 +18,21 @@
 
 import { Component, inject } from '@angular/core';
 import {
+    ConfirmDialogAction,
+    ConfirmDialogComponent,
+    DialogService,
+    ObjectManageDialogComponent,
+    ObjectManageDialogResourceConfig,
+    ObjectManageDialogResult,
+    PanelType,
     SpAssetBrowserService,
     SpBasicViewComponent,
 } from '@streampipes/shared-ui';
-import { Router } from '@angular/router';
+import {
+    ActivatedRouteSnapshot,
+    Router,
+    RouterStateSnapshot,
+} from '@angular/router';
 import { BaseAssetDetailsDirective } from '../base-asset-details.directive';
 import { SpAssetSelectionPanelComponent } from 
'./asset-selection-panel/asset-selection-panel.component';
 import {
@@ -29,9 +40,24 @@ import {
     LayoutAlignDirective,
     LayoutDirective,
 } from '@ngbracket/ngx-layout/flex';
-import { MatButton } from '@angular/material/button';
+import { MatButton, MatIconButton } from '@angular/material/button';
 import { AssetDetailsBasicsComponent } from 
'./asset-details-panel/asset-details-basics/asset-details-basics.component';
-import { TranslatePipe } from '@ngx-translate/core';
+import { TranslatePipe, TranslateService } from '@ngx-translate/core';
+import { MatMenu, MatMenuItem, MatMenuTrigger } from '@angular/material/menu';
+import { MatIcon } from '@angular/material/icon';
+import {
+    PermissionsService,
+    SpAssetModel,
+} from '@streampipes/platform-services';
+import { MatDialog } from '@angular/material/dialog';
+import { firstValueFrom, from, Observable, of } from 'rxjs';
+import { map, switchMap } from 'rxjs/operators';
+import { SupportsUnsavedChangeDialog } from 
'../../../../chart-shared/models/dataview-dashboard.model';
+
+type ManageableAsset = SpAssetModel & {
+    name: string;
+    description: string;
+};
 
 @Component({
     selector: 'sp-asset-details',
@@ -43,19 +69,34 @@ import { TranslatePipe } from '@ngx-translate/core';
         LayoutAlignDirective,
         LayoutDirective,
         MatButton,
+        MatIconButton,
+        MatMenuTrigger,
+        MatMenu,
+        MatMenuItem,
+        MatIcon,
         AssetDetailsBasicsComponent,
         TranslatePipe,
     ],
 })
-export class SpAssetDetailsComponent extends BaseAssetDetailsDirective {
+export class SpAssetDetailsComponent
+    extends BaseAssetDetailsDirective
+    implements SupportsUnsavedChangeDialog
+{
     private router = inject(Router);
     private assetBrowserService = inject(SpAssetBrowserService);
+    private dialog = inject(MatDialog);
+    private dialogService = inject(DialogService);
+    private translateService = inject(TranslateService);
+    private permissionsService = inject(PermissionsService);
 
-    saveAsset() {
-        this.cleanupEmpty();
-        this.assetService.updateAsset(this.asset).subscribe(_res => {
-            this.assetBrowserService.refreshBrowserAssetData();
-            this.router.navigate(['assets']);
+    private pendingManageAssetResult?: 
ObjectManageDialogResult<ManageableAsset>;
+    private originalAsset: SpAssetModel;
+
+    async saveAsset() {
+        await this.saveAssetChanges();
+        this.assetBrowserService.refreshBrowserAssetData();
+        this.router.navigate(['assets'], {
+            state: { omitConfirm: true },
         });
     }
 
@@ -68,5 +109,169 @@ export class SpAssetDetailsComponent extends 
BaseAssetDetailsDirective {
         }
     }
 
-    onAssetAvailable() {}
+    manageAsset(): void {
+        const resource = this.makeManageableAsset(this.asset);
+        const resourceConfig: 
ObjectManageDialogResourceConfig<ManageableAsset> =
+            {
+                resourceLabel: 'Asset',
+                nameLabel: 'Asset name',
+                descriptionLabel: 'Asset description',
+                nameProperty: 'name',
+                showAssetLinking: false,
+            };
+        const dialogRef = this.dialogService.open(ObjectManageDialogComponent, 
{
+            panelType: PanelType.SLIDE_IN_PANEL,
+            title: this.translateService.instant('Manage'),
+            width: '50vw',
+            data: {
+                objectInstanceId: resource.elementId,
+                resource,
+                saveMode: 'deferred',
+                resourceConfig,
+                headerTitle:
+                    this.translateService.instant('Manage Asset ') +
+                    resource.name,
+            },
+        });
+        dialogRef.afterClosed().subscribe(result => {
+            if (result && typeof result !== 'boolean') {
+                this.pendingManageAssetResult = result;
+                Object.assign(
+                    this.asset,
+                    this.makeAssetResource(result.resource),
+                );
+            }
+        });
+    }
+
+    deleteAsset(): void {
+        const dialogRef = this.dialog.open(ConfirmDialogComponent, {
+            width: '500px',
+            data: {
+                title: this.translateService.instant(
+                    'Are you sure you want to delete this asset?',
+                ),
+                subtitle: this.translateService.instant(
+                    'This action cannot be reversed!',
+                ),
+                cancelTitle: this.translateService.instant('Cancel'),
+                confirmTitle: this.translateService.instant('Delete Asset'),
+            },
+        });
+        dialogRef.afterClosed().subscribe(result => {
+            if (result === 'confirm') {
+                this.assetService
+                    .deleteAsset(this.asset.elementId)
+                    .subscribe(() => {
+                        this.assetBrowserService.refreshBrowserAssetData();
+                        this.router.navigate(['assets'], {
+                            state: { omitConfirm: true },
+                        });
+                    });
+            }
+        });
+    }
+
+    confirmLeaveDialog(
+        _route: ActivatedRouteSnapshot,
+        _state: RouterStateSnapshot,
+    ): Observable<boolean> {
+        if (this.setShouldShowConfirm()) {
+            const dialogRef = this.dialog.open(ConfirmDialogComponent, {
+                width: '500px',
+                data: {
+                    title: this.translateService.instant('Save changes?'),
+                    subtitle: this.translateService.instant(
+                        'Update all changes to asset or discard current 
changes.',
+                    ),
+                    neutralTitle: this.translateService.instant('Keep 
editing'),
+                    cancelTitle:
+                        this.translateService.instant('Discard changes'),
+                    confirmTitle: this.translateService.instant('Update'),
+                },
+            });
+            return dialogRef.afterClosed().pipe(
+                switchMap((dialogResult: ConfirmDialogAction | undefined) => {
+                    if (dialogResult === 'confirm') {
+                        return from(this.saveAssetChanges()).pipe(
+                            map(() => true),
+                        );
+                    }
+
+                    if (dialogResult === 'cancel') {
+                        return of(true);
+                    }
+
+                    return of(false);
+                }),
+            );
+        } else {
+            return of(true);
+        }
+    }
+
+    setShouldShowConfirm(): boolean {
+        return (
+            this.pendingManageAssetResult !== undefined ||
+            this.hasAssetChanged()
+        );
+    }
+
+    onAssetAvailable() {
+        this.originalAsset = this.cloneAsset(this.asset);
+    }
+
+    private makeManageableAsset(asset: SpAssetModel): ManageableAsset {
+        return {
+            ...asset,
+            name: asset.assetName,
+            description: asset.assetDescription,
+        };
+    }
+
+    private makeAssetResource(resource: ManageableAsset): SpAssetModel {
+        const { name, description, ...asset } = resource;
+        return {
+            ...asset,
+            assetName: name,
+            assetDescription: description,
+        };
+    }
+
+    private async saveAssetChanges(): Promise<void> {
+        this.cleanupEmpty();
+        await firstValueFrom(this.assetService.updateAsset(this.asset));
+        await this.savePendingManageAssetChanges();
+        this.originalAsset = this.cloneAsset(this.asset);
+    }
+
+    private async savePendingManageAssetChanges(): Promise<void> {
+        const result = this.pendingManageAssetResult;
+        if (!result) {
+            return;
+        }
+
+        if (result.permission) {
+            await firstValueFrom(
+                this.permissionsService.updatePermission(result.permission),
+            );
+        }
+
+        this.pendingManageAssetResult = undefined;
+    }
+
+    private hasAssetChanged(): boolean {
+        if (!this.originalAsset || !this.asset) {
+            return false;
+        }
+
+        return (
+            JSON.stringify(this.originalAsset) !==
+            JSON.stringify(this.cloneAsset(this.asset))
+        );
+    }
+
+    private cloneAsset(asset: SpAssetModel): SpAssetModel {
+        return JSON.parse(JSON.stringify(asset));
+    }
 }
diff --git 
a/ui/src/app/assets/components/asset-overview/asset-overview.component.html 
b/ui/src/app/assets/components/asset-overview/asset-overview.component.html
index 80a6478999..9ab3b13349 100644
--- a/ui/src/app/assets/components/asset-overview/asset-overview.component.html
+++ b/ui/src/app/assets/components/asset-overview/asset-overview.component.html
@@ -117,11 +117,11 @@
                         }
                         <button
                             mat-menu-item
-                            data-cy="open-manage-permissions"
-                            (click)="openPermissionsDialog(element)"
+                            [attr.data-cy]="'open-manage-' + element.assetName"
+                            (click)="showManageDialog(element)"
                         >
-                            <mat-icon>share</mat-icon>
-                            <span>{{ 'Manage permissions' | translate }}</span>
+                            <mat-icon>settings</mat-icon>
+                            <span>{{ 'Manage' | translate }}</span>
                         </button>
 
                         @if (hasWritePrivilege) {
diff --git 
a/ui/src/app/assets/components/asset-overview/asset-overview.component.ts 
b/ui/src/app/assets/components/asset-overview/asset-overview.component.ts
index d270b80e3b..0dab1654a3 100644
--- a/ui/src/app/assets/components/asset-overview/asset-overview.component.ts
+++ b/ui/src/app/assets/components/asset-overview/asset-overview.component.ts
@@ -34,7 +34,8 @@ import {
     ConfirmDialogComponent,
     CurrentUserService,
     DialogService,
-    ObjectPermissionDialogComponent,
+    ObjectManageDialogComponent,
+    ObjectManageDialogResourceConfig,
     PanelType,
     SpAssetBrowserService,
     SpBasicHeaderTitleComponent,
@@ -62,6 +63,11 @@ import { MatTooltip } from '@angular/material/tooltip';
 import { MatMenuItem } from '@angular/material/menu';
 import { MatIcon } from '@angular/material/icon';
 
+type ManageableAsset = SpAssetModel & {
+    name: string;
+    description: string;
+};
+
 @Component({
     selector: 'sp-asset-overview',
     templateUrl: './asset-overview.component.html',
@@ -240,18 +246,58 @@ export class SpAssetOverviewComponent implements OnInit {
         });
     }
 
-    openPermissionsDialog(asset: AssetSummaryDto) {
-        this.dialogService.open(ObjectPermissionDialogComponent, {
-            panelType: PanelType.SLIDE_IN_PANEL,
-            title: this.translateService.instant('Manage permissions'),
-            width: '70vw',
-            data: {
-                objectInstanceId: asset.elementId,
-                headerTitle:
-                    this.translateService.instant(
-                        'Manage permissions for asset ',
-                    ) + asset.assetName,
-            },
+    showManageDialog(assetSummary: AssetSummaryDto) {
+        this.assetService.getAsset(assetSummary.elementId).subscribe(asset => {
+            const resource: ManageableAsset = {
+                ...asset,
+                name: asset.assetName,
+                description: asset.assetDescription,
+            };
+            const resourceConfig: 
ObjectManageDialogResourceConfig<ManageableAsset> =
+                {
+                    resourceLabel: 'Asset',
+                    nameLabel: 'Asset name',
+                    descriptionLabel: 'Asset description',
+                    nameProperty: 'name',
+                    showResourceFields: this.hasWritePrivilege,
+                    showAssetLinking: false,
+                    saveResource: this.hasWritePrivilege
+                        ? resource => {
+                              const { name, description, ...assetToSave } =
+                                  resource;
+                              return this.assetService.updateAsset({
+                                  ...assetToSave,
+                                  assetName: name,
+                                  assetDescription: description,
+                              });
+                          }
+                        : undefined,
+                };
+
+            const dialogRef = this.dialogService.open(
+                ObjectManageDialogComponent,
+                {
+                    panelType: PanelType.SLIDE_IN_PANEL,
+                    title: this.translateService.instant('Manage'),
+                    width: '50vw',
+                    data: {
+                        objectInstanceId: resource.elementId,
+                        resource,
+                        saveMode: 'immediate',
+                        resourceConfig,
+                        headerTitle:
+                            this.translateService.instant('Manage Asset ') +
+                            resource.assetName,
+                    },
+                },
+            );
+
+            dialogRef.afterClosed().subscribe(refresh => {
+                if (refresh) {
+                    this.loadAssets();
+                    this.assetBrowserService.refreshBrowserAssetData();
+                }
+            });
         });
     }
 }

Reply via email to