From 2eece4f458297a52bd4e1952e5cae5a56369d9c6 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 2 May 2024 18:55:14 -0500 Subject: [PATCH] gh-1418: swagger-ui-dist strawman --- .gitignore | 1 + jupyter_server/services/api/handlers.py | 13 ++++++++++++ jupyter_server/templates/apidocs.html | 27 +++++++++++++++++++++++++ package-lock.json | 13 +++++++++++- package.json | 7 +++++-- pyproject.toml | 11 ++++++++-- tests/base/test_handlers.py | 10 +++++++++ 7 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 jupyter_server/templates/apidocs.html diff --git a/.gitignore b/.gitignore index 04ef5c46fa..5cab6cc5c7 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ docs/gh-pages jupyter_server/i18n/*/LC_MESSAGES/*.mo jupyter_server/i18n/*/LC_MESSAGES/nbjs.json jupyter_server/static/style/*.min.css* +jupyter_server/static/vendor/ node_modules *.py[co] __pycache__ diff --git a/jupyter_server/services/api/handlers.py b/jupyter_server/services/api/handlers.py index 22904fdb07..3d2703ea88 100644 --- a/jupyter_server/services/api/handlers.py +++ b/jupyter_server/services/api/handlers.py @@ -43,6 +43,18 @@ def get_content_type(self): return "text/x-yaml" +class APIDocsHandler(JupyterHandler): + """A handler for the documentation of the REST API.""" + + auth_resource = AUTH_RESOURCE + + @web.authenticated + @authorized + async def get(self): + """Get the REST API documentation.""" + return self.finish(self.render_template("apidocs.html")) + + class APIStatusHandler(APIHandler): """An API status handler.""" @@ -115,6 +127,7 @@ async def get(self): default_handlers = [ + (r"/api/apidocs", APIDocsHandler), (r"/api/spec.yaml", APISpecHandler), (r"/api/status", APIStatusHandler), (r"/api/me", IdentityHandler), diff --git a/jupyter_server/templates/apidocs.html b/jupyter_server/templates/apidocs.html new file mode 100644 index 0000000000..9d4e761935 --- /dev/null +++ b/jupyter_server/templates/apidocs.html @@ -0,0 +1,27 @@ +{% extends "page.html" %} + +{% block stylesheet %} + {{ super() }} + + +{% endblock stylesheet %} + +{% block site %} +
+{% endblock site %} + +{% block script %} + {{ super() }} + + + +{% endblock script %} diff --git a/package-lock.json b/package-lock.json index 546528db85..d34b816d1f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,8 @@ "license": "BSD", "dependencies": { "bootstrap": "^3.4.0", - "copyfiles": "^2.4.1" + "copyfiles": "^2.4.1", + "swagger-ui-dist": "^5.17.2" } }, "node_modules/ansi-regex": { @@ -288,6 +289,11 @@ "node": ">=8" } }, + "node_modules/swagger-ui-dist": { + "version": "5.17.2", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.17.2.tgz", + "integrity": "sha512-V/NqUw6QoTrjSpctp2oLQvxrl3vW29UsUtZyq7B1CF0v870KOFbYGDQw8rpKaKm0JxTwHpWnW1SN9YuKZdiCyw==" + }, "node_modules/through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -621,6 +627,11 @@ "ansi-regex": "^5.0.0" } }, + "swagger-ui-dist": { + "version": "5.17.2", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.17.2.tgz", + "integrity": "sha512-V/NqUw6QoTrjSpctp2oLQvxrl3vW29UsUtZyq7B1CF0v870KOFbYGDQw8rpKaKm0JxTwHpWnW1SN9YuKZdiCyw==" + }, "through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", diff --git a/package.json b/package.json index 78c0dce49a..139b1a91b3 100644 --- a/package.json +++ b/package.json @@ -4,11 +4,14 @@ "version": "1.0.0", "license": "BSD", "scripts": { - "build": "copyfiles -f node_modules/bootstrap/dist/css/*.min.* jupyter_server/static/style" + "build": "npm run build:bootstrap && npm run build:swagger-ui", + "build:bootstrap": "copyfiles -f node_modules/bootstrap/dist/css/*.min.* jupyter_server/static/style", + "build:swagger-ui": "copyfiles -f \"node_modules/swagger-ui-dist/{NOTICE,LICENSE}\" \"node_modules/swagger-ui-dist/swagger-ui{.css,-bundle.js}\" jupyter_server/static/vendor/swagger-ui" }, "dependencies": { "bootstrap": "^3.4.0", - "copyfiles": "^2.4.1" + "copyfiles": "^2.4.1", + "swagger-ui-dist": "^5.17.2" }, "eslintIgnore": [ "*.min.js", diff --git a/pyproject.toml b/pyproject.toml index 42350ed474..f4908ad99b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -133,9 +133,16 @@ dependencies = ["hatch-jupyter-builder>=0.8.1"] build-function = "hatch_jupyter_builder.npm_builder" ensured-targets = [ "jupyter_server/static/style/bootstrap.min.css", - "jupyter_server/static/style/bootstrap-theme.min.css" + "jupyter_server/static/style/bootstrap-theme.min.css", + "jupyter_server/static/vendor/swagger-ui/dist/dist/swagger-ui.css", + "jupyter_server/static/vendor/swagger-ui/dist/swagger-ui-bundle.js", + "jupyter_server/static/vendor/swagger-ui/LICENSE", + "jupyter_server/static/vendor/swagger-ui/NOTICE", +] +skip-if-exists = [ + "jupyter_server/static/style/bootstrap.min.css", + "jupyter_server/static/vendor/swagger-ui/dist/swagger-ui-bundle.js", ] -skip-if-exists = ["jupyter_server/static/style/bootstrap.min.css"] install-pre-commit-hook = true optional-editable-build = true diff --git a/tests/base/test_handlers.py b/tests/base/test_handlers.py index 6d8dce90da..6ef460c484 100644 --- a/tests/base/test_handlers.py +++ b/tests/base/test_handlers.py @@ -13,6 +13,7 @@ from jupyter_server.auth.decorator import allow_unauthenticated from jupyter_server.base.handlers import ( APIHandler, + APIDocsHandler, APIVersionHandler, AuthenticatedFileHandler, AuthenticatedHandler, @@ -238,6 +239,15 @@ async def test_api_version_handler(jp_serverapp): assert handler.get_status() == 200 +async def test_api_docs_handler(jp_serverapp): + app: ServerApp = jp_serverapp + request = HTTPRequest("GET") + request.connection = MagicMock() + handler = APIDocsHandler(app.web_app, request) + handler.get() + assert handler.get_status() == 200 + + async def test_files_redirect_handler(jp_serverapp): app: ServerApp = jp_serverapp request = HTTPRequest("GET")