This is an automated email from the ASF dual-hosted git repository.
zehnder 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 fe1ef7b4d6 Tests for PR #3829 and #3817 (#3835)
fe1ef7b4d6 is described below
commit fe1ef7b4d63ae7ba800e1448b625c2a98592bb33
Author: Jacqueline Höllig <[email protected]>
AuthorDate: Wed Oct 15 12:25:26 2025 +0200
Tests for PR #3829 and #3817 (#3835)
Co-authored-by: Philipp Zehnder <[email protected]>
Co-authored-by: Dominik Riemer <[email protected]>
---
ui/cypress/support/utils/GeneralUtils.ts | 1 +
ui/cypress/support/utils/asset/AssetBtns.ts | 4 +
ui/cypress/support/utils/asset/AssetUtils.ts | 42 ++-
ui/cypress/support/utils/connect/ConnectUtils.ts | 53 ++++
ui/cypress/support/utils/pipeline/PipelineUtils.ts | 55 +++-
.../tests/connect/adapterWithAssets.smoke.spec.ts | 106 +++++++
.../tests/pipeline/pipelineAsset.smoke.spec.ts | 97 ++++++
.../asset-link-configuration.component.html | 72 +++++
.../asset-link-configuration.component.scss} | 5 +
.../asset-link-configuration.component.ts} | 17 +-
.../lib/services/asset-configuration.service.ts | 337 +++++++++++++++++++++
.../shared-ui/src/lib/shared-ui.module.ts | 3 +
.../streampipes/shared-ui/src/public-api.ts | 2 +
.../create-asset-dialog.component.html | 2 +-
.../adapter-asset-configuration.component.html | 64 ----
.../start-adapter-configuration.component.html | 6 +-
.../start-adapter-configuration.component.ts | 1 -
ui/src/app/connect/connect.module.ts | 2 -
.../adapter-started-dialog.component.ts | 31 +-
.../save-pipeline-settings.component.html | 19 ++
.../save-pipeline-settings.component.ts | 43 ++-
.../save-pipeline/save-pipeline.component.html | 3 +
.../save-pipeline/save-pipeline.component.ts | 96 +++++-
ui/src/app/editor/editor.module.ts | 1 +
24 files changed, 941 insertions(+), 121 deletions(-)
diff --git a/ui/cypress/support/utils/GeneralUtils.ts
b/ui/cypress/support/utils/GeneralUtils.ts
index 893240022d..1012f4be22 100644
--- a/ui/cypress/support/utils/GeneralUtils.ts
+++ b/ui/cypress/support/utils/GeneralUtils.ts
@@ -22,6 +22,7 @@ export class GeneralUtils {
}
public static openMenuForRow(rowText: string) {
+ cy.log('ROW TEXT', rowText);
cy.contains('[role="row"], tr, mat-row', rowText) // be flexible on
row element
.scrollIntoView()
.within(() => {
diff --git a/ui/cypress/support/utils/asset/AssetBtns.ts
b/ui/cypress/support/utils/asset/AssetBtns.ts
index 8d574e0e7e..d958a3fe14 100644
--- a/ui/cypress/support/utils/asset/AssetBtns.ts
+++ b/ui/cypress/support/utils/asset/AssetBtns.ts
@@ -29,6 +29,10 @@ export class AssetBtns {
return cy.dataCy('save-asset', { timeout: 10000 });
}
+ public static createAssetPanelBtn() {
+ return cy.dataCy('create-asset-panel', { timeout: 10000 });
+ }
+
public static editAssetBtn(assetName: string) {
return cy.dataCy('edit-asset-' + assetName, { timeout: 10000 });
}
diff --git a/ui/cypress/support/utils/asset/AssetUtils.ts
b/ui/cypress/support/utils/asset/AssetUtils.ts
index 3b10320edf..bbed4c184a 100644
--- a/ui/cypress/support/utils/asset/AssetUtils.ts
+++ b/ui/cypress/support/utils/asset/AssetUtils.ts
@@ -23,6 +23,7 @@ import { GeneralUtils } from '../GeneralUtils';
export class AssetUtils {
public static goToAssets() {
cy.visit('#/assets/overview');
+ cy.dataCy('create-new-asset-button').should('be.visible');
}
public static goBackToOverview() {
@@ -33,7 +34,14 @@ export class AssetUtils {
AssetBtns.createAssetBtn().click();
AssetBtns.assetNameInput().clear();
AssetBtns.assetNameInput().type(assetName);
+ AssetBtns.createAssetPanelBtn().click();
+ }
+
+ public static addAndSaveAsset(assetName: string) {
+ AssetUtils.addNewAsset(assetName);
+
AssetBtns.saveAssetBtn().click();
+ AssetBtns.createAssetBtn().should('be.visible');
}
public static openManageAssetLinks() {
@@ -59,9 +67,41 @@ export class AssetUtils {
.should('have.length', amount);
}
+ public static checkAmountOfAssetsGreaterThan(amount: number) {
+ cy.dataCy('assets-table', { timeout: 10000 }).should(
+ 'have.length.greaterThan',
+ amount,
+ );
+ }
+
+ public static checkAmountOfLinkedResourcesByAssetName(
+ assetName: string,
+ amount: number,
+ ) {
+ AssetUtils.goToAssets();
+ cy.wait(400);
+ AssetUtils.editAsset(assetName);
+ cy.wait(400);
+ AssetBtns.assetLinksTab().click();
+ AssetUtils.checkAmountOfLinkedResources(amount);
+ }
+
+ public static checkResourceNamingByAssetName(
+ assetName: string,
+ name: string,
+ ) {
+ AssetUtils.goToAssets();
+ AssetUtils.editAsset(assetName);
+ AssetBtns.assetLinksTab().click();
+ cy.dataCy('linked-resources-list').children().contains(name);
+ //.should('have.length', amount);
+ }
+
public static editAsset(assetName: string) {
GeneralUtils.openMenuForRow(assetName);
- AssetBtns.editAssetBtn(assetName).click();
+ cy.contains('button', 'Edit').click({ force: true });
+ //This is the old version and there in case above does not work for
all tests
+ //AssetBtns.editAssetBtn(assetName).click({ force: true });
}
public static addAssetWithOneAdapter(assetName: string) {
diff --git a/ui/cypress/support/utils/connect/ConnectUtils.ts
b/ui/cypress/support/utils/connect/ConnectUtils.ts
index a9c682a1eb..fcb4d0a779 100644
--- a/ui/cypress/support/utils/connect/ConnectUtils.ts
+++ b/ui/cypress/support/utils/connect/ConnectUtils.ts
@@ -84,6 +84,21 @@ export class ConnectUtils {
ConnectEventSchemaUtils.finishEventSchemaConfiguration();
}
+ public static addAdapterWithLinkedAssets(
+ adapterConfiguration: AdapterInput,
+ assetNameList,
+ ) {
+ ConnectUtils.addAdapter(adapterConfiguration);
+
+ ConnectUtils.startAdapter(
+ adapterConfiguration,
+ false,
+ false,
+ true,
+ assetNameList,
+ );
+ }
+
private static configureDimensionProperties(
adapterConfiguration: AdapterInput,
) {
@@ -98,6 +113,10 @@ export class ConnectUtils {
}
}
+ public static renameAdapter(newName: string) {
+ cy.dataCy('sp-adapter-name').clear().type(newName);
+ cy.dataCy('sp-adapter-name').should('have.value', newName);
+ }
public static addMachineDataSimulator(
name: string,
persist: boolean = false,
@@ -128,6 +147,7 @@ export class ConnectUtils {
public static goToConnect() {
cy.visit('#/connect');
+ cy.dataCy('connect-create-new-adapter-button').should('be.visible');
}
public static goToNewAdapterPage() {
@@ -181,6 +201,8 @@ export class ConnectUtils {
adapterInput: AdapterInput,
noLiveDataView = false,
adapterStartFails = false,
+ addToAsset = false,
+ assetNameList = [],
) {
// Set adapter name
cy.dataCy('sp-adapter-name').type(adapterInput.adapterName);
@@ -204,6 +226,12 @@ export class ConnectUtils {
ConnectBtns.startAdapterNowCheckbox().click();
}
+ //add the Adapter to an Asset
+
+ if (addToAsset) {
+ this.addToAsset(assetNameList);
+ }
+
ConnectBtns.adapterSettingsStartAdapter().click();
if (adapterStartFails) {
@@ -225,6 +253,31 @@ export class ConnectUtils {
this.closeAdapterPreview();
}
+ public static addToAsset(assetNameList = []) {
+ cy.dataCy('show-asset-checkbox').click();
+ cy.get('mat-tree.asset-tree', { timeout: 10000 }).should('exist');
+
+ assetNameList.forEach(assetName => {
+ cy.get('mat-tree.asset-tree')
+ .find('.mat-tree-node')
+ .contains(assetName)
+ .click();
+ });
+ }
+
+ public static editAsset(assetNameList = []) {
+ //cy.dataCy('show-asset-checkbox').click();
+ cy.get('mat-tree.asset-tree', { timeout: 10000 }).should('exist');
+
+ assetNameList.forEach(assetName => {
+ console.log(assetName);
+ cy.get('mat-tree.asset-tree')
+ .find('.mat-tree-node')
+ .contains(assetName)
+ .click();
+ });
+ }
+
// Close adapter preview
public static closeAdapterPreview() {
cy.get('button').contains('Close').parent().click();
diff --git a/ui/cypress/support/utils/pipeline/PipelineUtils.ts
b/ui/cypress/support/utils/pipeline/PipelineUtils.ts
index 5af6c42d7a..ebb619c216 100644
--- a/ui/cypress/support/utils/pipeline/PipelineUtils.ts
+++ b/ui/cypress/support/utils/pipeline/PipelineUtils.ts
@@ -37,6 +37,22 @@ export class PipelineUtils {
PipelineUtils.startPipeline(pipelineInput);
}
+ public static addPipelineWithAssetLinks(
+ pipelineInput: PipelineInput,
+ assetNameList: String[],
+ ) {
+ PipelineUtils.goToPipelineEditor();
+
+ PipelineUtils.selectDataStream(pipelineInput);
+
+ PipelineUtils.configurePipeline(pipelineInput);
+
+ PipelineUtils.startPipelineWithAssetLinkage(
+ pipelineInput,
+ assetNameList,
+ );
+ }
+
/**
* This method adds a sample adapter and pipeline
*/
@@ -146,6 +162,39 @@ export class PipelineUtils {
PipelineUtils.finalizePipelineStart();
}
+ public static startPipelineWithAssetLinkage(
+ pipelineInput?: PipelineInput,
+ assetNameList?: String[],
+ ) {
+ // Save and start pipeline
+ cy.dataCy('sp-editor-save-pipeline').click();
+ if (pipelineInput) {
+ cy.dataCy('sp-editor-pipeline-name').type(
+ pipelineInput.pipelineName,
+ );
+ }
+ PipelineUtils.finalizePipelineStart(assetNameList);
+ }
+
+ private static addToAsset(assetNameList) {
+ cy.dataCy('sp-show-pipeline-asset-checkbox')
+ .find('input[type="checkbox"]')
+ .then($checkbox => {
+ if (!$checkbox.prop('checked')) {
+ cy.wrap($checkbox).click();
+ }
+ });
+
+ cy.get('mat-tree.asset-tree', { timeout: 10000 }).should('exist');
+ assetNameList.forEach(assetName => {
+ console.log(assetName);
+ cy.get('mat-tree.asset-tree')
+ .find('.mat-tree-node')
+ .contains(assetName)
+ .click();
+ });
+ }
+
public static clonePipeline(newPipelineName: string) {
cy.dataCy('pipeline-update-mode-clone').children().click();
cy.dataCy('sp-editor-pipeline-name').type(newPipelineName);
@@ -156,9 +205,13 @@ export class PipelineUtils {
cy.dataCy('sp-editor-pipeline-name').type(newPipelineName);
}
- public static finalizePipelineStart() {
+ public static finalizePipelineStart(assetNameList?: String[]) {
cy.dataCy('sp-editor-checkbox-navigate-to-overview').children().click();
+ if (assetNameList) {
+ PipelineUtils.addToAsset(assetNameList);
+ }
cy.dataCy('sp-editor-apply').click();
+
cy.dataCy('sp-pipeline-started-success', { timeout: 15000 }).should(
'be.visible',
);
diff --git a/ui/cypress/tests/connect/adapterWithAssets.smoke.spec.ts
b/ui/cypress/tests/connect/adapterWithAssets.smoke.spec.ts
new file mode 100644
index 0000000000..c69d7d71b8
--- /dev/null
+++ b/ui/cypress/tests/connect/adapterWithAssets.smoke.spec.ts
@@ -0,0 +1,106 @@
+/*
+ * 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 { ConnectUtils } from '../../support/utils/connect/ConnectUtils';
+import { AdapterBuilder } from '../../support/builder/AdapterBuilder';
+import { AssetUtils } from '../../support/utils/asset/AssetUtils';
+import { ConnectBtns } from '../../support/utils/connect/ConnectBtns';
+import { AssetBtns } from '../../support/utils/asset/AssetBtns';
+
+describe('Creates a new adapter with a linked asset', () => {
+ const assetName1 = 'TestAsset1';
+ const assetName2 = 'TestAsset2';
+ const assetName3 = 'TestAsset3';
+ const adapterConfiguration =
AdapterBuilder.create('Machine_Data_Simulator')
+ .setName('Machine Data Simulator Test')
+ .addInput('input', 'wait-time-ms', '1000')
+ .setStartAdapter(false)
+ .build();
+
+ beforeEach('Setup Test', () => {
+ cy.initStreamPipesTest();
+ AssetUtils.goToAssets();
+ AssetUtils.addAndSaveAsset(assetName3);
+ AssetUtils.addAndSaveAsset(assetName2);
+ AssetUtils.addAndSaveAsset(assetName1);
+ });
+
+ it('Add Assets during Adapter generation', () => {
+ // Create
+ ConnectUtils.goToConnect();
+ ConnectUtils.addAdapterWithLinkedAssets(adapterConfiguration, [
+ assetName1,
+ ]);
+
+ //Go Back to Asset
+ AssetUtils.goToAssets();
+ AssetUtils.checkAmountOfAssetsGreaterThan(0);
+
+ AssetUtils.editAsset(assetName1);
+ AssetBtns.assetLinksTab().click();
+
+ //Check if Link is there
+ AssetUtils.checkAmountOfLinkedResources(2);
+ });
+
+ it('Edit Assets during Adapter editing', () => {
+ // Add the first two Asssets by default
+ ConnectUtils.addAdapterWithLinkedAssets(adapterConfiguration, [
+ assetName1,
+ assetName2,
+ ]);
+
+ //Check if Added Correctly
+ AssetUtils.checkAmountOfLinkedResourcesByAssetName(assetName1, 2);
+ AssetUtils.checkAmountOfLinkedResourcesByAssetName(assetName2, 2);
+
+ //Edit
+ ConnectUtils.goToConnect();
+ ConnectBtns.openActionsMenu('Machine Data Simulator Test');
+ ConnectBtns.editAdapter().should('not.be.disabled');
+ ConnectBtns.editAdapter().click();
+
+ // Go over the first two steps
+ ConnectBtns.nextBtn().click();
+ ConnectUtils.finishEventSchemaConfiguration();
+
+ // Rename
+ ConnectUtils.renameAdapter('Changed');
+
+ // Deselect Asset 2
+ ConnectUtils.editAsset([assetName1]);
+
+ // Select Asset 3 //TODO Click on Asset
+ ConnectUtils.editAsset([assetName3]);
+
+ ConnectBtns.storeEditAdapter().click();
+
+ cy.dataCy('sp-connect-adapter-success-added', {
+ timeout: 60000,
+ }).should('be.visible');
+
+ ConnectUtils.closeAdapterPreview();
+
+ // Test Number of Asset Links
+ AssetUtils.checkAmountOfLinkedResourcesByAssetName(assetName2, 2);
+ AssetUtils.checkAmountOfLinkedResourcesByAssetName(assetName3, 2);
+
+ // Test Renaming
+ AssetUtils.checkResourceNamingByAssetName(assetName2, 'Changed');
+ });
+});
diff --git a/ui/cypress/tests/pipeline/pipelineAsset.smoke.spec.ts
b/ui/cypress/tests/pipeline/pipelineAsset.smoke.spec.ts
new file mode 100644
index 0000000000..6638c182e2
--- /dev/null
+++ b/ui/cypress/tests/pipeline/pipelineAsset.smoke.spec.ts
@@ -0,0 +1,97 @@
+/*
+ * 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 { PipelineUtils } from '../../support/utils/pipeline/PipelineUtils';
+import { AssetUtils } from '../../support/utils/asset/AssetUtils';
+import { ConnectUtils } from '../../support/utils/connect/ConnectUtils';
+import { PipelineBuilder } from '../../support/builder/PipelineBuilder';
+import { PipelineElementBuilder } from
'../../support/builder/PipelineElementBuilder';
+import { AssetBtns } from '../../support/utils/asset/AssetBtns';
+
+describe('Test Saving Pipeline with Asset Link', () => {
+ const assetName1 = 'Test1';
+ const assetName2 = 'Test2';
+ const assetName3 = 'Test3';
+ beforeEach('Setup Test', () => {
+ cy.initStreamPipesTest();
+ AssetUtils.goToAssets();
+ AssetUtils.addAndSaveAsset(assetName3);
+ AssetUtils.addAndSaveAsset(assetName2);
+ AssetUtils.addAndSaveAsset(assetName1);
+
+ // Generate A Pipeline
+ const adapterName = 'simulator';
+
+ ConnectUtils.addMachineDataSimulator(adapterName);
+
+ const pipelineInput = PipelineBuilder.create('Pipeline Test')
+ .addSource(adapterName)
+ .addSink(
+ PipelineElementBuilder.create('data_lake')
+ .addInput('input', 'db_measurement', 'demo')
+ .build(),
+ )
+ .build();
+
+ PipelineUtils.addPipelineWithAssetLinks(pipelineInput, [
+ assetName1,
+ assetName2,
+ ]);
+ });
+
+ it('Add Pipeline to Asset during creation', () => {
+ PipelineUtils.deletePipeline(`Pipeline Test`);
+
+ // Go Back to Asset
+ AssetUtils.goToAssets();
+ AssetUtils.checkAmountOfAssetsGreaterThan(0);
+
+ // CLick on Asset
+
+ AssetUtils.editAsset(assetName1);
+ AssetBtns.assetLinksTab().click();
+ AssetUtils.checkAmountOfLinkedResources(2);
+
+ // Go Back to Asset
+ AssetUtils.goToAssets();
+ AssetUtils.checkAmountOfAssetsGreaterThan(0);
+ AssetUtils.editAsset(assetName2);
+ AssetBtns.assetLinksTab().click();
+ AssetUtils.checkAmountOfLinkedResources(2);
+ });
+
+ it('Edit Pipeline to Asset during Edit', () => {
+ PipelineUtils.editPipeline('Pipeline Test');
+ cy.dataCy('sp-editor-save-pipeline', { timeout: 10000 })
+ .should('exist')
+ .click();
+ cy.dataCy('sp-editor-pipeline-name').clear();
+ PipelineUtils.updatePipeline('Renamed Pipeline');
+ PipelineUtils.finalizePipelineStart([assetName1, assetName3]);
+
+ // Test Number of Asset Links
+ AssetUtils.checkAmountOfLinkedResourcesByAssetName(assetName2, 2);
+ AssetUtils.checkAmountOfLinkedResourcesByAssetName(assetName3, 2);
+
+ // Test Renaming
+ AssetUtils.checkResourceNamingByAssetName(
+ assetName2,
+ 'Renamed Pipeline',
+ );
+ });
+});
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/components/asset-link-configuration/asset-link-configuration.component.html
b/ui/projects/streampipes/shared-ui/src/lib/components/asset-link-configuration/asset-link-configuration.component.html
new file mode 100644
index 0000000000..db309f64f4
--- /dev/null
+++
b/ui/projects/streampipes/shared-ui/src/lib/components/asset-link-configuration/asset-link-configuration.component.html
@@ -0,0 +1,72 @@
+<!--
+ ~ 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.
+ ~
+ -->
+
+@if (assetsData?.length) {
+ <div class="mt-10">
+ <div class="tree-container">
+ <mat-tree
+ [dataSource]="dataSource"
+ [treeControl]="treeControl"
+ class="asset-tree"
+ dataCy="sp-asset-linkage-tree"
+ >
+ <!-- Parent Node Definition -->
+ <mat-nested-tree-node
+ *matTreeNodeDef="let node; when: hasChild"
+ >
+ <div class="mat-tree-node" (click)="onAssetSelect(node)">
+ <button
+ mat-icon-button
+ matTreeNodeToggle
+ [attr.aria-label]="'Toggle ' + node.assetName"
+ >
+ <mat-icon>{{
+ treeControl.isExpanded(node)
+ ? 'expand_more'
+ : 'chevron_right'
+ }}</mat-icon>
+ </button>
+ <span [class.selected-node]="isSelected(node)">{{
+ node.assetName
+ }}</span>
+ </div>
+ <div *ngIf="treeControl.isExpanded(node)" role="group">
+ <ng-container matTreeNodeOutlet></ng-container>
+ </div>
+ </mat-nested-tree-node>
+
+ <!-- Leaf Node Definition (no children) -->
+ <mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle>
+ <div class="mat-tree-node" (click)="onAssetSelect(node)">
+ <span class="mat-icon-button placeholder-icon">
+ <mat-icon class="invisible"></mat-icon>
+ </span>
+ <span [class.selected-node]="isSelected(node)">{{
+ node.assetName
+ }}</span>
+ </div>
+ </mat-tree-node>
+ </mat-tree>
+ </div>
+ </div>
+} @else {
+ <!-- If no assets available -->
+ <div *ngIf="!assetsData?.length">
+ <p>No assets available</p>
+ </div>
+}
diff --git
a/ui/src/app/connect/components/adapter-configuration/adapter-asset-configuration/adapter-asset-configuration.component.scss
b/ui/projects/streampipes/shared-ui/src/lib/components/asset-link-configuration/asset-link-configuration.component.scss
similarity index 95%
rename from
ui/src/app/connect/components/adapter-configuration/adapter-asset-configuration/adapter-asset-configuration.component.scss
rename to
ui/projects/streampipes/shared-ui/src/lib/components/asset-link-configuration/asset-link-configuration.component.scss
index 5e1df3b47e..f0a60ac442 100644
---
a/ui/src/app/connect/components/adapter-configuration/adapter-asset-configuration/adapter-asset-configuration.component.scss
+++
b/ui/projects/streampipes/shared-ui/src/lib/components/asset-link-configuration/asset-link-configuration.component.scss
@@ -42,6 +42,11 @@ mat-checkbox {
padding-left: 10px;
margin-left: 10px;
}
+.tree-container {
+ max-height: 200px;
+ overflow-y: auto;
+ padding-right: 10px;
+}
.mat-tree-node {
display: flex;
diff --git
a/ui/src/app/connect/components/adapter-configuration/adapter-asset-configuration/adapter-asset-configuration.component.ts
b/ui/projects/streampipes/shared-ui/src/lib/components/asset-link-configuration/asset-link-configuration.component.ts
similarity index 92%
rename from
ui/src/app/connect/components/adapter-configuration/adapter-asset-configuration/adapter-asset-configuration.component.ts
rename to
ui/projects/streampipes/shared-ui/src/lib/components/asset-link-configuration/asset-link-configuration.component.ts
index 8666dc0958..84f55d2530 100644
---
a/ui/src/app/connect/components/adapter-configuration/adapter-asset-configuration/adapter-asset-configuration.component.ts
+++
b/ui/projects/streampipes/shared-ui/src/lib/components/asset-link-configuration/asset-link-configuration.component.ts
@@ -26,22 +26,21 @@ import {
AssetLinkType,
SpAsset,
SpAssetTreeNode,
- AdapterDescription,
} from '@streampipes/platform-services';
import { MatStepper } from '@angular/material/stepper';
import { Observable } from 'rxjs';
@Component({
- selector: 'sp-adapter-asset-configuration',
- templateUrl: './adapter-asset-configuration.component.html',
- styleUrls: ['./adapter-asset-configuration.component.scss'],
+ selector: 'sp-asset-link-configuration',
+ templateUrl: './asset-link-configuration.component.html',
+ styleUrls: ['./asset-link-configuration.component.scss'],
standalone: false,
})
-export class AdapterAssetConfigurationComponent implements OnInit {
+export class AssetLinkConfigurationComponent implements OnInit {
@Input() linkageData: LinkageData[] = [];
@Input() stepper: MatStepper;
@Input() isEdit: boolean;
- @Input() adapter: AdapterDescription;
+ @Input() itemId: unknown;
@Output() adapterStartedEmitter: EventEmitter<void> =
new EventEmitter<void>();
@@ -142,7 +141,7 @@ export class AdapterAssetConfigurationComponent implements
OnInit {
}
private setSelect() {
- if (!this.adapter || !this.adapter.elementId) {
+ if (!this.itemId) {
return;
}
@@ -159,9 +158,7 @@ export class AdapterAssetConfigurationComponent implements
OnInit {
if (
node.assetLinks &&
- node.assetLinks.some(
- link => link.resourceId === this.adapter.elementId,
- )
+ node.assetLinks.some(link => link.resourceId === this.itemId)
) {
if (!this.isSelected(node)) {
this.selectedAssets.push(node);
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/services/asset-configuration.service.ts
b/ui/projects/streampipes/shared-ui/src/lib/services/asset-configuration.service.ts
new file mode 100644
index 0000000000..c7dbe9ef9a
--- /dev/null
+++
b/ui/projects/streampipes/shared-ui/src/lib/services/asset-configuration.service.ts
@@ -0,0 +1,337 @@
+/*
+ * 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 { Injectable, Output, EventEmitter } from '@angular/core';
+import {
+ AssetConstants,
+ AssetManagementService,
+ AssetLink,
+ LinkageData,
+ SpAssetModel,
+ AssetLinkType,
+ GenericStorageService,
+ SpAssetTreeNode,
+} from '@streampipes/platform-services';
+import { firstValueFrom } from 'rxjs';
+
+@Injectable({
+ providedIn: 'root',
+})
+export class AssetSaveService {
+ assetLinkTypes: AssetLinkType[] = [];
+ currentAsset: SpAssetModel;
+ constructor(
+ private assetService: AssetManagementService,
+ private storageService: GenericStorageService,
+ ) {
+ this.loadAssetLinkTypes();
+ }
+
+ @Output() adapterStartedEmitter: EventEmitter<void> =
+ new EventEmitter<void>();
+
+ async saveSelectedAssets(
+ selectedAssets: SpAssetTreeNode[],
+ linkageData: LinkageData[],
+ deselectedAssets: SpAssetTreeNode[] = [],
+ originalAssets: SpAssetTreeNode[] = [],
+ ): Promise<void> {
+ const links = this.buildLinks(linkageData);
+
+ if (deselectedAssets.length > 0) {
+ await this.deleteLinkOnDeselectAssets(deselectedAssets, links);
+ }
+ if (selectedAssets.length > 0) {
+ await this.setLinkOnSelectAssets(selectedAssets, links);
+ }
+
+ if (originalAssets.length > 0) {
+ //filter is necessary, otherwise conflicting database instances
are produced
+ const filteredOriginal = this.filterAssets(
+ originalAssets,
+ deselectedAssets,
+ selectedAssets,
+ );
+
+ if (filteredOriginal.length > 0) {
+ this.renameLinkage(filteredOriginal, links);
+ }
+ }
+ }
+ private filterAssets(
+ originalAssets: SpAssetTreeNode[],
+ deselectedAssets: SpAssetTreeNode[],
+ selectedAssets: SpAssetTreeNode[],
+ ): SpAssetTreeNode[] {
+ const deselectedAssetIds = new Set(
+ deselectedAssets.map(asset => asset.assetId),
+ );
+ const selectedAssetIds = new Set(
+ selectedAssets.map(asset => asset.assetId),
+ );
+
+ return originalAssets.filter(
+ asset =>
+ !deselectedAssetIds.has(asset.assetId) &&
+ !selectedAssetIds.has(asset.assetId),
+ );
+ }
+
+ renameLinkage(originalAssets, links) {
+ const uniqueAssetIDsDict = this.getAssetPaths(originalAssets);
+ const uniqueAssetIDs = Object.keys(uniqueAssetIDsDict);
+
+ uniqueAssetIDs.forEach(spAssetModelId => {
+ this.assetService.getAsset(spAssetModelId).subscribe({
+ next: current => {
+ this.currentAsset = current;
+
+ uniqueAssetIDsDict[spAssetModelId].forEach(path => {
+ if (path.length === 2) {
+ current.assetLinks = (current.assetLinks ??
[]).map(
+ (link: any) => {
+ const matchedLink = links.find(
+ l => l.resourceId === link.resourceId,
+ );
+ if (matchedLink) {
+ link.linkLabel = matchedLink.linkLabel;
+ }
+ return link;
+ },
+ );
+ }
+
+ if (path.length > 2) {
+ links.forEach(linkToUpdate => {
+ this.updateLinkLabelInDict(
+ current,
+ path,
+ linkToUpdate,
+ );
+ });
+ }
+ });
+
+ const updateObservable =
+ this.assetService.updateAsset(current);
+
+ updateObservable?.subscribe({
+ next: () => {
+ this.adapterStartedEmitter.emit();
+ },
+ });
+ },
+ });
+ });
+ }
+
+ private updateLinkLabelInDict(
+ dict: SpAssetTreeNode,
+ path: (string | number)[],
+ linkToUpdate: any,
+ ) {
+ let current = dict;
+
+ for (let i = 2; i < path.length; i++) {
+ const key = path[i];
+ if (i === path.length - 1) {
+ if (current.assets?.[key]?.assetLinks) {
+ current.assets[key].assetLinks = current.assets[
+ key
+ ].assetLinks.map((link: any) => {
+ if (link.resourceId === linkToUpdate.resourceId) {
+ link.linkLabel = linkToUpdate.linkLabel;
+ }
+ return link;
+ });
+ }
+ } else {
+ if (Array.isArray(current.assets)) {
+ current = current.assets[key as number];
+ }
+ }
+ }
+
+ return current;
+ }
+ async setLinkOnSelectAssets(
+ selectedAssets: SpAssetTreeNode[],
+ links: AssetLink[],
+ ): Promise<void> {
+ const uniqueAssetIDsDict = this.getAssetPaths(selectedAssets);
+ const uniqueAssetIDs = Object.keys(uniqueAssetIDsDict);
+
+ for (const spAssetModelId of uniqueAssetIDs) {
+ const current = await firstValueFrom(
+ this.assetService.getAsset(spAssetModelId),
+ );
+
+ uniqueAssetIDsDict[spAssetModelId].forEach(path => {
+ if (path.length === 2) {
+ current.assetLinks = [
+ ...(current.assetLinks ?? []),
+ ...links,
+ ];
+ }
+
+ if (path.length > 2) {
+ this.updateDictValue(current, path, links);
+ }
+ });
+
+ const updateObservable = this.assetService.updateAsset(current);
+ await firstValueFrom(updateObservable); // Ensure this completes
before continuing
+ }
+ }
+
+ async deleteLinkOnDeselectAssets(
+ deselectedAssets: SpAssetTreeNode[],
+ links: AssetLink[],
+ ): Promise<void> {
+ const uniqueAssetIDsDict = this.getAssetPaths(deselectedAssets);
+ const uniqueAssetIDs = Object.keys(uniqueAssetIDsDict);
+
+ for (const spAssetModelId of uniqueAssetIDs) {
+ const current = await firstValueFrom(
+ this.assetService.getAsset(spAssetModelId),
+ );
+
+ uniqueAssetIDsDict[spAssetModelId].forEach(path => {
+ if (path.length === 2) {
+ current.assetLinks = (current.assetLinks ?? []).filter(
+ (link: any) =>
+ !links.some(
+ l =>
+ JSON.stringify(l.resourceId) ===
+ JSON.stringify(link.resourceId),
+ ),
+ );
+ }
+
+ if (path.length > 2) {
+ links.forEach(linkToRemove => {
+ this.deleteDictValue(current, path, linkToRemove);
+ });
+ }
+ });
+
+ const updateObservable = this.assetService.updateAsset(current);
+ await firstValueFrom(updateObservable); // Ensure this completes
before continuing
+ }
+ }
+
+ private deleteDictValue(
+ dict: SpAssetTreeNode,
+ path: (string | number)[],
+ linkToRemove: any,
+ ) {
+ let current = dict;
+
+ for (let i = 2; i < path.length; i++) {
+ const key = path[i];
+ if (i === path.length - 1) {
+ if (current.assets?.[key]?.assetLinks) {
+ current.assets[key].assetLinks = current.assets[
+ key
+ ].assetLinks.filter(
+ (link: any) =>
+ JSON.stringify(link.resourceId) !==
+ JSON.stringify(linkToRemove.resourceId),
+ );
+ }
+ } else {
+ if (Array.isArray(current.assets)) {
+ current = current.assets[key as number];
+ }
+ }
+ }
+
+ return current;
+ }
+
+ private updateDictValue(
+ dict: SpAssetModel,
+ path: (string | number)[],
+ newValue: any,
+ ) {
+ const result: any = { ...dict };
+ let current = result;
+ for (let i = 2; i < path.length; i++) {
+ const key = path[i];
+
+ if (i === path.length - 1) {
+ current.assets[key].assetLinks = [
+ ...(current.assets[key].assetLinks ?? []),
+ ...newValue,
+ ];
+
+ break;
+ }
+
+ if (Array.isArray(current.assets)) {
+ parent = current;
+ current = { ...current.assets[key as number] };
+ }
+ }
+
+ return result;
+ }
+
+ private getAssetPaths(apiAssets: SpAssetTreeNode[]): {
+ [key: string]: Array<Array<string | number>>;
+ } {
+ const idPaths = {};
+ apiAssets.forEach(item => {
+ if (item.spAssetModelId && item.flattenPath) {
+ if (!idPaths[item.spAssetModelId]) {
+ idPaths[item.spAssetModelId] = [];
+ }
+ idPaths[item.spAssetModelId].push(item.flattenPath);
+ }
+ });
+ return idPaths;
+ }
+
+ private buildLinks(data: LinkageData[]): AssetLink[] {
+ return data.map(item => {
+ const linkType = this.getAssetLinkTypeById(item.type);
+ return {
+ linkLabel: item.name,
+ linkType: item.type,
+ editingDisabled: false,
+ queryHint: item.type,
+ navigationActive: linkType?.navigationActive ?? false,
+ resourceId: item.id,
+ };
+ });
+ }
+
+ private getAssetLinkTypeById(linkType: string): AssetLinkType | undefined {
+ return this.assetLinkTypes.find(a => a.linkType === linkType);
+ }
+
+ private loadAssetLinkTypes(): void {
+ this.storageService
+ .getAllDocuments(AssetConstants.ASSET_LINK_TYPES_DOC_NAME)
+ .subscribe(linkTypes => {
+ this.assetLinkTypes = linkTypes.sort((a, b) =>
+ a.linkLabel.localeCompare(b.linkLabel),
+ );
+ });
+ }
+}
diff --git a/ui/projects/streampipes/shared-ui/src/lib/shared-ui.module.ts
b/ui/projects/streampipes/shared-ui/src/lib/shared-ui.module.ts
index 0bbe262e61..176c333794 100644
--- a/ui/projects/streampipes/shared-ui/src/lib/shared-ui.module.ts
+++ b/ui/projects/streampipes/shared-ui/src/lib/shared-ui.module.ts
@@ -99,6 +99,7 @@ import { MatExpansionModule } from
'@angular/material/expansion';
import { SortByRuntimeNamePipe } from './pipes/sort-by-runtime-name.pipe';
import { DragDropModule } from '@angular/cdk/drag-drop';
import { SpTableActionsDirective } from
'./components/sp-table/sp-table-actions.directive';
+import { AssetLinkConfigurationComponent } from
'./components/asset-link-configuration/asset-link-configuration.component';
@NgModule({
declarations: [
@@ -151,6 +152,7 @@ import { SpTableActionsDirective } from
'./components/sp-table/sp-table-actions.
InputSchemaPropertyComponent,
SortByRuntimeNamePipe,
SpTableActionsDirective,
+ AssetLinkConfigurationComponent,
],
imports: [
CommonModule,
@@ -190,6 +192,7 @@ import { SpTableActionsDirective } from
'./components/sp-table/sp-table-actions.
],
exports: [
AssetBrowserComponent,
+ AssetLinkConfigurationComponent,
ConfirmDialogComponent,
DataDownloadDialogComponent,
DateInputComponent,
diff --git a/ui/projects/streampipes/shared-ui/src/public-api.ts
b/ui/projects/streampipes/shared-ui/src/public-api.ts
index f416efe889..e25e3ad2b4 100644
--- a/ui/projects/streampipes/shared-ui/src/public-api.ts
+++ b/ui/projects/streampipes/shared-ui/src/public-api.ts
@@ -54,6 +54,7 @@ export * from
'./lib/components/pipeline-element-documentation/pipeline-element-
export * from './lib/components/pipeline-element/pipeline-element.component';
export * from
'./lib/components/input-schema-panel/input-schema-panel.component';
export * from './lib/components/sidebar-resize/sidebar-resize.component';
+export * from
'./lib/components/asset-link-configuration/asset-link-configuration.component';
export * from './lib/models/sp-navigation.model';
@@ -66,3 +67,4 @@ export * from './lib/services/time-selection.service';
export * from './lib/components/asset-browser/asset-browser.service';
export * from './lib/services/date-format.service';
export * from './lib/services/pipeline-element-schema.service';
+export * from './lib/services/asset-configuration.service';
diff --git
a/ui/src/app/assets/dialog/create-asset/create-asset-dialog.component.html
b/ui/src/app/assets/dialog/create-asset/create-asset-dialog.component.html
index 83e25dad6f..eb59282107 100644
--- a/ui/src/app/assets/dialog/create-asset/create-asset-dialog.component.html
+++ b/ui/src/app/assets/dialog/create-asset/create-asset-dialog.component.html
@@ -54,7 +54,7 @@
mat-button
mat-flat-button
color="accent"
- data-cy="save-asset"
+ data-cy="create-asset-panel"
(click)="onSave()"
>
Create
diff --git
a/ui/src/app/connect/components/adapter-configuration/adapter-asset-configuration/adapter-asset-configuration.component.html
b/ui/src/app/connect/components/adapter-configuration/adapter-asset-configuration/adapter-asset-configuration.component.html
deleted file mode 100644
index 1688dc5899..0000000000
---
a/ui/src/app/connect/components/adapter-configuration/adapter-asset-configuration/adapter-asset-configuration.component.html
+++ /dev/null
@@ -1,64 +0,0 @@
-<!--
- ~ 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.
- ~
- -->
-<div *ngIf="assetsData?.length" class="mt-10">
- <mat-tree
- [dataSource]="dataSource"
- [treeControl]="treeControl"
- class="asset-tree"
- >
- <!-- Parent Node Definition -->
- <mat-nested-tree-node *matTreeNodeDef="let node; when: hasChild">
- <div class="mat-tree-node" (click)="onAssetSelect(node)">
- <button
- mat-icon-button
- matTreeNodeToggle
- [attr.aria-label]="'Toggle ' + node.assetName"
- >
- <mat-icon>{{
- treeControl.isExpanded(node)
- ? 'expand_more'
- : 'chevron_right'
- }}</mat-icon>
- </button>
- <span [class.selected-node]="isSelected(node)">{{
- node.assetName
- }}</span>
- </div>
- <div *ngIf="treeControl.isExpanded(node)" role="group">
- <ng-container matTreeNodeOutlet></ng-container>
- </div>
- </mat-nested-tree-node>
-
- <!-- Leaf Node Definition (no children) -->
- <mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle>
- <div class="mat-tree-node" (click)="onAssetSelect(node)">
- <span class="mat-icon-button placeholder-icon">
- <mat-icon class="invisible"></mat-icon>
- </span>
- <span [class.selected-node]="isSelected(node)">{{
- node.assetName
- }}</span>
- </div>
- </mat-tree-node>
- </mat-tree>
-</div>
-
-<!-- If no assets available -->
-<div *ngIf="!assetsData?.length">
- <p>No assets available</p>
-</div>
diff --git
a/ui/src/app/connect/components/adapter-configuration/start-adapter-configuration/start-adapter-configuration.component.html
b/ui/src/app/connect/components/adapter-configuration/start-adapter-configuration/start-adapter-configuration.component.html
index 4f9c96965e..843ac4bc7d 100644
---
a/ui/src/app/connect/components/adapter-configuration/start-adapter-configuration/start-adapter-configuration.component.html
+++
b/ui/src/app/connect/components/adapter-configuration/start-adapter-configuration/start-adapter-configuration.component.html
@@ -113,15 +113,15 @@
"
(optionSelectedEmitter)="showAsset = $event"
>
- <sp-adapter-asset-configuration
+ <sp-asset-link-configuration
*ngIf="showAsset"
[isEdit]="isEditMode"
- [adapter]="adapterDescription"
+ [itemId]="adapterDescription.elementId"
(selectedAssetsChange)="onSelectedAssetsChange($event)"
(deselectedAssetsChange)="onDeselectedAssetsChange($event)"
(originalAssetsEmitter)="onOriginalAssetsEmitted($event)"
>
- </sp-adapter-asset-configuration>
+ </sp-asset-link-configuration>
</sp-adapter-options-panel>
<sp-adapter-options-panel
diff --git
a/ui/src/app/connect/components/adapter-configuration/start-adapter-configuration/start-adapter-configuration.component.ts
b/ui/src/app/connect/components/adapter-configuration/start-adapter-configuration/start-adapter-configuration.component.ts
index 5d2ccbaf39..f38436f3b8 100644
---
a/ui/src/app/connect/components/adapter-configuration/start-adapter-configuration/start-adapter-configuration.component.ts
+++
b/ui/src/app/connect/components/adapter-configuration/start-adapter-configuration/start-adapter-configuration.component.ts
@@ -15,7 +15,6 @@
* limitations under the License.
*
*/
-
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import {
AdapterDescription,
diff --git a/ui/src/app/connect/connect.module.ts
b/ui/src/app/connect/connect.module.ts
index a0cdb02303..260e3a12d7 100644
--- a/ui/src/app/connect/connect.module.ts
+++ b/ui/src/app/connect/connect.module.ts
@@ -52,7 +52,6 @@ import { ErrorMessageComponent } from
'./components/adapter-configuration/schema
import { LoadingMessageComponent } from
'./components/adapter-configuration/schema-editor/loading-message/loading-message.component';
import { SchemaEditorHeaderComponent } from
'./components/adapter-configuration/schema-editor/schema-editor-header/schema-editor-header.component';
import { StartAdapterConfigurationComponent } from
'./components/adapter-configuration/start-adapter-configuration/start-adapter-configuration.component';
-import { AdapterAssetConfigurationComponent } from
'./components/adapter-configuration/adapter-asset-configuration/adapter-asset-configuration.component';
import { DeleteAdapterDialogComponent } from
'./dialog/delete-adapter-dialog/delete-adapter-dialog.component';
import { PlatformServicesModule } from '@streampipes/platform-services';
import { RouterModule } from '@angular/router';
@@ -237,7 +236,6 @@ import { TranslatePipe } from '@ngx-translate/core';
SchemaEditorHeaderComponent,
SpEpSettingsSectionComponent,
StartAdapterConfigurationComponent,
- AdapterAssetConfigurationComponent,
SpAdapterDeploymentSettingsComponent,
SpAdapterDetailsLogsComponent,
SpAdapterDetailsMetricsComponent,
diff --git
a/ui/src/app/connect/dialog/adapter-started/adapter-started-dialog.component.ts
b/ui/src/app/connect/dialog/adapter-started/adapter-started-dialog.component.ts
index 8f16df7572..d93ce246ee 100644
---
a/ui/src/app/connect/dialog/adapter-started/adapter-started-dialog.component.ts
+++
b/ui/src/app/connect/dialog/adapter-started/adapter-started-dialog.component.ts
@@ -24,7 +24,6 @@ import {
Output,
inject,
} from '@angular/core';
-
import { ShepherdService } from '../../../services/tour/shepherd.service';
import {
AdapterDescription,
@@ -42,11 +41,10 @@ import {
LinkageData,
CompactPipelineService,
} from '@streampipes/platform-services';
-import { DialogRef } from '@streampipes/shared-ui';
+import { AssetSaveService, DialogRef } from '@streampipes/shared-ui';
import { TranslateService } from '@ngx-translate/core';
-import { AssetSaveService } from
'../../services/adapter-asset-configuration.service';
-import { firstValueFrom } from 'rxjs';
+import { firstValueFrom, lastValueFrom } from 'rxjs';
@Component({
selector: 'sp-dialog-adapter-started-dialog',
@@ -55,6 +53,13 @@ import { firstValueFrom } from 'rxjs';
})
export class AdapterStartedDialog implements OnInit {
translateService = inject(TranslateService);
+ public dialogRef = inject(DialogRef<AdapterStartedDialog>);
+ private adapterService = inject(AdapterService);
+ private shepherdService = inject(ShepherdService);
+ private pipelineTemplateService = inject(PipelineTemplateService);
+ private compactPipelineService = inject(CompactPipelineService);
+ private assetSaveService = inject(AssetSaveService);
+ private dataLakeService = inject(DatalakeRestService);
adapterInstalled = false;
@@ -110,16 +115,6 @@ export class AdapterStartedDialog implements OnInit {
addToAssetText = '';
deletedFromAssetText = '';
- constructor(
- public dialogRef: DialogRef<AdapterStartedDialog>,
- private adapterService: AdapterService,
- private shepherdService: ShepherdService,
- private pipelineTemplateService: PipelineTemplateService,
- private compactPipelineService: CompactPipelineService,
- private assetSaveService: AssetSaveService,
- private dataLakeService: DatalakeRestService,
- ) {}
-
ngOnInit() {
if (this.editMode) {
this.initAdapterUpdatePreflight();
@@ -205,7 +200,6 @@ export class AdapterStartedDialog implements OnInit {
} else {
this.startAdapter(adapterElementId, true);
this.addToAsset();
- this.addToAsset();
}
} else {
const errorMsg: SpLogMessage =
@@ -345,9 +339,9 @@ export class AdapterStartedDialog implements OnInit {
name: pipelineId,
});
- const res = await this.dataLakeService
- .getMeasurementByName(adapter.name)
- .toPromise();
+ const res = await lastValueFrom(
+ this.dataLakeService.getMeasurementByName(adapter.name),
+ );
linkageData.push({
type: 'measurement',
@@ -430,7 +424,6 @@ export class AdapterStartedDialog implements OnInit {
pipelineOperationStatus;
this.startAdapter(adapterElementId, true);
this.addToAsset();
- this.addToAsset();
},
error => {
this.onAdapterFailure(error.error);
diff --git
a/ui/src/app/editor/dialog/save-pipeline/save-pipeline-settings/save-pipeline-settings.component.html
b/ui/src/app/editor/dialog/save-pipeline/save-pipeline-settings/save-pipeline-settings.component.html
index 320ff93ad3..cb88a730c0 100644
---
a/ui/src/app/editor/dialog/save-pipeline/save-pipeline-settings/save-pipeline-settings.component.html
+++
b/ui/src/app/editor/dialog/save-pipeline/save-pipeline-settings/save-pipeline-settings.component.html
@@ -122,6 +122,25 @@
>
Navigate to pipeline overview afterwards
</mat-checkbox>
+ <mat-checkbox
+ [(ngModel)]="addToAssets"
+ color="accent"
+ data-cy="sp-show-pipeline-asset-checkbox"
+ >
+ Add Pipeline to Assets
+ </mat-checkbox>
+ @if (addToAssets) {
+ <div class="mt-10">
+ <sp-asset-link-configuration
+ [isEdit]="storageOptions.updateMode === 'update'"
+ [itemId]="pipeline._id"
+ (selectedAssetsChange)="onSelectedAssetsChange($event)"
+ (deselectedAssetsChange)="onDeselectedAssetsChange($event)"
+ (originalAssetsEmitter)="onOriginalAssetsEmitted($event)"
+ >
+ </sp-asset-link-configuration>
+ </div>
+ }
<div class="mt-10">
<mat-expansion-panel class="mat-elevation-z0 border-1">
<mat-expansion-panel-header
diff --git
a/ui/src/app/editor/dialog/save-pipeline/save-pipeline-settings/save-pipeline-settings.component.ts
b/ui/src/app/editor/dialog/save-pipeline/save-pipeline-settings/save-pipeline-settings.component.ts
index e60883b7cc..59531bfa91 100644
---
a/ui/src/app/editor/dialog/save-pipeline/save-pipeline-settings/save-pipeline-settings.component.ts
+++
b/ui/src/app/editor/dialog/save-pipeline/save-pipeline-settings/save-pipeline-settings.component.ts
@@ -16,7 +16,14 @@
*
*/
-import { Component, Input, OnInit } from '@angular/core';
+import {
+ Component,
+ EventEmitter,
+ inject,
+ Input,
+ OnInit,
+ Output,
+} from '@angular/core';
import { ShepherdService } from '../../../../services/tour/shepherd.service';
import {
UntypedFormControl,
@@ -27,6 +34,7 @@ import {
CompactPipeline,
Pipeline,
PipelineService,
+ SpAssetTreeNode,
} from '@streampipes/platform-services';
import { PipelineStorageOptions } from '../../../model/editor.model';
import { ValidateName } from
'../../../../core-ui/static-properties/input.validator';
@@ -50,12 +58,22 @@ export class SavePipelineSettingsComponent implements
OnInit {
@Input()
currentPipelineName: string;
+ private shepherdService = inject(ShepherdService);
+ private pipelineService = inject(PipelineService);
+
compactPipeline: CompactPipeline;
- constructor(
- private shepherdService: ShepherdService,
- private pipelineService: PipelineService,
- ) {}
+ addToAssets: boolean = false;
+ @Input()
+ selectedAssets: SpAssetTreeNode[];
+ @Input()
+ deselectedAssets: SpAssetTreeNode[];
+ @Input()
+ originalAssets: SpAssetTreeNode[];
+
+ @Output() selectedAssetsChange = new EventEmitter<SpAssetTreeNode[]>();
+ @Output() deselectedAssetsChange = new EventEmitter<SpAssetTreeNode[]>();
+ @Output() originalAssetsChange = new EventEmitter<SpAssetTreeNode[]>();
ngOnInit() {
this.submitPipelineForm.addControl(
@@ -90,6 +108,21 @@ export class SavePipelineSettingsComponent implements
OnInit {
.subscribe(p => (this.compactPipeline = p));
}
+ onSelectedAssetsChange(updatedAssets: SpAssetTreeNode[]): void {
+ this.selectedAssets = updatedAssets;
+ this.selectedAssetsChange.emit(this.selectedAssets);
+ }
+
+ onDeselectedAssetsChange(updatedAssets: SpAssetTreeNode[]): void {
+ this.deselectedAssets = updatedAssets;
+ this.deselectedAssetsChange.emit(this.deselectedAssets);
+ }
+
+ onOriginalAssetsEmitted(updatedAssets: SpAssetTreeNode[]): void {
+ this.originalAssets = updatedAssets;
+ this.originalAssetsChange.emit(this.originalAssets);
+ }
+
triggerTutorial() {
if (this.shepherdService.isTourActive()) {
this.shepherdService.trigger('save-pipeline-dialog');
diff --git
a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.html
b/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.html
index a2bd2573e1..684b3a23c6 100644
--- a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.html
+++ b/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.html
@@ -25,6 +25,9 @@
[submitPipelineForm]="submitPipelineForm"
[pipeline]="pipeline"
[storageOptions]="storageOptions"
+ [(selectedAssets)]="selectedAssets"
+ [(deselectedAssets)]="deselectedAssets"
+ [(originalAssets)]="originalAssets"
>
</sp-save-pipeline-settings>
diff --git a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.ts
b/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.ts
index fc603bf532..efe116e521 100644
--- a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.ts
+++ b/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.ts
@@ -16,15 +16,19 @@
*
*/
-import { Component, Input, OnInit } from '@angular/core';
-import { DialogRef } from '@streampipes/shared-ui';
+import { Component, inject, Input, OnInit } from '@angular/core';
+import { AssetSaveService, DialogRef } from '@streampipes/shared-ui';
import {
+ DatalakeRestService,
+ DataSinkInvocation,
+ LinkageData,
Message,
Pipeline,
PipelineCanvasMetadata,
PipelineCanvasMetadataService,
PipelineOperationStatus,
PipelineService,
+ SpAssetTreeNode,
} from '@streampipes/platform-services';
import { EditorService } from '../../services/editor.service';
import { ShepherdService } from '../../../services/tour/shepherd.service';
@@ -35,7 +39,7 @@ import {
PipelineStorageOptions,
} from '../../model/editor.model';
import { IdGeneratorService } from
'../../../core-services/id-generator/id-generator.service';
-import { Observable, of, tap } from 'rxjs';
+import { firstValueFrom, lastValueFrom, Observable, of, tap } from 'rxjs';
import { filter, switchMap } from 'rxjs/operators';
import {
Status,
@@ -50,12 +54,26 @@ import { PipelineAction } from
'../../../pipelines/model/pipeline-model';
standalone: false,
})
export class SavePipelineComponent implements OnInit {
+ private editorService = inject(EditorService);
+ private dialogRef = inject(DialogRef<SavePipelineComponent>);
+ private idGeneratorService = inject(IdGeneratorService);
+ private pipelineService = inject(PipelineService);
+ private router = inject(Router);
+ private shepherdService = inject(ShepherdService);
+ private pipelineCanvasService = inject(PipelineCanvasMetadataService);
+ private assetSaveService = inject(AssetSaveService);
+ private dataLakeService = inject(DatalakeRestService);
+
@Input()
pipeline: Pipeline;
@Input()
originalPipeline: Pipeline;
+ selectedAssets: SpAssetTreeNode[];
+ deselectedAssets: SpAssetTreeNode[];
+ originalAssets: SpAssetTreeNode[];
+
@Input()
pipelineCanvasMetadata: PipelineCanvasMetadata;
@@ -78,16 +96,6 @@ export class SavePipelineComponent implements OnInit {
finalPipelineOperationStatus: PipelineOperationStatus;
pipelineAction: PipelineAction;
- constructor(
- private editorService: EditorService,
- private dialogRef: DialogRef<SavePipelineComponent>,
- private idGeneratorService: IdGeneratorService,
- private pipelineService: PipelineService,
- private router: Router,
- private shepherdService: ShepherdService,
- private pipelineCanvasService: PipelineCanvasMetadataService,
- ) {}
-
ngOnInit() {
this.storageOptions.updateModeActive =
this.originalPipeline !== undefined;
@@ -139,7 +147,11 @@ export class SavePipelineComponent implements OnInit {
switchMap(() => this.getStartPipeline$()),
)
.subscribe({
- next: message => this.onSuccess(message),
+ next: message => {
+ this.onSuccess(message);
+ // Add Asset as soon as pipelineId is known
+ this.addToAsset();
+ },
error: msg => {
this.onFailure(msg);
},
@@ -174,6 +186,7 @@ export class SavePipelineComponent implements OnInit {
);
}
}
+
this.performStorageOperations(stopPipeline$, savePipeline$);
}
@@ -316,4 +329,59 @@ export class SavePipelineComponent implements OnInit {
}
this.dialogRef.close(reloadConfig);
}
+
+ async addToAsset(): Promise<void> {
+ let linkageData: LinkageData[] = [];
+ linkageData = await this.addPipelineLinkageData(linkageData);
+
+ await this.saveAssets(linkageData);
+ }
+ private async addPipelineLinkageData(
+ linkageData: LinkageData[],
+ ): Promise<LinkageData[]> {
+ const pipeline = await firstValueFrom(
+ this.pipelineService.getPipelineById(this.pipelineId),
+ );
+
+ linkageData.push({
+ type: 'pipeline',
+ id: this.pipelineId,
+ name: pipeline.name,
+ });
+
+ const serviceList: DataSinkInvocation[] =
+ pipeline.actions as DataSinkInvocation[];
+ const dataSinkServices: DataSinkInvocation[] = serviceList.filter(
+ action => action.serviceTagPrefix === 'DATA_SINK',
+ );
+
+ for (const service of dataSinkServices) {
+ const staticProperty = service.staticProperties.find(
+ prop => prop.internalName === 'db_measurement',
+ );
+
+ const measureFromPipeline = (staticProperty as { value: string })
+ .value;
+
+ const measure = await lastValueFrom(
+ this.dataLakeService.getMeasurementByName(measureFromPipeline),
+ );
+
+ linkageData.push({
+ type: 'measurement',
+ id: measure.elementId,
+ name: measureFromPipeline,
+ });
+ }
+ return linkageData;
+ }
+
+ private async saveAssets(linkageData: LinkageData[]): Promise<void> {
+ await this.assetSaveService.saveSelectedAssets(
+ this.selectedAssets,
+ linkageData,
+ this.deselectedAssets,
+ this.originalAssets,
+ );
+ }
}
diff --git a/ui/src/app/editor/editor.module.ts
b/ui/src/app/editor/editor.module.ts
index f9c735cef7..f78a1691c2 100644
--- a/ui/src/app/editor/editor.module.ts
+++ b/ui/src/app/editor/editor.module.ts
@@ -125,6 +125,7 @@ import { TranslatePipe } from '@ngx-translate/core';
PlatformServicesModule,
SharedUiModule,
TranslatePipe,
+ SharedUiModule,
],
declarations: [
AddTemplateDialogComponent,