| <?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\"/>" : ' '; |
| } |
| ), |
| '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 " "; |
| 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('/\-/','‑',$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); |
| ?> |