Repository : http://git.fedorahosted.org/cgit/copr.git
On branch : master
commit 5bd0dd3f7ed93d89a543423472fab6c503d29952 Author: Adam Samalik asamalik@redhat.com Date: Wed May 7 09:59:10 2014 +0200
build detail and new builds table
frontend/coprs_frontend/coprs/filters.py | 41 ++++++++++- .../coprs_frontend/coprs/logic/builds_logic.py | 4 + frontend/coprs_frontend/coprs/static/copr.css | 37 ++++++++--- .../templates/coprs/detail/_builds_table.html | 60 ++++------------- .../templates/coprs/detail/build-no-project.html | 7 ++ .../coprs/templates/coprs/detail/build.html | 70 ++++++++++++++++++++ .../coprs/views/coprs_ns/coprs_builds.py | 37 ++++++++++- .../test_views/test_coprs_ns/test_coprs_general.py | 10 ++- 8 files changed, 204 insertions(+), 62 deletions(-)
diff --git a/frontend/coprs_frontend/coprs/filters.py b/frontend/coprs_frontend/coprs/filters.py index 2153240..8271ddc 100644 --- a/frontend/coprs_frontend/coprs/filters.py +++ b/frontend/coprs_frontend/coprs/filters.py @@ -3,6 +3,8 @@ import pytz import time import markdown
+import os + from flask import Markup
from coprs import app @@ -22,6 +24,13 @@ def perm_type_from_num(num): return helpers.PermissionEnum(num)
+@app.template_filter("state_from_num") +def state_from_num(num): + if num is None: + return "unknown" + return helpers.StatusEnum(num) + + @app.template_filter("os_name_short") def os_name_short(os_name, os_version): # TODO: make it models.MockChroot method or not? @@ -43,7 +52,7 @@ def localized_time(time_in, timezone): """ if not time_in: return "Not yet" - format_tz = "%Y-%m-%d %H:%M:%S %Z" + format_tz = "%y-%m-%d %H:%M %Z" utc_tz = pytz.timezone('UTC') if timezone: user_tz = pytz.timezone(timezone) @@ -55,20 +64,37 @@ def localized_time(time_in, timezone):
@app.template_filter('time_ago') -def time_ago(time_in): +def time_ago(time_in, until = None): """ returns string saying how long ago the time on input was
Input is in EPOCH (seconds since epoch). """ - now = datetime.datetime.now() + if time_in is None: + return " - " + if until is not None: + now = datetime.datetime.fromtimestamp(until) + else: + now = datetime.datetime.now() diff = now - datetime.datetime.fromtimestamp(time_in) secdiff = int(diff.total_seconds()) if secdiff < 120: + # less than 2 minutes return "1 minute" elif secdiff < 7200: + # less than 2 hours return str(secdiff/60) + " minutes" - else: + elif secdiff < 172800: + # less than 2 days return str(secdiff/3600) + " hours" + elif secdiff < 5184000: + # less than 2 months + return str(secdiff/86400) + " days" + elif secdiff < 63072000: + # less than 2 years + return str(secdiff/2592000) + " months" + else: + # more than 2 years + return str(secdiff/31536000) + " days"
@app.template_filter("markdown") @@ -81,3 +107,10 @@ def markdown_filter(data): html_replacement_text="--RAW HTML NOT ALLOWED--")
return Markup(md.convert(data)) + + +@app.template_filter("pkg_name") +def parse_package_name(pkg): + if pkg is not None: + return helpers.parse_package_name(os.path.basename(pkg)) + return pkg diff --git a/frontend/coprs_frontend/coprs/logic/builds_logic.py b/frontend/coprs_frontend/coprs/logic/builds_logic.py index ba6dac7..4536f2b 100644 --- a/frontend/coprs_frontend/coprs/logic/builds_logic.py +++ b/frontend/coprs_frontend/coprs/logic/builds_logic.py @@ -67,6 +67,10 @@ class BuildsLogic(object): return models.Build.query.filter(models.Build.id.in_(ids))
@classmethod + def get_by_id(cls, id): + return models.Build.query.get(id) + + @classmethod def add(cls, user, pkgs, copr, repos=None, memory_reqs=None, timeout=None, chroots=[]):
diff --git a/frontend/coprs_frontend/coprs/static/copr.css b/frontend/coprs_frontend/coprs/static/copr.css index 03586c5..fc245ce 100644 --- a/frontend/coprs_frontend/coprs/static/copr.css +++ b/frontend/coprs_frontend/coprs/static/copr.css @@ -21,6 +21,14 @@ h2 { font-size: 1.1em; }
+h2.build-detail { + font-size: 1.8em; +} + +h3 { + font-size: 1.1em; +} + #logo { position: relative; top: 8px; @@ -238,7 +246,7 @@ div.shift-right { }
dt.field-label { - margin: 15px 0; + margin: 15px 0 0 0; font-weight: bold; }
@@ -286,6 +294,10 @@ table.builds-table form { display: inline; }
+div.build-buttons form { + display: inline; +} + table.builds-table tr.details { width: 100%; } @@ -307,29 +319,36 @@ table.status-table tr:first-child { background-color: #1f4b89; }
-tr.build-state:hover { +table.status-table a { text-decoration: underline; - background-color: #E6E6E6; +} + +tr.build-row { + color: #666; +} + +tr.build-row-succeeded { + color: black; }
.build-pending { - color: #3B6EB4; + color: #55b; }
.build-running { - color: #FF6600; + color: #c84; }
.build-succeeded { - color: #22DD22; + color: #272; }
.build-failed { - color: #DD2222; + color: #c44; }
-.build-canceled { - color: #CDC90C; +.build-cancelled { + color: #666; }
table.releases th { diff --git a/frontend/coprs_frontend/coprs/templates/coprs/detail/_builds_table.html b/frontend/coprs_frontend/coprs/templates/coprs/detail/_builds_table.html index 720ef76..d5751fb 100644 --- a/frontend/coprs_frontend/coprs/templates/coprs/detail/_builds_table.html +++ b/frontend/coprs_frontend/coprs/templates/coprs/detail/_builds_table.html @@ -2,57 +2,27 @@
{% macro builds_table(builds, page) %} {% if builds %} - <table class="builds-table"> + <table class="status-table"> <tr> <th>Id</th> - <th>Submitted on</th> - <th>Submitted by</th> - <th>Started on</th> - <th>Ended on</th> - <th>State</th> + <th>Package</th> + <th>Submitted</th> + <th>Build time</th> + <th>Status</th> </tr> {% for build in builds %} - <tr class="build-{{ build.state }} build-state"> - <td>{{ build.id }}</td> -{% if g.user %} - <td>{{ build.submitted_on|localized_time(g.user.timezone) }}</td> -{% else %} - <td>{{ build.submitted_on|localized_time("UTC") }}</td> -{% endif %} - - <td>{{ build.user.name }}</td> -{% if g.user %} - <td>{{ build.started_on|localized_time(g.user.timezone) }}</td> - <td>{{ build.ended_on|localized_time(g.user.timezone) }}</td> -{% else %} - <td>{{ build.started_on|localized_time("UTC") }}</td> - <td>{{ build.ended_on|localized_time("UTC") }}</td> -{% endif %} - <td>{{ build.state }}</td> - </tr> - <tr class="details"> - <td colspan=6 class="end"> - <div> - {% if g.user and g.user.can_build_in(copr) %} - {{ copr_build_cancel_form(build, page) }} - {% endif %} - {% if g.user and g.user.can_build_in(copr) %} - {{ copr_build_repeat_form(build, page) }} - {% endif %} - {% if g.user and g.user.can_edit(copr) %} - {{ copr_build_delete_form(build, page) }} - {% endif %} - <div> - {% if build.results %} - <h2>Results: </h2><a href="{{ build.results }}">{{ build.results }}</a> + <tr class="build-row build-row-{{ build.state }}" > + <th><a href="{{url_for("coprs_ns.copr_build", username = build.copr.owner.name, coprname = build.copr.name, build_id = build.id)}}">{{ build.id }}</a></th> + <th>{{ build.pkgs|pkg_name }}</th> + <th> + {% if g.user %} + {{ build.submitted_on|localized_time(g.user.timezone) }} {% else %} - <h2>No results yet.</h2> + {{ build.submitted_on|localized_time("UTC") }} {% endif %} - <div> - <h2>Package URLs:</h2> - <div class="pkg-url-list">{% if build.pkgs is not none %}{% for pkg in build.pkgs.split() %}<a href="{{ pkg }}">{{ pkg }}</a> -{% endfor %}{% endif %}</div> - </td> + </th> + <th> {{ build.started_on|time_ago(build.ended_on) }} </th> + <th class="build-{{ build.state }}">{{ build.state }}</th> </tr> {% endfor %} </table> diff --git a/frontend/coprs_frontend/coprs/templates/coprs/detail/build-no-project.html b/frontend/coprs_frontend/coprs/templates/coprs/detail/build-no-project.html new file mode 100644 index 0000000..6e28e6a --- /dev/null +++ b/frontend/coprs_frontend/coprs/templates/coprs/detail/build-no-project.html @@ -0,0 +1,7 @@ +{% extends "layout.html" %} +{% block title%}Build {{ build.id }}{%endblock%} +{% block body %} +<h2 class="build-detail"> Build {{ build.id }} </h2> +<p> Project {{username}}/{{coprname}} doesn't exist!</p> +<p> You can go to <a href="{{url_for("coprs_ns.copr_build", username = build.copr.owner.name, coprname = build.copr.name, build_id = build.id)}}"> {{ build.copr.owner.name }}/{{build.copr.name }}/build/{{build.id}} </a>to see this build.</p> +{% endblock %} diff --git a/frontend/coprs_frontend/coprs/templates/coprs/detail/build.html b/frontend/coprs_frontend/coprs/templates/coprs/detail/build.html new file mode 100644 index 0000000..9f75dfe --- /dev/null +++ b/frontend/coprs_frontend/coprs/templates/coprs/detail/build.html @@ -0,0 +1,70 @@ +{% extends "coprs/detail.html" %} +{% from "coprs/detail/_builds_forms.html" import copr_build_cancel_form, copr_build_repeat_form, copr_build_delete_form %} +{% block title %}Build {{ build.id }} in {{ build.copr.owner.name }}/{{ build.copr.name }}{% endblock %} + +{% block detail_body %} + +{% if build.copr != copr %} +<h2 class="build-detail"> Build {{ build.id }} doesn't belong to this project. </h2> +<p> You can go to <a href="{{url_for("coprs_ns.copr_build", username = build.copr.owner.name, coprname = build.copr.name, build_id = build.id)}}"> {{ build.copr.owner.name }}/{{ build.copr.name }}/build/{{build.id}} </a>to see this build. </p> + + +{% else %} + <h2 class="build-detail"> Build {{ build.id }} in {{ build.copr.owner.name }}/{{ build.copr.name }}</h2> + <div class="build-buttons" > + {% if g.user and g.user.can_build_in(copr) %} + {{ copr_build_cancel_form(build, page) }} + {% endif %} + {% if g.user and g.user.can_build_in(copr) %} + {{ copr_build_repeat_form(build, page) }} + {% endif %} + {% if g.user and g.user.can_edit(copr) %} + {{ copr_build_delete_form(build, page) }} + {% endif %} + </div> + + <dl> + <dt class="field-label"> Package name:<dt> + <dd>{{ build.pkgs|pkg_name }}</dd> + </dt> + <dt class="field-label"> Build time:</dt> + <dd><b>Submitted: </b> + {% if g.user %} + {{ build.submitted_on|localized_time(g.user.timezone) }} + {% else %} + {{ build.submitted_on|localized_time("UTC") }} + {% endif %} + ({{ build.submitted_on|time_ago }} ago) + </dd> + <dd><b>Started: </b> + {% if g.user %} + {{ build.started_on|localized_time(g.user.timezone) }} + {% else %} + {{ build.started_on|localized_time("UTC") }} + {% endif %} + ({{ build.started_on|time_ago }} ago) + </dd> + <dd><b>Build time: </b> {{ build.started_on|time_ago(build.ended_on) }} </dd> + <dt class="field-label"> Results:</dt> + {% if build.results %} + <dd><a href="{{ build.results }}">{{ build.results }}</a></dd> + {% else %} + <dd>No results yet</dd> + {% endif %} + <dt class="field-label"> Chroots:</dt> + {% for chroot in build.build_chroots %} + <dd><b>{{ chroot.name }}: </b> + <span class="build-{{ chroot.status|state_from_num }}">{{ chroot.status|state_from_num }}</span> + </dd> + {% endfor %} + <dt class="field-label"> Package URL:</dt> + {% if build.pkgs is not none %}{% for pkg in build.pkgs.split() %} + <dd> <a href="{{ pkg }}">{{ pkg }}</a></dd> + {% endfor %}{% endif %} + <dt class="field-label"> Submitted by:</dt> + <dd>{{ build.user.name }}</dd> + </dl> + + +{% endif %} +{% endblock %} diff --git a/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_builds.py b/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_builds.py index e6a2fa5..20112a5 100644 --- a/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_builds.py +++ b/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_builds.py @@ -14,6 +14,41 @@ from coprs.exceptions import (ActionInProgressException, InsufficientRightsException)
+ +@coprs_ns.route("/build/int:build_id/") +def copr_build_redirect(build_id): + build = builds_logic.BuildsLogic.get_by_id(build_id) + if not build: + return page_not_found( + "Build {0} does not exist.".format(str(build_id))) + + return flask.redirect(flask.url_for("coprs_ns.copr_build", + username = build.copr.owner.name, + coprname = build.copr.name, + build_id = build_id)) + + +@coprs_ns.route("/<username>/<coprname>/build/int:build_id/") +def copr_build(username, coprname, build_id): + build = builds_logic.BuildsLogic.get_by_id(build_id) + copr = coprs_logic.CoprsLogic.get(flask.g.user, username, coprname).first() + + if not build: + return page_not_found( + "Build {0} does not exist.".format(str(build_id))) + + if not copr: # but the build does + return flask.render_template("coprs/detail/build-no-project.html", + build = build, + username = username, + coprname = coprname) + + return flask.render_template("coprs/detail/build.html", + build = build, + copr = copr) + + + @coprs_ns.route("/<username>/<coprname>/builds/", defaults={"page": 1}) @coprs_ns.route("/<username>/<coprname>/builds/int:page/") def copr_builds(username, coprname, page=1): @@ -27,7 +62,7 @@ def copr_builds(username, coprname, page=1): flask.g.user, copr=copr)
paginator = helpers.Paginator( - builds_query, copr.build_count, page, per_page_override=10) + builds_query, copr.build_count, page, per_page_override=30)
return flask.render_template("coprs/detail/builds.html", copr=copr, diff --git a/frontend/coprs_frontend/tests/test_views/test_coprs_ns/test_coprs_general.py b/frontend/coprs_frontend/tests/test_views/test_coprs_ns/test_coprs_general.py index b31e611..06137e7 100644 --- a/frontend/coprs_frontend/tests/test_views/test_coprs_ns/test_coprs_general.py +++ b/frontend/coprs_frontend/tests/test_views/test_coprs_ns/test_coprs_general.py @@ -232,7 +232,9 @@ class TestCoprDetail(CoprsTestCase): assert '<select id="copr_admin_1" name="copr_admin_1">' in r.data
def test_copr_detail_doesnt_show_cancel_build_for_anonymous(self, f_users, f_coprs, f_builds, f_db): - r = self.tc.get("/coprs/{0}/{1}/".format(self.u2.name, self.c2.name)) + r = self.tc.get( + "/coprs/{0}/{1}/build/{2}/".format(self.u2.name, self.c2.name, + str(self.c2.builds[0].id))) assert "/cancel_build/" not in r.data
@TransactionDecorator("u1") @@ -241,7 +243,8 @@ class TestCoprDetail(CoprsTestCase):
self.db.session.add_all([self.u2, self.c2]) r = self.test_client.get( - "/coprs/{0}/{1}/builds/".format(self.u2.name, self.c2.name)) + "/coprs/{0}/{1}/build/{2}/".format(self.u2.name, self.c2.name, + str(self.c2.builds[0].id))) assert "/cancel_build/" not in r.data
@TransactionDecorator("u2") @@ -250,7 +253,8 @@ class TestCoprDetail(CoprsTestCase):
self.db.session.add_all([self.u2, self.c2]) r = self.test_client.get( - "/coprs/{0}/{1}/builds/".format(self.u2.name, self.c2.name)) + "/coprs/{0}/{1}/build/{2}/".format(self.u2.name, self.c2.name, + str(self.c2.builds[0].id))) assert "/cancel_build/" in r.data
copr-commits@lists.fedorahosted.org