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

JHoelli pushed a commit to branch 
1357-harmonize-resource-creation-workflow-for-pipelines
in repository https://gitbox.apache.org/repos/asf/streampipes.git

commit a6c9c877bee29f6930fd7bf14eab1efacb413ebb
Author: Jacqueline Höllig <[email protected]>
AuthorDate: Fri Jul 10 08:31:00 2026 +0000

    Edit works
---
 .../pipeline-assembly-options.component.html       |  28 ++++
 .../pipeline-assembly-options.component.ts         |  15 +++
 .../pipeline-assembly.component.html               |   3 +
 .../pipeline-assembly.component.ts                 | 149 +++++++++++++++++++--
 4 files changed, 186 insertions(+), 9 deletions(-)

diff --git 
a/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly-options/pipeline-assembly-options.component.html
 
b/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly-options/pipeline-assembly-options.component.html
index 80cfb53eca..d3fac0f1ed 100644
--- 
a/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly-options/pipeline-assembly-options.component.html
+++ 
b/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly-options/pipeline-assembly-options.component.html
@@ -96,6 +96,34 @@
         #assemblyOptionsPipelineCacheComponent
     >
     </sp-pipeline-assembly-options-pipeline-cache>
+    @if (editMode) {
+        <button
+            mat-icon-button
+            [matMenuTriggerFor]="pipelineOptionsMenu"
+            [attr.aria-label]="'Options' | translate"
+            data-cy="options-pipeline"
+        >
+            <mat-icon>more_vert</mat-icon>
+        </button>
+        <mat-menu #pipelineOptionsMenu="matMenu">
+            <button
+                mat-menu-item
+                (click)="managePipelineEmitter.emit()"
+                data-cy="manage-pipeline-btn"
+            >
+                <mat-icon>settings</mat-icon>
+                <span>{{ 'Manage pipeline' | translate }}</span>
+            </button>
+            <button
+                mat-menu-item
+                (click)="deletePipelineEmitter.emit()"
+                data-cy="delete-pipeline-btn"
+            >
+                <mat-icon>clear</mat-icon>
+                <span>{{ 'Delete pipeline' | translate }}</span>
+            </button>
+        </mat-menu>
+    }
     <span fxFlex></span>
     <button
         mat-icon-button
diff --git 
a/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly-options/pipeline-assembly-options.component.ts
 
b/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly-options/pipeline-assembly-options.component.ts
index 19fda82087..fda424a9f8 100644
--- 
a/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly-options/pipeline-assembly-options.component.ts
+++ 
b/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly-options/pipeline-assembly-options.component.ts
@@ -53,6 +53,8 @@ import {
 } from '@ngbracket/ngx-layout/flex';
 import { MatButton, MatIconButton } from '@angular/material/button';
 import { MatCheckbox } from '@angular/material/checkbox';
+import { MatIcon } from '@angular/material/icon';
+import { MatMenu, MatMenuItem, MatMenuTrigger } from '@angular/material/menu';
 import { MatTooltip } from '@angular/material/tooltip';
 import { TranslatePipe } from '@ngx-translate/core';
 
@@ -67,8 +69,12 @@ import { TranslatePipe } from '@ngx-translate/core';
         LayoutAlignDirective,
         MatButton,
         MatCheckbox,
+        MatIcon,
         MatTooltip,
         MatIconButton,
+        MatMenuTrigger,
+        MatMenu,
+        MatMenuItem,
         PipelineAssemblyOptionsPipelineCacheComponent,
         TranslatePipe,
     ],
@@ -96,6 +102,9 @@ export class PipelineAssemblyOptionsComponent {
     @Input()
     previewModeActive: boolean;
 
+    @Input()
+    editMode = false;
+
     @Output()
     savePipelineEmitter: EventEmitter<boolean> = new EventEmitter<boolean>();
 
@@ -109,6 +118,12 @@ export class PipelineAssemblyOptionsComponent {
     displayPipelineTemplateEmitter: EventEmitter<Pipeline> =
         new EventEmitter<Pipeline>();
 
+    @Output()
+    managePipelineEmitter: EventEmitter<void> = new EventEmitter<void>();
+
+    @Output()
+    deletePipelineEmitter: EventEmitter<void> = new EventEmitter<void>();
+
     @ViewChild('assemblyOptionsPipelineCacheComponent')
     assemblyOptionsCacheComponent: 
PipelineAssemblyOptionsPipelineCacheComponent;
 
diff --git 
a/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly.component.html
 
b/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly.component.html
index fcaf7c04e9..5d423ce280 100644
--- 
a/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly.component.html
+++ 
b/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly.component.html
@@ -26,9 +26,12 @@
             [allElements]="allElements"
             [jsplumbBridge]="jsplumbBridge"
             [previewModeActive]="previewModeActive"
+            [editMode]="!!originalPipeline"
             (clearAssemblyEmitter)="clearAssembly()"
             (togglePreviewEmitter)="togglePreview()"
             (savePipelineEmitter)="submit($event)"
+            (managePipelineEmitter)="managePipeline()"
+            (deletePipelineEmitter)="deletePipeline()"
             (displayPipelineTemplateEmitter)="displayPipelineTemplate($event)"
         >
         </sp-pipeline-assembly-options>
diff --git 
a/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly.component.ts 
b/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly.component.ts
index bf233df637..d8b2e3e8b9 100644
--- 
a/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly.component.ts
+++ 
b/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly.component.ts
@@ -19,6 +19,7 @@
 import {
     AfterViewInit,
     Component,
+    EventEmitter,
     Input,
     OnDestroy,
     ViewChild,
@@ -33,21 +34,25 @@ import {
 } from '../../model/editor.model';
 import { ObjectProvider } from '../../services/object-provider.service';
 import {
+    AssetSaveService,
     DialogService,
     KeyboardShortcutService,
     ObjectManageDialogComponent,
     ObjectManageDialogResourceConfig,
+    ObjectManageDialogResult,
     PanelType,
     ShortcutRegistration,
     SpBasicViewComponent,
 } from '@streampipes/shared-ui';
 import { EditorService } from '../../services/editor.service';
 import {
+    LinkageData,
     Message,
     Pipeline,
     PipelineCanvasMetadata,
     PipelineCanvasMetadataService,
     PipelineOperationStatus,
+    PermissionsService,
     PipelineService,
 } from '@streampipes/platform-services';
 import { JsplumbFactoryService } from '../../services/jsplumb-factory.service';
@@ -58,6 +63,7 @@ import { PipelineAssemblyOptionsComponent } from 
'./pipeline-assembly-options/pi
 import { JsplumbService } from '../../services/jsplumb.service';
 import { TranslateService } from '@ngx-translate/core';
 import { FlexDirective } from '@ngbracket/ngx-layout/flex';
+import { PipelineOperationsService } from 
'../../../pipelines/services/pipeline-operations.service';
 
 @Component({
     selector: 'sp-pipeline-assembly',
@@ -83,6 +89,9 @@ export class PipelineAssemblyComponent implements 
AfterViewInit, OnDestroy {
     private shortcutService = inject(KeyboardShortcutService);
     private pipelineService = inject(PipelineService);
     private pipelineCanvasService = inject(PipelineCanvasMetadataService);
+    private permissionsService = inject(PermissionsService);
+    private assetSaveService = inject(AssetSaveService);
+    private pipelineOperationsService = inject(PipelineOperationsService);
 
     @Input()
     rawPipelineModel: PipelineElementConfig[];
@@ -101,6 +110,7 @@ export class PipelineAssemblyComponent implements 
AfterViewInit, OnDestroy {
 
     previewModeActive = false;
     readonly: boolean;
+    private pendingManagePipelineResult?: ObjectManageDialogResult<Pipeline>;
 
     jsplumbBridge: JsplumbBridge;
     private shortcutReg: ShortcutRegistration;
@@ -154,6 +164,11 @@ export class PipelineAssemblyComponent implements 
AfterViewInit, OnDestroy {
      * Sends the pipeline to the server
      */
     submit(startPipelineAfterStorage = true) {
+        if (this.originalPipeline) {
+            void this.savePipelineChanges(startPipelineAfterStorage);
+            return;
+        }
+
         const pipelineModel = this.rawPipelineModel;
         const pipeline = this.objectProvider.makePipeline(pipelineModel);
         this.pipelinePositioningService.collectPipelineElementPositions(
@@ -164,15 +179,6 @@ export class PipelineAssemblyComponent implements 
AfterViewInit, OnDestroy {
             pipelineModel,
             this.readonly,
         );
-        if (this.originalPipeline) {
-            pipeline._id = this.originalPipeline._id;
-            pipeline.name = this.originalPipeline.name;
-            pipeline.description = this.originalPipeline.description;
-            pipeline.running = this.originalPipeline.running;
-            pipeline.createdAt = this.originalPipeline.createdAt;
-            pipeline.createdByUser = this.originalPipeline.createdByUser;
-        }
-
         const resourceConfig: ObjectManageDialogResourceConfig<Pipeline> = {
             resourceLabel: 'Pipeline',
             nameLabel: 'Pipeline name',
@@ -206,6 +212,86 @@ export class PipelineAssemblyComponent implements 
AfterViewInit, OnDestroy {
         });
     }
 
+    managePipeline(): void {
+        if (!this.originalPipeline) {
+            return;
+        }
+
+        const resource: Pipeline = { ...this.originalPipeline };
+        const resourceConfig: ObjectManageDialogResourceConfig<Pipeline> = {
+            resourceLabel: 'Pipeline',
+            nameLabel: 'Pipeline name',
+            descriptionLabel: 'Description',
+            nameProperty: 'name',
+            assetLinkType: 'pipeline',
+            assetLinkCheckboxLabel:
+                'Add the current pipeline to an existing asset',
+        };
+
+        const dialogRef = this.dialogService.open(ObjectManageDialogComponent, 
{
+            panelType: PanelType.SLIDE_IN_PANEL,
+            title: this.translateService.instant('Manage'),
+            width: '50vw',
+            data: {
+                objectInstanceId: resource._id,
+                resource,
+                saveMode: 'deferred',
+                resourceConfig,
+                headerTitle:
+                    this.translateService.instant('Manage Pipeline ') +
+                    resource.name,
+            },
+        });
+
+        dialogRef.afterClosed().subscribe(result => {
+            if (result && typeof result !== 'boolean') {
+                this.pendingManagePipelineResult = result;
+                Object.assign(this.originalPipeline, result.resource);
+            }
+        });
+    }
+
+    deletePipeline(): void {
+        if (!this.originalPipeline) {
+            return;
+        }
+
+        this.pipelineOperationsService.showDeleteDialog(
+            this.originalPipeline._id,
+            this.originalPipeline.name,
+            this.originalPipeline.running,
+            new EventEmitter<boolean>(),
+            () => this.router.navigate(['pipelines']),
+        );
+    }
+
+    private async savePipelineChanges(
+        startPipelineAfterStorage: boolean,
+    ): Promise<void> {
+        const pipelineModel = this.rawPipelineModel;
+        const pipeline = this.objectProvider.makePipeline(pipelineModel);
+        this.pipelinePositioningService.collectPipelineElementPositions(
+            this.pipelineCanvasMetadata,
+            pipelineModel,
+        );
+        pipeline.valid = this.pipelineValidationService.isValidPipeline(
+            pipelineModel,
+            this.readonly,
+        );
+        pipeline._id = this.originalPipeline._id;
+        pipeline.name = this.originalPipeline.name;
+        pipeline.description = this.originalPipeline.description;
+        pipeline.running = this.originalPipeline.running;
+        pipeline.createdAt = this.originalPipeline.createdAt;
+        pipeline.createdByUser = this.originalPipeline.createdByUser;
+
+        await this.savePipelineResource(pipeline, startPipelineAfterStorage);
+        await this.savePendingManagePipelineChanges();
+        this.editorService.makePipelineAssemblyEmpty(true);
+        this.editorService.removePipelineFromCache().subscribe();
+        this.router.navigate(['pipelines']);
+    }
+
     private async savePipelineResource(
         pipeline: Pipeline,
         startPipelineAfterStorage: boolean,
@@ -279,6 +365,51 @@ export class PipelineAssemblyComponent implements 
AfterViewInit, OnDestroy {
         }
     }
 
+    private async savePendingManagePipelineChanges(): Promise<void> {
+        const result = this.pendingManagePipelineResult;
+        if (!result) {
+            return;
+        }
+
+        if (result.permission) {
+            await firstValueFrom(
+                this.permissionsService.updatePermission(result.permission),
+            );
+        }
+
+        if (this.shouldSaveManagePipelineAssets(result)) {
+            await this.assetSaveService.saveSelectedAssets(
+                result.selectedAssets,
+                this.createPipelineLinkageData(result.resource),
+                result.deselectedAssets,
+                result.originalAssets,
+            );
+        }
+
+        this.pendingManagePipelineResult = undefined;
+    }
+
+    private shouldSaveManagePipelineAssets(
+        result: ObjectManageDialogResult<Pipeline>,
+    ): boolean {
+        return (
+            result.addToAssets &&
+            (result.selectedAssets.length > 0 ||
+                result.deselectedAssets.length > 0 ||
+                result.originalAssets.length > 0)
+        );
+    }
+
+    private createPipelineLinkageData(pipeline: Pipeline): LinkageData[] {
+        return [
+            {
+                type: 'pipeline',
+                id: pipeline._id ?? '',
+                name: pipeline.name ?? '',
+            },
+        ];
+    }
+
     togglePreview(): void {
         this.previewModeActive = !this.previewModeActive;
         this.drawingAreaComponent.togglePipelineElementLivePreview();

Reply via email to