├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ ├── build.yml │ ├── check-release.yml │ └── update-integration-tests.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .stylelintrc ├── 310_notebook ├── __init__.py └── _version.py ├── CHANGELOG.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── RELEASE.md ├── babel.config.js ├── install.json ├── jest.config.js ├── package.json ├── pyproject.toml ├── setup.py ├── src ├── __tests__ │ └── 310_notebook.spec.ts └── index.ts ├── style ├── base.css ├── index.css └── index.js ├── tsconfig.json └── ui-tests ├── README.md ├── jupyter_server_test_config.py ├── package.json ├── playwright.config.js └── tests └── 310_notebook.spec.ts /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | coverage 4 | **/*.d.ts 5 | tests 6 | 7 | **/__tests__ 8 | ui-tests 9 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'eslint:recommended', 4 | 'plugin:@typescript-eslint/eslint-recommended', 5 | 'plugin:@typescript-eslint/recommended', 6 | 'plugin:prettier/recommended' 7 | ], 8 | parser: '@typescript-eslint/parser', 9 | parserOptions: { 10 | project: 'tsconfig.json', 11 | sourceType: 'module' 12 | }, 13 | plugins: ['@typescript-eslint'], 14 | rules: { 15 | '@typescript-eslint/naming-convention': [ 16 | 'error', 17 | { 18 | selector: 'interface', 19 | format: ['PascalCase'], 20 | custom: { 21 | regex: '^I[A-Z]', 22 | match: true 23 | } 24 | } 25 | ], 26 | '@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }], 27 | '@typescript-eslint/no-explicit-any': 'off', 28 | '@typescript-eslint/no-namespace': 'off', 29 | '@typescript-eslint/no-use-before-define': 'off', 30 | '@typescript-eslint/quotes': [ 31 | 'error', 32 | 'single', 33 | { avoidEscape: true, allowTemplateLiterals: false } 34 | ], 35 | curly: ['error', 'all'], 36 | eqeqeq: 'error', 37 | 'prefer-arrow-callback': 'error' 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: main 6 | pull_request: 7 | branches: '*' 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v2 16 | 17 | - name: Base Setup 18 | uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 19 | 20 | - name: Install dependencies 21 | run: python -m pip install -U jupyterlab~=3.1 check-manifest 22 | 23 | - name: Lint the extension 24 | run: | 25 | set -eux 26 | jlpm 27 | jlpm run lint:check 28 | 29 | - name: Test the extension 30 | run: | 31 | set -eux 32 | jlpm run test 33 | 34 | - name: Build the extension 35 | run: | 36 | set -eux 37 | python -m pip install .[test] 38 | 39 | jupyter labextension list 40 | jupyter labextension list 2>&1 | grep -ie "310_notebook.*OK" 41 | python -m jupyterlab.browser_check 42 | 43 | - name: Package the extension 44 | run: | 45 | set -eux 46 | check-manifest -v 47 | 48 | pip install build 49 | python -m build 50 | pip uninstall -y "310_notebook" jupyterlab 51 | 52 | - name: Upload extension packages 53 | uses: actions/upload-artifact@v2 54 | with: 55 | name: extension-artifacts 56 | path: dist/310_notebook* 57 | if-no-files-found: error 58 | 59 | test_isolated: 60 | needs: build 61 | runs-on: ubuntu-latest 62 | 63 | steps: 64 | - name: Checkout 65 | uses: actions/checkout@v2 66 | - name: Install Python 67 | uses: actions/setup-python@v2 68 | with: 69 | python-version: '3.9' 70 | architecture: 'x64' 71 | - uses: actions/download-artifact@v2 72 | with: 73 | name: extension-artifacts 74 | - name: Install and Test 75 | run: | 76 | set -eux 77 | # Remove NodeJS, twice to take care of system and locally installed node versions. 78 | sudo rm -rf $(which node) 79 | sudo rm -rf $(which node) 80 | 81 | pip install "jupyterlab~=3.1" 310_notebook*.whl 82 | 83 | 84 | jupyter labextension list 85 | jupyter labextension list 2>&1 | grep -ie "310_notebook.*OK" 86 | python -m jupyterlab.browser_check --no-chrome-test 87 | 88 | integration-tests: 89 | name: Integration tests 90 | needs: build 91 | runs-on: ubuntu-latest 92 | 93 | env: 94 | PLAYWRIGHT_BROWSERS_PATH: ${{ github.workspace }}/pw-browsers 95 | 96 | steps: 97 | - name: Checkout 98 | uses: actions/checkout@v2 99 | 100 | - name: Base Setup 101 | uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 102 | 103 | - name: Download extension package 104 | uses: actions/download-artifact@v2 105 | with: 106 | name: extension-artifacts 107 | 108 | - name: Install the extension 109 | run: | 110 | set -eux 111 | python -m pip install "jupyterlab~=3.1" 310_notebook*.whl 112 | 113 | - name: Install dependencies 114 | working-directory: ui-tests 115 | env: 116 | PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 117 | run: jlpm install 118 | 119 | - name: Set up browser cache 120 | uses: actions/cache@v2 121 | with: 122 | path: | 123 | ${{ github.workspace }}/pw-browsers 124 | key: ${{ runner.os }}-${{ hashFiles('ui-tests/yarn.lock') }} 125 | 126 | - name: Install browser 127 | run: jlpm playwright install chromium 128 | working-directory: ui-tests 129 | 130 | - name: Execute integration tests 131 | working-directory: ui-tests 132 | run: | 133 | jlpm playwright test 134 | 135 | - name: Upload Playwright Test report 136 | if: always() 137 | uses: actions/upload-artifact@v2 138 | with: 139 | name: 310_notebook-playwright-tests 140 | path: | 141 | ui-tests/test-results 142 | ui-tests/playwright-report 143 | -------------------------------------------------------------------------------- /.github/workflows/check-release.yml: -------------------------------------------------------------------------------- 1 | name: Check Release 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | 10 | permissions: 11 | contents: write 12 | 13 | jobs: 14 | check_release: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v2 19 | - name: Install Python 20 | uses: actions/setup-python@v2 21 | with: 22 | python-version: 3.9 23 | architecture: 'x64' 24 | - name: Install node 25 | uses: actions/setup-node@v2 26 | with: 27 | node-version: '14.x' 28 | 29 | 30 | - name: Get pip cache dir 31 | id: pip-cache 32 | run: | 33 | echo "::set-output name=dir::$(pip cache dir)" 34 | - name: Cache pip 35 | uses: actions/cache@v1 36 | with: 37 | path: ${{ steps.pip-cache.outputs.dir }} 38 | key: ${{ runner.os }}-pip-${{ hashFiles('package.json') }} 39 | restore-keys: | 40 | ${{ runner.os }}-pip- 41 | - name: Cache checked links 42 | uses: actions/cache@v2 43 | with: 44 | path: ~/.cache/pytest-link-check 45 | key: ${{ runner.os }}-linkcheck-${{ hashFiles('**/.md') }}-md-links 46 | restore-keys: | 47 | ${{ runner.os }}-linkcheck- 48 | - name: Upgrade packaging dependencies 49 | run: | 50 | pip install --upgrade pip setuptools wheel jupyter-packaging~=0.10 --user 51 | - name: Install Dependencies 52 | run: | 53 | pip install . 54 | - name: Check Release 55 | uses: jupyter-server/jupyter_releaser/.github/actions/check-release@v1 56 | with: 57 | token: ${{ secrets.GITHUB_TOKEN }} 58 | 59 | - name: Upload Distributions 60 | uses: actions/upload-artifact@v2 61 | with: 62 | name: 310_notebook-releaser-dist-${{ github.run_number }} 63 | path: .jupyter_releaser_checkout/dist 64 | -------------------------------------------------------------------------------- /.github/workflows/update-integration-tests.yml: -------------------------------------------------------------------------------- 1 | name: Update Playwright Snapshots 2 | 3 | on: 4 | issue_comment: 5 | types: [created, edited] 6 | 7 | permissions: 8 | contents: write 9 | pull-requests: write 10 | 11 | jobs: 12 | 13 | 14 | update-snapshots: 15 | if: ${{ github.event.issue.pull_request && contains(github.event.comment.body, 'please update playwright snapshots') }} 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v2 21 | with: 22 | token: ${{ secrets.GITHUB_TOKEN }} 23 | 24 | - name: Configure git to use https 25 | run: git config --global hub.protocol https 26 | 27 | - name: Checkout the branch from the PR that triggered the job 28 | run: hub pr checkout ${{ github.event.issue.number }} 29 | env: 30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | 32 | - name: Install dependencies 33 | run: | 34 | set -eux 35 | jlpm 36 | python -m pip install . 37 | 38 | - uses: jupyterlab/maintainer-tools/.github/actions/update-snapshots@v1 39 | with: 40 | github_token: ${{ secrets.GITHUB_TOKEN }} 41 | # Playwright knows how to start JupyterLab server 42 | start_server_script: 'null' 43 | test_folder: ui-tests 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.bundle.* 2 | lib/ 3 | node_modules/ 4 | .eslintcache 5 | .stylelintcache 6 | *.egg-info/ 7 | .ipynb_checkpoints 8 | *.tsbuildinfo 9 | 310_notebook/labextension 10 | 11 | # Integration tests 12 | ui-tests/test-results/ 13 | ui-tests/playwright-report/ 14 | 15 | # Created by https://www.gitignore.io/api/python 16 | # Edit at https://www.gitignore.io/?templates=python 17 | 18 | ### Python ### 19 | # Byte-compiled / optimized / DLL files 20 | __pycache__/ 21 | *.py[cod] 22 | *$py.class 23 | 24 | # C extensions 25 | *.so 26 | 27 | # Distribution / packaging 28 | .Python 29 | build/ 30 | develop-eggs/ 31 | dist/ 32 | downloads/ 33 | eggs/ 34 | .eggs/ 35 | lib/ 36 | lib64/ 37 | parts/ 38 | sdist/ 39 | var/ 40 | wheels/ 41 | pip-wheel-metadata/ 42 | share/python-wheels/ 43 | .installed.cfg 44 | *.egg 45 | MANIFEST 46 | 47 | # PyInstaller 48 | # Usually these files are written by a python script from a template 49 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 50 | *.manifest 51 | *.spec 52 | 53 | # Installer logs 54 | pip-log.txt 55 | pip-delete-this-directory.txt 56 | 57 | # Unit test / coverage reports 58 | htmlcov/ 59 | .tox/ 60 | .nox/ 61 | .coverage 62 | .coverage.* 63 | .cache 64 | nosetests.xml 65 | coverage.xml 66 | *.cover 67 | .hypothesis/ 68 | .pytest_cache/ 69 | 70 | # Translations 71 | *.mo 72 | *.pot 73 | 74 | # Scrapy stuff: 75 | .scrapy 76 | 77 | # Sphinx documentation 78 | docs/_build/ 79 | 80 | # PyBuilder 81 | target/ 82 | 83 | # pyenv 84 | .python-version 85 | 86 | # celery beat schedule file 87 | celerybeat-schedule 88 | 89 | # SageMath parsed files 90 | *.sage.py 91 | 92 | # Spyder project settings 93 | .spyderproject 94 | .spyproject 95 | 96 | # Rope project settings 97 | .ropeproject 98 | 99 | # Mr Developer 100 | .mr.developer.cfg 101 | .project 102 | .pydevproject 103 | 104 | # mkdocs documentation 105 | /site 106 | 107 | # mypy 108 | .mypy_cache/ 109 | .dmypy.json 110 | dmypy.json 111 | 112 | # Pyre type checker 113 | .pyre/ 114 | 115 | # End of https://www.gitignore.io/api/python 116 | 117 | # OSX files 118 | .DS_Store 119 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | **/node_modules 3 | **/lib 4 | **/package.json 5 | 310_notebook 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "none", 4 | "arrowParens": "avoid", 5 | "endOfLine": "auto" 6 | } 7 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "stylelint-config-recommended", 4 | "stylelint-config-standard", 5 | "stylelint-prettier/recommended" 6 | ], 7 | "rules": { 8 | "property-no-vendor-prefix": null, 9 | "selector-no-vendor-prefix": null, 10 | "value-no-vendor-prefix": null 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /310_notebook/__init__.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pathlib import Path 3 | 4 | from ._version import __version__ 5 | 6 | 7 | HERE = Path(__file__).parent.resolve() 8 | 9 | 10 | with (HERE / "labextension" / "package.json").open() as fid: 11 | data = json.load(fid) 12 | 13 | 14 | def _jupyter_labextension_paths(): 15 | return [{ 16 | "src": "labextension", 17 | "dest": data["name"] 18 | }] 19 | 20 | -------------------------------------------------------------------------------- /310_notebook/_version.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pathlib import Path 3 | 4 | __all__ = ["__version__"] 5 | 6 | def _fetchVersion(): 7 | HERE = Path(__file__).parent.resolve() 8 | 9 | for settings in HERE.rglob("package.json"): 10 | try: 11 | with settings.open() as f: 12 | version = json.load(f)["version"] 13 | return ( 14 | version.replace("-alpha.", "a") 15 | .replace("-beta.", "b") 16 | .replace("-rc.", "rc") 17 | ) 18 | except FileNotFoundError: 19 | pass 20 | 21 | raise FileNotFoundError(f"Could not find package.json under dir {HERE!s}") 22 | 23 | __version__ = _fetchVersion() 24 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022, 310.ai 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include *.md 3 | include pyproject.toml 4 | include conftest.py 5 | 6 | include package.json 7 | include install.json 8 | include ts*.json 9 | include *.config.js 10 | include yarn.lock 11 | 12 | graft 310_notebook/labextension 13 | 14 | # Javascript files 15 | graft src 16 | graft style 17 | graft ui-tests 18 | prune **/node_modules 19 | prune lib 20 | prune binder 21 | 22 | # Patterns to exclude from any directory 23 | global-exclude *~ 24 | global-exclude *.pyc 25 | global-exclude *.pyo 26 | global-exclude .git 27 | global-exclude .ipynb_checkpoints 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 310_notebook 2 | 3 | [![Github Actions Status](https://github.com/pooriyapfn/jupyterlab_extension.git/workflows/Build/badge.svg)](https://github.com/pooriyapfn/jupyterlab_extension.git/actions/workflows/build.yml) 4 | A JupyterLab extension. 5 | 6 | ## Requirements 7 | 8 | - JupyterLab >= 3.0 9 | 10 | ## Install 11 | 12 | To install the extension, execute: 13 | 14 | ```bash 15 | pip install 310_notebook 16 | ``` 17 | 18 | ## Uninstall 19 | 20 | To remove the extension, execute: 21 | 22 | ```bash 23 | pip uninstall 310_notebook 24 | ``` 25 | 26 | ## Contributing 27 | 28 | ### Development install 29 | 30 | Note: You will need NodeJS to build the extension package. 31 | 32 | The `jlpm` command is JupyterLab's pinned version of 33 | [yarn](https://yarnpkg.com/) that is installed with JupyterLab. You may use 34 | `yarn` or `npm` in lieu of `jlpm` below. 35 | 36 | ```bash 37 | # Clone the repo to your local environment 38 | # Change directory to the 310_notebook directory 39 | # Install package in development mode 40 | pip install -e . 41 | # Link your development version of the extension with JupyterLab 42 | jupyter labextension develop . --overwrite 43 | # Rebuild extension Typescript source after making changes 44 | jlpm build 45 | ``` 46 | 47 | 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. 48 | 49 | ```bash 50 | # Watch the source directory in one terminal, automatically rebuilding when needed 51 | jlpm watch 52 | # Run JupyterLab in another terminal 53 | jupyter lab 54 | ``` 55 | 56 | 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). 57 | 58 | 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: 59 | 60 | ```bash 61 | jupyter lab build --minimize=False 62 | ``` 63 | 64 | ### Development uninstall 65 | 66 | ```bash 67 | pip uninstall 310_notebook 68 | ``` 69 | 70 | In development mode, you will also need to remove the symlink created by `jupyter labextension develop` 71 | command. To find its location, you can run `jupyter labextension list` to figure out where the `labextensions` 72 | folder is located. Then you can remove the symlink named `310_notebook` within that folder. 73 | 74 | ### Testing the extension 75 | 76 | #### Frontend tests 77 | 78 | This extension is using [Jest](https://jestjs.io/) for JavaScript code testing. 79 | 80 | To execute them, execute: 81 | 82 | ```sh 83 | jlpm 84 | jlpm test 85 | ``` 86 | 87 | #### Integration tests 88 | 89 | This extension uses [Playwright](https://playwright.dev/docs/intro/) for the integration tests (aka user level tests). 90 | More precisely, the JupyterLab helper [Galata](https://github.com/jupyterlab/jupyterlab/tree/master/galata) is used to handle testing the extension in JupyterLab. 91 | 92 | More information are provided within the [ui-tests](./ui-tests/README.md) README. 93 | 94 | ### Packaging the extension 95 | 96 | See [RELEASE](RELEASE.md) 97 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Making a new release of 310_notebook 2 | 3 | The extension can be published to `PyPI` and `npm` manually or using the [Jupyter Releaser](https://github.com/jupyter-server/jupyter_releaser). 4 | 5 | ## Manual release 6 | 7 | ### Python package 8 | 9 | This extension can be distributed as Python 10 | packages. All of the Python 11 | packaging instructions in the `pyproject.toml` file to wrap your extension in a 12 | Python package. Before generating a package, we first need to install `build`. 13 | 14 | ```bash 15 | pip install build twine 16 | ``` 17 | 18 | To create a Python source package (`.tar.gz`) and the binary package (`.whl`) in the `dist/` directory, do: 19 | 20 | ```bash 21 | python -m build 22 | ``` 23 | 24 | > `python setup.py sdist bdist_wheel` is deprecated and will not work for this package. 25 | 26 | Then to upload the package to PyPI, do: 27 | 28 | ```bash 29 | twine upload dist/* 30 | ``` 31 | 32 | ### NPM package 33 | 34 | To publish the frontend part of the extension as a NPM package, do: 35 | 36 | ```bash 37 | npm login 38 | npm publish --access public 39 | ``` 40 | 41 | ## Automated releases with the Jupyter Releaser 42 | 43 | The extension repository should already be compatible with the Jupyter Releaser. 44 | 45 | Check out the [workflow documentation](https://github.com/jupyter-server/jupyter_releaser#typical-workflow) for more information. 46 | 47 | Here is a summary of the steps to cut a new release: 48 | 49 | - Fork the [`jupyter-releaser` repo](https://github.com/jupyter-server/jupyter_releaser) 50 | - Add `ADMIN_GITHUB_TOKEN`, `PYPI_TOKEN` and `NPM_TOKEN` to the Github Secrets in the fork 51 | - Go to the Actions panel 52 | - Run the "Draft Changelog" workflow 53 | - Merge the Changelog PR 54 | - Run the "Draft Release" workflow 55 | - Run the "Publish Release" workflow 56 | 57 | ## Publishing to `conda-forge` 58 | 59 | 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 60 | 61 | Otherwise a bot should pick up the new version publish to PyPI, and open a new PR on the feedstock repository automatically. 62 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require('@jupyterlab/testutils/lib/babel.config'); 2 | -------------------------------------------------------------------------------- /install.json: -------------------------------------------------------------------------------- 1 | { 2 | "packageManager": "python", 3 | "packageName": "310_notebook", 4 | "uninstallInstructions": "Use your Python package manager (pip, conda, etc.) to uninstall the package 310_notebook" 5 | } 6 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const jestJupyterLab = require('@jupyterlab/testutils/lib/jest-config'); 2 | 3 | const esModules = [ 4 | '@jupyterlab/', 5 | 'lib0', 6 | 'y\\-protocols', 7 | 'y\\-websocket', 8 | 'yjs' 9 | ].join('|'); 10 | 11 | const jlabConfig = jestJupyterLab(__dirname); 12 | 13 | const { 14 | moduleFileExtensions, 15 | moduleNameMapper, 16 | preset, 17 | setupFilesAfterEnv, 18 | setupFiles, 19 | testPathIgnorePatterns, 20 | transform 21 | } = jlabConfig; 22 | 23 | module.exports = { 24 | moduleFileExtensions, 25 | moduleNameMapper, 26 | preset, 27 | setupFilesAfterEnv, 28 | setupFiles, 29 | testPathIgnorePatterns, 30 | transform, 31 | automock: false, 32 | collectCoverageFrom: ['src/**/*.{ts,tsx}', '!src/**/*.d.ts'], 33 | coverageDirectory: 'coverage', 34 | coverageReporters: ['lcov', 'text'], 35 | globals: { 36 | 'ts-jest': { 37 | tsconfig: 'tsconfig.json' 38 | } 39 | }, 40 | testRegex: 'src/.*/.*.spec.ts[x]?$', 41 | transformIgnorePatterns: [`/node_modules/(?!${esModules}).+`] 42 | }; 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "310_notebook", 3 | "version": "0.1.0", 4 | "description": "A JupyterLab extension.", 5 | "keywords": [ 6 | "jupyter", 7 | "jupyterlab", 8 | "jupyterlab-extension" 9 | ], 10 | "homepage": "https://github.com/pooriyapfn/jupyterlab_extension.git", 11 | "bugs": { 12 | "url": "https://github.com/pooriyapfn/jupyterlab_extension.git/issues" 13 | }, 14 | "license": "BSD-3-Clause", 15 | "author": { 16 | "name": "310.ai", 17 | "email": "dev@310.ai" 18 | }, 19 | "files": [ 20 | "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}", 21 | "style/**/*.{css,js,eot,gif,html,jpg,json,png,svg,woff2,ttf}" 22 | ], 23 | "main": "lib/index.js", 24 | "types": "lib/index.d.ts", 25 | "style": "style/index.css", 26 | "repository": { 27 | "type": "git", 28 | "url": "https://github.com/pooriyapfn/jupyterlab_extension.git.git" 29 | }, 30 | "scripts": { 31 | "build": "jlpm build:lib && jlpm build:labextension:dev", 32 | "build:prod": "jlpm clean && jlpm build:lib && jlpm build:labextension", 33 | "build:labextension": "jupyter labextension build .", 34 | "build:labextension:dev": "jupyter labextension build --development True .", 35 | "build:lib": "tsc", 36 | "clean": "jlpm clean:lib", 37 | "clean:lib": "rimraf lib tsconfig.tsbuildinfo", 38 | "clean:lintcache": "rimraf .eslintcache .stylelintcache", 39 | "clean:labextension": "rimraf 310_notebook/labextension", 40 | "clean:all": "jlpm clean:lib && jlpm clean:labextension && jlpm clean:lintcache", 41 | "eslint": "jlpm eslint:check --fix", 42 | "eslint:check": "eslint . --cache --ext .ts,.tsx", 43 | "install:extension": "jlpm build", 44 | "lint": "jlpm stylelint && jlpm prettier && jlpm eslint", 45 | "lint:check": "jlpm stylelint:check && jlpm prettier:check && jlpm eslint:check", 46 | "prettier": "jlpm prettier:base --write --list-different", 47 | "prettier:base": "prettier \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"", 48 | "prettier:check": "jlpm prettier:base --check", 49 | "stylelint": "jlpm stylelint:check --fix", 50 | "stylelint:check": "stylelint --cache \"style/**/*.css\"", 51 | "test": "jest --coverage", 52 | "watch": "run-p watch:src watch:labextension", 53 | "watch:src": "tsc -w", 54 | "watch:labextension": "jupyter labextension watch ." 55 | }, 56 | "dependencies": { 57 | "@jupyterlab/application": "^3.1.0" 58 | }, 59 | "devDependencies": { 60 | "@babel/core": "^7.0.0", 61 | "@babel/preset-env": "^7.0.0", 62 | "@jupyterlab/builder": "^3.1.0", 63 | "@jupyterlab/testutils": "^3.0.0", 64 | "@types/jest": "^26.0.0", 65 | "@typescript-eslint/eslint-plugin": "^4.8.1", 66 | "@typescript-eslint/parser": "^4.8.1", 67 | "eslint": "^7.14.0", 68 | "eslint-config-prettier": "^6.15.0", 69 | "eslint-plugin-prettier": "^3.1.4", 70 | "jest": "^26.0.0", 71 | "npm-run-all": "^4.1.5", 72 | "prettier": "^2.1.1", 73 | "rimraf": "^3.0.2", 74 | "stylelint": "^14.3.0", 75 | "stylelint-config-prettier": "^9.0.3", 76 | "stylelint-config-recommended": "^6.0.0", 77 | "stylelint-config-standard": "~24.0.0", 78 | "stylelint-prettier": "^2.0.0", 79 | "typescript": "~4.1.3", 80 | "ts-jest": "^26.0.0" 81 | }, 82 | "sideEffects": [ 83 | "style/*.css", 84 | "style/index.js" 85 | ], 86 | "styleModule": "style/index.js", 87 | "publishConfig": { 88 | "access": "public" 89 | }, 90 | "jupyterlab": { 91 | "extension": true, 92 | "outputDir": "310_notebook/labextension" 93 | }, 94 | "jupyter-releaser": { 95 | "hooks": { 96 | "before-build-npm": [ 97 | "python -m pip install jupyterlab~=3.1", 98 | "jlpm" 99 | ], 100 | "before-build-python": [ 101 | "jlpm clean:all" 102 | ] 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["jupyter_packaging~=0.10,<2", "jupyterlab~=3.1"] 3 | build-backend = "jupyter_packaging.build_api" 4 | 5 | [tool.jupyter-packaging.options] 6 | skip-if-exists = ["310_notebook/labextension/static/style.js"] 7 | ensured-targets = ["310_notebook/labextension/static/style.js", "310_notebook/labextension/package.json"] 8 | 9 | [tool.jupyter-packaging.builder] 10 | factory = "jupyter_packaging.npm_builder" 11 | 12 | [tool.jupyter-packaging.build-args] 13 | build_cmd = "build:prod" 14 | npm = ["jlpm"] 15 | 16 | [tool.check-manifest] 17 | ignore = ["310_notebook/labextension/**", "yarn.lock", ".*", "package-lock.json"] 18 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | 310_notebook setup 3 | """ 4 | import json 5 | import sys 6 | from pathlib import Path 7 | 8 | import setuptools 9 | 10 | HERE = Path(__file__).parent.resolve() 11 | 12 | # Get the package info from package.json 13 | pkg_json = json.loads((HERE / "package.json").read_bytes()) 14 | 15 | # The name of the project 16 | name = "310_notebook" 17 | 18 | lab_path = (HERE / pkg_json["jupyterlab"]["outputDir"]) 19 | 20 | # Representative files that should exist after a successful build 21 | ensured_targets = [ 22 | str(lab_path / "package.json"), 23 | str(lab_path / "static/style.js") 24 | ] 25 | 26 | labext_name = pkg_json["name"] 27 | 28 | data_files_spec = [ 29 | ("share/jupyter/labextensions/%s" % labext_name, str(lab_path.relative_to(HERE)), "**"), 30 | ("share/jupyter/labextensions/%s" % labext_name, str("."), "install.json"), 31 | ] 32 | 33 | long_description = (HERE / "README.md").read_text(encoding="utf8") 34 | 35 | version = ( 36 | pkg_json["version"] 37 | .replace("-alpha.", "a") 38 | .replace("-beta.", "b") 39 | .replace("-rc.", "rc") 40 | ) 41 | 42 | setup_args = dict( 43 | name=name, 44 | version=version, 45 | url=pkg_json["homepage"], 46 | author=pkg_json["author"]["name"], 47 | author_email=pkg_json["author"]["email"], 48 | description=pkg_json["description"], 49 | license=pkg_json["license"], 50 | license_file="LICENSE", 51 | long_description=long_description, 52 | long_description_content_type="text/markdown", 53 | packages=setuptools.find_packages(), 54 | zip_safe=False, 55 | include_package_data=True, 56 | python_requires=">=3.7", 57 | platforms="Linux, Mac OS X, Windows", 58 | keywords=["Jupyter", "JupyterLab", "JupyterLab3"], 59 | classifiers=[ 60 | "License :: OSI Approved :: BSD License", 61 | "Programming Language :: Python", 62 | "Programming Language :: Python :: 3", 63 | "Programming Language :: Python :: 3.7", 64 | "Programming Language :: Python :: 3.8", 65 | "Programming Language :: Python :: 3.9", 66 | "Programming Language :: Python :: 3.10", 67 | "Framework :: Jupyter", 68 | "Framework :: Jupyter :: JupyterLab", 69 | "Framework :: Jupyter :: JupyterLab :: 3", 70 | "Framework :: Jupyter :: JupyterLab :: Extensions", 71 | "Framework :: Jupyter :: JupyterLab :: Extensions :: Prebuilt", 72 | ], 73 | ) 74 | 75 | try: 76 | from jupyter_packaging import ( 77 | wrap_installers, 78 | npm_builder, 79 | get_data_files 80 | ) 81 | post_develop = npm_builder( 82 | build_cmd="install:extension", source_dir="src", build_dir=lab_path 83 | ) 84 | setup_args["cmdclass"] = wrap_installers(post_develop=post_develop, ensured_targets=ensured_targets) 85 | setup_args["data_files"] = get_data_files(data_files_spec) 86 | except ImportError as e: 87 | import logging 88 | logging.basicConfig(format="%(levelname)s: %(message)s") 89 | logging.warning("Build tool `jupyter-packaging` is missing. Install it with pip or conda.") 90 | if not ("--name" in sys.argv or "--version" in sys.argv): 91 | raise e 92 | 93 | if __name__ == "__main__": 94 | setuptools.setup(**setup_args) 95 | -------------------------------------------------------------------------------- /src/__tests__/310_notebook.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Example of [Jest](https://jestjs.io/docs/getting-started) unit tests 3 | */ 4 | 5 | describe('310_notebook', () => { 6 | it('should be tested', () => { 7 | expect(1 + 1).toEqual(2); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | JupyterFrontEnd, 3 | JupyterFrontEndPlugin 4 | } from '@jupyterlab/application'; 5 | 6 | /** 7 | * Initialization data for the 310_notebook extension. 8 | */ 9 | const plugin: JupyterFrontEndPlugin = { 10 | id: '310_notebook:plugin', 11 | autoStart: true, 12 | activate: (app: JupyterFrontEnd) => { 13 | console.log('JupyterLab extension 310_notebook is activated!'); 14 | } 15 | }; 16 | 17 | export default plugin; 18 | -------------------------------------------------------------------------------- /style/base.css: -------------------------------------------------------------------------------- 1 | /* 2 | See the JupyterLab Developer Guide for useful CSS Patterns: 3 | 4 | https://jupyterlab.readthedocs.io/en/stable/developer/css.html 5 | */ 6 | -------------------------------------------------------------------------------- /style/index.css: -------------------------------------------------------------------------------- 1 | @import url('base.css'); 2 | -------------------------------------------------------------------------------- /style/index.js: -------------------------------------------------------------------------------- 1 | import './base.css'; 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "composite": true, 5 | "declaration": true, 6 | "esModuleInterop": true, 7 | "incremental": true, 8 | "jsx": "react", 9 | "module": "esnext", 10 | "moduleResolution": "node", 11 | "noEmitOnError": true, 12 | "noImplicitAny": true, 13 | "noUnusedLocals": true, 14 | "preserveWatchOutput": true, 15 | "resolveJsonModule": true, 16 | "outDir": "lib", 17 | "rootDir": "src", 18 | "strict": true, 19 | "strictNullChecks": true, 20 | "target": "es2017", 21 | "types": ["jest"] 22 | }, 23 | "include": ["src/*"] 24 | } 25 | -------------------------------------------------------------------------------- /ui-tests/README.md: -------------------------------------------------------------------------------- 1 | # Integration Testing 2 | 3 | This folder contains the integration tests of the extension. 4 | 5 | They are defined using [Playwright](https://playwright.dev/docs/intro) test runner 6 | and [Galata](https://github.com/jupyterlab/jupyterlab/tree/master/galata) helper. 7 | 8 | The Playwright configuration is defined in [playwright.config.js](./playwright.config.js). 9 | 10 | The JupyterLab server configuration to use for the integration test is defined 11 | in [jupyter_server_test_config.py](./jupyter_server_test_config.py). 12 | 13 | The default configuration will produce video for failing tests and an HTML report. 14 | 15 | ## Run the tests 16 | 17 | > All commands are assumed to be executed from the root directory 18 | 19 | To run the tests, you need to: 20 | 21 | 1. Compile the extension: 22 | 23 | ```sh 24 | jlpm install 25 | jlpm build:prod 26 | ``` 27 | 28 | > Check the extension is installed in JupyterLab. 29 | 30 | 2. Install test dependencies (needed only once): 31 | 32 | ```sh 33 | cd ./ui-tests 34 | jlpm install 35 | jlpm playwright install 36 | cd .. 37 | ``` 38 | 39 | 3. Execute the [Playwright](https://playwright.dev/docs/intro) tests: 40 | 41 | ```sh 42 | cd ./ui-tests 43 | jlpm playwright test 44 | ``` 45 | 46 | Test results will be shown in the terminal. In case of any test failures, the test report 47 | will be opened in your browser at the end of the tests execution; see 48 | [Playwright documentation](https://playwright.dev/docs/test-reporters#html-reporter) 49 | for configuring that behavior. 50 | 51 | ## Update the tests snapshots 52 | 53 | > All commands are assumed to be executed from the root directory 54 | 55 | If you are comparing snapshots to validate your tests, you may need to update 56 | the reference snapshots stored in the repository. To do that, you need to: 57 | 58 | 1. Compile the extension: 59 | 60 | ```sh 61 | jlpm install 62 | jlpm build:prod 63 | ``` 64 | 65 | > Check the extension is installed in JupyterLab. 66 | 67 | 2. Install test dependencies (needed only once): 68 | 69 | ```sh 70 | cd ./ui-tests 71 | jlpm install 72 | jlpm playwright install 73 | cd .. 74 | ``` 75 | 76 | 3. Execute the [Playwright](https://playwright.dev/docs/intro) command: 77 | 78 | ```sh 79 | cd ./ui-tests 80 | jlpm playwright test -u 81 | ``` 82 | 83 | > Some discrepancy may occurs between the snapshots generated on your computer and 84 | > the one generated on the CI. To ease updating the snapshots on a PR, you can 85 | > type `please update playwright snapshots` to trigger the update by a bot on the CI. 86 | > Once the bot has computed new snapshots, it will commit them to the PR branch. 87 | 88 | ## Create tests 89 | 90 | > All commands are assumed to be executed from the root directory 91 | 92 | To create tests, the easiest way is to use the code generator tool of playwright: 93 | 94 | 1. Compile the extension: 95 | 96 | ```sh 97 | jlpm install 98 | jlpm build:prod 99 | ``` 100 | 101 | > Check the extension is installed in JupyterLab. 102 | 103 | 2. Install test dependencies (needed only once): 104 | 105 | ```sh 106 | cd ./ui-tests 107 | jlpm install 108 | jlpm playwright install 109 | cd .. 110 | ``` 111 | 112 | 3. Execute the [Playwright code generator](https://playwright.dev/docs/codegen): 113 | 114 | ```sh 115 | cd ./ui-tests 116 | jlpm playwright codegen localhost:8888 117 | ``` 118 | 119 | ## Debug tests 120 | 121 | > All commands are assumed to be executed from the root directory 122 | 123 | To debug tests, a good way is to use the inspector tool of playwright: 124 | 125 | 1. Compile the extension: 126 | 127 | ```sh 128 | jlpm install 129 | jlpm build:prod 130 | ``` 131 | 132 | > Check the extension is installed in JupyterLab. 133 | 134 | 2. Install test dependencies (needed only once): 135 | 136 | ```sh 137 | cd ./ui-tests 138 | jlpm install 139 | jlpm playwright install 140 | cd .. 141 | ``` 142 | 143 | 3. Execute the Playwright tests in [debug mode](https://playwright.dev/docs/debug): 144 | 145 | ```sh 146 | cd ./ui-tests 147 | PWDEBUG=1 jlpm playwright test 148 | ``` 149 | -------------------------------------------------------------------------------- /ui-tests/jupyter_server_test_config.py: -------------------------------------------------------------------------------- 1 | """Server configuration for integration tests. 2 | 3 | !! Never use this configuration in production because it 4 | opens the server to the world and provide access to JupyterLab 5 | JavaScript objects through the global window variable. 6 | """ 7 | from tempfile import mkdtemp 8 | 9 | c.ServerApp.port = 8888 10 | c.ServerApp.port_retries = 0 11 | c.ServerApp.open_browser = False 12 | 13 | c.ServerApp.root_dir = mkdtemp(prefix='galata-test-') 14 | c.ServerApp.token = "" 15 | c.ServerApp.password = "" 16 | c.ServerApp.disable_check_xsrf = True 17 | c.LabApp.expose_app_in_browser = True 18 | 19 | # Uncomment to set server log level to debug level 20 | # c.ServerApp.log_level = "DEBUG" 21 | -------------------------------------------------------------------------------- /ui-tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "310_notebook-ui-tests", 3 | "version": "1.0.0", 4 | "description": "JupyterLab 310_notebook Integration Tests", 5 | "private": true, 6 | "scripts": { 7 | "start": "jupyter lab --config jupyter_server_test_config.py", 8 | "test": "jlpm playwright test" 9 | }, 10 | "devDependencies": { 11 | "@jupyterlab/galata": "^4.3.0" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ui-tests/playwright.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Configuration for Playwright using default from @jupyterlab/galata 3 | */ 4 | const baseConfig = require('@jupyterlab/galata/lib/playwright-config'); 5 | 6 | module.exports = { 7 | ...baseConfig, 8 | webServer: { 9 | command: 'jlpm start', 10 | url: 'http://localhost:8888/lab', 11 | timeout: 120 * 1000, 12 | reuseExistingServer: !process.env.CI 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /ui-tests/tests/310_notebook.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@jupyterlab/galata'; 2 | 3 | /** 4 | * Don't load JupyterLab webpage before running the tests. 5 | * This is required to ensure we capture all log messages. 6 | */ 7 | test.use({ autoGoto: false }); 8 | 9 | test('should emit an activation console message', async ({ page }) => { 10 | const logs: string[] = []; 11 | 12 | page.on('console', message => { 13 | logs.push(message.text()); 14 | }); 15 | 16 | await page.goto(); 17 | 18 | expect( 19 | logs.filter(s => s === 'JupyterLab extension 310_notebook is activated!') 20 | ).toHaveLength(1); 21 | }); 22 | --------------------------------------------------------------------------------