Skip to content

Commit db3b6c9

Browse files
committed
[ADD] Extend render_full_table with bump_policy visualization
closes ingadhoc#37 closes ingadhoc#38 Signed-off-by: Augusto Weiss <awe@adhoc.com.ar> Signed-off-by: Andrés Zacchino <az@adhoc.inc>
1 parent 8ab9e8b commit db3b6c9

File tree

7 files changed

+327
-9
lines changed

7 files changed

+327
-9
lines changed

runbot_merge_ux/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@
33
# directory
44
##############################################################################
55

6+
from . import controllers
67
from . import models
78
from .patch import post_load

runbot_merge_ux/__manifest__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"data": [
3232
"data/runbot_merge.pull_requests.feedback.template.csv",
3333
"views/runbot_merge_pull_requests_views.xml",
34+
"views/templates.xml",
3435
],
3536
"post_load": "post_load",
3637
"installable": False,
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
##############################################################################
2+
# For copyright and license notices, see __manifest__.py file in module root
3+
# directory
4+
##############################################################################
5+
6+
from . import dashboard
Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
##############################################################################
2+
# For copyright and license notices, see __manifest__.py file in module root
3+
# directory
4+
##############################################################################
5+
6+
import collections
7+
from itertools import chain
8+
9+
from odoo.addons.runbot_merge.controllers import dashboard
10+
from odoo.tools import file_open
11+
from PIL import Image, ImageDraw, ImageFont
12+
13+
# Import classes from the original module
14+
Cell = dashboard.Cell
15+
Line = dashboard.Line
16+
Lines = dashboard.Lines
17+
Text = dashboard.Text
18+
Checkbox = dashboard.Checkbox
19+
Decoration = dashboard.Decoration
20+
BG = dashboard.BG
21+
TEXT = dashboard.TEXT
22+
SUCCESS = dashboard.SUCCESS
23+
ERROR = dashboard.ERROR
24+
HORIZONTAL_PADDING = dashboard.HORIZONTAL_PADDING
25+
VERTICAL_PADDING = dashboard.VERTICAL_PADDING
26+
blend = dashboard.blend
27+
lighten = dashboard.lighten
28+
29+
30+
# Monkey patch render_full_table to add bump_policy visualization
31+
_original_render_full_table = dashboard.render_full_table
32+
33+
34+
def render_full_table_with_bump_policy(pr, branches, repos, batches): # noqa: C901
35+
"""Extended version of render_full_table that includes bump_policy information"""
36+
37+
with file_open("web/static/fonts/google/Open_Sans/Open_Sans-Regular.ttf", "rb") as f:
38+
font = ImageFont.truetype(f, size=16, layout_engine=0)
39+
f.seek(0)
40+
supfont = ImageFont.truetype(f, size=13, layout_engine=0)
41+
with file_open("web/static/fonts/google/Open_Sans/Open_Sans-Bold.ttf", "rb") as f:
42+
bold = ImageFont.truetype(f, size=16, layout_engine=0)
43+
with file_open("web/static/src/libs/fontawesome/fonts/fontawesome-webfont.ttf", "rb") as f:
44+
icons = ImageFont.truetype(f, size=16, layout_engine=0)
45+
46+
rowheights = collections.defaultdict(int)
47+
colwidths = collections.defaultdict(int)
48+
cells = {}
49+
for b in chain([None], branches):
50+
for r in chain([None], repos):
51+
opacity = 1.0 if b is None or b.active else 0.5
52+
current_row = b == pr.target
53+
background = BG["info"] if current_row or r == pr.repository else BG[None]
54+
55+
if b is None: # first row
56+
cell = Cell(Text("" if r is None else r.name, bold, TEXT), background)
57+
elif r is None: # first column
58+
cell = Cell(Text(b.name, font, blend(TEXT, opacity, over=background)), background)
59+
elif current_row:
60+
ps = batches[r, b]
61+
bgcolor = lighten(BG[ps["state"]], by=-0.05) if pr in ps["pr_ids"] else BG[ps["state"]]
62+
background = blend(bgcolor, opacity, over=background)
63+
foreground = blend((39, 110, 114), opacity, over=background)
64+
success = blend(SUCCESS, opacity, over=background)
65+
error = blend(ERROR, opacity, over=background)
66+
67+
boxes = {
68+
False: Checkbox(False, icons, foreground, success, error),
69+
True: Checkbox(True, icons, foreground, success, error),
70+
None: Checkbox(None, icons, foreground, success, error),
71+
}
72+
prs = []
73+
attached = True
74+
for p in ps["prs"]:
75+
pr = p["pr"]
76+
attached = attached and p["attached"]
77+
78+
if pr.staging_id:
79+
sub = ": is staged"
80+
elif pr.error:
81+
sub = ": staging failed"
82+
else:
83+
sub = ""
84+
85+
lines = [
86+
Line(
87+
[
88+
Text(
89+
f"#{p['number']}{sub}",
90+
font,
91+
foreground,
92+
decoration=Decoration.STRIKETHROUGH if p["closed"] else Decoration(0),
93+
)
94+
]
95+
),
96+
]
97+
98+
# Show bump status for merged PRs
99+
if pr.state == "merged" and pr.bump_status:
100+
status_color = success if pr.bump_status == "success" else error
101+
lines.append(
102+
Line(
103+
[
104+
Text(
105+
f" Bump status: {pr.bump_status}",
106+
supfont,
107+
status_color,
108+
)
109+
]
110+
)
111+
)
112+
113+
# no need for details if closed or in error
114+
if pr.state not in ("merged", "closed", "error") and not pr.staging_id:
115+
if pr.draft:
116+
lines.append(Line([boxes[False], Text("is in draft", font, error)]))
117+
118+
# Standard validations
119+
lines.extend(
120+
[
121+
Line(
122+
[
123+
boxes[bool(pr.squash or pr.merge_method)],
124+
Text(
125+
"merge method: {}".format(
126+
"single" if pr.squash else (pr.merge_method or "missing")
127+
),
128+
font,
129+
foreground if pr.squash or pr.merge_method else error,
130+
),
131+
]
132+
),
133+
Line(
134+
[
135+
boxes[bool(pr.reviewed_by)],
136+
Text(
137+
"Reviewed" if pr.reviewed_by else "Not Reviewed",
138+
font,
139+
foreground if pr.reviewed_by else error,
140+
),
141+
]
142+
),
143+
]
144+
)
145+
146+
# Add bump_policy check if the field exists
147+
bump_text = f": {pr.bump_policy}" if pr.bump_policy else ""
148+
lines.append(
149+
Line(
150+
[
151+
boxes[bool(pr.bump_policy)],
152+
Text(
153+
f"Bump policy{bump_text}",
154+
font,
155+
foreground if pr.bump_policy else error,
156+
),
157+
]
158+
)
159+
)
160+
# Show specific modules if defined
161+
if pr.bump_modules:
162+
lines.append(
163+
Line(
164+
[
165+
Text(
166+
f" modules: {pr.bump_modules}",
167+
supfont,
168+
foreground,
169+
)
170+
]
171+
)
172+
)
173+
174+
# CI check
175+
lines.append(
176+
Line(
177+
[
178+
boxes[pr.batch_id.skipchecks or pr.status == "success"],
179+
Text(
180+
"CI",
181+
font,
182+
foreground if pr.batch_id.skipchecks or pr.status == "success" else error,
183+
),
184+
]
185+
)
186+
)
187+
188+
if not pr.batch_id.skipchecks:
189+
import json
190+
191+
statuses = json.loads(pr.statuses_full)
192+
for ci in pr.repository.status_ids._for_pr(pr):
193+
if (status := statuses.get(ci.context.strip())) is None:
194+
if ci.prs != "required":
195+
continue
196+
status = {"state": "pending"}
197+
color = foreground
198+
match status["state"]:
199+
case "error" | "failure":
200+
color = error
201+
box = boxes[False]
202+
case "success":
203+
box = boxes[True]
204+
case _:
205+
box = boxes[None]
206+
207+
lines.append(
208+
Line(
209+
[
210+
Text(" - ", font, color),
211+
box,
212+
Text(f"{ci.repo_id.name}: {ci.context}", font, color),
213+
]
214+
)
215+
)
216+
prs.append(Lines(lines))
217+
cell = Cell(Line(prs), background, attached)
218+
else:
219+
ps = batches[r, b]
220+
bgcolor = lighten(BG[ps["state"]], by=-0.05) if pr in ps["pr_ids"] else BG[ps["state"]]
221+
background = blend(bgcolor, opacity, over=background)
222+
foreground = blend((39, 110, 114), opacity, over=background)
223+
224+
line = []
225+
attached = True
226+
for p in ps["prs"]:
227+
line.append(
228+
Text(
229+
f"#{p['number']}",
230+
font,
231+
foreground,
232+
decoration=Decoration.STRIKETHROUGH if p["closed"] else Decoration(0),
233+
)
234+
)
235+
attached = attached and p["attached"]
236+
for attribute in filter(
237+
None,
238+
[
239+
"error" if p["pr"].error else "",
240+
"" if p["checked"] else "missing statuses",
241+
"" if p["reviewed"] else "missing r+",
242+
"" if p["attached"] else "detached",
243+
"staged" if p["pr"].staging_id else "ready" if p["pr"]._ready else "",
244+
],
245+
):
246+
color = SUCCESS if attribute in ("staged", "ready") else ERROR
247+
line.append(Text(f" {attribute}", supfont, blend(color, opacity, over=background)))
248+
line.append(Text(" ", font, foreground))
249+
cell = Cell(Line(line), background, attached)
250+
251+
cells[r, b] = cell
252+
rowheights[b] = max(rowheights[b], cell.height)
253+
colwidths[r] = max(colwidths[r], cell.width)
254+
255+
im = Image.new("RGB", (sum(colwidths.values()), sum(rowheights.values())), "white")
256+
# no need to set the font here because every text element has its own
257+
draw = ImageDraw.Draw(im, "RGB")
258+
top = 0
259+
for b in chain([None], branches):
260+
left = 0
261+
for r in chain([None], repos):
262+
cell = cells[r, b]
263+
264+
# for a given cell, we first print the background, then the text, then
265+
# the borders
266+
# need to subtract 1 because pillow uses inclusive rect coordinates
267+
right = left + colwidths[r] - 1
268+
bottom = top + rowheights[b] - 1
269+
draw.rectangle(
270+
(left, top, right, bottom),
271+
cell.background,
272+
)
273+
# draw content adding padding
274+
cell.content.draw(draw, left=left + HORIZONTAL_PADDING, top=top + VERTICAL_PADDING)
275+
# draw bottom-right border
276+
draw.line(
277+
[
278+
(left, bottom),
279+
(right, bottom),
280+
(right, top),
281+
],
282+
fill=(172, 176, 170),
283+
)
284+
if not cell.attached:
285+
# overdraw previous cell's bottom border
286+
draw.line([(left, top - 1), (right - 1, top - 1)], fill=ERROR)
287+
288+
left += colwidths[r]
289+
top += rowheights[b]
290+
291+
return im
292+
293+
294+
# Apply the monkey patch
295+
dashboard.render_full_table = render_full_table_with_bump_policy

runbot_merge_ux/data/runbot_merge.pull_requests.feedback.template.csv

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,6 @@ id,template,help
22
runbot_merge_ux.pr.bump_policy,"{pr.ping}this PR cannot be staged without a bump policy. Please specify either `bump` or `nobump` in your comment.","Comment when a PR is ready but doesn't have a bump policy set
33
44
pr: the pr we can't stage"
5-
runbot_merge_ux.command.bump_policy,"Bump policy set to {new_policy}. Modules: {modules}","Responds to the setting of the bump policy.
6-
7-
new_policy: the bump policy that was set
8-
modules: optional comma-separated list of specific modules to bump"
95
runbot_merge_ux.command.version_bump_failure,":warning: {pr.ping} **Version Bump Failed**
106
117
The automatic version bump for this PR failed with the following error:

runbot_merge_ux/models/pull_requests.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,6 @@ def _parse_commands(self, author, comment, login):
115115
self.bump_modules = bump_modules
116116
# Reset warning flag when bump policy is set
117117
self.bump_warned = False
118-
self.env.ref("runbot_merge_ux.command.bump_policy")._send(
119-
repository=self.repository,
120-
pull_request=self.number,
121-
format_args={"new_policy": bump_setting, "modules": bump_modules or "all"},
122-
)
123118
# if the bump policy is the only thing preventing (but not
124119
# *blocking*) staging, trigger a staging
125120
if self.state == "ready":
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<odoo>
3+
<template id="view_pull_request_info_open" inherit_id="runbot_merge.view_pull_request_info_open">
4+
<xpath expr="//li[@t-att-class=&quot;'ok' if pr._approved else 'fail'&quot;]" position="after">
5+
<li t-att-class="'ok' if pr.bump_policy else 'fail'">
6+
Bump policy
7+
<t t-if="pr.bump_policy">: <t t-out="pr.bump_policy"/></t>
8+
<ul t-if="pr.bump_modules">
9+
<li>
10+
Modules: <t t-out="pr.bump_modules"/>
11+
</li>
12+
</ul>
13+
</li>
14+
</xpath>
15+
</template>
16+
17+
<template id="view_pull_request_info_merged" inherit_id="runbot_merge.view_pull_request_info_merged">
18+
<xpath expr="//div[@class='alert alert-success']" position="inside">
19+
<div t-if="pr.bump_status">
20+
<p>Bump status: <span t-out="pr.bump_status"/></p>
21+
</div>
22+
</xpath>
23+
</template>
24+
</odoo>

0 commit comments

Comments
 (0)