Add filtering and sorting capabilities to the project list page and

also render the project activity with a small bar graph below the
logo image. Sanitize the tagcloud css.
This commit is contained in:
Thomas Keller 2011-12-24 02:45:01 +01:00
parent 608e7a40e4
commit 6e305eb541
9 changed files with 138 additions and 42 deletions

View File

@ -51,4 +51,23 @@ class IDF_Forge
public function setProjectLabels($labels) {
$this->conf->setVal('project_labels', $labels);
}
public function getProjectLabelsWithCounts() {
$sql = new Pluf_SQL('project=0');
$tagList = Pluf::factory('IDF_Tag')->getList(array(
'filter' => $sql->gen(),
'view' => 'join_projects',
'order' => 'class ASC, lcname ASC'
));
$tags = array();
foreach ($tagList as $tag) {
// group by class
if (!array_key_exists($tag->class, $tags)) {
$tags[$tag->class] = array();
}
$tags[$tag->class][] = $tag;
}
return $tags;
}
}

View File

@ -108,13 +108,15 @@ class IDF_Project extends Pluf_Model
'verbose' => __('current project activity'),
),
);
$table = $this->_con->pfx.'idf_projectactivities';
$activityTable = $this->_con->pfx.'idf_projectactivities';
$tagTable = $this->_con->pfx.'idf_project_idf_tag_assoc';
$this->_a['views'] = array(
'join_activities' =>
'join_activities_and_tags' =>
array(
'join' => 'LEFT JOIN '.$table
.' ON current_activity='.$table.'.id',
'join' => 'LEFT JOIN '.$activityTable.' ON current_activity='.$activityTable.'.id '
.'LEFT JOIN '.$tagTable.' ON idf_project_id='.$this->getSqlTable().'.id',
'select' => $this->getSelect().', date, value',
'group' => $this->getSqlTable().'.id',
'props' => array(
'date' => 'current_activity_date',
'value' => 'current_activity_value'

View File

@ -75,6 +75,18 @@ class IDF_Tag extends Pluf_Model
),
);
$table = $this->_con->pfx.'idf_project_idf_tag_assoc';
$this->_a['views'] = array(
'join_projects' =>
array(
'join' => 'LEFT JOIN '.$table
.' ON idf_tag_id=id',
'select' => $this->getSelect().',COUNT(idf_project_id) as project_count',
'group' => 'idf_tag_id',
'props' => array('project_count' => 'project_count'),
),
);
$this->_a['idx'] = array(
'lcname_idx' =>
array(

View File

@ -38,6 +38,7 @@ class IDF_Views
{
// TODO: add a switch here later on to determine whether the project list
// or a custom start page should be displayed
$match = array('', 'all', 'name');
return $this->listProjects($request, $match);
}
@ -47,15 +48,30 @@ class IDF_Views
* Only the public projects are listed or the private with correct
* rights.
*/
public function listProjects($request, $match, $api=false)
public function listProjects($request, $match)
{
$projects = self::getProjects($request->user);
$stats = self::getProjectsStatistics($projects);
list(, $tagId, $order) = $match;
$tag = false;
if ($tagId !== 'all') {
$tag = Pluf::factory('IDF_Tag')->get($match[1]);
// ignore non-global tags
if ($tag !== false && $tag->project > 0) {
$tag = false;
}
}
$order = in_array($order, array('name', 'activity')) ? $order : 'name';
$projects = self::getProjects($request->user, $tag, $order);
$stats = self::getProjectsStatistics($projects);
$projectLabels = IDF_Forge::instance()->getProjectLabelsWithCounts();
if ($api == true) return $projects;
return Pluf_Shortcuts_RenderToResponse('idf/listProjects.html',
array('page_title' => __('Projects'),
'projects' => $projects,
'projectLabels' => $projectLabels,
'tag' => $tag,
'order' => $order,
'stats' => new Pluf_Template_ContextVars($stats)),
$request);
}
@ -335,16 +351,20 @@ class IDF_Views
}
/**
* Returns a list of projects accessible for the user.
* Returns a list of projects accessible for the user and optionally filtered by tag.
*
* @param Pluf_User
* @param IDF_Tag
* @return ArrayObject IDF_Project
*/
public static function getProjects($user)
public static function getProjects($user, $tag = false, $order = 'name')
{
$db =& Pluf::db();
$false = Pluf_DB_BooleanToDb(false, $db);
$sql = new Pluf_SQL(1);
if ($tag !== false) {
$sql->SAnd(new Pluf_SQL('idf_tag_id=%s', $tag->id));
}
if ($user->isAnonymous())
{
@ -373,10 +393,14 @@ class IDF_Views
$sql->SAnd($authSql);
}
$orderTypes = array(
'name' => 'name ASC',
'activity' => 'value DESC, name ASC',
);
return Pluf::factory('IDF_Project')->getList(array(
'filter'=> $sql->gen(),
'view' => 'join_activities',
'order' => 'name ASC'
'view' => 'join_activities_and_tags',
'order' => $orderTypes[$order],
));
}
@ -397,7 +421,7 @@ class IDF_Views
// Count for each projects
foreach ($projects as $p) {
$pstats = $p->getStats ();
$pstats = $p->getStats();
$forgestats['downloads'] += $pstats['downloads'];
$forgestats['reviews'] += $pstats['reviews'];
$forgestats['issues'] += $pstats['issues'];
@ -405,9 +429,6 @@ class IDF_Views
$forgestats['commits'] += $pstats['commits'];
}
// Count projects
$forgestats['projects'] = count($projects);
// Count members
$sql = new Pluf_SQL('first_name != %s', array('---'));
$forgestats['members'] = Pluf::factory('Pluf_User')

View File

@ -29,7 +29,7 @@ $ctl[] = array('regex' => '#^/$#',
'model' => 'IDF_Views',
'method' => 'index');
$ctl[] = array('regex' => '#^/label/(\d+)/$#',
$ctl[] = array('regex' => '#^/label/(\w+)/(\w+)/$#',
'base' => $base,
'model' => 'IDF_Views',
'method' => 'listProjects');

View File

@ -22,11 +22,11 @@
<a href="{$open_url}" class="label"><strong>{$tag.class}:</strong>{$tag.name}</a></p>{/blocktrans}
{else}
{* yes, this is duplicated from tags-cloud.html, but the code there cannot be easily overridden *}
<div id="tagscloud" class="smaller"><dl>{foreach $all_tags as $class => $labels}
<dl class="tagscloud smaller">{foreach $all_tags as $class => $labels}
<dt class="label">{$class}</dt>
{foreach $labels as $idx => $label}
{aurl 'url', 'IDF_Views_Issue::searchLabel', array($project.shortname, $label.id, $status), array('q'=> $query)}
<dd><a href="{$url}" class="label">{$label.name}{if $idx != count($labels) - 1},{/if}</a></dd>
{/foreach}{/foreach}</dl></p>
{/foreach}{/foreach}</dl>
{/if}
{/block}

View File

@ -38,9 +38,9 @@
<p class="smaller">{trans 'Labels:'}
{assign $tags = $p.get_tags_list()}
{if count($tags) == 0}{trans 'n/a'}{else}
{foreach $p.get_tags_list() as $idx => $tag}
{foreach $p.get_tags_list() as $idx => $label}
{if $idx != 0}, {/if}
<a class="label" href="{url 'IDF_Views::listProjects', array($tag->id)}">{$tag}</a>
<a class="label" href="{url 'IDF_Views::listProjects', array($label->id, $order)}">{$label}</a>
{/foreach}
{/if}
</p>
@ -48,18 +48,46 @@
{/foreach}
{/if}
{/block}
{block context}
<div id="stats" class="issue-submit-info">
<h3 class="a-c">{trans 'Forge statistics'}</h3>
<table>
<tr><td class="right">{trans 'Projects:'}</td><td>{$stats.projects}</td></tr>
<tr><td class="right">{trans 'Members:'}</td><td>{$stats.members}</td></tr>
<tr><td class="right">{trans 'Issues:'}</td><td>{$stats.issues}</td></tr>
<tr><td class="right">{trans 'Commits:'}</td><td>{$stats.commits}</td></tr>
<tr><td class="right">{trans 'Documentations:'}</td><td>{$stats.docpages}</td></tr>
<tr><td class="right">{trans 'Downloads:'}</td><td>{$stats.downloads}</td></tr>
<tr><td class="right">{trans 'Code reviews:'}</td><td>{$stats.reviews}</td></tr>
</table>
</div>
<strong>{trans 'Filter projects by label'}</strong>
{if count($projectLabels) == 0}
<p class="smaller">{trans 'No projects with labels found.'}</p>
{else}
<dl class="tagscloud smaller">{foreach $projectLabels as $class => $labels}
<dt class="label">{$class}</dt>
{foreach $labels as $idx => $label}
<dd><a href="{url 'IDF_Views::listProjects', array($label->id, $order)}" class="label" title="{blocktrans $label.project_count}1 project{plural}{$label.project_count} projects{/blocktrans}">{$label.name}{if $idx != count($labels) - 1},{/if}</a></dd>
{/foreach}
{/foreach}</dl>
{if $tag}
<p class="smaller"><a href="{url 'IDF_Views::listProjects', array('all', $order)}">
{blocktrans}Remove filter for {$tag}{/blocktrans}</a></p>
{/if}
{/if}
<br />
<strong>{trans 'Order'}</strong>
{assign $labelPart = 'all'}
{if $tag}{assign $labelPart = $tag->id}{/if}
<p class="smaller">
{if $order != 'name'}<a href="{url 'IDF_Views::listProjects', array($labelPart, 'name')}">{/if}
{trans 'By name'}{if $order != 'name'}</a>{/if}
&ndash;
{if $order != 'activity'}<a href="{url 'IDF_Views::listProjects', array($labelPart, 'activity')}">{/if}
{trans 'By activity'}{if $order != 'activity'}</a>{/if}
</p>
<br />
<strong>{trans 'Filtered project stats'}</strong>
<dl class="statistics smaller">
<dt>{trans 'Members:'}</dt><dd>{$stats.members}</dd>
<dt>{trans 'Issues:'}</dt><dd>{$stats.issues}</dd>
<dt>{trans 'Commits:'}</dt><dd>{$stats.commits}</dd>
<dt>{trans 'Documentations:'}</dt><dd>{$stats.docpages}</dd>
<dt>{trans 'Downloads:'}</dt><dd>{$stats.downloads}</dd>
<dt>{trans 'Code reviews:'}</dt><dd>{$stats.reviews}</dd>
</dl>
{/block}
{block foot}<div id="branding">Powered by <a href="http://www.indefero.net" title="InDefero, bug tracking and more">InDefero</a>,<br />a <a href="http://www.ceondo.com">Céondo Ltd</a> initiative.</div>{/block}

View File

@ -1,6 +1,6 @@
<div id="tagscloud" class="smaller"><dl>{foreach $project.getTagCloud($cloud) as $class => $labels}
<dl class="tagscloud smaller"><dl>{foreach $project.getTagCloud($cloud) as $class => $labels}
<dt class="label">{$class}</dt>
{foreach $labels as $idx => $label}
{aurl 'url', $cloud_url, array($project.shortname, $label.id, 'open')}
<dd><a href="{$url}" class="label">{$label.name}{if $idx != count($labels) - 1},{/if}</a></dd>
{/foreach}{/foreach}</dl></p>
{/foreach}{/foreach}</dl>

View File

@ -213,23 +213,37 @@ span.px-header-title a, span.px-header-title a:link, span.px-header-title a:visi
color: #000;
}
/**
* Issue
*/
#tagscloud dl {
margin: 0;
dl.tagscloud,
dl.statistics {
margin: 0;
margin-bottom: 1em;
}
#tagscloud dt {
dl.tagscloud dt {
margin-top: .5em;
font-weight: bold;
}
#tagscloud dd {
dl.tagscloud dd {
margin: 0;
display: inline;
}
dl.statistics dt {
margin-top: .5em;
font-style: italic;
}
dl.statistics dd {
margin: 0;
float: right;
margin-top: -1.2em;
margin-right: 2em;
}
/**
* Issue
*/
a.issue-c {
text-decoration: line-through;
}