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': '
this is customized
' - }) - 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