diff --git a/master/buildbot/test/unit/www/test_config.py b/master/buildbot/test/unit/www/test_config.py
index bf477a93c696..1a6b0de230fa 100644
--- a/master/buildbot/test/unit/www/test_config.py
+++ b/master/buildbot/test/unit/www/test_config.py
@@ -17,8 +17,9 @@
import os
from unittest import mock
+from parameterized import parameterized
+
from twisted.internet import defer
-from twisted.python import log
from twisted.trial import unittest
from buildbot.test.reactor import TestReactorMixin
@@ -82,108 +83,7 @@ def test_render(self):
self.assertEqual(res, exp)
-class IndexResource(TestReactorMixin, www.WwwTestMixin, unittest.TestCase):
- def setUp(self):
- self.setup_test_reactor()
-
- @defer.inlineCallbacks
- def test_render(self):
- _auth = auth.NoAuth()
- _auth.maybeAutoLogin = mock.Mock()
-
- custom_versions = [['test compoent', '0.1.2'], ['test component 2', '0.2.1']]
-
- master = self.make_master(url='h:/a/b/', auth=_auth, versions=custom_versions)
- rsrc = config.IndexResource(master, "foo")
- rsrc.reconfigResource(master.config)
- rsrc.jinja = mock.Mock()
- template = mock.Mock()
- rsrc.jinja.get_template = lambda x: template
- template.render = lambda configjson, config, custom_templates: configjson
-
- vjson = [list(v) for v in config.get_environment_versions()] + custom_versions
-
- res = yield self.render_resource(rsrc, b'/')
- res = json.loads(bytes2unicode(res))
- _auth.maybeAutoLogin.assert_called_with(mock.ANY)
- exp = {
- "authz": {},
- "titleURL": "http://buildbot.net",
- "versions": vjson,
- "title": "Buildbot",
- "auth": {"name": "NoAuth"},
- "user": {"anonymous": True},
- "buildbotURL": "h:/a/b/",
- "multiMaster": False,
- "port": None,
- }
- self.assertEqual(res, exp)
-
- master.session.user_info = {"name": 'me', "email": 'me@me.org'}
- res = yield self.render_resource(rsrc, b'/')
- res = json.loads(bytes2unicode(res))
- exp = {
- "authz": {},
- "titleURL": "http://buildbot.net",
- "versions": vjson,
- "title": "Buildbot",
- "auth": {"name": "NoAuth"},
- "user": {"email": "me@me.org", "name": "me"},
- "buildbotURL": "h:/a/b/",
- "multiMaster": False,
- "port": None,
- }
- self.assertEqual(res, exp)
-
- master = self.make_master(url='h:/a/c/', auth=_auth, versions=custom_versions)
- rsrc.reconfigResource(master.config)
- res = yield self.render_resource(rsrc, b'/')
- res = json.loads(bytes2unicode(res))
- exp = {
- "authz": {},
- "titleURL": "http://buildbot.net",
- "versions": vjson,
- "title": "Buildbot",
- "auth": {"name": "NoAuth"},
- "user": {"anonymous": True},
- "buildbotURL": "h:/a/b/",
- "multiMaster": False,
- "port": None,
- }
- self.assertEqual(res, exp)
-
- def test_parseCustomTemplateDir(self):
- exp = {'views/builds.html': '
\n
'}
- try:
- # we make the test work if pypugjs is present or note
- # It is better than just skip if pypugjs is not there
- import pypugjs # pylint: disable=import-outside-toplevel
-
- [pypugjs]
- exp.update({
- 'plugin/views/plugin.html': ''
- })
- except ImportError:
- log.msg("Only testing html based template override")
- template_dir = os.path.join(
- os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'test_templates_dir'
- )
- master = self.make_master(url='h:/a/b/')
- rsrc = config.IndexResource(master, "foo")
- res = rsrc.parseCustomTemplateDir(template_dir)
- self.assertEqual(res, exp)
-
- def test_CustomTemplateDir(self):
- master = self.make_master(url='h:/a/b/')
- rsrc = config.IndexResource(master, "foo")
- master.config.www['custom_templates_dir'] = 'foo'
- rsrc.parseCustomTemplateDir = mock.Mock(return_value="returnvalue")
- rsrc.reconfigResource(master.config)
- self.assertNotIn('custom_templates_dir', rsrc.frontend_config)
- self.assertEqual('returnvalue', rsrc.custom_templates)
-
-
-class IndexResourceReactTest(TestReactorMixin, www.WwwTestMixin, unittest.TestCase):
+class IndexResourceTest(TestReactorMixin, www.WwwTestMixin, unittest.TestCase):
def setUp(self):
self.setup_test_reactor()
@@ -216,19 +116,29 @@ def extract_config_json(self, res):
config_json = config_json.strip(';').strip()
return json.loads(config_json)
+ @parameterized.expand([
+ ('anonymous_user', None, {'anonymous': True}),
+ (
+ 'logged_in_user',
+ {"name": 'me', "email": 'me@me.org'},
+ {"email": "me@me.org", "name": "me"},
+ ),
+ ])
@defer.inlineCallbacks
- def test_render(self):
+ def test_render(self, name, user_info, expected_user):
_auth = auth.NoAuth()
_auth.maybeAutoLogin = mock.Mock()
custom_versions = [['test compoent', '0.1.2'], ['test component 2', '0.2.1']]
master = self.make_master(url='h:/a/b/', auth=_auth, versions=custom_versions, plugins={})
+ if user_info is not None:
+ master.session.user_info = user_info
- # IndexResourceReact only uses static path to get index.html. In the source checkout
+ # IndexResource only uses static path to get index.html. In the source checkout
# index.html resides not in www/base/public but in www/base. Thus
- # base path is sent to IndexResourceReact.
- rsrc = config.IndexResourceReact(master, self.get_react_base_path())
+ # base path is sent to IndexResource.
+ rsrc = config.IndexResource(master, self.get_react_base_path())
rsrc.reconfigResource(master.config)
vjson = [list(v) for v in config.get_environment_versions()] + custom_versions
@@ -243,7 +153,7 @@ def test_render(self):
"versions": vjson,
"title": "Buildbot",
"auth": {"name": "NoAuth"},
- "user": {"anonymous": True},
+ "user": expected_user,
"buildbotURL": "h:/a/b/",
"multiMaster": False,
"port": None,
diff --git a/master/buildbot/www/config.py b/master/buildbot/www/config.py
index 44c5d604aad8..31a5e236f189 100644
--- a/master/buildbot/www/config.py
+++ b/master/buildbot/www/config.py
@@ -16,11 +16,8 @@
import json
import os
-import posixpath
-import jinja2
from twisted.internet import defer
-from twisted.python import log
from twisted.web.error import Error
from buildbot.interfaces import IConfigured
@@ -144,91 +141,6 @@ class IndexResource(resource.Resource):
# enable reconfigResource calls
needsReconfig = True
- def __init__(self, master, staticdir):
- super().__init__(master)
- loader = jinja2.FileSystemLoader(staticdir)
- self.jinja = jinja2.Environment(loader=loader, undefined=jinja2.StrictUndefined)
-
- def reconfigResource(self, new_config):
- self.config = new_config.www
- self.frontend_config = get_www_frontend_config_dict(self.master, self.config)
-
- self.custom_templates = {}
- template_dir = self.config.get('custom_templates_dir', None)
- if template_dir is not None:
- template_dir = os.path.join(self.master.basedir, template_dir)
- self.custom_templates = self.parseCustomTemplateDir(template_dir)
-
- def render_GET(self, request):
- return self.asyncRenderHelper(request, self.renderIndex)
-
- def parseCustomTemplateDir(self, template_dir):
- res = {}
- allowed_ext = [".html"]
- try:
- import pypugjs # pylint: disable=import-outside-toplevel
-
- allowed_ext.append(".jade")
- except ImportError: # pragma: no cover
- log.msg(f"pypugjs not installed. Ignoring .jade files from {template_dir}")
- pypugjs = None
- for root, _, files in os.walk(template_dir):
- if root == template_dir:
- template_name = posixpath.join("views", "%s.html")
- else:
- # template_name is a url, so we really want '/'
- # root is a os.path, though
- template_name = posixpath.join(os.path.basename(root), "views", "%s.html")
- for f in files:
- fn = os.path.join(root, f)
- basename, ext = os.path.splitext(f)
- if ext not in allowed_ext:
- continue
- html = None
- if ext == ".html":
- with open(fn, encoding='utf-8') as f:
- html = f.read().strip()
- elif ext == ".jade":
- with open(fn, encoding='utf-8') as f:
- jade = f.read()
- parser = pypugjs.parser.Parser(jade)
- block = parser.parse()
- compiler = pypugjs.ext.html.Compiler(block, pretty=False)
- html = compiler.compile()
- res[template_name % (basename,)] = html
-
- return res
-
- @defer.inlineCallbacks
- def renderIndex(self, request):
- config = {}
- request.setHeader(b"content-type", b'text/html')
- request.setHeader(b"Cache-Control", b"public,max-age=0")
-
- try:
- yield self.config['auth'].maybeAutoLogin(request)
- except Error as e:
- config["on_load_warning"] = e.message
-
- config.update(self.frontend_config)
- config.update({"user": self.master.www.getUserInfos(request)})
-
- tpl = self.jinja.get_template('index.html')
- # we use Jinja in order to render some server side dynamic stuff
- # For example, custom_templates javascript is generated by the
- # layout.jade jinja template
- tpl = tpl.render(
- configjson=serialize_www_frontend_config_dict_to_json(config),
- custom_templates=self.custom_templates,
- config=self.config,
- )
- return unicode2bytes(tpl, encoding='ascii')
-
-
-class IndexResourceReact(resource.Resource):
- # enable reconfigResource calls
- needsReconfig = True
-
def __init__(self, master, staticdir):
super().__init__(master)
self.static_dir = staticdir
diff --git a/master/buildbot/www/service.py b/master/buildbot/www/service.py
index cf78b427f575..9635ff40ce72 100644
--- a/master/buildbot/www/service.py
+++ b/master/buildbot/www/service.py
@@ -321,20 +321,10 @@ def setupSite(self, new_config):
root = self.apps.get(self.base_plugin_name).resource
self.configPlugins(root, new_config)
# /
- if self.base_plugin_name == 'base':
- root.putChild(
- b'',
- wwwconfig.IndexResource(
- self.master, self.apps.get(self.base_plugin_name).static_dir
- ),
- )
- else:
- root.putChild(
- b'',
- wwwconfig.IndexResourceReact(
- self.master, self.apps.get(self.base_plugin_name).static_dir
- ),
- )
+ root.putChild(
+ b'',
+ wwwconfig.IndexResource(self.master, self.apps.get(self.base_plugin_name).static_dir),
+ )
# /auth
root.putChild(b'auth', auth.AuthRootResource(self.master))
diff --git a/master/docs/spelling_wordlist.txt b/master/docs/spelling_wordlist.txt
index ff2685520c76..2a7e955ddb23 100644
--- a/master/docs/spelling_wordlist.txt
+++ b/master/docs/spelling_wordlist.txt
@@ -884,6 +884,7 @@ tgrid
tgz
tls
th
+theming
timestamp
Todo
tokenUri
diff --git a/newsfragments/www-fix-broken-theming.bugfix b/newsfragments/www-fix-broken-theming.bugfix
new file mode 100644
index 000000000000..48bda1c92499
--- /dev/null
+++ b/newsfragments/www-fix-broken-theming.bugfix
@@ -0,0 +1 @@
+Fix broken theming in web frontend when not using it via `base_react` plugin name.
diff --git a/requirements-ci.txt b/requirements-ci.txt
index 90349ee444e7..87719424341b 100644
--- a/requirements-ci.txt
+++ b/requirements-ci.txt
@@ -2,7 +2,7 @@
-e worker
-e pkg
# we install buildbot www from pypi to avoid the slow nodejs build at each test
-buildbot-www==3.11.3
+buildbot-www==4.0.0
autobahn==23.6.2; python_version >= "3.9"
autobahn==22.7.1; python_version < "3.9" # pyup: ignore