Merge ~andersson123/autopkgtest-cloud:user-specific-page into autopkgtest-cloud:master

Proposed by Tim Andersson
Status: Needs review
Proposed branch: ~andersson123/autopkgtest-cloud:user-specific-page
Merge into: autopkgtest-cloud:master
Diff against target: 472 lines (+346/-40)
6 files modified
charms/focal/autopkgtest-web/webcontrol/browse.cgi (+219/-0)
charms/focal/autopkgtest-web/webcontrol/helpers/utils.py (+5/-0)
charms/focal/autopkgtest-web/webcontrol/templates/browse-layout.html (+2/-0)
charms/focal/autopkgtest-web/webcontrol/templates/browse-results.html (+2/-40)
charms/focal/autopkgtest-web/webcontrol/templates/browse-user.html (+73/-0)
charms/focal/autopkgtest-web/webcontrol/templates/macros.html (+45/-0)
Reviewer Review Type Date Requested Status
Skia Needs Fixing
Review via email: mp+462713@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Tim Andersson (andersson123) wrote :

Needs:
- browse-user.html
- refactoring of the code used - it's somewhat inefficient
- testing

Revision history for this message
Tim Andersson (andersson123) wrote :

not sure the refactoring will be viable now, since the semantics of this page are different to other results pages.

This is also now tested in staging.

Revision history for this message
Tim Andersson (andersson123) :
Revision history for this message
Tim Andersson (andersson123) wrote :

This is all fully amended now and ready for re-review

Revision history for this message
Tim Andersson (andersson123) wrote :

all tested, see:
https://autopkgtest.staging.ubuntu.com/user/andersson123

Just need to maybe add a section that redirects to this on the navbar, not sure about that yet.

Revision history for this message
Tim Andersson (andersson123) wrote :

also need to add a button to go back to the top as well

Revision history for this message
Tim Andersson (andersson123) wrote :

Need to also look into having the requester as an index

Revision history for this message
Tim Andersson (andersson123) wrote :

session from request.cgi isn't shared with browse.cgi - so adding the button to the navbar isn't easy right now.

We need to add the secret key setup (and session from flask) to browse.cgi - then users logged in via request.cgi will have their session saved in browse.cgi, and the navbar section will be easier to implement.

Revision history for this message
Tim Andersson (andersson123) wrote :

I added an index for the requester but it didn't improve load speed :/

 CREATE INDEX result_requester_idx ON result(requester);

altho actually I didn't do it in the public db lol so I should do that too and test again

Revision history for this message
Tim Andersson (andersson123) wrote :

yeah, adding the index on the public db helped, I'll amend the mp to include this in init_db

Revision history for this message
Tim Andersson (andersson123) wrote :

Sharing the flask session worked immediately ... amazing

Revision history for this message
Tim Andersson (andersson123) wrote :

Commit message needs amending but this is ready for review.

Revision history for this message
Skia (hyask) wrote :

First round of comments

review: Needs Fixing
Revision history for this message
Tim Andersson (andersson123) wrote :

I've addressed all comments apart from the refactoring of browse-user.html and explained my reasoning in an inline comment.

Revision history for this message
Tim Andersson (andersson123) wrote :

Okay, I think I've actually finalised this now. It's ready for review.

Revision history for this message
Tim Andersson (andersson123) wrote :

Tim to split this into two commits.

ff445d3... by Tim Andersson

feat: web: add a user specific history page

Fixes bug LP: #2057947

This commit adds a new endpoint to browse.cgi: /user/<username>

This endpoint shows all the running, queued and historical tests for a
specific user.

When the user is logged in, the login button will display "User".
This button will redirect to the /user/<username> page for the currently
logged in user.

This commit also adds a new line into helpers/utils.py - init_db.
The new line simply creates an index based on the requester column of
the db, in order to speed up the loading of the /user/<username>
endpoint.

This commit also creates a jinja macro called set_up_results_table
which sets up the columns for viewing test results.

It also includes a second macro called results_table_core, which
avoids duplicate html between browse-user and browse-results.

Revision history for this message
Tim Andersson (andersson123) wrote :

This has now been split into two commits :)

Revision history for this message
Tim Andersson (andersson123) wrote :
Revision history for this message
Tim Andersson (andersson123) wrote :

I need to further the jinja macro-isation and split the user page function into multiple functions to reduce the cyclomatic complexity.

Revision history for this message
Skia (hyask) :
review: Needs Fixing
Revision history for this message
Tim Andersson (andersson123) wrote :

make sure session doesn't interact with the user param specified in the request

Revision history for this message
Tim Andersson (andersson123) wrote :

User landing page link to be removed

Revision history for this message
Tim Andersson (andersson123) wrote :

if I login as hyask and go to https://autopkgtest.staging.ubuntu.com/user/andersson123, the "Next page of Results" link is wrong

Revision history for this message
Tim Andersson (andersson123) wrote :

amended based on all comments, please re-review

Revision history for this message
Skia (hyask) :
review: Needs Fixing
Revision history for this message
Tim Andersson (andersson123) :
Revision history for this message
Tim Andersson (andersson123) :
Revision history for this message
Tim Andersson (andersson123) wrote :

Amended and ready for re-review

Revision history for this message
Skia (hyask) :
review: Needs Fixing
Revision history for this message
Tim Andersson (andersson123) wrote :

amended based on comments, I'll test this and request another review.

Revision history for this message
Tim Andersson (andersson123) wrote :

TIM: FIX ALL THE VARIABLE SPACES IN TEMPLATES!

Revision history for this message
Tim Andersson (andersson123) wrote :

also add buttons for running only, queued only, historical only

Revision history for this message
Tim Andersson (andersson123) wrote :

all of this is done and it's ready for re-review.

Revision history for this message
Skia (hyask) wrote :

A few inline comments, we're almost there. :-)

review: Needs Fixing
7ebbaac... by Tim Andersson

refactor: web: make browse-results.html use the results_table_core macro

Revision history for this message
Tim Andersson (andersson123) wrote :

amended and testing now

Revision history for this message
Tim Andersson (andersson123) wrote :

tested, fully functional, found a little bug too which I fixed, ready for re-review

Revision history for this message
Skia (hyask) :
review: Needs Fixing
Revision history for this message
Tim Andersson (andersson123) :
Revision history for this message
Tim Andersson (andersson123) wrote :

spent some time breaking the page, I'm about to push a couple of fixes and then this is ready for review again

Unmerged commits

7ebbaac... by Tim Andersson

refactor: web: make browse-results.html use the results_table_core macro

Succeeded
[SUCCEEDED] pre_commit:0 (build)
[SUCCEEDED] unit_tests:0 (build)
[SUCCEEDED] build_charms:0 (build)
13 of 3 results
ff445d3... by Tim Andersson

feat: web: add a user specific history page

Fixes bug LP: #2057947

This commit adds a new endpoint to browse.cgi: /user/<username>

This endpoint shows all the running, queued and historical tests for a
specific user.

When the user is logged in, the login button will display "User".
This button will redirect to the /user/<username> page for the currently
logged in user.

This commit also adds a new line into helpers/utils.py - init_db.
The new line simply creates an index based on the requester column of
the db, in order to speed up the loading of the /user/<username>
endpoint.

This commit also creates a jinja macro called set_up_results_table
which sets up the columns for viewing test results.

It also includes a second macro called results_table_core, which
avoids duplicate html between browse-user and browse-results.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/charms/focal/autopkgtest-web/webcontrol/browse.cgi b/charms/focal/autopkgtest-web/webcontrol/browse.cgi
2index 309fb82..16cafea 100755
3--- a/charms/focal/autopkgtest-web/webcontrol/browse.cgi
4+++ b/charms/focal/autopkgtest-web/webcontrol/browse.cgi
5@@ -54,6 +54,7 @@ def init_config():
6 uri=True,
7 check_same_thread=False,
8 )
9+ db_con.row_factory = sqlite3.Row
10 try:
11 url = cp["web"]["ExternalURL"]
12 except KeyError:
13@@ -62,6 +63,22 @@ def init_config():
14 swift_container_url = os.path.join(url, "autopkgtest-%s")
15
16
17+def get_package_release_arch(test_id):
18+ """
19+ The opposite of get_test_id - gets the package/release/arch
20+ combo for a given test_id.
21+ """
22+ c = db_con.cursor()
23+ c.execute(
24+ "SELECT package, release, arch FROM test WHERE id=?",
25+ (test_id,),
26+ )
27+ try:
28+ return c.fetchone()
29+ except TypeError:
30+ return None
31+
32+
33 def get_test_id(release, arch, src):
34 c = db_con.cursor()
35 c.execute(
36@@ -256,6 +273,128 @@ def success_count_for_release_and_arch(db, release, arch, src_versions):
37 return count
38
39
40+def get_historical_results_for_user(
41+ user: str, limit: int, offset: int
42+) -> list:
43+ historical_results = []
44+ for row in db_con.execute(
45+ "SELECT test_id, run_id, version, triggers, "
46+ "duration, exitcode, requester, env, uuid FROM result "
47+ "WHERE requester=? "
48+ "ORDER BY run_id DESC "
49+ "LIMIT ? OFFSET ? ",
50+ (user, limit, offset),
51+ ):
52+ test_id = row["test_id"]
53+ version = row["version"]
54+ triggers = row["triggers"]
55+ additional_params = row[
56+ "env"
57+ ] # string of comma separated env variables e.g. all-proposed=1,test-name=mytest
58+ code = human_exitcode(row["exitcode"])
59+ package, release, arch = get_package_release_arch(test_id)
60+ url = os.path.join(
61+ swift_container_url % release,
62+ release,
63+ arch,
64+ srchash(package),
65+ package,
66+ row["run_id"],
67+ )
68+ show_retry = code != "pass"
69+ all_proposed = (
70+ additional_params is not None
71+ and "all-proposed=1" in additional_params
72+ )
73+ historical_results.append(
74+ (
75+ version,
76+ triggers,
77+ additional_params,
78+ human_date(row["run_id"]),
79+ human_sec(int(row["duration"])),
80+ user,
81+ code,
82+ url,
83+ show_retry,
84+ all_proposed,
85+ row["uuid"],
86+ package,
87+ release,
88+ arch,
89+ )
90+ )
91+ return historical_results
92+
93+
94+def get_queued_results_for_user(user: str):
95+ queued_results = []
96+ (_, _, queues_info) = get_queues_info()
97+ for _, queue in queues_info.items():
98+ for release, queue_by_arch in queue.items():
99+ for arch, queue_items in queue_by_arch.items():
100+ if queue_items[0] == 0:
101+ continue
102+ requests = queue_items[1]
103+ for req in requests:
104+ try:
105+ req_info = json.loads(req.split("\n")[1])
106+ except json.JSONDecodeError as _:
107+ continue
108+ package = req.split("\n")[0]
109+ if req_info.get("requester", "") == user:
110+ queued_results.append(
111+ (
112+ "N/A",
113+ req_info.get("triggers"),
114+ "N/A",
115+ human_date(req_info.get("submit-time")),
116+ "N/A",
117+ user,
118+ "queued",
119+ "",
120+ False,
121+ "",
122+ req_info.get("uuid", ""),
123+ package,
124+ release,
125+ arch,
126+ ),
127+ )
128+ return queued_results
129+
130+
131+def get_running_results_for_user(user: str):
132+ running_results = []
133+ for package, running_hash in get_running_jobs().items():
134+ for _, running in running_hash.items():
135+ for release, vals in running.items():
136+ for arch, list_of_running_items in vals.items():
137+ if len(list_of_running_items) < 1:
138+ continue
139+ info_dict = list_of_running_items[0]
140+ if info_dict.get("requester", "") == user:
141+ running_results.append(
142+ (
143+ "N/A",
144+ info_dict.get("triggers"),
145+ "N/A",
146+ human_date(info_dict.get("submit-time")),
147+ human_sec(int(list_of_running_items[1])),
148+ user,
149+ "running",
150+ "",
151+ False,
152+ "",
153+ info_dict.get("uuid", "-"),
154+ package,
155+ release,
156+ arch,
157+ ),
158+ )
159+ return running_results
160+
161+
162 @app.route("/")
163 def index_root():
164 flask.session.permanent = True
165@@ -344,6 +483,86 @@ def package_overview(package, _=None):
166 )
167
168
169+@app.route("/user/<user>")
170+def user_overview(user):
171+ """
172+ This endpoint provides a "per-user" view for autopkgtest-cloud.
173+ It shows a page, much like the package/release/arch pages,
174+ except all of the queued, running and historical results are
175+ only shown if they were requested by the user provided.
176+ """
177+ user = user.split("&")[0] if "&" in user else user
178+
179+ def is_it_true(test_string):
180+ mapping_dict = {
181+ "true": True,
182+ "false": False,
183+ }
184+ return mapping_dict.get(test_string.lower(), True)
185+
186+ args = flask.request.args
187+ # make sure this works with showing all historical results
188+ try:
189+ limit = int(args.get("limit", 100))
190+ assert isinstance(limit, int)
191+ assert limit > 0
192+ assert limit <= 100
193+ except AssertionError:
194+ limit = 100
195+ try:
196+ offset = int(args.get("offset", 0))
197+ assert offset > 0
198+ assert isinstance(offset, int)
199+ except AssertionError:
200+ offset = 0
201+
202+ show_running = args.get("show-running", True, type=is_it_true)
203+ show_queued = args.get("show-queued", True, type=is_it_true)
204+ show_historical = args.get("show-historical", True, type=is_it_true)
205+ only_historical = args.get("only-historical", False, type=is_it_true)
206+ if only_historical:
207+ limit = 10000
208+ offset = 0
209+ show_running = False
210+ show_queued = False
211+ show_historical = (
212+ True # just incase anyone tries fumbling with the args!
213+ )
214+
215+ # Get historical results for this user
216+ if show_historical:
217+ historical_results = get_historical_results_for_user(
218+ user, limit, offset
219+ )
220+ else:
221+ historical_results = []
222+
223+ # add queued tests for this user
224+ if show_queued:
225+ queued_results = get_queued_results_for_user(user)
226+ else:
227+ queued_results = []
228+
229+ # add running tests from this user
230+ if show_running:
231+ running_results = get_running_results_for_user(user)
232+ else:
233+ running_results = []
234+
235+ return render(
236+ "browse-user.html",
237+ running_results=running_results,
238+ queued_results=queued_results,
239+ historical_results=historical_results,
240+ limit=limit,
241+ offset=offset,
242+ show_running=show_running,
243+ show_queued=show_queued,
244+ show_historical=show_historical,
245+ user=user,
246+ )
247+
248+
249 # backwards-compatible path with debci that specifies the source hash
250 @app.route("/packages/<_>/<package>/<release>/<arch>")
251 @app.route("/packages/<package>/<release>/<arch>")
252diff --git a/charms/focal/autopkgtest-web/webcontrol/helpers/utils.py b/charms/focal/autopkgtest-web/webcontrol/helpers/utils.py
253index 4e26eb8..1b2a298 100644
254--- a/charms/focal/autopkgtest-web/webcontrol/helpers/utils.py
255+++ b/charms/focal/autopkgtest-web/webcontrol/helpers/utils.py
256@@ -137,6 +137,11 @@ def init_db(path, **kwargs):
257 "CREATE INDEX IF NOT EXISTS result_run_ix ON result("
258 " run_id desc)"
259 )
260+ # /user/<username> page benefits greatly from this idx
261+ # Prior to idx, this page would take ~90s to load, down to ~5s.
262+ c.execute(
263+ "CREATE INDEX IF NOT EXISTS result_requester_idx ON result(requester) "
264+ )
265 # /admin mostly benefits from the index on test_id (~80s -> 50ms)
266 # /packages/<name> also sees some improvements (~14s -> 30ms)
267 c.execute(
268diff --git a/charms/focal/autopkgtest-web/webcontrol/templates/browse-layout.html b/charms/focal/autopkgtest-web/webcontrol/templates/browse-layout.html
269index e4cfce6..f3ffd2f 100644
270--- a/charms/focal/autopkgtest-web/webcontrol/templates/browse-layout.html
271+++ b/charms/focal/autopkgtest-web/webcontrol/templates/browse-layout.html
272@@ -36,6 +36,8 @@
273 <li><a href="{{base_url}}admin">Admin</a></li>
274 {% if not session.get("nickname") %}
275 <li><a href="{{base_url}}request.cgi?/login">Login</a></li>
276+ {% else %}
277+ <li><a href="{{ base_url }}user/{{ session.get("nickname") }}">User</a></li>
278 {% endif %}
279 </ul>
280 </div><!--/.nav-collapse -->
281diff --git a/charms/focal/autopkgtest-web/webcontrol/templates/browse-results.html b/charms/focal/autopkgtest-web/webcontrol/templates/browse-results.html
282index fadff6d..4b72ab2 100644
283--- a/charms/focal/autopkgtest-web/webcontrol/templates/browse-results.html
284+++ b/charms/focal/autopkgtest-web/webcontrol/templates/browse-results.html
285@@ -14,50 +14,12 @@
286
287 <table class="table">
288 <tr>
289- <td><b>Version</b></td>
290- <td><b>Triggers</b></td>
291- <td><b>Env</b></td>
292- <td><b>Date</b></td>
293- <td><b>Duration</b></td>
294- <td><b>Requester</b></td>
295- <td><b>Result</b></td>
296- <td><b>UUID</b></td>
297- <td></td>
298+ {{ macros.set_up_results_table() }}
299 </tr>
300
301 {% for row in package_results %}
302 <tr {% if row[6] in ["running", "queued"] %}class="unfinished"{% endif %}>
303- <td>{{row[0]}}</td>
304- <td>{{row[1]}}</td>
305- <td>{{row[2]}}</td>
306- <td>{{row[3]}}</td>
307- <td>{{row[4]}}</td>
308- <td>
309- {% if row[5] != "-" %}
310- <a href="https://launchpad.net/~{{row[5]}}">{{row[5]}}</a>
311- {% else %}
312- {{row[5]}}
313- {% endif %}
314- </td>
315- <td class="nowrap {{row[6]}}" title={{ row[6] }}>{{row[6]}}</td>
316- <td>{{row[10]}}</td>
317- <td class="nowrap">
318- {% if row[6] not in ["running", "queued"] %}
319- <a href="{{row[7]}}/log.gz">log</a> &emsp;
320- <a href="{{row[7]}}/artifacts.tar.gz">artifacts</a> &emsp;
321- {% endif %}
322- </td>
323- <td class="nowrap">
324- {% if row[8] %}
325- {% set ts = row[1].split()|map('urlencode')|join("&trigger=")|safe %}
326- {% set all_proposed = row[9] %}
327- {% if all_proposed %}
328- <a href="{{base_url}}request.cgi?release={{release}}&arch={{arch}}&package={{package|urlencode}}&trigger={{ts}}&all-proposed=1">&#9851;</a>
329- {% else %}
330- <a href="{{base_url}}request.cgi?release={{release}}&arch={{arch}}&package={{package|urlencode}}&trigger={{ts}}">&#9851;</a>
331- {% endif %}
332- {% endif %}
333- </td>
334+ {{ macros.results_table_core(row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[9], row[10]) }}
335 </tr>
336 {% endfor %}
337 </table>
338diff --git a/charms/focal/autopkgtest-web/webcontrol/templates/browse-user.html b/charms/focal/autopkgtest-web/webcontrol/templates/browse-user.html
339new file mode 100644
340index 0000000..2c11010
341--- /dev/null
342+++ b/charms/focal/autopkgtest-web/webcontrol/templates/browse-user.html
343@@ -0,0 +1,73 @@
344+
345+{% extends "browse-layout.html" %}
346+{% import "macros.html" as macros %}
347+
348+{% macro display_user_test_info(user_results) -%}
349+ <table class="table">
350+ <tr>
351+ <td><b>Package</b></td>
352+ <td><b>Release</b></td>
353+ <td><b>Arch</b></td>
354+ {{ macros.set_up_results_table() }}
355+ </tr>
356+ {% for row in user_results %}
357+ <tr {% if row[6] in ["running", "queued"] %}class="unfinished"{% endif %}>
358+ {% set package = row[11] %}
359+ {% set release = row[12] %}
360+ {% set arch = row[13] %}
361+ <td>{{ package }}</td>
362+ <td>{{ release }}</td>
363+ <td>{{ arch }}</td>
364+ {{ macros.results_table_core(row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[9], row[10]) }}
365+ </tr>
366+ {% endfor %}
367+ </table>
368+{%- endmacro %}
369+
370+{% block content %}
371+ <h1 class="page-header" id="user-home">Running, queued and complete tests by user</h1>
372+ <ul class="external-links">
373+ <a href="https://launchpad.net/~{{ user }}"><img src="{{ url_for('static', filename='launchpad.ico') }}" class="icon">Launchpad</a>
374+ </ul>
375+ <p>Click the link to jump to those specific tests</p>
376+
377+ {% if show_running %}
378+ <h3><a href="#results-running">Running</a></h3>
379+ <h4><a href="{{ base_url }}user/{{ user }}?show-running=true&show-queued=false&show-historical=false">Show Running Only</a></h4>
380+ {% endif %}
381+ {% if show_queued %}
382+ <h3><a href="#results-queued">Queued</a></h3>
383+ <h4><a href="{{ base_url }}user/{{ user }}?show-running=false&show-queued=true&show-historical=false">Show Queued Only</a></h4>
384+ {% endif %}
385+ {% if show_historical %}
386+ <h3><a href="#results-complete">Complete</a></h3>
387+ <h4><a href="{{ base_url }}user/{{ user }}?only-historical=true">Show Historical Only</a></h4>
388+ {% endif %}
389+
390+ {% if show_running %}
391+ <h2 id="results-running">Running</h2>
392+ <h4><a href="#user-home">Back to top</a></h4>
393+ {{ display_user_test_info(running_results)}}
394+ {% endif %}
395+
396+ {% if show_queued %}
397+ <h2 id="results-queued">Queued</h2>
398+ <h4><a href="#user-home">Back to top</a></h4>
399+ {{ display_user_test_info(queued_results)}}
400+ {% endif %}
401+
402+ {% if show_historical %}
403+ <h2 id="results-complete">Complete</h2>
404+ <h4><a href="#user-home">Back to top</a></h4>
405+ {{ display_user_test_info(historical_results)}}
406+
407+ {% if limit is not none %}
408+ <h4><a href="{{ base_url }}user/{{ user }}?offset={{ offset + limit }}&limit={{ limit }}">Next Page of Results</a></h4>
409+ {% if offset != 0 %}
410+ <h4><a href="{{ base_url }}user/{{ user }}?offset={{ offset - limit }}&limit={{ limit }}">Previous Page of Results</a></h4>
411+ {% endif %}
412+ <h4><a href="{{ base_url }}user/{{ user }}?only-historical=true">All Results</a></h4>
413+ {% endif %}
414+ {% endif %}
415+
416+{% endblock %}
417diff --git a/charms/focal/autopkgtest-web/webcontrol/templates/macros.html b/charms/focal/autopkgtest-web/webcontrol/templates/macros.html
418index 941dc77..36809a4 100644
419--- a/charms/focal/autopkgtest-web/webcontrol/templates/macros.html
420+++ b/charms/focal/autopkgtest-web/webcontrol/templates/macros.html
421@@ -49,6 +49,51 @@
422 {%- endfor %}
423 {%- endmacro %}
424
425+{% macro set_up_results_table() -%}
426+ <td><b>Version</b></td>
427+ <td><b>Triggers</b></td>
428+ <td><b>Env</b></td>
429+ <td><b>Date</b></td>
430+ <td><b>Duration</b></td>
431+ <td><b>Requester</b></td>
432+ <td><b>Result</b></td>
433+ <td><b>UUID</b></td>
434+ <td></td>
435+{%- endmacro %}
436+
437+{% macro results_table_core(version, triggers, env, date, duration, requester, code, url, show_retry, all_proposed, uuid) -%}
438+ <td>{{ version }}</td>
439+ <td>{{ triggers }}</td>
440+ <td>{{ env }}</td>
441+ <td>{{ date }}</td>
442+ <td>{{ duration }}</td>
443+ <td>
444+ {% if requester != "-" %}
445+ <a href="https://launchpad.net/~{{ requester }}">{{ requester }}</a>
446+ {% else %}
447+ {{ requester }}
448+ {% endif %}
449+ </td>
450+ <td class="nowrap {{ code }}" title={{ code }}>{{ code }}</td>
451+ <td>{{uuid}}</td>
452+ <td class="nowrap">
453+ {% if code not in ["running", "queued"] %}
454+ <a href="{{ url }}/log.gz">log</a> &emsp;
455+ <a href="{{ url }}/artifacts.tar.gz">artifacts</a> &emsp;
456+ {% endif %}
457+ </td>
458+ <td class="nowrap">
459+ {% if show_retry %}
460+ {% set ts = triggers.split()|map('urlencode')|join("&trigger=")|safe %}
461+ {% if all_proposed %}
462+ <a href="{{ base_url }}request.cgi?release={{ release }}&arch={{ arch }}&package={{ package|urlencode }}&trigger={{ ts }}&all-proposed=1">&#9851;</a>
463+ {% else %}
464+ <a href="{{ base_url }}request.cgi?release={{ release }}&arch={{ arch }}&package={{ package|urlencode }}&trigger={{ ts }}">&#9851;</a>
465+ {% endif %}
466+ {% endif %}
467+ </td>
468+{%- endmacro %}
469+
470 {% macro excuses_link(package_name, release="") -%}
471 {% if release != "" %}
472 {% set release = release + "/" %}

Subscribers

People subscribed via source and target branches