templates_packages_modulesdir = $(templates_packagesdir)/modules
dist_templates_packages_name_DATA = \
+ src/templates/packages/name/builds.html \
src/templates/packages/name/index.html
templates_packages_namedir = $(templates_packagesdir)/name
name,
)
- def get_recent(self, limit=None, offset=None):
+ def get_by_name(self, name, limit=None):
+ """
+ Returns all builds by this name
+ """
+ builds = self._get_builds("""
+ SELECT
+ builds.*
+ FROM
+ builds
+ JOIN
+ packages ON builds.pkg_id = packages.id
+ WHERE
+ builds.deleted_at IS NULL
+ AND
+ packages.deleted_at IS NULL
+ AND
+ packages.name = %s
+ ORDER BY
+ builds.created_at
+ """, name,
+ )
+
+ return list(builds)
+
+ def get_release_builds_by_name(self, name, limit=None):
+ builds = self._get_builds("""
+ WITH builds AS (
+ SELECT
+ builds.*,
+
+ -- Number all builds per distribution so that we can filter out
+ -- the first N builds later
+ ROW_NUMBER() OVER (
+ PARTITION BY packages.distro_id ORDER BY builds.created_at DESC
+ ) AS _number
+ FROM
+ builds
+ JOIN
+ packages ON builds.pkg_id = packages.id
+ WHERE
+ builds.deleted_at IS NULL
+ AND
+ packages.deleted_at IS NULL
+ AND
+ packages.name = %s
+ )
+
+ SELECT
+ *
+ FROM
+ builds
+ WHERE
+ %s IS NULL
+ OR
+ _number <= %s
+ """, name, limit, limit,
+ )
+
+ return list(builds)
+
+ def get_scratch_builds_by_name(self, name, limit=None):
+ builds = self._get_builds("""
+ WITH builds AS (
+ SELECT
+ builds.*,
+
+ -- Number all builds per user so that we can filter out
+ -- the first N builds later
+ ROW_NUMBER() OVER (
+ PARTITION BY builds.owner_id ORDER BY builds.created_at DESC
+ ) AS _number
+ FROM
+ builds
+ JOIN
+ packages ON builds.pkg_id = packages.id
+ WHERE
+ builds.deleted_at IS NULL
+ AND
+ builds.owner_id IS NOT NULL
+ AND
+ packages.deleted_at IS NULL
+ AND
+ packages.name = %s
+ )
+
+ SELECT
+ *
+ FROM
+ builds
+ WHERE
+ %s IS NULL
+ OR
+ _number <= %s
+ """, name, limit, limit,
+ )
+
+ return list(builds)
+
+ def get_recent(self, name=None, limit=None, offset=None):
"""
Returns the most recent (non-test) builds
"""
+ if name:
+ return self.get_recent_by_name(name, limit=limit, offset=offset)
+
builds = self._get_builds("""
SELECT
*
return list(builds)
- def get_by_user(self, user, limit=None, offset=None):
+ def get_recent_by_name(self, name, limit=None, offset=None):
"""
- Returns builds by a certain user
+ Returns the most recent (non-test) builds
"""
builds = self._get_builds("""
SELECT
- *
+ builds.*
FROM
builds
+ JOIN
+ packages ON builds.pkg_id = packages.id
WHERE
- deleted_at IS NULL
+ builds.deleted_at IS NULL
AND
- test IS FALSE
+ builds.test IS FALSE
+ AND
+ packages.deleted_at IS NULL
AND
- owner_id = %s
+ packages.name = %s
ORDER BY
created_at DESC
LIMIT
%s
OFFSET
- %s
- """, user, limit, offset,
+ %s""",
+ name, limit, offset,
)
return list(builds)
return 0
+ # Builds
+
+ def get_builds(self, name=None, limit=None, offset=None):
+ """
+ Returns builds by a certain user
+ """
+ if name:
+ return self.get_builds_by_name(name, limit=limit, offset=offset)
+
+ builds = self.backend.builds._get_builds("""
+ SELECT
+ *
+ FROM
+ builds
+ WHERE
+ deleted_at IS NULL
+ AND
+ test IS FALSE
+ AND
+ owner_id = %s
+ ORDER BY
+ created_at DESC
+ LIMIT
+ %s
+ OFFSET
+ %s
+ """, self.id, limit, offset,
+ )
+
+ return list(builds)
+
+ def get_builds_by_name(self, name, limit=None, offset=None):
+ """
+ Fetches all builds matching name
+ """
+ builds = self.backend.builds._get_builds("""
+ SELECT
+ builds.*
+ FROM
+ builds
+ JOIN
+ packages ON builds.pkg_id = packages.id
+ WHERE
+ builds.deleted_at IS NULL
+ AND
+ builds.test IS FALSE
+ AND
+ builds.owner_id = %s
+ AND
+ packages.deleted_at IS NULL
+ AND
+ packages.name = %s
+ LIMIT
+ %s
+ OFFSET
+ %s
+ """, self.id, name, limit, offset,
+ )
+
+ return list(builds)
+
# Stats
@lazy_property
{% block title %}{{ _("Builds") }}{% end block %}
{% block body %}
- <section class="section">
- <div class="container">
- <nav class="breadcrumb" aria-label="breadcrumbs">
- <ul>
- <li class="is-active">
- <a href="#" aria-current="page">
- {{ _("Builds") }}
- </a>
- </li>
- </ul>
- </nav>
+ <section class="hero is-light">
+ <div class="hero-body">
+ <div class="container">
+ <nav class="breadcrumb" aria-label="breadcrumbs">
+ <ul>
+ <li class="is-active">
+ <a href="#" aria-current="page">
+ {{ _("Builds") }}
+ </a>
+ </li>
+ </ul>
+ </nav>
- <h1 class="title">
- {% if user %}
- {{ _("%s's Builds") % user }}
- {% else %}
- {{ _("Recent Builds") }}
- {% end %}
- </h1>
+ <h1 class="title">
+ {% if user and name %}
+ {{ _("%(user)s's Builds Of '%(name)s'") \
+ % { "user" : user, "name" : name } }}
+ {% elif user %}
+ {{ _("%s's Builds") % user }}
+ {% elif name %}
+ {{ _("Builds Of '%s'") % name }}
+ {% else %}
+ {{ _("Recent Builds") }}
+ {% end %}
+ </h1>
+ </div>
+ </div>
+ </section>
+ <section class="section">
+ <div class="container">
{# Render all builds #}
{% for date in builds %}
<div class="block">
<div class="block">
<nav class="pagination is-centered" role="navigation" aria-label="pagination">
<a class="pagination-previous {% if not offset %}is-disabled{% end %}"
- href="{{ make_url("/builds", offset=offset - limit, limit=limit, user=user.name if user else None) }}">
+ href="{{ make_url("/builds", offset=offset - limit, limit=limit, name=name, user=user.name if user else None) }}">
{{ _("Previous Page") }}
</a>
<a class="pagination-next"
- href="{{ make_url("/builds", offset=offset + limit, limit=limit, user=user.name if user else None) }}">
+ href="{{ make_url("/builds", offset=offset + limit, limit=limit, name=name, user=user.name if user else None) }}">
{{ _("Next Page") }}
</a>
</nav>
-{% for build in builds %}
- {% set package = build.pkg %}
-
+{% if builds %}
<div class="block">
- <div class="box">
- <h5 class="title is-5 mb-2">
- <a href="/builds/{{ build.uuid }}">{{ build }}</a>
-
- {% if build.jobs %}
- <div class="tags is-pulled-right">
- {% for job in sorted(build.jobs) %}
- {# Pending #}
- {% if job.is_pending() %}
- <span class="tag">
- <span class="icon-text">
- <span class="icon">
- <i class="fa-solid fa-clock"></i>
+ <nav class="panel">
+ {% for build in builds %}
+ <a class="panel-block is-block p-4" href="/builds/{{ build.uuid }}">
+ {% if build.jobs %}
+ <div class="tags is-pulled-right is-hidden-mobile">
+ {% for job in sorted(build.jobs) %}
+ {# Pending #}
+ {% if job.is_pending() %}
+ <span class="tag">
+ <span class="icon-text">
+ <span class="icon">
+ <i class="fa-solid fa-clock"></i>
+ </span>
+
+ <span>{{ job.arch }}</span>
</span>
-
- <span>{{ job.arch }}</span>
</span>
- </span>
- {# Running #}
- {% elif job.is_running() %}
- <span class="tag">
- <span class="icon-text">
- <span class="icon">
- <i class="fa-solid fa-gear fa-spin"></i>
- </span>
+ {# Running #}
+ {% elif job.is_running() %}
+ <span class="tag">
+ <span class="icon-text">
+ <span class="icon">
+ <i class="fa-solid fa-gear fa-spin"></i>
+ </span>
- <span>{{ job.arch }}</span>
+ <span>{{ job.arch }}</span>
+ </span>
</span>
- </span>
- {# Failed #}
- {% elif job.has_failed() %}
- <span class="tag is-danger">
- <span class="icon-text">
- <span class="icon">
- <i class="fa-solid fa-bug"></i>
- </span>
+ {# Failed #}
+ {% elif job.has_failed() %}
+ <span class="tag is-danger">
+ <span class="icon-text">
+ <span class="icon">
+ <i class="fa-solid fa-bug"></i>
+ </span>
- <span>{{ job.arch }}</span>
+ <span>{{ job.arch }}</span>
+ </span>
</span>
- </span>
- {# Aborted #}
- {% elif job.is_aborted() %}
- <span class="tag is-dark">
- <span class="icon-text">
- <span class="icon">
- <i class="fa-solid fa-xmark"></i>
- </span>
+ {# Aborted #}
+ {% elif job.is_aborted() %}
+ <span class="tag is-dark">
+ <span class="icon-text">
+ <span class="icon">
+ <i class="fa-solid fa-xmark"></i>
+ </span>
- <span>{{ job.arch }}</span>
+ <span>{{ job.arch }}</span>
+ </span>
</span>
- </span>
- {# Finished #}
- {% elif job.has_finished() %}
- <span class="tag is-success">
- <span class="icon-text">
- <span class="icon">
- <i class="fa-solid fa-check"></i>
- </span>
+ {# Finished #}
+ {% elif job.has_finished() %}
+ <span class="tag is-success">
+ <span class="icon-text">
+ <span class="icon">
+ <i class="fa-solid fa-check"></i>
+ </span>
- <span>{{ job.arch }}</span>
+ <span>{{ job.arch }}</span>
+ </span>
</span>
- </span>
- {# Unknown State #}
- {% else %}
- <span class="tag is-light">
- {{ _("Unknown State") }} - {{ job.arch }}
- </span>
+ {# Unknown State #}
+ {% else %}
+ <span class="tag is-light">
+ {{ _("Unknown State") }} - {{ job.arch }}
+ </span>
+ {% end %}
{% end %}
- {% end %}
- </div>
- {% end %}
- </h5>
-
- <div class="level">
- <div class="level-left">
- {% if build.owner %}
- <div class="level-item">
- <span class="icon-text">
- <span class="icon">
- <figure class="image">
- <img class="is-rounded" src="{{ build.owner.avatar(32) }}"
- alt="{{ build.owner }}">
- </figure>
- </span>
- <span>
- <a href="/users/{{ build.owner.name }}">
- {{ build.owner }}
- </a>
- </span>
- </span>
</div>
{% end %}
- <div class="level-item">
- {{ _("Created %s") % locale.format_date(build.created_at, shorter=True) }}
- </div>
- </div>
- </div>
-
- {# XXX show repository #}
- </div>
+ <strong>
+ {{ build }}
+ </strong>
+
+ <p>
+ <small>
+ {% if build.owner %}
+ {{ _("Created %(when)s by %(owner)s") % {
+ "when" : locale.format_date(build.created_at, shorter=True),
+ "owner" : build.owner,
+ } }}
+ {% else %}
+ {{ _("Created %s") % locale.format_date(build.created_at, shorter=True) }}
+ {% end %}
+ </small>
+ </p>
+ </a>
+ {% end %}
+
+ {% if rest and more_url %}
+ <a class="panel-block is-justify-content-center" href="{{ more_url }}">
+ {{ _("Show One More", "Show %(num)s More", len(rest)) % { "num" : len(rest) } }}
+ </a>
+ {% end %}
+ </nav>
</div>
{% end %}
-
--- /dev/null
+{% extends "../../base.html" %}
+
+{% block title %}{{ _("Package") }} - {{ package.name }}{% end block %}
+
+{% block body %}
+ <section class="hero is-light">
+ <div class="hero-body">
+ <div class="container">
+ <nav class="breadcrumb" aria-label="breadcrumbs">
+ <ul>
+ <li>
+ <a href="/packages">{{ _("Packages") }}</a>
+ </li>
+ <li>
+ <a href="/packages/{{ package.name }}">{{ package.name }}</a>
+ </li>
+ <li class="is-active">
+ <a href="#" aria-current="page">{{ _("Builds") }}</a>
+ </li>
+ </ul>
+ </nav>
+
+ <h1 class="title is-1">{{ package.name }}</h1>
+ <h4 class="subtitle is-4">{{ _("Builds") }}</h4>
+ </div>
+ </div>
+ </section>
+
+ {# Release Builds #}
+ {% if distros %}
+ <section class="section">
+ <div class="container">
+ <h4 class="title is-4">{{ _("Release Builds") }}</h4>
+
+ {% for distro in sorted(distros, reverse=True) %}
+ <h5 class="title is-5">{{ distro }}</h5>
+
+ {% module BuildsList(distros[distro], limit=limit,
+ more_url=make_url("/builds", name=package.name)) %}
+ {% end %}
+ </div>
+ </section>
+ {% end %}
+
+ {# Scratch Builds #}
+ {% if users %}
+ <section class="section">
+ <div class="container">
+ <h4 class="title is-4">{{ _("Scratch Builds") }}</h4>
+
+ {% for user in users %}
+ <h5 class="title is-5">{{ user }}</h5>
+
+ {% module BuildsList(users[user], limit=limit,
+ more_url=make_url("/builds", name=package.name, user=user.name)) %}
+ {% end %}
+ </div>
+ </section>
+ {% end %}
+{% end block %}
{% if builds %}
<h4 class="title is-4">{{ repo }}</h4>
- <div class="panel">
+ <nav class="panel">
{% for build in builds %}
<a class="panel-block" href="/builds/{{ build.uuid }}">
<span class="panel-icon">
{{ build }}
</a>
{% end %}
- </div>
+ </nav>
{% end %}
{% end %}
+
+ {% if distro in scratch_builds %}
+ <h4 class="title is-4">{{ _("My Scratch Builds") }}</h4>
+
+ <nav class="panel">
+ {% for build in scratch_builds[distro] %}
+ <a class="panel-block" href="/builds/{{ build.uuid }}">
+ <span class="panel-icon">
+ <i class="fa-solid fa-box" aria-hidden="true"></i>
+ </span>
+
+ {{ build }}
+ </a>
+ {% end %}
+ </nav>
+ {% end %}
</div>
{# Release Monitoring #}
{% if current_user and current_user.is_admin() %}
- {% if monitoring %}
- <div class="buttons">
+ <div class="buttons">
+ {% if monitoring %}
<a class="button is-light" href="{{ monitoring.url }}">
{{ _("Show Upstream Releases") }}
</a>
<a class="button is-warning" href="{{ monitoring.url }}/edit">
{{ _("Edit Release Monitoring") }}
</a>
- </div>
- {% else %}
- <a class="button is-success" href="/distros/{{ distro.slug }}/monitorings/{{ package.name }}/create">
- {{ _("Enable Release Monitoring") }}
- </a>
- {% end %}
+ {% else %}
+ <a class="button is-success" href="/distros/{{ distro.slug }}/monitorings/{{ package.name }}/create">
+ {{ _("Enable Release Monitoring") }}
+ </a>
+ {% end %}
+ </div>
{% end %}
{% end %}
+
+ <div class="buttons">
+ <a class="button is-light" href="/packages/{{ package.name }}/builds">
+ {{ _("Show All Builds") }}
+ </a>
+ </div>
</div>
</section>
(r"/packages", packages.IndexHandler),
(r"/packages/([\w]{8}-[\w]{4}-[\w]{4}-[\w]{4}-[\w]{12})", packages.ShowHandler),
(r"/packages/([\w\-\+]+)", packages.NameHandler),
+ (r"/packages/([\w\-\+]+)/builds", packages.NameBuildsHandler),
(r"/packages/([\w]{8}-[\w]{4}-[\w]{4}-[\w]{4}-[\w]{12})/download(.*)",
packages.FileDownloadHandler),
(r"/packages/([\w]{8}-[\w]{4}-[\w]{4}-[\w]{4}-[\w]{12})/view(.*)",
limit = self.get_argument_int("limit", None) or 25
# Filters
+ name = self.get_argument("name", None)
user = self.get_argument_user("user", None)
# Fetch the most recent builds
if user:
- builds = self.backend.builds.get_by_user(user, limit=limit, offset=offset)
+ builds = user.get_builds(name, limit=limit, offset=offset)
else:
- builds = self.backend.builds.get_recent(limit=limit, offset=offset)
+ builds = self.backend.builds.get_recent(name=name, limit=limit, offset=offset)
# Group builds by date
builds = misc.group(builds, lambda build: build.created_at.date())
- self.render("builds/index.html", builds=builds, user=user,
+ self.render("builds/index.html", builds=builds, name=name, user=user,
limit=limit, offset=offset)
class ListModule(ui_modules.UIModule):
- def render(self, builds):
- return self.render_string("builds/modules/list.html", builds=builds)
+ def render(self, builds, limit=None, more_url=None):
+ rest = None
+
+ # Limit builds
+ if limit:
+ builds, rest = builds[:limit], builds[limit:]
+
+ return self.render_string("builds/modules/list.html", builds=builds,
+ rest=rest, more_url=more_url)
class GroupListModule(ui_modules.UIModule):
import os.path
import tornado.web
+from .. import misc
+
from . import base
from . import ui_modules
-from ..constants import BUFFER_SIZE
-
class IndexHandler(base.BaseHandler):
def get(self):
# Sort all packages in an array like "<first char>" --> [packages, ...]
for distro in distros),
)))
- self.render("packages/name/index.html", package=build.pkg, distros=distros, bugs=bugs)
+ # Fetch my own builds
+ if self.current_user:
+ scratch_builds = misc.group(
+ self.current_user.get_builds_by_name(name), lambda build: build.distro,
+ )
+ else:
+ scratch_builds = None
+
+ self.render("packages/name/index.html", package=build.pkg, distros=distros,
+ scratch_builds=scratch_builds, bugs=bugs)
+
+
+class NameBuildsHandler(base.BaseHandler):
+ def get(self, name):
+ build = self.backend.builds.get_latest_by_name(name)
+ if not build:
+ raise tornado.web.HTTPError(404, "Package '%s' was not found" % name)
+
+ # Fetch the limit
+ limit = self.get_argument_int("limit", 5)
+
+ # Find the latest release builds
+ release_builds = self.backend.builds.get_release_builds_by_name(name)
+
+ # Group them by distribution
+ distros = misc.group(release_builds, lambda build: build.distro)
+
+ # Find the latest scratch builds
+ scratch_builds = self.backend.builds.get_scratch_builds_by_name(name)
+
+ # Group them by user
+ users = misc.group(scratch_builds, lambda build: build.owner)
+
+ self.render("packages/name/builds.html", limit=limit,
+ package=build.pkg, distros=distros, users=users)
class ShowHandler(base.BaseHandler):