Repository :
http://git.fedorahosted.org/cgit/copr.git
On branch : master
---------------------------------------------------------------
commit 5bd0dd3f7ed93d89a543423472fab6c503d29952
Author: Adam Samalik <asamalik(a)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)
+(a)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))
+
+
+(a)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