JAL-3954 reuse spinner dialog to show search task status
authorMateusz Warowny <mmzwarowny@dundee.ac.uk>
Tue, 30 May 2023 15:41:34 +0000 (17:41 +0200)
committerMateusz Warowny <mmzwarowny@dundee.ac.uk>
Fri, 2 Jun 2023 13:16:47 +0000 (15:16 +0200)
resources/lang/Messages.properties
src/jalview/ws2/client/ebi/PhmmerWSClient.java
src/jalview/ws2/gui/SearchServiceGuiHandler.java
src/jalview/ws2/gui/WebServicesMenuManager.java

index f8c2b68..59c2bd3 100644 (file)
@@ -1109,6 +1109,7 @@ info.alignment_object_method_notes = \nAlignment Object Method Notes\n
 info.server_exception = \n{0} Server exception\!\n{1}
 info.invalid_msa_input_mininfo = Need at least two sequences with at least 3 residues each, with no hidden regions between them.  
 info.invalid_msa_notenough = Not enough sequence data to align
+info.invalid_search_input = Invalid input for sequence search.
 status.processing_commandline_args = Processing commandline arguments...
 status.das_features_being_retrived = DAS features being retrieved...
 status.searching_for_sequences_from = Searching for sequences from {0}
index 9c79644..0bcb2f9 100644 (file)
@@ -5,6 +5,7 @@ import java.io.StringReader;
 import java.net.URI;
 import java.util.List;
 
+import jalview.bin.Console;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.SequenceI;
 import jalview.io.DataSourceType;
@@ -58,6 +59,7 @@ public class PhmmerWSClient implements AlignmentWebServiceClientI
     var email = credentials.getEmail() != null ? credentials.getEmail() :
       "nouser@jalview.org";
     var jobId = client.submit(request.build(), email);
+    Console.debug("Phmmer client submitted new job with id " + jobId);
     return new WebServiceJobHandle(
             getClientName(), "phmmer", getUrl(), jobId);
   }
index b3ae5c1..519adc7 100644 (file)
@@ -2,94 +2,241 @@ package jalview.ws2.gui;
 
 import java.util.List;
 
+import javax.swing.SwingUtilities;
+
 import jalview.bin.Console;
+import jalview.datamodel.Alignment;
+import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.gui.AlignFrame;
 import jalview.gui.Desktop;
+import jalview.gui.JvOptionPane;
+import jalview.gui.WebserviceInfo;
+import jalview.util.ArrayUtils;
+import jalview.util.MessageManager;
+import jalview.ws2.actions.api.ActionI;
 import jalview.ws2.actions.api.JobI;
 import jalview.ws2.actions.api.TaskEventListener;
 import jalview.ws2.actions.api.TaskI;
 import jalview.ws2.api.JobStatus;
+import jalview.ws2.api.WebService;
+import jalview.ws2.helpers.WSClientTaskWrapper;
+
+import static java.lang.String.format;
 
-public class SearchServiceGuiHandler implements TaskEventListener<AlignmentI>
+class SearchServiceGuiHandler implements TaskEventListener<AlignmentI>
 {
   private final AlignFrame parentFrame;
-  
-  public SearchServiceGuiHandler(AlignFrame parentFrame)
+
+  private final ActionI<?> action;
+
+  private final WebService<?> service;
+
+  private WebserviceInfo infoPanel;
+
+  private JobI[] jobs = new JobI[0];
+
+  private int[] tabs = new int[0];
+
+  private int[] logOffset = new int[0];
+
+  private int[] errLogOffset = new int[0];
+
+  public SearchServiceGuiHandler(ActionI<?> action, AlignFrame parentFrame)
   {
     this.parentFrame = parentFrame;
+    this.action = action;
+    this.service = action.getWebService();
+    var info = String.format("%s search using service at %s%n%s",
+            service.getName(), service.getUrl(), service.getDescription());
+    this.infoPanel = new WebserviceInfo(service.getName(), info, false);
   }
 
   @Override
   public void taskStarted(TaskI<AlignmentI> source,
           List<? extends JobI> subJobs)
   {
-    Console.info("task started with " + subJobs.size() + " jobs");
-    // TODO Auto-generated method stub
-    
+    Console.debug(format("task %s#%x started with %d sub-jobs",
+            service.getName(), source.getUid(), subJobs.size()));
+    jobs = subJobs.toArray(new JobI[subJobs.size()]);
+    tabs = new int[subJobs.size()];
+    logOffset = new int[subJobs.size()];
+    errLogOffset = new int[subJobs.size()];
+    for (int i = 0; i < subJobs.size(); i++)
+    {
+      JobI job = jobs[i];
+      int tabIndex = infoPanel.addJobPane();
+      tabs[i] = tabIndex;
+      infoPanel.setProgressName(format("region %d", i), tabIndex);
+      infoPanel.setProgressText(tabIndex, "Job details:\n");
+      // jobs should not have states other than invalid or ready at this point
+      if (job.getStatus() == JobStatus.INVALID)
+        infoPanel.setStatus(tabIndex, WebserviceInfo.STATE_STOPPED_OK);
+      else if (job.getStatus() == JobStatus.READY)
+        infoPanel.setStatus(tabIndex, WebserviceInfo.STATE_QUEUING);
+    }
   }
 
   @Override
   public void taskStatusChanged(TaskI<AlignmentI> source, JobStatus status)
   {
-    Console.info("task status " + status);
-    // TODO Auto-generated method stub
-    
+    Console.debug(format("task %s#%x status changed to %s",
+            service.getName(), source.getUid(), status));
+    switch (status)
+    {
+    case INVALID:
+      infoPanel.setVisible(false);
+      JvOptionPane.showMessageDialog(parentFrame,
+              MessageManager.getString("info.invalid_search_input"),
+              MessageManager.getString("info.invalid_search_input"),
+              JvOptionPane.INFORMATION_MESSAGE);
+      break;
+    case READY:
+      infoPanel.setthisService(new WSClientTaskWrapper(source));
+      infoPanel.setVisible(true);
+      // intentional no break
+    case SUBMITTED:
+    case QUEUED:
+      infoPanel.setStatus(WebserviceInfo.STATE_QUEUING);
+      break;
+    case RUNNING:
+    case UNKNOWN: // unsure what to do with unknown
+      infoPanel.setStatus(WebserviceInfo.STATE_RUNNING);
+      break;
+    case COMPLETED:
+      infoPanel.setProgressBar(
+              MessageManager.getString("status.collecting_job_results"),
+              jobs[0].getInternalId());
+      infoPanel.setStatus(WebserviceInfo.STATE_STOPPED_OK);
+      break;
+    case FAILED:
+      infoPanel.removeProgressBar(jobs[0].getInternalId());
+      infoPanel.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
+      break;
+    case CANCELLED:
+      infoPanel.setStatus(WebserviceInfo.STATE_CANCELLED_OK);
+      break;
+    case SERVER_ERROR:
+      infoPanel.removeProgressBar(jobs[0].getInternalId());
+      infoPanel.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
+      break;
+    }
   }
 
   @Override
   public void taskCompleted(TaskI<AlignmentI> source, AlignmentI result)
   {
-    Console.info("task completed");
-    displayResultsNewFrame(result);
+    Console.debug(format("task %s#%x completed", service.getName(),
+            source.getUid()));
+    SwingUtilities.invokeLater(
+            () -> infoPanel.removeProgressBar(jobs[0].getInternalId()));
+    if (result == null)
+    {
+      SwingUtilities.invokeLater(infoPanel::setFinishedNoResults);
+      return;
+    }
+    infoPanel.showResultsNewFrame.addActionListener(evt -> {
+      // copy alignment for each frame to have its own instance
+      var alnCpy = new Alignment(result);
+      alnCpy.setGapCharacter(result.getGapCharacter());
+      alnCpy.setDataset(result.getDataset());
+      for (AlignmentAnnotation annotation : result.getAlignmentAnnotation())
+        alnCpy.addAnnotation(new AlignmentAnnotation(annotation));
+      displayResultsNewFrame(alnCpy);
+    });
+    SwingUtilities.invokeLater(infoPanel::setResultsReady);
+  }
+
+  private void displayResultsNewFrame(AlignmentI aln)
+  {
+    AlignFrame frame = new AlignFrame(aln, AlignFrame.DEFAULT_WIDTH,
+            AlignFrame.DEFAULT_HEIGHT);
+    frame.getFeatureRenderer().transferSettings(
+            parentFrame.getFeatureRenderer().getSettings());
+    var actionName = action.getName() != null ? action.getName() : "Search";
+    var title = String.format("%s %s of %s", service.getName(), actionName,
+            parentFrame.getTitle());
+    Desktop.addInternalFrame(frame, title, AlignFrame.DEFAULT_WIDTH,
+            AlignFrame.DEFAULT_HEIGHT);
   }
 
   @Override
   public void taskException(TaskI<AlignmentI> source, Exception e)
   {
-    Console.info("task failed", e);
-    // TODO Auto-generated method stub
-    
+    Console.error(format("Task %s#%x raised an exception.",
+            service.getName(), source.getUid()), e);
+    infoPanel.appendProgressText(e.getMessage());
   }
 
   @Override
   public void taskRestarted(TaskI<AlignmentI> source)
   {
-    Console.info("task restarted");
-    // TODO Auto-generated method stub
-    
+    // search services non-restartable
   }
 
   @Override
   public void subJobStatusChanged(TaskI<AlignmentI> source, JobI job,
           JobStatus status)
   {
-    Console.info("sub-job " + job.getInternalId() + " status " + status);
-    // TODO Auto-generated method stub
-    
+    Console.debug(format("sub-job %x status changed to %s",
+            job.getInternalId(), status));
+    int i = ArrayUtils.indexOf(jobs, job);
+    assert i >= 0 : "job does not exist";
+    if (i < 0)
+      // safeguard that should not happen irl
+      return;
+    int wsStatus;
+    switch (status)
+    {
+    case INVALID:
+    case COMPLETED:
+      wsStatus = WebserviceInfo.STATE_STOPPED_OK;
+      break;
+    case READY:
+    case SUBMITTED:
+    case QUEUED:
+      wsStatus = WebserviceInfo.STATE_QUEUING;
+      break;
+    case RUNNING:
+    case UNKNOWN:
+      wsStatus = WebserviceInfo.STATE_RUNNING;
+      break;
+    case FAILED:
+      wsStatus = WebserviceInfo.STATE_STOPPED_ERROR;
+      break;
+    case CANCELLED:
+      wsStatus = WebserviceInfo.STATE_CANCELLED_OK;
+      break;
+    case SERVER_ERROR:
+      wsStatus = WebserviceInfo.STATE_STOPPED_SERVERERROR;
+      break;
+    default:
+      throw new AssertionError("Non-exhaustive switch statement");
+    }
+    infoPanel.setStatus(tabs[i], wsStatus);
   }
 
   @Override
   public void subJobLogChanged(TaskI<AlignmentI> source, JobI job,
           String log)
   {
-    // TODO Auto-generated method stub
-    
+    int i = ArrayUtils.indexOf(jobs, job);
+    assert i >= 0 : "job does not exist";
+    if (i < 0)
+      // safeguard that should never happen
+      return;
+    infoPanel.appendProgressText(tabs[i], log.substring(logOffset[i]));
   }
 
   @Override
   public void subJobErrorLogChanged(TaskI<AlignmentI> source, JobI job,
           String log)
   {
-    // TODO Auto-generated method stub
-    
-  }
-
-  private void displayResultsNewFrame(AlignmentI aln)
-  {
-    AlignFrame frame = new AlignFrame(aln, AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
-    frame.getFeatureRenderer().transferSettings(
-            parentFrame.getFeatureRenderer().getSettings());
-    Desktop.addInternalFrame(frame, "title", AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
+    int i = ArrayUtils.indexOf(jobs, job);
+    assert i >= 0 : "job does not exist";
+    if (i < 0)
+      // safeguard that should never happen
+      return;
+    infoPanel.appendProgressText(tabs[i], log.substring(errLogOffset[i]));
   }
 }
index 8e2dcee..12aeaa9 100644 (file)
@@ -474,7 +474,7 @@ public class WebServicesMenuManager
     if (action instanceof PhmmerAction)
     {
       var _action = (PhmmerAction) action;
-      var handler = new SearchServiceGuiHandler(frame);
+      var handler = new SearchServiceGuiHandler(_action, frame);
       return _action.perform(viewport, args, credentials, handler);
     }
     Console.warn(String.format(