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();
+ }
+ });
});
}
}