Repository: phac-nml/irida-uploader

Issue:
GUI: partially uploaded runs resulting in error are not being detected as partial
This means the user cannot continue an upload if their upload stops halfway through do to a server error


Discussion:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cef3b7d..8ee2cf4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,14 @@
 Changes
 =======
 
+Beta 0.8.3
+----------
+Changes:
+* [GUI] Partial uploads now display the reason (error) that caused the run to stop.
+
+Bug Fixes:
+* Fixed Error's setting run to ERROR when run has uploaded samples. Now sets these runs to PARTIAL to allow continuing from where the Error occurred.
+
 Beta 0.8.2
 ----------
 Bug Fixes:
diff --git a/iridauploader/__init__.py b/iridauploader/__init__.py
index b0c9879..e1fc42e 100644
--- a/iridauploader/__init__.py
+++ b/iridauploader/__init__.py
@@ -1,1 +1,1 @@
-VERSION_NUMBER = "0.8.2"
+VERSION_NUMBER = "0.8.3"
diff --git a/iridauploader/core/api_handler.py b/iridauploader/core/api_handler.py
index 4abeae8..90e9653 100644
--- a/iridauploader/core/api_handler.py
+++ b/iridauploader/core/api_handler.py
@@ -162,6 +162,7 @@ def upload_sequencing_run(sequencing_run, directory_status, upload_mode, run_id=
         logging.info("Using existing run id '{}' for this upload.".format(run_id))
     # Update directory status file
     directory_status.run_id = run_id
+    directory_status.status = model.DirectoryStatus.PARTIAL
     progress.write_directory_status(directory_status)
 
     try:
diff --git a/iridauploader/core/upload_helpers.py b/iridauploader/core/upload_helpers.py
index 6595b79..47e8927 100644
--- a/iridauploader/core/upload_helpers.py
+++ b/iridauploader/core/upload_helpers.py
@@ -28,6 +28,11 @@ def _set_and_write_directory_status(directory_status, status, message=None):
     :param message: string
     :return:
     """
+    # If a run is being set to error, but it already has a sequencing id (started upload),
+    # it is a partial run that could be resumed.
+    if status == DirectoryStatus.ERROR and directory_status.run_id is not None:
+        status = DirectoryStatus.PARTIAL
+
     try:
         directory_status.status = status
         directory_status.message = message
@@ -76,8 +81,6 @@ def parse_and_validate(directory_status, parse_as_partial):
     :param parse_as_partial: sequencing_run will not include any samples that have already been uploaded
     :return:
     """
-    # Set directory status to partial before starting
-    _set_and_write_directory_status(directory_status, DirectoryStatus.PARTIAL)
 
     try:
         sequencing_run = parsing_handler.parse_and_validate(directory_status.directory)
diff --git a/iridauploader/gui/main_dialog.py b/iridauploader/gui/main_dialog.py
index 6301c05..9bcffa5 100644
--- a/iridauploader/gui/main_dialog.py
+++ b/iridauploader/gui/main_dialog.py
@@ -413,8 +413,11 @@ class MainDialog(QtWidgets.QDialog):
             # We need to block upload until the user clicks continue
             self._upload_button.set_block()
             # give user info
-            self._show_and_fill_info_partial_upload_options(
-                "This run directory is partially uploaded. Choose how you would like to Continue.")
+            user_message = "This run directory is partially uploaded. Choose how you would like to Continue."
+            if status.message is not None:
+                user_message = user_message + " This directory had the error(s) below. "
+                self._show_previous_error(status.message)
+            self._show_and_fill_info_partial_upload_options(user_message)
 
         elif status.status_equals(DirectoryStatus.ERROR):
             # We need to block upload until the user clicks continue
diff --git a/setup.py b/setup.py
index d485422..64fa87e 100644
--- a/setup.py
+++ b/setup.py
@@ -6,7 +6,7 @@ with open("README.md", "r") as fh:
 
 setuptools.setup(
     name='iridauploader',
-    version='0.8.2',
+    version='0.8.3',
     description='IRIDA uploader: upload NGS data to IRIDA system',
     url='https://github.com/phac-nml/irida-uploader',
     author='Jeffrey Thiessen',
diff --git a/windows-installer.cfg b/windows-installer.cfg
index a8958e2..49930ba 100644
--- a/windows-installer.cfg
+++ b/windows-installer.cfg
@@ -1,6 +1,6 @@
 [Application]
 name=IRIDA Uploader GUI
-version=0.8.2
+version=0.8.3
 entry_point=iridauploader.gui.gui:main
 icon=iridauploader/gui/images/icon.ico
 # Uncomment this to have a console show alongside the application


Test patch:
diff --git a/iridauploader/tests/core/test_api_handler.py b/iridauploader/tests/core/test_api_handler.py
index f41cc16..8dfc3bc 100644
--- a/iridauploader/tests/core/test_api_handler.py
+++ b/iridauploader/tests/core/test_api_handler.py
@@ -8,6 +8,7 @@ from iridauploader.parsers.miseq.parser import Parser
 from iridauploader.api import MODE_DEFAULT, MODE_FAST5, MODE_ASSEMBLIES
 from iridauploader.api.exceptions import IridaResourceError
 from iridauploader.model.exceptions import ModelValidationError
+from iridauploader.model import DirectoryStatus
 
 path_to_module = path.abspath(path.dirname(__file__))
 if len(path_to_module) == 0:
@@ -151,6 +152,7 @@ class TestUploadSequencingRun(unittest.TestCase):
 
     class StubDirectoryStatus:
         run_id = None
+        status = None
 
         def set_sample_uploaded(self, sample_name, project_id, uploaded):
             return None
@@ -189,12 +191,13 @@ class TestUploadSequencingRun(unittest.TestCase):
         stub_api_instance.set_seq_run_uploading.side_effect = [True]
         stub_api_instance.send_sequence_files.side_effect = [True, True, True]
         stub_api_instance.set_seq_run_complete.side_effect = [True]
+        stub_directory_status = self.StubDirectoryStatus()
 
         mock_api_instance.side_effect = [stub_api_instance]
         mock_progress.side_effect = [None, None, None, None, None, None, None, None, None, None]
 
         api_handler.upload_sequencing_run(sequencing_run,
-                                          directory_status=self.StubDirectoryStatus(),
+                                          directory_status=stub_directory_status,
                                           upload_mode=MODE_DEFAULT)
 
         # ensure the response matches our mocks, and that all the needed functions were called correctly
@@ -210,6 +213,9 @@ class TestUploadSequencingRun(unittest.TestCase):
                                upload_id=55, upload_mode=MODE_DEFAULT)
         ])
         stub_api_instance.set_seq_run_complete.assert_called_once_with(mock_sequence_run_id)
+        # Verify the DirectoryStatus object got assigned a run_id and status for upload
+        self.assertEqual(stub_directory_status.status, DirectoryStatus.PARTIAL)
+        self.assertEqual(stub_directory_status.run_id, mock_sequence_run_id)
 
     @patch("iridauploader.core.api_handler._get_api_instance")
     @patch("iridauploader.progress.write_directory_status")
diff --git a/iridauploader/tests/core/test_upload_helpers.py b/iridauploader/tests/core/test_upload_helpers.py
index 69ae1a1..1b71d62 100644
--- a/iridauploader/tests/core/test_upload_helpers.py
+++ b/iridauploader/tests/core/test_upload_helpers.py
@@ -22,6 +22,7 @@ class TestSetAndWriteDirectoryStatus(unittest.TestCase):
     class StubDirectoryStatus:
         status = None
         message = ""
+        run_id = None
 
     def setUp(self):
         print("\nStarting " + self.__module__ + ": " + self._testMethodName)
@@ -43,6 +44,46 @@ class TestSetAndWriteDirectoryStatus(unittest.TestCase):
         # verify write
         mock_progress.write_directory_status.assert_called_with(stub_dir_status)
 
+    @patch("iridauploader.core.upload_helpers.progress")
+    def test_valid_write_error(self, mock_progress):
+        """
+        Tessts a valid write attempt of ERROR, where the upload has not begun
+        :param mock_progress:
+        :return:
+        """
+        mock_initial_status = DirectoryStatus.ERROR
+        mock_expected_status = DirectoryStatus.ERROR
+        mock_message = "message"
+        stub_dir_status = self.StubDirectoryStatus()
+        stub_dir_status.run_id = None  # Upload has not started
+        # mock main call to test
+        mock_progress.write_directory_status.side_effect = [None]
+        # run function
+        upload_helpers._set_and_write_directory_status(stub_dir_status, mock_initial_status, mock_message)
+        # verify write
+        stub_dir_status.status = mock_expected_status
+        mock_progress.write_directory_status.assert_called_with(stub_dir_status)
+
+    @patch("iridauploader.core.upload_helpers.progress")
+    def test_valid_write_partial(self, mock_progress):
+        """
+        Tessts a valid write attempt of PARTIAL, ERROR is given but the run has started
+        :param mock_progress:
+        :return:
+        """
+        mock_initial_status = DirectoryStatus.ERROR
+        mock_expected_status = DirectoryStatus.ERROR
+        mock_message = "message"
+        stub_dir_status = self.StubDirectoryStatus()
+        stub_dir_status.run_id = 123  # Upload has not started
+        # mock main call to test
+        mock_progress.write_directory_status.side_effect = [None]
+        # run function
+        upload_helpers._set_and_write_directory_status(stub_dir_status, mock_initial_status, mock_message)
+        # verify write
+        stub_dir_status.status = mock_expected_status
+        mock_progress.write_directory_status.assert_called_with(stub_dir_status)
+
     @patch("iridauploader.core.upload_helpers.progress.write_directory_status")
     def test_invalid_write(self, mock_progress):
         """
@@ -114,7 +155,6 @@ class TestParseAndValidate(unittest.TestCase):
     def test_valid_parse(self, mock_parsing_handler, mock_set_and_write):
         """
         verifies parse and validate was called,
-        and _set_and_write_directory_status is called once with PARTIAL
         :param mock_parsing_handler:
         :param mock_set_and_write:
         :return:
@@ -127,15 +167,13 @@ class TestParseAndValidate(unittest.TestCase):
             directory_status=stub_directory_status, parse_as_partial=False))
 
         mock_parsing_handler.parse_and_validate.assert_called_with(stub_directory_status.directory)
-        mock_set_and_write.assert_called_with(stub_directory_status, DirectoryStatus.PARTIAL)
 
     @patch("iridauploader.core.upload_helpers.set_uploaded_samples_to_skip")
     @patch("iridauploader.core.upload_helpers._set_and_write_directory_status")
     @patch("iridauploader.core.upload_helpers.parsing_handler")
     def test_valid_partial_parse(self, mock_parsing_handler, mock_set_and_write, mock_set_uploaded_samples_to_skip):
         """
-        verifies parse and validate was called,
-        and _set_and_write_directory_status is called once with PARTIAL
+        verifies parse and validate was called with partial run handling
         :param mock_parsing_handler:
         :param mock_set_and_write:
         :return:
@@ -149,7 +187,6 @@ class TestParseAndValidate(unittest.TestCase):
             directory_status=stub_directory_status, parse_as_partial=True))
 
         mock_parsing_handler.parse_and_validate.assert_called_with(stub_directory_status.directory)
-        mock_set_and_write.assert_called_with(stub_directory_status, DirectoryStatus.PARTIAL)
         mock_set_uploaded_samples_to_skip.assert_called_with("return_value",
                                                              stub_directory_status.get_sample_status_list())
 
@@ -422,6 +459,11 @@ class TestUploadSequencingRun(unittest.TestCase):
     Tests core.upload_helpers.upload_sequencing_run
     """
 
+    class StubDirectoryStatus:
+        status = None
+        message = ""
+        run_id = None
+
     def setUp(self):
         print("\nStarting " + self.__module__ + ": " + self._testMethodName)
 
@@ -480,7 +522,7 @@ class TestUploadSequencingRun(unittest.TestCase):
     def test_invalid_connection(self, mock_api_handler, mock_set_and_write):
         """
         tests catching and raising IridaConnectionError
-        and _set_and_write_directory_status is DirectoryStatus.COMPLETE
+        and _set_and_write_directory_status is DirectoryStatus.ERROR
         :param mock_api_handler:
         :param mock_set_and_write:
         :return:
@@ -488,16 +530,23 @@ class TestUploadSequencingRun(unittest.TestCase):
         mock_api_handler.upload_sequencing_run.side_effect = [IridaConnectionError]
         mock_set_and_write.side_effect = [True]
 
+        # Init directory status as a new run
+        stub_directory_status = self.StubDirectoryStatus()
+        stub_directory_status.status = DirectoryStatus.NEW
+        stub_directory_status.run_id = None
+
         with self.assertRaises(IridaConnectionError):
-            upload_helpers.upload_sequencing_run(directory_status='status',
+            upload_helpers.upload_sequencing_run(directory_status=stub_directory_status,
                                                  sequencing_run='run',
                                                  upload_mode='mode')
 
-        mock_api_handler.upload_sequencing_run.assert_called_with(directory_status='status',
+        mock_api_handler.upload_sequencing_run.assert_called_with(directory_status=stub_directory_status,
                                                                   sequencing_run='run',
                                                                   upload_mode='mode',
                                                                   run_id=None)
-        mock_set_and_write.assert_called_with("status", DirectoryStatus.ERROR, 'Lost connection to Irida. Errors: ()')
+        mock_set_and_write.assert_called_with(stub_directory_status,
+                                              DirectoryStatus.ERROR,
+                                              'Lost connection to Irida. Errors: ()')
 
     @patch("iridauploader.core.upload_helpers._set_and_write_directory_status")
     @patch("iridauploader.core.upload_helpers.api_handler")