Skip to content

Commit b72cd88

Browse files
authored
Merge pull request #1856 from yuvipanda/closure
Rewrite the frontend
2 parents 57e3b3a + 8679ba5 commit b72cd88

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+2038
-2367
lines changed

.eslintrc.js

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,48 @@
11
module.exports = {
22
env: {
33
browser: true,
4-
jquery: true,
5-
node: true,
6-
es6: true,
7-
"jest/globals": true,
8-
},
9-
extends: ["eslint:recommended"],
10-
ignorePatterns: ["**/dist"],
11-
parser: "@babel/eslint-parser",
12-
plugins: ["jest"],
13-
rules: {},
4+
es2021: true,
5+
},
6+
extends: ["eslint:recommended", "plugin:react/recommended"],
7+
ignorePatterns: ["dist"],
8+
overrides: [
9+
{
10+
env: {
11+
node: true,
12+
},
13+
files: [".eslintrc.{js,cjs}"],
14+
parserOptions: {
15+
sourceType: "script",
16+
},
17+
},
18+
{
19+
files: ["**/*.test.js", "**/*.test.jsx"],
20+
env: {
21+
jest: true,
22+
node: true,
23+
},
24+
},
25+
],
26+
parserOptions: {
27+
ecmaVersion: "latest",
28+
sourceType: "module",
29+
},
30+
plugins: ["react"],
31+
rules: {
32+
"react/react-in-jsx-scope": "off",
33+
"react/jsx-uses-react": "off",
34+
// Temporarily turn off prop-types
35+
"react/prop-types": "off",
36+
"no-unused-vars": ["error", { args: "after-used" }],
37+
},
38+
ignorePatterns: [
39+
"jupyterhub_fancy_profiles/static/*.js",
40+
"webpack.config.js",
41+
"babel.config.js",
42+
],
43+
settings: {
44+
react: {
45+
version: "detect",
46+
},
47+
},
1448
};

.github/workflows/test.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,9 @@ jobs:
173173
pip install --no-binary pycurl -r dev-requirements.txt -r helm-chart/images/binderhub/requirements.txt
174174
pip install -e .
175175
176+
- name: Install Playwright browser
177+
run: playwright install firefox
178+
176179
- name: Install JupyterHub chart for main tests
177180
if: matrix.test == 'main'
178181
run: |
@@ -434,6 +437,9 @@ jobs:
434437
pip install ".[pycurl]" --no-binary pycurl
435438
pip install -e ".[pycurl]" --no-binary pycurl
436439
440+
- name: Install Playwright browser
441+
run: playwright install firefox
442+
437443
- name: Setup JupyterHub NPM dependencies
438444
run: npm install -g configurable-http-proxy
439445

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ coverage.xml
8181
*.py,cover
8282
.hypothesis/
8383
.pytest_cache/
84+
coverage
8485

8586
# Translations
8687
*.mo

babel.config.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
{
2-
"presets": ["@babel/preset-env"]
2+
"presets": [
3+
"@babel/preset-env",
4+
["@babel/preset-react", { "runtime": "automatic" }]
5+
]
36
}

binderhub/app.py

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,15 @@
4242
)
4343
from traitlets.config import Application
4444

45-
from .base import AboutHandler, Custom404, VersionHandler
45+
from .base import VersionHandler
4646
from .build import BuildExecutor, KubernetesBuildExecutor, KubernetesCleaner
4747
from .builder import BuildHandler
48-
from .config import ConfigHandler
4948
from .events import EventLog
49+
from .handlers.repoproviders import RepoProvidersHandlers
5050
from .health import HealthHandler, KubernetesHealthHandler
5151
from .launcher import Launcher
5252
from .log import log_request
53-
from .main import LegacyRedirectHandler, MainHandler, ParameterizedMainHandler
53+
from .main import LegacyRedirectHandler, RepoLaunchUIHandler, UIHandler
5454
from .metrics import MetricsHandler
5555
from .quota import KubernetesLaunchQuota, LaunchQuota
5656
from .ratelimit import RateLimiter
@@ -107,6 +107,11 @@ def _log_level(self):
107107
None,
108108
allow_none=True,
109109
help="""
110+
..removed::
111+
112+
No longer supported. If you want to use Google Analytics, use :attr:`extra_footer_scripts`
113+
to load JS from Google Analytics.
114+
110115
The Google Analytics code to use on the main page.
111116
112117
Note that we'll respect Do Not Track settings, despite the fact that GA does not.
@@ -118,6 +123,11 @@ def _log_level(self):
118123
google_analytics_domain = Unicode(
119124
"auto",
120125
help="""
126+
..removed::
127+
128+
No longer supported. If you want to use Google Analytics, use :attr:`extra_footer_scripts`
129+
to load JS from Google Analytics.
130+
121131
The Google Analytics domain to use on the main page.
122132
123133
By default this is set to 'auto', which sets it up for current domain and all
@@ -126,6 +136,13 @@ def _log_level(self):
126136
config=True,
127137
)
128138

139+
@observe("google_analytics_domain", "google_analytics_code")
140+
def _google_analytics_deprecation(self, change):
141+
if change.new:
142+
raise ValueError(
143+
f"Setting {change.owner.__class__.__name__}.{change.name} is no longer supported. Use {change.owner.__class__.__name__}.extra_footer_scripts to load Google Analytics JS directly"
144+
)
145+
129146
about_message = Unicode(
130147
"",
131148
help="""
@@ -149,6 +166,14 @@ def _log_level(self):
149166
config=True,
150167
)
151168

169+
default_opengraph_title = Unicode(
170+
"The Binder Project",
171+
help="""
172+
The default opengraph title for pages that don't have a generated opengraph title.
173+
""",
174+
config=True,
175+
)
176+
152177
extra_footer_scripts = Dict(
153178
{},
154179
help="""
@@ -785,7 +810,6 @@ def _template_path_default(self):
785810
- /versions
786811
- /build/([^/]+)/(.+)
787812
- /health
788-
- /_config
789813
- /* -> shows a 404 page
790814
""",
791815
config=True,
@@ -913,6 +937,7 @@ def initialize(self, *args, **kwargs):
913937
"log_function": log_request,
914938
"image_prefix": self.image_prefix,
915939
"debug": self.debug,
940+
"default_opengraph_title": self.default_opengraph_title,
916941
"launcher": self.launcher,
917942
"ban_networks": self.ban_networks,
918943
"build_pool": self.build_pool,
@@ -931,8 +956,6 @@ def initialize(self, *args, **kwargs):
931956
"registry": registry,
932957
"traitlets_config": self.config,
933958
"traitlets_parent": self,
934-
"google_analytics_code": self.google_analytics_code,
935-
"google_analytics_domain": self.google_analytics_domain,
936959
"about_message": self.about_message,
937960
"banner_message": self.banner_message,
938961
"extra_footer_scripts": self.extra_footer_scripts,
@@ -961,15 +984,23 @@ def initialize(self, *args, **kwargs):
961984
(r"/versions", VersionHandler),
962985
(r"/build/([^/]+)/(.+)", BuildHandler),
963986
(r"/health", self.health_handler_class, {"hub_url": self.hub_url_local}),
964-
(r"/_config", ConfigHandler),
987+
(r"/api/repoproviders", RepoProvidersHandlers),
965988
]
966989
if not self.enable_api_only_mode:
967990
# In API only mode the endpoints in the list below
968-
# are unregistered as they don't make sense in a API only scenario
991+
# are not registered since they are primarily about providing UI
992+
993+
for provider_id in self.repo_providers:
994+
# Register launchable URLs for all our repo providers
995+
# These render social previews, but otherwise redirect to UIHandler
996+
handlers += [
997+
(
998+
rf"/v2/({provider_id})/(.+)",
999+
RepoLaunchUIHandler,
1000+
{"repo_provider": self.repo_providers[provider_id]},
1001+
)
1002+
]
9691003
handlers += [
970-
(r"/about", AboutHandler),
971-
(r"/v2/([^/]+)/(.+)", ParameterizedMainHandler),
972-
(r"/", MainHandler),
9731004
(r"/repo/([^/]+)/([^/]+)(/.*)?", LegacyRedirectHandler),
9741005
# for backward-compatible mybinder.org badge URLs
9751006
# /assets/images/badge.svg
@@ -1036,9 +1067,8 @@ def initialize(self, *args, **kwargs):
10361067
)
10371068
},
10381069
),
1070+
(r"/.*", UIHandler),
10391071
]
1040-
# This needs to be the last handler in the list, because it needs to match "everything else"
1041-
handlers.append((r".*", Custom404))
10421072
handlers = self.add_url_prefix(self.base_url, handlers)
10431073
if self.extra_static_path:
10441074
handlers.insert(

binderhub/base.py

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import json
44
import urllib.parse
5-
from http.client import responses
65

76
import jwt
87
from jupyterhub.services.auth import HubOAuth, HubOAuthenticated
@@ -204,47 +203,10 @@ def extract_message(self, exc_info):
204203
except Exception:
205204
return ""
206205

207-
def write_error(self, status_code, **kwargs):
208-
exc_info = kwargs.get("exc_info")
209-
message = ""
210-
status_message = responses.get(status_code, "Unknown HTTP Error")
211-
if exc_info:
212-
message = self.extract_message(exc_info)
213-
214-
self.render_template(
215-
"error.html",
216-
status_code=status_code,
217-
status_message=status_message,
218-
message=message,
219-
)
220-
221206
def options(self, *args, **kwargs):
222207
pass
223208

224209

225-
class Custom404(BaseHandler):
226-
"""Raise a 404 error, rendering the error.html template"""
227-
228-
def prepare(self):
229-
raise web.HTTPError(404)
230-
231-
232-
class AboutHandler(BaseHandler):
233-
"""Serve the about page"""
234-
235-
async def get(self):
236-
self.render_template(
237-
"about.html",
238-
base_url=self.settings["base_url"],
239-
submit=False,
240-
binder_version=binder_version,
241-
message=self.settings["about_message"],
242-
google_analytics_code=self.settings["google_analytics_code"],
243-
google_analytics_domain=self.settings["google_analytics_domain"],
244-
extra_footer_scripts=self.settings["extra_footer_scripts"],
245-
)
246-
247-
248210
class VersionHandler(BaseHandler):
249211
"""Serve information about versions running"""
250212

binderhub/handlers/__init__.py

Whitespace-only changes.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import json
2+
3+
from ..base import BaseHandler
4+
5+
6+
class RepoProvidersHandlers(BaseHandler):
7+
"""Serve config"""
8+
9+
async def get(self):
10+
config = [
11+
repo_provider_class.display_config
12+
for repo_provider_class in self.settings["repo_providers"].values()
13+
]
14+
self.set_header("Content-type", "application/json")
15+
self.write(json.dumps(config))

0 commit comments

Comments
 (0)