code: def monitor_jobs(
      jobs: tp.Sequence[core.Job[core.R]],
      poll_frequency: float = 30,
      test_mode: bool = False,
      custom_logging: tp.Callable = _default_custom_logging,
  ) -> None:
      """Continuously monitors given jobs until they are all done or failed.
  
      Parameters
      ----------
      jobs: List[Jobs]
          A list of jobs to monitor
      poll_frequency: int
          The time (in seconds) between two refreshes of the monitoring.
          Can't be inferior to 30s.
      test_mode: bool
          If in test mode, we do not check the length of poll_frequency
      """
  
      if not test_mode:
          assert (
              poll_frequency >= 30
          ), "You can't refresh too often (>= 30s) to avoid overloading squeue"
  
      n_jobs = len(jobs)
      if n_jobs == 0:
          print("There are no jobs to monitor")
          return
  
      job_arrays = ", ".join(
          sorted(set(str(job.job_id).split("_", 1)[0] for job in jobs))
      )
      print(f"Monitoring {n_jobs} jobs from job arrays {job_arrays} \n")
  
      monitoring_start_time = time.time()
      while True:
          if not test_mode:
              jobs[0].get_info(
                  mode="force"
              )  # Force update once to sync the state
          state_jobs = collections.defaultdict(set)
          for i, job in enumerate(jobs):
              state_jobs[job.state.upper()].add(i)
              if job.done():
                  state_jobs["DONE"].add(i)
  
          failed_job_indices = sorted(state_jobs["FAILED"])
          if len(state_jobs["DONE"]) == len(jobs):
              print(
                  f"All jobs finished, jobs with indices {failed_job_indices} failed",
                  flush=True,
              )
              break
  
          custom_logging(monitoring_start_time, n_jobs, state_jobs)
          time.sleep(poll_frequency)
  
      print(
          f"Whole process is finished, took {int((time.time() - monitoring_start_time) / 60)} minutes"
      )
  
imports: import collections
  import contextlib
  import datetime
  import itertools
  import os
  import random
  import shutil
  import subprocess
  import tempfile
  import time
  import typing
  from pathlib import Path
  from .core import core
  from .core.job_environment import JobEnvironment
  from .core.utils import CommandFunction
  from .core.utils import DelayedSubmission
  from .core.utils import environment_variables
label_desc: Use image node_id 4 for calling a global function with example usage: monitor_jobs(jobs, poll_frequency, test_mode, custom_logging) without return types