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 2020/09/17 21:25:21 UTC

[incubator-streampipes] 01/03: [hotfix] Fix bug that caused pipeline element recommender to crash

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

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

commit 8daee400d31ad7bb61f22f3a400e4739bbb1de55
Author: Dominik Riemer <ri...@fzi.de>
AuthorDate: Thu Sep 17 23:20:52 2020 +0200

    [hotfix] Fix bug that caused pipeline element recommender to crash
---
 .../manager/matching/ConnectionValidator.java      |  4 +-
 .../manager/matching/InvocationGraphBuilder.java   |  3 +-
 .../matching/PipelineVerificationHandler.java      | 18 +++--
 .../manager/recommender/ElementRecommender.java    | 20 +++--
 .../pipeline-element-options.component.html        | 91 +++++++++++-----------
 .../pipeline-element-options.component.ts          | 21 +++--
 .../pipeline-element-recommendation.component.html |  4 +-
 .../pipeline-element-recommendation.component.ts   | 35 ++++-----
 .../components/pipeline/pipeline.component.ts      |  3 +-
 9 files changed, 106 insertions(+), 93 deletions(-)

diff --git a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/matching/ConnectionValidator.java b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/matching/ConnectionValidator.java
index a52126a..4e383c5 100644
--- a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/matching/ConnectionValidator.java
+++ b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/matching/ConnectionValidator.java
@@ -44,7 +44,7 @@ public class ConnectionValidator {
     this.verifier = new ElementVerification();
   }
 
-  public void validateConnection() throws InvalidConnectionException {
+  public List<InvocableStreamPipesEntity> validateConnection() throws InvalidConnectionException {
     boolean verified = true;
     InvocableStreamPipesEntity rightElement = rootPipelineElement;
     List<String> connectedTo = rootPipelineElement.getConnectedTo();
@@ -66,6 +66,8 @@ public class ConnectionValidator {
     if (!verified) {
       throw new InvalidConnectionException(verifier.getErrorLog());
     }
+
+    return invocationGraphs;
   }
 
   private DataProcessorInvocation findInvocationGraph(List<InvocableStreamPipesEntity> graphs, String domId) {
diff --git a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/matching/InvocationGraphBuilder.java b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/matching/InvocationGraphBuilder.java
index fc2547e..7a2ffc8 100644
--- a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/matching/InvocationGraphBuilder.java
+++ b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/matching/InvocationGraphBuilder.java
@@ -72,7 +72,7 @@ public class InvocationGraphBuilder {
             .getEventGrounding();
 
     if (source instanceof InvocableStreamPipesEntity) {
-      if (source instanceof DataProcessorInvocation) {
+      if (source instanceof DataProcessorInvocation && ((DataProcessorInvocation) source).isConfigured()) {
 
         DataProcessorInvocation dataProcessorInvocation = (DataProcessorInvocation) source;
         Tuple2<EventSchema, ? extends OutputStrategy> outputSettings;
@@ -190,4 +190,5 @@ public class InvocationGraphBuilder {
             .findFirst()
             .get();
   }
+
 }
\ No newline at end of file
diff --git a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/matching/PipelineVerificationHandler.java b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/matching/PipelineVerificationHandler.java
index bbdc6bd..ff80bdb 100644
--- a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/matching/PipelineVerificationHandler.java
+++ b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/matching/PipelineVerificationHandler.java
@@ -44,7 +44,7 @@ public class PipelineVerificationHandler {
 
   private final Pipeline pipeline;
   private final PipelineModificationMessage pipelineModificationMessage;
-  private final List<InvocableStreamPipesEntity> invocationGraphs;
+  private List<InvocableStreamPipesEntity> invocationGraphs;
   private final InvocableStreamPipesEntity rootPipelineElement;
 
   public PipelineVerificationHandler(Pipeline pipeline) throws NoSepaInPipelineException {
@@ -61,7 +61,8 @@ public class PipelineVerificationHandler {
    * @throws InvalidConnectionException if the connection is not considered valid
    */
   public PipelineVerificationHandler validateConnection() throws InvalidConnectionException {
-    new ConnectionValidator(pipeline, invocationGraphs, rootPipelineElement).validateConnection();
+    invocationGraphs = new ConnectionValidator(pipeline, invocationGraphs, rootPipelineElement)
+            .validateConnection();
     return this;
   }
 
@@ -182,13 +183,13 @@ public class PipelineVerificationHandler {
     return pipelineModificationMessage;
   }
 
+  public List<InvocableStreamPipesEntity> getInvocationGraphs() {
+    return this.invocationGraphs;
+  }
+
   public List<InvocableStreamPipesEntity> makeInvocationGraphs() {
-    if (onlyStreamAncestorsPresentInPipeline()) {
-      return new ArrayList<>();
-    } else {
-      PipelineGraph pipelineGraph = new PipelineGraphBuilder(pipeline).buildGraph();
-      return new InvocationGraphBuilder(pipelineGraph, null).buildGraphs();
-    }
+    PipelineGraph pipelineGraph = new PipelineGraphBuilder(pipeline).buildGraph();
+    return new InvocationGraphBuilder(pipelineGraph, null).buildGraphs();
   }
 
   private boolean onlyStreamAncestorsPresentInPipeline() {
@@ -198,4 +199,5 @@ public class PipelineVerificationHandler {
             .map(connectedTo -> TreeUtils.findSEPAElement(connectedTo, pipeline.getSepas(), pipeline.getStreams()))
             .allMatch(pe -> pe instanceof SpDataStream);
   }
+
 }
\ No newline at end of file
diff --git a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/recommender/ElementRecommender.java b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/recommender/ElementRecommender.java
index d409d77..60f2b34 100644
--- a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/recommender/ElementRecommender.java
+++ b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/recommender/ElementRecommender.java
@@ -20,7 +20,9 @@ package org.apache.streampipes.manager.recommender;
 
 import org.apache.streampipes.commons.exceptions.NoSepaInPipelineException;
 import org.apache.streampipes.commons.exceptions.NoSuitableSepasAvailableException;
-import org.apache.streampipes.manager.matching.PipelineVerificationHandler;
+import org.apache.streampipes.manager.data.PipelineGraph;
+import org.apache.streampipes.manager.data.PipelineGraphBuilder;
+import org.apache.streampipes.manager.matching.InvocationGraphBuilder;
 import org.apache.streampipes.manager.matching.v2.StreamMatch;
 import org.apache.streampipes.manager.storage.UserManagementService;
 import org.apache.streampipes.manager.util.PipelineVerificationUtils;
@@ -30,17 +32,19 @@ import org.apache.streampipes.model.base.InvocableStreamPipesEntity;
 import org.apache.streampipes.model.base.NamedStreamPipesEntity;
 import org.apache.streampipes.model.client.exception.InvalidConnectionException;
 import org.apache.streampipes.model.client.matching.MatchingResultMessage;
-import org.apache.streampipes.model.pipeline.Pipeline;
-import org.apache.streampipes.model.pipeline.PipelineElementRecommendation;
-import org.apache.streampipes.model.pipeline.PipelineElementRecommendationMessage;
 import org.apache.streampipes.model.graph.DataProcessorDescription;
 import org.apache.streampipes.model.graph.DataProcessorInvocation;
 import org.apache.streampipes.model.graph.DataSinkDescription;
 import org.apache.streampipes.model.graph.DataSinkInvocation;
+import org.apache.streampipes.model.pipeline.Pipeline;
+import org.apache.streampipes.model.pipeline.PipelineElementRecommendation;
+import org.apache.streampipes.model.pipeline.PipelineElementRecommendationMessage;
 import org.apache.streampipes.storage.api.INoSqlStorage;
 import org.apache.streampipes.storage.api.IPipelineElementDescriptionStorage;
 import org.apache.streampipes.storage.management.StorageDispatcher;
 import org.apache.streampipes.storage.management.StorageManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -49,6 +53,8 @@ import java.util.stream.Collectors;
 
 public class ElementRecommender {
 
+  private static final Logger LOG = LoggerFactory.getLogger(ElementRecommender.class);
+
   private Pipeline pipeline;
   private String email;
   private PipelineElementRecommendationMessage recommendationMessage;
@@ -205,9 +211,9 @@ public class ElementRecommender {
     } else if (rootNode instanceof DataSinkInvocation) {
       return Optional.empty();
     } else {
-      List<InvocableStreamPipesEntity> graphs = new PipelineVerificationHandler(pipeline)
-              .validateConnection()
-              .makeInvocationGraphs();
+      ((DataProcessorInvocation) rootNode).setConfigured(true);
+      PipelineGraph pipelineGraph = new PipelineGraphBuilder(pipeline).buildGraph();
+      List<InvocableStreamPipesEntity> graphs = new InvocationGraphBuilder(pipelineGraph, null).buildGraphs();
 
       Optional<InvocableStreamPipesEntity> rootElementWithOutputStream = graphs
               .stream()
diff --git a/ui/src/app/editor/components/pipeline-element-options/pipeline-element-options.component.html b/ui/src/app/editor/components/pipeline-element-options/pipeline-element-options.component.html
index b345465..976a382 100644
--- a/ui/src/app/editor/components/pipeline-element-options/pipeline-element-options.component.html
+++ b/ui/src/app/editor/components/pipeline-element-options/pipeline-element-options.component.html
@@ -15,55 +15,56 @@
   ~ limitations under the License.
   ~
   -->
-
-<div *ngIf="currentMouseOverElement==pipelineElement.payload.dom" class="sp-fade-options">
-    <span class="options-button customize-button" *ngIf="pipelineElement.type!='stream'" style="z-index:10">
-        <button mat-button mat-icon-button matTooltip="Configure Element"
-                [matTooltipPosition]="'above'"
-                (click)="customizeElement(pipelineElement)">
-            <i class="material-icons">settings</i>
+<div>
+    <div [ngStyle]="currentMouseOverElement==pipelineElement.payload.dom ? {opacity: 1} : {opacity: 0}" class="sp-fade-options">
+        <span class="options-button customize-button" *ngIf="pipelineElement.type!='stream'" style="z-index:10">
+            <button mat-button mat-icon-button matTooltip="Configure Element"
+                    [matTooltipPosition]="'above'"
+                    (click)="customizeElement(pipelineElement)">
+                <i class="material-icons">settings</i>
+                </button>
+        </span>
+        <span class="options-button customize-button"
+              *ngIf="pipelineElement.type=='stream' && isWildcardTopic()"
+              style="z-index:10">
+            <button mat-button mat-icon-button matTooltip="Configure Element" [matTooltipPosition]="'above'"
+                    (click)="openCustomizeStreamDialog()" [disabled]="!isRootElement()">
+                <i class="material-icons">settings</i>
             </button>
-    </span>
-    <span class="options-button customize-button"
-          *ngIf="pipelineElement.type=='stream' && isWildcardTopic()"
-          style="z-index:10">
-        <button mat-button mat-icon-button matTooltip="Configure Element" [matTooltipPosition]="'above'"
-                (click)="openCustomizeStreamDialog()" [disabled]="!isRootElement()">
-            <i class="material-icons">settings</i>
-        </button>
-    </span>
-    <span class="options-button delete-button" style="z-index:10">
-        <button mat-button mat-icon-button matTooltip="Delete Element" [matTooltipPosition]="'above'"
-                (click)="removeElement(pipelineElement)" [disabled]="!isRootElement()">
-            <i class="material-icons">clear</i>
-        </button>
-    </span>
-    <span class="options-button possible-button" *ngIf="pipelineElement.type!='action'" style="z-index:10">
-        <button mat-button mat-icon-button matTooltip="Compatible Elements" [matTooltipPosition]="'below'"
-                [disabled]="!possibleElements || possibleElements.length == 0 || !isConfigured()"
-                (click)="openPossibleElementsDialog()">
-            <i class="material-icons">visibility</i>
-    </button>
-    </span>
-    <span class="options-button recommended-button"
-          *ngIf="pipelineElement.type!='action' && (recommendationsAvailable) && recommendedElements.length > 0"
-          style="z-index:10">
-        <button mat-button mat-icon-button matTooltip="Recommended Elements" [matTooltipPosition]="'below'"
-                (click)="showRecommendations($event)"
-                [disabled]="!recommendationsAvailable || !isConfigured()">
-            <i class="material-icons">add</i>
+        </span>
+        <span class="options-button delete-button" style="z-index:10">
+            <button mat-button mat-icon-button matTooltip="Delete Element" [matTooltipPosition]="'above'"
+                    (click)="removeElement(pipelineElement)" [disabled]="!isRootElement()">
+                <i class="material-icons">clear</i>
+            </button>
+        </span>
+        <span class="options-button possible-button" *ngIf="pipelineElement.type!='action'" style="z-index:10">
+            <button mat-button mat-icon-button matTooltip="Compatible Elements" [matTooltipPosition]="'below'"
+                    [disabled]="!possibleElements || possibleElements.length == 0 || !isConfigured()"
+                    (click)="openPossibleElementsDialog()">
+                <i class="material-icons">visibility</i>
         </button>
-    </span>
-    <span class="options-button help-button" style="z-index:10">
-        <button matTooltip="Help" [matTooltipPosition]="'below'"
-                mat-button mat-icon-button (click)="openHelpDialog()">?
+        </span>
+        <span class="options-button recommended-button"
+              *ngIf="pipelineElement.type!='action' && (recommendationsAvailable) && recommendedElements.length > 0"
+              style="z-index:10">
+            <button mat-button mat-icon-button matTooltip="Recommended Elements" [matTooltipPosition]="'below'"
+                    (click)="showRecommendations($event)"
+                    [disabled]="!recommendationsAvailable || !isConfigured()">
+                <i class="material-icons">add</i>
             </button>
-    </span>
-    <div class="editor-pe-info" [ngClass]="'pe-info-' + pipelineElementCssType">
-        {{pipelineElement.payload.name}}
+        </span>
+        <span class="options-button help-button" style="z-index:10">
+            <button matTooltip="Help" [matTooltipPosition]="'below'"
+                    mat-button mat-icon-button (click)="openHelpDialog()">?
+                </button>
+        </span>
+        <div class="editor-pe-info" [ngClass]="'pe-info-' + pipelineElementCssType">
+            {{pipelineElement.payload.name}}
+        </div>
     </div>
-</div>
-<pipeline-element-recommendation [rawPipelineModel]="rawPipelineModel"
+    <pipeline-element-recommendation [rawPipelineModel]="rawPipelineModel"
                                  [pipelineElementDomId]="pipelineElement.payload.dom"
                                  [recommendedElements]="recommendedElements"
                                  [recommendationsShown]="recommendationsShown" *ngIf="recommendationsAvailable"></pipeline-element-recommendation>
+</div>
\ No newline at end of file
diff --git a/ui/src/app/editor/components/pipeline-element-options/pipeline-element-options.component.ts b/ui/src/app/editor/components/pipeline-element-options/pipeline-element-options.component.ts
index 6a84660..6d20d18 100644
--- a/ui/src/app/editor/components/pipeline-element-options/pipeline-element-options.component.ts
+++ b/ui/src/app/editor/components/pipeline-element-options/pipeline-element-options.component.ts
@@ -20,7 +20,7 @@ import {JsplumbBridge} from "../../services/jsplumb-bridge.service";
 import {JsplumbService} from "../../services/jsplumb.service";
 import {PipelineValidationService} from "../../services/pipeline-validation.service";
 import {RestApi} from "../../../services/rest-api.service";
-import {Component, EventEmitter, Input, OnInit, Output} from "@angular/core";
+import {Component, EventEmitter, Input, OnDestroy, OnInit, Output} from "@angular/core";
 import {PipelineElementRecommendationService} from "../../services/pipeline-element-recommendation.service";
 import {ObjectProvider} from "../../services/object-provider.service";
 import {
@@ -35,18 +35,19 @@ import {DialogService} from "../../../core-ui/dialog/base-dialog/base-dialog.ser
 import {CompatibleElementsComponent} from "../../dialog/compatible-elements/compatible-elements.component";
 import {Tuple2} from "../../../core-model/base/Tuple2";
 import { cloneDeep } from "lodash";
+import {Observable, Subscription} from "rxjs";
 
 @Component({
   selector: 'pipeline-element-options',
   templateUrl: './pipeline-element-options.component.html',
   styleUrls: ['./pipeline-element-options.component.css']
 })
-export class PipelineElementOptionsComponent implements OnInit{
+export class PipelineElementOptionsComponent implements OnInit, OnDestroy {
 
-  recommendationsAvailable: any;
+  recommendationsAvailable: any = false;
   possibleElements: PipelineElementUnion[];
   recommendedElements: PipelineElementUnion[];
-  recommendationsShown: any;
+  recommendationsShown: any = false;
   pipelineElementCssType: string;
 
   @Input()
@@ -76,6 +77,8 @@ export class PipelineElementOptionsComponent implements OnInit{
   @Output()
   customize: EventEmitter<Tuple2<Boolean, PipelineElementConfig>> = new EventEmitter<Tuple2<Boolean, PipelineElementConfig>>();
 
+  pipelineElementConfiguredObservable: Subscription;
+
   constructor(private ObjectProvider: ObjectProvider,
               private PipelineElementRecommendationService: PipelineElementRecommendationService,
               private DialogService: DialogService,
@@ -91,7 +94,7 @@ export class PipelineElementOptionsComponent implements OnInit{
   }
 
   ngOnInit() {
-    this.EditorService.pipelineElementConfigured$.subscribe(pipelineElementDomId => {
+    this.pipelineElementConfiguredObservable = this.EditorService.pipelineElementConfigured$.subscribe(pipelineElementDomId => {
       this.pipelineElement.settings.openCustomize = false;
       this.RestApi.updateCachedPipeline(this.rawPipelineModel);
       if (pipelineElementDomId === this.pipelineElement.payload.dom) {
@@ -134,8 +137,8 @@ export class PipelineElementOptionsComponent implements OnInit{
     var currentPipeline = this.ObjectProvider.makePipeline(clonedModel);
     this.EditorService.recommendPipelineElement(currentPipeline).subscribe((result) => {
       if (result.success) {
-        this.possibleElements = this.PipelineElementRecommendationService.collectPossibleElements(this.allElements, result.possibleElements);
-        this.recommendedElements = this.PipelineElementRecommendationService.populateRecommendedList(this.allElements, result.recommendedElements);
+        this.possibleElements = cloneDeep(this.PipelineElementRecommendationService.collectPossibleElements(this.allElements, result.possibleElements));
+        this.recommendedElements = cloneDeep(this.PipelineElementRecommendationService.populateRecommendedList(this.allElements, result.recommendedElements));
         this.recommendationsAvailable = true;
       }
     });
@@ -180,4 +183,8 @@ export class PipelineElementOptionsComponent implements OnInit{
         .transportProtocols[0]
         .topicDefinition instanceof WildcardTopicDefinition;
   }
+
+  ngOnDestroy(): void {
+    this.pipelineElementConfiguredObservable.unsubscribe();
+  }
 }
\ No newline at end of file
diff --git a/ui/src/app/editor/components/pipeline-element-recommendation/pipeline-element-recommendation.component.html b/ui/src/app/editor/components/pipeline-element-recommendation/pipeline-element-recommendation.component.html
index fe13f3c..09748fc 100644
--- a/ui/src/app/editor/components/pipeline-element-recommendation/pipeline-element-recommendation.component.html
+++ b/ui/src/app/editor/components/pipeline-element-recommendation/pipeline-element-recommendation.component.html
@@ -22,8 +22,8 @@
             [style]="recommendedElement.layoutSettings.skewStyle">
             <a [style]="recommendedElement.layoutSettings.unskewStyle" [ngClass]="recommendedElement.layoutSettings.type" (click)="create(recommendedElement)" *ngIf="recommendedElement.name">
             </a>
-            <div (click)="create(recommendedElement)" [style]="recommendedElement.layoutSettings.unskewStyleLabel | safeCss"
-                 class="{{recommendedElement.layoutSettings.type}}">
+            <div (click)="create(recommendedElement)" [style]="recommendedElement.layoutSettings.unskewStyleLabel"
+                 [ngClass]="recommendedElement.layoutSettings.type">
                 <pipeline-element [iconSize]="'small'" [pipelineElement]="recommendedElement" *ngIf="recommendedElement.name">
                 </pipeline-element>
             </div>
diff --git a/ui/src/app/editor/components/pipeline-element-recommendation/pipeline-element-recommendation.component.ts b/ui/src/app/editor/components/pipeline-element-recommendation/pipeline-element-recommendation.component.ts
index 2d6cf8a..61ca035 100644
--- a/ui/src/app/editor/components/pipeline-element-recommendation/pipeline-element-recommendation.component.ts
+++ b/ui/src/app/editor/components/pipeline-element-recommendation/pipeline-element-recommendation.component.ts
@@ -17,7 +17,7 @@
  */
 
 import {JsplumbService} from "../../services/jsplumb.service";
-import {Component, Input, OnInit} from "@angular/core";
+import {AfterViewInit, ChangeDetectorRef, Component, Input, OnInit} from "@angular/core";
 import {PipelineElementConfig} from "../../model/editor.model";
 import {DataProcessorInvocation} from "../../../core-model/gen/streampipes-model";
 import {SafeCss} from "../../utils/style-sanitizer";
@@ -27,7 +27,7 @@ import {SafeCss} from "../../utils/style-sanitizer";
   templateUrl: './pipeline-element-recommendation.component.html',
   styleUrls: ['./pipeline-element-recommendation.component.scss']
 })
-export class PipelineElementRecommendationComponent implements OnInit {
+export class PipelineElementRecommendationComponent implements OnInit, AfterViewInit {
 
   @Input()
   recommendationsShown: boolean;
@@ -38,7 +38,8 @@ export class PipelineElementRecommendationComponent implements OnInit {
   @Input()
   pipelineElementDomId: string;
 
-  _recommendedElements: any;
+  @Input()
+  recommendedElements: any;
 
   recommendationsPrepared: boolean = false;
 
@@ -48,11 +49,16 @@ export class PipelineElementRecommendationComponent implements OnInit {
   }
 
   ngOnInit() {
+    this.fillRemainingItems();
+    this.prepareStyles(this.recommendedElements);
+    this.recommendationsPrepared = true;
+  }
+
+  ngAfterViewInit(): void {
 
   }
 
   prepareStyles(recommendedElements) {
-    this.fillRemainingItems(recommendedElements);
     recommendedElements.forEach((element, index) => {
       this.setLayoutSettings(element, index, recommendedElements);
     });
@@ -130,25 +136,12 @@ export class PipelineElementRecommendationComponent implements OnInit {
     return (360 / recommendedElements.length);
   }
 
-  fillRemainingItems(recommendedElements) {
-    if (recommendedElements.length < 6) {
-      for (var i = recommendedElements.length; i < 6; i++) {
+  fillRemainingItems() {
+    if (this.recommendedElements.length < 6) {
+      for (var i = this.recommendedElements.length; i < 6; i++) {
         let element = {fakeElement: true, weight: 0};
-        //this.setLayoutSettings(element, i);
-        recommendedElements.push(element);
+        this.recommendedElements.push(element);
       }
     }
   }
-
-  get recommendedElements() {
-    return this._recommendedElements;
-  }
-
-  @Input()
-  set recommendedElements(recommendedElements: any) {
-    this.recommendationsPrepared = false;
-    this.prepareStyles(recommendedElements);
-    this._recommendedElements = recommendedElements;
-    this.recommendationsPrepared = true;
-  }
 }
\ No newline at end of file
diff --git a/ui/src/app/editor/components/pipeline/pipeline.component.ts b/ui/src/app/editor/components/pipeline/pipeline.component.ts
index 6c3d013..dd83082 100644
--- a/ui/src/app/editor/components/pipeline/pipeline.component.ts
+++ b/ui/src/app/editor/components/pipeline/pipeline.component.ts
@@ -312,9 +312,9 @@ export class PipelineComponent implements OnInit {
                 if ((payload.staticProperties && payload.staticProperties.length > 0) || this.isCustomOutput(pe)) {
                   this.showCustomizeDialog({a: false, b: pe});
                 } else {
-                  this.announceConfiguredElement(pe);
                   (pe.payload as InvocablePipelineElementUnion).configured = true;
                   pe.settings.completed = true;
+                  this.announceConfiguredElement(pe);
                 }
               }
             }, status => {
@@ -410,6 +410,7 @@ export class PipelineComponent implements OnInit {
     dialogRef.afterClosed().subscribe(c => {
       if (c) {
         pipelineElementInfo.b.settings.openCustomize = false;
+        (pipelineElementInfo.b.payload as InvocablePipelineElementUnion).configured = true;
         this.JsplumbService.activateEndpoint(pipelineElementInfo.b.payload.dom, pipelineElementInfo.b.settings.completed);
         this.JsplumbBridge.getSourceEndpoint(pipelineElementInfo.b.payload.dom).setType("token");
         this.triggerPipelineCacheUpdate();