From 467deb0b98b122a4ca84f6236d190f01553c52de Mon Sep 17 00:00:00 2001
From: Mel Hall <37735232+datamel@users.noreply.github.com>
Date: Mon, 6 Jun 2022 15:26:05 +0100
Subject: [PATCH 1/8] Add workflow argument to cylc gui command.
---
cylc/uiserver/app.py | 8 ++++++--
cylc/uiserver/scripts/gui.py | 25 ++++++++++++++++++++++++-
2 files changed, 30 insertions(+), 3 deletions(-)
diff --git a/cylc/uiserver/app.py b/cylc/uiserver/app.py
index b1c79fc4..4320a855 100644
--- a/cylc/uiserver/app.py
+++ b/cylc/uiserver/app.py
@@ -126,7 +126,7 @@ class CylcUIServer(ExtensionApp):
name = 'cylc'
app_name = 'cylc-gui'
- default_url = "/cylc"
+ subcommands = []
load_other_extensions = True
description = '''
Cylc - A user interface for monitoring and controlling Cylc workflows.
@@ -506,7 +506,11 @@ def initialize_templates(self):
"""Change the jinja templating environment."""
@classmethod
- def launch_instance(cls, argv=None, **kwargs):
+ def launch_instance(cls, argv=None, workflow_id=None, **kwargs):
+ if workflow_id:
+ cls.default_url = f"/cylc/#/workflows/{workflow_id}"
+ else:
+ cls.default_url = "/cylc"
if argv is None:
# jupyter server isn't expecting to be launched by a Cylc command
# this patches some internal logic
diff --git a/cylc/uiserver/scripts/gui.py b/cylc/uiserver/scripts/gui.py
index c0f86b72..c94a81c8 100644
--- a/cylc/uiserver/scripts/gui.py
+++ b/cylc/uiserver/scripts/gui.py
@@ -20,10 +20,33 @@
For a multi-user system see `cylc hub`.
"""
+import sys
+
+# from cylc.flow.id_cli import parse_id, parse_id_async
+from cylc.flow.exceptions import InputError
+
from cylc.uiserver import init_log
from cylc.uiserver.app import CylcUIServer
def main(*argv):
init_log()
- return CylcUIServer.launch_instance(argv or None)
+ workflow_id = None
+ for arg in argv:
+ if arg.startswith('-'):
+ continue
+ try:
+ # workflow_id, _, _ = parse_id(
+ # arg, constraint='workflows', infer_latest_runs=True)
+
+ # The following line needs to be replaced with the line above in
+ # order to interpret the workflow_id. This, causes problems with
+ # the ServerApp Event loop.
+
+ workflow_id = arg
+ argv = tuple(x for x in argv if x != arg)
+ sys.argv.remove(arg)
+ except InputError:
+ print(f"Workflow '{arg}' does not exist.")
+ break
+ return CylcUIServer.launch_instance(argv or None, workflow_id=workflow_id)
From 8900fc99c7779f5212eadf1daae7357c700fcee9 Mon Sep 17 00:00:00 2001
From: Mel Hall <37735232+datamel@users.noreply.github.com>
Date: Thu, 23 Jun 2022 10:50:59 +0100
Subject: [PATCH 2/8] Update Examples.
---
cylc/uiserver/app.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cylc/uiserver/app.py b/cylc/uiserver/app.py
index 4320a855..617ed888 100644
--- a/cylc/uiserver/app.py
+++ b/cylc/uiserver/app.py
@@ -132,7 +132,7 @@ class CylcUIServer(ExtensionApp):
Cylc - A user interface for monitoring and controlling Cylc workflows.
''' # type: ignore[assignment]
examples = '''
- cylc gui # start the cylc GUI
+ cylc gui [workflow] # start the cylc GUI [at the workflow page]
''' # type: ignore[assignment]
config_file_paths = get_conf_dir_hierarchy(
[
From 61e0be8c94bf95836b929a8b0f420b5a629e9482 Mon Sep 17 00:00:00 2001
From: Mel Hall <37735232+datamel@users.noreply.github.com>
Date: Thu, 23 Jun 2022 13:45:21 +0100
Subject: [PATCH 3/8] Interpret Workflow ID supplied for gui command
Remove empty subcommands
Change log
---
CHANGES.md | 5 +++++
cylc/uiserver/app.py | 1 -
cylc/uiserver/scripts/gui.py | 19 +++++++++----------
3 files changed, 14 insertions(+), 11 deletions(-)
diff --git a/CHANGES.md b/CHANGES.md
index 9c2b8279..ac1dc986 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -46,6 +46,11 @@ Traitlets `logging_config` "trait" for more information.
[#323](https://github.com/cylc/cylc-uiserver/pull/323) -
`cylc clean` made available for runs and files within runs.
+### Enhancements
+
+[#370](https://github.com/cylc/cylc-uiserver/pull/370) - Gui command adapted:
+`cylc gui workflow_name` is now supported and will open gui at that workflow.
+
-------------------------------------------------------------------------------
## __cylc-uiserver-1.0.3 (Released 2022-05-31)__
diff --git a/cylc/uiserver/app.py b/cylc/uiserver/app.py
index 617ed888..902155d5 100644
--- a/cylc/uiserver/app.py
+++ b/cylc/uiserver/app.py
@@ -126,7 +126,6 @@ class CylcUIServer(ExtensionApp):
name = 'cylc'
app_name = 'cylc-gui'
- subcommands = []
load_other_extensions = True
description = '''
Cylc - A user interface for monitoring and controlling Cylc workflows.
diff --git a/cylc/uiserver/scripts/gui.py b/cylc/uiserver/scripts/gui.py
index c94a81c8..6a9f2295 100644
--- a/cylc/uiserver/scripts/gui.py
+++ b/cylc/uiserver/scripts/gui.py
@@ -21,8 +21,8 @@
"""
import sys
-
-# from cylc.flow.id_cli import parse_id, parse_id_async
+import asyncio
+from cylc.flow.id_cli import parse_id_async
from cylc.flow.exceptions import InputError
from cylc.uiserver import init_log
@@ -36,14 +36,13 @@ def main(*argv):
if arg.startswith('-'):
continue
try:
- # workflow_id, _, _ = parse_id(
- # arg, constraint='workflows', infer_latest_runs=True)
-
- # The following line needs to be replaced with the line above in
- # order to interpret the workflow_id. This, causes problems with
- # the ServerApp Event loop.
-
- workflow_id = arg
+ loop = asyncio.new_event_loop()
+ task = loop.create_task(
+ parse_id_async(
+ arg, constraint='workflows'))
+ loop.run_until_complete(task)
+ loop.close()
+ workflow_id, _, _ = task.result()
argv = tuple(x for x in argv if x != arg)
sys.argv.remove(arg)
except InputError:
From d710f28830aa1eea38bef667e4cb01d4a5a34213 Mon Sep 17 00:00:00 2001
From: Mel Hall <37735232+datamel@users.noreply.github.com>
Date: Tue, 28 Jun 2022 13:40:54 +0100
Subject: [PATCH 4/8] Restrict number of gui instances to one.
---
cylc/uiserver/app.py | 10 ++++++++++
cylc/uiserver/scripts/gui.py | 38 +++++++++++++++++++++++++++++++++++-
2 files changed, 47 insertions(+), 1 deletion(-)
diff --git a/cylc/uiserver/app.py b/cylc/uiserver/app.py
index 902155d5..2da4738d 100644
--- a/cylc/uiserver/app.py
+++ b/cylc/uiserver/app.py
@@ -54,6 +54,7 @@
"""
from concurrent.futures import ProcessPoolExecutor
+import contextlib
import getpass
from pathlib import Path, PurePath
import sys
@@ -525,3 +526,12 @@ async def stop_extension(self):
self.data_store_mgr.executor.shutdown(wait=False)
# Destroy ZeroMQ context of all sockets
self.workflows_mgr.context.destroy()
+ self.clean_pid_file()
+
+ @staticmethod
+ def clean_pid_file():
+ """Remove pid file if it exists."""
+ pid_file = Path("~/.cylc/uiserver/pid").expanduser()
+ print(f"Removing Process Id file from {pid_file.parent}.")
+ with contextlib.suppress(FileNotFoundError):
+ pid_file.unlink()
diff --git a/cylc/uiserver/scripts/gui.py b/cylc/uiserver/scripts/gui.py
index 6a9f2295..1d7c43c9 100644
--- a/cylc/uiserver/scripts/gui.py
+++ b/cylc/uiserver/scripts/gui.py
@@ -20,8 +20,13 @@
For a multi-user system see `cylc hub`.
"""
-import sys
import asyncio
+import os
+import sys
+
+from pathlib import Path
+from psutil import pid_exists
+
from cylc.flow.id_cli import parse_id_async
from cylc.flow.exceptions import InputError
@@ -31,6 +36,10 @@
def main(*argv):
init_log()
+ pid_file = Path("~/.cylc/uiserver/pid").expanduser()
+ check_pid(pid_file)
+ create_pid_file(pid_file)
+
workflow_id = None
for arg in argv:
if arg.startswith('-'):
@@ -49,3 +58,30 @@ def main(*argv):
print(f"Workflow '{arg}' does not exist.")
break
return CylcUIServer.launch_instance(argv or None, workflow_id=workflow_id)
+
+
+def check_pid(pid_file: Path):
+ """Check for process_id file.
+
+ Raises exception if gui is currently running with active process id.
+ """
+ if not pid_file.is_file():
+ return
+ else:
+ pid = pid_file.read_text()
+ try:
+ if pid_exists(int(pid)):
+ raise Exception(f"cylc gui is running at process id: {pid}")
+ except (TypeError, ValueError):
+ try:
+ pid_file.unlink()
+ print(f"Deleting corrupt process id file. {pid_file.parent}")
+ except FileNotFoundError:
+ pass
+
+
+def create_pid_file(pid_file: Path):
+ """Write current process to process id file."""
+ pid = os.getpid()
+ with open(pid_file, "w") as f:
+ f.write(str(pid))
From 8566bd4ee612cfbd59a09135a9059a3ed19694de Mon Sep 17 00:00:00 2001
From: Mel Hall <37735232+datamel@users.noreply.github.com>
Date: Tue, 28 Jun 2022 13:41:24 +0100
Subject: [PATCH 5/8] Add Tests
Add workflow argument to cylc gui command.
---
CHANGES.md | 5 ++++
cylc/uiserver/tests/test_gui.py | 49 +++++++++++++++++++++++++++++++++
2 files changed, 54 insertions(+)
create mode 100644 cylc/uiserver/tests/test_gui.py
diff --git a/CHANGES.md b/CHANGES.md
index ac1dc986..6817c843 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -51,6 +51,11 @@ Traitlets `logging_config` "trait" for more information.
[#370](https://github.com/cylc/cylc-uiserver/pull/370) - Gui command adapted:
`cylc gui workflow_name` is now supported and will open gui at that workflow.
+### Enhancements
+
+[#370](https://github.com/cylc/cylc-uiserver/pull/370) - Gui command adapted:
+`cylc gui workflow_name` is now supported and will open gui at that workflow.
+
-------------------------------------------------------------------------------
## __cylc-uiserver-1.0.3 (Released 2022-05-31)__
diff --git a/cylc/uiserver/tests/test_gui.py b/cylc/uiserver/tests/test_gui.py
new file mode 100644
index 00000000..f94b9e22
--- /dev/null
+++ b/cylc/uiserver/tests/test_gui.py
@@ -0,0 +1,49 @@
+# Copyright (C) NIWA & British Crown (Met Office) & Contributors.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+from pathlib import Path
+import pytest
+import os
+
+from cylc.uiserver.scripts.gui import check_pid
+
+
+def test_check_pid_raises_error_for_existing_process(mod_tmp_path):
+ """Tests exisiting process in file raises Exception"""
+ pid = os.getpid()
+ tmp_pid_file = Path(mod_tmp_path/"pid")
+ with open(tmp_pid_file, "w") as f:
+ f.write(str(pid))
+ with pytest.raises(Exception) as exc_msg:
+ check_pid(tmp_pid_file)
+ assert f'cylc gui is running at process id: {pid}' == str(exc_msg.value)
+
+
+def test_checking_no_pid_file_does_not_raise_exception(mod_tmp_path):
+ """Test check with no process id file runs without exception."""
+ tmp_pid_file = Path(mod_tmp_path)
+ try:
+ check_pid(tmp_pid_file)
+ except Exception as ex:
+ pytest.fail(f"check_pid() raised Exception unexpectedly: {ex}")
+
+
+def test_check_junk_in_pid_file_deletes_file(mod_tmp_path):
+ """Tests exisiting process in file raises Exception"""
+ tmp_pid_file = Path(mod_tmp_path/"pid")
+ with open(tmp_pid_file, "w") as f:
+ f.write(str("This is a load of rubbish."))
+ check_pid(tmp_pid_file)
+ assert not tmp_pid_file.exists()
From 3806387e0ff1ba33a06d2db6402d2e3cd97173ef Mon Sep 17 00:00:00 2001
From: Melanie Hall <37735232+datamel@users.noreply.github.com>
Date: Wed, 29 Jun 2022 16:20:13 +0100
Subject: [PATCH 6/8] Update cylc/uiserver/scripts/gui.py
Co-authored-by: Oliver Sanders
Add --new option for gui
Review Feedback RD
---
CHANGES.md | 13 +--
cylc/uiserver/app.py | 24 ++---
cylc/uiserver/jupyter_config.py | 1 -
cylc/uiserver/scripts/gui.py | 171 ++++++++++++++++++++++++--------
cylc/uiserver/tests/test_gui.py | 78 +++++++++------
setup.cfg | 1 +
6 files changed, 197 insertions(+), 91 deletions(-)
diff --git a/CHANGES.md b/CHANGES.md
index 6817c843..3c91cb27 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -17,6 +17,9 @@ ones in. -->
### Enhancements
+[#370](https://github.com/cylc/cylc-uiserver/pull/370) - Gui command adapted:
+`cylc gui workflow_id` is now supported and will open gui at that workflow.
+
[#376](https://github.com/cylc/cylc-uiserver/pull/376) -
UIServer logs are now archived. The five most recent logs are retained (located
in `~/.cylc/uiserver/log/`). A new log is created with each UIServer instance.
@@ -46,16 +49,6 @@ Traitlets `logging_config` "trait" for more information.
[#323](https://github.com/cylc/cylc-uiserver/pull/323) -
`cylc clean` made available for runs and files within runs.
-### Enhancements
-
-[#370](https://github.com/cylc/cylc-uiserver/pull/370) - Gui command adapted:
-`cylc gui workflow_name` is now supported and will open gui at that workflow.
-
-### Enhancements
-
-[#370](https://github.com/cylc/cylc-uiserver/pull/370) - Gui command adapted:
-`cylc gui workflow_name` is now supported and will open gui at that workflow.
-
-------------------------------------------------------------------------------
## __cylc-uiserver-1.0.3 (Released 2022-05-31)__
diff --git a/cylc/uiserver/app.py b/cylc/uiserver/app.py
index 2da4738d..61d3d757 100644
--- a/cylc/uiserver/app.py
+++ b/cylc/uiserver/app.py
@@ -54,8 +54,8 @@
"""
from concurrent.futures import ProcessPoolExecutor
-import contextlib
import getpass
+import os
from pathlib import Path, PurePath
import sys
from typing import List
@@ -105,6 +105,8 @@
from cylc.uiserver.websockets.tornado import TornadoSubscriptionServer
from cylc.uiserver.workflows_mgr import WorkflowsManager
+INFO_FILES_DIR = Path(USER_CONF_ROOT / "info_files")
+
class PathType(TraitType):
"""A pathlib traitlet type which allows string and undefined values."""
@@ -129,10 +131,15 @@ class CylcUIServer(ExtensionApp):
app_name = 'cylc-gui'
load_other_extensions = True
description = '''
- Cylc - A user interface for monitoring and controlling Cylc workflows.
+ Cylc gui - A user interface for monitoring and controlling Cylc workflows.
''' # type: ignore[assignment]
examples = '''
- cylc gui [workflow] # start the cylc GUI [at the workflow page]
+ cylc gui # Start the Cylc GUI (At the dashboard page)
+cylc gui [workflow] # Start the Cylc GUI (at the workflow page),
+cylc gui --new [workflow] # Start the Cylc GUI (at the workflow page), with a
+ new instance. By default, if there is an existing
+ gui instance, Cylc will use that.
+
''' # type: ignore[assignment]
config_file_paths = get_conf_dir_hierarchy(
[
@@ -515,7 +522,9 @@ def launch_instance(cls, argv=None, workflow_id=None, **kwargs):
# jupyter server isn't expecting to be launched by a Cylc command
# this patches some internal logic
argv = sys.argv[2:]
+ os.environ["JUPYTER_RUNTIME_DIR"] = str(INFO_FILES_DIR)
super().launch_instance(argv=argv, **kwargs)
+ del os.environ["JUPYTER_RUNTIME_DIR"]
async def stop_extension(self):
# stop the async scan task
@@ -526,12 +535,3 @@ async def stop_extension(self):
self.data_store_mgr.executor.shutdown(wait=False)
# Destroy ZeroMQ context of all sockets
self.workflows_mgr.context.destroy()
- self.clean_pid_file()
-
- @staticmethod
- def clean_pid_file():
- """Remove pid file if it exists."""
- pid_file = Path("~/.cylc/uiserver/pid").expanduser()
- print(f"Removing Process Id file from {pid_file.parent}.")
- with contextlib.suppress(FileNotFoundError):
- pid_file.unlink()
diff --git a/cylc/uiserver/jupyter_config.py b/cylc/uiserver/jupyter_config.py
index 1175ae8b..4880f178 100644
--- a/cylc/uiserver/jupyter_config.py
+++ b/cylc/uiserver/jupyter_config.py
@@ -41,7 +41,6 @@
# may be used by Cylc UI developers to use a development UI build
'CYLC_DEV',
)
-
# this auto-spawns uiservers without user interaction
c.JupyterHub.implicit_spawn_seconds = 0.01
diff --git a/cylc/uiserver/scripts/gui.py b/cylc/uiserver/scripts/gui.py
index 1d7c43c9..82ae40b7 100644
--- a/cylc/uiserver/scripts/gui.py
+++ b/cylc/uiserver/scripts/gui.py
@@ -20,68 +20,159 @@
For a multi-user system see `cylc hub`.
"""
+from ansimarkup import parse as cparse
+import argparse
import asyncio
+from contextlib import suppress
+from glob import glob
import os
+from pathlib import Path
+import random
+import re
import sys
+import webbrowser
-from pathlib import Path
-from psutil import pid_exists
from cylc.flow.id_cli import parse_id_async
-from cylc.flow.exceptions import InputError
+from cylc.flow.exceptions import (
+ InputError,
+ WorkflowFilesError
+)
from cylc.uiserver import init_log
-from cylc.uiserver.app import CylcUIServer
+from cylc.uiserver.app import (
+ CylcUIServer,
+ INFO_FILES_DIR
+)
+
+CLI_OPT_NEW = "--new"
def main(*argv):
init_log()
- pid_file = Path("~/.cylc/uiserver/pid").expanduser()
- check_pid(pid_file)
- create_pid_file(pid_file)
+ new_gui = CLI_OPT_NEW in argv
+ jp_server_opts, new_gui, workflow_id = parse_args_opts()
+ if '--help' not in sys.argv:
+ # get existing jpserver--open.html files
+ # assume if this exists then the server is still running
+ # these files are cleaned by jpserver on shutdown
+ existing_guis = glob(os.path.join(INFO_FILES_DIR, "*open.html"))
+ if existing_guis and not new_gui:
+ gui_file = random.choice(existing_guis)
+ print(
+ "Opening with existing gui." +
+ f" Use {CLI_OPT_NEW} option for a new gui.",
+ file=sys.stderr
+ )
+ update_html_file(gui_file, workflow_id)
+ if '--no-browser' not in sys.argv:
+ webbrowser.open(gui_file, autoraise=True)
+ return
+ return CylcUIServer.launch_instance(
+ jp_server_opts or None, workflow_id=workflow_id
+ )
+
+
+def print_error(error: str, msg: str):
+ """Print formatted error with message"""
+ print(cparse(
+ f'{error}: {msg}'
+ ), file=sys.stderr
+ )
+
+
+def parse_args_opts():
+ """Parse cli args
+ Separate JPserver args from Cylc specific ones.
+ Parse workflow id.
+ Raise error if invalid workflow provided
+ """
+ cylc_opts, jp_server_opts = get_arg_parser().parse_known_args()
+ # gui arg is stripped cylc end but need to re-strip after arg parsing here
+ with suppress(ValueError):
+ jp_server_opts.remove('gui')
+ new_gui = cylc_opts.new
+ if new_gui:
+ sys.argv.remove(CLI_OPT_NEW)
workflow_id = None
- for arg in argv:
- if arg.startswith('-'):
- continue
+ workflow_arg = [
+ arg for arg in jp_server_opts
+ if not arg.startswith('-')
+ ]
+ if len(workflow_arg) > 1:
+ msg = "Wrong number of arguments (too many)"
+ print_error("CylcError", msg)
+ sys.exit(1)
+ if len(workflow_arg) == 1:
try:
loop = asyncio.new_event_loop()
- task = loop.create_task(
- parse_id_async(
- arg, constraint='workflows'))
+ task = loop.create_task(parse_id_async(
+ workflow_arg[0], constraint='workflows'))
loop.run_until_complete(task)
loop.close()
workflow_id, _, _ = task.result()
- argv = tuple(x for x in argv if x != arg)
- sys.argv.remove(arg)
- except InputError:
- print(f"Workflow '{arg}' does not exist.")
- break
- return CylcUIServer.launch_instance(argv or None, workflow_id=workflow_id)
+ except (InputError, WorkflowFilesError) as exc:
+ # workflow not found
+ print_error(exc.__class__.__name__, exc)
+ sys.exit(1)
+
+ # Remove args which are workflow ids from jp_server_opts.
+ jp_server_opts = tuple(
+ arg for arg in jp_server_opts if arg not in workflow_arg)
+ [sys.argv.remove(arg) for arg in workflow_arg]
+ return jp_server_opts, new_gui, workflow_id
+
+def get_arg_parser():
+ """Cylc specific options"""
+ parser = argparse.ArgumentParser(add_help=False)
+ parser.add_argument(
+ CLI_OPT_NEW,
+ action='store_true',
+ default=False,
+ dest='new'
+ )
+ return parser
-def check_pid(pid_file: Path):
- """Check for process_id file.
- Raises exception if gui is currently running with active process id.
+def update_html_file(gui_file, workflow_id):
+ """ Update the html file to open at the correct workflow in the gui.
"""
- if not pid_file.is_file():
+ with open(gui_file, "r") as f:
+ file_content = f.read()
+ url_extract_regex = re.compile('url=(.*?)\"')
+ url_string = url_extract_regex.search(file_content)
+ if not url_string:
return
+ url = url_string.group(1)
+ split_url = url.split('/workflows/')
+ if not workflow_id:
+ # new url should point to dashboard
+ if len(split_url) == 1:
+ # no update required
+ return
+ else:
+ # previous url points to workflow page and needs updating
+ # replace with base url (including token)
+ replacement_url_string = split_url[0]
else:
- pid = pid_file.read_text()
- try:
- if pid_exists(int(pid)):
- raise Exception(f"cylc gui is running at process id: {pid}")
- except (TypeError, ValueError):
- try:
- pid_file.unlink()
- print(f"Deleting corrupt process id file. {pid_file.parent}")
- except FileNotFoundError:
- pass
-
-
-def create_pid_file(pid_file: Path):
- """Write current process to process id file."""
- pid = os.getpid()
- with open(pid_file, "w") as f:
- f.write(str(pid))
+ if len(split_url) > 1:
+ old_workflow = split_url[1]
+ if workflow_id == old_workflow:
+ # same workflow page requested, no update needed
+ return
+ else:
+ replacement_url_string = url.replace(old_workflow, workflow_id)
+ else:
+ # current url points to dashboard, update to point to workflow
+ replacement_url_string = f"{url}/workflows/{workflow_id}"
+ update_url_string(gui_file, url, replacement_url_string)
+
+
+def update_url_string(gui_file: str, url: str, replacement_url_string: str):
+ """Updates the url string in the given gui file."""
+ file = Path(gui_file)
+ current_text = file.read_text()
+ updated_text = current_text.replace(url, replacement_url_string)
+ file.write_text(updated_text)
diff --git a/cylc/uiserver/tests/test_gui.py b/cylc/uiserver/tests/test_gui.py
index f94b9e22..3f390ab6 100644
--- a/cylc/uiserver/tests/test_gui.py
+++ b/cylc/uiserver/tests/test_gui.py
@@ -13,37 +13,59 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+import json
from pathlib import Path
import pytest
import os
-from cylc.uiserver.scripts.gui import check_pid
+from cylc.uiserver.scripts.gui import update_html_file
+@pytest.mark.parametrize(
+ 'existing_content,workflow_id,expected_updated_content',
+ [
+ pytest.param(
+ 'content="1;url=http://localhost:8892/cylc/?token=1234567890some_big_long_token1234567890#" /> ',
+ None,
+ 'content="1;url=http://localhost:8892/cylc/?token=1234567890some_big_long_token1234567890#" /> ',
+ id='existing_no_workflow_new_no_workflow'
+ ),
+ pytest.param(
+ 'content="1;url=http://localhost:8892/cylc/?token=1234567890some_big_long_token1234567890#" /> ',
+ 'some/workflow',
+ 'content="1;url=http://localhost:8892/cylc/?token=1234567890some_big_long_token1234567890#/workflows/some/workflow" /> ',
+ id='existing_no_workflow_new_workflow'
+ ),
+ pytest.param(
+ 'content="1;url=http://localhost:8892/cylc/?token=1234567890some_big_long_token1234567890#/workflows/some/workflow" /> ',
+ 'another/flow',
+ 'content="1;url=http://localhost:8892/cylc/?token=1234567890some_big_long_token1234567890#/workflows/another/flow" /> ',
+ id='existing_workflow_new_workflow'
+ ),
+ pytest.param(
+ 'content="1;url=http://localhost:8892/cylc/?token=1234567890some_big_long_token1234567890#/workflows/some/workflow" /> ',
+ None,
+ 'content="1;url=http://localhost:8892/cylc/?token=1234567890some_big_long_token1234567890#" /> ',
+ id='existing_workflow_no_new_workflow'
+ ),
+ pytest.param(
+ 'content="1;no url in this file "',
+ 'another/flow',
+ 'content="1;no url in this file "',
+ id='no_url_no_change'
+ ),
+ ]
+)
+def test_update_html_file_updates_gui_file(
+ existing_content,
+ workflow_id,
+ expected_updated_content,
+ tmp_path):
+ """Tests html file is updated correctly"""
+ Path(tmp_path).mkdir(exist_ok=True)
+ tmp_gui_file = Path(tmp_path / "gui")
+ tmp_gui_file.touch()
+ tmp_gui_file.write_text(existing_content)
+ update_html_file(tmp_gui_file, workflow_id)
+ updated_file_content = tmp_gui_file.read_text()
-def test_check_pid_raises_error_for_existing_process(mod_tmp_path):
- """Tests exisiting process in file raises Exception"""
- pid = os.getpid()
- tmp_pid_file = Path(mod_tmp_path/"pid")
- with open(tmp_pid_file, "w") as f:
- f.write(str(pid))
- with pytest.raises(Exception) as exc_msg:
- check_pid(tmp_pid_file)
- assert f'cylc gui is running at process id: {pid}' == str(exc_msg.value)
-
-
-def test_checking_no_pid_file_does_not_raise_exception(mod_tmp_path):
- """Test check with no process id file runs without exception."""
- tmp_pid_file = Path(mod_tmp_path)
- try:
- check_pid(tmp_pid_file)
- except Exception as ex:
- pytest.fail(f"check_pid() raised Exception unexpectedly: {ex}")
-
-
-def test_check_junk_in_pid_file_deletes_file(mod_tmp_path):
- """Tests exisiting process in file raises Exception"""
- tmp_pid_file = Path(mod_tmp_path/"pid")
- with open(tmp_pid_file, "w") as f:
- f.write(str("This is a load of rubbish."))
- check_pid(tmp_pid_file)
- assert not tmp_pid_file.exists()
+ assert updated_file_content == expected_updated_content
diff --git a/setup.cfg b/setup.cfg
index 2269d913..485d27e7 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -51,6 +51,7 @@ install_requires =
# NB: no graphene version specified; we only make light use of it in our
# own code, so graphene-tornado's transitive version should do.
cylc-flow==8.1.*
+ ansimarkup>=1.0.0
graphene
graphene-tornado==2.6.*
graphql-ws==0.4.4
From a391c9bb180dcfeb5097459e3d8325a3789e47c5 Mon Sep 17 00:00:00 2001
From: Mel Hall <37735232+datamel@users.noreply.github.com>
Date: Wed, 26 Oct 2022 18:26:21 +0100
Subject: [PATCH 7/8] tidy
---
cylc/uiserver/app.py | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/cylc/uiserver/app.py b/cylc/uiserver/app.py
index 61d3d757..c1cfacf2 100644
--- a/cylc/uiserver/app.py
+++ b/cylc/uiserver/app.py
@@ -58,6 +58,7 @@
import os
from pathlib import Path, PurePath
import sys
+from textwrap import dedent
from typing import List
from pkg_resources import parse_version
@@ -133,14 +134,13 @@ class CylcUIServer(ExtensionApp):
description = '''
Cylc gui - A user interface for monitoring and controlling Cylc workflows.
''' # type: ignore[assignment]
- examples = '''
- cylc gui # Start the Cylc GUI (At the dashboard page)
-cylc gui [workflow] # Start the Cylc GUI (at the workflow page),
-cylc gui --new [workflow] # Start the Cylc GUI (at the workflow page), with a
- new instance. By default, if there is an existing
- gui instance, Cylc will use that.
+ examples = dedent('''
+ cylc gui # Start the Cylc GUI (At the dashboard page)
+ cylc gui [workflow] # Start the Cylc GUI (at the workflow page)
+ cylc gui --new [workflow] # Start the Cylc GUI (at the workflow page), with
+ a new instance.
- ''' # type: ignore[assignment]
+ ''') # type: ignore[assignment]
config_file_paths = get_conf_dir_hierarchy(
[
SITE_CONF_ROOT, # site configuration
From 48b6fff5e27cf622425ac8d34133a719df6d0101 Mon Sep 17 00:00:00 2001
From: Mel Hall <37735232+datamel@users.noreply.github.com>
Date: Fri, 13 Jan 2023 09:44:05 +0000
Subject: [PATCH 8/8] RD review feedback
---
cylc/uiserver/scripts/gui.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/cylc/uiserver/scripts/gui.py b/cylc/uiserver/scripts/gui.py
index 82ae40b7..8fed803e 100644
--- a/cylc/uiserver/scripts/gui.py
+++ b/cylc/uiserver/scripts/gui.py
@@ -50,7 +50,6 @@
def main(*argv):
init_log()
- new_gui = CLI_OPT_NEW in argv
jp_server_opts, new_gui, workflow_id = parse_args_opts()
if '--help' not in sys.argv:
# get existing jpserver--open.html files