diff options
author | Tobias Henkel <tobias.henkel@bmw.de> | 2020-01-12 11:21:10 +0100 |
---|---|---|
committer | Tobias Henkel <tobias.henkel@bmw.de> | 2020-05-21 06:57:19 +0000 |
commit | 5350525e65ac6a6e848865480c45ccccaeb74a5e (patch) | |
tree | 986d2c3fe213d113f477a7103c635e276e0cac13 | |
parent | 551dbcbbc60edb4996563856e2b5f77d31b0bc4a (diff) | |
download | zuul-5350525e65ac6a6e848865480c45ccccaeb74a5e.tar.gz |
Support dynamic badges
Zuul currently has a zuul/gated badge that can be linked e.g. in a
readme of a project. This is sufficient for many use cases. However if
a project has periodic jobs that do extended testing which is not
possible in check/gate this is not sufficient. For use cases like
those we can add support for dynamic badges in zuul itself.
Change-Id: I449fa9f38ca251522789b6075fbc876d21bd0200
-rw-r--r-- | doc/source/howtos/badges.rst | 7 | ||||
-rw-r--r-- | releasenotes/notes/dynamic-badges-9b0e7a39e73775ba.yaml | 4 | ||||
-rw-r--r-- | tests/unit/test_web.py | 33 | ||||
-rw-r--r-- | web/public/failing.svg | 18 | ||||
-rw-r--r-- | web/public/openapi.yaml | 38 | ||||
-rw-r--r-- | web/public/passing.svg | 18 | ||||
-rwxr-xr-x | zuul/web/__init__.py | 23 |
7 files changed, 141 insertions, 0 deletions
diff --git a/doc/source/howtos/badges.rst b/doc/source/howtos/badges.rst index e6613dfc8..10ff36c1d 100644 --- a/doc/source/howtos/badges.rst +++ b/doc/source/howtos/badges.rst @@ -16,3 +16,10 @@ report, it is a simple static file: To use it, simply put ``https://zuul-ci.org/gated.svg`` into an RST or markdown formatted README file, or use it in an ``<img>`` tag in HTML. + +For advanced usage Zuul also supports generating dynamic badges via the +REST api. This can be useful if you want to display the status of e.g. periodic +pipelines of a project. To use it use an url like +``https://zuul.opendev.org/api/tenant/zuul/badge?project=zuul/zuul-website&pipeline=post`` +instead of the above mentioned url. It supports filtering by ``project``, +``pipeline`` and ``branch``. diff --git a/releasenotes/notes/dynamic-badges-9b0e7a39e73775ba.yaml b/releasenotes/notes/dynamic-badges-9b0e7a39e73775ba.yaml new file mode 100644 index 000000000..33a76488e --- /dev/null +++ b/releasenotes/notes/dynamic-badges-9b0e7a39e73775ba.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Support for generating dynamic :ref:`badges` has been added. diff --git a/tests/unit/test_web.py b/tests/unit/test_web.py index af968b1bc..381d4585a 100644 --- a/tests/unit/test_web.py +++ b/tests/unit/test_web.py @@ -1126,6 +1126,39 @@ class TestBuildInfo(ZuulDBTestCase, BaseTestWeb): resp = self.get_url("api/tenant/non-tenant/builds") self.assertEqual(404, resp.status_code) + def test_web_badge(self): + # Generate some build records in the db. + self.add_base_changes() + self.executor_server.hold_jobs_in_build = False + self.executor_server.release() + self.waitUntilSettled() + + # Now request badge for the buildsets + result = self.get_url("api/tenant/tenant-one/badge") + + self.log.error(result.content) + result.raise_for_status() + self.assertTrue(result.text.startswith('<svg ')) + self.assertIn('passing', result.text) + + # Generate a failing record + self.executor_server.hold_jobs_in_build = True + C = self.fake_gerrit.addFakeChange('org/project', 'master', 'C') + C.addApproval('Code-Review', 2) + self.fake_gerrit.addEvent(C.addApproval('Approved', 1)) + self.waitUntilSettled() + self.executor_server.failJob('project-merge', C) + self.executor_server.hold_jobs_in_build = False + self.executor_server.release() + self.waitUntilSettled() + + # Request again badge for the buildsets + result = self.get_url("api/tenant/tenant-one/badge") + self.log.error(result.content) + result.raise_for_status() + self.assertTrue(result.text.startswith('<svg ')) + self.assertIn('failing', result.text) + def test_web_list_buildsets(self): # Generate some build records in the db. self.add_base_changes() diff --git a/web/public/failing.svg b/web/public/failing.svg new file mode 100644 index 000000000..d6bc3cf95 --- /dev/null +++ b/web/public/failing.svg @@ -0,0 +1,18 @@ +<svg width="80.2" height="20" viewBox="0 0 802 200" xmlns="http://www.w3.org/2000/svg"> + <linearGradient id="a" x2="0" y2="100%"> + <stop offset="0" stop-opacity=".1" stop-color="#EEE"/> + <stop offset="1" stop-opacity=".1"/> + </linearGradient> + <mask id="m"><rect width="802" height="200" rx="30" fill="#FFF"/></mask> + <g mask="url(#m)"> + <rect width="368" height="200" fill="#555"/> + <rect width="434" height="200" fill="#E43" x="368"/> + <rect width="802" height="200" fill="url(#a)"/> + </g> + <g fill="#fff" text-anchor="start" font-family="Verdana,DejaVu Sans,sans-serif" font-size="110"> + <text x="60" y="148" textLength="268" fill="#000" opacity="0.25">build</text> + <text x="50" y="138" textLength="268">build</text> + <text x="423" y="148" textLength="334" fill="#000" opacity="0.25">failing</text> + <text x="413" y="138" textLength="334">failing</text> + </g> +</svg> diff --git a/web/public/openapi.yaml b/web/public/openapi.yaml index 4338e5a43..6313d1263 100644 --- a/web/public/openapi.yaml +++ b/web/public/openapi.yaml @@ -6,6 +6,44 @@ openapi: 3.0.0 tags: - name: tenant paths: + /api/tenant/{tenant}/badge: + get: + operationId: get-badge + parameters: + - description: The tenant name + in: path + name: tenant + required: true + schema: + type: string + - description: A project name + in: query + name: project + schema: + type: string + - description: A pipeline name + in: query + name: pipeline + schema: + type: string + - description: A branch name + in: query + name: branch + schema: + type: string + responses: + '200': + content: + image/svg+xml: + schema: + description: SVG image + type: object + description: Badge describing the result of the latest buildset found. + '404': + description: No buildset found + summary: Get a badge describing the result of the latest buildset found. + tags: + - tenant /api/tenant/{tenant}/builds: get: operationId: list-builds diff --git a/web/public/passing.svg b/web/public/passing.svg new file mode 100644 index 000000000..54cf11b43 --- /dev/null +++ b/web/public/passing.svg @@ -0,0 +1,18 @@ +<svg width="88.6" height="20" viewBox="0 0 886 200" xmlns="http://www.w3.org/2000/svg"> + <linearGradient id="a" x2="0" y2="100%"> + <stop offset="0" stop-opacity=".1" stop-color="#EEE"/> + <stop offset="1" stop-opacity=".1"/> + </linearGradient> + <mask id="m"><rect width="886" height="200" rx="30" fill="#FFF"/></mask> + <g mask="url(#m)"> + <rect width="368" height="200" fill="#555"/> + <rect width="518" height="200" fill="#3C1" x="368"/> + <rect width="886" height="200" fill="url(#a)"/> + </g> + <g fill="#fff" text-anchor="start" font-family="Verdana,DejaVu Sans,sans-serif" font-size="110"> + <text x="60" y="148" textLength="268" fill="#000" opacity="0.25">build</text> + <text x="50" y="138" textLength="268">build</text> + <text x="423" y="148" textLength="418" fill="#000" opacity="0.25">passing</text> + <text x="413" y="138" textLength="418">passing</text> + </g> +</svg> diff --git a/zuul/web/__init__.py b/zuul/web/__init__.py index 6336ac15f..9f5c504ca 100755 --- a/zuul/web/__init__.py +++ b/zuul/web/__init__.py @@ -529,6 +529,7 @@ class ZuulWebAPI(object): 'project_ssh_key': '/api/tenant/{tenant}/project-ssh-key/' '{project:.*}.pub', 'console_stream': '/api/tenant/{tenant}/console-stream', + 'badge': '/api/tenant/{tenant}/badge', 'builds': '/api/tenant/{tenant}/builds', 'build': '/api/tenant/{tenant}/build/{uuid}', 'buildsets': '/api/tenant/{tenant}/buildsets', @@ -929,6 +930,26 @@ class ZuulWebAPI(object): @cherrypy.expose @cherrypy.tools.save_params() + def badge(self, tenant, project=None, pipeline=None, branch=None): + connection = self._get_connection(tenant) + + buildsets = connection.getBuildsets( + tenant=tenant, project=project, pipeline=pipeline, + branch=branch, limit=1) + if not buildsets: + raise cherrypy.HTTPError(404, 'No buildset found') + + if buildsets[0].result == 'SUCCESS': + file = 'passing.svg' + else: + file = 'failing.svg' + path = os.path.join(self.zuulweb.static_path, file) + + return cherrypy.lib.static.serve_file( + path=path, content_type="image/svg+xml") + + @cherrypy.expose + @cherrypy.tools.save_params() @cherrypy.tools.json_out(content_type='application/json; charset=utf-8') def buildsets(self, tenant, project=None, pipeline=None, change=None, branch=None, patchset=None, ref=None, newrev=None, @@ -1179,6 +1200,8 @@ class ZuulWeb(object): controller=api, action='console_stream') route_map.connect('api', '/api/tenant/{tenant}/builds', controller=api, action='builds') + route_map.connect('api', '/api/tenant/{tenant}/badge', + controller=api, action='badge') route_map.connect('api', '/api/tenant/{tenant}/build/{uuid}', controller=api, action='build') route_map.connect('api', '/api/tenant/{tenant}/buildsets', |