You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@streampipes.apache.org by ri...@apache.org on 2022/08/21 08:40:49 UTC

[incubator-streampipes] branch rel/0.70.0 updated: [STREAMPIPES-545] Improve assignment of multiple resource links

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

riemer pushed a commit to branch rel/0.70.0
in repository https://gitbox.apache.org/repos/asf/incubator-streampipes.git


The following commit(s) were added to refs/heads/rel/0.70.0 by this push:
     new 7bcd86f0e [STREAMPIPES-545] Improve assignment of multiple resource links
7bcd86f0e is described below

commit 7bcd86f0e78564f65d0e6c0ff3fb4008d4d6f76b
Author: Dominik Riemer <do...@gmail.com>
AuthorDate: Sun Aug 21 10:40:01 2022 +0200

    [STREAMPIPES-545] Improve assignment of multiple resource links
---
 .../export/dataimport/ImportGenerator.java         |  78 ++++++--
 .../export/resolver/AbstractResolver.java          |   3 +-
 .../export/resolver/AdapterResolver.java           |   3 +-
 .../export/resolver/DataSourceResolver.java        |   4 +-
 .../AbstractPipelineElementResourceManager.java    |   6 +-
 .../couchdb/impl/AdapterInstanceStorageImpl.java   |   2 +-
 ui/src/app/assets/assets.module.ts                 |   4 +-
 .../asset-details-panel.component.html             |   1 +
 .../asset-details-panel.component.ts               |  19 ++
 .../assets/dialog/base-asset-links.directive.ts    |  97 +++++++++
 .../edit-asset-link-dialog.component.ts            |  79 +++-----
 .../manage-asset-links-dialog.component.html       | 216 +++++++++++++++++++++
 .../manage-asset-links-dialog.component.scss       |  31 +++
 .../manage-asset-links-dialog.component.ts         | 159 +++++++++++++++
 .../event-schema/event-schema.component.ts         |  14 +-
 .../core/components/toolbar/toolbar.component.html |   2 +-
 16 files changed, 642 insertions(+), 76 deletions(-)

diff --git a/streampipes-data-export/src/main/java/org/apache/streampipes/export/dataimport/ImportGenerator.java b/streampipes-data-export/src/main/java/org/apache/streampipes/export/dataimport/ImportGenerator.java
index d2dbe2231..23bd41d53 100644
--- a/streampipes-data-export/src/main/java/org/apache/streampipes/export/dataimport/ImportGenerator.java
+++ b/streampipes-data-export/src/main/java/org/apache/streampipes/export/dataimport/ImportGenerator.java
@@ -24,6 +24,9 @@ import org.apache.streampipes.commons.zip.ZipFileExtractor;
 import org.apache.streampipes.export.constants.ExportConstants;
 import org.apache.streampipes.export.utils.SerializationUtils;
 import org.apache.streampipes.model.export.StreamPipesApplicationPackage;
+import org.lightcouch.DocumentConflictException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -32,6 +35,8 @@ import java.util.Map;
 
 public abstract class ImportGenerator<T> {
 
+  private static final Logger LOG = LoggerFactory.getLogger(ImportGenerator.class);
+
   protected ObjectMapper spMapper;
   protected ObjectMapper defaultMapper;
 
@@ -45,44 +50,84 @@ public abstract class ImportGenerator<T> {
 
     var manifest = getManifest(previewFiles);
 
-    for (String assetId: manifest.getAssets()) {
-      handleAsset(previewFiles, assetId);
+    for (String assetId : manifest.getAssets()) {
+      try {
+        handleAsset(previewFiles, assetId);
+      } catch (DocumentConflictException | IOException e) {
+        LOG.warn("Skipping import of asset model {} (already present with the same id)", assetId);
+      }
     }
 
-    for (String adapterId: manifest.getAdapters()) {
-      handleAdapter(asString(previewFiles.get(adapterId)), adapterId);
+    for (String adapterId : manifest.getAdapters()) {
+      try {
+        handleAdapter(asString(previewFiles.get(adapterId)), adapterId);
+      } catch (DocumentConflictException e) {
+        LOG.warn("Skipping import of adapter {} (already present with the same id)", adapterId);
+      }
     }
 
-    for(String dashboardId: manifest.getDashboards()) {
+    for (String dashboardId : manifest.getDashboards()) {
+      try {
       handleDashboard(asString(previewFiles.get(dashboardId)), dashboardId);
+      } catch (DocumentConflictException e) {
+        LOG.warn("Skipping import of dashboard {} (already present with the same id)", dashboardId);
+      }
     }
 
-    for (String dataViewId: manifest.getDataViews()) {
+    for (String dataViewId : manifest.getDataViews()) {
+      try {
       handleDataView(asString(previewFiles.get(dataViewId)), dataViewId);
+      } catch (DocumentConflictException e) {
+        LOG.warn("Skipping import of data view {} (already present with the same id)", dataViewId);
+      }
     }
 
-    for (String dataSourceId: manifest.getDataSources()) {
+    for (String dataSourceId : manifest.getDataSources()) {
+      try {
       handleDataSource(asString(previewFiles.get(dataSourceId)), dataSourceId);
+      } catch (DocumentConflictException e) {
+        LOG.warn("Skipping import of data source {} (already present with the same id)", dataSourceId);
+      }
     }
 
-    for (String pipelineId: manifest.getPipelines()) {
+    for (String pipelineId : manifest.getPipelines()) {
+      try {
       handlePipeline(asString(previewFiles.get(pipelineId)), pipelineId);
+      } catch (DocumentConflictException e) {
+        LOG.warn("Skipping import of pipeline {} (already present with the same id)", pipelineId);
+      }
     }
 
-    for (String measurementId: manifest.getDataLakeMeasures()) {
+    for (String measurementId : manifest.getDataLakeMeasures()) {
+      try {
       handleDataLakeMeasure(asString(previewFiles.get(measurementId)), measurementId);
+      } catch (DocumentConflictException e) {
+        LOG.warn("Skipping import of data lake measure {} (already present with the same id)", measurementId);
+      }
     }
 
-    for (String dashboardWidgetId: manifest.getDashboardWidgets()) {
+    for (String dashboardWidgetId : manifest.getDashboardWidgets()) {
+      try {
       handleDashboardWidget(asString(previewFiles.get(dashboardWidgetId)), dashboardWidgetId);
+      } catch (DocumentConflictException e) {
+        LOG.warn("Skipping import of dashboard widget {} (already present with the same id)", dashboardWidgetId);
+      }
     }
 
-    for (String dataViewWidgetId: manifest.getDataViewWidgets()) {
+    for (String dataViewWidgetId : manifest.getDataViewWidgets()) {
+      try {
       handleDataViewWidget(asString(previewFiles.get(dataViewWidgetId)), dataViewWidgetId);
+      } catch (DocumentConflictException e) {
+        LOG.warn("Skipping import of data view widget {} (already present with the same id)", dataViewWidgetId);
+      }
     }
 
-    for(String fileMetadataId: manifest.getFiles()) {
+    for (String fileMetadataId : manifest.getFiles()) {
+      try {
       handleFile(asString(previewFiles.get(fileMetadataId)), fileMetadataId, previewFiles);
+      } catch (DocumentConflictException e) {
+        LOG.warn("Skipping import of file {} (already present with the same id)", fileMetadataId);
+      }
     }
 
     afterResourcesCreated();
@@ -99,14 +144,23 @@ public abstract class ImportGenerator<T> {
   }
 
   protected abstract void handleAsset(Map<String, byte[]> previewFiles, String assetId) throws IOException;
+
   protected abstract void handleAdapter(String document, String adapterId) throws JsonProcessingException;
+
   protected abstract void handleDashboard(String document, String dashboardId) throws JsonProcessingException;
+
   protected abstract void handleDataView(String document, String dataViewId) throws JsonProcessingException;
+
   protected abstract void handleDataSource(String document, String dataSourceId) throws JsonProcessingException;
+
   protected abstract void handlePipeline(String document, String pipelineId) throws JsonProcessingException;
+
   protected abstract void handleDataLakeMeasure(String document, String dataLakeMeasureId) throws JsonProcessingException;
+
   protected abstract void handleDashboardWidget(String document, String dashboardWidgetId) throws JsonProcessingException;
+
   protected abstract void handleDataViewWidget(String document, String dataViewWidgetId) throws JsonProcessingException;
+
   protected abstract void handleFile(String document, String fileMetadataId, Map<String, byte[]> zipContent) throws IOException;
 
   protected abstract T getReturnObject();
diff --git a/streampipes-data-export/src/main/java/org/apache/streampipes/export/resolver/AbstractResolver.java b/streampipes-data-export/src/main/java/org/apache/streampipes/export/resolver/AbstractResolver.java
index 8e085240f..c0bae151f 100644
--- a/streampipes-data-export/src/main/java/org/apache/streampipes/export/resolver/AbstractResolver.java
+++ b/streampipes-data-export/src/main/java/org/apache/streampipes/export/resolver/AbstractResolver.java
@@ -25,6 +25,7 @@ import org.apache.streampipes.model.assets.AssetLink;
 import org.apache.streampipes.model.export.ExportItem;
 import org.apache.streampipes.storage.api.INoSqlStorage;
 import org.apache.streampipes.storage.management.StorageDispatcher;
+import org.lightcouch.DocumentConflictException;
 
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -61,7 +62,7 @@ public abstract class AbstractResolver<T> {
 
   public abstract ExportItem convert(T document);
 
-  public abstract void writeDocument(String document) throws JsonProcessingException;
+  public abstract void writeDocument(String document) throws JsonProcessingException, DocumentConflictException;
 
   protected abstract T deserializeDocument(String document) throws JsonProcessingException;
 }
diff --git a/streampipes-data-export/src/main/java/org/apache/streampipes/export/resolver/AdapterResolver.java b/streampipes-data-export/src/main/java/org/apache/streampipes/export/resolver/AdapterResolver.java
index c879d982c..681afbaab 100644
--- a/streampipes-data-export/src/main/java/org/apache/streampipes/export/resolver/AdapterResolver.java
+++ b/streampipes-data-export/src/main/java/org/apache/streampipes/export/resolver/AdapterResolver.java
@@ -30,7 +30,7 @@ public class AdapterResolver extends AbstractResolver<AdapterDescription> {
 
   @Override
   public AdapterDescription findDocument(String resourceId) {
-    var doc =  getNoSqlStore().getAdapterInstanceStorage().getAdapter(resourceId);
+    var doc = getNoSqlStore().getAdapterInstanceStorage().getAdapter(resourceId);
     doc.setRev(null);
     doc.setSelectedEndpointUrl(null);
     if (doc instanceof AdapterStreamDescription) {
@@ -67,4 +67,5 @@ public class AdapterResolver extends AbstractResolver<AdapterDescription> {
   protected AdapterDescription deserializeDocument(String document) throws JsonProcessingException {
     return this.spMapper.readValue(document, AdapterDescription.class);
   }
+
 }
diff --git a/streampipes-data-export/src/main/java/org/apache/streampipes/export/resolver/DataSourceResolver.java b/streampipes-data-export/src/main/java/org/apache/streampipes/export/resolver/DataSourceResolver.java
index a38bc810a..98a338b8a 100644
--- a/streampipes-data-export/src/main/java/org/apache/streampipes/export/resolver/DataSourceResolver.java
+++ b/streampipes-data-export/src/main/java/org/apache/streampipes/export/resolver/DataSourceResolver.java
@@ -52,7 +52,9 @@ public class DataSourceResolver extends AbstractResolver<SpDataStream> {
                             boolean overrideDocument) throws JsonProcessingException {
     var dataStream = deserializeDocument(document);
     if (overrideDocument) {
-      EventGroundingProcessor.applyOverride(dataStream.getEventGrounding().getTransportProtocol());
+      if (dataStream.getEventGrounding() != null) {
+        EventGroundingProcessor.applyOverride(dataStream.getEventGrounding().getTransportProtocol());
+      }
     }
     getNoSqlStore().getDataStreamStorage().createElement(dataStream);
   }
diff --git a/streampipes-resource-management/src/main/java/org/apache/streampipes/resource/management/AbstractPipelineElementResourceManager.java b/streampipes-resource-management/src/main/java/org/apache/streampipes/resource/management/AbstractPipelineElementResourceManager.java
index 90b31c39d..ad77da9aa 100644
--- a/streampipes-resource-management/src/main/java/org/apache/streampipes/resource/management/AbstractPipelineElementResourceManager.java
+++ b/streampipes-resource-management/src/main/java/org/apache/streampipes/resource/management/AbstractPipelineElementResourceManager.java
@@ -56,8 +56,10 @@ public abstract class AbstractPipelineElementResourceManager<T extends CRUDStora
 
   public void delete(String elementId) {
     D description = find(elementId);
-    deleteAssetsAndPermissions(description);
-    db.deleteElement(description);
+    if (description != null) {
+      deleteAssetsAndPermissions(description);
+      db.deleteElement(description);
+    }
   }
 
   private void deleteAssetsAndPermissions(D description) {
diff --git a/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/AdapterInstanceStorageImpl.java b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/AdapterInstanceStorageImpl.java
index da84fb826..ff6540b98 100644
--- a/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/AdapterInstanceStorageImpl.java
+++ b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/AdapterInstanceStorageImpl.java
@@ -58,7 +58,7 @@ public class AdapterInstanceStorageImpl extends AbstractDao<AdapterDescription>
     @Override
     public AdapterDescription getAdapter(String adapterId) {
         DbCommand<Optional<AdapterDescription>, AdapterDescription> cmd = new FindCommand<>(couchDbClientSupplier, adapterId, AdapterDescription.class);
-        return cmd.execute().get();
+        return cmd.execute().orElse(null);
     }
 
     @Override
diff --git a/ui/src/app/assets/assets.module.ts b/ui/src/app/assets/assets.module.ts
index f3417b778..89d9314cc 100644
--- a/ui/src/app/assets/assets.module.ts
+++ b/ui/src/app/assets/assets.module.ts
@@ -43,6 +43,7 @@ import { MatTreeModule } from '@angular/material/tree';
 import { SpAssetLinkItemComponent } from './components/asset-details/asset-details-panel/asset-link-item/asset-link-item.component';
 import { EditAssetLinkDialogComponent } from './dialog/edit-asset-link/edit-asset-link-dialog.component';
 import { SpCreateAssetDialogComponent } from './dialog/create-asset/create-asset-dialog.component';
+import { SpManageAssetLinksDialogComponent } from './dialog/manage-asset-links/manage-asset-links-dialog.component';
 
 @NgModule({
   imports: [
@@ -93,7 +94,8 @@ import { SpCreateAssetDialogComponent } from './dialog/create-asset/create-asset
     SpAssetLinkItemComponent,
     SpAssetOverviewComponent,
     SpAssetSelectionPanelComponent,
-    SpCreateAssetDialogComponent
+    SpCreateAssetDialogComponent,
+    SpManageAssetLinksDialogComponent
   ],
   providers: [],
 })
diff --git a/ui/src/app/assets/components/asset-details/asset-details-panel/asset-details-panel.component.html b/ui/src/app/assets/components/asset-details/asset-details-panel/asset-details-panel.component.html
index 064bfa416..a3cc3d1fd 100644
--- a/ui/src/app/assets/components/asset-details/asset-details-panel/asset-details-panel.component.html
+++ b/ui/src/app/assets/components/asset-details/asset-details-panel/asset-details-panel.component.html
@@ -39,6 +39,7 @@
 
     <sp-basic-inner-panel panelTitle="Linked Resources" outerMargin="0px 0px">
         <div header fxLayoutAlign="end center" fxLayout="row" fxFlex="100">
+            <button mat-button color="accent" *ngIf="editMode" (click)="openManageAssetLinksDialog()"><i class="material-icons">add</i><span>&nbsp;Manage links</span></button>
             <button mat-button color="accent" *ngIf="editMode" (click)="openCreateAssetLinkDialog()"><i class="material-icons">add</i><span>&nbsp;Add link</span></button>
         </div>
         <div fxLayout="column" fxFlex="100" *ngIf="assetLinkTypes">
diff --git a/ui/src/app/assets/components/asset-details/asset-details-panel/asset-details-panel.component.ts b/ui/src/app/assets/components/asset-details/asset-details-panel/asset-details-panel.component.ts
index 6d8e3d66b..9bb97559c 100644
--- a/ui/src/app/assets/components/asset-details/asset-details-panel/asset-details-panel.component.ts
+++ b/ui/src/app/assets/components/asset-details/asset-details-panel/asset-details-panel.component.ts
@@ -22,6 +22,7 @@ import { AssetConstants } from '../../../constants/asset.constants';
 import { AssetUploadDialogComponent } from '../../../dialog/asset-upload/asset-upload-dialog.component';
 import { DialogService, PanelType } from '../../../../../../dist/streampipes/shared-ui';
 import { EditAssetLinkDialogComponent } from '../../../dialog/edit-asset-link/edit-asset-link-dialog.component';
+import { SpManageAssetLinksDialogComponent } from '../../../dialog/manage-asset-links/manage-asset-links-dialog.component';
 
 
 @Component({
@@ -53,6 +54,24 @@ export class SpAssetDetailsPanelComponent implements OnInit {
     });
   }
 
+  openManageAssetLinksDialog(): void {
+    const dialogRef = this.dialogService.open(SpManageAssetLinksDialogComponent, {
+      panelType: PanelType.SLIDE_IN_PANEL,
+      title: 'Manage asset links',
+      width: '50vw',
+      data: {
+        'assetLinks': this.asset.assetLinks,
+        'assetLinkTypes': this.assetLinkTypes
+      }
+    });
+
+    dialogRef.afterClosed().subscribe(assetLinks => {
+      if (assetLinks) {
+        this.asset.assetLinks = assetLinks;
+      }
+    });
+  }
+
   openEditAssetLinkDialog(assetLink: AssetLink, index: number, createMode: boolean): void {
     const dialogRef = this.dialogService.open(EditAssetLinkDialogComponent, {
       panelType: PanelType.SLIDE_IN_PANEL,
diff --git a/ui/src/app/assets/dialog/base-asset-links.directive.ts b/ui/src/app/assets/dialog/base-asset-links.directive.ts
new file mode 100644
index 000000000..79002369b
--- /dev/null
+++ b/ui/src/app/assets/dialog/base-asset-links.directive.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 { Directive } from '@angular/core';
+import {
+  AdapterDescriptionUnion, AdapterService,
+  Dashboard, DashboardService,
+  DataLakeMeasure, DatalakeRestService, DataViewDataExplorerService, FileMetadata, FilesService, GenericStorageService,
+  Pipeline, PipelineElementService, PipelineService,
+  SpDataStream
+} from '@streampipes/platform-services';
+import { zip } from 'rxjs';
+
+@Directive()
+export abstract class BaseAssetLinksDirective {
+
+  // Resources
+  pipelines: Pipeline[];
+  dataViews: Dashboard[];
+  dashboards: Dashboard[];
+  dataLakeMeasures: DataLakeMeasure[];
+  dataSources: SpDataStream[];
+  adapters: AdapterDescriptionUnion[];
+  files: FileMetadata[];
+
+  allResources: any[] = [];
+
+  constructor(protected genericStorageService: GenericStorageService,
+              protected pipelineService: PipelineService,
+              protected dataViewService: DataViewDataExplorerService,
+              protected dashboardService: DashboardService,
+              protected dataLakeService: DatalakeRestService,
+              protected pipelineElementService: PipelineElementService,
+              protected adapterService: AdapterService,
+              protected filesService: FilesService) {
+
+  }
+
+  onInit() {
+    this.getAllResources();
+  }
+
+  getAllResources() {
+    zip(
+      this.pipelineService.getOwnPipelines(),
+        this.dataViewService.getDataViews(),
+        this.dashboardService.getDashboards(),
+        this.pipelineElementService.getDataStreams(),
+        this.dataLakeService.getAllMeasurementSeries(),
+        this.filesService.getFileMetadata(),
+        this.adapterService.getAdapters()).subscribe((
+      [pipelines,
+        dataViews,
+        dashboards,
+        streams,
+        measurements,
+        files,
+        adapters
+      ]) => {
+      this.pipelines = pipelines.sort((a, b) => a.name.localeCompare(b.name));
+      this.dataViews = dataViews.sort((a, b) => a.name.localeCompare(b.name));
+      this.dashboards = dashboards.sort((a, b) => a.name.localeCompare(b.name));
+      this.dataSources = streams.sort((a, b) => a.name.localeCompare(b.name));
+      this.dataLakeMeasures = measurements.sort((a, b) => a.measureName.localeCompare(b.measureName));
+      this.files = files.sort((a, b) => a.originalFilename.localeCompare(b.originalFilename));
+      this.adapters = adapters.sort((a, b) => a.name.localeCompare(b.name));
+
+      this.allResources = [
+        ...this.pipelines,
+        ...this.dataViews,
+        ...this.dashboards,
+        ...this.dataSources,
+        ...this.dataLakeMeasures,
+        ...this.files,
+        ...this.adapters
+      ];
+    });
+  }
+
+  abstract afterResourcesLoaded(): void;
+}
+
diff --git a/ui/src/app/assets/dialog/edit-asset-link/edit-asset-link-dialog.component.ts b/ui/src/app/assets/dialog/edit-asset-link/edit-asset-link-dialog.component.ts
index fee3b0d9e..0a1b8b6f5 100644
--- a/ui/src/app/assets/dialog/edit-asset-link/edit-asset-link-dialog.component.ts
+++ b/ui/src/app/assets/dialog/edit-asset-link/edit-asset-link-dialog.component.ts
@@ -36,13 +36,14 @@ import {
 import { FormGroup } from '@angular/forms';
 import { zip } from 'rxjs';
 import { MatSelectChange } from '@angular/material/select';
+import { BaseAssetLinksDirective } from '../base-asset-links.directive';
 
 @Component({
   selector: 'sp-edit-asset-link-dialog-component',
   templateUrl: './edit-asset-link-dialog.component.html',
   styleUrls: ['./edit-asset-link-dialog.component.scss']
 })
-export class EditAssetLinkDialogComponent implements OnInit {
+export class EditAssetLinkDialogComponent extends BaseAssetLinksDirective implements OnInit {
 
   @Input()
   assetLink: AssetLink;
@@ -57,33 +58,33 @@ export class EditAssetLinkDialogComponent implements OnInit {
 
   clonedAssetLink: AssetLink;
 
-  // Resources
-  pipelines: Pipeline[];
-  dataViews: Dashboard[];
-  dashboards: Dashboard[];
-  dataLakeMeasures: DataLakeMeasure[];
-  dataSources: SpDataStream[];
-  adapters: AdapterDescriptionUnion[];
-  files: FileMetadata[];
 
-  allResources: any[] = [];
   currentResource: any;
 
   selectedLinkType: AssetLinkType;
 
   constructor(private dialogRef: DialogRef<EditAssetLinkDialogComponent>,
-              private genericStorageService: GenericStorageService,
-              private pipelineService: PipelineService,
-              private dataViewService: DataViewDataExplorerService,
-              private dashboardService: DashboardService,
-              private dataLakeService: DatalakeRestService,
-              private pipelineElementService: PipelineElementService,
-              private adapterService: AdapterService,
-              private filesService: FilesService) {
+              protected genericStorageService: GenericStorageService,
+              protected pipelineService: PipelineService,
+              protected dataViewService: DataViewDataExplorerService,
+              protected dashboardService: DashboardService,
+              protected dataLakeService: DatalakeRestService,
+              protected pipelineElementService: PipelineElementService,
+              protected adapterService: AdapterService,
+              protected filesService: FilesService) {
+    super(
+      genericStorageService,
+      pipelineService,
+      dataViewService,
+      dashboardService,
+      dataLakeService,
+      pipelineElementService,
+      adapterService,
+      filesService);
   }
 
   ngOnInit(): void {
-    this.getAllResources();
+    super.onInit();
     this.clonedAssetLink = {...this.assetLink};
     this.selectedLinkType = this.getCurrAssetLinkType();
   }
@@ -101,39 +102,6 @@ export class EditAssetLinkDialogComponent implements OnInit {
     this.dialogRef.close();
   }
 
-  getAllResources() {
-    zip(
-      this.pipelineService.getOwnPipelines(),
-      this.dataViewService.getDataViews(),
-      this.dashboardService.getDashboards(),
-      this.pipelineElementService.getDataStreams(),
-      this.dataLakeService.getAllMeasurementSeries(),
-      this.filesService.getFileMetadata(),
-      this.adapterService.getAdapters()).subscribe(response => {
-      this.pipelines = response[0];
-      this.dataViews = response[1];
-      this.dashboards = response[2];
-      this.dataSources = response[3];
-      this.dataLakeMeasures = response[4];
-      this.files = response[5];
-      this.adapters = response[6];
-
-      this.allResources = [
-        ...this.pipelines,
-        ...this.dataViews,
-        ...this.dashboards,
-        ...this.dataSources,
-        ...this.dataLakeMeasures,
-        ...this.files,
-        ...this.adapters
-      ];
-      if (!this.createMode) {
-        this.currentResource = this.allResources.find(r => r._id === this.clonedAssetLink.resourceId ||
-          r.elementId === this.clonedAssetLink.resourceId);
-      }
-    });
-  }
-
   onLinkTypeChanged(event: MatSelectChange): void {
     this.selectedLinkType = event.value;
     const linkType = this.assetLinkTypes.find(a => a.linkType === this.selectedLinkType.linkType);
@@ -149,4 +117,11 @@ export class EditAssetLinkDialogComponent implements OnInit {
     this.currentResource = currentResource;
   }
 
+  afterResourcesLoaded(): void {
+    if (!this.createMode) {
+      this.currentResource = this.allResources.find(r => r._id === this.clonedAssetLink.resourceId ||
+        r.elementId === this.clonedAssetLink.resourceId);
+    }
+  }
+
 }
diff --git a/ui/src/app/assets/dialog/manage-asset-links/manage-asset-links-dialog.component.html b/ui/src/app/assets/dialog/manage-asset-links/manage-asset-links-dialog.component.html
new file mode 100644
index 000000000..eba2f811f
--- /dev/null
+++ b/ui/src/app/assets/dialog/manage-asset-links/manage-asset-links-dialog.component.html
@@ -0,0 +1,216 @@
+<!--
+~ 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 class="sp-dialog-container">
+    <div class="sp-dialog-content p-15">
+        <div fxFlex="100" fxLayout="column" *ngIf="clonedAssetLinks">
+            <div fxLayout="column" class="link-configuration">
+                <div fxLayout="row" fxLayoutAlign="start center" class="mb-10" fxFlex="100">
+                    <div fxLayout="row" fxLayoutAlign="start center">
+                        <span class="general-options-header mb-0">Adapters</span>
+                    </div>
+                    <div fxLayout="row" fxLayoutAlign="end center" fxFlex>
+                        <button mat-button mat-raised-button color="accent" class="small-button"
+                                (click)="selectAll(adapters, elementIdFunction, nameFunction, 'adapter')"
+                                style="margin-right:5px;margin-left:15px;">
+                            <span>Select All</span>
+                        </button>
+                        <button mat-button mat-raised-button class="mat-basic small-button"
+                                (click)="deselectAll(adapters, elementIdFunction)"
+                                style="margin-right:10px;margin-left:5px;">
+                            <span>Deselect All</span>
+                        </button>
+                    </div>
+                </div>
+                <div *ngFor="let element of adapters" fxLayout="row">
+                    <mat-checkbox color="accent"
+                                  [checked]="linkSelected(element.elementId)"
+                                  (change)="selectLink($event.checked, element.elementId, element.name, 'adapter')">
+                        {{element.name}}
+                    </mat-checkbox>
+                </div>
+            </div>
+            <div fxLayout="column" class="link-configuration">
+                <div fxLayout="row" fxLayoutAlign="start center" class="mb-10">
+                    <div fxLayout="row" fxLayoutAlign="start center">
+                        <span class="general-options-header mb-0">Dasboards</span>
+                    </div>
+                    <div fxLayout="row" fxLayoutAlign="end center" fxFlex>
+                        <button mat-button mat-raised-button color="accent" class="small-button"
+                                (click)="selectAll(dashboards, idFunction, nameFunction, 'dashboard')"
+                                style="margin-right:5px;margin-left:15px;">
+                            <span>Select All</span>
+                        </button>
+                        <button mat-button mat-raised-button class="mat-basic small-button"
+                                (click)="deselectAll(dashboards, idFunction)"
+                                style="margin-right:10px;margin-left:5px;">
+                            <span>Deselect All</span>
+                        </button>
+                    </div>
+                </div>
+                <div *ngFor="let element of dashboards" fxLayout="row">
+                    <mat-checkbox color="accent"
+                                  [checked]="linkSelected(element._id)"
+                                  (change)="selectLink($event.checked, element._id, element.name, 'dashboard')">
+                        {{element.name}}
+                    </mat-checkbox>
+                </div>
+            </div>
+            <div fxLayout="column" class="link-configuration">
+                <div fxLayout="row" fxLayoutAlign="start center" class="mb-10">
+                    <div fxLayout="row" fxLayoutAlign="start center">
+                        <span class="general-options-header mb-0">Data Lake Storage</span>
+                    </div>
+                    <div fxLayout="row" fxLayoutAlign="end center" fxFlex>
+                        <button mat-button mat-raised-button color="accent" class="small-button"
+                                (click)="selectAll(dataLakeMeasures, elementIdFunction, measureNameFunction, 'measurement')"
+                                style="margin-right:5px;margin-left:15px;">
+                            <span>Select All</span>
+                        </button>
+                        <button mat-button mat-raised-button class="mat-basic small-button"
+                                (click)="deselectAll(dataLakeMeasures, elementIdFunction)"
+                                style="margin-right:10px;margin-left:5px;">
+                            <span>Deselect All</span>
+                        </button>
+                    </div>
+                </div>
+                <div *ngFor="let element of dataLakeMeasures" fxLayout="row">
+                    <mat-checkbox color="accent"
+                                  [checked]="linkSelected(element.elementId)"
+                                  (change)="selectLink($event.checked, element.elementId, element.measureName, 'measurement')">
+                        {{element.measureName}}
+                    </mat-checkbox>
+                </div>
+            </div>
+            <div fxLayout="column" class="link-configuration">
+                <div fxLayout="row" fxLayoutAlign="start center" class="mb-10">
+                    <div fxLayout="row" fxLayoutAlign="start center">
+                        <span class="general-options-header mb-0">Data Sources</span>
+                    </div>
+                    <div fxLayout="row" fxLayoutAlign="end center" fxFlex>
+                        <button mat-button mat-raised-button color="accent" class="small-button"
+                                (click)="selectAll(dataSources, elementIdFunction, nameFunction, 'data-source')"
+                                style="margin-right:5px;margin-left:15px;">
+                            <span>Select All</span>
+                        </button>
+                        <button mat-button mat-raised-button class="mat-basic small-button"
+                                (click)="deselectAll(dataSources, elementIdFunction)"
+                                style="margin-right:10px;margin-left:5px;">
+                            <span>Deselect All</span>
+                        </button>
+                    </div>
+                </div>
+                <div *ngFor="let source of dataSources" fxLayout="row">
+                    <mat-checkbox color="accent"
+                                  [checked]="linkSelected(source.elementId)"
+                                  (change)="selectLink($event.checked, source.elementId, source.name, 'data-source')">
+                        {{source.name}}
+                    </mat-checkbox>
+                </div>
+            </div>
+            <div fxLayout="column" class="link-configuration">
+                <div fxLayout="row" fxLayoutAlign="start center" class="mb-10">
+                    <div fxLayout="row" fxLayoutAlign="start center">
+                        <span class="general-options-header mb-0">Data Views</span>
+                    </div>
+                    <div fxLayout="row" fxLayoutAlign="end center" fxFlex>
+                        <button mat-button mat-raised-button color="accent" class="small-button"
+                                (click)="selectAll(dataViews, idFunction, nameFunction, 'data-view')"
+                                style="margin-right:5px;margin-left:15px;">
+                            <span>Select All</span>
+                        </button>
+                        <button mat-button mat-raised-button class="mat-basic small-button"
+                                (click)="deselectAll(dataViews, idFunction)"
+                                style="margin-right:10px;margin-left:5px;">
+                            <span>Deselect All</span>
+                        </button>
+                    </div>
+                </div>
+                <div *ngFor="let element of dataViews" fxLayout="row">
+                    <mat-checkbox color="accent"
+                                  [checked]="linkSelected(element._id)"
+                                  (change)="selectLink($event.checked, element._id, element.name, 'data-view')">
+                        {{element.name}}
+                    </mat-checkbox>
+                </div>
+            </div>
+            <div fxLayout="column" class="link-configuration">
+                <div fxLayout="row" fxLayoutAlign="start center" class="mb-10">
+                    <div fxLayout="row" fxLayoutAlign="start center">
+                        <span class="general-options-header mb-0">Files</span>
+                    </div>
+                    <div fxLayout="row" fxLayoutAlign="end center" fxFlex>
+                        <button mat-button mat-raised-button color="accent" class="small-button"
+                                (click)="selectAll(files, fileIdFunction, filenameFunction, 'file')"
+                                style="margin-right:5px;margin-left:15px;">
+                            <span>Select All</span>
+                        </button>
+                        <button mat-button mat-raised-button class="mat-basic small-button"
+                                (click)="deselectAll(files, fileIdFunction)"
+                                style="margin-right:10px;margin-left:5px;">
+                            <span>Deselect All</span>
+                        </button>
+                    </div>
+                </div>
+                <div *ngFor="let element of files" fxLayout="row">
+                    <mat-checkbox color="accent"
+                                  [checked]="linkSelected(element.fileId)"
+                                  (change)="selectLink($event.checked, element.fileId, element.originalFilename, 'file')">
+                        {{element.originalFilename}}
+                    </mat-checkbox>
+                </div>
+            </div>
+            <div fxLayout="column" class="link-configuration">
+                <div fxLayout="row" fxLayoutAlign="start center" class="mb-10">
+                    <div fxLayout="row" fxLayoutAlign="start center">
+                        <span class="general-options-header mb-0">Pipelines</span>
+                    </div>
+                    <div fxLayout="row" fxLayoutAlign="end center" fxFlex>
+                        <button mat-button mat-raised-button color="accent" class="small-button"
+                                (click)="selectAll(pipelines, idFunction, nameFunction, 'pipeline')"
+                                style="margin-right:5px;margin-left:15px;">
+                            <span>Select All</span>
+                        </button>
+                        <button mat-button mat-raised-button class="mat-basic small-button"
+                                (click)="deselectAll(pipelines, idFunction)"
+                                style="margin-right:10px;margin-left:5px;">
+                            <span>Deselect All</span>
+                        </button>
+                    </div>
+                </div>
+                <div *ngFor="let pipeline of pipelines" fxLayout="row">
+                    <mat-checkbox color="accent" [checked]="linkSelected(pipeline._id)"
+                                  (change)="selectLink($event.checked, pipeline._id, pipeline.name, 'pipeline')">{{pipeline.name}}</mat-checkbox>
+                </div>
+            </div>
+        </div>
+    </div>
+    <mat-divider></mat-divider>
+    <div class="sp-dialog-actions">
+        <button mat-button
+                mat-raised-button
+                color="accent"
+                (click)="store()" style="margin-right:10px;">
+            Update links
+        </button>
+        <button mat-button mat-raised-button class="mat-basic" (click)="cancel()" style="margin-right:10px;">
+            Cancel
+        </button>
+    </div>
+</div>
diff --git a/ui/src/app/assets/dialog/manage-asset-links/manage-asset-links-dialog.component.scss b/ui/src/app/assets/dialog/manage-asset-links/manage-asset-links-dialog.component.scss
new file mode 100644
index 000000000..c7debbd6e
--- /dev/null
+++ b/ui/src/app/assets/dialog/manage-asset-links/manage-asset-links-dialog.component.scss
@@ -0,0 +1,31 @@
+/*
+ * 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 'src/scss/sp/sp-dialog';
+
+.link-configuration {
+  padding: 10px;
+  width:100%;
+  margin:5px;
+  background: var(--color-bg-1);
+  border: 1px solid var(--color-bg-3);
+}
+
+.mb-0 {
+  margin-bottom: 0;
+}
diff --git a/ui/src/app/assets/dialog/manage-asset-links/manage-asset-links-dialog.component.ts b/ui/src/app/assets/dialog/manage-asset-links/manage-asset-links-dialog.component.ts
new file mode 100644
index 000000000..320bf835a
--- /dev/null
+++ b/ui/src/app/assets/dialog/manage-asset-links/manage-asset-links-dialog.component.ts
@@ -0,0 +1,159 @@
+/*
+ * 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 { Component, Input, OnInit } from '@angular/core';
+import { DialogRef } from '@streampipes/shared-ui';
+import {
+  AdapterDescriptionUnion,
+  SpDataStream,
+  AdapterService,
+  AssetLink,
+  AssetLinkType,
+  Dashboard,
+  DashboardService,
+  DataLakeMeasure, DatalakeRestService,
+  DataViewDataExplorerService,
+  GenericStorageService,
+  Pipeline,
+  PipelineService,
+  PipelineElementService, FileMetadata, FilesService
+} from '@streampipes/platform-services';
+import { FormGroup } from '@angular/forms';
+import { zip } from 'rxjs';
+import { MatSelectChange } from '@angular/material/select';
+import { BaseAssetLinksDirective } from '../base-asset-links.directive';
+
+@Component({
+  selector: 'sp-manage-asset-links-dialog-component',
+  templateUrl: './manage-asset-links-dialog.component.html',
+  styleUrls: ['./manage-asset-links-dialog.component.scss']
+})
+export class SpManageAssetLinksDialogComponent extends BaseAssetLinksDirective implements OnInit {
+
+  @Input()
+  assetLinks: AssetLink[];
+
+  @Input()
+  assetLinkTypes: AssetLinkType[];
+
+  clonedAssetLinks: AssetLink[] = [];
+
+  idFunction = (el) => el._id;
+  elementIdFunction = (el) => el.elementId;
+  fileIdFunction = (el) => el.fileId;
+  nameFunction = (el) => el.name;
+  filenameFunction = (el) => el.originalFilename;
+  measureNameFunction = (el) => el.measureName;
+
+
+  constructor(private dialogRef: DialogRef<SpManageAssetLinksDialogComponent>,
+              protected genericStorageService: GenericStorageService,
+              protected pipelineService: PipelineService,
+              protected dataViewService: DataViewDataExplorerService,
+              protected dashboardService: DashboardService,
+              protected dataLakeService: DatalakeRestService,
+              protected pipelineElementService: PipelineElementService,
+              protected adapterService: AdapterService,
+              protected filesService: FilesService) {
+    super(
+      genericStorageService,
+      pipelineService,
+      dataViewService,
+      dashboardService,
+      dataLakeService,
+      pipelineElementService,
+      adapterService,
+      filesService);
+  }
+
+  ngOnInit(): void {
+    super.onInit();
+    this.clonedAssetLinks = [
+      ...this.assetLinks.map(al => {
+        return {...al};
+      })
+    ];
+  }
+
+  cancel(): void {
+    this.dialogRef.close();
+  }
+
+  store(): void {
+    this.assetLinks = this.clonedAssetLinks;
+    this.dialogRef.close(this.assetLinks);
+  }
+
+  afterResourcesLoaded(): void {
+  }
+
+  linkSelected(resourceId: string): boolean {
+    return this.clonedAssetLinks.find(al => al.resourceId === resourceId) !== undefined;
+  }
+
+  selectLink(checked: boolean,
+             resourceId: string,
+             label: string,
+             assetLinkType: string): void {
+    if (checked) {
+      this.clonedAssetLinks.push(this.makeLink(resourceId, label, assetLinkType));
+    } else {
+      const index = this.clonedAssetLinks.findIndex(al => al.resourceId === resourceId);
+      this.clonedAssetLinks.splice(index, 1);
+    }
+  }
+
+  makeLink(resourceId: string,
+           label: string,
+           assetLinkType: string): AssetLink {
+
+    const linkType = this.assetLinkTypes.find(a => a.linkType === assetLinkType);
+    return {
+      linkLabel: label,
+      linkType: linkType.linkType,
+      editingDisabled: false,
+      queryHint: linkType.linkQueryHint,
+      navigationActive: linkType.navigationActive,
+      resourceId
+    };
+  }
+
+  selectAll(elements: any[],
+            idFunction: any,
+            nameFunction: any,
+            assetLinkType: string): void {
+    elements.forEach(el => {
+      const id = idFunction(el);
+      const elementName = nameFunction(el);
+      if (!this.linkSelected(id)) {
+        this.selectLink(true, id, elementName, assetLinkType);
+      }
+    });
+  }
+
+  deselectAll(elements: any[],
+              idFunction: any): void {
+    elements.forEach(el => {
+      const id = idFunction(el);
+      const index = this.clonedAssetLinks.findIndex(al => al.resourceId === id);
+      if (index > -1) {
+        this.clonedAssetLinks.splice(index, 1);
+      }
+    });
+  }
+}
diff --git a/ui/src/app/connect/components/new-adapter/schema-editor/event-schema/event-schema.component.ts b/ui/src/app/connect/components/new-adapter/schema-editor/event-schema/event-schema.component.ts
index 76d9bdaf1..b18a8e13c 100644
--- a/ui/src/app/connect/components/new-adapter/schema-editor/event-schema/event-schema.component.ts
+++ b/ui/src/app/connect/components/new-adapter/schema-editor/event-schema/event-schema.component.ts
@@ -146,6 +146,7 @@ export class EventSchemaComponent implements OnChanges {
     this.nodes = new Array<EventProperty>();
     this.nodes.push(this.eventSchema as unknown as EventProperty);
     this.validEventSchema = this.checkIfValid(this.eventSchema);
+    this.updatePreview();
   }
 
   public addNestedProperty(eventProperty?: EventPropertyNested): void {
@@ -213,10 +214,15 @@ export class EventSchemaComponent implements OnChanges {
     this.transformationRuleService.setOldEventSchema(this.oldEventSchema);
     this.transformationRuleService.setNewEventSchema(this.eventSchema);
     const ruleDescriptions = this.transformationRuleService.getTransformationRuleDescriptions();
-    this.restService.getAdapterEventPreview({rules: ruleDescriptions, inputData: this.eventPreview[0]}).subscribe(preview => {
-      this.desiredPreview = preview;
-      this.isPreviewEnabled = true;
-    });
+    if (this.eventPreview && this.eventPreview.length > 0) {
+      this.restService.getAdapterEventPreview({
+        rules: ruleDescriptions,
+        inputData: this.eventPreview[0]
+      }).subscribe(preview => {
+        this.desiredPreview = preview;
+        this.isPreviewEnabled = true;
+      });
+    }
   }
 
   ngOnChanges(changes: SimpleChanges) {
diff --git a/ui/src/app/core/components/toolbar/toolbar.component.html b/ui/src/app/core/components/toolbar/toolbar.component.html
index b64c19756..2767609f5 100644
--- a/ui/src/app/core/components/toolbar/toolbar.component.html
+++ b/ui/src/app/core/components/toolbar/toolbar.component.html
@@ -34,7 +34,7 @@
                 <button mat-button mat-icon-button class="md-icon-button button-margin-iconbar iconbar-size"
                         (click)="go('notifications')"
                         fxLayout fxLayoutAlign="center center"
-                        matTooltip="Notifications" matTooltipPosition="bottom">
+                        matTooltip="Notifications" matTooltipPosition="below">
                     <mat-icon [matBadge]="unreadNotificationCount"
                               matBadgeColor="accent"
                               matBadgePosition="below after"