Repository: Qiskit/qiskit

Issue:
backend.view_jobs()
Many times we want to be able to view the jobs we have ran and save on the cloud. 

In the spec, we defined a  backend.jobs() method which returns job objects. The problem is if this returns a job object then service that requires the job to be deleted after it has been called it removes it from the cloud. Since the use case is just to have a way of checking on all my jobs 

```
for ran_job in backend.jobs(limit=5):
    print(str(ran_job.job_id()) + " " + str(ran_job.status()))
```
```
5bba0ceb1a814c003cbfdda2 JobStatus.CANCELLED
5bba0c3d5d72db004c6e1867 JobStatus.DONE
5bba0c3dc6992500552f155c JobStatus.DONE
5bb9b0f19458ec0044ab4954 JobStatus.DONE
5bb9abd60b99f20045d26437 JobStatus.DONE
```
I suggest that we change this to 

```
backend.view_jobs()
```
Are there any other things that should be displayed. I think something like a message to add that is human readable if an error occuers. 





Discussion:
Actually, with some help from the API we might be able to keep `backend.jobs()` returning `Job` instances, but avoid consuming them until the user explicitly calls `the_job.result()`. Would that be preferable to adding a `.view_jobs` method? I think it would be conceptually neater (as in, the user would get one or more jobs one way or the other, and they can decide how to list them and inspect them).
This works as long as they don’t delete and I can get enough meta data to eventually go into an activity monitor. 

Resolving patch:
diff --git a/qiskit/backends/ibmq/ibmqbackend.py b/qiskit/backends/ibmq/ibmqbackend.py
--- a/qiskit/backends/ibmq/ibmqbackend.py
+++ b/qiskit/backends/ibmq/ibmqbackend.py
@@ -242,20 +242,21 @@ def jobs(self, limit=50, skip=0, status=None, db_filter=None):
             elif status == JobStatus.ERROR:
                 this_filter = {'status': {'regexp': '^ERROR'}}
             else:
-                raise IBMQBackendValueError('unrecongized value for "status" keyword '
+                raise IBMQBackendValueError('unrecognized value for "status" keyword '
                                             'in job filter')
             api_filter.update(this_filter)
         if db_filter:
-            # status takes precendence over db_filter for same keys
+            # status takes precedence over db_filter for same keys
             api_filter = {**db_filter, **api_filter}
-        job_info_list = self._api.get_jobs(limit=limit, skip=skip,
-                                           filter=api_filter)
+        job_info_list = self._api.get_status_jobs(limit=limit, skip=skip,
+                                                  filter=api_filter)
         job_list = []
         for job_info in job_info_list:
             job_class = _job_class_from_job_response(job_info)
             is_device = not bool(self._configuration.get('simulator'))
             job = job_class(self, job_info.get('id'), self._api, is_device,
-                            creation_date=job_info.get('creationDate'))
+                            creation_date=job_info.get('creationDate'),
+                            api_status=job_info.get('status'))
             job_list.append(job)
         return job_list
 
@@ -272,7 +273,7 @@ def retrieve_job(self, job_id):
             IBMQBackendError: if retrieval failed
         """
         try:
-            job_info = self._api.get_job(job_id)
+            job_info = self._api.get_status_job(job_id)
             if 'error' in job_info:
                 raise IBMQBackendError('Failed to get job "{}": {}'
                                        .format(job_id, job_info['error']))
@@ -282,7 +283,8 @@ def retrieve_job(self, job_id):
         job_class = _job_class_from_job_response(job_info)
         is_device = not bool(self._configuration.get('simulator'))
         job = job_class(self, job_info.get('id'), self._api, is_device,
-                        creation_date=job_info.get('creationDate'))
+                        creation_date=job_info.get('creationDate'),
+                        api_status=job_info.get('status'))
         return job
 
     def __repr__(self):
diff --git a/qiskit/backends/ibmq/ibmqjob.py b/qiskit/backends/ibmq/ibmqjob.py
--- a/qiskit/backends/ibmq/ibmqjob.py
+++ b/qiskit/backends/ibmq/ibmqjob.py
@@ -115,19 +115,21 @@ class IBMQJob(BaseJob):
     _executor = futures.ThreadPoolExecutor()
 
     def __init__(self, backend, job_id, api, is_device, qobj=None,
-                 creation_date=None, **kwargs):
+                 creation_date=None, api_status=None, **kwargs):
         """IBMQJob init function.
+
         We can instantiate jobs from two sources: A QObj, and an already submitted job returned by
         the API servers.
 
         Args:
+            backend (str): The backend instance used to run this job.
+            job_id (str): The job ID of an already submitted job. Pass `None`
+                if you are creating a new one.
             api (IBMQuantumExperience): IBM Q API
             is_device (bool): whether backend is a real device  # TODO: remove this after Qobj
             qobj (Qobj): The Quantum Object. See notes below
-            job_id (String): The job ID of an already submitted job. Pass `None`
-            if you are creating a new one.
-            creation_date(String): When the job was run.
-            backend (str): The backend instance used to run this job.
+            creation_date (str): When the job was run.
+            api_status (str): `status` field directly from the API response.
             kwargs (dict): You can pass `backend_name` to this function although
                 it has been deprecated.
 
@@ -161,13 +163,25 @@ def __init__(self, backend, job_id, api, is_device, qobj=None,
         self._future_captured_exception = None
         self._api = api
         self._backend = backend
+        self._cancelled = False
         self._status = JobStatus.INITIALIZING
-        # In case of not providing a qobj, it assumes job_id has been provided
-        # and query the API for updating the status.
+        # In case of not providing a `qobj`, it is assumed the job already
+        # exists in the API (with `job_id`).
         if qobj is None:
-            self.status()
+            # Some API calls (`get_status_jobs`, `get_status_job`) provide
+            # enough information to recreate the `Job`. If that is the case, try
+            # to make use of that information during instantiation, as
+            # `self.status()` involves an extra call to the API.
+            if api_status == 'VALIDATING':
+                self._status = JobStatus.VALIDATING
+            elif api_status == 'COMPLETED':
+                self._status = JobStatus.DONE
+            elif api_status == 'CANCELLED':
+                self._status = JobStatus.CANCELLED
+                self._cancelled = True
+            else:
+                self.status()
         self._queue_position = None
-        self._cancelled = False
         self._is_device = is_device
 
         def current_utc_time():
@@ -264,7 +278,7 @@ def status(self):
             # TODO: See result values
             api_job = self._api.get_status_job(self._job_id)
             if 'status' not in api_job:
-                raise JobError('get_job didn\'t return status: %s' %
+                raise JobError('get_status_job didn\'t return status: %s' %
                                pprint.pformat(api_job))
         # pylint: disable=broad-except
         except Exception as err:
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -17,7 +17,7 @@
 
 requirements = [
     "jsonschema>=2.6,<2.7",
-    "IBMQuantumExperience>=2.0.3",
+    "IBMQuantumExperience>=2.0.4",
     "matplotlib>=2.1",
     "networkx>=2.0",
     "numpy>=1.13",


Test patch: