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:
		| @@ -51,4 +51,23 @@ class IDF_Forge | |||||||
|     public function setProjectLabels($labels) { |     public function setProjectLabels($labels) { | ||||||
|         $this->conf->setVal('project_labels', $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; | ||||||
|  |     } | ||||||
| } | } | ||||||
| @@ -108,13 +108,15 @@ class IDF_Project extends Pluf_Model | |||||||
|                                   'verbose' => __('current project activity'), |                                   '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( |         $this->_a['views'] = array( | ||||||
|             'join_activities' => |             'join_activities_and_tags' => | ||||||
|                 array( |                 array( | ||||||
|                     'join' => 'LEFT JOIN '.$table |                     'join' => 'LEFT JOIN '.$activityTable.' ON current_activity='.$activityTable.'.id ' | ||||||
|                              .' ON current_activity='.$table.'.id', |                              .'LEFT JOIN '.$tagTable.' ON idf_project_id='.$this->getSqlTable().'.id', | ||||||
|                     'select' => $this->getSelect().', date, value', |                     'select' => $this->getSelect().', date, value', | ||||||
|  |                     'group' => $this->getSqlTable().'.id', | ||||||
|                     'props' => array( |                     'props' => array( | ||||||
|                         'date' => 'current_activity_date', |                         'date' => 'current_activity_date', | ||||||
|                         'value' => 'current_activity_value' |                         'value' => 'current_activity_value' | ||||||
|   | |||||||
| @@ -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( |         $this->_a['idx'] =  array( | ||||||
|                                   'lcname_idx' => |                                   'lcname_idx' => | ||||||
|                                   array( |                                   array( | ||||||
|   | |||||||
| @@ -38,6 +38,7 @@ class IDF_Views | |||||||
|     { |     { | ||||||
|         // TODO: add a switch here later on to determine whether the project list |         // TODO: add a switch here later on to determine whether the project list | ||||||
|         //       or a custom start page should be displayed |         //       or a custom start page should be displayed | ||||||
|  |         $match = array('', 'all', 'name'); | ||||||
|         return $this->listProjects($request, $match); |         return $this->listProjects($request, $match); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -47,15 +48,30 @@ class IDF_Views | |||||||
|      * Only the public projects are listed or the private with correct |      * Only the public projects are listed or the private with correct | ||||||
|      * rights. |      * rights. | ||||||
|      */ |      */ | ||||||
|     public function listProjects($request, $match, $api=false) |     public function listProjects($request, $match) | ||||||
|     { |     { | ||||||
|         $projects = self::getProjects($request->user); |         list(, $tagId, $order) = $match; | ||||||
|         $stats = self::getProjectsStatistics($projects); |  | ||||||
|  |         $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', |         return Pluf_Shortcuts_RenderToResponse('idf/listProjects.html', | ||||||
|                                                array('page_title' => __('Projects'), |                                                array('page_title' => __('Projects'), | ||||||
|                                                      'projects' => $projects, |                                                      'projects' => $projects, | ||||||
|  |                                                      'projectLabels' => $projectLabels, | ||||||
|  |                                                      'tag' => $tag, | ||||||
|  |                                                      'order' => $order, | ||||||
|                                                      'stats' => new Pluf_Template_ContextVars($stats)), |                                                      'stats' => new Pluf_Template_ContextVars($stats)), | ||||||
|                                                $request); |                                                $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 Pluf_User | ||||||
|  |      * @param IDF_Tag | ||||||
|      * @return ArrayObject IDF_Project |      * @return ArrayObject IDF_Project | ||||||
|      */ |      */ | ||||||
|     public static function getProjects($user) |     public static function getProjects($user, $tag = false, $order = 'name') | ||||||
|     { |     { | ||||||
|         $db =& Pluf::db(); |         $db =& Pluf::db(); | ||||||
|         $false = Pluf_DB_BooleanToDb(false, $db); |         $false = Pluf_DB_BooleanToDb(false, $db); | ||||||
|         $sql = new Pluf_SQL(1); |         $sql = new Pluf_SQL(1); | ||||||
|  |         if ($tag !== false) { | ||||||
|  |             $sql->SAnd(new Pluf_SQL('idf_tag_id=%s', $tag->id)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         if ($user->isAnonymous()) |         if ($user->isAnonymous()) | ||||||
|         { |         { | ||||||
| @@ -373,10 +393,14 @@ class IDF_Views | |||||||
|             $sql->SAnd($authSql); |             $sql->SAnd($authSql); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         $orderTypes = array( | ||||||
|  |             'name' => 'name ASC', | ||||||
|  |             'activity' => 'value DESC, name ASC', | ||||||
|  |         ); | ||||||
|         return Pluf::factory('IDF_Project')->getList(array( |         return Pluf::factory('IDF_Project')->getList(array( | ||||||
|             'filter'=> $sql->gen(), |             'filter'=> $sql->gen(), | ||||||
|             'view' => 'join_activities', |             'view' => 'join_activities_and_tags', | ||||||
|             'order' => 'name ASC' |             'order' => $orderTypes[$order], | ||||||
|         )); |         )); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -405,9 +429,6 @@ class IDF_Views | |||||||
|             $forgestats['commits'] += $pstats['commits']; |             $forgestats['commits'] += $pstats['commits']; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Count projects |  | ||||||
|         $forgestats['projects'] = count($projects); |  | ||||||
|  |  | ||||||
|         // Count members |         // Count members | ||||||
|         $sql = new Pluf_SQL('first_name != %s', array('---')); |         $sql = new Pluf_SQL('first_name != %s', array('---')); | ||||||
|         $forgestats['members'] = Pluf::factory('Pluf_User') |         $forgestats['members'] = Pluf::factory('Pluf_User') | ||||||
|   | |||||||
| @@ -29,7 +29,7 @@ $ctl[] = array('regex' => '#^/$#', | |||||||
|                'model' => 'IDF_Views', |                'model' => 'IDF_Views', | ||||||
|                'method' => 'index'); |                'method' => 'index'); | ||||||
|  |  | ||||||
| $ctl[] = array('regex' => '#^/label/(\d+)/$#', | $ctl[] = array('regex' => '#^/label/(\w+)/(\w+)/$#', | ||||||
|                'base' => $base, |                'base' => $base, | ||||||
|                'model' => 'IDF_Views', |                'model' => 'IDF_Views', | ||||||
|                'method' => 'listProjects'); |                'method' => 'listProjects'); | ||||||
|   | |||||||
| @@ -22,11 +22,11 @@ | |||||||
| <a href="{$open_url}" class="label"><strong>{$tag.class}:</strong>{$tag.name}</a></p>{/blocktrans} | <a href="{$open_url}" class="label"><strong>{$tag.class}:</strong>{$tag.name}</a></p>{/blocktrans} | ||||||
| {else} | {else} | ||||||
| {* yes, this is duplicated from tags-cloud.html, but the code there cannot be easily overridden *} | {* 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> | <dt class="label">{$class}</dt> | ||||||
| {foreach $labels as $idx => $label} | {foreach $labels as $idx => $label} | ||||||
| {aurl 'url', 'IDF_Views_Issue::searchLabel', array($project.shortname, $label.id, $status), array('q'=> $query)} | {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> | <dd><a href="{$url}" class="label">{$label.name}{if $idx != count($labels) - 1},{/if}</a></dd> | ||||||
| {/foreach}{/foreach}</dl></p> | {/foreach}{/foreach}</dl> | ||||||
| {/if} | {/if} | ||||||
| {/block} | {/block} | ||||||
|   | |||||||
| @@ -38,9 +38,9 @@ | |||||||
|     <p class="smaller">{trans 'Labels:'} |     <p class="smaller">{trans 'Labels:'} | ||||||
|     {assign $tags = $p.get_tags_list()} |     {assign $tags = $p.get_tags_list()} | ||||||
|     {if count($tags) == 0}{trans 'n/a'}{else} |     {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} |             {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} |         {/foreach} | ||||||
|     {/if} |     {/if} | ||||||
|     </p> |     </p> | ||||||
| @@ -48,18 +48,46 @@ | |||||||
| {/foreach} | {/foreach} | ||||||
| {/if} | {/if} | ||||||
| {/block} | {/block} | ||||||
|  |  | ||||||
| {block context} | {block context} | ||||||
| <div id="stats" class="issue-submit-info"> | <strong>{trans 'Filter projects by label'}</strong> | ||||||
| <h3 class="a-c">{trans 'Forge statistics'}</h3> | {if count($projectLabels) == 0} | ||||||
| <table> | <p class="smaller">{trans 'No projects with labels found.'}</p> | ||||||
| <tr><td class="right">{trans 'Projects:'}</td><td>{$stats.projects}</td></tr> | {else} | ||||||
| <tr><td class="right">{trans 'Members:'}</td><td>{$stats.members}</td></tr> |     <dl class="tagscloud smaller">{foreach $projectLabels as $class => $labels} | ||||||
| <tr><td class="right">{trans 'Issues:'}</td><td>{$stats.issues}</td></tr> |         <dt class="label">{$class}</dt> | ||||||
| <tr><td class="right">{trans 'Commits:'}</td><td>{$stats.commits}</td></tr> |         {foreach $labels as $idx => $label} | ||||||
| <tr><td class="right">{trans 'Documentations:'}</td><td>{$stats.docpages}</td></tr> |             <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> | ||||||
| <tr><td class="right">{trans 'Downloads:'}</td><td>{$stats.downloads}</td></tr> |         {/foreach} | ||||||
| <tr><td class="right">{trans 'Code reviews:'}</td><td>{$stats.reviews}</td></tr> |     {/foreach}</dl> | ||||||
| </table> |     {if $tag} | ||||||
| </div> |         <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}  | ||||||
|  |  – | ||||||
|  | {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} | ||||||
|  |  | ||||||
| {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} | {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} | ||||||
|   | |||||||
| @@ -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> | <dt class="label">{$class}</dt> | ||||||
| {foreach $labels as $idx => $label} | {foreach $labels as $idx => $label} | ||||||
| {aurl 'url', $cloud_url, array($project.shortname, $label.id, 'open')} | {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> | <dd><a href="{$url}" class="label">{$label.name}{if $idx != count($labels) - 1},{/if}</a></dd> | ||||||
| {/foreach}{/foreach}</dl></p> | {/foreach}{/foreach}</dl> | ||||||
|   | |||||||
| @@ -213,23 +213,37 @@ span.px-header-title a, span.px-header-title a:link, span.px-header-title a:visi | |||||||
|   color: #000; |   color: #000; | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | dl.tagscloud, | ||||||
|  * Issue | dl.statistics { | ||||||
|  */ |  | ||||||
| #tagscloud dl { |  | ||||||
|     margin: 0; |     margin: 0; | ||||||
|  |     margin-bottom: 1em; | ||||||
| } | } | ||||||
|  |  | ||||||
| #tagscloud dt { | dl.tagscloud dt { | ||||||
|   margin-top: .5em; |   margin-top: .5em; | ||||||
|   font-weight: bold; |   font-weight: bold; | ||||||
| } | } | ||||||
|  |  | ||||||
| #tagscloud dd { | dl.tagscloud dd { | ||||||
|   margin: 0; |   margin: 0; | ||||||
|   display: inline; |   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 { | a.issue-c { | ||||||
|   text-decoration: line-through; |   text-decoration: line-through; | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user