This is an automated email from the ASF dual-hosted git repository.
riemer 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 3472bcb25b feat(#3105): Allow modification of running pipelines (#3106)
3472bcb25b is described below
commit 3472bcb25bd9377799470d17b11a0a689489e430
Author: Dominik Riemer <[email protected]>
AuthorDate: Thu Aug 8 12:39:49 2024 +0200
feat(#3105): Allow modification of running pipelines (#3106)
* feat(#3105): Allow modification of running pipelines
* Remove unused export
* Add e2e test
---
.../rest/impl/PipelineCanvasMetadataResource.java | 2 +
ui/cypress/support/utils/PipelineUtils.ts | 31 +-
.../pipeline/updatePipelineTest.smoke.spec.ts | 67 +++++
.../authentication-configuration.component.ts | 1 -
.../edit-schema-transformation.component.ts | 1 -
ui/src/app/core-ui/core-ui.module.ts | 12 +
.../loading-indicator.component.html | 22 ++
.../loading-indicator.component.scss} | 28 +-
.../loading-indicator.component.ts} | 35 +--
.../multi-step-status-indicator.component.html | 47 ++++
.../multi-step-status-indicator.component.scss} | 30 +-
.../multi-step-status-indicator.component.ts} | 37 +--
.../multi-step-status-indicator.model.ts} | 32 +--
.../pipeline-operation-status.component.html | 56 ++++
.../pipeline-operation-status.component.scss} | 29 +-
.../pipeline-operation-status.component.ts} | 36 +--
.../pipeline-started-status.component.html | 57 ++--
.../pipeline-started-status.component.ts | 3 +
.../status-indicator.component.html | 23 ++
.../status-indicator.component.scss} | 30 +-
.../status-indicator.component.ts} | 37 +--
.../pipeline-assembly.component.ts | 177 ++++++------
.../save-pipeline-settings.component.html | 86 ++++++
.../save-pipeline-settings.component.scss} | 29 --
.../save-pipeline-settings.component.ts | 82 ++++++
.../save-pipeline/save-pipeline.component.html | 164 ++++-------
.../save-pipeline/save-pipeline.component.scss | 9 -
.../save-pipeline/save-pipeline.component.ts | 311 ++++++++++++++-------
ui/src/app/editor/editor.component.html | 30 +-
ui/src/app/editor/editor.component.ts | 19 +-
ui/src/app/editor/editor.module.ts | 2 +
ui/src/app/editor/model/editor.model.ts | 23 +-
.../pipeline-overview.component.html | 5 +-
.../pipeline-overview.component.ts | 13 -
.../pipeline-status-dialog.component.ts | 5 -
ui/src/app/pipelines/pipelines.component.html | 1 -
ui/src/app/pipelines/pipelines.component.ts | 36 +--
.../tour/create-pipeline-tour.constants.ts | 2 +-
ui/src/app/services/tour/shepherd.service.ts | 8 -
ui/src/scss/sp/main.scss | 4 +-
ui/src/scss/sp/shepherd-new.scss | 5 +-
41 files changed, 910 insertions(+), 717 deletions(-)
diff --git
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineCanvasMetadataResource.java
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineCanvasMetadataResource.java
index 20a6e35a0a..5ba0edcbbb 100644
---
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineCanvasMetadataResource.java
+++
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineCanvasMetadataResource.java
@@ -95,6 +95,8 @@ public class PipelineCanvasMetadataResource extends
AbstractRestResource {
public ResponseEntity<Void>
updatePipelineCanvasMetadata(@PathVariable("canvasId") String pipelineCanvasId,
@RequestBody
PipelineCanvasMetadata pipelineCanvasMetadata) {
try {
+ var existing =
getPipelineCanvasMetadataStorage().getElementById(pipelineCanvasMetadata.getId());
+ pipelineCanvasMetadata.setRev(existing.getRev());
getPipelineCanvasMetadataStorage().updateElement(pipelineCanvasMetadata);
} catch (IllegalArgumentException e) {
getPipelineCanvasMetadataStorage().persist(pipelineCanvasMetadata);
diff --git a/ui/cypress/support/utils/PipelineUtils.ts
b/ui/cypress/support/utils/PipelineUtils.ts
index cb132e9347..846dd478ca 100644
--- a/ui/cypress/support/utils/PipelineUtils.ts
+++ b/ui/cypress/support/utils/PipelineUtils.ts
@@ -32,6 +32,10 @@ export class PipelineUtils {
PipelineUtils.startPipeline(pipelineInput);
}
+ public static editPipeline() {
+ cy.dataCy('modify-pipeline-btn').first().click();
+ }
+
public static goToPipelines() {
cy.visit('#/pipelines');
}
@@ -99,16 +103,31 @@ export class PipelineUtils {
cy.dataCy('sp-element-configuration-save').click();
}
- private static startPipeline(pipelineInput: PipelineInput) {
+ public static startPipeline(pipelineInput?: PipelineInput) {
// Save and start pipeline
cy.dataCy('sp-editor-save-pipeline').click();
- cy.dataCy('sp-editor-pipeline-name').type(pipelineInput.pipelineName);
- cy.dataCy('sp-editor-checkbox-start-immediately').children().click();
- cy.dataCy('sp-editor-save').click();
- cy.dataCy('sp-pipeline-started-dialog', { timeout: 15000 }).should(
+ if (pipelineInput) {
+ cy.dataCy('sp-editor-pipeline-name').type(
+ pipelineInput.pipelineName,
+ );
+ }
+ PipelineUtils.finalizePipelineStart();
+ }
+
+ public static clonePipeline(newPipelineName: string) {
+ cy.dataCy('pipeline-update-mode-clone').children().click();
+ cy.dataCy('sp-editor-pipeline-name').type(newPipelineName);
+ }
+
+ public static finalizePipelineStart() {
+
cy.dataCy('sp-editor-checkbox-navigate-to-overview').children().click();
+ cy.dataCy('sp-editor-apply').click();
+ cy.dataCy('sp-pipeline-started-success', { timeout: 15000 }).should(
'be.visible',
);
- cy.dataCy('sp-pipeline-dialog-close', { timeout: 15000 }).click();
+ cy.dataCy('sp-navigate-to-pipeline-overview', {
+ timeout: 15000,
+ }).click();
}
public static checkAmountOfPipelinesPipeline(amount: number) {
diff --git a/ui/cypress/tests/pipeline/updatePipelineTest.smoke.spec.ts
b/ui/cypress/tests/pipeline/updatePipelineTest.smoke.spec.ts
new file mode 100644
index 0000000000..b2e69bd0b4
--- /dev/null
+++ b/ui/cypress/tests/pipeline/updatePipelineTest.smoke.spec.ts
@@ -0,0 +1,67 @@
+/*
+ * 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 { PipelineUtils } from '../../support/utils/PipelineUtils';
+import { PipelineElementBuilder } from
'../../support/builder/PipelineElementBuilder';
+import { PipelineBuilder } from '../../support/builder/PipelineBuilder';
+
+const adapterName = 'simulator';
+
+describe('Test update of running pipeline', () => {
+ beforeEach('Setup Test', () => {
+ cy.initStreamPipesTest();
+ ConnectUtils.addMachineDataSimulator(adapterName);
+ });
+
+ it('Perform Test', () => {
+ const pipelineInput = PipelineBuilder.create('Pipeline Test')
+ .addSource(adapterName)
+ .addProcessingElement(
+ PipelineElementBuilder.create('field_renamer')
+ .addInput('drop-down', 'convert-property', 'timestamp')
+ .addInput('input', 'field-name', 't')
+ .build(),
+ )
+ .addSink(
+ PipelineElementBuilder.create('data_lake')
+ .addInput('input', 'db_measurement', 'demo')
+ .build(),
+ )
+ .build();
+
+ PipelineUtils.addPipeline(pipelineInput);
+ PipelineUtils.editPipeline();
+ cy.wait(1000);
+ PipelineUtils.startPipeline();
+ cy.dataCy('modify-pipeline-btn', { timeout: 10000 }).should(
+ 'have.length',
+ 1,
+ );
+
+ PipelineUtils.editPipeline();
+ cy.wait(1000);
+ cy.dataCy('sp-editor-save-pipeline').click();
+ PipelineUtils.clonePipeline('Pipeline Test 2');
+ PipelineUtils.finalizePipelineStart();
+ cy.dataCy('modify-pipeline-btn', { timeout: 10000 }).should(
+ 'have.length',
+ 2,
+ );
+ });
+});
diff --git
a/ui/src/app/configuration/security-configuration/authentication-configuration/authentication-configuration.component.ts
b/ui/src/app/configuration/security-configuration/authentication-configuration/authentication-configuration.component.ts
index b07187eebe..9ad15881ee 100644
---
a/ui/src/app/configuration/security-configuration/authentication-configuration/authentication-configuration.component.ts
+++
b/ui/src/app/configuration/security-configuration/authentication-configuration/authentication-configuration.component.ts
@@ -30,7 +30,6 @@ export class SecurityAuthenticationConfigurationComponent {
generateKeyPair() {
this.configurationService.generateKeyPair().subscribe(result => {
- console.log(result);
this.saveKeyfile('public.key', result[0]);
this.saveKeyfile('private.pem', result[1]);
});
diff --git
a/ui/src/app/connect/dialog/edit-event-property/components/edit-schema-transformation/edit-schema-transformation.component.ts
b/ui/src/app/connect/dialog/edit-event-property/components/edit-schema-transformation/edit-schema-transformation.component.ts
index 608b6c4124..7c262a8e2f 100644
---
a/ui/src/app/connect/dialog/edit-event-property/components/edit-schema-transformation/edit-schema-transformation.component.ts
+++
b/ui/src/app/connect/dialog/edit-event-property/components/edit-schema-transformation/edit-schema-transformation.component.ts
@@ -91,7 +91,6 @@ export class EditSchemaTransformationComponent implements
OnInit {
}
triggerTutorialStep(): void {
- console.log('lu');
if (this.cachedProperty.runtimeName === 'temp') {
this.shepherdService.trigger('adapter-runtime-name-changed');
}
diff --git a/ui/src/app/core-ui/core-ui.module.ts
b/ui/src/app/core-ui/core-ui.module.ts
index 05aa78f741..85c08d4157 100644
--- a/ui/src/app/core-ui/core-ui.module.ts
+++ b/ui/src/app/core-ui/core-ui.module.ts
@@ -105,6 +105,10 @@ import { MatTooltipModule } from
'@angular/material/tooltip';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { StaticRuntimeResolvableGroupComponent } from
'./static-properties/static-runtime-resolvable-group/static-runtime-resolvable-group.component';
+import { LoadingIndicatorComponent } from
'./loading-indicator/loading-indicator.component';
+import { StatusIndicatorComponent } from
'./status-indicator/status-indicator.component';
+import { MultiStepStatusIndicatorComponent } from
'./multi-step-status-indicator/multi-step-status-indicator.component';
+import { PipelineOperationStatusComponent } from
'./pipeline/pipeline-operation-status/pipeline-operation-status.component';
@NgModule({
imports: [
@@ -198,6 +202,10 @@ import { StaticRuntimeResolvableGroupComponent } from
'./static-properties/stati
LivePreviewLoadingComponent,
LivePreviewTableComponent,
LivePreviewErrorComponent,
+ LoadingIndicatorComponent,
+ StatusIndicatorComponent,
+ MultiStepStatusIndicatorComponent,
+ PipelineOperationStatusComponent,
],
providers: [MatDatepickerModule, DisplayRecommendedPipe],
exports: [
@@ -228,6 +236,10 @@ import { StaticRuntimeResolvableGroupComponent } from
'./static-properties/stati
SpSimpleLogsComponent,
SpSimpleMetricsComponent,
StatusWidgetComponent,
+ LoadingIndicatorComponent,
+ StatusIndicatorComponent,
+ MultiStepStatusIndicatorComponent,
+ PipelineOperationStatusComponent,
],
})
export class CoreUiModule {}
diff --git
a/ui/src/app/core-ui/loading-indicator/loading-indicator.component.html
b/ui/src/app/core-ui/loading-indicator/loading-indicator.component.html
new file mode 100644
index 0000000000..3982ccb2ac
--- /dev/null
+++ b/ui/src/app/core-ui/loading-indicator/loading-indicator.component.html
@@ -0,0 +1,22 @@
+<!--
+ ~ 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 fxLayout="column" fxFlex="100" fxLayoutAlign="center center">
+ <mat-spinner [diameter]="30" color="accent"></mat-spinner>
+ <div class="message-text">{{ message }}</div>
+</div>
diff --git
a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.scss
b/ui/src/app/core-ui/loading-indicator/loading-indicator.component.scss
similarity index 72%
copy from ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.scss
copy to ui/src/app/core-ui/loading-indicator/loading-indicator.component.scss
index 5b19b82153..ab46954fee 100644
--- a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.scss
+++ b/ui/src/app/core-ui/loading-indicator/loading-indicator.component.scss
@@ -16,31 +16,7 @@
*
*/
-@import '../../../../scss/sp/sp-dialog.scss';
-
-.customize-section {
- display: flex;
- flex: 1 1 auto;
- padding: 20px;
-}
-
-.padding-20 {
- padding: 20px;
-}
-
-.mb-10 {
- margin-bottom: 10px;
-}
-
-::ng-deep .pipeline-radio-group .mat-radio-label {
- padding: 0;
-}
-
-.status-text {
- font-size: 14pt;
- margin-top: 10px;
-}
-
-.status-subtext {
+.message-text {
font-size: 12pt;
+ margin-top: 10px;
}
diff --git
a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.scss
b/ui/src/app/core-ui/loading-indicator/loading-indicator.component.ts
similarity index 68%
copy from ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.scss
copy to ui/src/app/core-ui/loading-indicator/loading-indicator.component.ts
index 5b19b82153..1378cf7811 100644
--- a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.scss
+++ b/ui/src/app/core-ui/loading-indicator/loading-indicator.component.ts
@@ -16,31 +16,14 @@
*
*/
-@import '../../../../scss/sp/sp-dialog.scss';
+import { Component, Input } from '@angular/core';
-.customize-section {
- display: flex;
- flex: 1 1 auto;
- padding: 20px;
-}
-
-.padding-20 {
- padding: 20px;
-}
-
-.mb-10 {
- margin-bottom: 10px;
-}
-
-::ng-deep .pipeline-radio-group .mat-radio-label {
- padding: 0;
-}
-
-.status-text {
- font-size: 14pt;
- margin-top: 10px;
-}
-
-.status-subtext {
- font-size: 12pt;
+@Component({
+ selector: 'sp-loading-indicator',
+ templateUrl: './loading-indicator.component.html',
+ styleUrls: ['./loading-indicator.component.scss'],
+})
+export class LoadingIndicatorComponent {
+ @Input()
+ message = 'Loading';
}
diff --git
a/ui/src/app/core-ui/multi-step-status-indicator/multi-step-status-indicator.component.html
b/ui/src/app/core-ui/multi-step-status-indicator/multi-step-status-indicator.component.html
new file mode 100644
index 0000000000..139340253c
--- /dev/null
+++
b/ui/src/app/core-ui/multi-step-status-indicator/multi-step-status-indicator.component.html
@@ -0,0 +1,47 @@
+<!--
+ ~ 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 fxLayout="column" fxLayoutGap="10px">
+ <div fxLayout="column" *ngFor="let step of statusIndicators">
+ <div
+ fxLayout="row"
+ fxLayoutGap="30px"
+ fxFlex="100"
+ fxLayoutAlign="start center"
+ >
+ <div *ngIf="step.status === Status.PROGRESS">
+ <mat-spinner [diameter]="25" color="accent"></mat-spinner>
+ </div>
+ <div
+ *ngIf="step.status === Status.SUCCESS"
+ fxLayoutAlign="start center"
+ >
+ <i class="material-icons status-icon">check_circle</i>
+ </div>
+ <div
+ *ngIf="step.status === Status.FAILURE"
+ fxLayoutAlign="start centers"
+ >
+ <i class="material-icons status-icon">error</i>
+ </div>
+ <div fxFlex class="message-text" fxLayoutAlign="start center">
+ {{ step.message }}
+ </div>
+ </div>
+ </div>
+</div>
diff --git
a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.scss
b/ui/src/app/core-ui/multi-step-status-indicator/multi-step-status-indicator.component.scss
similarity index 70%
copy from ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.scss
copy to
ui/src/app/core-ui/multi-step-status-indicator/multi-step-status-indicator.component.scss
index 5b19b82153..b3412ccbee 100644
--- a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.scss
+++
b/ui/src/app/core-ui/multi-step-status-indicator/multi-step-status-indicator.component.scss
@@ -1,4 +1,4 @@
-/*
+/*!
* 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.
@@ -16,31 +16,11 @@
*
*/
-@import '../../../../scss/sp/sp-dialog.scss';
-
-.customize-section {
- display: flex;
- flex: 1 1 auto;
- padding: 20px;
-}
-
-.padding-20 {
- padding: 20px;
-}
-
-.mb-10 {
- margin-bottom: 10px;
-}
-
-::ng-deep .pipeline-radio-group .mat-radio-label {
- padding: 0;
-}
-
-.status-text {
- font-size: 14pt;
- margin-top: 10px;
+.status-icon {
+ font-size: 22pt;
+ color: var(--color-accent);
}
-.status-subtext {
+.message-text {
font-size: 12pt;
}
diff --git
a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.scss
b/ui/src/app/core-ui/multi-step-status-indicator/multi-step-status-indicator.component.ts
similarity index 63%
copy from ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.scss
copy to
ui/src/app/core-ui/multi-step-status-indicator/multi-step-status-indicator.component.ts
index 5b19b82153..c297973fb4 100644
--- a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.scss
+++
b/ui/src/app/core-ui/multi-step-status-indicator/multi-step-status-indicator.component.ts
@@ -16,31 +16,16 @@
*
*/
-@import '../../../../scss/sp/sp-dialog.scss';
+import { Component, Input } from '@angular/core';
+import { Status, StatusIndicator } from './multi-step-status-indicator.model';
-.customize-section {
- display: flex;
- flex: 1 1 auto;
- padding: 20px;
-}
-
-.padding-20 {
- padding: 20px;
-}
-
-.mb-10 {
- margin-bottom: 10px;
-}
-
-::ng-deep .pipeline-radio-group .mat-radio-label {
- padding: 0;
-}
-
-.status-text {
- font-size: 14pt;
- margin-top: 10px;
-}
-
-.status-subtext {
- font-size: 12pt;
+@Component({
+ selector: 'sp-multi-step-status-indicator',
+ templateUrl: './multi-step-status-indicator.component.html',
+ styleUrls: ['./multi-step-status-indicator.component.scss'],
+})
+export class MultiStepStatusIndicatorComponent {
+ @Input()
+ statusIndicators: StatusIndicator[] = [];
+ protected readonly Status = Status;
}
diff --git
a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.scss
b/ui/src/app/core-ui/multi-step-status-indicator/multi-step-status-indicator.model.ts
similarity index 68%
copy from ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.scss
copy to
ui/src/app/core-ui/multi-step-status-indicator/multi-step-status-indicator.model.ts
index 5b19b82153..9e9c7f1d52 100644
--- a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.scss
+++
b/ui/src/app/core-ui/multi-step-status-indicator/multi-step-status-indicator.model.ts
@@ -16,31 +16,13 @@
*
*/
-@import '../../../../scss/sp/sp-dialog.scss';
-
-.customize-section {
- display: flex;
- flex: 1 1 auto;
- padding: 20px;
-}
-
-.padding-20 {
- padding: 20px;
-}
-
-.mb-10 {
- margin-bottom: 10px;
-}
-
-::ng-deep .pipeline-radio-group .mat-radio-label {
- padding: 0;
-}
-
-.status-text {
- font-size: 14pt;
- margin-top: 10px;
+export enum Status {
+ PROGRESS,
+ SUCCESS,
+ FAILURE,
}
-.status-subtext {
- font-size: 12pt;
+export interface StatusIndicator {
+ message: string;
+ status: Status;
}
diff --git
a/ui/src/app/core-ui/pipeline/pipeline-operation-status/pipeline-operation-status.component.html
b/ui/src/app/core-ui/pipeline/pipeline-operation-status/pipeline-operation-status.component.html
new file mode 100644
index 0000000000..914bc3f8be
--- /dev/null
+++
b/ui/src/app/core-ui/pipeline/pipeline-operation-status/pipeline-operation-status.component.html
@@ -0,0 +1,56 @@
+<!--
+ ~ 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
+ fxFlex="100"
+ fxLayout="column"
+ class="status-outer mt-10"
+ *ngFor="let msg of pipelineOperationStatus.elementStatus"
+>
+ <div fxFlex="100" fxLayout="column">
+ <div
+ fxFlex="100"
+ fxLayout="row"
+ fxLayoutAlign="start center"
+ class="p-15"
+ >
+ <mat-icon color="accent" *ngIf="msg.success">done</mat-icon>
+ <mat-icon style="color: red"
*ngIf="!msg.success">warning</mat-icon>
+ <div fxFlex="100" fxLayout="column" class="ml-5">
+ <span
+ ><b>{{ msg.elementName }}</b></span
+ >
+ <small>{{
+ msg.elementId.substr(0, msg.elementId.lastIndexOf('/'))
+ }}</small>
+ </div>
+ </div>
+ <div>
+ <div
+ fxFlex="100"
+ fxLayout="column"
+ *ngIf="msg.optionalMessage"
+ class="mt-10"
+ >
+ <div class="error-message">
+ <div class="p-10">{{ msg.optionalMessage }}</div>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
diff --git
a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.scss
b/ui/src/app/core-ui/pipeline/pipeline-operation-status/pipeline-operation-status.component.scss
similarity index 68%
copy from ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.scss
copy to
ui/src/app/core-ui/pipeline/pipeline-operation-status/pipeline-operation-status.component.scss
index 5b19b82153..16a5951fa8 100644
--- a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.scss
+++
b/ui/src/app/core-ui/pipeline/pipeline-operation-status/pipeline-operation-status.component.scss
@@ -16,31 +16,6 @@
*
*/
-@import '../../../../scss/sp/sp-dialog.scss';
-
-.customize-section {
- display: flex;
- flex: 1 1 auto;
- padding: 20px;
-}
-
-.padding-20 {
- padding: 20px;
-}
-
-.mb-10 {
- margin-bottom: 10px;
-}
-
-::ng-deep .pipeline-radio-group .mat-radio-label {
- padding: 0;
-}
-
-.status-text {
- font-size: 14pt;
- margin-top: 10px;
-}
-
-.status-subtext {
- font-size: 12pt;
+.status-outer {
+ border: 1px solid var(--color-bg-3);
}
diff --git
a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.scss
b/ui/src/app/core-ui/pipeline/pipeline-operation-status/pipeline-operation-status.component.ts
similarity index 65%
copy from ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.scss
copy to
ui/src/app/core-ui/pipeline/pipeline-operation-status/pipeline-operation-status.component.ts
index 5b19b82153..72324f8652 100644
--- a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.scss
+++
b/ui/src/app/core-ui/pipeline/pipeline-operation-status/pipeline-operation-status.component.ts
@@ -16,31 +16,15 @@
*
*/
-@import '../../../../scss/sp/sp-dialog.scss';
+import { Component, Input } from '@angular/core';
+import { PipelineOperationStatus } from '@streampipes/platform-services';
-.customize-section {
- display: flex;
- flex: 1 1 auto;
- padding: 20px;
-}
-
-.padding-20 {
- padding: 20px;
-}
-
-.mb-10 {
- margin-bottom: 10px;
-}
-
-::ng-deep .pipeline-radio-group .mat-radio-label {
- padding: 0;
-}
-
-.status-text {
- font-size: 14pt;
- margin-top: 10px;
-}
-
-.status-subtext {
- font-size: 12pt;
+@Component({
+ selector: 'sp-pipeline-operation-status',
+ templateUrl: './pipeline-operation-status.component.html',
+ styleUrls: ['./pipeline-operation-status.component.scss'],
+})
+export class PipelineOperationStatusComponent {
+ @Input()
+ pipelineOperationStatus: PipelineOperationStatus;
}
diff --git
a/ui/src/app/core-ui/pipeline/pipeline-started-status/pipeline-started-status.component.html
b/ui/src/app/core-ui/pipeline/pipeline-started-status/pipeline-started-status.component.html
index 9ff6b42b6d..85f64484d8 100644
---
a/ui/src/app/core-ui/pipeline/pipeline-started-status/pipeline-started-status.component.html
+++
b/ui/src/app/core-ui/pipeline/pipeline-started-status/pipeline-started-status.component.html
@@ -25,7 +25,10 @@
data-cy="sp-pipeline-started-dialog"
>
<div fxLayout="row">
- <mat-icon color="accent" *ngIf="pipelineOperationStatus.success"
+ <mat-icon
+ data-cy="sp-pipeline-started-success"
+ color="accent"
+ *ngIf="pipelineOperationStatus.success"
>done</mat-icon
>
<mat-icon
@@ -36,7 +39,11 @@
<span> {{ pipelineOperationStatus.title }}.</span>
</div>
<span
- *ngIf="action === 1 && !pipelineOperationStatus.success"
+ *ngIf="
+ action === 1 &&
+ !pipelineOperationStatus.success &&
+ !forceStopDisabled
+ "
class="message-small"
>You can perform a forced stop, which will stop and reset the
pipeline status.</span
@@ -58,7 +65,11 @@
class="ml-10"
color="accent"
(click)="forceStopPipeline()"
- *ngIf="action === 1 && !pipelineOperationStatus.success"
+ *ngIf="
+ action === 1 &&
+ !pipelineOperationStatus.success &&
+ !forceStopDisabled
+ "
>
<div>Force stop</div>
</button>
@@ -70,43 +81,9 @@
*ngIf="statusDetailsVisible"
class="w-100"
>
- <div
- fxFlex="100"
- fxLayout="column"
- class="mat-elevation-z1 mt-10"
- *ngFor="let msg of pipelineOperationStatus.elementStatus"
+ <sp-pipeline-operation-status
+ [pipelineOperationStatus]="pipelineOperationStatus"
>
- <div fxFlex="100" fxLayout="column" class="p-15">
- <div fxFlex="100" fxLayout="row" fxLayoutAlign="start center">
- <mat-icon color="accent"
*ngIf="msg.success">done</mat-icon>
- <mat-icon style="color: red" *ngIf="!msg.success"
- >warning</mat-icon
- >
- <div fxFlex="100" fxLayout="column" class="ml-5">
- <span
- ><b>{{ msg.elementName }}</b></span
- >
- <small>{{
- msg.elementId.substr(
- 0,
- msg.elementId.lastIndexOf('/')
- )
- }}</small>
- </div>
- </div>
- <div>
- <div
- fxFlex="100"
- fxLayout="column"
- *ngIf="msg.optionalMessage"
- class="mt-10"
- >
- <div class="error-message">
- {{ msg.optionalMessage }}
- </div>
- </div>
- </div>
- </div>
- </div>
+ </sp-pipeline-operation-status>
</div>
</div>
diff --git
a/ui/src/app/core-ui/pipeline/pipeline-started-status/pipeline-started-status.component.ts
b/ui/src/app/core-ui/pipeline/pipeline-started-status/pipeline-started-status.component.ts
index d36360323c..c281426a6b 100644
---
a/ui/src/app/core-ui/pipeline/pipeline-started-status/pipeline-started-status.component.ts
+++
b/ui/src/app/core-ui/pipeline/pipeline-started-status/pipeline-started-status.component.ts
@@ -32,6 +32,9 @@ export class PipelineStartedStatusComponent implements OnInit
{
@Input()
action: PipelineAction;
+ @Input()
+ forceStopDisabled = false;
+
@Output()
forceStopPipelineEmitter = new EventEmitter();
diff --git
a/ui/src/app/core-ui/status-indicator/status-indicator.component.html
b/ui/src/app/core-ui/status-indicator/status-indicator.component.html
new file mode 100644
index 0000000000..bbb27508b0
--- /dev/null
+++ b/ui/src/app/core-ui/status-indicator/status-indicator.component.html
@@ -0,0 +1,23 @@
+<!--
+ ~ 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 fxLayout="column" fxFlex="100" fxLayoutAlign="center center">
+ <i class="material-icons status-icon">{{ icon }}</i>
+ <div class="status-text">{{ message }}</div>
+ <div class="status-subtext">{{ additionalDescription }}</div>
+</div>
diff --git
a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.scss
b/ui/src/app/core-ui/status-indicator/status-indicator.component.scss
similarity index 75%
copy from ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.scss
copy to ui/src/app/core-ui/status-indicator/status-indicator.component.scss
index 5b19b82153..30ab52e3a0 100644
--- a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.scss
+++ b/ui/src/app/core-ui/status-indicator/status-indicator.component.scss
@@ -16,31 +16,17 @@
*
*/
-@import '../../../../scss/sp/sp-dialog.scss';
-
-.customize-section {
- display: flex;
- flex: 1 1 auto;
- padding: 20px;
-}
-
-.padding-20 {
- padding: 20px;
-}
-
-.mb-10 {
- margin-bottom: 10px;
-}
-
-::ng-deep .pipeline-radio-group .mat-radio-label {
- padding: 0;
-}
-
.status-text {
- font-size: 14pt;
+ font-size: 12pt;
margin-top: 10px;
}
.status-subtext {
- font-size: 12pt;
+ margin-top: 10px;
+ font-size: 10pt;
+}
+
+.status-icon {
+ font-size: 22pt;
+ color: var(--color-accent);
}
diff --git
a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.scss
b/ui/src/app/core-ui/status-indicator/status-indicator.component.ts
similarity index 68%
copy from ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.scss
copy to ui/src/app/core-ui/status-indicator/status-indicator.component.ts
index 5b19b82153..fd89fccdc8 100644
--- a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.scss
+++ b/ui/src/app/core-ui/status-indicator/status-indicator.component.ts
@@ -16,31 +16,20 @@
*
*/
-@import '../../../../scss/sp/sp-dialog.scss';
+import { Component, Input } from '@angular/core';
-.customize-section {
- display: flex;
- flex: 1 1 auto;
- padding: 20px;
-}
-
-.padding-20 {
- padding: 20px;
-}
-
-.mb-10 {
- margin-bottom: 10px;
-}
-
-::ng-deep .pipeline-radio-group .mat-radio-label {
- padding: 0;
-}
+@Component({
+ selector: 'sp-status-indicator',
+ templateUrl: './status-indicator.component.html',
+ styleUrls: ['./status-indicator.component.scss'],
+})
+export class StatusIndicatorComponent {
+ @Input()
+ message = 'Loading';
-.status-text {
- font-size: 14pt;
- margin-top: 10px;
-}
+ @Input()
+ additionalDescription = '';
-.status-subtext {
- font-size: 12pt;
+ @Input()
+ icon: string;
}
diff --git
a/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly.component.ts
b/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly.component.ts
index 4f138e2694..fa5c9afa1e 100644
---
a/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly.component.ts
+++
b/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly.component.ts
@@ -23,6 +23,7 @@ import {
EventEmitter,
Input,
NgZone,
+ OnDestroy,
OnInit,
Output,
ViewChild,
@@ -31,7 +32,6 @@ import { JsplumbBridge } from
'../../services/jsplumb-bridge.service';
import { PipelinePositioningService } from
'../../services/pipeline-positioning.service';
import { PipelineValidationService } from
'../../services/pipeline-validation.service';
import { JsplumbService } from '../../services/jsplumb.service';
-import { ShepherdService } from '../../../services/tour/shepherd.service';
import {
PipelineElementConfig,
PipelineElementUnion,
@@ -47,6 +47,7 @@ import { SavePipelineComponent } from
'../../dialog/save-pipeline/save-pipeline.
import { MatDialog } from '@angular/material/dialog';
import { EditorService } from '../../services/editor.service';
import {
+ Pipeline,
PipelineCanvasMetadata,
PipelineCanvasMetadataService,
PipelineService,
@@ -55,22 +56,25 @@ import { JsplumbFactoryService } from
'../../services/jsplumb-factory.service';
import Panzoom, { PanzoomObject } from '@panzoom/panzoom';
import { PipelineElementDraggedService } from
'../../services/pipeline-element-dragged.service';
import { PipelineComponent } from '../pipeline/pipeline.component';
-import { forkJoin } from 'rxjs';
+import { forkJoin, of, Subscription } from 'rxjs';
import { PipelineElementDiscoveryComponent } from
'../../dialog/pipeline-element-discovery/pipeline-element-discovery.component';
import { SpPipelineRoutes } from '../../../pipelines/pipelines.routes';
import { Router } from '@angular/router';
+import { catchError } from 'rxjs/operators';
@Component({
selector: 'sp-pipeline-assembly',
templateUrl: './pipeline-assembly.component.html',
styleUrls: ['./pipeline-assembly.component.scss'],
})
-export class PipelineAssemblyComponent implements OnInit, AfterViewInit {
+export class PipelineAssemblyComponent
+ implements OnInit, AfterViewInit, OnDestroy
+{
@Input()
rawPipelineModel: PipelineElementConfig[];
@Input()
- currentModifiedPipelineId: any;
+ currentModifiedPipelineId: string;
@Input()
allElements: PipelineElementUnion[];
@@ -80,17 +84,11 @@ export class PipelineAssemblyComponent implements OnInit,
AfterViewInit {
new EventEmitter<boolean>();
JsplumbBridge: JsplumbBridge;
-
- pipelineCanvasMaximized = false;
-
- currentMouseOverElement: any;
currentZoomLevel: any;
preview: any;
selectMode: any;
- currentPipelineName: any;
- currentPipelineDescription: any;
-
+ originalPipeline: Pipeline;
pipelineValid = false;
pipelineCacheRunning = false;
@@ -107,6 +105,7 @@ export class PipelineAssemblyComponent implements OnInit,
AfterViewInit {
pipelineComponent: PipelineComponent;
panzoom: PanzoomObject;
+ moveSub: Subscription;
constructor(
private jsPlumbFactoryService: JsplumbFactoryService,
@@ -116,14 +115,13 @@ export class PipelineAssemblyComponent implements OnInit,
AfterViewInit {
public pipelineValidationService: PipelineValidationService,
private pipelineService: PipelineService,
private jsplumbService: JsplumbService,
- private shepherdService: ShepherdService,
private dialogService: DialogService,
private dialog: MatDialog,
private ngZone: NgZone,
+ private router: Router,
private pipelineElementDraggedService: PipelineElementDraggedService,
private pipelineCanvasMetadataService: PipelineCanvasMetadataService,
private breadcrumbService: SpBreadcrumbService,
- private router: Router,
) {
this.selectMode = true;
this.currentZoomLevel = 1;
@@ -131,30 +129,31 @@ export class PipelineAssemblyComponent implements OnInit,
AfterViewInit {
ngOnInit(): void {
if (this.currentModifiedPipelineId) {
- this.displayPipelineById();
+ this.displayPipelineById(this.currentModifiedPipelineId);
} else {
this.checkAndDisplayCachedPipeline();
}
-
this.pipelineElementDraggedService.pipelineElementMovedSubject.subscribe(
- position => {
- const offsetHeight =
- this.pipelineCanvas.nativeElement.offsetHeight;
- const offsetWidth =
- this.pipelineCanvas.nativeElement.offsetWidth;
- const currentPan = this.panzoom.getPan();
- let xOffset = 0;
- let yOffset = 0;
- if (position.y + currentPan.y > offsetHeight - 100) {
- yOffset = -10;
- }
- if (position.x + currentPan.x > offsetWidth - 100) {
- xOffset = -10;
- }
- if (xOffset < 0 || yOffset < 0) {
- this.pan(xOffset, yOffset);
- }
- },
- );
+ this.moveSub =
+
this.pipelineElementDraggedService.pipelineElementMovedSubject.subscribe(
+ position => {
+ const offsetHeight =
+ this.pipelineCanvas.nativeElement.offsetHeight;
+ const offsetWidth =
+ this.pipelineCanvas.nativeElement.offsetWidth;
+ const currentPan = this.panzoom.getPan();
+ let xOffset = 0;
+ let yOffset = 0;
+ if (position.y + currentPan.y > offsetHeight - 100) {
+ yOffset = -10;
+ }
+ if (position.x + currentPan.x > offsetWidth - 100) {
+ xOffset = -10;
+ }
+ if (xOffset < 0 || yOffset < 0) {
+ this.pan(xOffset, yOffset);
+ }
+ },
+ );
}
ngAfterViewInit() {
@@ -221,20 +220,15 @@ export class PipelineAssemblyComponent implements OnInit,
AfterViewInit {
* clears the Assembly of all elements
*/
clearAssembly() {
- // $('#assembly').children().not('#clear, #submit').remove();
this.JsplumbBridge.deleteEveryEndpoint();
this.rawPipelineModel = [];
this.currentZoomLevel = 1;
this.JsplumbBridge.setZoom(this.currentZoomLevel);
this.JsplumbBridge.repaintEverything();
- const removePipelineFromCache =
- this.editorService.removePipelineFromCache();
- const removeCanvasMetadataFromCache =
- this.editorService.removeCanvasMetadataFromCache();
forkJoin([
- removePipelineFromCache,
- removeCanvasMetadataFromCache,
+ this.editorService.removePipelineFromCache(),
+ this.editorService.removeCanvasMetadataFromCache(),
]).subscribe(msg => {
this.pipelineCached = false;
this.pipelineCacheRunning = false;
@@ -255,21 +249,30 @@ export class PipelineAssemblyComponent implements OnInit,
AfterViewInit {
pipelineModel,
this.preview,
);
- pipeline.name = this.currentPipelineName;
- pipeline.description = this.currentPipelineDescription;
- if (this.currentModifiedPipelineId) {
- pipeline._id = this.currentModifiedPipelineId;
- }
-
- this.dialogService.open(SavePipelineComponent, {
+ const dialogRef = this.dialogService.open(SavePipelineComponent, {
panelType: PanelType.SLIDE_IN_PANEL,
+ disableClose: true,
title: 'Save pipeline',
+ width: '40vw',
data: {
pipeline: pipeline,
- currentModifiedPipelineId: this.currentModifiedPipelineId,
+ originalPipeline: this.originalPipeline,
pipelineCanvasMetadata: this.pipelineCanvasMetadata,
},
});
+ dialogRef
+ .afterClosed()
+ .subscribe((config: { reload: boolean; pipelineId: string }) => {
+ if (config?.reload) {
+ this.clearAssembly();
+ this.editorService.makePipelineAssemblyEmpty(true);
+ this.rawPipelineModel = [];
+ this.currentModifiedPipelineId = undefined;
+ setTimeout(() => {
+ this.router.navigate(['pipelines', 'create']);
+ });
+ }
+ });
}
checkAndDisplayCachedPipeline() {
@@ -280,40 +283,48 @@ export class PipelineAssemblyComponent implements OnInit,
AfterViewInit {
if (results[0] && results[0].length > 0) {
this.rawPipelineModel = results[0] as PipelineElementConfig[];
this.handleCanvasMetadataResponse(results[1]);
+ this.displayPipelineInEditor(
+ !this.pipelineCanvasMetadataAvailable,
+ this.pipelineCanvasMetadata,
+ );
}
});
}
- displayPipelineById() {
- const pipelineRequest = this.pipelineService.getPipelineById(
- this.currentModifiedPipelineId,
- );
- const canvasRequest =
- this.pipelineCanvasMetadataService.getPipelineCanvasMetadata(
- this.currentModifiedPipelineId,
- );
- pipelineRequest.subscribe(pipelineResp => {
- const pipeline = pipelineResp;
- this.currentPipelineName = pipeline.name;
- this.breadcrumbService.updateBreadcrumb([
- SpPipelineRoutes.BASE,
- { label: pipeline.name },
- { label: 'Modify' },
- ]);
- this.currentPipelineDescription = pipeline.description;
- this.rawPipelineModel = this.jsplumbService.makeRawPipeline(
- pipeline,
- false,
- );
- canvasRequest.subscribe(
- canvasResp => {
- this.handleCanvasMetadataResponse(canvasResp);
- },
- error => {
+ displayPipelineById(pipelineId: string) {
+ const pipelineReq = this.pipelineService.getPipelineById(pipelineId);
+ const canvasMetadataReq = this.pipelineCanvasMetadataService
+ .getPipelineCanvasMetadata(pipelineId)
+ .pipe(
+ catchError(error => {
this.handleCanvasMetadataResponse(undefined);
- },
+ return of(undefined);
+ }),
);
- });
+
+ forkJoin([pipelineReq, canvasMetadataReq]).subscribe(
+ ([pipelineResp, canvasResp]) => {
+ if (pipelineResp) {
+ this.originalPipeline = pipelineResp;
+ this.breadcrumbService.updateBreadcrumb([
+ SpPipelineRoutes.BASE,
+ { label: this.originalPipeline.name },
+ { label: 'Modify' },
+ ]);
+ this.rawPipelineModel =
this.jsplumbService.makeRawPipeline(
+ this.originalPipeline,
+ false,
+ );
+ }
+ if (canvasResp !== undefined) {
+ this.handleCanvasMetadataResponse(canvasResp);
+ }
+ this.displayPipelineInEditor(
+ !this.pipelineCanvasMetadataAvailable,
+ this.pipelineCanvasMetadata,
+ );
+ },
+ );
}
handleCanvasMetadataResponse(canvasMetadata: PipelineCanvasMetadata) {
@@ -324,16 +335,12 @@ export class PipelineAssemblyComponent implements OnInit,
AfterViewInit {
this.pipelineCanvasMetadataAvailable = false;
this.pipelineCanvasMetadata = new PipelineCanvasMetadata();
}
- this.displayPipelineInEditor(
- !this.pipelineCanvasMetadataAvailable,
- this.pipelineCanvasMetadata,
- );
}
displayPipelineInEditor(
- autoLayout,
+ autoLayout: boolean,
pipelineCanvasMetadata?: PipelineCanvasMetadata,
- ) {
+ ): void {
setTimeout(() => {
this.pipelinePositioningService.displayPipeline(
this.rawPipelineModel,
@@ -409,4 +416,8 @@ export class PipelineAssemblyComponent implements OnInit,
AfterViewInit {
},
});
}
+
+ ngOnDestroy() {
+ this.moveSub?.unsubscribe();
+ }
}
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
new file mode 100644
index 0000000000..56c283b6c0
--- /dev/null
+++
b/ui/src/app/editor/dialog/save-pipeline/save-pipeline-settings/save-pipeline-settings.component.html
@@ -0,0 +1,86 @@
+<!--
+ ~ 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 fxLayout="column">
+ <div
+ id="overwriteCheckbox"
+ class="checkbox"
+ *ngIf="storageOptions.updateModeActive"
+ >
+ <mat-radio-group
+ [(ngModel)]="storageOptions.updateMode"
+ fxLayout="column"
+ color="accent"
+ class="pipeline-radio-group"
+ >
+ <mat-radio-button
+ [value]="'update'"
+ style="padding-left: 0"
+ data-cy="pipeline-update-mode-update"
+ >
+ Update pipeline <b>{{ currentPipelineName }}</b>
+ </mat-radio-button>
+ <mat-radio-button
+ [value]="'clone'"
+ class="mb-10"
+ data-cy="pipeline-update-mode-clone"
+ >
+ Create new pipeline
+ </mat-radio-button>
+ </mat-radio-group>
+ </div>
+ <form [formGroup]="submitPipelineForm">
+ <div
+ fxFlex="100"
+ fxLayout="column"
+ *ngIf="
+ !storageOptions.updateModeActive ||
+ storageOptions.updateMode === 'clone'
+ "
+ >
+ <mat-form-field fxFlex color="accent">
+ <mat-label>Pipeline Name</mat-label>
+ <input
+ [formControlName]="'pipelineName'"
+ data-cy="sp-editor-pipeline-name"
+ matInput
+ name="pipelineName"
+ (blur)="triggerTutorial()"
+ />
+ </mat-form-field>
+ <mat-form-field fxFlex color="accent">
+ <mat-label>Description</mat-label>
+ <input [formControlName]="'pipelineDescription'" matInput />
+ </mat-form-field>
+ </div>
+ </form>
+ <mat-checkbox
+ [(ngModel)]="storageOptions.startPipelineAfterStorage"
+ color="accent"
+ data-cy="sp-editor-checkbox-start-immediately"
+ >
+ Start pipeline immediately
+ </mat-checkbox>
+ <mat-checkbox
+ [(ngModel)]="storageOptions.navigateToPipelineOverview"
+ color="accent"
+ data-cy="sp-editor-checkbox-navigate-to-overview"
+ >
+ Navigate to pipeline overview afterwards
+ </mat-checkbox>
+</div>
diff --git
a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.scss
b/ui/src/app/editor/dialog/save-pipeline/save-pipeline-settings/save-pipeline-settings.component.scss
similarity index 68%
copy from ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.scss
copy to
ui/src/app/editor/dialog/save-pipeline/save-pipeline-settings/save-pipeline-settings.component.scss
index 5b19b82153..13cbc4aacb 100644
--- a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.scss
+++
b/ui/src/app/editor/dialog/save-pipeline/save-pipeline-settings/save-pipeline-settings.component.scss
@@ -15,32 +15,3 @@
* limitations under the License.
*
*/
-
-@import '../../../../scss/sp/sp-dialog.scss';
-
-.customize-section {
- display: flex;
- flex: 1 1 auto;
- padding: 20px;
-}
-
-.padding-20 {
- padding: 20px;
-}
-
-.mb-10 {
- margin-bottom: 10px;
-}
-
-::ng-deep .pipeline-radio-group .mat-radio-label {
- padding: 0;
-}
-
-.status-text {
- font-size: 14pt;
- margin-top: 10px;
-}
-
-.status-subtext {
- font-size: 12pt;
-}
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
new file mode 100644
index 0000000000..6f32859192
--- /dev/null
+++
b/ui/src/app/editor/dialog/save-pipeline/save-pipeline-settings/save-pipeline-settings.component.ts
@@ -0,0 +1,82 @@
+/*
+ * 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 { ShepherdService } from '../../../../services/tour/shepherd.service';
+import {
+ UntypedFormControl,
+ UntypedFormGroup,
+ Validators,
+} from '@angular/forms';
+import { Pipeline } from '@streampipes/platform-services';
+import { PipelineStorageOptions } from '../../../model/editor.model';
+
+@Component({
+ selector: 'sp-save-pipeline-settings',
+ templateUrl: './save-pipeline-settings.component.html',
+ styleUrls: ['./save-pipeline-settings.component.scss'],
+})
+export class SavePipelineSettingsComponent implements OnInit {
+ @Input()
+ submitPipelineForm: UntypedFormGroup = new UntypedFormGroup({});
+
+ @Input()
+ pipeline: Pipeline;
+
+ @Input()
+ storageOptions: PipelineStorageOptions;
+
+ @Input()
+ currentPipelineName: string;
+
+ constructor(private shepherdService: ShepherdService) {}
+
+ ngOnInit() {
+ this.submitPipelineForm.addControl(
+ 'pipelineName',
+ new UntypedFormControl(this.pipeline.name, [
+ Validators.required,
+ Validators.maxLength(40),
+ ]),
+ );
+ this.submitPipelineForm.addControl(
+ 'pipelineDescription',
+ new UntypedFormControl(this.pipeline.description, [
+ Validators.maxLength(80),
+ ]),
+ );
+
+
this.submitPipelineForm.controls['pipelineName'].valueChanges.subscribe(
+ value => {
+ this.pipeline.name = value;
+ },
+ );
+
+ this.submitPipelineForm.controls[
+ 'pipelineDescription'
+ ].valueChanges.subscribe(value => {
+ this.pipeline.description = value;
+ });
+ }
+
+ 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 94af6d9021..9d5bf72304 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
@@ -19,138 +19,78 @@
<div class="sp-dialog-container">
<div class="sp-dialog-content padding-20">
<div fxFlex="100" fxLayout="column">
- <div
- fxFlex="100"
- fxLayout="column"
- *ngIf="!saved && !saving && !storageError"
+ <sp-save-pipeline-settings
+ *ngIf="!operationCompleted && !operationProgress"
+ [currentPipelineName]="pipeline.name"
+ [submitPipelineForm]="submitPipelineForm"
+ [pipeline]="pipeline"
+ [storageOptions]="storageOptions"
>
- <div
- id="overwriteCheckbox"
- class="checkbox"
- *ngIf="currentModifiedPipelineId"
- >
- <mat-radio-group
- [(ngModel)]="updateMode"
- fxLayout="column"
- color="accent"
- class="pipeline-radio-group"
- >
- <mat-radio-button
- [value]="'update'"
- class="mb-10"
- style="padding-left: 0"
- >
- Update pipeline <b>{{ currentPipelineName }}</b>
- </mat-radio-button>
- <mat-radio-button [value]="'clone'" class="mb-10">
- Create new pipeline
- </mat-radio-button>
- </mat-radio-group>
- </div>
- <form [formGroup]="submitPipelineForm">
- <div
- fxFlex="100"
- fxLayout="column"
- *ngIf="
- !currentModifiedPipelineId || updateMode ===
'clone'
- "
- >
- <mat-form-field fxFlex color="accent">
- <mat-label>Pipeline Name</mat-label>
- <input
- [formControlName]="'pipelineName'"
- data-cy="sp-editor-pipeline-name"
- matInput
- name="pipelineName"
- />
- </mat-form-field>
- <mat-form-field fxFlex color="accent">
- <mat-label>Description</mat-label>
- <input
- [formControlName]="'pipelineDescription'"
- matInput
- />
- </mat-form-field>
- </div>
- </form>
- <mat-checkbox
- (click)="triggerTutorial()"
- [(ngModel)]="startPipelineAfterStorage"
- color="accent"
- data-cy="sp-editor-checkbox-start-immediately"
- >
- Start pipeline immediately
- </mat-checkbox>
- </div>
- <div
- fxFlex="100"
- fxLayout="column"
- fxLayoutAlign="center center"
- *ngIf="saving"
- >
- <mat-spinner
- [mode]="'indeterminate'"
- [diameter]="50"
- color="accent"
- ></mat-spinner>
- <span class="status-text">Saving pipeline...</span>
- </div>
- <div
- fxFlex="100"
- fxLayout="column"
- fxLayoutAlign="center center"
- *ngIf="saved"
- >
- <mat-icon
- color="accent"
- style="font-size: 50pt; height: 60px; width: 60px"
- >check_circle</mat-icon
- >
- <span class="status-text">Pipeline successfully stored.</span>
- </div>
- <div
- fxFlex="100"
- fxLayout="column"
- fxLayoutAlign="center center"
- *ngIf="storageError"
+ </sp-save-pipeline-settings>
+
+ <sp-multi-step-status-indicator
+ *ngIf="operationProgress || operationCompleted"
+ [statusIndicators]="statusIndicators"
>
- <mat-icon
- color="accent"
- style="font-size: 50pt; height: 60px; width: 60px"
- >error</mat-icon
- >
- <span class="status-text"
- >Your pipeline could not be stored.</span
+ </sp-multi-step-status-indicator>
+
+ <div *ngIf="finalPipelineOperationStatus" class="mt-10">
+ <mat-divider></mat-divider>
+ <sp-pipeline-started-status
+ class="mt-10"
+ [forceStopDisabled]="true"
+ [action]="pipelineAction"
+ [pipelineOperationStatus]="finalPipelineOperationStatus"
>
- <span class="status-subtext">{{ errorMessage }}</span>
+ </sp-pipeline-started-status>
</div>
</div>
</div>
<mat-divider></mat-divider>
<div class="sp-dialog-actions">
<button
- [disabled]="!submitPipelineForm.valid || saving || saved"
- mat-button
mat-raised-button
color="accent"
- (click)="savePipeline(false)"
- style="margin-right: 10px"
+ (click)="hide(false)"
+ *ngIf="operationCompleted"
>
- Save
+ Create another pipeline
</button>
<button
- [disabled]="!submitPipelineForm.valid || saving || saved"
+ mat-raised-button
+ color="accent"
+ data-cy="sp-navigate-to-pipeline-overview"
+ (click)="navigateToPipelineOverview()"
+ *ngIf="operationCompleted"
+ >
+ Open pipeline overview
+ </button>
+ <button
+ *ngIf="!operationCompleted && !operationSuccess"
+ [disabled]="
+ !submitPipelineForm.valid ||
+ operationProgress ||
+ operationCompleted
+ "
mat-button
mat-raised-button
color="accent"
- (click)="savePipeline(true)"
+ (click)="savePipeline()"
style="margin-right: 10px"
- data-cy="sp-editor-save"
+ data-cy="sp-editor-apply"
>
- Save and go to pipeline view
+ Apply
</button>
- <button mat-button mat-raised-button class="mat-basic"
(click)="hide()">
- {{ saved ? 'Close' : 'Cancel' }}
+ <button
+ *ngIf="
+ !operationProgress && (!operationCompleted ||
!operationSuccess)
+ "
+ mat-button
+ mat-raised-button
+ class="mat-basic"
+ (click)="hide(true)"
+ >
+ {{ 'Cancel' }}
</button>
</div>
</div>
diff --git
a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.scss
b/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.scss
index 5b19b82153..32ffbd0a6d 100644
--- a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.scss
+++ b/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.scss
@@ -35,12 +35,3 @@
::ng-deep .pipeline-radio-group .mat-radio-label {
padding: 0;
}
-
-.status-text {
- font-size: 14pt;
- margin-top: 10px;
-}
-
-.status-subtext {
- font-size: 12pt;
-}
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 863d9693fc..7efa549cf1 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
@@ -23,18 +23,25 @@ import {
Pipeline,
PipelineCanvasMetadata,
PipelineCanvasMetadataService,
+ PipelineOperationStatus,
PipelineService,
} from '@streampipes/platform-services';
import { EditorService } from '../../services/editor.service';
import { ShepherdService } from '../../../services/tour/shepherd.service';
-import {
- UntypedFormControl,
- UntypedFormGroup,
- Validators,
-} from '@angular/forms';
+import { UntypedFormGroup } from '@angular/forms';
import { Router } from '@angular/router';
-import { InvocablePipelineElementUnion } from '../../model/editor.model';
+import {
+ InvocablePipelineElementUnion,
+ PipelineStorageOptions,
+} from '../../model/editor.model';
import { IdGeneratorService } from
'../../../core-services/id-generator/id-generator.service';
+import { Observable, of, tap } from 'rxjs';
+import { filter, switchMap } from 'rxjs/operators';
+import {
+ Status,
+ StatusIndicator,
+} from
'../../../core-ui/multi-step-status-indicator/multi-step-status-indicator.model';
+import { PipelineAction } from '../../../pipelines/model/pipeline-model';
@Component({
selector: 'sp-save-pipeline',
@@ -42,30 +49,33 @@ import { IdGeneratorService } from
'../../../core-services/id-generator/id-gener
styleUrls: ['./save-pipeline.component.scss'],
})
export class SavePipelineComponent implements OnInit {
- startPipelineAfterStorage: boolean;
- updateMode: any;
-
- submitPipelineForm: UntypedFormGroup = new UntypedFormGroup({});
-
@Input()
pipeline: Pipeline;
@Input()
- modificationMode: string;
-
- @Input()
- currentModifiedPipelineId: string;
+ originalPipeline: Pipeline;
@Input()
pipelineCanvasMetadata: PipelineCanvasMetadata;
- saving = false;
- saved = false;
+ operationProgress = false;
+ operationCompleted = false;
+ operationSuccess = false;
- storageError = false;
errorMessage = '';
+ pipelineId: string;
- currentPipelineName: string;
+ storageOptions: PipelineStorageOptions = {
+ updateMode: 'update',
+ startPipelineAfterStorage: true,
+ navigateToPipelineOverview: true,
+ updateModeActive: false,
+ };
+
+ submitPipelineForm: UntypedFormGroup = new UntypedFormGroup({});
+ statusIndicators: StatusIndicator[] = [];
+ finalPipelineOperationStatus: PipelineOperationStatus;
+ pipelineAction: PipelineAction;
constructor(
private editorService: EditorService,
@@ -75,90 +85,95 @@ export class SavePipelineComponent implements OnInit {
private router: Router,
private shepherdService: ShepherdService,
private pipelineCanvasService: PipelineCanvasMetadataService,
- ) {
- this.updateMode = 'update';
- }
+ ) {}
ngOnInit() {
- if (this.currentModifiedPipelineId) {
- this.currentPipelineName = this.pipeline.name;
+ this.storageOptions.updateModeActive =
+ this.originalPipeline !== undefined;
+ if (this.storageOptions.updateModeActive) {
+ this.pipeline._id = this.originalPipeline._id;
+ this.pipeline.name = this.originalPipeline.name;
+ this.pipeline.description = this.originalPipeline.description;
+ this.pipeline.running = this.originalPipeline.running;
+ this.pipeline.createdAt = this.originalPipeline.createdAt;
+ this.pipeline.createdByUser = this.originalPipeline.createdByUser;
}
- this.submitPipelineForm.addControl(
- 'pipelineName',
- new UntypedFormControl(this.pipeline.name, [
- Validators.required,
- Validators.maxLength(40),
- ]),
- );
- this.submitPipelineForm.addControl(
- 'pipelineDescription',
- new UntypedFormControl(this.pipeline.description, [
- Validators.maxLength(80),
- ]),
- );
-
-
this.submitPipelineForm.controls['pipelineName'].valueChanges.subscribe(
- value => {
- this.pipeline.name = value;
- },
- );
-
- this.submitPipelineForm.controls[
- 'pipelineDescription'
- ].valueChanges.subscribe(value => {
- this.pipeline.description = value;
- });
-
if (this.shepherdService.isTourActive()) {
this.shepherdService.trigger('enter-pipeline-name');
}
}
- triggerTutorial() {
- if (this.shepherdService.isTourActive()) {
- this.shepherdService.trigger('save-pipeline-dialog');
- }
+ performStorageOperations(
+ stopPipeline$: Observable<null | PipelineOperationStatus>,
+ savePipeline$: Observable<Message>,
+ ) {
+ // if pipeline is running and update mode: stop pipeline
+ // if update mode: update pipeline, if not update mode or update mode
clone: save pipeline
+ // if update mode and not clone: update canvas, else store new canvas
+ // if should start: start pipeline
+ stopPipeline$
+ .pipe(
+ tap(() =>
+ this.addStatusIndicator('Saving pipeline',
Status.PROGRESS),
+ ),
+ switchMap(() => savePipeline$),
+ tap(message => {
+ this.operationSuccess = message.success;
+ if (!message.success) {
+ this.handleStorageError();
+ }
+ this.modifyStatusIndicator(Status.SUCCESS);
+ this.pipelineId = message.notifications[1].description;
+ }),
+ // only continue if pipeline was saved
+ filter(message => message.success),
+ tap(() =>
+ this.addStatusIndicator('Saving metadata',
Status.PROGRESS),
+ ),
+ switchMap(() =>
+ this.getPipelineCanvasMetadata$(this.pipelineId),
+ ),
+ tap(() => this.modifyStatusIndicator(Status.SUCCESS)),
+ switchMap(() => this.getStartPipeline$()),
+ )
+ .subscribe({
+ next: message => this.onSuccess(message),
+ error: msg => {
+ this.onFailure(msg);
+ },
+ });
}
- displayErrors(data?: string) {
- this.storageError = true;
- this.errorMessage = data;
+ clonePipeline(): void {
+ this.pipeline._id = undefined;
+ this.pipeline._rev = undefined;
+ this.pipeline.running = false;
+ this.pipeline.actions.forEach(element => this.updateId(element));
+ this.pipeline.sepas.forEach(element => this.updateId(element));
+ this.pipelineCanvasMetadata._id = undefined;
+ this.pipelineCanvasMetadata._rev = undefined;
}
- savePipeline(switchTab) {
- let storageRequest;
- const updateMode =
- this.currentModifiedPipelineId && this.updateMode === 'update';
-
- if (updateMode) {
- storageRequest =
this.pipelineService.updatePipeline(this.pipeline);
- } else {
- if (this.currentModifiedPipelineId) {
- this.pipeline.actions.forEach(element =>
- this.updateId(element),
+ savePipeline() {
+ let stopPipeline$: Observable<null | PipelineOperationStatus> =
+ of(null);
+ let savePipeline$: Observable<Message> =
+ this.pipelineService.storePipeline(this.pipeline);
+ this.operationProgress = true;
+ if (this.storageOptions.updateModeActive) {
+ if (this.storageOptions.updateMode === 'clone') {
+ this.clonePipeline();
+ } else {
+ if (this.pipeline.running) {
+ stopPipeline$ = this.getStopPipeline$();
+ }
+ savePipeline$ = this.pipelineService.updatePipeline(
+ this.pipeline,
);
- this.pipeline.sepas.forEach(element => this.updateId(element));
}
- this.pipeline._id = undefined;
- storageRequest = this.pipelineService.storePipeline(this.pipeline);
}
-
- storageRequest.subscribe(
- statusMessage => {
- if (statusMessage.success) {
- const pipelineId: string =
- statusMessage.notifications[1].description;
- this.storePipelineCanvasMetadata(pipelineId, updateMode);
- this.afterStorage(statusMessage, switchTab, pipelineId);
- } else {
- this.displayErrors(statusMessage.notifications[0]);
- }
- },
- data => {
- this.displayErrors();
- },
- );
+ this.performStorageOperations(stopPipeline$, savePipeline$);
}
updateId(entity: InvocablePipelineElementUnion) {
@@ -168,10 +183,60 @@ export class SavePipelineComponent implements OnInit {
this.idGeneratorService.generate(5);
}
- storePipelineCanvasMetadata(pipelineId: string, updateMode: boolean) {
+ getStopPipeline$(): Observable<PipelineOperationStatus> {
+ return of(null).pipe(
+ tap(() =>
+ this.addStatusIndicator('Stopping pipeline', Status.PROGRESS),
+ ),
+ switchMap(() =>
+ this.pipelineService.stopPipeline(this.originalPipeline._id),
+ ),
+ tap(msg => {
+ this.operationSuccess = msg.success;
+ if (!msg.success) {
+ this.handlePipelineOperationError(msg,
PipelineAction.Stop);
+ } else {
+ this.modifyStatusIndicator(Status.SUCCESS);
+ }
+ }),
+ filter(status => status.success),
+ );
+ }
+
+ getStartPipeline$(): Observable<null | PipelineOperationStatus> {
+ if (this.storageOptions.startPipelineAfterStorage) {
+ return of(null).pipe(
+ tap(() =>
+ this.addStatusIndicator(
+ 'Starting pipeline',
+ Status.PROGRESS,
+ ),
+ ),
+ switchMap(() =>
+ this.pipelineService.startPipeline(this.pipelineId),
+ ),
+ tap(msg => {
+ if (!msg.success) {
+ this.handlePipelineOperationError(
+ msg,
+ PipelineAction.Start,
+ );
+ } else {
+ this.modifyStatusIndicator(
+ msg.success ? Status.SUCCESS : Status.FAILURE,
+ );
+ }
+ }),
+ );
+ } else {
+ return of(null);
+ }
+ }
+
+ getPipelineCanvasMetadata$(pipelineId: string): Observable<Object> {
let request;
this.pipelineCanvasMetadata.pipelineId = pipelineId;
- if (updateMode) {
+ if (this.storageOptions.updateModeActive) {
request = this.pipelineCanvasService.updatePipelineCanvasMetadata(
this.pipelineCanvasMetadata,
);
@@ -182,28 +247,72 @@ export class SavePipelineComponent implements OnInit {
this.pipelineCanvasMetadata,
);
}
+ return request;
+ }
+
+ addStatusIndicator(message: string, status: Status) {
+ this.statusIndicators.push({ message, status });
+ }
+
+ modifyStatusIndicator(status: Status) {
+ // modify status of the last indicator
+ this.statusIndicators[this.statusIndicators.length - 1].status =
status;
+ }
- request.subscribe();
+ handleStorageError(): void {
+ this.onFailure();
}
- afterStorage(statusMessage: Message, switchTab, pipelineId?: string) {
- this.hide();
+ handlePipelineOperationError(
+ status: PipelineOperationStatus,
+ pipelineAction: PipelineAction,
+ ) {
+ this.onFailure();
+ this.showPipelineOperationStatus(status, pipelineAction);
+ }
+
+ onFailure(msg?: any) {
+ this.operationCompleted = true;
+ this.operationSuccess = false;
+ this.modifyStatusIndicator(Status.FAILURE);
+ }
+
+ showPipelineOperationStatus(
+ status: PipelineOperationStatus,
+ pipelineAction: PipelineAction,
+ ) {
+ this.finalPipelineOperationStatus = status;
+ this.pipelineAction = pipelineAction;
+ }
+
+ onSuccess(status?: PipelineOperationStatus) {
+ this.operationProgress = false;
+ this.operationCompleted = true;
+ if (status) {
+ this.showPipelineOperationStatus(status, PipelineAction.Start);
+ }
this.editorService.makePipelineAssemblyEmpty(true);
this.editorService.removePipelineFromCache().subscribe();
if (this.shepherdService.isTourActive()) {
this.shepherdService.hideCurrentStep();
}
- if (switchTab && !this.startPipelineAfterStorage) {
- this.router.navigate(['pipelines']);
- }
- if (this.startPipelineAfterStorage) {
- this.router.navigate(['pipelines'], {
- queryParams: { pipeline: pipelineId },
- });
+ if (this.storageOptions.navigateToPipelineOverview) {
+ this.navigateToPipelineOverview();
}
}
- hide() {
- this.dialogRef.close();
+ navigateToPipelineOverview(): void {
+ this.hide(true);
+ this.router.navigate(['pipelines']);
+ }
+
+ hide(skipReload: boolean) {
+ let reloadConfig = undefined;
+ if (!skipReload) {
+ reloadConfig = this.operationSuccess
+ ? { reload: true, pipelineId: this.pipelineId }
+ : undefined;
+ }
+ this.dialogRef.close(reloadConfig);
}
}
diff --git a/ui/src/app/editor/editor.component.html
b/ui/src/app/editor/editor.component.html
index fae4332d0e..27da459f4a 100644
--- a/ui/src/app/editor/editor.component.html
+++ b/ui/src/app/editor/editor.component.html
@@ -18,25 +18,25 @@
<div fxLayout="column" class="page-container">
<div
- class="fixed-height"
+ fxFlex="100"
+ fxLayout="column"
+ fxLayoutAlign="center center"
+ *ngIf="!allElementsLoaded"
+ >
+ <mat-spinner
+ [diameter]="30"
+ color="accent"
+ mode="indeterminate"
+ ></mat-spinner>
+ <p>Preparing pipeline editor...</p>
+ </div>
+ <div
+ *ngIf="allElementsLoaded"
+ class="fixed-height editor-container-inner"
fxLayout="row"
fxFlex="100"
- class="editor-container-inner"
>
<div fxFlex="250px">
- <div
- fxFlex="100"
- fxLayout="column"
- fxLayoutAlign="center center"
- *ngIf="!allElementsLoaded"
- >
- <mat-spinner
- [diameter]="30"
- color="accent"
- mode="indeterminate"
- ></mat-spinner>
- <p>Loading pipeline elements...</p>
- </div>
<div
id="shepherd-test"
style="
diff --git a/ui/src/app/editor/editor.component.ts
b/ui/src/app/editor/editor.component.ts
index 59a7bfacf3..a6ed4a5a0b 100644
--- a/ui/src/app/editor/editor.component.ts
+++ b/ui/src/app/editor/editor.component.ts
@@ -47,16 +47,15 @@ export class EditorComponent implements OnInit {
) {}
ngOnInit() {
- this.activatedRoute.params.subscribe(params => {
- if (params.pipelineId) {
- this.currentModifiedPipelineId = params.pipelineId;
- } else {
- this.breadcrumbService.updateBreadcrumb([
- SpPipelineRoutes.BASE,
- { label: 'New Pipeline' },
- ]);
- }
- });
+ const pipelineId = this.activatedRoute.snapshot.params.pipelineId;
+ if (pipelineId) {
+ this.currentModifiedPipelineId = pipelineId;
+ } else {
+ this.breadcrumbService.updateBreadcrumb([
+ SpPipelineRoutes.BASE,
+ { label: 'New Pipeline' },
+ ]);
+ }
zip(
this.pipelineElementService.getDataStreams(),
this.pipelineElementService.getDataProcessors(),
diff --git a/ui/src/app/editor/editor.module.ts
b/ui/src/app/editor/editor.module.ts
index 8e647b451a..f1d0090ee2 100644
--- a/ui/src/app/editor/editor.module.ts
+++ b/ui/src/app/editor/editor.module.ts
@@ -74,6 +74,7 @@ import { MatProgressBarModule } from
'@angular/material/progress-bar';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatChipsModule } from '@angular/material/chips';
import { MatSliderModule } from '@angular/material/slider';
+import { SavePipelineSettingsComponent } from
'./dialog/save-pipeline/save-pipeline-settings/save-pipeline-settings.component';
@NgModule({
imports: [
@@ -138,6 +139,7 @@ import { MatSliderModule } from '@angular/material/slider';
PipelineComponent,
PropertySelectionComponent,
SavePipelineComponent,
+ SavePipelineSettingsComponent,
SafeCss,
],
providers: [SafeCss],
diff --git a/ui/src/app/editor/model/editor.model.ts
b/ui/src/app/editor/model/editor.model.ts
index c92142950c..54c2ba151e 100644
--- a/ui/src/app/editor/model/editor.model.ts
+++ b/ui/src/app/editor/model/editor.model.ts
@@ -21,10 +21,12 @@ import {
DataSinkInvocation,
SpDataStream,
} from '@streampipes/platform-services';
-import { InjectionToken } from '@angular/core';
-export interface PipelineElementHolder {
- [key: string]: PipelineElementUnion[];
+export interface PipelineStorageOptions {
+ startPipelineAfterStorage: boolean;
+ navigateToPipelineOverview: boolean;
+ updateModeActive: boolean;
+ updateMode: 'update' | 'clone';
}
export interface PipelineElementPosition {
@@ -55,25 +57,12 @@ export interface PipelineElementConfig {
payload: PipelineElementUnion;
}
-export interface PipelineElementRecommendationLayout {
- skewStyle: any;
- unskewStyle: any;
- unskewStyleLabel: any;
- type: string;
-}
-
export enum PipelineElementType {
DataStream,
DataProcessor,
DataSink,
}
-export interface TabsModel {
- title: string;
- type: PipelineElementIdentifier;
- shorthand: string;
-}
-
export type PipelineElementUnion =
| SpDataStream
| DataProcessorInvocation
@@ -83,8 +72,6 @@ export type InvocablePipelineElementUnion =
| DataProcessorInvocation
| DataSinkInvocation;
-export const PIPELINE_ELEMENT_TOKEN = new
InjectionToken<{}>('pipelineElement');
-
export type PipelineElementIdentifier =
| 'org.apache.streampipes.model.SpDataStream'
| 'org.apache.streampipes.model.graph.DataProcessorInvocation'
diff --git
a/ui/src/app/pipelines/components/pipeline-overview/pipeline-overview.component.html
b/ui/src/app/pipelines/components/pipeline-overview/pipeline-overview.component.html
index 0c262d080d..924a9e1004 100644
---
a/ui/src/app/pipelines/components/pipeline-overview/pipeline-overview.component.html
+++
b/ui/src/app/pipelines/components/pipeline-overview/pipeline-overview.component.html
@@ -135,9 +135,9 @@
<th mat-header-cell mat-sort-header *matHeaderCellDef>Name</th>
<td mat-cell *matCellDef="let pipeline">
<h4 style="margin-bottom: 0">{{ pipeline.name }}</h4>
- <h5>
+ <span>
{{ pipeline.description !== '' ? pipeline.description : '-' }}
- </h5>
+ </span>
</td>
</ng-container>
@@ -170,7 +170,6 @@
mat-icon-button
matTooltip="Modify pipeline"
matTooltipPosition="above"
- [disabled]="pipeline.running"
*ngIf="hasPipelineWritePrivileges"
(click)="
pipelineOperationsService.modifyPipeline(pipeline._id)
diff --git
a/ui/src/app/pipelines/components/pipeline-overview/pipeline-overview.component.ts
b/ui/src/app/pipelines/components/pipeline-overview/pipeline-overview.component.ts
index 3519316ccb..aa78ffac1c 100644
---
a/ui/src/app/pipelines/components/pipeline-overview/pipeline-overview.component.ts
+++
b/ui/src/app/pipelines/components/pipeline-overview/pipeline-overview.component.ts
@@ -46,9 +46,6 @@ import { Subscription } from 'rxjs';
export class PipelineOverviewComponent implements OnInit, OnDestroy {
_pipelines: Pipeline[];
- @Input()
- pipelineToStart: Pipeline;
-
@Output()
refreshPipelinesEmitter: EventEmitter<boolean> =
new EventEmitter<boolean>();
@@ -93,16 +90,6 @@ export class PipelineOverviewComponent implements OnInit,
OnDestroy {
);
});
this.toggleRunningOperation = this.toggleRunningOperation.bind(this);
-
- if (this.pipelineToStart) {
- if (!this.pipelineToStart.running) {
- this.pipelineOperationsService.startPipeline(
- this.pipelineToStart._id,
- this.refreshPipelinesEmitter,
- this.toggleRunningOperation,
- );
- }
- }
}
toggleRunningOperation(currentOperation: string) {
diff --git
a/ui/src/app/pipelines/dialog/pipeline-status/pipeline-status-dialog.component.ts
b/ui/src/app/pipelines/dialog/pipeline-status/pipeline-status-dialog.component.ts
index c60548217f..31c411bdbf 100644
---
a/ui/src/app/pipelines/dialog/pipeline-status/pipeline-status-dialog.component.ts
+++
b/ui/src/app/pipelines/dialog/pipeline-status/pipeline-status-dialog.component.ts
@@ -23,7 +23,6 @@ import {
} from '@streampipes/platform-services';
import { Component, Input, OnInit } from '@angular/core';
import { PipelineAction } from '../../model/pipeline-model';
-import { ShepherdService } from '../../../services/tour/shepherd.service';
@Component({
selector: 'sp-pipeline-status-dialog',
@@ -44,7 +43,6 @@ export class PipelineStatusDialogComponent implements OnInit {
constructor(
private dialogRef: DialogRef<PipelineStatusDialogComponent>,
private pipelineService: PipelineService,
- private shepherdService: ShepherdService,
) {}
ngOnInit(): void {
@@ -64,9 +62,6 @@ export class PipelineStatusDialogComponent implements OnInit {
msg => {
this.pipelineOperationStatus = msg;
this.operationInProgress = false;
- if (this.shepherdService.isTourActive()) {
- this.shepherdService.trigger('pipeline-started');
- }
},
error => {
this.operationInProgress = false;
diff --git a/ui/src/app/pipelines/pipelines.component.html
b/ui/src/app/pipelines/pipelines.component.html
index fa7fc442f4..73338ecaba 100644
--- a/ui/src/app/pipelines/pipelines.component.html
+++ b/ui/src/app/pipelines/pipelines.component.html
@@ -83,7 +83,6 @@
<div fxFlex="90">
<sp-pipeline-overview
[pipelines]="pipelines"
- [pipelineToStart]="pipelineToStart"
(refreshPipelinesEmitter)="refreshPipelines()"
*ngIf="pipelinesReady"
></sp-pipeline-overview>
diff --git a/ui/src/app/pipelines/pipelines.component.ts
b/ui/src/app/pipelines/pipelines.component.ts
index 55f13b3927..1a4ef6da47 100644
--- a/ui/src/app/pipelines/pipelines.component.ts
+++ b/ui/src/app/pipelines/pipelines.component.ts
@@ -31,7 +31,7 @@ import {
SpBreadcrumbService,
} from '@streampipes/shared-ui';
import { StartAllPipelinesDialogComponent } from
'./dialog/start-all-pipelines/start-all-pipelines-dialog.component';
-import { ActivatedRoute, Router } from '@angular/router';
+import { Router } from '@angular/router';
import { AuthService } from '../services/auth.service';
import { UserPrivilege } from '../_enums/user-privilege.enum';
import { SpPipelineRoutes } from './pipelines.routes';
@@ -50,9 +50,6 @@ export class PipelinesComponent implements OnInit, OnDestroy {
starting: boolean;
stopping: boolean;
- pipelineIdToStart: string;
- pipelineToStart: Pipeline;
-
pipelinesReady = false;
hasPipelineWritePrivileges = false;
@@ -61,15 +58,12 @@ export class PipelinesComponent implements OnInit,
OnDestroy {
isAdminRole = false;
tutorialActive = false;
-
- activatedRouteSubscription: Subscription;
tutorialActiveSubscription: Subscription;
userSubscription: Subscription;
constructor(
private pipelineService: PipelineService,
private dialogService: DialogService,
- private activatedRoute: ActivatedRoute,
private authService: AuthService,
private currentUserService: CurrentUserService,
private router: Router,
@@ -95,17 +89,11 @@ export class PipelinesComponent implements OnInit,
OnDestroy {
);
},
);
- this.activatedRouteSubscription =
- this.activatedRoute.queryParams.subscribe(params => {
- if (params['pipeline']) {
- this.pipelineIdToStart = params['pipeline'];
- }
- if (params.startTutorial) {
- this.startPipelineTour();
- }
- this.getPipelines();
- this.getFunctions();
- });
+ if (this.shepherdService.isTourActive()) {
+ this.shepherdService.trigger('pipeline-started');
+ }
+ this.getPipelines();
+ this.getFunctions();
this.tutorialActiveSubscription =
this.shepherdService.tutorialActive$.subscribe(tutorialActive => {
this.tutorialActive = tutorialActive;
@@ -123,21 +111,10 @@ export class PipelinesComponent implements OnInit,
OnDestroy {
this.pipelines = [];
this.pipelineService.getPipelines().subscribe(pipelines => {
this.pipelines = pipelines;
- this.checkForImmediateStart(pipelines);
this.pipelinesReady = true;
});
}
- checkForImmediateStart(pipelines: Pipeline[]) {
- this.pipelineToStart = undefined;
- pipelines.forEach(pipeline => {
- if (pipeline._id === this.pipelineIdToStart) {
- this.pipelineToStart = pipeline;
- }
- });
- this.pipelineIdToStart = undefined;
- }
-
checkCurrentSelectionStatus(status) {
let active = true;
this.pipelines.forEach(pipeline => {
@@ -184,7 +161,6 @@ export class PipelinesComponent implements OnInit,
OnDestroy {
}
ngOnDestroy() {
- this.activatedRouteSubscription?.unsubscribe();
this.userSubscription?.unsubscribe();
this.tutorialActiveSubscription?.unsubscribe();
}
diff --git a/ui/src/app/services/tour/create-pipeline-tour.constants.ts
b/ui/src/app/services/tour/create-pipeline-tour.constants.ts
index c99c9f5d69..39209c18a1 100644
--- a/ui/src/app/services/tour/create-pipeline-tour.constants.ts
+++ b/ui/src/app/services/tour/create-pipeline-tour.constants.ts
@@ -136,7 +136,7 @@ export default {
{
stepId: 'step-15',
title: 'Save Pipeline Dialog',
- text: '<p>Click on <b>Save and go to pipeline view</b> to
start the pipeline.</p>',
+ text: '<p>Click on <b>Apply</b> to start the pipeline.</p>',
attachToElement: '[data-cy="sp-editor-save"]',
attachPosition: 'top',
buttons: ['cancel'],
diff --git a/ui/src/app/services/tour/shepherd.service.ts
b/ui/src/app/services/tour/shepherd.service.ts
index 069c33f5d8..bab7b97155 100644
--- a/ui/src/app/services/tour/shepherd.service.ts
+++ b/ui/src/app/services/tour/shepherd.service.ts
@@ -206,14 +206,6 @@ export class ShepherdService {
this.startTour(this.tourProviderService.getTourById('adapter'));
}
- setTimeWaitMillis(value) {
- this.tourProviderService.setTime(value);
- }
-
- getTimeWaitMillis() {
- return this.tourProviderService.getTime();
- }
-
changeTutorialStatus(tutorialActive: boolean): void {
this.tutorialActive = tutorialActive;
this.tutorialActive$.next(tutorialActive);
diff --git a/ui/src/scss/sp/main.scss b/ui/src/scss/sp/main.scss
index f1c21f0d0d..269701f473 100644
--- a/ui/src/scss/sp/main.scss
+++ b/ui/src/scss/sp/main.scss
@@ -861,10 +861,10 @@ label {
.error-message {
background-color: black;
font:
- 0.8rem Inconsolata,
+ 0.7rem Inconsolata,
monospace;
text-shadow: 0 0 5px #c8c8c8;
color: white;
- padding: 10px;
width: 100%;
+ max-width: 100%;
}
diff --git a/ui/src/scss/sp/shepherd-new.scss b/ui/src/scss/sp/shepherd-new.scss
index 6da36608a8..2615e66f72 100644
--- a/ui/src/scss/sp/shepherd-new.scss
+++ b/ui/src/scss/sp/shepherd-new.scss
@@ -54,6 +54,7 @@
display: flex;
justify-content: flex-end;
padding: 0 0.75rem 0.75rem;
+ background: white;
}
.shepherd-footer .shepherd-button:last-child {
@@ -114,6 +115,7 @@
font-size: 1rem;
line-height: 1.3em;
padding: 0.75em;
+ background: white;
}
.shepherd-text p {
@@ -168,11 +170,10 @@
height: 16px;
position: absolute;
width: 16px;
- z-index: -1;
}
.shepherd-arrow:before {
- background: #fafafa;
+ background: lightblue;
content: '';
transform: rotate(45deg);
}