Skip to content

Commit 1433411

Browse files
check global config for hub url and (#507)
gui: open the hub url if configured
1 parent b043bb9 commit 1433411

File tree

4 files changed

+109
-6
lines changed

4 files changed

+109
-6
lines changed

changes.d/507.feat.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added functionality for routing to a multiuser deployment when running cylc gui command.

cylc/uiserver/scripts/gui.py

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@
3030
from requests.exceptions import RequestException
3131
import requests
3232
import sys
33+
from textwrap import dedent
3334
from typing import Optional
3435
import webbrowser
36+
from getpass import getuser
3537

3638

3739
from cylc.flow.id_cli import parse_id_async
@@ -40,6 +42,8 @@
4042
WorkflowFilesError
4143
)
4244

45+
from cylc.flow.cfgspec.glbl_cfg import glbl_cfg
46+
4347
from cylc.uiserver import init_log
4448
from cylc.uiserver.app import (
4549
CylcUIServer,
@@ -51,8 +55,32 @@
5155

5256
def main(*argv):
5357
init_log()
58+
hub_url = glbl_cfg().get(['hub', 'url'])
5459
jp_server_opts, new_gui, workflow_id = parse_args_opts()
55-
if '--help' not in sys.argv:
60+
if '--help' in sys.argv and hub_url:
61+
print(
62+
dedent('''
63+
cylc gui [WORKFLOW]
64+
65+
Open the Cylc GUI in a new web browser tab.
66+
67+
If WORKFLOW is specified, the GUI will open on this workflow.
68+
69+
This command has been configured to use a centrally configured
70+
Jupyter Hub instance rather than start a standalone server.
71+
To see the configuration options for the server run
72+
"cylc gui --help-all", these options can be configured in the
73+
Jupyter configuration files using "c.Spawner.cmd", see the Cylc
74+
and Jupyter Hub documentation for more details.
75+
'''))
76+
return
77+
if not {'--help', '--help-all'} & set(sys.argv):
78+
if hub_url:
79+
print(f"Running on {hub_url } as specified in global config.")
80+
webbrowser.open(
81+
update_url(hub_url, workflow_id), autoraise=True
82+
)
83+
return
5684
# get existing jpserver-<pid>-open.html files
5785
# check if the server is available for use
5886
# prompt for user whether to clean files for un-usable uiservers
@@ -190,6 +218,7 @@ def get_arg_parser():
190218
def update_url(url, workflow_id):
191219
""" Update the url to open at the correct workflow in the gui.
192220
"""
221+
hub_url = glbl_cfg().get(['hub', 'url'])
193222
if not url:
194223
return
195224
split_url = url.split('/workspace/')
@@ -212,4 +241,8 @@ def update_url(url, workflow_id):
212241
return url.replace(old_workflow, workflow_id)
213242
else:
214243
# current url points to dashboard, update to point to workflow
215-
return f"{url}/workspace/{workflow_id}"
244+
if hub_url:
245+
return (f"{url}/user/{getuser()}/{CylcUIServer.name}"
246+
f"/#/workspace/{workflow_id}")
247+
else:
248+
return f"{url}/workspace/{workflow_id}"

cylc/uiserver/tests/conftest.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@
4343
from cylc.uiserver.data_store_mgr import DataStoreMgr
4444
from cylc.uiserver.workflows_mgr import WorkflowsManager
4545

46+
from cylc.flow.cfgspec.globalcfg import SPEC
47+
from cylc.flow.parsec.config import ParsecConfig
48+
from cylc.flow.parsec.validate import cylc_config_validate
4649

4750
class AsyncClientFixture(WorkflowRuntimeClient):
4851
pattern = zmq.REQ
@@ -360,3 +363,51 @@ def workflow_run_dir(request):
360363
yield flow_name, log_dir
361364
if not request.session.testsfailed:
362365
rmtree(run_dir)
366+
367+
@pytest.fixture
368+
def mock_glbl_cfg(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
369+
"""A Pytest fixture for fiddling global config values.
370+
371+
* Hacks the specified `glbl_cfg` object.
372+
* Can be called multiple times within a test function.
373+
374+
Args:
375+
pypath (str):
376+
The python-like path to the global configuation object you want
377+
to fiddle.
378+
E.G. if you want to hack the `glbl_cfg` in
379+
`cylc.flow.scheduler` you would provide
380+
`cylc.flow.scheduler.glbl_cfg`
381+
global_config (str):
382+
The globlal configuration as a multi-line string.
383+
384+
Example:
385+
Change the value of `UTC mode` in the global config as seen from
386+
`the scheduler` module.
387+
388+
def test_something(mock_glbl_cfg):
389+
mock_glbl_cfg(
390+
'cylc.flow.scheduler.glbl_cfg',
391+
'''
392+
[scheduler]
393+
UTC mode = True
394+
'''
395+
)
396+
397+
"""
398+
# TODO: modify Parsec so we can use StringIO rather than a temp file.
399+
def _mock_glbl_cfg(pypath: str, global_config: str) -> None:
400+
nonlocal tmp_path, monkeypatch
401+
global_config_path = tmp_path / 'global.cylc'
402+
global_config_path.write_text(global_config)
403+
glbl_cfg = ParsecConfig(SPEC, validator=cylc_config_validate)
404+
glbl_cfg.loadcfg(global_config_path)
405+
406+
def _inner(cached=False):
407+
nonlocal glbl_cfg
408+
return glbl_cfg
409+
410+
monkeypatch.setattr(pypath, _inner)
411+
412+
yield _mock_glbl_cfg
413+
rmtree(tmp_path)

cylc/uiserver/tests/test_gui.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@
1717
from glob import glob
1818
import os
1919
from pathlib import Path
20+
from getpass import getuser
2021
import pytest
2122
from random import randint
2223
import requests
23-
from shutil import rmtree
2424
from time import sleep
2525

2626
from cylc.uiserver.scripts.gui import (
@@ -30,46 +30,64 @@
3030
)
3131

3232
@pytest.mark.parametrize(
33-
'existing_content,workflow_id,expected_updated_content',
33+
'existing_content,workflow_id,expected_updated_content,hub_url',
3434
[
3535
pytest.param(
3636
'http://localhost:8892/cylc/?token=1234567890some_big_long_token1234567890#',
3737
None,
3838
'http://localhost:8892/cylc/?token=1234567890some_big_long_token1234567890#',
39+
'',
3940
id='existing_no_workflow_new_no_workflow'
4041
),
4142
pytest.param(
4243
'http://localhost:8892/cylc/?token=1234567890some_big_long_token1234567890#',
4344
'some/workflow',
4445
'http://localhost:8892/cylc/?token=1234567890some_big_long_token1234567890#/workspace/some/workflow',
46+
'',
4547
id='existing_no_workflow_new_workflow'
4648
),
49+
pytest.param(
50+
'http://localhost:8892/cylc/?token=1234567890some_big_long_token1234567890#',
51+
'some/hub/workflow',
52+
f'http://localhost:8892/cylc/?token=1234567890some_big_long_token1234567890#/user/{getuser()}/cylc/#/workspace/some/hub/workflow',
53+
'localhost:8000',
54+
id='existing_no_workflow_new_workflow_hub'
55+
),
4756
pytest.param(
4857
'http://localhost:8892/cylc/?token=1234567890some_big_long_token1234567890#/workspace/some/workflow',
4958
'another/flow',
5059
'http://localhost:8892/cylc/?token=1234567890some_big_long_token1234567890#/workspace/another/flow',
60+
'',
5161
id='existing_workflow_new_workflow'
5262
),
5363
pytest.param(
5464
'http://localhost:8892/cylc/?token=1234567890some_big_long_token1234567890#/workspace/some/workflow',
5565
None,
5666
'http://localhost:8892/cylc/?token=1234567890some_big_long_token1234567890#',
67+
'',
5768
id='existing_workflow_no_new_workflow'
5869
),
5970
pytest.param(
6071
'',
6172
'another/flow',
6273
None,
74+
'',
6375
id='no_url_no_change'
6476
),
6577
]
6678
)
79+
6780
def test_update_html_file_updates_gui_file(
6881
existing_content,
6982
workflow_id,
70-
expected_updated_content):
83+
expected_updated_content,
84+
hub_url,
85+
mock_glbl_cfg):
7186
"""Tests url is updated correctly"""
72-
87+
mock_glbl_cfg('cylc.uiserver.scripts.gui.glbl_cfg',
88+
f'''[hub]
89+
url = {hub_url}
90+
''')
7391
updated_file_content = update_url(existing_content, workflow_id)
7492
assert updated_file_content == expected_updated_content
7593

0 commit comments

Comments
 (0)