| <?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); | |
| ?> |