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

Open
wants to merge 44 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
44 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
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 @@ -107,6 +107,11 @@ def _log_level(self):
None,
allow_none=True,
help="""
..deprecated::

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 @@ -118,6 +123,11 @@ def _log_level(self):
google_analytics_domain = Unicode(
"auto",
help="""
..deprecated::

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 @@ -126,6 +136,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 @@ -798,7 +815,6 @@ def _template_path_default(self):
- /versions
- /build/([^/]+)/(.+)
- /health
- /_config
- /* -> shows a 404 page
""",
config=True,
Expand Down Expand Up @@ -945,8 +961,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 @@ -975,16 +989,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))
129 changes: 18 additions & 111 deletions binderhub/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,128 +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",
"ckan": "CKAN",
}


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