blob: 35b351e986c4f673b133e1c0daca8a0a5e4ff77b [file] [log] [blame]
<?php
/*******************************************************************************
* Copyright (c) 2011, 2016 Eclipse Foundation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Wayne Beaton (Eclipse Foundation) - initial API and implementation
*******************************************************************************/
require_once($_SERVER['DOCUMENT_ROOT'] . "/eclipse.org-common/system/app.class.php");
require_once($_SERVER['DOCUMENT_ROOT'] . "/eclipse.org-common/system/nav.class.php");
require_once($_SERVER['DOCUMENT_ROOT'] . "/eclipse.org-common/system/menu.class.php");
$App = new App();
$Nav = new Nav();
$Menu = new Menu();
$pageTitle = "Eclipse Project Status";
$pageAuthor = "Wayne Beaton";
$pageKeywords = "eclipse,scm,vcs,dvcs,git,cvs,svn";
require_once $_SERVER['DOCUMENT_ROOT'] . '/projects/classes/Project.class.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/projects/classes/images.inc';
require_once $_SERVER['DOCUMENT_ROOT'] . '/projects/classes/common.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/projects/classes/debug.php';
//mustBeCommitter();
$columnDefinitions = array(
'name' => array(
'label' => 'Project'
),
'id' => array(
'label' => 'Project',
'function' => function($value, $key, $project) {
$link = $project['url'];
return "<a href=\"$link\">$value</a>";
}
),
'top' => array('label' => 'Top-Level'),
'phase' => array(
'label' => 'Phase',
'function' =>
function($value, $key, $project) {
global $images;
return $value == 'incubating' ? "<img src=\"$images->incubation_conforming_small\"/>" : '&nbsp;';
}
),
'liveliness' => array(
'label' => 'Liveliness',
'function' =>
function($value, $key, $project) {
$liveliness = getLivelinessIcon($value);
return "<img src=\"$liveliness\"/>";
}
),
'committers' => array('label' => 'Committers Count'),
'organizations' => array('label' => 'Orgs Count'),
'initial_cq' => array(
'label' => 'Initial Contribution',
'function' =>
function($value, $key, $project) {
if (!isset($value)) return "&nbsp;";
return "<a href=\"$value\"><img src=\"/projects/images/external.gif\"/></a>";
}
),
'initial_cq_created' => array('label' => 'Initial Contribution Created'),
'initial_cq_pmc' => array('label' => 'Initial Contribution PMC Approved'),
'initial_cq_checkin' => array('label' => 'Initial Contribution Check-in'),
'initial_cq_approved' => array('label' => 'Initial Contribution Approved'),
'initial_cq_latency' => array(
'label' => 'Time Since IC Change',
'function' =>
function($value, $key, $project) {
if (@$project['initial_cq_approved']) return '--';
if ($checkin = @$project['initial_cq_checkin'])
return getTimeSince($checkin);
if ($created = @$project['initial_cq_created'])
return getTimeSince($created);
return '--';
},
'sort' =>
function($value, $key, $project) {
if (@$project['initial_cq_approved']) return 0;
if ($checkin = @$project['initial_cq_checkin'])
return (int)(time() - strtotime($checkin));
if ($created = @$project['initial_cq_created'])
return (int)(time() - strtotime($created));
return 0;
},
'class' => 'column-align-right'
),
'commits_initial' => array('label' => 'Initial Commit'),
'commits_latest' => array('label' => 'Latest Commit'),
'commits' => array(
'label' => 'Commits Count',
'function' =>
function($value, $key, $project) {
if ($value !== null)
return number_format($value);
return '--';
},
'sort' =>
function($value, $key, $project) {
return (int)$value;
},
'class' => 'column-align-right'
),
'commits_latency' => array(
'label' => 'Time Since Last Commit',
'function' =>
function($value, $key, $project) {
if ($latest = @$project['commits_latest'])
return getTimeSince($latest);
return '--';
},
'sort' =>
function($value, $key, $project) {
if ($latest = @$project['commits_latest'])
return (int)(time() - strtotime($latest));
return 0;
},
'class' => 'column-align-right'
),
'downloads_first' => array(
'label' => 'Oldest Download',
),
);
function getColumnDefinitions() {
global $columnDefinitions;
return $columnDefinitions;
}
function getColumnDefinition($key) {
global $columnDefinitions;
return $columnDefinitions[$key];
}
function getTimeSince($date) {
if (!$date) return 0;
$milliseconds = date(time() - strtotime($date));
$days = number_format(ceil($milliseconds / (60 * 60 * 24)));
return "$days days";
}
function filter($info) {
foreach($_GET as $key=>$value) {
if (isset($info[$key])) {
if ($info[$key] != $value) return true;
}
}
return false;
}
function getProjectStatus() {
global $App;
$projects = array();
foreach(getActiveProjects() as $project) {
switch ($id = $project->getId()) {
case 'polarsys' :
break;
default:
$info = array(
'project' => $project,
'id' => $id,
'name' => $project->getName(),
'top' => $project->getTopLevelProject()->getId(),
'topName' => preg_replace('/ Root$/','', $project->getTopLevelProject()->getName()),
'phase' => $project->isInIncubationPhase() ? 'incubating' : 'regular',
'url' => $project->getUrl(),
'liveliness' => $project->getLiveliness()
);
if (!filter($info)) $projects[$id] = $info;
}
}
addDashData($projects);
addInitialContributionStats($projects);
return $projects;
}
/**
* Add stats that are tracked by Dash. This generally refers to values
* that are calculated in batch and cached on the dashboard database.
*
* @param mixed $projects
*/
function addDashData(&$projects) {
global $App;
$sql = "
select distinct
p.subproject as id,
date(ps.initialCommit) as commits_initial,
date(ps.latestCommit) as commits_latest,
ps.totalCommits as commits,
count(distinct pca.company) as organizations,
count(distinct pda.login) as committers,
min(pd.first) as downloads_first
from ProjectRollup as p
left join ProjectCompanyActivity as pca on p.subproject=pca.project
left join ProjectCommitterActivity as pda on p.subproject=pda.project
left join ProjectStats as ps on p.subproject=ps.project
left join ProjectInfoDownloads as pd on p.subproject=pd.project
group by p.subproject";
$result = $App->dashboard_sql($sql);
while ($row = mysql_fetch_assoc($result)) {
$id = $row['id'];
if (isset($projects[$id])) {
$projects[$id] += $row;
}
}
}
/**
* Add information about the initial contribution to the
* project data. Note that data is only provided for projects
* that are already represented in the array.
*
* @param mixed $projects
*/
function addInitialContributionStats(&$projects) {
global $App;
$sql = "select
f.name as id,
b.bug_id as cq,
b.short_desc as title,
date(b.creation_ts) as created,
max(date(pmc.bug_when)) as pmc,
max(date(checkin.bug_when)) as checkin,
max(date(approved.bug_when)) as approved
from bugs as b
join (select
c.name,
min(bb.bug_id) as cq
from bugs as bb
join products as p on bb.product_id = p.id
join components as c on bb.component_id = c.id
join keywords as k on bb.bug_id=k.bug_id
join keyworddefs as kd on k.keywordid=kd.id and kd.name in ('projectcode')
where c.name not in ('foundation-internal', 'IP_Discussion', 'IP', 'dsdp')
and bug_severity not in ('withdrawn')
and resolution not in ('INVALID')
group by c.id) as f on b.bug_id=f.cq
left join bugs_activity as pmc on b.bug_id=pmc.bug_id and pmc.added in ('PMC_Approved+')
left join bugs_activity as checkin on b.bug_id=checkin.bug_id and checkin.added in ('checkin', 'checkintocvs')
left join bugs_activity as approved on b.bug_id=approved.bug_id and approved.added in ('approved', 'approved_all_projects', 'FIXED', 'WORKSFORME')
group by f.name
order by f.name";
$result = $App->ipzilla_sql($sql);
while ($row = mysql_fetch_assoc($result)) {
$id = $row['id'];
if (!isset($projects[$id])) continue;
$cq = $row['cq'];
$projects[$id]['initial_cq'] = "https://dev.eclipse.org/ipzilla/show_bug.cgi?id=$cq";
$projects[$id]['initial_cq_created'] = $row['created'];
$projects[$id]['initial_cq_pmc'] = $row['pmc'];
$projects[$id]['initial_cq_checkin'] = $row['checkin'];
$projects[$id]['initial_cq_approved'] = $row['approved'];
}
}
function getHttpParameters($sort=null, $top=null) {
$parameters = array();
foreach($_GET as $key=>$value) $parameters[$key] = $value;
if ($top == 'all') unset($parameters['top']);
else if ($top) $parameters['top'] = $top;
$out = ''; $separator = '';
foreach($parameters as $key => $value) {
$out = "$out$separator$key=$value";
$separator = '&';
}
return $out;
}
function getProjectUrl($id) {
}
function generateTable($projects, $tableDiv = "statusTable") {
global $App;
// TODO it'd be nice to sort out dynamic table height.
echo "<div style=\"height:600px\" id=\"$tableDiv\"></div>";
$columns = '';
foreach(getColumnDefinitions() as $column) {
$label = $column['label'];
$columns .= "data.addColumn('string','$label');";
}
$rows = array();
foreach($projects as $project) {
$row = array();
foreach(getColumnDefinitions() as $id => $config) {
$value = getColumnValue($project, $id);
$sort = (string)getSortValue($project, $id);
$row[] = array('f' => $value, 'v' => $sort);
}
$rows[] = $row;
}
$rowsJSON = json_encode($rows);
$js = "
google.charts.load('current', {'packages':['table']});
google.charts.setOnLoadCallback(drawTable{$tableDiv});
function drawTable{$tableDiv}() {
var data = new google.visualization.DataTable();
$columns
data.addRows($rowsJSON);
var table = new google.visualization.Table(document.getElementById('$tableDiv'));
table.draw(data, {allowHtml: true, frozenColumns: 1, width: '100%', height: '100%'});
}
";
$App->addExtraHtmlHeader("<script type=\"text/javascript\" src=\"https://www.gstatic.com/charts/loader.js\"></script>");
$App->addExtraHtmlHeader("<script type=\"text/javascript\">$js</script>");
}
function valueOrDashes($value) {
if (!$value) return '--';
return preg_replace('/\-/','&#8209;',$value);
}
function getColumnValue($project, $key) {
if ($project == null) return null;
if ($key == null) return null;
$value = @$project[$key];
if ($column = getColumnDefinition($key)) {
if (isset($column['function'])) {
$function = $column['function'];
$value = $function($value, $key, $project);
} else $value = valueOrDashes($value);
}
return $value;
}
/**
* Determine a sort value for a value from a project
* record. If the column provides a sort function, use
* that sort function. Otherwise, if the value is non
* null, return that value. Otherwise, use the display
* function to figure out a value.
*
* @param unknown $project
* @param unknown $key
* @return NULL|string
*/
function getSortValue($project, $key) {
if ($project == null) return null;
if ($key == null) return null;
$value = @$project[$key];
if ($column = getColumnDefinition($key)) {
if ($function = @$column['sort'])
return $function($value, $key, $project);
if ($value !== null) return $value;
if ($function = $column['function'])
return $function(null, $key, $project);
}
return null;
}
function getGitStatus($git) {
switch($git) {
case PROJECT_REPO_ALL_GIT: return 'All Git';
case PROJECT_REPO_SOME_GIT: return 'Some Git';
case PROJECT_REPO_NO_GIT: return 'No Git';
default: return 'No repositories';
}
}
function getLivelinessIcon($liveliness) {
global $images;
switch($liveliness) {
case PROJECT_LIVELINESS_NEVER_ACTIVE: return $images->tools_small;
case PROJECT_LIVELINESS_ACTIVE: return $images->active_small;
case PROJECT_LIVELINESS_STALE: return $images->stale_small;
case PROJECT_LIVELINESS_INACTIVE: return $images->inactive_small;
case PROJECT_LIVELINESS_DEAD: return $images->dead_small;
}
}
if (isset($_GET['json'])) {
header("Content-type: application/json");
echo json_encode(getProjectStatus());
exit;
}
include($App->getProjectCommon());
// Add Top-Level Filters to the nav.
$Nav->addNavSeparator( "Filter", null);
foreach(Project::getTopLevelProjects() as $project) {
$id = $project->getId();
$name = preg_replace("/ Root$/", '', $project->getName());
$link = "?" . getHttpParameters(null, $id);
$Nav->addCustomNav($name, $link, "_self", 2);
}
$link = "?" . getHttpParameters(null, 'all');
$Nav->addCustomNav('All', $link, "_self", 2);
ob_start();
?>
<div id="maincontent">
<div id="midcolumn">
<h1><?php echo $pageTitle; ?></h1>
<?php generateTable(getProjectStatus()); ?>
<h4>Legend</h4>
<ul>
<li style="list-style-image: url('<?php echo $images->tools_small; ?>')">No project activity (project may be new)</li>
<li style="list-style-image: url('<?php echo $images->active_small; ?>')">Activity in the last three months</li>
<li style="list-style-image: url('<?php echo $images->stale_small; ?>')">Activity in the last six months</li>
<li style="list-style-image: url('<?php echo $images->inactive_small; ?>')">No activity for more than six months</li>
<li style="list-style-image: url('<?php echo $images->dead_small; ?>')">No activity for more than twelve months</li>
</ul>
<p>Note that subprojects are included in the determination of liveliness. A project is as lively as the most lively of its subprojects.</p>
</div>
</div>
<?php
$html = ob_get_contents();
ob_end_clean();
$App->generatePage(NULL, $Menu, $Nav, $pageAuthor, $pageKeywords, $pageTitle, $html);
?>