Add workflow execution page 55/143555/5
authorFiete Ostkamp <fiete.ostkamp@telekom.de>
Mon, 9 Mar 2026 14:20:52 +0000 (15:20 +0100)
committerFiete Ostkamp <fiete.ostkamp@telekom.de>
Mon, 9 Mar 2026 16:06:41 +0000 (17:06 +0100)
- add new execute page to sidepane
- execute page features
  - execution setup
  - execution history (not yet working)
  - live view (not yet working)
- fix 'lateinit property workFlowData has not been initialized'
  error on /workflow-spec endpoint

Issue-ID: CCSDK-4184
Change-Id: I6091a023638bf54d9acab592c9d0c94c00c4ceb6
Signed-off-by: Fiete Ostkamp <fiete.ostkamp@telekom.de>
96 files changed:
.gitignore
cds-ui/application/pom.xml
cds-ui/client/pom.xml
cds-ui/designer-client/pom.xml
cds-ui/designer-client/src/app/app-routing.module.ts
cds-ui/designer-client/src/app/common/constants/app-constants.ts
cds-ui/designer-client/src/app/modules/feature-modules/execution/execution-api.service.ts [new file with mode: 0644]
cds-ui/designer-client/src/app/modules/feature-modules/execution/execution-dashboard/execution-dashboard.component.css [new file with mode: 0644]
cds-ui/designer-client/src/app/modules/feature-modules/execution/execution-dashboard/execution-dashboard.component.html [new file with mode: 0644]
cds-ui/designer-client/src/app/modules/feature-modules/execution/execution-dashboard/execution-dashboard.component.ts [new file with mode: 0644]
cds-ui/designer-client/src/app/modules/feature-modules/execution/execution-history/execution-history.component.css [new file with mode: 0644]
cds-ui/designer-client/src/app/modules/feature-modules/execution/execution-history/execution-history.component.html [new file with mode: 0644]
cds-ui/designer-client/src/app/modules/feature-modules/execution/execution-history/execution-history.component.ts [new file with mode: 0644]
cds-ui/designer-client/src/app/modules/feature-modules/execution/execution-setup/execution-setup.component.css [new file with mode: 0644]
cds-ui/designer-client/src/app/modules/feature-modules/execution/execution-setup/execution-setup.component.html [new file with mode: 0644]
cds-ui/designer-client/src/app/modules/feature-modules/execution/execution-setup/execution-setup.component.ts [new file with mode: 0644]
cds-ui/designer-client/src/app/modules/feature-modules/execution/execution.module.ts [new file with mode: 0644]
cds-ui/designer-client/src/app/modules/feature-modules/execution/execution.routing.module.ts [new file with mode: 0644]
cds-ui/designer-client/src/app/modules/feature-modules/execution/live-view/live-view.component.css [new file with mode: 0644]
cds-ui/designer-client/src/app/modules/feature-modules/execution/live-view/live-view.component.html [new file with mode: 0644]
cds-ui/designer-client/src/app/modules/feature-modules/execution/live-view/live-view.component.ts [new file with mode: 0644]
cds-ui/designer-client/src/app/modules/feature-modules/packages/configuration-dashboard/configuration-dashboard.component.html
cds-ui/designer-client/src/app/modules/feature-modules/packages/configuration-dashboard/configuration-dashboard.component.ts
cds-ui/designer-client/src/app/modules/shared-modules/header/header.component.html
cds-ui/e2e-playwright/mock-processor/fixtures/blueprints.json
cds-ui/e2e-playwright/mock-processor/fixtures/workflow-specs.json [new file with mode: 0644]
cds-ui/e2e-playwright/mock-processor/server.js
cds-ui/e2e-playwright/tests/execution.spec.ts [new file with mode: 0644]
cds-ui/pom.xml
cds-ui/server/pom.xml
cds-ui/server/src/controllers/blueprint-rest.controller.ts
components/cba-parent/pom.xml
components/model-catalog/blueprint-model/archetype-blueprint/pom.xml
components/model-catalog/blueprint-model/cba-assembly-descriptor/pom.xml
components/model-catalog/blueprint-model/pom.xml
components/model-catalog/blueprint-model/test-blueprint-kotlin-parent/pom.xml
components/model-catalog/blueprint-model/test-blueprint/capability_cli/pom.xml
components/model-catalog/blueprint-model/test-blueprint/pom.xml
components/model-catalog/blueprint-model/test-blueprint/resource-audit/pom.xml
components/pom.xml
ms/blueprintsprocessor/application/pom.xml
ms/blueprintsprocessor/functions/ansible-awx-executor/pom.xml
ms/blueprintsprocessor/functions/blueprint-audit-status/pom.xml
ms/blueprintsprocessor/functions/cli-executor/pom.xml
ms/blueprintsprocessor/functions/config-snapshots/pom.xml
ms/blueprintsprocessor/functions/k8s-connection-plugin/pom.xml
ms/blueprintsprocessor/functions/message-prioritization/pom.xml
ms/blueprintsprocessor/functions/netconf-executor/pom.xml
ms/blueprintsprocessor/functions/pom.xml
ms/blueprintsprocessor/functions/python-executor/pom.xml
ms/blueprintsprocessor/functions/resource-resolution/pom.xml
ms/blueprintsprocessor/functions/restconf-executor/pom.xml
ms/blueprintsprocessor/functions/restful-executor/pom.xml
ms/blueprintsprocessor/modules/blueprints/blueprint-core/pom.xml
ms/blueprintsprocessor/modules/blueprints/blueprint-proto/pom.xml
ms/blueprintsprocessor/modules/blueprints/blueprint-validation/pom.xml
ms/blueprintsprocessor/modules/blueprints/pom.xml
ms/blueprintsprocessor/modules/blueprints/resource-dict/pom.xml
ms/blueprintsprocessor/modules/commons/db-lib/pom.xml
ms/blueprintsprocessor/modules/commons/grpc-lib/pom.xml
ms/blueprintsprocessor/modules/commons/message-lib/pom.xml
ms/blueprintsprocessor/modules/commons/nats-lib/pom.xml
ms/blueprintsprocessor/modules/commons/pom.xml
ms/blueprintsprocessor/modules/commons/processor-core/pom.xml
ms/blueprintsprocessor/modules/commons/rest-lib/pom.xml
ms/blueprintsprocessor/modules/commons/ssh-lib/pom.xml
ms/blueprintsprocessor/modules/inbounds/configs-api/pom.xml
ms/blueprintsprocessor/modules/inbounds/designer-api/pom.xml
ms/blueprintsprocessor/modules/inbounds/designer-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/designer/api/handler/BluePrintModelHandler.kt
ms/blueprintsprocessor/modules/inbounds/designer-api/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/designer/api/BlueprintModelControllerTest.kt
ms/blueprintsprocessor/modules/inbounds/health-api-common/pom.xml
ms/blueprintsprocessor/modules/inbounds/health-api/pom.xml
ms/blueprintsprocessor/modules/inbounds/pom.xml
ms/blueprintsprocessor/modules/inbounds/resource-api/pom.xml
ms/blueprintsprocessor/modules/inbounds/selfservice-api/pom.xml
ms/blueprintsprocessor/modules/inbounds/workflow-api/pom.xml
ms/blueprintsprocessor/modules/outbounds/pom.xml
ms/blueprintsprocessor/modules/pom.xml
ms/blueprintsprocessor/modules/services/execution-service/pom.xml
ms/blueprintsprocessor/modules/services/pom.xml
ms/blueprintsprocessor/modules/services/workflow-service/pom.xml
ms/blueprintsprocessor/parent/pom.xml
ms/blueprintsprocessor/pom.xml
ms/command-executor/pom.xml
ms/error-catalog/application/pom.xml
ms/error-catalog/core/pom.xml
ms/error-catalog/pom.xml
ms/error-catalog/services/pom.xml
ms/pom.xml
ms/py-executor/pom.xml
ms/sdclistener/application/pom.xml
ms/sdclistener/distribution/pom.xml
ms/sdclistener/parent/pom.xml
ms/sdclistener/pom.xml
pom.xml
version.properties

index a25b189..a14f3ce 100644 (file)
@@ -163,3 +163,4 @@ MacOS
 # Generated dependency list
 direct-dependencies.txt
 .coverage
+out-tsc
index 1d3308f..8eb6015 100644 (file)
@@ -25,7 +25,7 @@ limitations under the License.
     <parent>
         <groupId>org.onap.ccsdk.cds</groupId>
         <artifactId>cds-ui</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
         <relativePath>..</relativePath>
     </parent>
 
index 1432587..befcc45 100644 (file)
@@ -25,7 +25,7 @@ limitations under the License.
     <parent>
         <groupId>org.onap.ccsdk.cds</groupId>
         <artifactId>cds-ui</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
         <relativePath>..</relativePath>
     </parent>
 
index 33c00bb..44392b0 100644 (file)
@@ -25,7 +25,7 @@ limitations under the License.
     <parent>
         <groupId>org.onap.ccsdk.cds</groupId>
         <artifactId>cds-ui</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
         <relativePath>..</relativePath>
     </parent>
 
index d0a8e2a..3e937bc 100644 (file)
@@ -31,6 +31,10 @@ const routes: Routes = [
         path: 'resource-dictionary',
         loadChildren: './modules/feature-modules/resource-dictionary/resource-dictionary.module#ResourceDictionaryModule'
     },
+    {
+        path: 'execute',
+        loadChildren: './modules/feature-modules/execution/execution.module#ExecutionModule'
+    },
     // { path: '', component: MainAppComponent },
     {
         path: '',
@@ -45,4 +49,3 @@ const routes: Routes = [
 })
 export class AppRoutingModule {
 }
-
index 6f64ee1..df58b10 100644 (file)
@@ -100,7 +100,9 @@ export const BlueprintURLs = {
     getMetaDate: '/controllerblueprint/meta-data/',
     countOfAllBluePrints: '/controllerblueprint/list/count',
     getMetaDatePageable: '/controllerblueprint/metadata/paged',
-    getBlueprintByName: '/controllerblueprint/by-name/'
+    getBlueprintByName: '/controllerblueprint/by-name/',
+    getWorkflows: '/controllerblueprint/workflows/',
+    getWorkflowSpec: '/controllerblueprint/workflow-spec'
 };
 
 export const ResourceDictionaryURLs = {
@@ -124,5 +126,9 @@ export const ControllerCatalogURLs = {
     getDerivedFrom: '/controllercatalog/model-type/by-derivedfrom'
 };
 
+export const ExecutionURLs = {
+    execute: '/controllerblueprint/execute',
+};
+
 
 export const ActionElementTypeName = 'app.ActionElement';
diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/execution/execution-api.service.ts b/cds-ui/designer-client/src/app/modules/feature-modules/execution/execution-api.service.ts
new file mode 100644 (file)
index 0000000..9efbb05
--- /dev/null
@@ -0,0 +1,44 @@
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { ApiService } from '../../../common/core/services/api.typed.service';
+import { ExecutionURLs, BlueprintURLs } from '../../../common/constants/app-constants';
+import { BluePrintPage } from '../packages/model/BluePrint.model';
+
+@Injectable({
+    providedIn: 'root',
+})
+export class ExecutionApiService {
+
+    constructor(private api: ApiService<any>) {
+    }
+
+    executeBlueprint(payload: any): Observable<any> {
+        return this.api.post(ExecutionURLs.execute, payload);
+    }
+
+    getPagedPackages(pageNumber: number, pageSize: number, sortBy: string): Observable<BluePrintPage[]> {
+        const sortType = sortBy.includes('DATE') ? 'DESC' : 'ASC';
+        return this.api.get(BlueprintURLs.getPagedBlueprints, {
+            offset: pageNumber,
+            limit: pageSize,
+            sort: sortBy,
+            sortType,
+        });
+    }
+
+    getBlueprintByNameAndVersion(name: string, version: string): Observable<any> {
+        return this.api.get(BlueprintURLs.getBlueprintByName + name + '/version/' + version);
+    }
+
+    getWorkflows(name: string, version: string): Observable<any> {
+        return this.api.getCustomized(BlueprintURLs.getWorkflows + name + '/' + version);
+    }
+
+    getWorkflowSpec(name: string, version: string, workflowName: string): Observable<any> {
+        return this.api.post(BlueprintURLs.getWorkflowSpec, {
+            blueprintName: name,
+            version,
+            workflowName,
+        });
+    }
+}
diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/execution/execution-dashboard/execution-dashboard.component.css b/cds-ui/designer-client/src/app/modules/feature-modules/execution/execution-dashboard/execution-dashboard.component.css
new file mode 100644 (file)
index 0000000..277ff72
--- /dev/null
@@ -0,0 +1,11 @@
+.tab-content {
+    padding: 20px 0;
+}
+
+.nav-tabs .nav-link {
+    cursor: pointer;
+}
+
+.nav-tabs .nav-link.active {
+    font-weight: 600;
+}
diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/execution/execution-dashboard/execution-dashboard.component.html b/cds-ui/designer-client/src/app/modules/feature-modules/execution/execution-dashboard/execution-dashboard.component.html
new file mode 100644 (file)
index 0000000..ce46677
--- /dev/null
@@ -0,0 +1,64 @@
+<app-header></app-header>
+
+<div class="new-wrapper">
+    <div class="container-fluid main-container">
+        <header class="page-title" style="padding: 16px 30px 12px;">
+            <div class="row">
+                <h2 class="col m-0">
+                    <ul class="breadcrumb-header">
+                        <li><a routerLink="/packages">CBA Packages</a></li>
+                        <i class="fa fa-angle-right ml-2 mr-2"></i>
+                        <li>Execute</li>
+                    </ul>
+                </h2>
+            </div>
+        </header>
+        <div class="container-fluid body-container">
+            <div class="container">
+                <nav class="row">
+                    <div class="col">
+                        <div class="nav nav-tabs" id="execution-nav-tab" role="tablist">
+                            <a class="nav-item nav-link"
+                               [class.active]="activeTab === 'setup'"
+                               (click)="selectTab('setup')"
+                               id="nav-setup-tab"
+                               role="tab">
+                                <i class="fa fa-cog mr-1"></i> Execution Setup
+                            </a>
+                            <a class="nav-item nav-link"
+                               [class.active]="activeTab === 'history'"
+                               (click)="selectTab('history')"
+                               id="nav-history-tab"
+                               role="tab">
+                                <i class="fa fa-history mr-1"></i> Execution History
+                            </a>
+                            <a class="nav-item nav-link"
+                               [class.active]="activeTab === 'liveview'"
+                               (click)="selectTab('liveview')"
+                               id="nav-liveview-tab"
+                               role="tab">
+                                <i class="fa fa-terminal mr-1"></i> Live View
+                            </a>
+                        </div>
+                    </div>
+                </nav>
+
+                <div class="tab-content" id="execution-tab-content">
+                    <div *ngIf="activeTab === 'setup'" class="tab-pane fade show active" role="tabpanel">
+                        <app-execution-setup
+                            [prefilledName]="prefilledName"
+                            [prefilledVersion]="prefilledVersion"
+                            (executionCompleted)="selectTab('liveview')">
+                        </app-execution-setup>
+                    </div>
+                    <div *ngIf="activeTab === 'history'" class="tab-pane fade show active" role="tabpanel">
+                        <app-execution-history></app-execution-history>
+                    </div>
+                    <div *ngIf="activeTab === 'liveview'" class="tab-pane fade show active" role="tabpanel">
+                        <app-live-view></app-live-view>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/execution/execution-dashboard/execution-dashboard.component.ts b/cds-ui/designer-client/src/app/modules/feature-modules/execution/execution-dashboard/execution-dashboard.component.ts
new file mode 100644 (file)
index 0000000..d22babc
--- /dev/null
@@ -0,0 +1,34 @@
+import { Component, OnInit } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+
+@Component({
+    selector: 'app-execution-dashboard',
+    templateUrl: './execution-dashboard.component.html',
+    styleUrls: ['./execution-dashboard.component.css'],
+})
+export class ExecutionDashboardComponent implements OnInit {
+
+    activeTab = 'setup';
+
+    // Pre-filled from query params when navigating from package detail
+    prefilledName = '';
+    prefilledVersion = '';
+
+    constructor(private route: ActivatedRoute) {
+    }
+
+    ngOnInit() {
+        this.route.queryParams.subscribe(params => {
+            if (params.name) {
+                this.prefilledName = params.name;
+            }
+            if (params.version) {
+                this.prefilledVersion = params.version;
+            }
+        });
+    }
+
+    selectTab(tab: string) {
+        this.activeTab = tab;
+    }
+}
diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/execution/execution-history/execution-history.component.css b/cds-ui/designer-client/src/app/modules/feature-modules/execution/execution-history/execution-history.component.css
new file mode 100644 (file)
index 0000000..7eeb0a9
--- /dev/null
@@ -0,0 +1,20 @@
+.execution-history-container {
+    padding: 15px 0;
+}
+
+.history-row {
+    cursor: pointer;
+}
+
+.request-id {
+    font-family: monospace;
+    font-size: 12px;
+    max-width: 220px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+}
+
+.empty-state i {
+    display: block;
+}
diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/execution/execution-history/execution-history.component.html b/cds-ui/designer-client/src/app/modules/feature-modules/execution/execution-history/execution-history.component.html
new file mode 100644 (file)
index 0000000..e1ec19e
--- /dev/null
@@ -0,0 +1,48 @@
+<div class="execution-history-container">
+    <div class="row mb-3" *ngIf="hasHistory">
+        <div class="col text-right">
+            <button class="btn btn-sm btn-outline-secondary" (click)="clearHistory()">
+                <i class="fa fa-trash mr-1"></i> Clear History
+            </button>
+        </div>
+    </div>
+
+    <div *ngIf="!hasHistory" class="empty-state text-center p-5">
+        <i class="fa fa-history fa-3x text-muted mb-3"></i>
+        <p class="text-muted">No execution history yet.</p>
+        <p class="text-muted small">Execute a blueprint from the Execution Setup tab to see results here.</p>
+    </div>
+
+    <table class="table table-hover" *ngIf="hasHistory">
+        <thead>
+            <tr>
+                <th>Request ID</th>
+                <th>Blueprint</th>
+                <th>Version</th>
+                <th>Action</th>
+                <th>Status</th>
+                <th>Timestamp</th>
+            </tr>
+        </thead>
+        <tbody>
+            <tr *ngFor="let exec of executions"
+                (click)="selectExecution(exec)"
+                [class.table-active]="selectedExecution === exec"
+                class="history-row">
+                <td class="request-id">{{ exec.requestId }}</td>
+                <td>{{ exec.blueprintName }}</td>
+                <td>{{ exec.blueprintVersion }}</td>
+                <td>{{ exec.actionName }}</td>
+                <td>
+                    <span class="badge"
+                          [class.badge-success]="exec.status === 'success'"
+                          [class.badge-danger]="exec.status === 'failure'"
+                          [class.badge-warning]="exec.status === 'pending'">
+                        {{ exec.status }}
+                    </span>
+                </td>
+                <td>{{ exec.timestamp }}</td>
+            </tr>
+        </tbody>
+    </table>
+</div>
diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/execution/execution-history/execution-history.component.ts b/cds-ui/designer-client/src/app/modules/feature-modules/execution/execution-history/execution-history.component.ts
new file mode 100644 (file)
index 0000000..03b4b3f
--- /dev/null
@@ -0,0 +1,55 @@
+import { Component, OnInit } from '@angular/core';
+
+interface ExecutionRecord {
+    requestId: string;
+    blueprintName: string;
+    blueprintVersion: string;
+    actionName: string;
+    status: string;
+    timestamp: string;
+}
+
+@Component({
+    selector: 'app-execution-history',
+    templateUrl: './execution-history.component.html',
+    styleUrls: ['./execution-history.component.css'],
+})
+export class ExecutionHistoryComponent implements OnInit {
+
+    executions: ExecutionRecord[] = [];
+    selectedExecution: ExecutionRecord = null;
+
+    constructor() {
+    }
+
+    ngOnInit() {
+        this.loadHistory();
+    }
+
+    loadHistory() {
+        // History is populated from local session storage for now.
+        // API-based audit history can be added when the backend enhancement is available.
+        const raw = sessionStorage.getItem('cds-execution-history');
+        if (raw) {
+            try {
+                this.executions = JSON.parse(raw);
+            } catch (_) {
+                this.executions = [];
+            }
+        }
+    }
+
+    selectExecution(exec: ExecutionRecord) {
+        this.selectedExecution = exec;
+    }
+
+    clearHistory() {
+        this.executions = [];
+        sessionStorage.removeItem('cds-execution-history');
+        this.selectedExecution = null;
+    }
+
+    get hasHistory(): boolean {
+        return this.executions.length > 0;
+    }
+}
diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/execution/execution-setup/execution-setup.component.css b/cds-ui/designer-client/src/app/modules/feature-modules/execution/execution-setup/execution-setup.component.css
new file mode 100644 (file)
index 0000000..c0a831e
--- /dev/null
@@ -0,0 +1,86 @@
+.execution-setup-container {
+    padding: 15px 0;
+}
+
+.setup-card,
+.editor-card,
+.response-card {
+    border: 1px solid #e0e0e0;
+    border-radius: 4px;
+}
+
+.setup-card .card-title,
+.editor-card .card-title,
+.response-card .card-title {
+    font-size: 14px;
+    font-weight: 600;
+    color: #333;
+    margin-bottom: 15px;
+}
+
+.form-group label {
+    font-size: 12px;
+    font-weight: 600;
+    color: #666;
+    margin-bottom: 4px;
+}
+
+.btn-execute {
+    min-width: 120px;
+}
+
+.ace-editor-wrapper {
+    border: 1px solid #e0e0e0;
+    border-radius: 3px;
+}
+
+/* Action Inputs */
+.action-inputs-section {
+    border-top: 1px solid #e0e0e0;
+    padding-top: 12px;
+}
+
+.section-label {
+    font-size: 13px;
+    font-weight: 600;
+    color: #333;
+    margin-bottom: 10px;
+    display: block;
+}
+
+.input-field-label {
+    font-size: 11px;
+    font-weight: 600;
+    color: #555;
+    margin-bottom: 2px;
+}
+
+.complex-input-group {
+    margin-bottom: 8px;
+}
+
+.complex-group-label {
+    font-size: 12px;
+    font-weight: 600;
+    color: #444;
+    margin-bottom: 6px;
+    display: block;
+}
+
+.complex-fields {
+    padding-left: 10px;
+    border-left: 2px solid #e0e0e0;
+}
+
+.complex-fields .form-group {
+    margin-bottom: 8px;
+}
+
+.input-group-block {
+    margin-bottom: 4px;
+}
+
+.action-inputs-loading,
+.action-inputs-error {
+    padding: 6px 0;
+}
diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/execution/execution-setup/execution-setup.component.html b/cds-ui/designer-client/src/app/modules/feature-modules/execution/execution-setup/execution-setup.component.html
new file mode 100644 (file)
index 0000000..0b6984a
--- /dev/null
@@ -0,0 +1,191 @@
+<div class="execution-setup-container">
+    <div class="row">
+        <!-- Left column: form fields -->
+        <div class="col-md-4">
+            <div class="card setup-card">
+                <div class="card-body">
+                    <h5 class="card-title">Blueprint Selection</h5>
+
+                    <div class="form-group">
+                        <label for="blueprintName">Blueprint Name</label>
+                        <select id="blueprintName"
+                                class="form-control"
+                                [(ngModel)]="blueprintName"
+                                (ngModelChange)="onBlueprintNameChange()">
+                            <option value="" disabled>Select a blueprint...</option>
+                            <option *ngFor="let name of uniquePackageNames" [value]="name">
+                                {{ name }}
+                            </option>
+                        </select>
+                    </div>
+
+                    <div class="form-group">
+                        <label for="blueprintVersion">Version</label>
+                        <select id="blueprintVersion"
+                                class="form-control"
+                                [(ngModel)]="blueprintVersion"
+                                (ngModelChange)="onBlueprintVersionChange()"
+                                [disabled]="availableVersions.length === 0">
+                            <option value="" disabled>Select version...</option>
+                            <option *ngFor="let ver of availableVersions" [value]="ver">
+                                {{ ver }}
+                            </option>
+                        </select>
+                    </div>
+
+                    <div class="form-group">
+                        <label for="actionName">Action Name</label>
+                        <select id="actionName"
+                                class="form-control"
+                                [(ngModel)]="actionName"
+                                (ngModelChange)="onActionChange()"
+                                [disabled]="availableActions.length === 0">
+                            <option value="" disabled>Select an action...</option>
+                            <option *ngFor="let action of availableActions" [value]="action">
+                                {{ action }}
+                            </option>
+                        </select>
+                    </div>
+
+                    <!-- Action Inputs: loading state -->
+                    <div *ngIf="loadingSpec" class="action-inputs-loading mt-2">
+                        <i class="fa fa-spinner fa-spin mr-1"></i>
+                        <small class="text-muted">Loading action inputs...</small>
+                    </div>
+
+                    <!-- Action Inputs: spec load failed -->
+                    <div *ngIf="actionName && !loadingSpec && specLoadFailed" class="action-inputs-error mt-2">
+                        <small class="text-muted">
+                            <i class="fa fa-exclamation-triangle mr-1"></i>
+                            Could not load input schema. Edit the JSON payload manually.
+                        </small>
+                    </div>
+
+                    <!-- Action Inputs: dynamic form -->
+                    <div *ngIf="actionName && !loadingSpec && inputGroups.length > 0"
+                         class="action-inputs-section mt-3" id="actionInputs">
+                        <label class="section-label">Action Inputs</label>
+
+                        <div *ngFor="let group of inputGroups" class="input-group-block">
+                            <!-- Primitive input (not a data type reference) -->
+                            <div *ngIf="!group.isComplex" class="form-group">
+                                <label [for]="'input-' + group.inputName" class="input-field-label">
+                                    {{ group.inputName }}
+                                    <span class="text-danger" *ngIf="group.required">*</span>
+                                </label>
+                                <input *ngIf="group.type !== 'boolean'"
+                                       [id]="'input-' + group.inputName"
+                                       class="form-control form-control-sm"
+                                       [type]="group.type === 'integer' ? 'number' : 'text'"
+                                       [(ngModel)]="group.value"
+                                       (ngModelChange)="onFieldChange()"
+                                       [placeholder]="group.description"
+                                       [attr.data-input-name]="group.inputName">
+                                <select *ngIf="group.type === 'boolean'"
+                                        [id]="'input-' + group.inputName"
+                                        class="form-control form-control-sm"
+                                        [(ngModel)]="group.value"
+                                        (ngModelChange)="onFieldChange()"
+                                        [attr.data-input-name]="group.inputName">
+                                    <option value="">-- select --</option>
+                                    <option value="true">true</option>
+                                    <option value="false">false</option>
+                                </select>
+                            </div>
+
+                            <!-- Complex input (data type with nested fields) -->
+                            <div *ngIf="group.isComplex" class="complex-input-group">
+                                <label class="complex-group-label">
+                                    {{ group.inputName }}
+                                    <span class="text-danger" *ngIf="group.required">*</span>
+                                </label>
+                                <div class="complex-fields">
+                                    <div *ngFor="let field of group.fields" class="form-group">
+                                        <label [for]="'field-' + group.inputName + '-' + field.name"
+                                               class="input-field-label">
+                                            {{ field.name }}
+                                            <span class="text-danger" *ngIf="field.required">*</span>
+                                        </label>
+                                        <input *ngIf="field.type !== 'boolean'"
+                                               [id]="'field-' + group.inputName + '-' + field.name"
+                                               class="form-control form-control-sm"
+                                               [type]="field.type === 'integer' ? 'number' : 'text'"
+                                               [(ngModel)]="field.value"
+                                               (ngModelChange)="onFieldChange()"
+                                               [placeholder]="field.description"
+                                               [attr.data-field-name]="field.name">
+                                        <select *ngIf="field.type === 'boolean'"
+                                                [id]="'field-' + group.inputName + '-' + field.name"
+                                                class="form-control form-control-sm"
+                                                [(ngModel)]="field.value"
+                                                (ngModelChange)="onFieldChange()"
+                                                [attr.data-field-name]="field.name">
+                                            <option value="">-- select --</option>
+                                            <option value="true">true</option>
+                                            <option value="false">false</option>
+                                        </select>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+
+                    <hr>
+
+                    <div class="execution-actions">
+                        <button class="btn btn-primary btn-execute"
+                                [disabled]="!canExecute()"
+                                (click)="executeBlueprint()">
+                            <i class="fa fa-play mr-1" *ngIf="!isExecuting"></i>
+                            <i class="fa fa-spinner fa-spin mr-1" *ngIf="isExecuting"></i>
+                            {{ isExecuting ? 'Executing...' : 'Execute' }}
+                        </button>
+                        <button class="btn btn-outline-secondary ml-2"
+                                (click)="resetPayload()">
+                            <i class="fa fa-refresh mr-1"></i> Reset
+                        </button>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <!-- Right column: JSON payload editor -->
+        <div class="col-md-8">
+            <div class="card editor-card">
+                <div class="card-body">
+                    <h5 class="card-title">Request Payload (ExecutionServiceInput)</h5>
+                    <div class="ace-editor-wrapper">
+                        <ace-editor
+                            [(text)]="payloadText"
+                            [mode]="'json'"
+                            [theme]="'eclipse'"
+                            [options]="aceOptions"
+                            style="min-height: 350px; width: 100%; font-size: 13px;">
+                        </ace-editor>
+                    </div>
+                </div>
+            </div>
+
+            <!-- Response panel -->
+            <div class="card response-card mt-3" *ngIf="lastResponseText">
+                <div class="card-body">
+                    <h5 class="card-title">
+                        <i class="fa fa-check-circle text-success mr-1" *ngIf="lastResponse"></i>
+                        <i class="fa fa-times-circle text-danger mr-1" *ngIf="!lastResponse"></i>
+                        Execution Response
+                    </h5>
+                    <div class="ace-editor-wrapper">
+                        <ace-editor
+                            [text]="lastResponseText"
+                            [mode]="'json'"
+                            [theme]="'eclipse'"
+                            [readOnly]="true"
+                            [options]="{ maxLines: 20, minLines: 5, showPrintMargin: false }"
+                            style="min-height: 150px; width: 100%; font-size: 13px;">
+                        </ace-editor>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/execution/execution-setup/execution-setup.component.ts b/cds-ui/designer-client/src/app/modules/feature-modules/execution/execution-setup/execution-setup.component.ts
new file mode 100644 (file)
index 0000000..e138f28
--- /dev/null
@@ -0,0 +1,333 @@
+import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
+import { ToastrService } from 'ngx-toastr';
+import { ExecutionApiService } from '../execution-api.service';
+
+@Component({
+    selector: 'app-execution-setup',
+    templateUrl: './execution-setup.component.html',
+    styleUrls: ['./execution-setup.component.css'],
+})
+export class ExecutionSetupComponent implements OnInit {
+
+    @Input() prefilledName = '';
+    @Input() prefilledVersion = '';
+    @Output() executionCompleted = new EventEmitter<any>();
+
+    blueprintName = '';
+    blueprintVersion = '';
+    actionName = '';
+    isExecuting = false;
+
+    availablePackages: any[] = [];
+    availableVersions: string[] = [];
+    availableActions: string[] = [];
+
+    // Workflow-spec driven input form
+    inputGroups: Array<{
+        inputName: string;
+        type: string;
+        description: string;
+        required: boolean;
+        isComplex: boolean;
+        value: string;
+        fields: Array<{
+            name: string;
+            type: string;
+            description: string;
+            required: boolean;
+            value: string;
+        }>;
+    }> = [];
+    loadingSpec = false;
+    specLoadFailed = false;
+
+    payloadText = JSON.stringify(this.defaultPayload(), null, 2);
+    aceOptions: any = {
+        maxLines: 30,
+        minLines: 15,
+        autoScrollEditorIntoView: true,
+        showPrintMargin: false,
+    };
+
+    lastResponse: any = null;
+    lastResponseText = '';
+
+    constructor(
+        private executionApiService: ExecutionApiService,
+        private toastService: ToastrService,
+    ) {
+    }
+
+    ngOnInit() {
+        this.loadPackages();
+        if (this.prefilledName) {
+            this.blueprintName = this.prefilledName;
+        }
+        if (this.prefilledVersion) {
+            this.blueprintVersion = this.prefilledVersion;
+        }
+        if (this.prefilledName && this.prefilledVersion) {
+            this.buildPayload();
+        }
+    }
+
+    loadPackages() {
+        this.executionApiService.getPagedPackages(0, 1000, 'DATE').subscribe(
+            (pages: any) => {
+                const page = Array.isArray(pages) ? pages[0] : pages;
+                const content = page && page.content ? page.content : [];
+                this.availablePackages = content;
+            },
+            err => {
+                console.error('Failed to load packages', err);
+            }
+        );
+    }
+
+    onBlueprintNameChange() {
+        const matching = this.availablePackages.filter(p => p.artifactName === this.blueprintName);
+        this.availableVersions = matching.map(p => p.artifactVersion);
+        if (this.availableVersions.length === 1) {
+            this.blueprintVersion = this.availableVersions[0];
+        } else {
+            this.blueprintVersion = '';
+        }
+        this.actionName = '';
+        this.availableActions = [];
+        this.inputGroups = [];
+        this.loadingSpec = false;
+        this.specLoadFailed = false;
+        this.buildPayload();
+        if (this.blueprintVersion) {
+            this.loadActions();
+        }
+    }
+
+    onBlueprintVersionChange() {
+        this.actionName = '';
+        this.availableActions = [];
+        this.inputGroups = [];
+        this.loadingSpec = false;
+        this.specLoadFailed = false;
+        this.buildPayload();
+        if (this.blueprintVersion) {
+            this.loadActions();
+        }
+    }
+
+    loadActions() {
+        this.executionApiService.getWorkflows(this.blueprintName, this.blueprintVersion).subscribe(
+            (result: any) => {
+                if (result && Array.isArray(result.workflows)) {
+                    this.availableActions = result.workflows.slice().sort();
+                    if (this.availableActions.length === 1) {
+                        this.actionName = this.availableActions[0];
+                        this.onActionChange();
+                    }
+                }
+            },
+            err => {
+                console.error('Failed to load actions', err);
+            }
+        );
+    }
+
+    onActionChange() {
+        if (this.actionName) {
+            this.loadWorkflowSpec();
+        } else {
+            this.inputGroups = [];
+            this.specLoadFailed = false;
+            this.buildPayload();
+        }
+    }
+
+    loadWorkflowSpec() {
+        this.loadingSpec = true;
+        this.specLoadFailed = false;
+        this.inputGroups = [];
+        this.buildPayload();
+        this.executionApiService.getWorkflowSpec(
+            this.blueprintName, this.blueprintVersion, this.actionName
+        ).subscribe(
+            (spec: any) => {
+                this.loadingSpec = false;
+                this.resolveWorkflowSpec(spec);
+                this.buildPayload();
+            },
+            err => {
+                this.loadingSpec = false;
+                this.specLoadFailed = true;
+                console.error('Failed to load workflow spec', err);
+            }
+        );
+    }
+
+    resolveWorkflowSpec(spec: any) {
+        this.inputGroups = [];
+        if (!spec || !spec.workFlowData || !spec.workFlowData.inputs) {
+            return;
+        }
+        const inputs = spec.workFlowData.inputs;
+        const dataTypes = spec.dataTypes || {};
+
+        Object.keys(inputs).forEach(inputName => {
+            const inputDef = inputs[inputName];
+            const typeName = inputDef.type || 'string';
+            const dataType = dataTypes[typeName];
+
+            const group: any = {
+                inputName,
+                type: typeName,
+                description: inputDef.description || '',
+                required: !!inputDef.required,
+                isComplex: !!dataType,
+                value: '',
+                fields: [],
+            };
+
+            if (dataType && dataType.properties) {
+                group.fields = Object.keys(dataType.properties).map(fieldName => {
+                    const fieldDef = dataType.properties[fieldName];
+                    return {
+                        name: fieldName,
+                        type: fieldDef.type || 'string',
+                        description: fieldDef.description || '',
+                        required: !!fieldDef.required,
+                        value: '',
+                    };
+                });
+            }
+
+            this.inputGroups.push(group);
+        });
+    }
+
+    onFieldChange() {
+        this.buildPayload();
+    }
+
+    get uniquePackageNames(): string[] {
+        const names = new Set(this.availablePackages.map(p => p.artifactName));
+        return Array.from(names).sort();
+    }
+
+    canExecute(): boolean {
+        return this.blueprintName.length > 0
+            && this.blueprintVersion.length > 0
+            && this.payloadText.length > 0
+            && !this.isExecuting;
+    }
+
+    executeBlueprint() {
+        if (!this.canExecute()) {
+            return;
+        }
+
+        let payload: any;
+        try {
+            payload = JSON.parse(this.payloadText);
+        } catch (e) {
+            this.toastService.error('Invalid JSON payload. Please correct the syntax.');
+            return;
+        }
+
+        this.isExecuting = true;
+        this.lastResponse = null;
+        this.lastResponseText = '';
+
+        this.executionApiService.executeBlueprint(payload).subscribe(
+            response => {
+                this.isExecuting = false;
+                this.lastResponse = response;
+                this.lastResponseText = JSON.stringify(response, null, 2);
+                this.toastService.success('Execution completed successfully.');
+                this.executionCompleted.emit(response);
+            },
+            error => {
+                this.isExecuting = false;
+                const msg = (error.error && error.error.message) || error.message || 'Execution failed';
+                this.toastService.error(msg);
+                this.lastResponseText = JSON.stringify(error.error || error, null, 2);
+            }
+        );
+    }
+
+    private buildPayload() {
+        const payload = this.defaultPayload();
+        payload.actionIdentifiers.blueprintName = this.blueprintName;
+        payload.actionIdentifiers.blueprintVersion = this.blueprintVersion;
+        payload.actionIdentifiers.actionName = this.actionName;
+
+        if (this.actionName) {
+            const requestKey = this.actionName + '-request';
+            const requestObj: any = {};
+
+            this.inputGroups.forEach(group => {
+                if (group.isComplex) {
+                    const nested: any = {};
+                    group.fields.forEach(field => {
+                        nested[field.name] = this.convertValue(field.value, field.type);
+                    });
+                    requestObj[group.inputName] = nested;
+                } else {
+                    requestObj[group.inputName] = this.convertValue(group.value, group.type);
+                }
+            });
+
+            payload.payload[requestKey] = requestObj;
+        }
+
+        this.payloadText = JSON.stringify(payload, null, 2);
+    }
+
+    private convertValue(value: string, type: string): any {
+        if (!value) {
+            return (type === 'integer') ? 0 : (type === 'boolean') ? false : '';
+        }
+        if (type === 'integer') {
+            const num = parseInt(value, 10);
+            return isNaN(num) ? 0 : num;
+        }
+        if (type === 'boolean') {
+            return value === 'true';
+        }
+        return value;
+    }
+
+    resetPayload() {
+        this.inputGroups.forEach(group => {
+            group.value = '';
+            group.fields.forEach(field => { field.value = ''; });
+        });
+        this.buildPayload();
+    }
+
+    private defaultPayload(): any {
+        return {
+            commonHeader: {
+                originatorId: 'CDS-UI',
+                requestId: this.generateUUID(),
+                subRequestId: this.generateUUID(),
+                timestamp: new Date().toISOString(),
+            },
+            actionIdentifiers: {
+                blueprintName: '',
+                blueprintVersion: '',
+                actionName: '',
+                mode: 'sync',
+            },
+            payload: {},
+        };
+    }
+
+    private generateUUID(): string {
+        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
+            // tslint:disable-next-line:no-bitwise
+            const r = Math.random() * 16 | 0;
+            // tslint:disable-next-line:no-bitwise
+            const v = c === 'x' ? r : (r & 0x3 | 0x8);
+            return v.toString(16);
+        });
+    }
+}
diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/execution/execution.module.ts b/cds-ui/designer-client/src/app/modules/feature-modules/execution/execution.module.ts
new file mode 100644 (file)
index 0000000..8995456
--- /dev/null
@@ -0,0 +1,30 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { AceEditorModule } from 'ng2-ace-editor';
+import { SharedModulesModule } from '../../shared-modules/shared-modules.module';
+import { ExecutionRoutingModule } from './execution.routing.module';
+import { ExecutionDashboardComponent } from './execution-dashboard/execution-dashboard.component';
+import { ExecutionSetupComponent } from './execution-setup/execution-setup.component';
+import { ExecutionHistoryComponent } from './execution-history/execution-history.component';
+import { LiveViewComponent } from './live-view/live-view.component';
+import { ApiService } from '../../../common/core/services/api.typed.service';
+
+@NgModule({
+    declarations: [
+        ExecutionDashboardComponent,
+        ExecutionSetupComponent,
+        ExecutionHistoryComponent,
+        LiveViewComponent,
+    ],
+    imports: [
+        CommonModule,
+        FormsModule,
+        AceEditorModule,
+        SharedModulesModule,
+        ExecutionRoutingModule,
+    ],
+    providers: [ApiService],
+})
+export class ExecutionModule {
+}
diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/execution/execution.routing.module.ts b/cds-ui/designer-client/src/app/modules/feature-modules/execution/execution.routing.module.ts
new file mode 100644 (file)
index 0000000..b49646f
--- /dev/null
@@ -0,0 +1,17 @@
+import { NgModule } from '@angular/core';
+import { RouterModule, Routes } from '@angular/router';
+import { ExecutionDashboardComponent } from './execution-dashboard/execution-dashboard.component';
+
+const routes: Routes = [
+    {
+        path: '',
+        component: ExecutionDashboardComponent,
+    },
+];
+
+@NgModule({
+    imports: [RouterModule.forChild(routes)],
+    exports: [RouterModule],
+})
+export class ExecutionRoutingModule {
+}
diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/execution/live-view/live-view.component.css b/cds-ui/designer-client/src/app/modules/feature-modules/execution/live-view/live-view.component.css
new file mode 100644 (file)
index 0000000..a92f07e
--- /dev/null
@@ -0,0 +1,48 @@
+.live-view-container {
+    padding: 15px 0;
+}
+
+.status-banner {
+    padding: 12px 20px;
+    border-radius: 4px;
+    font-size: 16px;
+    font-weight: 600;
+}
+
+.status-success {
+    background-color: #d4edda;
+    color: #155724;
+    border: 1px solid #c3e6cb;
+}
+
+.status-failure {
+    background-color: #f8d7da;
+    color: #721c24;
+    border: 1px solid #f5c6cb;
+}
+
+.status-completed {
+    background-color: #d1ecf1;
+    color: #0c5460;
+    border: 1px solid #bee5eb;
+}
+
+.response-card {
+    border: 1px solid #e0e0e0;
+    border-radius: 4px;
+}
+
+.response-card .card-title {
+    font-size: 14px;
+    font-weight: 600;
+    color: #333;
+}
+
+.ace-editor-wrapper {
+    border: 1px solid #e0e0e0;
+    border-radius: 3px;
+}
+
+.empty-state i {
+    display: block;
+}
diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/execution/live-view/live-view.component.html b/cds-ui/designer-client/src/app/modules/feature-modules/execution/live-view/live-view.component.html
new file mode 100644 (file)
index 0000000..bb4182f
--- /dev/null
@@ -0,0 +1,36 @@
+<div class="live-view-container">
+    <div *ngIf="!lastResponse" class="empty-state text-center p-5">
+        <i class="fa fa-terminal fa-3x text-muted mb-3"></i>
+        <p class="text-muted">No execution response available.</p>
+        <p class="text-muted small">Execute a blueprint from the Execution Setup tab to see live results here.</p>
+    </div>
+
+    <div *ngIf="lastResponse">
+        <div class="status-banner" [ngClass]="{
+            'status-success': status === 'success',
+            'status-failure': status === 'failure',
+            'status-completed': status === 'completed'
+        }">
+            <i class="fa fa-check-circle mr-2" *ngIf="status === 'success'"></i>
+            <i class="fa fa-times-circle mr-2" *ngIf="status === 'failure'"></i>
+            <i class="fa fa-info-circle mr-2" *ngIf="status === 'completed'"></i>
+            {{ statusLabel }}
+        </div>
+
+        <div class="card response-card mt-3">
+            <div class="card-body">
+                <h5 class="card-title">Execution Response (ExecutionServiceOutput)</h5>
+                <div class="ace-editor-wrapper">
+                    <ace-editor
+                        [text]="lastResponseText"
+                        [mode]="'json'"
+                        [theme]="'eclipse'"
+                        [readOnly]="true"
+                        [options]="{ maxLines: 40, minLines: 10, showPrintMargin: false }"
+                        style="min-height: 300px; width: 100%; font-size: 13px;">
+                    </ace-editor>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/execution/live-view/live-view.component.ts b/cds-ui/designer-client/src/app/modules/feature-modules/execution/live-view/live-view.component.ts
new file mode 100644 (file)
index 0000000..dcc9e70
--- /dev/null
@@ -0,0 +1,50 @@
+import { Component, OnInit } from '@angular/core';
+
+@Component({
+    selector: 'app-live-view',
+    templateUrl: './live-view.component.html',
+    styleUrls: ['./live-view.component.css'],
+})
+export class LiveViewComponent implements OnInit {
+
+    lastResponse: any = null;
+    lastResponseText = '';
+
+    constructor() {
+    }
+
+    ngOnInit() {
+        // Load the last execution response from session storage
+        const raw = sessionStorage.getItem('cds-last-execution-response');
+        if (raw) {
+            try {
+                this.lastResponse = JSON.parse(raw);
+                this.lastResponseText = JSON.stringify(this.lastResponse, null, 2);
+            } catch (_) {
+                this.lastResponse = null;
+            }
+        }
+    }
+
+    get status(): string {
+        if (!this.lastResponse) {
+            return 'idle';
+        }
+        if (this.lastResponse.status && this.lastResponse.status.code === 200) {
+            return 'success';
+        }
+        if (this.lastResponse.status && this.lastResponse.status.code >= 400) {
+            return 'failure';
+        }
+        return 'completed';
+    }
+
+    get statusLabel(): string {
+        switch (this.status) {
+            case 'success': return 'Execution Successful';
+            case 'failure': return 'Execution Failed';
+            case 'completed': return 'Execution Completed';
+            default: return 'No Execution Data';
+        }
+    }
+}
index b5581d9..020da34 100644 (file)
                                     [title]="isMetadataValid ? 'Deploy package' : 'Fill in all required metadata fields before deploying'"><i
                                     class="fa fa-play-circle"></i> Deploy
                             </button>
+                            <button class="btn btn-sm btn-execute ml-1" (click)="goToExecute()"
+                                    title="Execute this blueprint">
+                                <i class="fa fa-bolt"></i> Execute
+                            </button>
                         </div>
                     </div>
 
index 4c48f59..f297e9a 100644 (file)
@@ -250,6 +250,15 @@ export class ConfigurationDashboardComponent extends ComponentCanDeactivate impl
         this.router.navigate(['/packages/designer', id, { actionName: this.customActionName }]);
     }
 
+    goToExecute() {
+        this.router.navigate(['/execute'], {
+            queryParams: {
+                name: this.viewedPackage.artifactName,
+                version: this.viewedPackage.artifactVersion
+            }
+        });
+    }
+
     public dropped(files: NgxFileDropEntry[]) {
 
     }
index c824a6f..91b6b1d 100644 (file)
           <span class="icon">
             <i class="icon-nav-dictionary"></i>
           </span>
+        </li>
+        <li>
+          <a routerLink="/execute">Execute</a>
+          <span class="icon">
+            <i class="fa fa-play-circle"></i>
+          </span>
         </li>
           <!-- <label title="toggle menu" for="settings">
             <span class="downarrow">
@@ -71,4 +77,4 @@
     </ul>-->
 
   </nav>
-</div>
\ No newline at end of file
+</div>
index 2b33f04..8dcc972 100644 (file)
     "artifactName": "RT-resource-resolution",
     "published": "Y",
     "updatedBy": "Selffish",
-    "tags": "test, regression"
+    "tags": "test, regression",
+    "workflows": {
+      "resource-resolution": { "steps": {}, "inputs": {}, "outputs": {} },
+      "config-assign": { "steps": {}, "inputs": {}, "outputs": {} },
+      "config-deploy": { "steps": {}, "inputs": {}, "outputs": {} }
+    }
   },
   {
     "id": "bf26fbf2-4bf5-482e-9cc1-e48e4878aceb",
     "artifactName": "vLB_CDS_KOTLIN",
     "published": "Y",
     "updatedBy": "PLATANIA, MARCO <platania@research.att.com>",
-    "tags": "test, vDNS-CDS, SCALE-OUT, MARCO"
+    "tags": "test, vDNS-CDS, SCALE-OUT, MARCO",
+    "workflows": {
+      "resource-resolution": { "steps": {}, "inputs": {}, "outputs": {} },
+      "health-check": { "steps": {}, "inputs": {}, "outputs": {} }
+    }
   },
   {
     "id": "3bca2b79-020d-4c98-ad19-a425a42c17d8",
     "artifactName": "vLB_CDS_RESTCONF",
     "published": "Y",
     "updatedBy": "Seaudi, Abdelmuhaimen <abdelmuhaimen.seaudi@orange.com>",
-    "tags": "vLB-CDS"
+    "tags": "vLB-CDS",
+    "workflows": {
+      "resource-resolution": { "steps": {}, "inputs": {}, "outputs": {} }
+    }
   },
   {
     "id": "8713ea11-fbdd-4a58-ae01-e5ba4ff42a44",
     "artifactName": "vLB_CDS",
     "published": "N",
     "updatedBy": "Seaudi, Abdelmuhaimen <abdelmuhaimen.seaudi@orange.com>",
-    "tags": "vLB_CDS"
+    "tags": "vLB_CDS",
+    "workflows": {
+      "resource-resolution": { "steps": {}, "inputs": {}, "outputs": {} },
+      "activate-restconf": { "steps": {}, "inputs": {}, "outputs": {} }
+    }
   },
   {
     "id": "d97bc302-0077-4cf7-a494-9d070767fac5",
     "artifactName": "5G_Core",
     "published": "Y",
     "updatedBy": "Thamlur Raju <TR00568434@TechMahindra.com>, Sangeeta Bellara <sangeeta.bellara@t-systems.com>",
-    "tags": "Thamlur Raju, Malinconico Aniello Paolo,Vamshi, 5G_Core"
+    "tags": "Thamlur Raju, Malinconico Aniello Paolo,Vamshi, 5G_Core",
+    "workflows": {
+      "resource-resolution": { "steps": {}, "inputs": {}, "outputs": {} },
+      "config-assign": { "steps": {}, "inputs": {}, "outputs": {} }
+    }
   },
   {
     "id": "a63117f8-2aa0-4b96-b8d2-ddaa9fa3b633",
     "artifactName": "vFW-CDS",
     "published": "Y",
     "updatedBy": "PLATANIA, MARCO <platania@research.att.com>",
-    "tags": "vFW-CDS"
+    "tags": "vFW-CDS",
+    "workflows": {
+      "resource-resolution": { "steps": {}, "inputs": {}, "outputs": {} }
+    }
   },
   {
     "id": "08dbe39c-f3db-40b7-8e77-c5ef069d4f1b",
     "artifactName": "pnf_netconf",
     "published": "N",
     "updatedBy": "Aarna Services",
-    "tags": "pnf_netconf"
+    "tags": "pnf_netconf",
+    "workflows": {
+      "config-assign": { "steps": {}, "inputs": {}, "outputs": {} },
+      "config-deploy": { "steps": {}, "inputs": {}, "outputs": {} }
+    }
   },
   {
     "id": "7b54c252-74a4-4cfa-b4f6-bdaa25c55ee7",
     "artifactName": "APACHE",
     "published": "Y",
     "updatedBy": "Lukasz Rajewski <lukasz.rajewski@orange.com>",
-    "tags": "Lukasz Rajewski, CNF"
+    "tags": "Lukasz Rajewski, CNF",
+    "workflows": {
+      "resource-resolution": { "steps": {}, "inputs": {}, "outputs": {} }
+    }
   },
   {
     "id": "9c254141-2083-47f6-980e-5b2f33563862",
     "artifactName": "ubuntu20",
     "published": "N",
     "updatedBy": "RG, ONES <romain.garel@orange.com>",
-    "tags": "ubuntu20"
+    "tags": "ubuntu20",
+    "workflows": {
+      "resource-resolution": { "steps": {}, "inputs": {}, "outputs": {} }
+    }
   }
-]
\ No newline at end of file
+]
diff --git a/cds-ui/e2e-playwright/mock-processor/fixtures/workflow-specs.json b/cds-ui/e2e-playwright/mock-processor/fixtures/workflow-specs.json
new file mode 100644 (file)
index 0000000..a7be9bc
--- /dev/null
@@ -0,0 +1,133 @@
+{
+  "resource-resolution": {
+    "inputs": {
+      "resolution-key": {
+        "type": "string",
+        "required": false,
+        "description": "Resolution key for the resource resolution"
+      },
+      "store-result": {
+        "type": "boolean",
+        "required": false,
+        "description": "Whether to store the resolved resources"
+      },
+      "resource-assignment-properties": {
+        "type": "dt-resource-assignment-properties",
+        "required": true,
+        "description": "Dynamic properties for resource assignment"
+      }
+    },
+    "outputs": {
+      "meshed-template": { "type": "json", "value": {} }
+    },
+    "dataTypes": {
+      "dt-resource-assignment-properties": {
+        "derivedFrom": "tosca.datatypes.Dynamic",
+        "description": "Dynamic DataType for resource assignment",
+        "properties": {
+          "service-instance-id": { "type": "string", "required": true, "description": "Service Instance ID" },
+          "vnf-id": { "type": "string", "required": true, "description": "VNF Instance ID" },
+          "vf-module-id": { "type": "string", "required": false, "description": "VF Module ID" }
+        }
+      }
+    }
+  },
+  "config-assign": {
+    "inputs": {
+      "resolution-key": {
+        "type": "string",
+        "required": false,
+        "description": "Resolution key for config assignment"
+      },
+      "config-assign-properties": {
+        "type": "dt-config-assign-properties",
+        "required": true,
+        "description": "Dynamic properties for config assignment"
+      }
+    },
+    "outputs": {},
+    "dataTypes": {
+      "dt-config-assign-properties": {
+        "derivedFrom": "tosca.datatypes.Dynamic",
+        "description": "Dynamic DataType for config assignment",
+        "properties": {
+          "service-instance-id": { "type": "string", "required": true, "description": "Service Instance ID" },
+          "vnf-id": { "type": "string", "required": true, "description": "VNF Instance ID" },
+          "hostname": { "type": "string", "required": false, "description": "Target hostname" }
+        }
+      }
+    }
+  },
+  "config-deploy": {
+    "inputs": {
+      "resolution-key": {
+        "type": "string",
+        "required": false,
+        "description": "Resolution key for config deployment"
+      },
+      "config-deploy-properties": {
+        "type": "dt-config-deploy-properties",
+        "required": true,
+        "description": "Dynamic properties for config deployment"
+      }
+    },
+    "outputs": {},
+    "dataTypes": {
+      "dt-config-deploy-properties": {
+        "derivedFrom": "tosca.datatypes.Dynamic",
+        "description": "Dynamic DataType for config deployment",
+        "properties": {
+          "service-instance-id": { "type": "string", "required": true, "description": "Service Instance ID" },
+          "vnf-id": { "type": "string", "required": true, "description": "VNF Instance ID" },
+          "ip-address": { "type": "string", "required": false, "description": "Device IP address" },
+          "hostname": { "type": "string", "required": false, "description": "Device hostname" }
+        }
+      }
+    }
+  },
+  "health-check": {
+    "inputs": {
+      "health-check-properties": {
+        "type": "dt-health-check-properties",
+        "required": true,
+        "description": "Health check parameters"
+      }
+    },
+    "outputs": {
+      "health-check-result": { "type": "string", "value": "" }
+    },
+    "dataTypes": {
+      "dt-health-check-properties": {
+        "derivedFrom": "tosca.datatypes.Dynamic",
+        "description": "Dynamic DataType for health check",
+        "properties": {
+          "service-instance-id": { "type": "string", "required": true, "description": "Service Instance ID" },
+          "vnf-id": { "type": "string", "required": true, "description": "VNF Instance ID" },
+          "health-check-type": { "type": "string", "required": false, "description": "Type of health check to perform" }
+        }
+      }
+    }
+  },
+  "activate-restconf": {
+    "inputs": {
+      "activate-restconf-properties": {
+        "type": "dt-activate-restconf-properties",
+        "required": true,
+        "description": "RESTCONF activation parameters"
+      }
+    },
+    "outputs": {},
+    "dataTypes": {
+      "dt-activate-restconf-properties": {
+        "derivedFrom": "tosca.datatypes.Dynamic",
+        "description": "Dynamic DataType for RESTCONF activation",
+        "properties": {
+          "service-instance-id": { "type": "string", "required": true, "description": "Service Instance ID" },
+          "vnf-id": { "type": "string", "required": true, "description": "VNF Instance ID" },
+          "ip-address": { "type": "string", "required": true, "description": "Device IP address" },
+          "port": { "type": "integer", "required": false, "description": "RESTCONF port number" }
+        }
+      }
+    }
+  }
+}
index efa9c9a..f19e73c 100644 (file)
@@ -37,6 +37,8 @@ const resourceDictionaries = JSON.parse(
   fs.readFileSync(path.join(FIXTURES, 'resource-dictionaries.json'), 'utf8'));
 const modelTypes = JSON.parse(
   fs.readFileSync(path.join(FIXTURES, 'model-types.json'), 'utf8'));
+const workflowSpecs = JSON.parse(
+  fs.readFileSync(path.join(FIXTURES, 'workflow-specs.json'), 'utf8'));
 
 // â”€â”€ helpers â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
 
@@ -168,6 +170,17 @@ const server = http.createServer(async (req, res) => {
     return bp ? json(res, bp) : json(res, { error: 'not found' }, 404);
   }
 
+  // GET /api/v1/blueprint-model/workflows/blueprint-name/:name/version/:version
+  if (method === 'GET' &&
+      (m = pathname.match(`^${BASE}/blueprint-model/workflows/blueprint-name/([^/]+)/version/([^/]+)$`))) {
+    const [, name, version] = m;
+    const bp = blueprints.find(
+      b => b.artifactName === decodeURIComponent(name) && b.artifactVersion === decodeURIComponent(version));
+    if (!bp) return json(res, { error: 'not found' }, 404);
+    const workflows = bp.workflows ? Object.keys(bp.workflows) : [];
+    return json(res, { blueprintName: bp.artifactName, version: bp.artifactVersion, workflows });
+  }
+
   // GET /api/v1/blueprint-model/download/by-name/:name/version/:version
   if (method === 'GET' &&
       (m = pathname.match(`^${BASE}/blueprint-model/download/by-name/([^/]+)/version/([^/]+)$`))) {
@@ -320,6 +333,73 @@ const server = http.createServer(async (req, res) => {
     return json(res, { message: 'deleted', name: m[1] });
   }
 
+  // â”€â”€ workflow-spec â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
+
+  // POST /api/v1/blueprint-model/workflow-spec
+  if (method === 'POST' && (pathname === `${BASE}/blueprint-model/workflow-spec` || pathname === `${BASE}/blueprint-model/workflow-spec/`)) {
+    const raw = await readBody(req);
+    let body;
+    try { body = JSON.parse(raw); } catch (_) { return json(res, { error: 'invalid body' }, 400); }
+    const workflowName = body.workflowName || '';
+    const spec = workflowSpecs[workflowName];
+    if (!spec) {
+      return json(res, {
+        code: 404,
+        status: 'NOT_FOUND',
+        message: 'No workflow spec found for ' + workflowName
+      }, 404);
+    }
+    return json(res, {
+      blueprintName: body.blueprintName || '',
+      version: body.version || '',
+      workFlowData: {
+        workFlowName: workflowName,
+        inputs: spec.inputs || {},
+        outputs: spec.outputs || {}
+      },
+      dataTypes: spec.dataTypes || {}
+    });
+  }
+
+  // â”€â”€ execution-service â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
+
+  // POST /api/v1/execution-service/process
+  if (method === 'POST' && (pathname === `${BASE}/execution-service/process` || pathname === `${BASE}/execution-service/process/`)) {
+    const raw = await readBody(req);
+    let input;
+    try { input = JSON.parse(raw); } catch (_) { input = {}; }
+    const header = (input.commonHeader || {});
+    const actionIds = (input.actionIdentifiers || {});
+    const response = {
+      commonHeader: {
+        timestamp: new Date().toISOString(),
+        originatorId: header.originatorId || 'CDS',
+        requestId: header.requestId || 'mock-request-id',
+        subRequestId: header.subRequestId || 'mock-sub-request-id',
+      },
+      actionIdentifiers: {
+        blueprintName: actionIds.blueprintName || '',
+        blueprintVersion: actionIds.blueprintVersion || '',
+        actionName: actionIds.actionName || '',
+        mode: actionIds.mode || 'sync',
+      },
+      status: {
+        code: 200,
+        eventType: 'EVENT_COMPONENT_EXECUTED',
+        timestamp: new Date().toISOString(),
+        message: 'success',
+      },
+      payload: {
+        'resource-resolution-response': {
+          'meshed-template': {
+            json: '{"hostname": "mock-host-01", "ip-address": "10.0.0.1"}',
+          },
+        },
+      },
+    };
+    return json(res, response);
+  }
+
   // â”€â”€ fallthrough â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   process.stderr.write(`[mock-processor] 404 â€“ no stub for ${method} ${pathname}\n`);
   json(res, { error: `no stub for ${method} ${pathname}` }, 404);
diff --git a/cds-ui/e2e-playwright/tests/execution.spec.ts b/cds-ui/e2e-playwright/tests/execution.spec.ts
new file mode 100644 (file)
index 0000000..75bab15
--- /dev/null
@@ -0,0 +1,333 @@
+/*
+ * execution.spec.ts
+ *
+ * End-to-end tests for the Blueprint Execution page.  These tests verify:
+ *   1. Page structure â€“ execution dashboard component renders with tabs.
+ *   2. Navigation â€“ the Execute link in the sidebar navigates to the page.
+ *   3. Tab switching â€“ Setup / History / Live View tabs work correctly.
+ *   4. Execution Setup â€“ form fields render, blueprint dropdown is populated
+ *      from the mock-processor, and the JSON payload editor is visible.
+ *   5. Action selection â€“ selecting a blueprint+version populates the action
+ *      dropdown from the blueprint's workflows.
+ *   6. Execution API â€“ submitting the form POSTs to the execution endpoint
+ *      and the mock-processor returns a successful response.
+ *   7. Package detail integration â€“ the Execute button on the package detail
+ *      page navigates to the execution page with pre-filled parameters.
+ */
+
+import { test, expect } from '@playwright/test';
+
+// â”€â”€ helpers â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
+
+/** Wait for the execution dashboard to be rendered. */
+async function waitForExecutionDashboard(page: import('@playwright/test').Page) {
+    await expect(page.locator('app-execution-dashboard')).toBeAttached({ timeout: 20_000 });
+}
+
+/** Wait for the blueprint select dropdown to be populated from mock data. */
+async function waitForBlueprintOptions(page: import('@playwright/test').Page) {
+    await expect(page.locator('#blueprintName option').nth(1)).toBeAttached({ timeout: 20_000 });
+}
+
+// â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
+// Page structure
+// â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
+
+test.describe('Execution Page â€“ structure', () => {
+    test.beforeEach(async ({ page }) => {
+        await page.goto('/#/execute');
+        await page.waitForLoadState('networkidle');
+    });
+
+    test('execution dashboard component is rendered', async ({ page }) => {
+        await waitForExecutionDashboard(page);
+    });
+
+    test('breadcrumb shows Execute', async ({ page }) => {
+        await waitForExecutionDashboard(page);
+        const breadcrumb = page.locator('.breadcrumb-header');
+        await expect(breadcrumb).toContainText('Execute');
+    });
+
+    test('shows the Execution Setup tab', async ({ page }) => {
+        await waitForExecutionDashboard(page);
+        const tab = page.locator('#execution-nav-tab .nav-link', { hasText: 'Execution Setup' });
+        await expect(tab).toBeVisible({ timeout: 10_000 });
+    });
+
+    test('shows the Execution History tab', async ({ page }) => {
+        await waitForExecutionDashboard(page);
+        const tab = page.locator('#execution-nav-tab .nav-link', { hasText: 'Execution History' });
+        await expect(tab).toBeVisible({ timeout: 10_000 });
+    });
+
+    test('shows the Live View tab', async ({ page }) => {
+        await waitForExecutionDashboard(page);
+        const tab = page.locator('#execution-nav-tab .nav-link', { hasText: 'Live View' });
+        await expect(tab).toBeVisible({ timeout: 10_000 });
+    });
+
+    test('Execution Setup tab is active by default', async ({ page }) => {
+        await waitForExecutionDashboard(page);
+        const tab = page.locator('#execution-nav-tab .nav-link', { hasText: 'Execution Setup' });
+        await expect(tab).toHaveClass(/active/, { timeout: 10_000 });
+    });
+});
+
+// â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
+// Tab switching
+// â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
+
+test.describe('Execution Page â€“ tab switching', () => {
+    test.beforeEach(async ({ page }) => {
+        await page.goto('/#/execute');
+        await waitForExecutionDashboard(page);
+    });
+
+    test('clicking History tab shows history content', async ({ page }) => {
+        const historyTab = page.locator('#execution-nav-tab .nav-link', { hasText: 'Execution History' });
+        await historyTab.click();
+        await expect(historyTab).toHaveClass(/active/);
+        await expect(page.locator('app-execution-history')).toBeAttached();
+    });
+
+    test('clicking Live View tab shows live view content', async ({ page }) => {
+        const liveTab = page.locator('#execution-nav-tab .nav-link', { hasText: 'Live View' });
+        await liveTab.click();
+        await expect(liveTab).toHaveClass(/active/);
+        await expect(page.locator('app-live-view')).toBeAttached();
+    });
+
+    test('clicking back to Setup tab re-renders setup form', async ({ page }) => {
+        // Switch away first
+        const historyTab = page.locator('#execution-nav-tab .nav-link', { hasText: 'Execution History' });
+        await historyTab.click();
+        await expect(historyTab).toHaveClass(/active/);
+
+        // Switch back
+        const setupTab = page.locator('#execution-nav-tab .nav-link', { hasText: 'Execution Setup' });
+        await setupTab.click();
+        await expect(setupTab).toHaveClass(/active/);
+        await expect(page.locator('app-execution-setup')).toBeAttached();
+    });
+});
+
+// â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
+// Execution Setup â€“ form elements
+// â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
+
+test.describe('Execution Page â€“ setup form', () => {
+    test.beforeEach(async ({ page }) => {
+        await page.goto('/#/execute');
+        await waitForExecutionDashboard(page);
+    });
+
+    test('blueprint name dropdown is rendered', async ({ page }) => {
+        const select = page.locator('#blueprintName');
+        await expect(select).toBeVisible({ timeout: 10_000 });
+    });
+
+    test('blueprint version dropdown is rendered', async ({ page }) => {
+        const select = page.locator('#blueprintVersion');
+        await expect(select).toBeVisible({ timeout: 10_000 });
+    });
+
+    test('action name dropdown is rendered', async ({ page }) => {
+        const select = page.locator('#actionName');
+        await expect(select).toBeVisible({ timeout: 10_000 });
+    });
+
+    test('Execute button is rendered and initially disabled', async ({ page }) => {
+        const btn = page.locator('.btn-execute');
+        await expect(btn).toBeVisible({ timeout: 10_000 });
+        await expect(btn).toBeDisabled();
+    });
+
+    test('JSON payload editor is rendered', async ({ page }) => {
+        // ace-editor renders a div with class "ace_editor"
+        const editor = page.locator('.ace_editor');
+        await expect(editor.first()).toBeVisible({ timeout: 15_000 });
+    });
+
+    test('blueprint dropdown is populated from mock API', async ({ page }) => {
+        await waitForBlueprintOptions(page);
+        // We expect at least some options from the fixture data
+        const options = page.locator('#blueprintName option');
+        const count = await options.count();
+        // First option is the placeholder, rest are blueprint names
+        expect(count).toBeGreaterThan(1);
+    });
+});
+
+// â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
+// Execution API integration via mock
+// â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
+
+/** Helper: select first blueprint + wait for version + wait for actions to load. */
+async function selectBlueprintAndVersion(page: import('@playwright/test').Page) {
+    await page.selectOption('#blueprintName', { index: 1 });
+    // Wait for version dropdown to populate and auto-select
+    await expect(page.locator('#blueprintVersion option').nth(1)).toBeAttached({ timeout: 10_000 });
+    const versionOptions = await page.locator('#blueprintVersion option').count();
+    if (versionOptions > 1) {
+        await page.selectOption('#blueprintVersion', { index: 1 });
+    }
+    // Wait for action dropdown to populate from by-name API
+    await expect(page.locator('#actionName option').nth(1)).toBeAttached({ timeout: 10_000 });
+}
+
+test.describe('Execution Page â€“ API integration', () => {
+    test.beforeEach(async ({ page }) => {
+        await page.goto('/#/execute');
+        await waitForExecutionDashboard(page);
+        await waitForBlueprintOptions(page);
+    });
+
+    test('selecting a blueprint populates the action dropdown', async ({ page }) => {
+        await selectBlueprintAndVersion(page);
+        const actionOptions = page.locator('#actionName option');
+        const count = await actionOptions.count();
+        // placeholder + at least one real action
+        expect(count).toBeGreaterThan(1);
+    });
+
+    test('selecting a blueprint enables the Execute button', async ({ page }) => {
+        await selectBlueprintAndVersion(page);
+        // Select an action if not auto-selected
+        const actionOptions = await page.locator('#actionName option').count();
+        if (actionOptions > 1) {
+            await page.selectOption('#actionName', { index: 1 });
+        }
+        await page.waitForTimeout(300);
+        const btn = page.locator('.btn-execute');
+        await expect(btn).toBeEnabled({ timeout: 5_000 });
+    });
+
+    test('executing a blueprint sends request and receives response', async ({ page }) => {
+        await selectBlueprintAndVersion(page);
+        // Select an action
+        const actionOptions = await page.locator('#actionName option').count();
+        if (actionOptions > 1) {
+            await page.selectOption('#actionName', { index: 1 });
+        }
+        await page.waitForTimeout(300);
+
+        // Click execute and wait for the API round-trip
+        const [apiResponse] = await Promise.all([
+            page.waitForResponse(
+                resp => resp.url().includes('/controllerblueprint/execute') && resp.status() === 200,
+                { timeout: 15_000 },
+            ),
+            page.locator('.btn-execute').click(),
+        ]);
+
+        expect(apiResponse.status()).toBe(200);
+
+        // Verify the response JSON contains expected mock data
+        const body = await apiResponse.json();
+        expect(body).toHaveProperty('status');
+        expect(body.status.code).toBe(200);
+        expect(body.status.message).toBe('success');
+    });
+});
+
+// â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
+// Dynamic action inputs
+// â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
+
+test.describe('Execution Page â€“ action inputs', () => {
+    test.beforeEach(async ({ page }) => {
+        await page.goto('/#/execute');
+        await waitForExecutionDashboard(page);
+        await waitForBlueprintOptions(page);
+    });
+
+    test('selecting an action shows action input fields', async ({ page }) => {
+        await selectBlueprintAndVersion(page);
+        // Select the first action
+        await page.selectOption('#actionName', { index: 1 });
+        // Wait for the workflow-spec API call to complete and form to render
+        const inputsSection = page.locator('#actionInputs');
+        await expect(inputsSection).toBeAttached({ timeout: 10_000 });
+    });
+
+    test('complex input group renders nested fields', async ({ page }) => {
+        await selectBlueprintAndVersion(page);
+        await page.selectOption('#actionName', { index: 1 });
+        // Wait for inputs section
+        await expect(page.locator('#actionInputs')).toBeAttached({ timeout: 10_000 });
+        // Complex group should have nested fields inside .complex-fields
+        const complexFields = page.locator('.complex-fields input, .complex-fields select');
+        const count = await complexFields.count();
+        expect(count).toBeGreaterThan(0);
+    });
+
+    test('typing in a field updates the JSON payload', async ({ page }) => {
+        await selectBlueprintAndVersion(page);
+        await page.selectOption('#actionName', { index: 1 });
+        await expect(page.locator('#actionInputs')).toBeAttached({ timeout: 10_000 });
+
+        // Find the first text input in the inputs section and type a value
+        const firstInput = page.locator('#actionInputs input[type="text"]').first();
+        await firstInput.fill('test-value-123');
+
+        // Wait for payload to update
+        await page.waitForTimeout(500);
+
+        // Get the ace editor content and verify the typed value appears
+        const editorContent = await page.locator('.ace_editor .ace_text-layer').first().textContent();
+        expect(editorContent).toContain('test-value-123');
+    });
+
+    test('reset clears input field values', async ({ page }) => {
+        await selectBlueprintAndVersion(page);
+        await page.selectOption('#actionName', { index: 1 });
+        await expect(page.locator('#actionInputs')).toBeAttached({ timeout: 10_000 });
+
+        // Fill a field
+        const firstInput = page.locator('#actionInputs input[type="text"]').first();
+        await firstInput.fill('will-be-cleared');
+        await page.waitForTimeout(300);
+
+        // Click reset
+        await page.locator('button', { hasText: 'Reset' }).click();
+        await page.waitForTimeout(300);
+
+        // Field should be cleared
+        await expect(firstInput).toHaveValue('');
+    });
+
+    test('payload JSON includes action-request structure', async ({ page }) => {
+        await selectBlueprintAndVersion(page);
+        // Select an action
+        await page.selectOption('#actionName', { index: 1 });
+        await expect(page.locator('#actionInputs')).toBeAttached({ timeout: 10_000 });
+        await page.waitForTimeout(500);
+
+        // Check that the payload contains a *-request key
+        const editorContent = await page.locator('.ace_editor .ace_text-layer').first().textContent();
+        expect(editorContent).toMatch(/-request/);
+    });
+});
+
+// â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
+// Sidebar navigation
+// â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
+
+test.describe('Execution Page â€“ navigation', () => {
+    test('Execute link is visible in the sidebar', async ({ page }) => {
+        await page.goto('/#/packages');
+        await page.waitForLoadState('networkidle');
+        const executeLink = page.locator('.menu-dropdown a', { hasText: 'Execute' });
+        await expect(executeLink).toBeVisible({ timeout: 10_000 });
+    });
+
+    test('clicking Execute link in sidebar navigates to execute page', async ({ page }) => {
+        await page.goto('/#/packages');
+        await page.waitForLoadState('networkidle');
+        const executeLink = page.locator('.menu-dropdown a', { hasText: 'Execute' });
+        await executeLink.click();
+        await expect(page).toHaveURL(/\/#\/execute/);
+        await waitForExecutionDashboard(page);
+    });
+});
index 4cbea0b..0a665f0 100644 (file)
@@ -25,7 +25,7 @@ limitations under the License.
     <parent>
         <groupId>org.onap.ccsdk.cds</groupId>
         <artifactId>cds-aggregator</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
         <relativePath>..</relativePath>
     </parent>
 
index 0b6360c..b5c7e88 100644 (file)
@@ -25,7 +25,7 @@ limitations under the License.
     <parent>
         <groupId>org.onap.ccsdk.cds</groupId>
         <artifactId>cds-ui</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
         <relativePath>..</relativePath>
     </parent>
 
index 15c2e4e..9260e90 100644 (file)
@@ -322,6 +322,98 @@ export class BlueprintRestController {
     });
   }
 
+  @post('/controllerblueprint/execute')
+  async executeBlueprint(
+    @requestBody({
+      description: 'ExecutionServiceInput JSON payload',
+      required: true,
+      content: {
+        'application/json': {
+          schema: { type: 'object' },
+        },
+      },
+    })
+    body: object,
+    @inject(RestBindings.Http.RESPONSE) response: Response,
+  ): Promise<Response> {
+    const options = {
+      url: processorApiConfig.http.url + '/execution-service/process',
+      headers: {
+        Authorization: processorApiConfig.http.authToken,
+        'Content-Type': 'application/json',
+      },
+      body: JSON.stringify(body),
+    };
+    return new Promise((resolve, reject) => {
+      request_lib.post(options)
+        .on('error', err => {
+          reject(err);
+        })
+        .pipe(response)
+        .once('finish', () => {
+          resolve(response);
+        });
+    });
+  }
+
+  @get('/controllerblueprint/workflows/{name}/{version}')
+  async getWorkflows(
+    @param.path.string('name') name: string,
+    @param.path.string('version') version: string,
+    @inject(RestBindings.Http.RESPONSE) response: Response,
+  ): Promise<Response> {
+    const options = {
+      url: processorApiConfig.http.url + '/blueprint-model/workflows/blueprint-name/' + name + '/version/' + version,
+      headers: {
+        Authorization: processorApiConfig.http.authToken,
+      },
+    };
+    return new Promise((resolve, reject) => {
+      request_lib.get(options)
+        .on('error', err => {
+          reject(err);
+        })
+        .pipe(response)
+        .once('finish', () => {
+          resolve(response);
+        });
+    });
+  }
+
+  @post('/controllerblueprint/workflow-spec')
+  async getWorkflowSpec(
+    @requestBody({
+      description: 'WorkFlowSpecRequest JSON payload',
+      required: true,
+      content: {
+        'application/json': {
+          schema: { type: 'object' },
+        },
+      },
+    })
+    body: object,
+    @inject(RestBindings.Http.RESPONSE) response: Response,
+  ): Promise<Response> {
+    const options = {
+      url: processorApiConfig.http.url + '/blueprint-model/workflow-spec',
+      headers: {
+        Authorization: processorApiConfig.http.authToken,
+        'Content-Type': 'application/json',
+      },
+      body: JSON.stringify(body),
+    };
+    return new Promise((resolve, reject) => {
+      request_lib.post(options)
+        .on('error', err => {
+          reject(err);
+        })
+        .pipe(response)
+        .once('finish', () => {
+          resolve(response);
+        });
+    });
+  }
+
   @post('/controllerblueprint/deploy-blueprint')
   async deploy(
     @requestBody({
index c0456da..07dedf6 100644 (file)
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>blueprintsprocessor-parent</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
         <relativePath>../../ms/blueprintsprocessor/parent</relativePath>
     </parent>
 
index 88efda2..334a1d5 100644 (file)
@@ -20,7 +20,7 @@
 
   <groupId>org.onap.ccsdk.cds.components.cba</groupId>
   <artifactId>archetype-blueprint</artifactId>
-  <version>1.9.1-SNAPSHOT</version>
+  <version>1.10.0-SNAPSHOT</version>
   <packaging>maven-archetype</packaging>
 
   <name>Components Model Catalog - Blueprints Model - Archetype Blueprints</name>
index 941db1d..1dd3ccc 100644 (file)
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.components.cba</groupId>
         <artifactId>blueprint-model</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>cba-assembly-descriptor</artifactId>
index b1cfcad..9acf545 100644 (file)
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>cba-parent</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
         <relativePath>../../cba-parent</relativePath>
     </parent>
 
index f9ac3af..79d3617 100644 (file)
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.components.cba</groupId>
         <artifactId>blueprint-model</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>test-blueprint-kotlin-parent</artifactId>
index 95fba77..010df92 100644 (file)
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.components.cba</groupId>
         <artifactId>test-blueprint-model</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>capability_cli</artifactId>
index 177c63c..5d564c9 100644 (file)
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.components.cba</groupId>
         <artifactId>blueprint-model</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>test-blueprint-model</artifactId>
index fcd228e..784fb26 100644 (file)
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.components.cba</groupId>
         <artifactId>test-blueprint-model</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>resource-audit</artifactId>
index e2b36f5..368614c 100644 (file)
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds</groupId>
         <artifactId>cds-aggregator</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
         <relativePath>..</relativePath>
     </parent>
 
index b6a9549..677e060 100755 (executable)
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>blueprintsprocessor-parent</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
         <relativePath>../parent</relativePath>
     </parent>
 
index 20d94c0..f67de37 100644 (file)
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>blueprintsprocessor-functions</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <groupId>org.onap.ccsdk.cds.blueprintsprocessor.functions</groupId>
index 17274cf..4aa5a0f 100644 (file)
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>blueprintsprocessor-functions</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <groupId>org.onap.ccsdk.cds.blueprintsprocessor.functions</groupId>
index 0c81fb2..39d2682 100644 (file)
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>blueprintsprocessor-functions</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <groupId>org.onap.ccsdk.cds.blueprintsprocessor.functions</groupId>
index dbbc18a..a54cc90 100644 (file)
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>blueprintsprocessor-functions</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <groupId>org.onap.ccsdk.cds.blueprintsprocessor.functions</groupId>
index ecdde0e..9b09592 100644 (file)
@@ -23,7 +23,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>blueprintsprocessor-functions</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <groupId>org.onap.ccsdk.cds.blueprintsprocessor.functions</groupId>
index bec39a3..b759acd 100644 (file)
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>blueprintsprocessor-functions</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <groupId>org.onap.ccsdk.cds.blueprintsprocessor.functions</groupId>
index be196a9..d516dd8 100644 (file)
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>blueprintsprocessor-functions</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <groupId>org.onap.ccsdk.cds.blueprintsprocessor.functions</groupId>
index 0bae857..600426f 100755 (executable)
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>blueprintsprocessor-parent</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
         <relativePath>../parent</relativePath>
     </parent>
 
index b37beef..62c8b85 100644 (file)
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>blueprintsprocessor-functions</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <groupId>org.onap.ccsdk.cds.blueprintsprocessor.functions</groupId>
index 38bb570..c5cd425 100644 (file)
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>blueprintsprocessor-functions</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <groupId>org.onap.ccsdk.cds.blueprintsprocessor.functions</groupId>
index ebfff02..de2eba0 100644 (file)
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>blueprintsprocessor-functions</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <groupId>org.onap.ccsdk.cds.blueprintsprocessor.functions</groupId>
index 5d61337..ecefcc0 100644 (file)
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>blueprintsprocessor-functions</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <groupId>org.onap.ccsdk.cds.blueprintsprocessor.functions</groupId>
index c544d2e..d8a1b5a 100644 (file)
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>modules-blueprints</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <groupId>org.onap.ccsdk.cds.blueprintsprocessor.modules</groupId>
index b59d73c..eee8791 100644 (file)
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>modules-blueprints</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <groupId>org.onap.ccsdk.cds.blueprintsprocessor.modules</groupId>
index ee20ccf..dce6a13 100644 (file)
@@ -23,7 +23,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>modules-blueprints</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <groupId>org.onap.ccsdk.cds.blueprintsprocessor.modules</groupId>
index d12997b..bf00c3b 100644 (file)
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>blueprintsprocessor-modules</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>modules-blueprints</artifactId>
index dbe9a21..85351f6 100644 (file)
@@ -23,7 +23,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>modules-blueprints</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <groupId>org.onap.ccsdk.cds.blueprintsprocessor.modules</groupId>
index 820b5f3..3a40e49 100644 (file)
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>modules-commons</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <groupId>org.onap.ccsdk.cds.blueprintsprocessor.modules</groupId>
index baee1fb..5815bfd 100644 (file)
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>modules-commons</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <groupId>org.onap.ccsdk.cds.blueprintsprocessor.modules</groupId>
index c69e9b0..f24c838 100644 (file)
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>modules-commons</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <groupId>org.onap.ccsdk.cds.blueprintsprocessor.modules</groupId>
index f7d6b8c..b73665e 100644 (file)
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>modules-commons</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <groupId>org.onap.ccsdk.cds.blueprintsprocessor.modules</groupId>
index d5737e2..db63906 100755 (executable)
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>blueprintsprocessor-modules</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>modules-commons</artifactId>
index 0d830eb..3f727eb 100644 (file)
@@ -23,7 +23,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>modules-commons</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <groupId>org.onap.ccsdk.cds.blueprintsprocessor.modules</groupId>
index 40ce739..77f30e9 100644 (file)
@@ -23,7 +23,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>modules-commons</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <groupId>org.onap.ccsdk.cds.blueprintsprocessor.modules</groupId>
index 6062f00..9fe4f61 100644 (file)
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>modules-commons</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <groupId>org.onap.ccsdk.cds.blueprintsprocessor.modules</groupId>
index 0b2921e..e72afa1 100644 (file)
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>modules-inbounds</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <groupId>org.onap.ccsdk.cds.blueprintsprocessor.modules</groupId>
index c8e63df..cb815c6 100644 (file)
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>modules-inbounds</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <groupId>org.onap.ccsdk.cds.blueprintsprocessor.modules</groupId>
index da6311d..f1931aa 100644 (file)
@@ -158,6 +158,8 @@ open class BluePrintModelHandler(
         workFlowData.inputs = workFlow.inputs
         workFlowData.outputs = workFlow.outputs
 
+        wfRes.workFlowData = workFlowData
+
         if (workFlow.inputs != null) {
             for ((k, v) in workFlow.inputs!!) {
                 addPropertyInfo(k, v, blueprintContext, wfRes)
@@ -169,8 +171,6 @@ open class BluePrintModelHandler(
                 addPropertyInfo(k, v, blueprintContext, wfRes)
             }
         }
-
-        wfRes.workFlowData = workFlowData
         return wfRes
     }
 
index 6d998d0..9a24f5f 100644 (file)
@@ -273,6 +273,77 @@ class BlueprintModelControllerTest {
             .jsonPath("$.content.length()").isEqualTo(0)
     }
 
+    /**
+     * Regression test for CCSDK-4184: workFlowData must be set on the response object before the
+     * input/output property loops run, because updatePropertyInfo() accesses
+     * res.workFlowData.workFlowName.  Without the fix, every call raises
+     * "lateinit property workFlowData has not been initialized".
+     */
+    @Test
+    fun test07d_getWorkflowSpec_withInputsAndComplexType_returnsWorkFlowDataAndDataTypes() {
+        // 'resource-assignment' has one input of the complex type dt-resource-assignment-properties
+        // which triggers both addPropertyInfo â†’ updatePropertyInfo AND addDataType paths.
+        webTestClient.post()
+            .uri("/api/v1/blueprint-model/workflow-spec")
+            .header(
+                "Authorization",
+                "Basic " + Base64.getEncoder()
+                    .encodeToString(("ccsdkapps:ccsdkapps").toByteArray(UTF_8))
+            )
+            .header("Content-Type", "application/json")
+            .bodyValue(
+                """{"blueprintName":"baseconfiguration","version":"1.0.0","workflowName":"resource-assignment"}"""
+            )
+            .exchange()
+            .expectStatus().isOk
+            .expectBody()
+            .jsonPath("$.workFlowData").exists()
+            .jsonPath("$.workFlowData.workFlowName").isEqualTo("resource-assignment")
+            .jsonPath("$.workFlowData.inputs").exists()
+            .jsonPath("$.workFlowData.inputs['resource-assignment-properties']").exists()
+            .jsonPath("$.dataTypes").exists()
+            .jsonPath("$.dataTypes['dt-resource-assignment-properties']").exists()
+    }
+
+    @Test
+    fun test07e_getWorkflowSpec_withNoInputs_returnsEmptyWorkFlowData() {
+        // 'activate-restconf' has no inputs or outputs â€” the loops are skipped entirely,
+        // but workFlowData must still be populated correctly.
+        webTestClient.post()
+            .uri("/api/v1/blueprint-model/workflow-spec")
+            .header(
+                "Authorization",
+                "Basic " + Base64.getEncoder()
+                    .encodeToString(("ccsdkapps:ccsdkapps").toByteArray(UTF_8))
+            )
+            .header("Content-Type", "application/json")
+            .bodyValue(
+                """{"blueprintName":"baseconfiguration","version":"1.0.0","workflowName":"activate-restconf"}"""
+            )
+            .exchange()
+            .expectStatus().isOk
+            .expectBody()
+            .jsonPath("$.workFlowData").exists()
+            .jsonPath("$.workFlowData.workFlowName").isEqualTo("activate-restconf")
+    }
+
+    @Test
+    fun test07f_getWorkflowSpec_unknownWorkflow_returns4xx() {
+        webTestClient.post()
+            .uri("/api/v1/blueprint-model/workflow-spec")
+            .header(
+                "Authorization",
+                "Basic " + Base64.getEncoder()
+                    .encodeToString(("ccsdkapps:ccsdkapps").toByteArray(UTF_8))
+            )
+            .header("Content-Type", "application/json")
+            .bodyValue(
+                """{"blueprintName":"baseconfiguration","version":"1.0.0","workflowName":"nonexistent-workflow"}"""
+            )
+            .exchange()
+            .expectStatus().is5xxServerError
+    }
+
     @Test
     @Throws(JSONException::class)
     fun test08_searchBlueprintModels() {
index 4784c2c..3f2c20a 100644 (file)
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>modules-inbounds</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <groupId>org.onap.ccsdk.cds.blueprintsprocessor.modules</groupId>
index aac2716..0cfe731 100644 (file)
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>modules-inbounds</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <groupId>org.onap.ccsdk.cds.blueprintsprocessor.modules</groupId>
index f1f02bb..9d33898 100644 (file)
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>blueprintsprocessor-modules</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>modules-inbounds</artifactId>
index 07c5204..2bf42bd 100644 (file)
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>modules-inbounds</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <groupId>org.onap.ccsdk.cds.blueprintsprocessor.modules</groupId>
index 8096f06..fbfbcb6 100755 (executable)
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>modules-inbounds</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <groupId>org.onap.ccsdk.cds.blueprintsprocessor.modules</groupId>
index 273e575..a32d3a7 100644 (file)
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>modules-inbounds</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <groupId>org.onap.ccsdk.cds.blueprintsprocessor.modules</groupId>
index 1839f5a..794f53a 100644 (file)
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>blueprintsprocessor-modules</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>modules-outbounds</artifactId>
index 1b07437..3593d9d 100644 (file)
@@ -23,7 +23,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>blueprintsprocessor-parent</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
         <relativePath>../parent</relativePath>
     </parent>
 
index cd2dfe1..e2874ec 100644 (file)
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>modules-services</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <groupId>org.onap.ccsdk.cds.blueprintsprocessor.modules</groupId>
index aa8d0c2..be41eea 100755 (executable)
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>blueprintsprocessor-modules</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>modules-services</artifactId>
index 06fe4fb..7c56061 100644 (file)
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
         <artifactId>modules-services</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <groupId>org.onap.ccsdk.cds.blueprintsprocessor.modules</groupId>
index 91662c3..2f1efd9 100755 (executable)
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds</groupId>
         <artifactId>blueprintsprocessor</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId>
index 27612d1..30503a2 100755 (executable)
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds</groupId>
         <artifactId>cds-ms</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
         <relativePath>..</relativePath>
     </parent>
 
index 07d11b3..f6b706f 100755 (executable)
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds</groupId>
         <artifactId>cds-ms</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
         <relativePath>..</relativePath>
     </parent>
 
index 4cf66a5..f9c5761 100644 (file)
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.error.catalog</groupId>
         <artifactId>error-catalog</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>error-catalog-application</artifactId>
index c854cd5..3e1a8d9 100644 (file)
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.error.catalog</groupId>
         <artifactId>error-catalog</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>error-catalog-core</artifactId>
index 973c82a..7dee566 100644 (file)
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds</groupId>
         <artifactId>cds-ms</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
         <relativePath>..</relativePath>
     </parent>
 
index f08650c..93a79ac 100644 (file)
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.error.catalog</groupId>
         <artifactId>error-catalog</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>error-catalog-services</artifactId>
index e260f3d..1e1bda0 100644 (file)
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds</groupId>
         <artifactId>cds-aggregator</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
         <relativePath>..</relativePath>
     </parent>
 
index 716ea39..673e8b8 100644 (file)
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds</groupId>
         <artifactId>cds-ms</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>py-executor</artifactId>
index c755bce..39f2f6f 100644 (file)
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.sdclistener</groupId>
         <artifactId>sdclistener-parent</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
         <relativePath>../parent</relativePath>
     </parent>
 
index 1b764c4..288594b 100755 (executable)
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds.sdclistener</groupId>
         <artifactId>sdclistener-parent</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
         <relativePath>../parent</relativePath>
     </parent>
 
index 1225bdc..83475a3 100755 (executable)
@@ -23,7 +23,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds</groupId>
         <artifactId>sdclistener</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
     </parent>
 
     <groupId>org.onap.ccsdk.cds.sdclistener</groupId>
index 4d97f16..6fee4ce 100644 (file)
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.onap.ccsdk.cds</groupId>
         <artifactId>cds-ms</artifactId>
-        <version>1.9.1-SNAPSHOT</version>
+        <version>1.10.0-SNAPSHOT</version>
         <relativePath>..</relativePath>
     </parent>
 
diff --git a/pom.xml b/pom.xml
index c571083..45ddefd 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -29,7 +29,7 @@ limitations under the License.
 
     <groupId>org.onap.ccsdk.cds</groupId>
     <artifactId>cds-aggregator</artifactId>
-    <version>1.9.1-SNAPSHOT</version>
+    <version>1.10.0-SNAPSHOT</version>
     <packaging>pom</packaging>
 
     <name>ccsdk-cds</name>
index d49ce7e..9e6d20d 100644 (file)
@@ -5,11 +5,10 @@
 
 
 release_name=1
-sprint_number=9
-feature_revision=1
+sprint_number=10
+feature_revision=0
 
 base_version=${release_name}.${sprint_number}.${feature_revision}
 
 release_version=${base_version}
 snapshot_version=${base_version}-SNAPSHOT
-