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