From 711fb2650f602696c99a9024136c79945df937f8 Mon Sep 17 00:00:00 2001 From: "David L. Qiu" Date: Tue, 15 Nov 2022 20:08:26 +0000 Subject: [PATCH 01/11] migrate to readthedocs documentation --- README.md | 165 +---------------------- RELEASE.md | 92 ------------- docs/Makefile | 20 +++ docs/_static/jupyter_logo.png | Bin 0 -> 26997 bytes docs/conf.py | 52 ++++++++ docs/contributors/index.md | 243 ++++++++++++++++++++++++++++++++++ docs/developers/index.md | 9 ++ docs/index.md | 16 +++ docs/make.bat | 35 +++++ docs/operators/index.md | 74 +++++++++++ docs/requirements.txt | 2 + docs/users/index.md | 24 ++++ pyproject.toml | 5 + 13 files changed, 486 insertions(+), 251 deletions(-) delete mode 100644 RELEASE.md create mode 100644 docs/Makefile create mode 100644 docs/_static/jupyter_logo.png create mode 100644 docs/conf.py create mode 100644 docs/contributors/index.md create mode 100644 docs/developers/index.md create mode 100644 docs/index.md create mode 100644 docs/make.bat create mode 100644 docs/operators/index.md create mode 100644 docs/requirements.txt create mode 100644 docs/users/index.md diff --git a/README.md b/README.md index 01f90aaf..8cf8ff40 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ # jupyter_scheduler [![Github Actions Status](https://github.com/jupyter-server/jupyter-scheduler/workflows/Build/badge.svg)](https://github.com/jupyter-server/jupyter-scheduler/actions/workflows/build.yml)[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/jupyter-server/jupyter-scheduler/main?urlpath=lab) -A JupyterLab extension for running notebook jobs + +A JupyterLab extension for running notebook jobs. Documentation is available on +ReadTheDocs. This extension is composed of a Python package named `jupyter_scheduler` for the server extension and a NPM package named `@jupyterlab/scheduler` @@ -30,165 +32,10 @@ To remove the extension, execute: pip uninstall jupyter_scheduler ``` -## Troubleshoot - -If you are seeing the frontend extension, but it is not working, check -that the server extension is enabled: - -```bash -jupyter server extension list -``` - -If the server extension is installed and enabled, but you are not seeing -the frontend extension, check the frontend extension is installed: +## User's guide -```bash -jupyter labextension list -``` +Please refer to our user's guide for more information on installation and usage. ## Contributing -### Development install - -Note: You will need NodeJS to build the extension package. - -The `jlpm` command is JupyterLab's pinned version of -[yarn](https://yarnpkg.com/) that is installed with JupyterLab. You may use -`yarn` or `npm` in lieu of `jlpm` below. - -```bash -# Clone the repo to your local environment -git clone https://github.com/jupyter-server/jupyter-scheduler.git - -# Change dir to the cloned project -cd jupyter-scheduler - -# Install the project in editable mode -pip install -e . - -# Link your development version of the extension with JupyterLab -jupyter labextension develop . --overwrite - -# Server extension must be manually installed in develop mode -jupyter server extension enable jupyter_scheduler - -# Rebuild extension Typescript source after making changes -jlpm build -``` - -You can watch the source directory and run JupyterLab at the same time in different terminals to watch for changes in the extension's source and automatically rebuild the extension. - -```bash -# Watch the source directory in one terminal, automatically rebuilding when needed -jlpm watch -# Run JupyterLab in another terminal -jupyter lab -``` - -With the watch command running, every saved change will immediately be built locally and available in your running JupyterLab. Refresh JupyterLab to load the change in your browser (you may need to wait several seconds for the extension to be rebuilt). - -By default, the `jlpm build` command generates the source maps for this extension to make it easier to debug using the browser dev tools. To also generate source maps for the JupyterLab core extensions, you can run the following command: - -```bash -jupyter lab build --minimize=False -``` - -### Development uninstall - -```bash -# Server extension must be manually disabled in develop mode -jupyter server extension disable jupyter_scheduler -pip uninstall jupyter_scheduler -``` - -In development mode, you will also need to remove the symlink created by `jupyter labextension develop` -command. To find its location, you can run `jupyter labextension list` to figure out where the `labextensions` -folder is located. Then you can remove the symlink named `jupyter-scheduler` within that folder. - -### Testing the extension - -#### Server tests - -This extension is using [Pytest](https://docs.pytest.org/) for Python code testing. - -Install test dependencies (needed only once): - -```sh -pip install -e ".[test]" -``` - -To execute them, run: - -```sh -pytest -vv -r ap --cov jupyter_scheduler -``` - -#### Frontend tests - -This extension is using [Jest](https://jestjs.io/) for JavaScript code testing. - -To execute them, execute: - -```sh -jlpm -jlpm test -``` - -#### Integration tests - -This extension uses Playwright for the integration tests (aka user level tests). -More precisely, the JupyterLab helper [Galata](https://github.com/jupyterlab/jupyterlab/tree/master/galata) is used to handle testing the extension in JupyterLab. - -More information are provided within the [ui-tests](./ui-tests/README.md) README. - -### Packaging the extension - -See [RELEASE](RELEASE.md) - -### Configuring the extension - -You can configure the server extension to replace the Scheduler server API, replace the execution engine, re-create the database tables, and select a database path. - -#### drop_tables - -Setting this value to `True` will re-create the database tables on each JupyterLab start. This will destroy all existing data. It may be necessary if your database's schema is out of date. - -``` -jupyter lab --SchedulerApp.drop_tables=True -``` - -#### db_url - -The fully qualified URL of the database. For example, a SQLite database path will look like `sqlite:///`. - -``` -jupyter lab --SchedulerApp.db_url=sqlite:/// -``` - -#### scheduler_class - -The fully classified classname to use for the scheduler API. This class should extend `jupyter_scheduler.scheduler.BaseScheduler` and implement all abstract methods. The default class is `jupyter_scheduler.scheduler.Scheduler`. - -``` -jupyter lab --SchedulerApp.scheduler_class=jupyter_scheduler.scheduler.Scheduler -``` - -#### environment_manager_class - -The fully classified classname to use for the environment manager. This class should extend `jupyter_scheduler.environments.EnvironmentManager` and implement all abstract methods. The default class is `jupyter_scheduler.environments.CondaEnvironmentManager`. - -``` -jupyter lab --SchedulerApp.environment_manager_class=jupyter_scheduler.environments.CondaEnvironmentManager -``` - -#### execution_manager_class - -The fully classified classname to use for the execution manager, the module that is responsible for reading the input file, executing and writing the output. This option lets you specify a custom execution engine without replacing the whole scheduler API. This class should extend `jupyter_scheduler.executors.ExecutionManager` and implement the execute method. The default class is `jupyter_scheduler.executors.DefaultExecutionManager`. - -``` -# This can be configured on the BaseScheduler class -jupyter lab --BaseScheduler.execution_manager_class=jupyter_scheduler.executors.DefaultExecutionManager - -# Or, on the Scheduler class directly -jupyter lab --Scheduler.execution_manager_class=jupyter_scheduler.executors.DefaultExecutionManager -``` +Please refer to our contributor's guide for more information on installation and usage. diff --git a/RELEASE.md b/RELEASE.md deleted file mode 100644 index 63134ded..00000000 --- a/RELEASE.md +++ /dev/null @@ -1,92 +0,0 @@ -# Making a new release of jupyter_scheduler - -The extension can be published to `PyPI` and `npm` manually or using the [Jupyter Releaser](https://github.com/jupyter-server/jupyter_releaser). - -## Automated releases with the Jupyter Releaser - -The extension repository should already be compatible with the Jupyter Releaser. - -Check out the [workflow documentation](https://github.com/jupyter-server/jupyter_releaser#typical-workflow) for more information. - -Here is a summary of the steps to cut a new release: - -- Fork the [`jupyter-releaser` repo](https://github.com/jupyter-server/jupyter_releaser) -- Add `ADMIN_GITHUB_TOKEN`, `PYPI_TOKEN` and `NPM_TOKEN` to the Github Secrets in the fork -- Go to the Actions panel -- Run the "Step 1: Prep Release" workflow, this will create a draft release in github for review -- Run the "Step 2: Publish Release" workflow - -## Manual release - -### Python package - -This extension can be distributed as Python -packages. All of the Python -packaging instructions in the `pyproject.toml` file to wrap your extension in a -Python package. Before generating a package, we first need to install `build`. - -```bash -pip install build twine tbump -``` - -Make sure you have checked out the `main` branch updated from remote. - -```bash -git checkout main -git remote update -git pull upstream main -``` - -Bump the version using `tbump`. By default this will create a tag. - -```bash -tbump --no-push -``` - -Push the bump version commit and tag to main upstream branch. - -```bash -git push upstream main -git push upstream -``` - -Checkout the new tagged commit. - -```bash -git checkout -``` - -Build the extension - -```bash -jlpm run build:prod -``` - -To create a Python source package (`.tar.gz`) and the binary package (`.whl`) in the `dist/` directory, do: - -```bash -python -m build -``` - -> `python setup.py sdist bdist_wheel` is deprecated and will not work for this package. - -Then to upload the package to PyPI, do: - -```bash -twine upload dist/* -``` - -### NPM package - -To publish the frontend part of the extension as a NPM package, do: - -```bash -npm login -npm publish --access public -``` - -## Publishing to `conda-forge` - -If the package is not on conda forge yet, check the documentation to learn how to add it: https://conda-forge.org/docs/maintainer/adding_pkgs.html - -Otherwise a bot should pick up the new version publish to PyPI, and open a new PR on the feedstock repository automatically. diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..d4bb2cbb --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/_static/jupyter_logo.png b/docs/_static/jupyter_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..1cea1a855624c14898e94ea69831bbf463f2fa36 GIT binary patch literal 26997 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 VA;O9-;5$kv@1+za%f$@?{tt$BdH4VT literal 0 HcmV?d00001 diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..d36c5315 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,52 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = "jupyter_scheduler" +copyright = "2022, Project Jupyter" +author = "Project Jupyter" + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = ["myst_parser"] + +templates_path = ["_templates"] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = "pydata_sphinx_theme" +html_static_path = ["_static"] + +# -- Jupyter theming ------------------------------------------------- +html_logo = "_static/jupyter_logo.png" + +html_theme_options = { + "logo": { + "text": "Jupyter Scheduler", + }, + "icon_links": [ + { + # Label for this link + "name": "GitHub", + # URL where the link will redirect + "url": "https://github.com/jupyter-server/jupyter-scheduler", # required + # Icon class (if "type": "fontawesome"), or path to local image (if "type": "local") + "icon": "fab fa-github-square", + # The type of image to be used (see below for details) + "type": "fontawesome", + }, + { + "name": "jupyter.org", + "url": "https://jupyter.org", + "icon": "_static/jupyter_logo.png", + "type": "local", + }, + ], +} diff --git a/docs/contributors/index.md b/docs/contributors/index.md new file mode 100644 index 00000000..a3bbf93d --- /dev/null +++ b/docs/contributors/index.md @@ -0,0 +1,243 @@ +# Contributors + +This page is targeted at people who wish to contribute to Jupyter Scheduler directly, + +Developers wishing to extend or override Jupyter Scheduler should instead refer +to our {doc}`developer's guide `. + +## Development install + +The below sequence of commands will install a development environment for +Jupyter Scheduler locally. Before running this, you should ensure that NodeJS is +installed locally. The `jlpm` command is JupyterLab's pinned version of +[yarn](https://yarnpkg.com/) that is installed with JupyterLab. You may use +`yarn` or `npm` in lieu of `jlpm` below. + +```bash +# Clone the repo to your local environment +git clone https://github.com/jupyter-server/jupyter-scheduler.git + +# Change dir to the cloned project +cd jupyter-scheduler + +# Install the project in editable mode +pip install -e . + +# Link your development version of the extension with JupyterLab +jupyter labextension develop . --overwrite + +# Server extension must be manually installed in develop mode +jupyter server extension enable jupyter_scheduler + +# Rebuild extension Typescript source after making changes +jlpm build +``` + +You can watch the source directory and run JupyterLab at the same time in +different terminals to watch for changes in the extension's source and +automatically rebuild the extension. + +```bash +# Watch the source directory in one terminal, automatically rebuilding when needed +jlpm watch +# Run JupyterLab in another terminal +jupyter lab +``` + +With the watch command running, every saved change will immediately be built +locally and available in your running JupyterLab. Refresh JupyterLab to load the +change in your browser (you may need to wait several seconds for the extension +to be rebuilt). + +By default, the `jlpm build` command generates the source maps for this +extension to make it easier to debug using the browser dev tools. To also +generate source maps for the JupyterLab core extensions, you can run the +following command: + +```bash +jupyter lab build --minimize=False +``` + +## Development uninstall + +```bash +# Server extension must be manually disabled in develop mode +jupyter server extension disable jupyter_scheduler +pip uninstall jupyter_scheduler +``` + +In development mode, you will also need to remove the symlink created by +`jupyter labextension develop` command. To find its location, you can run +`jupyter labextension list` to figure out where the `labextensions` folder is +located. Then you can remove the symlink named `jupyter-scheduler` within that +folder. + +## Testing + +### Server tests + +This extension is using [Pytest](https://docs.pytest.org/) for Python code testing. + +Install test dependencies (needed only once): + +```sh +pip install -e ".[test]" +``` + +To execute them, run: + +```sh +pytest -vv -r ap --cov jupyter_scheduler +``` + +### Frontend tests + +This extension is using [Jest](https://jestjs.io/) for JavaScript code testing. + +To execute them, execute: + +```sh +jlpm +jlpm test +``` + +### Integration tests + +This extension uses Playwright for the integration tests (aka user level tests). +More precisely, the JupyterLab helper +[Galata](https://github.com/jupyterlab/jupyterlab/tree/master/galata) is used to +handle testing the extension in JupyterLab. + +More information are provided within the +[ui-tests](https://github.com/jupyter-server/jupyter-scheduler/tree/main/ui-tests) +README. + +## Documentation + +First, ensure GNU Make is installed locally, and then install the `docs` extras: + +``` +pip install -e ".[docs]" +``` + +Documentation is built with the [Sphinx](https://www.sphinx-doc.org/en/master/) +documentation generator, via the following command run from the project root: + +``` +make -C docs html +``` + +Documentation source files are written in +[MyST](https://myst-parser.readthedocs.io/en/latest/index.html), a rich and more +expressive flavor of Markdown. These files are located under `docs/`. + +The built documentation files are generated under `docs/_build/html` can be +accessed in the browser via a file URI, e.g. +`file:///Users/dlq/workplace/jupyter-scheduler/docs/_build/html/index.html`. We +recommend bookmarking this if you will be editing documentation frequently. + +Sphinx by default only rebuilds files it detects were changed, though this +detection logic is sometimes faulty. To force a full rebuild of the +documentation: + +``` +make -C docs clean && make -C docs html +``` + +## Releasing + +Releases should be done via [Jupyter Releaser](https://github.com/jupyter-server/jupyter_releaser). +If you have admin permissions on the repository, you can go to the "Actions" +panel on GitHub and follow the below instructions to release a new version of +Jupyter Scheduler. + +1. Click the "Step 1: Prep Release" workflow on the left-hand panel, and then +click "Run workflow". This will open a form. Replace the "Next Version +Specifier" with the next version of Jupyter Scheduler (e.g. 1.2.3). Do not +include the "v" prefix. "Branch to Target" should be replaced with the branch +that should be released. Usually this will be `main`. Then click "Run workflow" +(the green button) to submit the form and run the workflow. + +2. Verify the draft release changelog in the +[Releases](https://github.com/jupyter-server/jupyter-scheduler/releases) page +for Jupyter Scheduler. + +3. Return to the Actions panel and click the "Step 2: Publish Release" workflow. +Run this workflow with the target branch set to the same branch specified in +Step 1. + +4. Wait for the workflow to complete, and then verify the publish on PyPi and +NPM. + +### Manual release + +In the unlikely scenario Jupyter Releaser fails in Step 2, the below steps +should be followed to perform a manual release. Make sure to publish the draft +release changelog afterwards. + +#### Python package + +This extension can be distributed as Python +packages. All of the Python +packaging instructions in the `pyproject.toml` file to wrap your extension in a +Python package. Before generating a package, we first need to install `build`. + +```bash +pip install build twine tbump +``` + +Make sure you have checked out the `main` branch updated from remote. + +```bash +git checkout main +git remote update +git pull upstream main +``` + +Bump the version using `tbump`. By default this will create a tag. + +```bash +tbump --no-push +``` + +Push the bump version commit and tag to main upstream branch. + +```bash +git push upstream main +git push upstream +``` + +Checkout the new tagged commit. + +```bash +git checkout +``` + +Build the extension + +```bash +jlpm run build:prod +``` + +To create a Python source package (`.tar.gz`) and the binary package (`.whl`) in the `dist/` directory, do: + +```bash +python -m build +``` + +> `python setup.py sdist bdist_wheel` is deprecated and will not work for this package. + +Then to upload the package to PyPI, do: + +```bash +twine upload dist/* +``` + +#### NPM package + +To publish the frontend part of the extension as a NPM package, do: + +```bash +npm login +npm publish --access public +``` diff --git a/docs/developers/index.md b/docs/developers/index.md new file mode 100644 index 00000000..cd9e23c9 --- /dev/null +++ b/docs/developers/index.md @@ -0,0 +1,9 @@ +# Developers + +These pages are targeted at people who want to extend or override the server or +lab extensions in Jupyter Scheduler. This is necessary for more complex features +that cannot simply be defined as a set of configuration options. + +For installation and usage instructions, please refer to our {doc}`user's guide `. + +For configuration options, please refer to our {doc}`operator's guide `. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..5e5f849b --- /dev/null +++ b/docs/index.md @@ -0,0 +1,16 @@ +# Jupyter Scheduler + +Jupyter Scheduler is collection of extensions for programming jobs to run now or +run on a schedule. + +## Contents + +```{toctree} +--- +maxdepth: 2 +--- +users/index +operators/index +developers/index +contributors/index +``` diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 00000000..32bb2452 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/operators/index.md b/docs/operators/index.md new file mode 100644 index 00000000..ab4eb53d --- /dev/null +++ b/docs/operators/index.md @@ -0,0 +1,74 @@ +# Operators + +These pages are targeted at people who want to configure and deploy Jupyter Scheduler. + +For installation and usage instructions, please refer to our {doc}`user's guide `. + +## Configuration + +You can configure the server extension to replace the Scheduler server API, +replace the execution engine, re-create the database tables, and select a +database path. + +### drop_tables + +Setting this value to `True` will re-create the database tables on each +JupyterLab start. This will destroy all existing data. It may be necessary if +your database's schema is out of date. + +``` +jupyter lab --SchedulerApp.drop_tables=True +``` + +### db_url + +The fully qualified URL of the database. For example, a SQLite database path +will look like `sqlite:///`. + +``` +jupyter lab --SchedulerApp.db_url=sqlite:/// +``` + +### scheduler_class + +The fully classified classname to use for the scheduler API. This class should +extend `jupyter_scheduler.scheduler.BaseScheduler` and implement all abstract +methods. The default class is `jupyter_scheduler.scheduler.Scheduler`. + +``` +jupyter lab --SchedulerApp.scheduler_class=jupyter_scheduler.scheduler.Scheduler +``` + +For more information on how to write a custom implementation, please to our {doc}`developer's guide `. + +### environment_manager_class + +The fully classified classname to use for the environment manager. This class +should extend `jupyter_scheduler.environments.EnvironmentManager` and implement +all abstract methods. The default class is +`jupyter_scheduler.environments.CondaEnvironmentManager`. + +``` +jupyter lab --SchedulerApp.environment_manager_class=jupyter_scheduler.environments.CondaEnvironmentManager +``` + +For more information on how to write a custom implementation, please to our {doc}`developer's guide `. + +### execution_manager_class + +The fully classified classname to use for the execution manager, the module that +is responsible for reading the input file, executing and writing the output. +This option lets you specify a custom execution engine without replacing the +whole scheduler API. This class should extend +`jupyter_scheduler.executors.ExecutionManager` and implement the execute method. +The default class is `jupyter_scheduler.executors.DefaultExecutionManager`. + +``` +# This can be configured on the BaseScheduler class +jupyter lab --BaseScheduler.execution_manager_class=jupyter_scheduler.executors.DefaultExecutionManager + +# Or, on the Scheduler class directly +jupyter lab --Scheduler.execution_manager_class=jupyter_scheduler.executors.DefaultExecutionManager +``` + +For more information on how to write a custom implementation, please to our {doc}`developer's guide `. diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 00000000..b9c17d5a --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,2 @@ +myst_parser +pydata_sphinx_theme diff --git a/docs/users/index.md b/docs/users/index.md new file mode 100644 index 00000000..42dcc26e --- /dev/null +++ b/docs/users/index.md @@ -0,0 +1,24 @@ +# Users + +These pages are targeted at people interested in simply installing Jupyter +Scheduler and using it. + +For configuration options, please refer to our {doc}`operator's guide `. + +## Installation + +Jupyter Scheduler can be installed from the PyPi registry via `pip`: + +``` +pip install jupyter-scheduler +``` + +This automatically enables its extensions. You can verify this by running + +``` +jupyter server extension list +jupyter labextension list +``` + +and asserting that both the `jupyter_scheduler` server extension and the +`@jupyterlab/scheduler` prebuilt lab extension are enabled. diff --git a/pyproject.toml b/pyproject.toml index ae7aafa6..6eab0ffb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,6 +48,11 @@ test = [ dev = [ "click" ] +docs = [ + "sphinx", + "myst_parser", + "pydata_sphinx_theme" +] [project.urls] Homepage = "https://github.com/jupyter-server/jupyter-scheduler" From f1ce68fccba19ffb67a7f45e4b148edc18106fca Mon Sep 17 00:00:00 2001 From: "David L. Qiu" Date: Tue, 15 Nov 2022 20:37:29 +0000 Subject: [PATCH 02/11] add UI configuration section to operator's guide --- docs/operators/index.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/docs/operators/index.md b/docs/operators/index.md index ab4eb53d..2f851e59 100644 --- a/docs/operators/index.md +++ b/docs/operators/index.md @@ -4,7 +4,7 @@ These pages are targeted at people who want to configure and deploy Jupyter Sche For installation and usage instructions, please refer to our {doc}`user's guide `. -## Configuration +## Server configuration You can configure the server extension to replace the Scheduler server API, replace the execution engine, re-create the database tables, and select a @@ -72,3 +72,21 @@ jupyter lab --Scheduler.execution_manager_class=jupyter_scheduler.executors.Defa ``` For more information on how to write a custom implementation, please to our {doc}`developer's guide `. + +## UI configuration + +You can configure the Jupyter Scheduler UI by installing a lab extension that both: + +1. Exports a +[plugin](https://jupyterlab.readthedocs.io/en/stable/extension/extension_dev.html#plugins) +providing the `Scheduler.IAdvancedOptions` +[token](https://jupyterlab.readthedocs.io/en/stable/extension/extension_dev.html#tokens). + +2. Disables the `@jupyterlab/scheduler:IAdvancedOptions` plugin. + +This allows you to customize the +"advanced options" shown in the "Create Job" form and the "Job Details" view +(and the same form/view for job definitions). + +For more information on how to write a custom plugin, please to our +{doc}`developer's guide `. From 0099171e8177f24e213464b60fbf6c592ac0814c Mon Sep 17 00:00:00 2001 From: Jason Weill <93281816+jweill-aws@users.noreply.github.com> Date: Mon, 14 Nov 2022 16:45:21 -0800 Subject: [PATCH 03/11] Update tooltip for consistency with rest of job row (#307) --- src/components/job-row.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/job-row.tsx b/src/components/job-row.tsx index 8686c633..6fbe27c9 100644 --- a/src/components/job-row.tsx +++ b/src/components/job-row.tsx @@ -79,7 +79,7 @@ function DownloadFilesButton(props: DownloadFilesButtonProps) { return ( { setDownloading(true); From 995dcf82bbc5a508d236ac4a76c3254f48afbc90 Mon Sep 17 00:00:00 2001 From: Jason Weill <93281816+jweill-aws@users.noreply.github.com> Date: Mon, 14 Nov 2022 17:27:06 -0800 Subject: [PATCH 04/11] Sets display error on edit JD page (#308) --- src/mainviews/edit-job-definition.tsx | 36 +++++++++++++++++---------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/src/mainviews/edit-job-definition.tsx b/src/mainviews/edit-job-definition.tsx index e1f06371..4298bb0f 100644 --- a/src/mainviews/edit-job-definition.tsx +++ b/src/mainviews/edit-job-definition.tsx @@ -1,13 +1,14 @@ import React, { useState, useMemo, useEffect } from 'react'; import { + Alert, Button, Box, Breadcrumbs, - Stack, - Link, - Typography, + CircularProgress, InputLabel, - CircularProgress + Link, + Stack, + Typography } from '@mui/material'; import { Heading } from '../components/heading'; @@ -32,6 +33,9 @@ function EditJobDefinitionBody(props: EditJobDefinitionProps): JSX.Element { const [utcOnly, setUtcOnly] = useState(false); const [saving, setSaving] = useState(false); const [fieldErrors, setFieldErrors] = useState({}); + const [displayError, setDisplayError] = useState( + null + ); const hasErrors = Object.keys(fieldErrors).some(key => !!fieldErrors[key]); /** @@ -61,16 +65,17 @@ function EditJobDefinitionBody(props: EditJobDefinitionProps): JSX.Element { } setSaving(true); - try { - await ss.updateJobDefinition(props.model.definitionId, { - schedule: props.model.schedule, - timezone: props.model.timezone + ss.updateJobDefinition(props.model.definitionId, { + schedule: props.model.schedule, + timezone: props.model.timezone + }) + .then(() => { + props.showJobDefinitionDetail(props.model.definitionId); + }) + .catch((e: Error) => { + setSaving(false); + setDisplayError(e.message); }); - props.showJobDefinitionDetail(props.model.definitionId); - } catch (e) { - // TODO: catch any errors from backend and display them to user. - setSaving(false); - } }; if (loading) { @@ -79,6 +84,11 @@ function EditJobDefinitionBody(props: EditJobDefinitionProps): JSX.Element { return ( + {displayError && ( + setDisplayError(null)}> + {displayError} + + )} {trans.__('Schedule')} Date: Tue, 15 Nov 2022 15:44:46 -0800 Subject: [PATCH 05/11] Adds plugin --- docs/developers/index.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/developers/index.md b/docs/developers/index.md index cd9e23c9..4525f311 100644 --- a/docs/developers/index.md +++ b/docs/developers/index.md @@ -7,3 +7,15 @@ that cannot simply be defined as a set of configuration options. For installation and usage instructions, please refer to our {doc}`user's guide `. For configuration options, please refer to our {doc}`operator's guide `. + +## Extension points + +You can customize Jupyter Scheduler using +[plugins](https://jupyterlab.readthedocs.io/en/stable/extension/extension_dev.html#plugins). +If you override the +[token](https://jupyterlab.readthedocs.io/en/stable/extension/extension_dev.html#tokens) +in the `@jupyterlab/scheduler:IAdvancedOptions` plugin, you can customize the advanced options +shown in the "Create Job" form and the "Job Details" view (and the same form/view for job +definitions). You can find the token exported as `IAdvancedOptions` in +[src/tokens.ts](https://github.com/jupyter-server/jupyter-scheduler/blob/main/src/tokens.ts). + From 38bc236d0d8cd68bc1d1d7c191201a3db5c0b426 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 15 Nov 2022 23:45:35 +0000 Subject: [PATCH 06/11] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/developers/index.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/developers/index.md b/docs/developers/index.md index 4525f311..f3c6d9a7 100644 --- a/docs/developers/index.md +++ b/docs/developers/index.md @@ -10,12 +10,11 @@ For configuration options, please refer to our {doc}`operator's guide Date: Tue, 15 Nov 2022 15:53:13 -0800 Subject: [PATCH 07/11] Apply suggestions from code review Copy edits per @jweill-aws and @3coins Co-authored-by: Piyush Jain --- docs/contributors/index.md | 51 ++++++++++++++++++-------------------- docs/developers/index.md | 2 +- docs/operators/index.md | 10 ++++---- docs/users/index.md | 7 +++--- 4 files changed, 33 insertions(+), 37 deletions(-) diff --git a/docs/contributors/index.md b/docs/contributors/index.md index a3bbf93d..6ad83db9 100644 --- a/docs/contributors/index.md +++ b/docs/contributors/index.md @@ -1,14 +1,14 @@ # Contributors -This page is targeted at people who wish to contribute to Jupyter Scheduler directly, +This page is for people who wish to contribute to Jupyter Scheduler directly, Developers wishing to extend or override Jupyter Scheduler should instead refer to our {doc}`developer's guide `. ## Development install -The below sequence of commands will install a development environment for -Jupyter Scheduler locally. Before running this, you should ensure that NodeJS is +The commands below will install a development environment for +Jupyter Scheduler locally. Before running these commands, you should ensure that NodeJS is installed locally. The `jlpm` command is JupyterLab's pinned version of [yarn](https://yarnpkg.com/) that is installed with JupyterLab. You may use `yarn` or `npm` in lieu of `jlpm` below. @@ -44,8 +44,8 @@ jlpm watch jupyter lab ``` -With the watch command running, every saved change will immediately be built -locally and available in your running JupyterLab. Refresh JupyterLab to load the +With the `watch` command running, every file change will be built immediately +and made available in your running JupyterLab. Refresh JupyterLab to load the change in your browser (you may need to wait several seconds for the extension to be rebuilt). @@ -67,7 +67,7 @@ pip uninstall jupyter_scheduler ``` In development mode, you will also need to remove the symlink created by -`jupyter labextension develop` command. To find its location, you can run +`jupyter labextension develop` command. To find it, run `jupyter labextension list` to figure out where the `labextensions` folder is located. Then you can remove the symlink named `jupyter-scheduler` within that folder. @@ -76,7 +76,7 @@ folder. ### Server tests -This extension is using [Pytest](https://docs.pytest.org/) for Python code testing. +This extension uses [Pytest](https://docs.pytest.org/) for Python code testing. Install test dependencies (needed only once): @@ -106,22 +106,22 @@ jlpm test This extension uses Playwright for the integration tests (aka user level tests). More precisely, the JupyterLab helper [Galata](https://github.com/jupyterlab/jupyterlab/tree/master/galata) is used to -handle testing the extension in JupyterLab. +test the extension in JupyterLab. -More information are provided within the +You can find more information in the [ui-tests](https://github.com/jupyter-server/jupyter-scheduler/tree/main/ui-tests) README. ## Documentation -First, ensure GNU Make is installed locally, and then install the `docs` extras: +First, ensure GNU Make is installed locally, and then install the `docs` dependencies: ``` pip install -e ".[docs]" ``` Documentation is built with the [Sphinx](https://www.sphinx-doc.org/en/master/) -documentation generator, via the following command run from the project root: +documentation generator, using the following command executed from the project root: ``` make -C docs html @@ -131,10 +131,9 @@ Documentation source files are written in [MyST](https://myst-parser.readthedocs.io/en/latest/index.html), a rich and more expressive flavor of Markdown. These files are located under `docs/`. -The built documentation files are generated under `docs/_build/html` can be -accessed in the browser via a file URI, e.g. -`file:///Users/dlq/workplace/jupyter-scheduler/docs/_build/html/index.html`. We -recommend bookmarking this if you will be editing documentation frequently. +The generated documentation files placed under `docs/_build/html` can be +directly opened in the browser. We recommend bookmarking these file links +if you will be editing and reviewing documentation frequently in the browser. Sphinx by default only rebuilds files it detects were changed, though this detection logic is sometimes faulty. To force a full rebuild of the @@ -148,21 +147,20 @@ make -C docs clean && make -C docs html Releases should be done via [Jupyter Releaser](https://github.com/jupyter-server/jupyter_releaser). If you have admin permissions on the repository, you can go to the "Actions" -panel on GitHub and follow the below instructions to release a new version of +panel on GitHub and follow the following instructions to release a new version of Jupyter Scheduler. -1. Click the "Step 1: Prep Release" workflow on the left-hand panel, and then -click "Run workflow". This will open a form. Replace the "Next Version -Specifier" with the next version of Jupyter Scheduler (e.g. 1.2.3). Do not -include the "v" prefix. "Branch to Target" should be replaced with the branch -that should be released. Usually this will be `main`. Then click "Run workflow" -(the green button) to submit the form and run the workflow. +1. Select the "Step 1: Prep Release" workflow on the left-hand panel, and then +select "Run workflow". This will open the workflow form. Replace the "Next Version +Specifier" with the next version of Jupyter Scheduler (e.g. 1.2.3). "Branch to Target" +should be replaced with the branch that should be released. Usually this will be `main`. +Then select "Run workflow" button to run the workflow. 2. Verify the draft release changelog in the [Releases](https://github.com/jupyter-server/jupyter-scheduler/releases) page for Jupyter Scheduler. -3. Return to the Actions panel and click the "Step 2: Publish Release" workflow. +3. Return to the Actions panel and select the "Step 2: Publish Release" workflow. Run this workflow with the target branch set to the same branch specified in Step 1. @@ -177,16 +175,15 @@ release changelog afterwards. #### Python package -This extension can be distributed as Python -packages. All of the Python -packaging instructions in the `pyproject.toml` file to wrap your extension in a +This extension can be distributed as Python packages. The Python +packaging instructions in the `pyproject.toml` file can wrap your extension in a Python package. Before generating a package, we first need to install `build`. ```bash pip install build twine tbump ``` -Make sure you have checked out the `main` branch updated from remote. +Check out your local `main` branch and keep it up to date with the remote version. ```bash git checkout main diff --git a/docs/developers/index.md b/docs/developers/index.md index f3c6d9a7..bb2f2bff 100644 --- a/docs/developers/index.md +++ b/docs/developers/index.md @@ -2,7 +2,7 @@ These pages are targeted at people who want to extend or override the server or lab extensions in Jupyter Scheduler. This is necessary for more complex features -that cannot simply be defined as a set of configuration options. +that go beyond the configuration options in this package. For installation and usage instructions, please refer to our {doc}`user's guide `. diff --git a/docs/operators/index.md b/docs/operators/index.md index 2f851e59..cf35c561 100644 --- a/docs/operators/index.md +++ b/docs/operators/index.md @@ -1,6 +1,6 @@ # Operators -These pages are targeted at people who want to configure and deploy Jupyter Scheduler. +These docs are intended for users who want to configure and deploy Jupyter Scheduler. For installation and usage instructions, please refer to our {doc}`user's guide `. @@ -52,7 +52,7 @@ all abstract methods. The default class is jupyter lab --SchedulerApp.environment_manager_class=jupyter_scheduler.environments.CondaEnvironmentManager ``` -For more information on how to write a custom implementation, please to our {doc}`developer's guide `. +For more information on writing a custom implementation, please see the {doc}`developer's guide `. ### execution_manager_class @@ -60,7 +60,7 @@ The fully classified classname to use for the execution manager, the module that is responsible for reading the input file, executing and writing the output. This option lets you specify a custom execution engine without replacing the whole scheduler API. This class should extend -`jupyter_scheduler.executors.ExecutionManager` and implement the execute method. +`jupyter_scheduler.executors.ExecutionManager` and implement the `execute` method. The default class is `jupyter_scheduler.executors.DefaultExecutionManager`. ``` @@ -71,7 +71,7 @@ jupyter lab --BaseScheduler.execution_manager_class=jupyter_scheduler.executors. jupyter lab --Scheduler.execution_manager_class=jupyter_scheduler.executors.DefaultExecutionManager ``` -For more information on how to write a custom implementation, please to our {doc}`developer's guide `. +For more information on writing a custom implementation, please see the {doc}`developer's guide `. ## UI configuration @@ -88,5 +88,5 @@ This allows you to customize the "advanced options" shown in the "Create Job" form and the "Job Details" view (and the same form/view for job definitions). -For more information on how to write a custom plugin, please to our +For more information about writing a custom plugin, please see the {doc}`developer's guide `. diff --git a/docs/users/index.md b/docs/users/index.md index 42dcc26e..a758aa04 100644 --- a/docs/users/index.md +++ b/docs/users/index.md @@ -1,13 +1,12 @@ # Users -These pages are targeted at people interested in simply installing Jupyter -Scheduler and using it. +These pages are for people interested in installing and using Jupyter Scheduler. For configuration options, please refer to our {doc}`operator's guide `. ## Installation -Jupyter Scheduler can be installed from the PyPi registry via `pip`: +Jupyter Scheduler can be installed from the PyPI registry via `pip`: ``` pip install jupyter-scheduler @@ -20,5 +19,5 @@ jupyter server extension list jupyter labextension list ``` -and asserting that both the `jupyter_scheduler` server extension and the +and checking that both the `jupyter_scheduler` server extension and the `@jupyterlab/scheduler` prebuilt lab extension are enabled. From eb065808f9ed18ea8bc0cd3b699eb28e53b61467 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 15 Nov 2022 23:53:23 +0000 Subject: [PATCH 08/11] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/contributors/index.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/contributors/index.md b/docs/contributors/index.md index 6ad83db9..ea4c77ea 100644 --- a/docs/contributors/index.md +++ b/docs/contributors/index.md @@ -132,7 +132,7 @@ Documentation source files are written in expressive flavor of Markdown. These files are located under `docs/`. The generated documentation files placed under `docs/_build/html` can be -directly opened in the browser. We recommend bookmarking these file links +directly opened in the browser. We recommend bookmarking these file links if you will be editing and reviewing documentation frequently in the browser. Sphinx by default only rebuilds files it detects were changed, though this @@ -152,8 +152,8 @@ Jupyter Scheduler. 1. Select the "Step 1: Prep Release" workflow on the left-hand panel, and then select "Run workflow". This will open the workflow form. Replace the "Next Version -Specifier" with the next version of Jupyter Scheduler (e.g. 1.2.3). "Branch to Target" -should be replaced with the branch that should be released. Usually this will be `main`. +Specifier" with the next version of Jupyter Scheduler (e.g. 1.2.3). "Branch to Target" +should be replaced with the branch that should be released. Usually this will be `main`. Then select "Run workflow" button to run the workflow. 2. Verify the draft release changelog in the From 6483e7419949bdc00ed83120293fdb9bc85f270e Mon Sep 17 00:00:00 2001 From: "David L. Qiu" Date: Wed, 16 Nov 2022 00:03:25 +0000 Subject: [PATCH 09/11] remove source map docs until confirmed to work --- docs/contributors/index.md | 9 --------- 1 file changed, 9 deletions(-) diff --git a/docs/contributors/index.md b/docs/contributors/index.md index ea4c77ea..8d2f5886 100644 --- a/docs/contributors/index.md +++ b/docs/contributors/index.md @@ -49,15 +49,6 @@ and made available in your running JupyterLab. Refresh JupyterLab to load the change in your browser (you may need to wait several seconds for the extension to be rebuilt). -By default, the `jlpm build` command generates the source maps for this -extension to make it easier to debug using the browser dev tools. To also -generate source maps for the JupyterLab core extensions, you can run the -following command: - -```bash -jupyter lab build --minimize=False -``` - ## Development uninstall ```bash From f43b163b8dadc126053f660e522e78da58af6878 Mon Sep 17 00:00:00 2001 From: "David L. Qiu" Date: Wed, 16 Nov 2022 00:06:08 +0000 Subject: [PATCH 10/11] elaborate on development uninstall procedure --- docs/contributors/index.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/docs/contributors/index.md b/docs/contributors/index.md index 8d2f5886..72f9afd7 100644 --- a/docs/contributors/index.md +++ b/docs/contributors/index.md @@ -58,10 +58,23 @@ pip uninstall jupyter_scheduler ``` In development mode, you will also need to remove the symlink created by -`jupyter labextension develop` command. To find it, run -`jupyter labextension list` to figure out where the `labextensions` folder is -located. Then you can remove the symlink named `jupyter-scheduler` within that +`jupyter labextension develop` command. First, find where the lab extension +folder is located: + +```bash +$ jupyter labextension list + +... +/opt/anaconda3/envs/jupyter-scheduler/share/jupyter/labextensions + @jupyterlab/scheduler v1.1.4 enabled OK +``` + +Then you can remove the symlink named `jupyter-scheduler` within that folder. +``` +# Remove the symlink +rm /opt/anaconda3/envs/jupyter-scheduler/share/jupyter/labextensions/jupyter_scheduler +``` ## Testing From 9da688bddd5893c88ab0147b1d0f56ab224e2403 Mon Sep 17 00:00:00 2001 From: Jason Weill <93281816+jweill-aws@users.noreply.github.com> Date: Tue, 15 Nov 2022 16:14:13 -0800 Subject: [PATCH 11/11] Apply suggestions from code review Additional small copy edits --- docs/contributors/index.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/contributors/index.md b/docs/contributors/index.md index 72f9afd7..74d1c304 100644 --- a/docs/contributors/index.md +++ b/docs/contributors/index.md @@ -58,8 +58,7 @@ pip uninstall jupyter_scheduler ``` In development mode, you will also need to remove the symlink created by -`jupyter labextension develop` command. First, find where the lab extension -folder is located: +`jupyter labextension develop` command. First, find the lab extension folder: ```bash $ jupyter labextension list @@ -69,8 +68,7 @@ $ jupyter labextension list @jupyterlab/scheduler v1.1.4 enabled OK ``` -Then you can remove the symlink named `jupyter-scheduler` within that -folder. +Then, remove the symlink named `jupyter-scheduler` within that folder. ``` # Remove the symlink rm /opt/anaconda3/envs/jupyter-scheduler/share/jupyter/labextensions/jupyter_scheduler @@ -107,7 +105,7 @@ jlpm test ### Integration tests -This extension uses Playwright for the integration tests (aka user level tests). +This extension uses Playwright for the integration tests (user-level tests). More precisely, the JupyterLab helper [Galata](https://github.com/jupyterlab/jupyterlab/tree/master/galata) is used to test the extension in JupyterLab. @@ -125,7 +123,7 @@ pip install -e ".[docs]" ``` Documentation is built with the [Sphinx](https://www.sphinx-doc.org/en/master/) -documentation generator, using the following command executed from the project root: +documentation generator, by running the following command from the project root: ``` make -C docs html