Skip to content

Commit

Permalink
Implement Workspaces GUI (#15946)
Browse files Browse the repository at this point in the history
* Refactor workspaces to provide a scaffold for Workspaces UI

Fixup versions

* Remove workspace commands from top-level file menu

* Implement workspaces sidebar and menu implementation

Fix sidebars and commands missing await

Add the menus and sidebars schema

Fixup integrity

Fix yarn lock

Fix margin around close buttons in sidebar

* Add workspaces docs to a dedicated file, add GUI stub

* Add unit tests for `WorkspacesModel`

* Implement validation of workspace names, cleanup

* Add documentation snapshot which tests opening

workspace from file, adding workspaces by command, and context menu.
Fix minor issue with docs formatting/linking.

* `.jupyterlab-workspace` files do not need licence headers

* Fix tests, update snapshots

* Fix sidebar test by fixing workspaces state mock implementation

Wait for launcher to avoid flaky snapshot

Add snapshot for sidebar

* Test workspaces menu

Add snapshot for workspaces submenu

* Lint: add missing brackets

* Add workspace name in the "Delete" tooltip

* Adjust use of ellipsis (…) in workspace commands/menu

* Add quotes to emphasize workspace name,

to avoid confusion when workspace is named with a word

* Add note that deleting all workspaces is irreversible

* Rename `InputDialogTextualBase` to `InputDialogTextBase`

* Add a note about characters allowed in workspace names.

* Add an example for workspace date/time format.

* Apply suggestions on wording in workspaces docs

Co-authored-by: Jason Weill <93281816+JasonWeill@users.noreply.github.com>

* Fix snapshot (previous update got flaky kernel from another test)

* Update Playwright Snapshots

* Update Playwright Snapshots

* Revert spurious snapshot updates

* Update the test workspace to account for the new "Recent Files" section

* Fix flaky snapshot

* Update one more snapshot after merge

---------

Co-authored-by: Jason Weill <93281816+JasonWeill@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
3 people committed Mar 25, 2024
1 parent 2267e0f commit 2fc6682
Show file tree
Hide file tree
Showing 69 changed files with 2,323 additions and 386 deletions.
8 changes: 8 additions & 0 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -443,3 +443,11 @@ pkg:vega:
- any-glob-to-any-file:
- packages/vega5-extension/**/*
- packages/vega5-extension/*

pkg:workspaces:
- changed-files:
- any-glob-to-any-file:
- packages/workspaces/**/*
- packages/workspaces/*
- packages/workspaces-extension/**/*
- packages/workspaces-extension/*
1 change: 1 addition & 0 deletions .github/workflows/linuxjs-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ jobs:
js-translation,
js-ui-components,
js-vega5-extension,
js-workspaces,
]
fail-fast: false
runs-on: ubuntu-22.04
Expand Down
1 change: 1 addition & 0 deletions .licenserc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ header:
- '**/*.svg'
- '**/*.yml'
- '**/*.yaml'
- '**/*.jupyterlab-workspace'
- '**/build'
- '**/lib'
- '**/node_modules'
Expand Down
3 changes: 2 additions & 1 deletion buildutils/src/ensure-repo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,8 @@ const SKIP_CSS: Dict<string[]> = {
'@jupyterlab/tooltip-extension',
'@jupyterlab/translation-extension',
'@jupyterlab/ui-components-extension',
'@jupyterlab/vega5-extension'
'@jupyterlab/vega5-extension',
'@jupyterlab/workspaces-extension'
],
'@jupyterlab/notebook': ['@jupyterlab/application'],
'@jupyterlab/rendermime-interfaces': ['@lumino/widgets'],
Expand Down
11 changes: 9 additions & 2 deletions dev_mode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@
"@jupyterlab/ui-components": "~4.2.0-alpha.1",
"@jupyterlab/ui-components-extension": "~4.2.0-alpha.1",
"@jupyterlab/vega5-extension": "~4.2.0-alpha.1",
"@jupyterlab/workspaces": "~4.2.0-alpha.1",
"@jupyterlab/workspaces-extension": "~4.2.0-alpha.1",
"@lezer/common": "^1.0.0",
"@lezer/highlight": "^1.0.0",
"@lumino/algorithm": "^2.0.0",
Expand Down Expand Up @@ -189,7 +191,8 @@
"@jupyterlab/tooltip-extension": "~4.2.0-alpha.1",
"@jupyterlab/translation-extension": "~4.2.0-alpha.1",
"@jupyterlab/ui-components-extension": "~4.2.0-alpha.1",
"@jupyterlab/vega5-extension": "~4.2.0-alpha.1"
"@jupyterlab/vega5-extension": "~4.2.0-alpha.1",
"@jupyterlab/workspaces-extension": "~4.2.0-alpha.1"
},
"devDependencies": {
"@jupyterlab/builder": "^4.2.0-alpha.1",
Expand Down Expand Up @@ -264,7 +267,8 @@
"@jupyterlab/toc-extension": "",
"@jupyterlab/tooltip-extension": "",
"@jupyterlab/translation-extension": "",
"@jupyterlab/ui-components-extension": ""
"@jupyterlab/ui-components-extension": "",
"@jupyterlab/workspaces-extension": ""
},
"mimeExtensions": {
"@jupyterlab/javascript-extension": "",
Expand Down Expand Up @@ -320,6 +324,7 @@
"@jupyterlab/tooltip",
"@jupyterlab/translation",
"@jupyterlab/ui-components",
"@jupyterlab/workspaces",
"@lezer/common",
"@lezer/highlight",
"@lumino/algorithm",
Expand Down Expand Up @@ -440,6 +445,8 @@
"@jupyterlab/ui-components": "../packages/ui-components",
"@jupyterlab/ui-components-extension": "../packages/ui-components-extension",
"@jupyterlab/vega5-extension": "../packages/vega5-extension",
"@jupyterlab/workspaces": "../packages/workspaces",
"@jupyterlab/workspaces-extension": "../packages/workspaces-extension",
"@jupyterlab/builder": "../builder",
"@jupyterlab/buildutils": "../buildutils",
"@jupyterlab/template": "../buildutils/template",
Expand Down
1 change: 1 addition & 0 deletions dev_mode/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,4 @@ import '@jupyterlab/tooltip-extension/style/index.js';
import '@jupyterlab/translation-extension/style/index.js';
import '@jupyterlab/ui-components-extension/style/index.js';
import '@jupyterlab/vega5-extension/style/index.js';
import '@jupyterlab/workspaces-extension/style/index.js';
4 changes: 2 additions & 2 deletions docs/source/getting_started/starting.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Example:
You may access JupyterLab by entering the notebook server's :ref:`URL <urls>`
into the browser. JupyterLab sessions always reside in a
:ref:`workspace <url-workspaces-ui>`. The default workspace is the main ``/lab`` URL:
:ref:`workspace <workspaces>`. The default workspace is the main ``/lab`` URL:

.. code-block:: none
Expand All @@ -37,7 +37,7 @@ Like the classic notebook,
JupyterLab provides a way for users to copy URLs that
:ref:`open a specific notebook or file <url-tree>`. Additionally,
JupyterLab URLs are an advanced part of the user interface that allows for
managing :ref:`workspaces <url-workspaces-ui>`. To learn more about URLs in
managing :ref:`workspaces <url-workspaces>`. To learn more about URLs in
Jupyterlab, visit :ref:`urls`.

JupyterLab runs on top of Jupyter Server, so see the `security
Expand Down
4 changes: 3 additions & 1 deletion docs/source/user/directories.rst
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,8 @@ the default values given by extensions, as well as the default overrides from
the :ref:`overrides.json <overridesjson>` file in the application's settings
directory.

.. _workspaces-directory:

JupyterLab Workspaces Directory
-------------------------------

Expand All @@ -315,4 +317,4 @@ environments. The location can be modified using the
``JUPYTERLAB_WORKSPACES_DIR`` environment variable.

These files can be imported and exported to create default "profiles", using
the :ref:`workspace command line tool <url-workspaces-cli>`.
the :ref:`workspace command line tool <workspaces-cli>`.
1 change: 1 addition & 0 deletions docs/source/user/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ file_formats
debugger
toc
extensions
workspaces
jupyterhub
export
language
Expand Down
5 changes: 3 additions & 2 deletions docs/source/user/interface.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,12 @@ cell tools inspector <notebook>`, and the :ref:`tabs list <tabs>`.
:class: jp-screenshot
:alt: A screenshot of the default JupyterLab interface. The main work area is in the middle. There is also a left sidebar and a top menu bar.

JupyterLab sessions always reside in a :ref:`workspace <url-workspaces-ui>`.
JupyterLab sessions always reside in a :ref:`workspace <workspaces>`.
Workspaces contain the state of JupyterLab: the files that are currently open,
the layout of the application areas and tabs, etc.
Workspaces can be saved on the server with
:ref:`named workspace URLs <url-workspaces-ui>`.
:ref:`named workspace URLs <url-workspaces>` or
:ref:`using workspace commands <workspaces-gui>` available in the menu and sidebar.
To learn more about URLs in Jupyterlab, visit :ref:`urls`.


Expand Down
89 changes: 3 additions & 86 deletions docs/source/user/urls.rst
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@ using ``#cell-id=<cell-id>`` Fragment Identification Syntax.
The ``cell-id`` fragment locator is not part of a formal Jupyter standard and subject to change.
To leave feedback, please comment in the discussion: `nbformat#317 <https://github.com/jupyter/nbformat/issues/317>`_.

.. _url-workspaces-ui:
.. _url-workspaces:

Managing Workspaces (UI)
------------------------
Managing Workspaces (URL)
-------------------------

JupyterLab sessions always reside in a workspace. Workspaces contain the state
of JupyterLab: the files that are currently open, the layout of the application
Expand Down Expand Up @@ -168,86 +168,3 @@ To reset the contents of the default workspace and load a notebook:
.. code-block:: none
http(s)://<server:port>/<lab-location>/lab/tree/path/to/notebook.ipynb?reset
.. _url-workspaces-cli:

Managing Workspaces (CLI)
-------------------------

JupyterLab provides a command-line interface for workspace ``import`` and
``export``:

.. code-block:: bash
$ # Exports the default JupyterLab workspace
$ jupyter lab workspaces export
{"data": {}, "metadata": {"id": "/lab"}}
$
$ # Exports the workspaces named `foo`
$ jupyter lab workspaces export foo
{"data": {}, "metadata": {"id": "/lab/workspaces/foo"}}
$
$ # Exports the workspace named `foo` into a file called `file_name.json`
$ jupyter lab workspaces export foo > file_name.json
$
$ # Imports the workspace file `file_name.json`.
$ jupyter lab workspaces import file_name.json
Saved workspace: <workspaces-directory>/labworkspacesfoo-54d5.jupyterlab-workspace
The ``export`` functionality is as friendly as possible: if a workspace does not
exist, it will still generate an empty workspace for export.

The ``import`` functionality validates the structure of the workspace file and
validates the ``id`` field in the workspace ``metadata`` to make sure its URL is
compatible with either the ``workspaces_url`` configuration or the ``page_url``
configuration to verify that it is a correctly named workspace or it is the
default workspace.


Workspace File Format
---------------------

A workspace file in a JSON file with a specific spec.


There are two top level keys requires, `data`, and `metadata`.

The `metadata` must be a mapping with an `id`
key that has the same value as the ID of the workspace. This should also be the relative URL path to access the workspace,
like `/lab/workspaces/foo`.

The `data` key maps to the initial state of the `IStateDB`. Many plugins look in the State DB for the configuration.
Also any plugins that register with the `ILayoutRestorer` will look up all keys in the State DB
that start with the `namespace` of their tracker before the first `:`. The values of these keys should have a `data`
attribute that maps.

For example, if your workspace looks like this:

.. code-block:: json
{
"data": {
"application-mimedocuments:package.json:JSON": {
"data": { "path": "package.json", "factory": "JSON" }
}
}
}
It will run the `docmanager:open` with the `{ "path": "package.json", "factory": "JSON" }` args, because the `application-mimedocuments` tracker is registered with the `docmanager:open` command, like this:


.. code-block:: typescript
const namespace = 'application-mimedocuments';
const tracker = new WidgetTracker<MimeDocument>({ namespace });
void restorer.restore(tracker, {
command: 'docmanager:open',
args: widget => ({
path: widget.context.path,
factory: Private.factoryNameProperty.get(widget)
}),
name: widget =>
`${widget.context.path}:${Private.factoryNameProperty.get(widget)}`
});
Note the part of the data key after the first `:` (`package.json:JSON`) is dropped and is irrelevant.
117 changes: 117 additions & 0 deletions docs/source/user/workspaces.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
.. Copyright (c) Jupyter Development Team.
.. Distributed under the terms of the Modified BSD License.
.. _workspaces:

Workspaces
==========

A JupyterLab Workspace defines the layout and state of the user interface such as the position of files, notebooks, sidebars, and open/closed state of the panels.

Workspaces can be managed in three ways:

- :ref:`via Graphical User Interface <workspaces-gui>`
- :ref:`via Command Line Interface <workspaces-cli>`
- :ref:`via URL schema and parameters <url-workspaces>`


A workspace name may only contain ASCII letters (a-z and A-Z), digits (0-9), hyphen-minuses (``-``) and underscores (``_``).

.. _workspaces-gui:

Managing Workspaces (GUI)
-------------------------

There are several commands for managing workspaces from the main menu, sidebar, and command palette:

- `create-new`, `clone`, `rename`, `reset`, and `delete` act on the workspaces stored by on the server in :ref:`the dedicated location <workspaces-directory>`.
- `save`, `save as`, `import`, and `export` can load and store the workspace to/from the file system (contained within the Jupyter root directory); `save` will save the workspace to the most recently saved file.

In the sidebar, in the "Running Terminals and Kernels" panel, under "Workspaces", the current workspace has a check mark (✓). Clicking on another workspace will open. Opening the context menu (right click) over the workspace item in the sidebar will present actions available for management of that workspace:

.. image:: ../images/workspaces-sidebar.png
:align: center
:class: jp-screenshot
:alt: The context menu opened over workspaces sidebar

.. _workspaces-cli:

Managing Workspaces (CLI)
-------------------------

JupyterLab provides a command line interface for workspace ``import`` and
``export``:

.. code-block:: bash
$ # Exports the default JupyterLab workspace
$ jupyter lab workspaces export
{"data": {}, "metadata": {"id": "/lab"}}
$
$ # Exports the workspaces named `foo`
$ jupyter lab workspaces export foo
{"data": {}, "metadata": {"id": "/lab/workspaces/foo"}}
$
$ # Exports the workspace named `foo` into a file called `file_name.json`
$ jupyter lab workspaces export foo > file_name.json
$
$ # Imports the workspace file `file_name.json`.
$ jupyter lab workspaces import file_name.json
Saved workspace: <workspaces-directory>/labworkspacesfoo-54d5.jupyterlab-workspace
The ``export`` command will generate a URL for any workspace you provide as an argument,
even if the workspace does not yet exist. Visiting a URL for a nonexistent workspace will create
a new workspace with that name.

The ``import`` functionality validates the structure of the workspace file and
validates the ``id`` field in the workspace ``metadata`` to make sure its URL is
compatible with either the ``workspaces_url`` configuration or the ``page_url``
configuration to verify that it is a correctly named workspace or it is the
default workspace.


Workspace File Format
---------------------

A workspace file is a JSON file that contains one object with two required top-level keys, `data`, and `metadata`.

The `metadata` must be a mapping with an `id`
key that has the same value as the ID of the workspace. This should also be the relative URL path to access the workspace,
like `/lab/workspaces/foo`. Additionally, `metadata` may contain `created` and `last_modified` fields with date and time creation and most recent modification, respectively.
The date and time are encoded using ISO 8601 format, for example ``2022-06-15T23:41:15.818986+00:00``.

The `data` key maps to the initial state of the ``IStateDB``. Many plugins look in the State DB for the configuration.
Also any plugins that register with the ``ILayoutRestorer`` will look up all keys in the State DB
that start with the `namespace` of their tracker before the first ``:``. The values of these keys should have a `data`
attribute that maps.

For example, if your workspace looks like this:

.. code-block:: json
{
"data": {
"application-mimedocuments:package.json:JSON": {
"data": { "path": "package.json", "factory": "JSON" }
}
}
}
It will run the `docmanager:open` with the ``{ "path": "package.json", "factory": "JSON" }`` args, because the `application-mimedocuments` tracker is registered with the `docmanager:open` command, like this:


.. code-block:: typescript
const namespace = 'application-mimedocuments';
const tracker = new WidgetTracker<MimeDocument>({ namespace });
void restorer.restore(tracker, {
command: 'docmanager:open',
args: widget => ({
path: widget.context.path,
factory: Private.factoryNameProperty.get(widget)
}),
name: widget =>
`${widget.context.path}:${Private.factoryNameProperty.get(widget)}`
});
Note the part of the data key after the first ``:`` (``package.json:JSON``) is dropped and is irrelevant.
Loading

0 comments on commit 2fc6682

Please sign in to comment.