blob: cd227e57cca68dac74c5204f16d75ce6c82fa95a [file] [log] [blame]
<?php
/**
* Copyright (c) 2018 Eclipse Foundation and others.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
require_once dirname ( __FILE__ ) . '/Project.class.php';
require_once dirname ( __FILE__ ) . '/common.php';
require_once dirname ( __FILE__ ) . '/debug.php';
/**
* The ProjectStatusReporter class provides behaviour to load
* information about some subset of the projects, and then
* extract and provide that information in various ways.
* Several "columns" are defined, each of which knows how
* to extract and present some piece of the data.
*
*/
class ProjectStatusReporter {
var $columnDefintions;
var $projects;
function __construct($ids) {
$this->projects = self::loadProjects ( $ids );
$this->columnDefinitions = array (
'name' => array (
'label' => 'Project Name'
),
'id' => array (
'label' => 'Project Id',
'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 = self::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 self::getTimeSince ( $checkin );
if ($created = @$project ['initial_cq_created'])
return self::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 self::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 values($id, $function) {
if ($project = $this->projects[$id]) {
foreach ( $this->columnDefinitions as $id => $config ) {
$function ( new ProjectStatusReporterColumn ( $id, $config, $project ) );
}
}
}
public function getColumnDefinition($key) {
return $this->columnDefinitions [$key];
}
public static function getTimeSince($date) {
if (! $date)
return 0;
$milliseconds = date ( time () - strtotime ( $date ) );
$days = number_format ( ceil ( $milliseconds / (60 * 60 * 24) ) );
return "$days days";
}
public function filter($info) {
foreach ( $_GET as $key => $value ) {
if (isset ( $info [$key] )) {
if ($info [$key] != $value)
return true;
}
}
return false;
}
private static function loadProjects($ids) {
$projects = array ();
foreach ( Project::getAllProjects ( $ids ) as $project ) {
switch ($id = $project->getId ()) {
case 'polarsys' :
break;
default :
$projects[$id] = 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 ()
);
}
}
self::addDashData ( $projects );
self::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
*/
private static function addDashData(&$projects) {
$ids = self::getProjectIds ( $projects );
$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
where p.subproject in ($ids)
group by p.subproject";
query ( 'dashboard', $sql, array(), function ($row) use (&$projects) {
$id = $row ['id'];
if (isset ( $projects [$id] )) {
$projects [$id] += $row;
}
} );
}
/**
* This function answers a string containing the quoted ids for
* the projects.
*
* @param mixed $projects
* @return string
*/
private static function getProjectIds(&$projects) {
$ids = array ();
foreach ( $projects as $project ) {
$ids [] = "'{$project['id']}'";
}
return implode ( ',', $ids );
}
/**
* 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
*/
private static function addInitialContributionStats(&$projects) {
$ids = self::getProjectIds ( $projects );
$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 in ($ids)
and 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";
query ( 'ipzilla', $sql, array(), function ($row) use (&$projects) {
$id = $row ['id'];
if (isset ( $projects [$id] )) {
$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'];
}
} );
}
public static function valueOrDashes($value) {
if (! $value)
return '--';
return $value;
}
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;
}
}
}
class ProjectStatusReporterColumn {
var $id, $config, $data;
public function __construct($id, $config, $data) {
$this->id = $id;
$this->config = $config;
$this->data = $data;
}
public function getTitle() {
return $this->config ['label'];
}
public function getValue() {
$value = $this->data [$this->id];
if (isset ( $this->config ['function'] )) {
$function = $this->config ['function'];
$value = call_user_func($function, $value, $this->id, $this->data );
} else {
$value = ProjectStatusReporter::valueOrDashes ( $value );
}
return $value;
}
public function getSortValue() {
return ( string ) getSortValue ( $this->data, $this->id );
}
}