Skip to content

[ Issue #509 + #510] Series views #589

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions htdocs/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,12 @@ span.p_mod { color: #a020f0; }
font-weight: bold;
}

/* series */
a.series-list-header {
color: inherit; /* Inherit color from parent element */
text-decoration: none; /* Optional: removes underline */
}

/* bundles */
table.bundlelist {
margin-top: 2em;
Expand Down
33 changes: 30 additions & 3 deletions patchwork/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from django.core.validators import validate_unicode_slug
from django.db.models import Count
from django.db import models
from django.urls import reverse
from django.utils.functional import cached_property
Expand Down Expand Up @@ -880,6 +881,32 @@ def received_total(self):
def received_all(self):
return self.total <= self.received_total

@property
def interest_count(self):
count = self.patches.aggregate(
Count('planning_to_review', distinct=True)
)
return count['planning_to_review__count']

@property
def check_count(self):
"""Generate a list of unique checks for all patchs in the series.

Compile a list of checks associated with this series patches for each
type of check. Only "unique" checks are considered, identified by their
'context' field. This means, given n checks with the same 'context', the
newest check is the only one counted regardless of its value. The end
result will be a association of types to number of unique checks for
said type.
"""
counts = {key: 0 for key, _ in Check.STATE_CHOICES}

for p in self.patches.all():
for check in p.checks:
counts[check.state] += 1

return counts

def add_cover_letter(self, cover):
"""Add a cover letter to the series.

Expand Down Expand Up @@ -935,10 +962,10 @@ def add_patch(self, patch, number):
return patch

def get_absolute_url(self):
# TODO(stephenfin): We really need a proper series view
return reverse(
'patch-list', kwargs={'project_id': self.project.linkname}
) + ('?series=%d' % self.id)
'series-detail',
kwargs={'project_id': self.project.linkname, 'series_id': self.id},
)

def get_mbox_url(self):
return reverse('series-mbox', kwargs={'series_id': self.id})
Expand Down
11 changes: 11 additions & 0 deletions patchwork/templates/patchwork/partials/download-buttons.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<div class="btn-group pull-right">
{% if submission %}
<button type="button" class="btn btn-default btn-copy"
data-clipboard-text="{{ submission.id }}" title="Copy to Clipboard">
{{ submission.id }}
Expand All @@ -24,4 +25,14 @@
series
</a>
{% endif %}
{% elif series %}
<button type="button" class="btn btn-default btn-copy"
data-clipboard-text="{{ series.id }}" title="Copy to Clipboard">
{{ series.id }}
</button>
<a href="{% url 'series-mbox' series_id=series.id %}"
class="btn btn-default" role="button" title="Download series mbox">
series
</a>
{% endif %}
</div>
54 changes: 54 additions & 0 deletions patchwork/templates/patchwork/series-detail.html
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need tests for this also.

Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{% extends "base.html" %}

{% load humanize %}
{% load syntax %}
{% load person %}
{% load patch %}
{% load static %}
{% load utils %}

{% block title %}{{series.name}}{% endblock %}

{% block body %}

<div>
{% include "patchwork/partials/download-buttons.html" %}
<h1>{{ series.name }}</h1>
</div>

<table class="patch-meta">
<tr>
<th>Series ID</th>
<td>
{{ series.id }}
<span class="btn-link btn-copy glyphicon glyphicon-copy" data-clipboard-text="{{ request.build_absolute_uri }}{{ series.get_absolute_url }}" title="Copy to Clipboard"></span>
</td>
</tr>
<tr>
<th>Date</th>
<td>{{ series.date }}</td>
</tr>
<tr>
<th>Submitter</th>
<td>{{ series.submitter }}</td>
</tr>
<tr>
<th>Total</th>
<td>{{ series.patches }}</td>
</tr>
</table>
<br>
<h2>Patches</h2>
<br>
{% include "patchwork/partials/patch-list.html" %}

<div id="cover-letter">
<h2>Cover Letter</h2>
{% if series.cover_letter.content %}
<pre class="content">{{ series.cover_letter.content }}</pre>
{% else %}
No cover letter available
{% endif %}
</div>

{% endblock %}
113 changes: 113 additions & 0 deletions patchwork/templates/patchwork/series-list.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
{% extends "base.html" %}

{% load person %}
{% load static %}

{% block title %}{{project.name}}{% endblock %}
{% block series_active %}active{% endblock %}

{% block body %}

{% load person %}
{% load listurl %}
{% load patch %}
{% load series %}
{% load project %}
{% load static %}

{% include "patchwork/partials/pagination.html" %}

<input type="hidden" name="form" value="serieslistform"/>
<input type="hidden" name="project" value="{{project.id}}"/>

<table id="serieslist" class="table table-hover table-extra-condensed table-striped pw-list" data-toggle="checkboxes" data-range="true">
<thead>
<tr>
{% if user.is_authenticated and user.profile.show_ids %}
<th>
ID
</th>
{% endif %}

<th>
<span class="series-list-header">Series</span>
</th>

<th>
Version
</th>

<th>
{% project_tags %}
</th>

<th>
<span class="series-list-header" title="Success / Warning / Fail">S/W/F</span>
</th>

<th>
<span class="series-list-header" title="Declared review interest">Review Interest</span>
</th>

<th>
<span class="series-list-header">Patches</span>
</th>

<th>
{% if 'date.asc' == order %}
<a href="{% listurl order='date.desc' %}" class="colactive">
<span class="glyphicon glyphicon-chevron-up"></span>
{% elif 'date.desc' == order %}
<a href="{% listurl order='date.asc' %}" class="colactive">
<span class="glyphicon glyphicon-chevron-down"></span>
{% endif %}
<span class="series-list-header">Date</span>
{% if 'date.asc' == order or 'date.desc' == order%}
</a>
{% endif %}
</th>

<th>
<span class="series-list-header">Submitter</span>
</th>
</tr>
</thead>

<tbody>
{% for series in page %}
<tr id="series_row:{{series.id}}">
{% if user.is_authenticated and user.profile.show_ids %}
<td>
<button type="button" class="btn btn-xs btn-copy" data-clipboard-text="{{ series.id }}" title="Copy to Clipboard">
{{ series.id }}
</button>
</td>
{% endif %}
<td>
<a href="{% url 'series-detail' project_id=project.linkname series_id=series.id %}?state=*">
{{ series.name|default:"[no subject]"|truncatechars:100 }}
</a>
</td>
<td>
{{ series.version|default:"-"}}
</td>

<td id="series-tags:{{series.id}}" class="text-nowrap">{{ series|series_tags }}</td>
<td id="series-checks:{{series.id}}" class="text-nowrap">{{ series|series_checks }}</td>
<td id="series-interest:{{series.id}}" class="text-nowrap">{{ series|series_interest }}</td>
<td>{{ series.received_total}}</td>
<td class="text-nowrap">{{ series.date|date:"Y-m-d" }}</td>
<td>{{ series.submitter|personify:project }}</td>
</tr>
{% empty %}
<tr>
<td colspan="8">No series to display</td>
</tr>
{% endfor %}
</tbody>
</table>

{% if page.paginator.count %}
{% include "patchwork/partials/pagination.html" %}
{% endif %}
{% endblock %}
14 changes: 14 additions & 0 deletions patchwork/templates/patchwork/submission.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,20 @@
<h1>{{ submission.name }}</h1>
</div>

<div class="btn-group pull-right">
<a class="btn btn-default {% if not next_submission %} disabled {% endif %}"
{% if next_submission %} href="{% url 'patch-detail' project_id=project.linkname msgid=next_submission.encoded_msgid %}" {% endif %}>
next
</a>
</div>

<div class="btn-group pull-right">
<a class="btn btn-default {% if not previous_submission %} disabled {% endif %}"
{% if previous_submission %} href="{% url 'patch-detail' project_id=project.linkname msgid=previous_submission.encoded_msgid %}" {% endif %}>
previous
</a>
</div>

<table id="patch-meta" class="patch-meta" data-submission-type={{submission|verbose_name_plural|lower}} data-submission-id={{submission.id}}>
<tr>
<th>Message ID</th>
Expand Down
76 changes: 76 additions & 0 deletions patchwork/templatetags/series.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Patchwork - automated patch tracking system
# Copyright (C) 2008 Jeremy Kerr <[email protected]>
# Copyright (C) 2015 Intel Corporation
#
# SPDX-License-Identifier: GPL-2.0-or-later

from django import template
from django.utils.safestring import mark_safe

from patchwork.models import Check


register = template.Library()


@register.filter(name='series_tags')
def series_tags(series):
counts = []
titles = []

for tag in [t for t in series.project.tags if t.show_column]:
count = 0
for patch in series.patches.with_tag_counts(series.project).all():
count += getattr(patch, tag.attr_name)

titles.append('%d %s' % (count, tag.name))
if count == 0:
counts.append('-')
else:
counts.append(str(count))

return mark_safe(
'<span title="%s">%s</span>' % (' / '.join(titles), ' '.join(counts))
)


@register.filter(name='series_checks')
def series_checks(series):
required = [Check.STATE_SUCCESS, Check.STATE_WARNING, Check.STATE_FAIL]
titles = ['Success', 'Warning', 'Fail']
counts = series.check_count

check_elements = []
for state in required[::-1]:
if counts[state]:
color = dict(Check.STATE_CHOICES).get(state)
count = str(counts[state])
else:
color = ''
count = '-'

check_elements.append(
f'<span class="patchlistchecks {color}">{count}</span>'
)

check_elements.reverse()

return mark_safe(
'<span title="%s">%s</span>'
% (' / '.join(titles), ''.join(check_elements))
)


@register.filter(name='series_interest')
def series_interest(series):
reviews = series.interest_count
review_title = (
f'has {reviews} interested reviewers'
if reviews > 0
else 'no interested reviewers'
)
review_class = 'exists' if reviews > 0 else ''
return mark_safe(
'<span class="patchinterest %s" title="%s">%s</span>'
% (review_class, review_title, reviews if reviews > 0 else '-')
)
Loading