#4269 repo requests web ui
Opened 4 months ago by mikem. Modified 7 hours ago
mikem/koji request-web-ui  into  master

@@ -31,7 +31,7 @@ 

  

          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"""
@@ -43,4 +43,7 @@ 

  

          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

file modified
+66 -3
@@ -792,6 +792,10 @@ 

      else:

          values['perms'] = []

  

+     values['requests'] = []

+     if task['method'] == 'newRepo':

+         values['requests'] = server.repo.queryQueue([['task_id', '=', task['id']]], ['id'])

+ 

      return _genHTML(environ, 'taskinfo.chtml')

  

  
@@ -1043,8 +1047,9 @@ 

      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:
@@ -2675,11 +2680,69 @@ 

          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'])

      return _genHTML(environ, 'repoinfo.chtml')

  

  

+ 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)

file modified
+22 -2
@@ -1,3 +1,4 @@ 

+ #import json

  #import koji

  #from kojiweb import util

  
@@ -10,17 +11,36 @@ 

    <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>

    #end if

    #set $state = $util.repoState($repo.state)

    <tr><th>State</th><td class="repo$state">$state</td></tr>

-   <tr><th>Event</th><td>$repo.create_event ($util.formatTimeLong($repo.create_ts))</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>

+   #end if

+   #end if

    #if $repo.state != koji.REPO_STATES['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>

    #end if

+   #if $repo.custom_opts

+   <th>Custom Opts</th><td class="usertext">$json.dumps($repo.custom_opts, indent=4)</td>

+   #end if

    <tr><th>Dist repo?</th><td class="$str($repo.dist).lower()">#if $repo.dist then 'yes' 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>

+     #end for

+   </td/></tr>

+   #end if

  </table>

  #else

  Repo $repo_id not found.

@@ -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"

@@ -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"

@@ -121,6 +121,10 @@ 

        </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>

      </tr>

@@ -139,6 +139,14 @@ 

      </tr>

      #end for

      #end if

+     #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>

+       #end for

+     </td/></tr>

+     #end if

      <tr>

        <th>Created</th><td>$util.formatTimeLong($task.create_ts)</td>

      </tr>

file modified
+11 -6
@@ -302,7 +302,7 @@ 

  

  

  @safe_return

- def passthrough(template, *vars, prefix='&'):

+ def passthrough(template, *vars, prefix='&', toggleOrder=None):

      """

      Construct a url parameter string from template vars

  
@@ -319,6 +319,11 @@ 

      result = []

      for var in vars:

          value = template.getVar(var, default=None)

+         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():
@@ -334,7 +339,7 @@ 

          return ''

  

  

- def passthrough_except(template, *exclude, prefix='&'):

+ def passthrough_except(template, *exclude, prefix='&', toggleOrder=None):

      """

      Construct a string suitable for use as URL

      parameters.  The template calling this method must have
@@ -348,7 +353,7 @@ 

      for var in template._PASSTHROUGH:

          if var not in exclude:

              passvars.append(var)

-     return passthrough(template, *passvars, prefix=prefix)

+     return passthrough(template, *passvars, prefix=prefix, toggleOrder=toggleOrder)

  

  

  def sortByKeyFuncNoneGreatest(key):
@@ -399,7 +404,7 @@ 

  

  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.
@@ -423,10 +428,10 @@ 

      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)

@@ -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>

These changes provide some visibility into the repo requests in the web ui.

  • reporequests page for listing requests
  • reporequest page for viewing request info
  • show additional info on repoinfo page
  • show task -> request link if there is one
  • show request count on taginfo page

Fixes https://pagure.io/koji/issue/4289

This symbol raises for me:
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 1500

does adding #encoding UTF-8 to reporequests.chtml help?

does adding #encoding UTF-8 to reporequests.chtml help?

yes, that's enough

1 new commit added

  • add explicit encoding
4 months ago

updated.

I'm still debating the right way to mark the "add tag to query" link. Open to suggestions.

Maybe just adding some descriptive title like <a href="..." title="Filter by tag"/>?

1 new commit added

  • add link title
4 months ago

Ah good idea.

But is the ↻ symbol ok? Any better ideas?

Metadata Update from @tkopecek:
- Pull-request tagged with: testing-ready

2 months ago

rebased onto 8e55098

a month ago

2 new commits added

  • adjust icon size
  • Use funnel icon for links to add query filters
a month ago

@cobrien pointed out that the filter or funnel icon is intended for this. There isn't a standard unicode character for it, so I used an svg icon

Metadata Update from @tkopecek:
- Pull-request untagged with: testing-ready
- Pull-request tagged with: testing-custom

7 hours ago