Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add gui workflow arg #370

Merged
merged 8 commits into from
Jan 18, 2023
Merged

Add gui workflow arg #370

merged 8 commits into from
Jan 18, 2023

Conversation

datamel
Copy link
Contributor

@datamel datamel commented Jun 23, 2022

This adds functionality for opening the gui at a specific workflow when running cylc gui <workflow_name>

These changes close #239 and #240

This PR also restricts the number of gui instances to one, by searching for pid file created by jupyter server.

  • I have read CONTRIBUTING.md and added my name as a Code Contributor.
  • Contains logically grouped changes (else tidy your branch by rebase).
  • Does not contain off-topic changes (use other PRs for other changes).
  • Any dependency changes applied to setup.cfg. No dependency changes.
  • Does not need tests (This is inbuilt Jupyter tech (default url)).
  • Appropriate change log entry included.
  • I have opened a documentation PR at cylc/cylc-doc/pull/XXXX.
  • No documentation update required.

@datamel datamel added the small label Jun 23, 2022
@datamel datamel added this to the 1.2.0 milestone Jun 23, 2022
@datamel datamel self-assigned this Jun 23, 2022
Comment on lines 37 to 44
# 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
Copy link
Contributor Author

@datamel datamel Jun 23, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR now interprets workflow_ID given on the command line. e.g. cylc gui workflow_name will resolve correctly to give cylc gui workflow_name/run4.

@codecov-commenter
Copy link

codecov-commenter commented Jun 26, 2022

Codecov Report

Merging #370 (658c7e6) into master (549afbd) will decrease coverage by 0.06%.
The diff coverage is 79.62%.

@@            Coverage Diff             @@
##           master     #370      +/-   ##
==========================================
- Coverage   79.89%   79.83%   -0.07%     
==========================================
  Files          12       12              
  Lines        1159     1210      +51     
  Branches      219      225       +6     
==========================================
+ Hits          926      966      +40     
- Misses        196      206      +10     
- Partials       37       38       +1     
Impacted Files Coverage Δ
cylc/uiserver/app.py 85.00% <37.50%> (-3.60%) ⬇️
cylc/uiserver/scripts/gui.py 87.50% <86.95%> (-12.50%) ⬇️

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

Copy link
Member

@oliver-sanders oliver-sanders left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, works great

Copy link
Member

@hjoliver hjoliver left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, tested as working, just a couple of small questions.

@wxtim wxtim self-assigned this Jul 26, 2022
@wxtim
Copy link
Member

wxtim commented Jul 26, 2022

I have picked this up and am running with in an attempt to get it over the line and responded to most of the comments.

I have 2 questions:

  • Where is the argument documentation coming from?
  • is it OK that cylc gui --new myflow works but that cylc gui myflow --new doesn't, or is this unexpected?

@hjoliver
Copy link
Member

hjoliver commented Jul 27, 2022

Thanks @wxtim - as noted on Element chat, no need to bust a gut if short of time to get this in the release (next release is OK).

Where is the argument documentation coming from?

I haven't had a chance to look today, sorry.

is it OK that cylc gui --new myflow works but that cylc gui myflow --new doesn't, or is this unexpected?

Personally I think options should come before args (otherwise, logically, it sort-of looks like --new is an option for the arg rather than the command). However, some (most?) programs are flexible on that, and in the cylc CLI is, so ideally this sub-command should be too for consistency. Whether or not that's feasible is another matter, given cylc gui starts jupyter server...

@oliver-sanders
Copy link
Member

The CLI docs are coming from Jupyter Server, options are configured "traits", these double as both CLI options and configurations. E.G. the CylcUIServer adds a trait called --ui-build-dir which can subsequently be set on the CLI or in the config.

This issue is effectively about extending the Jupyter Server CLI. If the modifications are light we can bodge them in, but if we want greater control over the CLI appearance they we would need to create our own argument parser here, parse the args, then reformat argv before calling JupyterServer. Icky but doable.

#!psudopython

def get_option_parser():
    ...
    return parser

@cli_function
def main(..., workflow_id):
    ...
    cylc_opts, jps_opts = parser.parse_known_args()
    if not gui_running or cylc_opts.new:
        argv = ['cylc', 'uiserver', *jps_opts]
        # do Mel's URL magic
        return CylcUIServer.launch_instance(argv)
    else:
        browser.open(get_gui_url())

@wxtim
Copy link
Member

wxtim commented Jul 27, 2022

I may or may not come back to this. Probably not a priority for me.

@oliver-sanders oliver-sanders modified the milestones: 1.3.0, 1.2.0 Jul 28, 2022
@datamel datamel force-pushed the add-gui-workflow-arg branch from f0ffeef to 47e8724 Compare August 5, 2022 12:57
@datamel
Copy link
Contributor Author

datamel commented Aug 8, 2022

Change of implementation:

Originally we were creating and maintaining our own PID file, to establish if an instance of the gui was running. It turns our Jupyter Server creates two files containing the information that we require. These, by default, are housed in ~/.local/share/jupyter/runtime. The two files created per run are:

  • jpserver-<pid>.json and
  • jpserver-<pid>-open.html

The json file contains info such as url, port number, version of jpserver, token etc...
The html file, when called will open the gui at the original page that it was opened at (adding the workflow_id if cylc gui was called with a wofkflow_id argument.)

In this PR, I have changed where this file is created (by setting an environment variable "JUPYTER_RUNTIME_DIR"). Now it can be found in .cylc/uiserver/info_files. Jupyter server cleans these files when shut down correctly but if killed unexpectedly they can remain (I've a bucketload in my dir). I have added a funtion that removes these files if the selected gui process is no longer running - it doesn't loop through all files as I think this could slow down the gui, but if it happens to select one that needs cleaning, it cleans it.

If a gui is started without the --new option it will check for these jp server runtime files and adapt the html file to open at the correct page in the gui.

@datamel datamel requested a review from oliver-sanders August 8, 2022 11:26
@datamel
Copy link
Contributor Author

datamel commented Aug 11, 2022

I will update the logic to consider no-browser option.

if arg.startswith('-'):
continue
try:
loop = asyncio.new_event_loop()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should only need one loop here. Suggest doing something like this:

def main(*argv):
    ... = asyncio.run(parse_args())

async def parse_args():
    ...
    for arg in jp_server_opts:
        ...
        workflow_id, *_ = await parse_id_async(...)

Comment on lines 69 to 72
print(
f"Workflow '{arg}' does not exist. Opening gui at dashboard.",
file=sys.stderr)
break
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might as well error here, other Cylc commands would fail with a no such workflow type error (saves the need for the redirect to dashboard thing).

pid_match = re.search(r"jpserver-(\d+)-open.html", file)
if pid_match is not None:
pid = pid_match[1]
if pid_exists(int(pid)):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spanner to throw into the works, the process might not be running on this server.

Does Jupyter Server have a mechanism for checking if a server is still running?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will have a look and report back, I had forgotten that case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, Jupyter Server does have a command for checking if a server is running. I don't think this is of use to us though 😢
The command is jupyter server list

  • this checks for the existence of pid file (remember this doesn't get cleaned up if the server shuts down unexpectedly)
  • it then does a os.kill(pid, 0) to check the process is running
  • it defaults to process running in the case of PermissionError(1, 'Operation not permitted'), which I am meeting regularly as I have a load of stale files.
  • the result is that it is claiming to have servers running when they are not.

If the process is running on a different server, maybe we can use the info in the pid file to pick the host out?
The info stored in the file is:

{
  "base_url": "/",
  "hostname": "localhost",
  "password": false,
  "pid": 11111,
  "port": 8888,
  "root_dir": "/net/home/../username",
  "secure": true,
  "sock": "",
  "token": "blah blah some big long token here",
  "url": "https://localhost:8888/",
  "version": "1.18.1"
} 

@datamel datamel force-pushed the add-gui-workflow-arg branch 2 times, most recently from bedfb19 to cd9796c Compare August 22, 2022 09:35
Copy link
Member

@oliver-sanders oliver-sanders left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem to detect a gui launched via JupyterHub, likely my fault, can't remember?

@@ -104,6 +105,9 @@
from cylc.uiserver.websockets.tornado import TornadoSubscriptionServer
from cylc.uiserver.workflows_mgr import WorkflowsManager

RUNTIME_PATH = Path("~/.cylc/uiserver").expanduser()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already defined as USER_CONF_ROOT.

@datamel
Copy link
Contributor Author

datamel commented Aug 23, 2022

This doesn't seem to detect a gui launched via JupyterHub, likely my fault, can't remember?

Looking into this now, perhaps due to the env variable maybe.


Update: Discussed at team meeting, needs some thought. Decision made that this PR should go in first and deal with this as a separate issue - raised: #382

Copy link
Member

@oliver-sanders oliver-sanders left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested, all working as expected.

One outstanding comment here - https://github.com/cylc/cylc-uiserver/pull/370/files/cd9796c448fc626d4eb877abb22ef1542d31656a#r952333283

Agree, Jupyter Hub is a different problem (wasn't thinking straight, that needs to be configured at the site-level).

Cheers!

@oliver-sanders
Copy link
Member

I tested after rebasing this onto master. Suggest maybe rebase-squashing to see the tests pass with latest UIS code.

@datamel datamel force-pushed the add-gui-workflow-arg branch from 658c7e6 to 3c882ee Compare October 20, 2022 14:25
Copy link
Member

@MetRonnie MetRonnie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partial review

Copy link
Member

@MetRonnie MetRonnie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aside from my suggestions, I have tested out and happy it's working

Comment on lines 136 to 143
examples = '''
cylc gui # start the cylc GUI
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]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"if there is an existing instance, Cylc will use that" applies more to the non --new case, right?

I would also use dedent to tidy this up (from textwrap import dedent):

    examples = dedent('''
    cylc gui                  # Start the Cylc GUI (at the dashboard page).
    cylc gui [workflow]       # Start the Cylc GUI (at the workflow page). If
                                there is an existing GUI instance, Cylc will
                                use that.
    cylc gui --new [workflow] # Start a new instance of the Cylc GUI (at the
                                workflow page).

    ''')  # type: ignore[assignment]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll change the wording. The formatting is dealt with through traitlets and they indent and dedent it with yield indent(dedent(self.examples.strip())). An additional dedent as suggested is having no effect for me.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From my testing the dedent is needed to avoid it being like this:

   examples = '''
        cylc gui                  # ...
cylc gui [workflow]       # ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is really strange. Two screengrabs from me below with/without dedent:
image

image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incidentally, the If there is an existing GUI instance, Cylc will use that. is required for both the cylc gui and the cylc gui [workflow] options. I actually think it is neater leaving it as it is, since it conveys the same information without repetition, what do you think?

Add workflow argument to cylc gui command.
Co-authored-by: Oliver Sanders <[email protected]>

Add --new option for gui

Review Feedback RD
@datamel datamel force-pushed the add-gui-workflow-arg branch from 3c882ee to 3806387 Compare October 21, 2022 12:58
@oliver-sanders oliver-sanders modified the milestones: 1.2.0, 1.3.0 Dec 7, 2022


def main(*argv):
init_log()
return CylcUIServer.launch_instance(argv or None)
new_gui = CLI_OPT_NEW in argv
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not used because it is redefined in next line

Suggested change
new_gui = CLI_OPT_NEW in argv

@datamel datamel merged commit cb4c843 into cylc:master Jan 18, 2023
@datamel datamel deleted the add-gui-workflow-arg branch January 18, 2023 10:09
@MetRonnie MetRonnie modified the milestones: 1.3.0, 1.2.1 Jan 27, 2023
@MetRonnie MetRonnie mentioned this pull request Jan 27, 2023
7 tasks
@datamel datamel mentioned this pull request Mar 1, 2023
8 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

cylc gui: add workflow ID argument
6 participants