Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite the frontend #1856

Merged
merged 99 commits into from
Jan 3, 2025
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
99 commits
Select commit Hold shift + click to select a range
31910bc
Rewrite the frontend completely
yuvipanda May 15, 2024
5c1183f
Split homepage into its own file
yuvipanda May 17, 2024
2e2a3ea
Add a loading page
yuvipanda May 18, 2024
55e0a41
Add nbviewer support
yuvipanda May 18, 2024
bb480e8
Re-implement the loader animation & text
yuvipanda May 18, 2024
f63e9b9
Update favicon correctly based on phases
yuvipanda May 18, 2024
ea7dbe7
Remove and cleanup unused stuff
yuvipanda May 18, 2024
abbf864
Add more type annotations
yuvipanda May 18, 2024
5661f17
Fix a few more typescript detected errors
yuvipanda May 18, 2024
b02256e
Don't enforce strict type checks yet
yuvipanda May 18, 2024
ffb3884
Create a central spec class
yuvipanda May 18, 2024
d6b549f
Document & rename spec class to be better
yuvipanda May 23, 2024
5da4111
Pass launchSpec correctly
yuvipanda May 23, 2024
23e491e
Remove GA code, add back extra_footer_scripts
yuvipanda May 23, 2024
b5c1c38
Move redirect calculation to Spec object
yuvipanda May 24, 2024
1bc530a
Remove unused static font
yuvipanda May 24, 2024
e4becd7
Move about page to frontend rendering
yuvipanda May 24, 2024
5a6adac
Fix case of component file
yuvipanda May 29, 2024
354118a
Remove remaining reference to `_config`
yuvipanda May 29, 2024
484680b
Support setting banner message
yuvipanda Jul 1, 2024
3e31936
Merge remote-tracking branch 'upstream/main' into closure
yuvipanda Jul 12, 2024
0ad9347
Remove unused functionality from binderhub-client
yuvipanda Jul 12, 2024
ea9d565
Add functionality to view logs in raw form
yuvipanda Jul 31, 2024
2c9d89f
Kill some unnecessary useEffects
yuvipanda Aug 1, 2024
7c55383
Add badge generator
yuvipanda Aug 2, 2024
c65214d
Merge remote-tracking branch 'upstream/main' into closure
yuvipanda Aug 2, 2024
c46a691
Add OpenGraph social card support
yuvipanda Aug 3, 2024
f942938
Fix copy buttons
yuvipanda Aug 3, 2024
ab59cb1
Tweak style of badge generator a little
yuvipanda Aug 3, 2024
97f1bfd
Fix lint
oliverroick Nov 21, 2024
e858fa8
Ignore spec.js in tests
oliverroick Nov 21, 2024
a4733b4
gitignore coverage directory
oliverroick Nov 21, 2024
6193fb1
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 21, 2024
ae035cb
Add basic link-generator tests
oliverroick Nov 22, 2024
5b7329a
Fix repo-select input group layout
oliverroick Nov 22, 2024
ce6a4bc
Fix label alignment
oliverroick Nov 22, 2024
e70e260
Replace links with buttons in dropdowns
oliverroick Nov 22, 2024
729b563
Add Github and Zenodo test cases
oliverroick Nov 22, 2024
1d6e8d4
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 22, 2024
d92e6b7
Remove test for custom template
yuvipanda Nov 27, 2024
85113b0
Remove about handler test
yuvipanda Nov 27, 2024
28dc23c
Add router test for homepage and about page
oliverroick Nov 27, 2024
d8816cf
Remove template based custom error and 404 pages
yuvipanda Nov 27, 2024
ddf6371
Don't stop after only 2 test failures
yuvipanda Nov 27, 2024
54f6e04
Add Not Found page
oliverroick Nov 28, 2024
7c2035a
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 28, 2024
2c87b24
Run test_loading_page with Playwright
oliverroick Dec 5, 2024
cf1e6cc
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 5, 2024
cc4b551
s/status_code/status/ for playwright response object
yuvipanda Dec 6, 2024
22b8366
Remove excessive inline comments
yuvipanda Dec 7, 2024
74a71b7
Remove unnecessary pytest mark
yuvipanda Dec 7, 2024
26ec60f
Install firefox for tests, not chrome
yuvipanda Dec 7, 2024
422d383
Dynamically determine port binderhub listens on
yuvipanda Dec 7, 2024
50f2879
Merge branch 'binderhub-local' into closure
yuvipanda Dec 8, 2024
279e232
Fix tests to match rewrite
yuvipanda Dec 8, 2024
1965809
Add handling for misconfigured specs
oliverroick Dec 9, 2024
cc09f36
Remove encoded url from loading-page test
oliverroick Dec 9, 2024
cf6a2ee
Verify by matching URL
oliverroick Dec 9, 2024
55d4318
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 9, 2024
3ee5069
Remove 'some errors disappear' error
yuvipanda Dec 9, 2024
f26d970
Merge remote-tracking branch 'upstream/main' into closure
yuvipanda Dec 9, 2024
a6522be
Add note about nbviewer URL
yuvipanda Dec 9, 2024
d16f2ea
Support build tokens
yuvipanda Dec 9, 2024
aa19f64
Handle situation where ref isn't enabled
yuvipanda Dec 12, 2024
a947c82
Validate spec for each repo provider separately
yuvipanda Dec 12, 2024
78bcdbf
Fix JS unit tests
oliverroick Dec 12, 2024
771b52b
Add info about wether repo should be urlencoded or not
yuvipanda Dec 12, 2024
8d0f089
Switch from react-router v6 to wouter
yuvipanda Dec 12, 2024
424f831
Generate build tokens correctly
yuvipanda Dec 12, 2024
ba212f5
Fix *some* jest tests
yuvipanda Dec 13, 2024
2f2b327
Fix route regex in Jest config
oliverroick Dec 13, 2024
b30a9e9
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 13, 2024
bded394
Remove jquery
yuvipanda Dec 13, 2024
352e4c1
Move chp to a dev dependency
yuvipanda Dec 13, 2024
42708a3
Upgrade version of xtermjs
yuvipanda Dec 13, 2024
ac62ba7
Remove unused 'clipboard' library
yuvipanda Dec 14, 2024
49d0f86
Write a clearer description for package.json
yuvipanda Dec 14, 2024
c089327
Mark the binderhub package itself as private
yuvipanda Dec 14, 2024
0fe56ae
Move react types to dev dependencies
yuvipanda Dec 14, 2024
73cc8f4
Upgrade version of react & related packages
yuvipanda Dec 14, 2024
dcd6bb1
Clarify Gist is GitHub Gist
yuvipanda Dec 14, 2024
9a73e00
Remove unused display_name property
yuvipanda Dec 14, 2024
4d670a5
Fix typo
yuvipanda Dec 14, 2024
b498b23
Fix typo
yuvipanda Dec 14, 2024
1927d34
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 14, 2024
b2dfd27
Fix off by 1 error with help messages
yuvipanda Dec 14, 2024
f379956
Remove debugging message
yuvipanda Dec 14, 2024
0fd5bb1
Add thicker borders to some controls
yuvipanda Dec 15, 2024
7276451
Make form background darker
yuvipanda Dec 15, 2024
3c895d9
Cleanup padding and rounded corners
yuvipanda Dec 15, 2024
2480656
Widen the form
yuvipanda Dec 16, 2024
b185b66
Clarify what binderhub supports for hydroshare
yuvipanda Dec 21, 2024
e117f39
Provide outline for buttons
yuvipanda Dec 21, 2024
7e0d5fb
Clarify what happens to the GA traitlets
yuvipanda Dec 21, 2024
e158610
Clarify what happens to the GA traitlets
yuvipanda Dec 21, 2024
acdc9fe
Turn tsc checking on (but don't enforce it)
yuvipanda Dec 21, 2024
54b6c19
Don't check js in our tsconfig
yuvipanda Dec 21, 2024
fbeb074
Revert adding noEmit
yuvipanda Dec 21, 2024
8679ba5
Make default opengraph title configurable
yuvipanda Dec 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 36 additions & 10 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,40 @@
module.exports = {
env: {
browser: true,
jquery: true,
node: true,
es6: true,
"jest/globals": true,
},
extends: ["eslint:recommended"],
ignorePatterns: ["**/dist"],
parser: "@babel/eslint-parser",
plugins: ["jest"],
rules: {},
es2021: true,
},
extends: ["eslint:recommended", "plugin:react/recommended"],
overrides: [
{
env: {
node: true,
},
files: [".eslintrc.{js,cjs}"],
parserOptions: {
sourceType: "script",
},
},
],
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
},
plugins: ["react"],
rules: {
"react/react-in-jsx-scope": "off",
"react/jsx-uses-react": "off",
// Temporarily turn off prop-types
"react/prop-types": "off",
"no-unused-vars": ["error", { args: "after-used" }],
},
ignorePatterns: [
"jupyterhub_fancy_profiles/static/*.js",
"webpack.config.js",
"babel.config.js",
],
settings: {
react: {
version: "detect",
},
},
};
5 changes: 4 additions & 1 deletion babel.config.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
{
"presets": ["@babel/preset-env"]
"presets": [
"@babel/preset-env",
["@babel/preset-react", { "runtime": "automatic" }]
]
}
32 changes: 22 additions & 10 deletions binderhub/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,15 @@
)
from traitlets.config import Application

from .base import AboutHandler, Custom404, VersionHandler
from .base import Custom404, VersionHandler
from .build import BuildExecutor, KubernetesBuildExecutor, KubernetesCleaner
from .builder import BuildHandler
from .config import ConfigHandler
from .events import EventLog
from .handlers.repoproviders import RepoProvidersHandlers
from .health import HealthHandler, KubernetesHealthHandler
from .launcher import Launcher
from .log import log_request
from .main import LegacyRedirectHandler, MainHandler, ParameterizedMainHandler
from .main import LegacyRedirectHandler, MainHandler
from .metrics import MetricsHandler
from .quota import KubernetesLaunchQuota, LaunchQuota
from .ratelimit import RateLimiter
Expand Down Expand Up @@ -106,6 +106,11 @@ def _log_level(self):
None,
allow_none=True,
help="""
..deprecated::
yuvipanda marked this conversation as resolved.
Show resolved Hide resolved

No longer supported. If you want to use Google Analytics, use :attr:`extra_footer_scripts`
to load JS from Google Analytics.

The Google Analytics code to use on the main page.

Note that we'll respect Do Not Track settings, despite the fact that GA does not.
Expand All @@ -117,6 +122,11 @@ def _log_level(self):
google_analytics_domain = Unicode(
"auto",
help="""
..deprecated::
yuvipanda marked this conversation as resolved.
Show resolved Hide resolved

No longer supported. If you want to use Google Analytics, use :attr:`extra_footer_scripts`
to load JS from Google Analytics.

The Google Analytics domain to use on the main page.

By default this is set to 'auto', which sets it up for current domain and all
Expand All @@ -125,6 +135,13 @@ def _log_level(self):
config=True,
)

@observe("google_analytics_domain", "google_analytics_code")
def _google_analytics_deprecation(self, change):
if change.new:
raise ValueError(
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"
)

about_message = Unicode(
"",
help="""
Expand Down Expand Up @@ -796,7 +813,6 @@ def _template_path_default(self):
- /versions
- /build/([^/]+)/(.+)
- /health
- /_config
- /* -> shows a 404 page
""",
config=True,
Expand Down Expand Up @@ -943,8 +959,6 @@ def initialize(self, *args, **kwargs):
"registry": registry,
"traitlets_config": self.config,
"traitlets_parent": self,
"google_analytics_code": self.google_analytics_code,
"google_analytics_domain": self.google_analytics_domain,
"about_message": self.about_message,
"banner_message": self.banner_message,
"extra_footer_scripts": self.extra_footer_scripts,
Expand Down Expand Up @@ -973,16 +987,14 @@ def initialize(self, *args, **kwargs):
(r"/versions", VersionHandler),
(r"/build/([^/]+)/(.+)", BuildHandler),
(r"/health", self.health_handler_class, {"hub_url": self.hub_url_local}),
(r"/_config", ConfigHandler),
]
if not self.enable_api_only_mode:
# In API only mode the endpoints in the list below
# are unregistered as they don't make sense in a API only scenario
handlers += [
(r"/about", AboutHandler),
(r"/v2/([^/]+)/(.+)", ParameterizedMainHandler),
(r"/", MainHandler),
(r"/(?:v2/.*|about)?", MainHandler),
(r"/repo/([^/]+)/([^/]+)(/.*)?", LegacyRedirectHandler),
(r"/api/repoproviders", RepoProvidersHandlers),
# for backward-compatible mybinder.org badge URLs
# /assets/images/badge.svg
(
Expand Down
16 changes: 0 additions & 16 deletions binderhub/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,22 +229,6 @@ def prepare(self):
raise web.HTTPError(404)


class AboutHandler(BaseHandler):
"""Serve the about page"""

async def get(self):
self.render_template(
"about.html",
base_url=self.settings["base_url"],
submit=False,
binder_version=binder_version,
message=self.settings["about_message"],
google_analytics_code=self.settings["google_analytics_code"],
google_analytics_domain=self.settings["google_analytics_domain"],
extra_footer_scripts=self.settings["extra_footer_scripts"],
)


class VersionHandler(BaseHandler):
"""Serve information about versions running"""

Expand Down
Empty file added binderhub/handlers/__init__.py
Empty file.
15 changes: 15 additions & 0 deletions binderhub/handlers/repoproviders.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import json

from ..base import BaseHandler


class RepoProvidersHandlers(BaseHandler):
"""Serve config"""

async def get(self):
config = [
repo_provider_class.display_config
for repo_provider_class in self.settings["repo_providers"].values()
]
self.set_header("Content-type", "application/json")
self.write(json.dumps(config))
128 changes: 18 additions & 110 deletions binderhub/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,127 +2,35 @@
Main handler classes for requests
"""

import time
import urllib.parse

import jwt
from tornado.httpclient import AsyncHTTPClient, HTTPRequest
from tornado.httputil import url_concat
from tornado.log import app_log
from tornado.web import HTTPError, authenticated
from tornado.web import authenticated

from . import __version__ as binder_version
from .base import BaseHandler

SPEC_NAMES = {
"gh": "GitHub",
"gist": "Gist",
"gl": "GitLab",
"git": "Git repo",
"zenodo": "Zenodo",
"figshare": "Figshare",
"hydroshare": "Hydroshare",
"dataverse": "Dataverse",
}


class MainHandler(BaseHandler):
"""Main handler for requests"""

@authenticated
def get(self):
repoproviders_display_config = [
repo_provider_class.display_config
for repo_provider_class in self.settings["repo_providers"].values()
]
page_config = {
"baseUrl": self.settings["base_url"],
"badgeBaseUrl": self.get_badge_base_url(),
"logoUrl": self.static_url("logo.svg"),
"logoWidth": "320px",
"repoProviders": repoproviders_display_config,
"aboutMessage": self.settings["about_message"],
"bannerHtml": self.settings["banner_message"],
"binderVersion": binder_version,
}
self.render_template(
"index.html",
badge_base_url=self.get_badge_base_url(),
base_url=self.settings["base_url"],
submit=False,
google_analytics_code=self.settings["google_analytics_code"],
google_analytics_domain=self.settings["google_analytics_domain"],
extra_footer_scripts=self.settings["extra_footer_scripts"],
repo_providers=self.settings["repo_providers"],
)


class ParameterizedMainHandler(BaseHandler):
"""Main handler that allows different parameter settings"""

@authenticated
async def get(self, provider_prefix, _unescaped_spec):
prefix = "/v2/" + provider_prefix
spec = self.get_spec_from_request(prefix)
spec = spec.rstrip("/")
try:
self.get_provider(provider_prefix, spec=spec)
except HTTPError:
raise
except Exception as e:
app_log.error(
"Failed to construct provider for %s/%s",
provider_prefix,
spec,
)
# FIXME: 400 assumes it's the user's fault (?)
# maybe we should catch a special InvalidSpecError here
raise HTTPError(400, str(e))

provider_spec = f"{provider_prefix}/{spec}"
social_desc = f"{SPEC_NAMES[provider_prefix]}: {spec}"
nbviewer_url = None
if provider_prefix == "gh":
# We can only produce an nbviewer URL for github right now
nbviewer_url = "https://nbviewer.jupyter.org/github"
org, repo_name, ref = spec.split("/", 2)
# NOTE: tornado unquotes query arguments too -> notebooks%2Findex.ipynb becomes notebooks/index.ipynb
filepath = self.get_argument("labpath", "").lstrip("/")
if not filepath:
filepath = self.get_argument("filepath", "").lstrip("/")

# Check the urlpath parameter for a file path, if so use it for the filepath
urlpath = self.get_argument("urlpath", "").lstrip("/")
if urlpath and "/tree/" in urlpath:
filepath = urlpath.split("tree/", 1)[-1]

blob_or_tree = "blob" if filepath else "tree"
nbviewer_url = (
f"{nbviewer_url}/{org}/{repo_name}/{blob_or_tree}/{ref}/{filepath}"
)

# Check if the nbviewer URL is valid and would display something
# useful to the reader, if not we don't show it
client = AsyncHTTPClient()
# quote any unicode characters in the URL
proto, rest = nbviewer_url.split("://")
rest = urllib.parse.quote(rest)

request = HTTPRequest(
proto + "://" + rest,
method="HEAD",
user_agent="BinderHub",
)
response = await client.fetch(request, raise_error=False)
if response.code >= 400:
nbviewer_url = None

build_token = jwt.encode(
{
"exp": int(time.time()) + self.settings["build_token_expires_seconds"],
"aud": provider_spec,
"origin": self.token_origin(),
},
key=self.settings["build_token_secret"],
algorithm="HS256",
)
self.render_template(
"loading.html",
base_url=self.settings["base_url"],
badge_base_url=self.get_badge_base_url(),
build_token=build_token,
provider_spec=provider_spec,
social_desc=social_desc,
nbviewer_url=nbviewer_url,
# urlpath=self.get_argument('urlpath', None),
submit=True,
google_analytics_code=self.settings["google_analytics_code"],
google_analytics_domain=self.settings["google_analytics_domain"],
"page.html",
page_config=page_config,
extra_footer_scripts=self.settings["extra_footer_scripts"],
)

Expand Down
Loading
Loading