Save a link to the latest activity value in the project model and
render a simple green bar as activity measure in the project list view.
This commit is contained in:
parent
6875e62942
commit
608e7a40e4
40
src/IDF/Migrations/24CurrentProjectActivity.php
Normal file
40
src/IDF/Migrations/24CurrentProjectActivity.php
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||||
|
/*
|
||||||
|
# ***** BEGIN LICENSE BLOCK *****
|
||||||
|
# This file is part of InDefero, an open source project management application.
|
||||||
|
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
|
||||||
|
#
|
||||||
|
# InDefero is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# InDefero is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
#
|
||||||
|
# ***** END LICENSE BLOCK ***** */
|
||||||
|
|
||||||
|
function IDF_Migrations_24CurrentProjectActivity_up($params=null)
|
||||||
|
{
|
||||||
|
$engine = Pluf::f('db_engine');
|
||||||
|
$db = Pluf::db();
|
||||||
|
|
||||||
|
if ($engine === 'PostgreSQL') {
|
||||||
|
$db->execute('ALTER TABLE '.$db->pfx.'idf_projects ADD COLUMN current_activity INTEGER');
|
||||||
|
} else if ($engine === 'MySQL') {
|
||||||
|
$db->execute('ALTER TABLE '.$db->pfx.'idf_projects ADD COLUMN current_activity MEDIUMINT');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function IDF_Migrations_24CurrentProjectActivity_down($params=null)
|
||||||
|
{
|
||||||
|
$db = Pluf::db();
|
||||||
|
$db->execute('ALTER TABLE '.$db->pfx.'idf_projects DROP COLUMN current_activity');
|
||||||
|
}
|
@ -100,7 +100,27 @@ class IDF_Project extends Pluf_Model
|
|||||||
'verbose' => __('private'),
|
'verbose' => __('private'),
|
||||||
'default' => 0,
|
'default' => 0,
|
||||||
),
|
),
|
||||||
);
|
'current_activity' =>
|
||||||
|
array(
|
||||||
|
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||||
|
'model' => 'IDF_ProjectActivity',
|
||||||
|
'blank' => true,
|
||||||
|
'verbose' => __('current project activity'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
$table = $this->_con->pfx.'idf_projectactivities';
|
||||||
|
$this->_a['views'] = array(
|
||||||
|
'join_activities' =>
|
||||||
|
array(
|
||||||
|
'join' => 'LEFT JOIN '.$table
|
||||||
|
.' ON current_activity='.$table.'.id',
|
||||||
|
'select' => $this->getSelect().', date, value',
|
||||||
|
'props' => array(
|
||||||
|
'date' => 'current_activity_date',
|
||||||
|
'value' => 'current_activity_value'
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -64,4 +64,15 @@ class IDF_ProjectActivity extends Pluf_Model
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function postSave($create=false)
|
||||||
|
{
|
||||||
|
$prj = $this->get_project();
|
||||||
|
$sql = new Pluf_SQL('project=%s', array($prj->id));
|
||||||
|
$latest = Pluf::factory('IDF_ProjectActivity')->getOne(array('filter' => $sql->gen(), 'order' => 'date desc'));
|
||||||
|
if ($prj->current_activity != $latest->id) {
|
||||||
|
$prj->current_activity = $latest;
|
||||||
|
$prj->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,19 +31,29 @@ Pluf::loadFunction('Pluf_Shortcuts_GetFormForModel');
|
|||||||
*/
|
*/
|
||||||
class IDF_Views
|
class IDF_Views
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* The index view.
|
||||||
|
*/
|
||||||
|
public function index($request, $match)
|
||||||
|
{
|
||||||
|
// TODO: add a switch here later on to determine whether the project list
|
||||||
|
// or a custom start page should be displayed
|
||||||
|
return $this->listProjects($request, $match);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List all the projects managed by InDefero.
|
* List all the projects managed by InDefero.
|
||||||
*
|
*
|
||||||
* 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 index($request, $match, $api=false)
|
public function listProjects($request, $match, $api=false)
|
||||||
{
|
{
|
||||||
$projects = self::getProjects($request->user);
|
$projects = self::getProjects($request->user);
|
||||||
$stats = self::getProjectsStatistics($projects);
|
$stats = self::getProjectsStatistics($projects);
|
||||||
|
|
||||||
if ($api == true) return $projects;
|
if ($api == true) return $projects;
|
||||||
return Pluf_Shortcuts_RenderToResponse('idf/index.html',
|
return Pluf_Shortcuts_RenderToResponse('idf/listProjects.html',
|
||||||
array('page_title' => __('Projects'),
|
array('page_title' => __('Projects'),
|
||||||
'projects' => $projects,
|
'projects' => $projects,
|
||||||
'stats' => new Pluf_Template_ContextVars($stats)),
|
'stats' => new Pluf_Template_ContextVars($stats)),
|
||||||
@ -334,34 +344,40 @@ class IDF_Views
|
|||||||
{
|
{
|
||||||
$db =& Pluf::db();
|
$db =& Pluf::db();
|
||||||
$false = Pluf_DB_BooleanToDb(false, $db);
|
$false = Pluf_DB_BooleanToDb(false, $db);
|
||||||
if ($user->isAnonymous()) {
|
$sql = new Pluf_SQL(1);
|
||||||
$sql = sprintf('%s=%s', $db->qn('private'), $false);
|
|
||||||
return Pluf::factory('IDF_Project')->getList(array('filter'=> $sql,
|
|
||||||
'order' => 'name ASC'));
|
|
||||||
}
|
|
||||||
if ($user->administrator) {
|
|
||||||
return Pluf::factory('IDF_Project')->getList(array('order' => 'name ASC'));
|
|
||||||
}
|
|
||||||
// grab the list of projects where the user is admin, member
|
|
||||||
// or authorized
|
|
||||||
$perms = array(
|
|
||||||
Pluf_Permission::getFromString('IDF.project-member'),
|
|
||||||
Pluf_Permission::getFromString('IDF.project-owner'),
|
|
||||||
Pluf_Permission::getFromString('IDF.project-authorized-user')
|
|
||||||
);
|
|
||||||
$sql = new Pluf_SQL("model_class='IDF_Project' AND owner_class='Pluf_User' AND owner_id=%s AND negative=".$false, $user->id);
|
|
||||||
$rows = Pluf::factory('Pluf_RowPermission')->getList(array('filter' => $sql->gen()));
|
|
||||||
|
|
||||||
$sql = sprintf('%s=%s', $db->qn('private'), $false);
|
if ($user->isAnonymous())
|
||||||
if ($rows->count() > 0) {
|
{
|
||||||
$ids = array();
|
$authSql = new Pluf_SQL('private=%s', $false);
|
||||||
foreach ($rows as $row) {
|
$sql->SAnd($authSql);
|
||||||
$ids[] = $row->model_id;
|
} else
|
||||||
|
if (!$user->administrator) {
|
||||||
|
// grab the list of projects where the user is admin,
|
||||||
|
// member or authorized
|
||||||
|
$perms = array(
|
||||||
|
Pluf_Permission::getFromString('IDF.project-member'),
|
||||||
|
Pluf_Permission::getFromString('IDF.project-owner'),
|
||||||
|
Pluf_Permission::getFromString('IDF.project-authorized-user')
|
||||||
|
);
|
||||||
|
$permSql = new Pluf_SQL("model_class='IDF_Project' AND owner_class='Pluf_User' AND owner_id=%s AND negative=".$false, $user->id);
|
||||||
|
$rows = Pluf::factory('Pluf_RowPermission')->getList(array('filter' => $permSql->gen()));
|
||||||
|
|
||||||
|
$authSql = new Pluf_SQL('private=%s', $false);
|
||||||
|
if ($rows->count() > 0) {
|
||||||
|
$ids = array();
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$ids[] = $row->model_id;
|
||||||
|
}
|
||||||
|
$authSql->SOr(new Pluf_SQL(sprintf('id IN (%s)', implode(', ', $ids))));
|
||||||
}
|
}
|
||||||
$sql .= sprintf(' OR id IN (%s)', implode(', ', $ids));
|
$sql->SAnd($authSql);
|
||||||
}
|
}
|
||||||
return Pluf::factory('IDF_Project')->getList(array('filter' => $sql,
|
|
||||||
'order' => 'name ASC'));
|
return Pluf::factory('IDF_Project')->getList(array(
|
||||||
|
'filter'=> $sql->gen(),
|
||||||
|
'view' => 'join_activities',
|
||||||
|
'order' => 'name ASC'
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
* JSON instead of HTML.
|
* JSON instead of HTML.
|
||||||
*
|
*
|
||||||
* A special precondition is used to set the $request->user from the
|
* A special precondition is used to set the $request->user from the
|
||||||
* _login, _hash and _salt parameters.
|
* _login, _hash and _salt parameters.
|
||||||
*/
|
*/
|
||||||
class IDF_Views_Api
|
class IDF_Views_Api
|
||||||
{
|
{
|
||||||
@ -90,17 +90,16 @@ class IDF_Views_Api
|
|||||||
* List all the projects
|
* List all the projects
|
||||||
*/
|
*/
|
||||||
public $projectIndex_precond = array('IDF_Precondition::apiSetUser');
|
public $projectIndex_precond = array('IDF_Precondition::apiSetUser');
|
||||||
|
|
||||||
public function projectIndex($request, $match)
|
public function projectIndex($request, $match)
|
||||||
{
|
{
|
||||||
$view = new IDF_Views();
|
$projects = IDF_Views::getProjects($request->user);
|
||||||
$projects = $view->index($request, $match, true);
|
|
||||||
|
|
||||||
$data = array();
|
$data = array();
|
||||||
foreach ($projects as $p) {
|
foreach ($projects as $p) {
|
||||||
$data[] = array("shortname" => $p->shortname, "name" => $p->name, "shortdesc" => $p->shortdesc, "private" => $p->private);
|
$data[] = array("shortname" => $p->shortname, "name" => $p->name, "shortdesc" => $p->shortdesc, "private" => $p->private);
|
||||||
}
|
}
|
||||||
|
|
||||||
$out = array();
|
$out = array();
|
||||||
$out['message'] = 'success';
|
$out['message'] = 'success';
|
||||||
$out['projects'] = $data;
|
$out['projects'] = $data;
|
||||||
|
@ -29,6 +29,11 @@ $ctl[] = array('regex' => '#^/$#',
|
|||||||
'model' => 'IDF_Views',
|
'model' => 'IDF_Views',
|
||||||
'method' => 'index');
|
'method' => 'index');
|
||||||
|
|
||||||
|
$ctl[] = array('regex' => '#^/label/(\d+)/$#',
|
||||||
|
'base' => $base,
|
||||||
|
'model' => 'IDF_Views',
|
||||||
|
'method' => 'listProjects');
|
||||||
|
|
||||||
$ctl[] = array('regex' => '#^/login/$#',
|
$ctl[] = array('regex' => '#^/login/$#',
|
||||||
'base' => $base,
|
'base' => $base,
|
||||||
'model' => 'IDF_Views',
|
'model' => 'IDF_Views',
|
||||||
|
@ -10,30 +10,41 @@
|
|||||||
<p><a href="{$url}"><img style="vertical-align: text-bottom;" src="{media '/idf/img/add.png'}" alt="+" align="bottom" /></a> <a href="{$url}">{trans 'Create Project'}</a></p>{/if}
|
<p><a href="{$url}"><img style="vertical-align: text-bottom;" src="{media '/idf/img/add.png'}" alt="+" align="bottom" /></a> <a href="{$url}">{trans 'Create Project'}</a></p>{/if}
|
||||||
{else}
|
{else}
|
||||||
{foreach $projects as $p}
|
{foreach $projects as $p}
|
||||||
<div class="p-list-img">
|
|
||||||
<a href="{url 'IDF_Views_Project::home', array($p.shortname)}">
|
|
||||||
<img src="{url 'IDF_Views_Project::logo', array($p.shortname)}" alt="{trans 'Project logo'}" />
|
|
||||||
</a>
|
|
||||||
{if $p.private}
|
|
||||||
<div class="p-list-private">
|
|
||||||
<a href="{url 'IDF_Views_Project::home', array($p.shortname)}">
|
|
||||||
<img style="float:right" src="{media '/idf/img/lock.png'}" alt="{trans 'Private project'}" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<div class="p-list-prj">
|
<div class="p-list-prj">
|
||||||
|
<div class="logo">
|
||||||
|
<a href="{url 'IDF_Views_Project::home', array($p.shortname)}">
|
||||||
|
<img src="{url 'IDF_Views_Project::logo', array($p.shortname)}" alt="{trans 'Project logo'}" />
|
||||||
|
</a>
|
||||||
|
{if $p.private}
|
||||||
|
<div class="private">
|
||||||
|
<a href="{url 'IDF_Views_Project::home', array($p.shortname)}">
|
||||||
|
<img style="float:right" src="{media '/idf/img/lock.png'}" alt="{trans 'Private project'}" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{if $p.current_activity_value}
|
||||||
|
<div class="activity" title="{trans 'Project activity'}"><div class="bar" style="width: {$p.current_activity_value * 100}%"></div></div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
<p>
|
<p>
|
||||||
<a href="{url 'IDF_Views_Project::home', array($p.shortname)}"><strong>{$p}</strong></a>
|
<a href="{url 'IDF_Views_Project::home', array($p.shortname)}"><strong>{$p}</strong></a>
|
||||||
{assign $url = $p.external_project_url}
|
{assign $url = $p.external_project_url}
|
||||||
{if $url != ''}
|
{if $url != ''}
|
||||||
<a href="{$url}" target="_blank" class="external-link" title="{trans 'External link to project'}" /> </a>
|
<a href="{$url}" target="_blank" class="external-link" title="{trans 'External link to project'}" /> </a>
|
||||||
{/if}
|
{/if}
|
||||||
{if $p.private} - {trans 'Private project'}{/if}
|
{if $p.private} - <span class="smaller">{trans 'Private project'}</span>{/if}
|
||||||
|
</p>
|
||||||
|
<p class="smaller">{$p.shortdesc}</p>
|
||||||
|
<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}
|
||||||
|
{if $idx != 0}, {/if}
|
||||||
|
<a class="label" href="{url 'IDF_Views::listProjects', array($tag->id)}">{$tag}</a>
|
||||||
|
{/foreach}
|
||||||
|
{/if}
|
||||||
</p>
|
</p>
|
||||||
<p>{$p.shortdesc}</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div style="clear: both"></div>
|
|
||||||
{/foreach}
|
{/foreach}
|
||||||
{/if}
|
{/if}
|
||||||
{/block}
|
{/block}
|
@ -1214,27 +1214,58 @@ span.scm-action.property-changed {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Project list on index
|
* Project list
|
||||||
*/
|
*/
|
||||||
div.p-list-img {
|
div.p-list-prj {
|
||||||
|
width: 24em;
|
||||||
|
min-height: 5em;
|
||||||
float: left;
|
float: left;
|
||||||
height: 32px;
|
margin: 0 1em 0.5em 0;
|
||||||
margin-top: .5em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
div.p-list-prj {
|
div.p-list-prj div.logo {
|
||||||
float: left;
|
float: left;
|
||||||
margin: .5em 0 .5em .8em;
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.p-list-prj div.logo img {
|
||||||
|
max-width: 32px;
|
||||||
|
max-height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.p-list-prj div.logo .private {
|
||||||
|
top: 18px;
|
||||||
|
right: -3px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.p-list-prj div.logo .activity {
|
||||||
|
height: 4px;
|
||||||
|
width: 32px;
|
||||||
|
margin-top: 5px;
|
||||||
|
-moz-border-radius: 2px;
|
||||||
|
-webkit-border-radius: 2px;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: #E6E6E6;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.p-list-prj div.logo .activity .bar {
|
||||||
|
background: #A5E26A;
|
||||||
|
height: 100%;
|
||||||
|
-moz-border-radius: 2px;
|
||||||
|
-webkit-border-radius: 2px;
|
||||||
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.p-list-prj p {
|
div.p-list-prj p {
|
||||||
margin: 0px;
|
margin: 0;
|
||||||
|
margin-left: 42px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.p-list-private {
|
div.p-list-prj .smaller {
|
||||||
bottom: 16px;
|
font-size: 85%;
|
||||||
right: -3px;
|
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
a.external-link {
|
a.external-link {
|
||||||
|
Loading…
Reference in New Issue
Block a user