From a25a1bbf67b497a36349bad47af5ad8792e8b03d Mon Sep 17 00:00:00 2001
From: Charlotte Hausman <chausman@nrao.edu>
Date: Tue, 20 Jul 2021 15:12:56 +0000
Subject: [PATCH] UI for creating follow on imaging requests from completed
 calibration requests

---
 .../request-operations.component.html         | 10 ++++
 .../request-operations.component.ts           | 19 +++++++-
 .../services/capability-launcher.service.ts   | 13 ++++++
 ...emove_white_space_from_capability_steps.py | 46 +++++++++++++++++++
 schema/versions/87dae5acfd34_.py              | 24 ++++++++++
 services/capability/capability/routes.py      |  5 ++
 .../capability/views/capability_request.py    | 34 ++++++++++++++
 .../capability/test/test_capability_server.py |  1 +
 .../workflow/services/workflow_service.py     | 17 +++++++
 9 files changed, 168 insertions(+), 1 deletion(-)
 create mode 100644 schema/versions/5ab2fcb61373_remove_white_space_from_capability_steps.py
 create mode 100644 schema/versions/87dae5acfd34_.py

diff --git a/apps/web/src/app/workspaces/components/capability-request/components/request-operations/request-operations.component.html b/apps/web/src/app/workspaces/components/capability-request/components/request-operations/request-operations.component.html
index 6589cf74b..744327ace 100644
--- a/apps/web/src/app/workspaces/components/capability-request/components/request-operations/request-operations.component.html
+++ b/apps/web/src/app/workspaces/components/capability-request/components/request-operations/request-operations.component.html
@@ -7,6 +7,16 @@
         [capabilityRequest]="capabilityRequest"
       ></app-create-new-version-form>
     </div>
+    <div class="col-auto d-flex" *ngIf="capabilityRequest.state === 'Complete' &&
+    capabilityRequest.capability_name === 'std_calibration' || capabilityRequest.capability_name === 'restore_cms'">
+      <button
+        id="create-image-request"
+        type="button"
+        class="btn btn-warning"
+        (click)="capabilityLauncherService.createImageRequestFromPreviousCal('std_cms_imaging', capabilityRequest.id).subscribe(followonRequestObserver)">
+        <span class="fas fa-camera"></span><span class="pl-2">Create Image Request</span>
+      </button>
+    </div>
     <div class="col-auto d-flex" *ngIf="capabilityRequest.state === 'Created'">
       <button
         id="submit-button"
diff --git a/apps/web/src/app/workspaces/components/capability-request/components/request-operations/request-operations.component.ts b/apps/web/src/app/workspaces/components/capability-request/components/request-operations/request-operations.component.ts
index a7dc8a97e..9559a9e5a 100644
--- a/apps/web/src/app/workspaces/components/capability-request/components/request-operations/request-operations.component.ts
+++ b/apps/web/src/app/workspaces/components/capability-request/components/request-operations/request-operations.component.ts
@@ -2,6 +2,7 @@ import { Component, Input, OnInit } from "@angular/core";
 import { CapabilityRequest } from "../../../../model/capability-request";
 import { CapabilityLauncherService } from "../../../../services/capability-launcher.service";
 import { CapabilityExecution } from "../../../../model/capability-execution";
+import {CapabilityRequestService} from "../../../../services/capability-request.service";
 
 @Component({
   selector: "app-request-operations",
@@ -23,7 +24,23 @@ export class RequestOperationsComponent implements OnInit {
     },
   };
 
-  constructor(public capabilityLauncherService: CapabilityLauncherService) {}
+  public followonRequestObserver = {
+    next: (followonResponse: CapabilityRequest) => {
+      if (followonResponse.id) {
+        // Capability request created; ID found
+        // Redirect to request status page
+        this.capabilityRequestService.redirectToRequestStatusPage(followonResponse.id);
+      }
+    },
+    error: (error) => {
+      console.log(error);
+    },
+  };
+
+  constructor(
+    public capabilityLauncherService: CapabilityLauncherService,
+    private capabilityRequestService: CapabilityRequestService,
+  ) {}
 
   ngOnInit(): void {}
 }
diff --git a/apps/web/src/app/workspaces/services/capability-launcher.service.ts b/apps/web/src/app/workspaces/services/capability-launcher.service.ts
index b21c71d64..9d7753499 100644
--- a/apps/web/src/app/workspaces/services/capability-launcher.service.ts
+++ b/apps/web/src/app/workspaces/services/capability-launcher.service.ts
@@ -23,6 +23,19 @@ export class CapabilityLauncherService {
     return this.httpClient.post<CapabilityRequest>(url, JSON.stringify({ parameters: parameters }));
   }
 
+  /**
+   * Create standard CMS imaging request from previously exeuted calibration request and send it to capability service
+   * @param: capabilityName  Name of capability to create request for
+   * @param: requestId Id of calibration request to image
+   */
+  createImageRequestFromPreviousCal(
+    followonType: string,
+    requestId: string,
+  ): Observable<CapabilityRequest> {
+    const url = this.endpoint + "request/" + requestId + "/followon/" + followonType;
+    return this.httpClient.post<CapabilityRequest>(url, null);
+  }
+
   /**
    * Submit capability request
    * @param: requestId  ID of capability request to submit
diff --git a/schema/versions/5ab2fcb61373_remove_white_space_from_capability_steps.py b/schema/versions/5ab2fcb61373_remove_white_space_from_capability_steps.py
new file mode 100644
index 000000000..c9b641587
--- /dev/null
+++ b/schema/versions/5ab2fcb61373_remove_white_space_from_capability_steps.py
@@ -0,0 +1,46 @@
+"""remove white space from capability steps
+
+Revision ID: 5ab2fcb61373
+Revises: 6508afd4da68
+Create Date: 2021-07-15 14:03:32.460285
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = "5ab2fcb61373"
+down_revision = "6508afd4da68"
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+    op.execute(
+        """
+        UPDATE capabilities
+        SET capability_steps = 'prepare-and-run-workflow restore_cms\nawait-workflow\nawait-qa' WHERE capability_name = 'restore_cms'
+        """
+    )
+    op.execute(
+        """
+        UPDATE capabilities
+        SET capability_steps = 'prepare-and-run-workflow std_restore_imaging\nawait-workflow\nawait-qa' WHERE capability_name = 'std_restore_imaging'
+        """
+    )
+
+
+def downgrade():
+    op.execute(
+        """
+        UPDATE capabilities
+        SET capability_steps = 'prepare-and-run-workflow restore_cms\nawait-workflow\nawait-qa\n' WHERE capability_name = 'restore_cms'
+        """
+    )
+    op.execute(
+        """
+        UPDATE capabilities
+        SET capability_steps = 'prepare-and-run-workflow std_restore_imaging\nawait-workflow\nawait-qa\n' WHERE capability_name = 'std_restore_imaging'
+        """
+    )
diff --git a/schema/versions/87dae5acfd34_.py b/schema/versions/87dae5acfd34_.py
new file mode 100644
index 000000000..204f1c1a5
--- /dev/null
+++ b/schema/versions/87dae5acfd34_.py
@@ -0,0 +1,24 @@
+"""empty message
+
+Revision ID: 87dae5acfd34
+Revises: 5ab2fcb61373, 10251d1732a4
+Create Date: 2021-07-20 08:58:26.887779
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = '87dae5acfd34'
+down_revision = ('5ab2fcb61373', '10251d1732a4')
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+    pass
+
+
+def downgrade():
+    pass
diff --git a/services/capability/capability/routes.py b/services/capability/capability/routes.py
index 23b1f731d..85efdec58 100644
--- a/services/capability/capability/routes.py
+++ b/services/capability/capability/routes.py
@@ -69,6 +69,11 @@ def capability_request_routes(config: Configurator):
         pattern="capability/{capability_name}/request/create",
         request_method="POST",
     )
+    config.add_route(
+        name="create_follow_on_capability_request",
+        pattern=f"{request_url}" + "/followon/{followon_type}",
+        request_method="POST",
+    )
     config.add_route(
         name="edit_capability_request",
         pattern=f"{request_url}",
diff --git a/services/capability/capability/views/capability_request.py b/services/capability/capability/views/capability_request.py
index 21cbb7938..702cbb50b 100644
--- a/services/capability/capability/views/capability_request.py
+++ b/services/capability/capability/views/capability_request.py
@@ -109,6 +109,40 @@ def create_capability_request(request: Request) -> Response:
         return Response(json_body=new_capability_request.__json__())
 
 
+@view_config(route_name="create_follow_on_capability_request", renderer="json")
+def create_follow_on_capability_request(request: Request) -> Response:
+    """
+    Pyramid view that accepts a request to create a follow-on capability request from a previous request
+    URL: capability/request/{request_id}/followon/{followon_type}
+
+    :param request: POST request
+    :return: 200 OK response with JSON-formatted info of newly created capability request
+        or 400 response (HTTPBadRequest) if expected parameters not given
+        or 412 response (HTTPPreconditionFailed) if capability with given name does not exist and thus cannot be
+        requested
+    """
+
+    request_id = request.matchdict["request_id"]
+    followon_type = request.matchdict["followon_type"]
+
+    capability_request = request.capability_info.lookup_capability_request(request_id)
+    parameters = capability_request.current_execution.parameters
+    user_email = parameters["user_email"]
+
+    previous_workflow_id = capability_request.current_execution.current_workflow_request_id
+
+    metadata = request.workflow_service.retrieve_file_content(
+        followon_type, previous_workflow_id, "metadata.json"
+    )
+    cms_path = metadata["cms_path"]
+    sdm_id = metadata["fileSetIds"]
+
+    new_capability_request = request.capability_service.create_request(
+        followon_type, parameters={"cmsPath": cms_path, "sdmId": sdm_id, "user_email": user_email}
+    )
+    return Response(json_body=new_capability_request.__json__())
+
+
 @view_config(route_name="edit_capability_request", renderer="json")
 def edit_capability_request(request: Request) -> Response:
     """
diff --git a/services/capability/test/test_capability_server.py b/services/capability/test/test_capability_server.py
index 4c494c0cf..39ca4d72a 100644
--- a/services/capability/test/test_capability_server.py
+++ b/services/capability/test/test_capability_server.py
@@ -20,6 +20,7 @@ def capability_routes() -> RouteList:
         "disable_capability",
         "view_capability_request",
         "create_capability_request",
+        "create_follow_on_capability_request",
         "edit_capability_request",
         "submit_capability_request",
         "cancel_capability_request",
diff --git a/shared/workspaces/workspaces/workflow/services/workflow_service.py b/shared/workspaces/workspaces/workflow/services/workflow_service.py
index 38f58f0b9..8cb4febba 100644
--- a/shared/workspaces/workspaces/workflow/services/workflow_service.py
+++ b/shared/workspaces/workspaces/workflow/services/workflow_service.py
@@ -71,6 +71,17 @@ class WorkflowServiceRESTClient(WorkflowServiceIF):
         )
         return response
 
+    def retrieve_file_content(self, name: str, request_id: int, filename: str):
+        """
+        Retrieve the conent of a workflow file
+        :param name: the workflow name
+        :param request_id: id of parent workflow request
+        :param filename: file to find
+        :return: dict containing file content
+        """
+
+        requests.get(f"{self.url}/workflow/{name}/requests/{request_id}/files/{filename}")
+
     def create_workflow_request(
         self, workflow: Union[str, WorkflowIF], argument: Dict
     ) -> WorkflowRequestIF:
@@ -147,6 +158,12 @@ class WorkflowService(WorkflowServiceIF):
         else:
             logger.info(f"{filename} is a protected file name.")
 
+    def retrieve_file_content(self, request_id: int, filename: str):
+        wf_req = self.info.lookup_workflow_request(request_id)
+        for file in wf_req.files:
+            if file.filename == filename:
+                return file.content.decode()
+
     def create_workflow_request(self, workflow, argument) -> WorkflowRequestIF:
         """
         Create a workflow request from the supplied arguments.
-- 
GitLab