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