repo requests web ui

* new page for request details
* new page for querying requests
* add links in taginfo and taskinfo pages
* show more data in repoinfo page
This commit is contained in:
Mike McLean 2024-11-11 12:18:34 -05:00 committed by Tomas Kopecek
parent 573bd41654
commit 357587a57c
9 changed files with 300 additions and 15 deletions

View file

@ -33,7 +33,7 @@ class TestRepoInfo(unittest.TestCase):
webidx.repoinfo(self.environ, self.repo_id)
self.server.repoInfo.assert_called_once_with(int(self.repo_id), strict=False)
self.server.listBuildroots.assert_called_once_with(repoID=int(self.repo_id))
self.server.listBuildroots.assert_called_once_with(repoID=int(self.repo_id), queryOpts={'countOnly': True})
def test_repoinfo_not_dist(self):
"""Test repoinfo function - not dist repo"""
@ -46,4 +46,7 @@ class TestRepoInfo(unittest.TestCase):
webidx.repoinfo(self.environ, self.repo_id)
self.server.repoInfo.assert_called_once_with(int(self.repo_id), strict=False)
self.server.listBuildroots.assert_called_once_with(repoID=int(self.repo_id))
self.server.listBuildroots.assert_called_once_with(repoID=int(self.repo_id), queryOpts={'countOnly': True})
# the end

View file

@ -797,6 +797,10 @@ def taskinfo(environ, taskID):
else:
values['perms'] = []
values['requests'] = []
if task['method'] == 'newRepo':
values['requests'] = server.repo.queryQueue([['task_id', '=', task['id']]], ['id'])
values['koji'] = koji
values['S'] = SafeValue
@ -1051,8 +1055,9 @@ def taginfo(environ, tagID, all='0', packageOrder='package_name', packageStart=N
values['srcTargets'] = srcTargets
values['destTargets'] = destTargets
values['all'] = all
values['repo'] = server.getRepo(tag['id'], state=koji.REPO_READY)
values['repo'] = server.repo.get(tag['id'])
values['external_repos'] = server.getExternalRepoList(tag['id'])
values['request_count'] = server.repo.queryQueue([['tag_id', '=', tag['id']]], opts={'countOnly': True})
child = None
if childID is not None:
@ -2696,13 +2701,72 @@ def repoinfo(environ, repoID):
else:
values['repo_json'] = os.path.join(
pathinfo.repo(repo_info['id'], repo_info['tag_name']), 'repo.json')
num_buildroots = len(server.listBuildroots(repoID=repoID)) or 0
values['numBuildroots'] = num_buildroots
num_buildroots = server.listBuildroots(repoID=repoID, queryOpts={'countOnly': True})
values['numBuildroots'] = num_buildroots
values['requests'] = server.repo.queryQueue([['repo_id', '=', repoID]], ['id'])
values['state_name'] = kojiweb.util.repoState(repo_info['state'])
values['create_time'] = kojiweb.util.formatTimeLong(repo_info['create_ts'])
return _genHTML(environ, 'repoinfo.html.j2')
def reporequest(environ, reqID):
values = _initValues(environ, 'Repo Request', 'tags')
server = _getServer(environ)
req_id = int(reqID)
values['req_id'] = req_id
rows = server.repo.queryQueue([['id', '=', req_id]], '**')
if not rows:
raise koji.GenericError("No such repo request: %s" % req_id)
req = rows[0]
if req['at_event']:
values['at_event'] = server.getEvent(req['at_event'])
elif req['min_event']:
values['min_event'] = server.getEvent(req['min_event'])
else:
# invalid, but technically not blocked in db
values['min_event'] = None
values['req'] = req
return _genHTML(environ, 'reporequest.chtml')
def reporequests(environ, active="true", tag=None, start=None, order=None):
values = _initValues(environ, 'Repo Requests', 'tags')
server = _getServer(environ)
clauses = []
desc_parts = []
if active.lower() == 'all':
desc_parts.append('Recent repo requests')
elif active.lower() in ('false', 'no', '0'):
desc_parts.append('Inactive repo requests')
clauses.append(["active", False])
active = 'false'
else:
desc_parts.append('Active repo requests')
clauses.append(["active", True])
active = 'true'
if tag:
taginfo = server.getTag(_convert_if_int(tag), strict=True, event='auto')
clauses.append(["tag_id", taginfo['id']])
desc_parts.append('for tag %(name)s' % taginfo)
tag = taginfo['name']
else:
tag = None
if order is None:
order = '-id'
values['desc'] = ' '.join(desc_parts)
values['order'] = order
values['active'] = active
values['tag'] = tag
kojiweb.util.paginateMethod(server, values, 'repo.queryQueue',
args=(clauses, '**'),
start=start, dataName='reqs', prefix='req', order=order,
optsarg='opts')
return _genHTML(environ, 'reporequests.chtml')
def activesession(environ, start=None, order=None):
values = _initValues(environ, 'Active sessions', 'activesession')
server = _getServer(environ)

View file

@ -0,0 +1,41 @@
#import json
#import koji
#from kojiweb import util
#include "includes/header.chtml"
<h4>Information for repo request $req_id</h4>
#if not $req
Repo request $req_id not found.
#else
<table>
<tr><th>ID</th><td>$req.id</td><th></tr>
<tr><th>Active</th><td>$req.active</td><th></tr>
<tr><th>Priority</th><td>$req.priority</td><th></tr>
<tr><th>Tag</th><td><a href="taginfo?tagID=$req.tag_id">$req.tag_name</a></td></tr>
#if $req.at_event
<tr><th>At specific event</th><td>$at_event.id ($util.formatTimeLong($at_event.ts))</td><th></tr>
#elif $req.min_event
<tr><th>Minimum event</th><td>$min_event.id ($util.formatTimeLong($min_event.ts))</td><th></tr>
#else
<tr><th>Invalid event</th><td>Unable to determine event for request</td><th></tr>
#end if
#if $req.opts
<th>Options</th><td class="usertext">$json.dumps($req.opts, indent=4)</td>
#end if
#if $req.repo_id
<tr><th>Fulfilled by repo</th><td><a href="repoinfo?repoID=$req.repo_id">$req.repo_id</a</td></tr>
#end if
#if $req.task_id
<tr><th>Task ID</th><td><a href="taskinfo?taskID=$req.task_id">$req.task_id</a> ($util.taskState($req.task_state))</td></tr>
<tr><th>Tries</th><td>$req.tries</td></tr>
#end if
<tr><th>Owner</th><td><a href="userinfo?userID=$req.owner">$req.owner_name</a></td></tr>
<tr><th>Created</th><td>$util.formatTimeLong($req.create_ts)</td></tr>
<tr><th>Updated</th><td>$util.formatTimeLong($req.update_ts)</td></tr>
</table>
#end if
#include "includes/footer.chtml"

View file

@ -0,0 +1,129 @@
#encoding UTF-8
#from kojiweb import util
#from kojiweb.util import passthrough as P
#include "includes/header.chtml"
#set $Pvars = ('active', 'tag', 'order')
<h4>$desc</h4>
<table class="data-list">
<td colspan="6">
<form action="">
<table class="nested">
<tr><td>
<strong>Active</strong>:
</td><td>
<select name="active" class="filterlist" onchange="javascript: window.location = 'reporequests?active=' + this.value + '$P($self, 'tag', 'order')';">
<option value="true" #if $active == 'true' then 'selected' else ''#>true</option>
<option value="false" #if $active == 'false' then 'selected' else ''#>false</option>
<option value="all" #if $active == 'all' then 'selected' else ''#>all</option>
</select>
</td>
<td>
<strong>Tag</strong>:
</td><td>
<input type="text" name="tag" value="$tag"/>
</td>
</tr>
</table>
</form>
</td>
</tr>
<tr>
<td class="paginate" colspan="6">
#if $len($reqPages) > 1
<form class="pageJump" action="">
Page:
<select onchange="javascript: window.location = 'reporequests?start=' + this.value * $reqRange + '$P($self, *Pvars)';">
#for $pageNum in $reqPages
<option value="$pageNum"#if $pageNum == $reqCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
#end for
</select>
</form>
#end if
#if $reqStart > 0
<a href="reporequests?start=#echo $reqStart - $reqRange #$P($self, *Pvars)">&lt;&lt;&lt;</a>
#end if
#if $totalReqs != 0
<strong>Requests #echo $reqStart + 1 # through #echo $reqStart + $reqCount # of $totalReqs</strong>
#end if
#if $reqStart + $reqCount < $totalReqs
<a href="reporequests?start=#echo $reqStart + $reqRange#$P($self, *Pvars)">&gt;&gt;&gt;</a>
#end if
</td>
</tr>
<tr class="list-header">
<th><a href="reporequests?$P($self, *Pvars, toggleOrder='id', prefix='')">ID</a> $util.sortImage($self, 'id')</th>
<th><a href="reporequests?$P($self, *Pvars, toggleOrder='priority', prefix='')">Priority</a> $util.sortImage($self, 'priority')</th>
<th><a href="reporequests?$P($self, *Pvars, toggleOrder='tag_name', prefix='')">Tag</a> $util.sortImage($self, 'tag_name')</th>
<th>Task</th>
<th>Repo</th>
<th>Status</th>
</tr>
#if $len($reqs) > 0
#for $req in $reqs
<tr class="$util.rowToggle($self)">
<td><a href="reporequest?reqID=$req.id">$req.id</a></td>
<td>$req.priority</td>
<td>
<a href="taginfo?tagID=$req.tag_id">$req.tag_name</a>
#if not $tag
<a href="reporequests?tag=$req.tag_id$P($self, 'active', 'order')" title="Filter by tag"><img src="$util.themePath('images/funnel.svg')"></a>
#end if
</td>
#if $req.task_id
<td><a href="taskinfo?taskID=$req.task_id">$req.task_id</a></td>
#else
<td>...</td>
#end if
#if $req.repo_id
<td><a href="repoinfo?repoID=$req.repo_id">$req.repo_id</a></td>
#else
<td>...</td>
#end if
<td>
## simulate a more helpful status
#if $req.active
$util.imageTag('waiting')
#elif $req.repo_id
$util.imageTag('yes')
#else
$util.imageTag('no')
#end if
</td>
</tr>
#end for
#else
<tr class="row-odd">
<td colspan="2">No repo requests</td>
</tr>
#end if
<tr>
<td class="paginate" colspan="2">
#if $len($reqPages) > 1
<form class="pageJump" action="">
Page:
<select onchange="javascript: window.location = 'reporequests?start=' + this.value * $reqRange + '$P($self, *Pvars)';">
#for $pageNum in $reqPages
<option value="$pageNum"#if $pageNum == $reqCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
#end for
</select>
</form>
#end if
#if $reqStart > 0
<a href="reporequests?start=#echo $reqStart - $reqRange #$P($self, *Pvars)">&lt;&lt;&lt;</a>
#end if
#if $totalReqs != 0
<strong>Reqs #echo $reqStart + 1 # through #echo $reqStart + $reqCount # of $totalReqs</strong>
#end if
#if $reqStart + $reqCount < $totalReqs
<a href="reporequests?start=#echo $reqStart + $reqRange#$P($self, *Pvars)">&gt;&gt;&gt;</a>
#end if
</td>
</tr>
</table>
#include "includes/footer.chtml"

View file

@ -7,16 +7,35 @@
<tr><th>ID</th><td>{{ repo.id }}</td><th></tr>
<tr><th>Tag</th><td><a href="taginfo?tagID={{ repo.tag_id }}">{{ repo.tag_name }}</a></td></tr>
{% if repo.task_id %}
<tr><th>Task ID</th><td><a href="taskinfo?taskID={{ repo.task_id }}">{{ repo.task_id }}</a></td></tr>
<tr><th>Task ID</th><td><a href="taskinfo?taskID={{ repo.task_id }}">{{ repo.task_id }}</a> ({{ util.taskState(repo.task_state) }})</td></tr>
{% endif %}
<tr><th>State</th><td class="repo{{ state_name }}">{{ state_name }}</td></tr>
<tr><th>Event</th><td>{{ repo.create_event }} ({{ create_time }})</td></tr>
<tr><th>Created</th><td>{{ util.formatTimeLong(repo.creation_ts) }}</td></tr>
<tr><th>State changed</th><td>{{ util.formatTimeLong(repo.state_ts) }}</td></tr>
<tr><th>Created from Event</th><td>{{ repo.create_event }} ({{ util.formatTimeLong(repo.create_ts) }})</td></tr>
{%- if repo.begin_event %}
{%- if repo.end_event %}
<tr><th>Event range</th><td>{{ repo.begin_event }} ... {{ repo.end_event }}</td></tr>
{%- else %}
<tr><th>Event range</th><td>{{ repo.begin_event }} ... </td></tr>
{%- endif %}
{%- endif %}
{%- if state_name != 'deleted' %}
<tr><th>URL</th><td><a href="{{ url }}">repodata</a></td></tr>
<tr><th>Repo json</th><td><a href="{{ repo_json }}">repo.json</a></td></tr>
{%- endif %}
#if repo.custom_opts
<th>Custom Opts</th><td class="usertext">{{ json.dumps(repo.custom_opts, indent=4) }}</td>
#endif
<tr><th>Dist repo?</th><td class="{{ repo.dist | lower }}">{{ 'yes' if repo.dist else 'no' }}</td></tr>
<tr><th>Number of buildroots: </th><td><a href="buildroots?repoID={{ repo.id }}">{{ numBuildroots }}</a></td></tr>
#if requests
<tr><th>Fulfills requests:</th><td>
#for req in requests
<a href="reporequest?reqID={{ req.id }}">{{ req.id }}</a>
#endfor
</td/></tr>
#endif
</table>
{% else %}
Repo {{ repo_id }} not found.

View file

@ -117,6 +117,10 @@
#endif
</td>
</tr>
<tr>
<th>Repo&nbsp;requests</th>
<td><a href="reporequests?active=all&tag={{ tag.id }}">{{ request_count }}</a></td>
</tr>
<tr>
<th>Packages</th>
<td><a href="packages?blocked=0&tagID={{ tag.id }}">{{ numPackages }}</a></td>

View file

@ -140,6 +140,14 @@ None
</tr>
#endfor
#endif
#if requests
<tr><th>For request:</th><td>
## we only expect one, but if we get more print them all
#for req in requests
<a href="reporequest?reqID={{ req.id }}">{{ req.id }}</a>
#endfor
</td/></tr>
#endif
<tr>
<th>Created</th><td>{{ util.formatTimeLong(task.create_ts) }}</td>
</tr>

View file

@ -353,17 +353,17 @@ def _sortImage(orderVal, sortKey, orderVar):
@safe_return
@pass_context
def passthrough(context, *varnames, prefix='&', invert=False):
def passthrough(context, *varnames, prefix='&', invert=False, toggleOrder=None):
if invert:
_PASSTHROUGH = context.get('_PASSTHROUGH', None)
if _PASSTHROUGH is None:
raise Exception('template does not define _PASSTHROUGH')
varnames = {n for n in _PASSTHROUGH if n not in varnames}
data = {n: context.get(n, default=None) for n in varnames}
return _passthrough(data, prefix)
return _passthrough(data, prefixi, toggleOrder)
def _passthrough(data, prefix='&'):
def _passthrough(data, prefix='&', toggleOrder=None):
"""
Construct a url parameter string from template vars
@ -380,6 +380,11 @@ def _passthrough(data, prefix='&'):
result = []
for var in sorted(data):
value = data[var]
if var == 'order' and toggleOrder is not None:
if value == toggleOrder:
value = '-' + value
else:
value = toggleOrder
if value is not None:
if isinstance(value, str):
if value.isdigit():
@ -396,7 +401,7 @@ def _passthrough(data, prefix='&'):
@pass_context
def passthrough_except(context, *exclude, prefix='&'):
def passthrough_except(context, *exclude, prefix='&', toggleOrder=None):
"""
Construct a string suitable for use as URL
parameters. The template calling this method must have
@ -408,7 +413,7 @@ def passthrough_except(context, *exclude, prefix='&'):
"""
# note that we have to pass context ourselves here
# the decorator only works when called directly from the template
return passthrough(context, *exclude, prefix=prefix, invert=True)
return passthrough(context, *exclude, prefix=prefix, invert=True, toggleOrder=toggleOrder)
def sortByKeyFuncNoneGreatest(key):
@ -459,7 +464,7 @@ def paginateList(values, data, start, dataName, prefix=None, order=None, noneGre
def paginateMethod(server, values, methodName, args=None, kw=None,
start=None, dataName=None, prefix=None, order=None, pageSize=50,
first_page_count=True):
first_page_count=True, optsarg='queryOpts'):
"""Paginate the results of the method with the given name when called with the given args and
kws. The method must support the queryOpts keyword parameter, and pagination is done in the
database.
@ -483,10 +488,10 @@ def paginateMethod(server, values, methodName, args=None, kw=None,
if start == 0 and not first_page_count:
totalRows = None
else:
kw['queryOpts'] = {'countOnly': True}
kw[optsarg] = {'countOnly': True}
totalRows = getattr(server, methodName)(*args, **kw)
kw['queryOpts'] = {'order': order,
kw[optsarg] = {'order': order,
'offset': start,
'limit': pageSize}
data = getattr(server, methodName)(*args, **kw)

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="#000000" height="16px" width="16px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 485.008 485.008" xml:space="preserve">
<g>
<g>
<path d="M171.501,464.698v-237.9l-166.3-192.6c-8.9-10.9-7.9-33.3,15.1-33.3h443.6c21.6,0,26.6,19.8,15.1,33.3l-162.3,187.5v147.2
c0,6-2,11.1-7.1,15.1l-103.8,95.8C193.801,488.698,171.501,483.898,171.501,464.698z M64.701,41.298l142.2,164.3c3,4,5,8.1,5,13.1
v200.6l64.5-58.5v-146.1c0-5,2-9.1,5-13.1l138.1-160.3L64.701,41.298L64.701,41.298z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 716 B