/* | |
Licensed to the Apache Software Foundation (ASF) under one | |
or more contributor license agreements. See the NOTICE file | |
distributed with this work for additional information | |
regarding copyright ownership. The ASF licenses this file | |
to you under the Apache License, Version 2.0 (the | |
"License"); you may not use this file except in compliance | |
with the License. You may obtain a copy of the License at | |
http://www.apache.org/licenses/LICENSE-2.0 | |
Unless required by applicable law or agreed to in writing, | |
software distributed under the License is distributed on an | |
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
KIND, either express or implied. See the License for the | |
specific language governing permissions and limitations | |
under the License. | |
*/ | |
// ----- Global hashes used throughout the script ------ \\ | |
var people = {}; // committer -> name lookups | |
var unixgroups = {}; // unix (ldap) groups (project -> committers lookup) | |
var committees = {}; // id -> committee info (chair, established, group, homepage, id, name, reporting, shortdesc) (current committees) | |
var committeesByName = {}; // name -> committee info | |
var retiredCommittees = {}; // retired committees information: id -> committee info (established, retired, homepage, id, name) | |
var projects = {}; // Projects | |
var podlings = {}; // current podlings | |
var podlingsHistory = {}; // Podlings history (now graduated or retired) | |
var repositories = {}; // source repositories id -> url | |
// --------- Global helpers ----------- \\ | |
function includeJs(jsFilePath) { | |
var js = document.createElement("script"); | |
js.type = "text/javascript"; | |
js.src = jsFilePath; | |
document.head.appendChild(js); | |
} | |
includeJs("js/underscore-min.js"); | |
function GetAsyncJSON(theUrl, xstate, callback) { | |
var xmlHttp = null; | |
if (window.XMLHttpRequest) { | |
xmlHttp = new XMLHttpRequest(); | |
} else { | |
xmlHttp = new ActiveXObject("Microsoft.XMLHTTP"); | |
} | |
xmlHttp.open("GET", theUrl, true); | |
xmlHttp.send(null); | |
xmlHttp.onreadystatechange = function(state) { | |
if (xmlHttp.readyState == 4 && xmlHttp.status == 200 || xmlHttp.status == 404) { | |
if (callback) { | |
if (xmlHttp.status == 404) { | |
callback({}, xstate); | |
} else { | |
callback(JSON.parse(xmlHttp.responseText), xstate); | |
} | |
} | |
} | |
} | |
} | |
var urlErrors = [] | |
var fetchCount = 0; | |
// Fetch an array of URLs, each with their description and own callback plus a final callback | |
// Used to fetch everything before rendering a page that relies on multiple JSON sources. | |
function GetAsyncJSONArray(urls, finalCallback) { | |
var obj = document.getElementById('progress'); | |
if (fetchCount == 0 ) { | |
fetchCount = urls.length; | |
} | |
if (urls.length > 0) { | |
var a = urls.shift(); | |
var URL = a[0]; | |
var desc = a[1]; | |
var cb = a[2]; | |
var xmlHttp = null; | |
if (window.XMLHttpRequest) { | |
xmlHttp = new XMLHttpRequest(); | |
} else { | |
xmlHttp = new ActiveXObject("Microsoft.XMLHTTP"); | |
} | |
if (obj) { obj.innerHTML = "loading file #" + ( fetchCount - urls.length ) + " / " + fetchCount + "<br>" + desc } | |
xmlHttp.open("GET", URL, true); | |
xmlHttp.onreadystatechange = function(state) { | |
if (xmlHttp.readyState == 4) { | |
if (cb) { | |
if (xmlHttp.status == 200) { | |
cb(JSON.parse(xmlHttp.responseText)); | |
} else { | |
urlErrors.push(URL) | |
cb({}); | |
} | |
} | |
GetAsyncJSONArray(urls, finalCallback); | |
} | |
} | |
xmlHttp.send(null); | |
} | |
else { | |
if (obj) { obj.innerHTML = "building page content..." } | |
finalCallback(); | |
} | |
} | |
/* | |
// ------------ form data functions for project editor ------------\\ | |
function addKeyVal(key, val) { | |
var div = document.createElement('div') | |
var left = document.createElement('div') | |
var right = document.createElement('div') | |
div.style = "width: 1020px; margin: 10px; overflow: auto;" | |
left.style = "float: left; width: 300px; font-weight: bold" | |
right.style = "float: left; width: 700px;" | |
left.appendChild(document.createTextNode(key + ": ")) | |
right.appendChild(val) | |
div.appendChild(left); | |
div.appendChild(right) | |
return div | |
} | |
function input(type, name, value) { | |
var t = document.createElement('input'); | |
t.setAttribute("type", type) | |
t.setAttribute("name", name) | |
t.setAttribute("value", value) | |
t.style.minWidth = "380px" | |
return t; | |
} | |
function makeSelect(name, arr, sarr) { | |
var sel = document.createElement('select'); | |
sel.setAttribute("name", name) | |
for (i in arr) { | |
var val = arr[i]; | |
var opt = document.createElement('option') | |
opt.setAttribute("value", val) | |
opt.innerHTML = val | |
sel.appendChild(opt); | |
} | |
return sel | |
} | |
function postREST(json, oform) { | |
var form = new FormData(oform) | |
var xmlHttp = null; | |
if (window.XMLHttpRequest) { | |
xmlHttp = new XMLHttpRequest(); | |
} else { | |
xmlHttp = new ActiveXObject("Microsoft.XMLHTTP"); | |
} | |
for (i in json) { | |
form.append(i, json[i]) | |
} | |
xmlHttp.open("POST", "/edit/save.py", false); | |
xmlHttp.send(form); | |
} | |
function editProject(json, project) { | |
var obj = document.getElementById('contents'); | |
obj.innerHTML = "<a href='/edit/'><img src='/images/back.png' style='vertical-align: middle; margin-right: 10px;'/><b>Back to project list...</b></a><br/><h1>Project editor:</h1><p>Editing " + project + ".json:</p>" | |
if (!json || !json.name) { | |
json = projects[project] ? projects[project] : json | |
json.name = json.name ? json.name : "Apache Foo"; | |
} | |
if (json.category) { | |
json.category = json.category.replace(/http:\/\/projects.apache.org\/category\//gi, "") | |
} | |
var form = document.createElement('form') | |
form.appendChild(input("hidden", "file", project)) | |
var keys = ['name','pmc','homepage','shortdesc','description','category','programming-language','mailing-list', 'download-page','bug-database','SVNRepository','GitRepository'] | |
for (i in keys) { | |
k = keys[i] | |
if (k == 'description') { | |
var txt = document.createElement('textarea'); | |
txt.setAttribute("name", "description") | |
txt.style.width = "600px" | |
txt.style.height = "140px" | |
txt.innerHTML = json[k] ? json[k] : ""; | |
form.appendChild(addKeyVal(k, txt)) | |
} | |
else { | |
form.appendChild(addKeyVal(k, input("text", k, json[k] ? json[k] : ""))) | |
} | |
} | |
var but = input("button", "submit", "Save changes") | |
but.setAttribute("onclick", "postREST({}, this.form); alert('Changes saved!');") | |
form.appendChild(but) | |
obj.appendChild(form) | |
} | |
function editProjectPreload(project) { | |
GetAsyncJSON("/json/projects/" + project + ".json?" + Math.random(), project, editProject); | |
} | |
function buildEditor(uid) { | |
var obj = document.getElementById('contents'); | |
obj.innerHTML = "<h1>Project editor:</h1><h3>Select a project to edit:</h3><p>Only projects where you are in the sponsoring PMC can be edited</p>" | |
for (i in projects) { | |
var p = i.split("-")[0]; | |
if (projects[i].name.match(/incubating/i)) { | |
p = 'incubator' | |
} | |
if (unixgroups[p+"-pmc"] && unixgroups[p+"-pmc"].indexOf(uid) >= 0) { | |
obj.innerHTML += "<a href='javascript:void(0);' onclick='editProjectPreload(\"" + i + "\");'>" + projects[i].name + "</a><br/>" | |
} | |
} | |
obj.innerHTML += "<hr/><h3>Or create a new project:</h3>" | |
var form = document.createElement('form') | |
var groups = [] | |
for (i in unixgroups) { | |
for (x in unixgroups[i]) { | |
if (unixgroups[i][x] == uid && i.match(/.+-pmc$/i)) { | |
groups.push(i.replace("-pmc","")) | |
break | |
} | |
} | |
} | |
form.appendChild(addKeyVal("PMC", makeSelect("pmc", groups, []))); | |
form.appendChild(addKeyVal("Sub-project (if any) (a-z,0-9 only)", input("text", "sub", ""))) | |
var but = input("button", "submit", "Create project data file") | |
but.setAttribute("onclick", "newProject(this.form);") | |
form.appendChild(but) | |
obj.appendChild(form) | |
} | |
function newProject(form) { | |
filename = form.pmc.value | |
if (form.sub.value.length > 0) { | |
filename += "-" + form.sub.value.toLowerCase().replace(/[^-a-z0-9]/g, "") | |
} | |
editProject({'pmc': form.pmc.value, 'homepage': 'http://'+form.pmc.value+'.apache.org/'}, filename); | |
} | |
*/ | |
// ------------ Project information page ------------\\ | |
function linkCommitterIndex(cid) { | |
var fullname = people[cid]; | |
var cl = isMember(cid) ? "member" : "committer"; | |
return "<a class='" + cl + "' title='" + cid + "' href='http://home.apache.org/phonebook.html?uid=" + cid + "' target='_blank'>" + fullname + "</a>"; | |
} | |
function appendElementWithInnerHTML(obj,type,html) { | |
var child = document.createElement(type); | |
child.innerHTML = html; | |
obj.appendChild(child); | |
return child; | |
} | |
function appendLiInnerHTML(ul,html) { | |
return appendElementWithInnerHTML(ul,'li',html); | |
} | |
function renderProjectPage(project, projectId) { | |
var obj = document.getElementById('contents'); | |
if ((!project || !project.name) && projects[projectId]) { | |
// no DOAP file but known project: podling (loaded from podlings.json) | |
project = projects[projectId]; | |
} | |
if (!project || !project.name) { | |
obj.innerHTML = "<h2>Sorry, I don't have any information available about this project</h2>"; | |
return; | |
} | |
fixProjectName(project); | |
var isIncubating = project && (project.podling || (project.pmc == 'incubator')); | |
// Rerig the unix name and committee id | |
var unixgroup = projectId.split("-")[0]; | |
/* | |
Temp hack for podling names. TODO need to sort out generated names | |
*/ | |
if (projectId.indexOf("incubator-") == 0) { | |
unixgroup = projectId.split("-")[1] | |
} | |
// special cases | |
if (unixgroup == "empire") unixgroup = "empire-db"; | |
if (unixgroup == "community") unixgroup = "comdev"; | |
if (project && project.pmc == "attic") { | |
unixgroup = "attic"; | |
} | |
var committeeId = isIncubating ? 'incubator' : unixgroup; | |
if (!committees[unixgroup]) { | |
// at least one committee has a unix group that is different from committee id: webservices (group=ws), see parsecommittees.py#group_ids | |
// search instead of hard-coding the currently known case | |
for (p in committees) { | |
if (committees[p].group == unixgroup) { | |
committeeId = p; | |
break; | |
} | |
} | |
} | |
var committee = committees[committeeId]; | |
if (!committee) { | |
obj.innerHTML = "<h2>Cannot find the PMC '" + committeeId + "' for this project. Check the DOAP is correct.</h2>"; | |
return; | |
} | |
// Start by splitting the name, thus fetching the root name of the project, and not the sub-project. | |
var description = ""; | |
if (project) { | |
if (!_.isEmpty(project.description)) { | |
description = project.description; | |
} else if (!_.isEmpty(project.shortdesc)) { | |
description = project.shortdesc; | |
} else if (!_.isEmpty(committee.shortdesc)) { | |
description = committee.shortdesc; | |
} else { | |
description = "No description available"; | |
} | |
} | |
// Title + description | |
obj.innerHTML = "<h1>" + project.name + " <font size='-1'>(a project managed by the <a href='committee.html?" + committeeId + "'>" + committee.name + " Committee</a>)</font></h1>"; | |
// project description | |
appendElementWithInnerHTML(obj,'p',description.replace(/([^\r\n]+)\r?\n\r?\n/g,function(a) { return "<p>"+a+"</p>"})); | |
var ul = document.createElement('ul'); | |
// Base data | |
appendElementWithInnerHTML(obj,'h4',"Project base data:"); | |
if (project.description && project.shortdesc) { | |
appendLiInnerHTML(ul, "<b>Short description:</b> " + project.shortdesc); | |
} | |
// Categories | |
if (project.category) { | |
var arr = project.category.split(/,\s*/); | |
var pls = ""; | |
for (i in arr) { | |
var cat = arr[i]; | |
pls += "<a href='projects.html?category#" + cat + "'>" + cat + "</a> "; | |
} | |
appendLiInnerHTML(ul, "<b>Category:</b> " + pls); | |
} | |
// Website | |
if (project.homepage) { | |
appendLiInnerHTML(ul, "<b>Website:</b> <a href='" + project.homepage + "' target='_blank'>" + project.homepage + "</a>"); | |
} | |
if (isIncubating) { | |
appendLiInnerHTML(ul, "<b>Project status:</b> <span class='ppodling'>Incubating</span>"); | |
} else if (committeeId != 'attic') { | |
appendLiInnerHTML(ul, "<b>Project status:</b> <span class='pactive'>Active</span>"); | |
} else { | |
appendLiInnerHTML(ul, "<b>Project status:</b> <span class='pretired'>Retired</span>"); | |
} | |
// Committers | |
if (isIncubating && unixgroups[unixgroup]) { | |
var commitl = []; | |
var commitgroup = unixgroups[unixgroup]; | |
for (i in commitgroup) { | |
commitl.push(linkCommitterIndex(commitgroup[i])); | |
} | |
appendLiInnerHTML(ul, "<b>Committers (" + commitgroup.length + "):</b> <blockquote>" + commitl.join(", ") + "</blockquote>"); | |
} | |
if (project.implements) { | |
var stds = document.createElement('ul'); | |
var impl; | |
for (impl in project.implements) { | |
impl = project.implements[impl]; | |
var std = ""; | |
if (impl.body) { | |
std += impl.body + ' '; | |
} | |
if (impl.id) { | |
std += "<a href='" + impl.url + "'>" + impl.id + "</a>: " + impl.title; | |
} else { | |
std += "<a href='" + impl.url + "'>" + impl.title + "</a>"; | |
} | |
appendLiInnerHTML(stds, std); | |
} | |
appendLiInnerHTML(ul, "<b>Implemented standards</b>").appendChild(stds); | |
} | |
// doap/rdf | |
if (project.doap) { | |
appendLiInnerHTML(ul, "<b>Project data file:</b> <a href='" + project.doap + "' target='_blank'>DOAP RDF Source</a> (<a href='json/projects/" + projectId + ".json'>generated json</a>)"); | |
} else { | |
appendLiInnerHTML(ul, "<b>Project data file:</b> no <a href='https://projects.apache.org/create.html'>DOAP file</a> available"); | |
} | |
// maintainer | |
if (project.maintainer) { | |
var mt; | |
var maintainers = ""; | |
for (mt in project.maintainer) { | |
mt = project.maintainer[mt]; | |
if (mt.mbox) { | |
var id = mt.mbox; | |
id = id.substr(id.indexOf(':') + 1); | |
id = id.substr(0, id.indexOf('@')); | |
if (people[id]) { | |
maintainers += linkCommitterIndex(id) + " "; | |
} else { | |
maintainers += "<a href='" + mt.mbox + "'>" + mt.name + "</a> "; | |
} | |
} else { | |
maintainers += mt.name + " "; | |
} | |
} | |
appendLiInnerHTML(ul, "<b>Project data maintainer(s):</b> " + maintainers); | |
} | |
obj.appendChild(ul); | |
// Code data | |
appendElementWithInnerHTML(obj,'h4',"Development:"); | |
ul = document.createElement('ul'); | |
if (project['programming-language']) { | |
var pl = project['programming-language']; | |
var arr = pl.split(/,\s*/); | |
var pls = ""; | |
for (i in arr) { | |
pls += "<a href='projects.html?language#" + arr[i] + "'>" + arr[i] + "</a> "; | |
} | |
appendLiInnerHTML(ul, "<b>Programming language:</b> " + pls); | |
} | |
if (project['bug-database']) { | |
var bd = project['bug-database']; | |
var arr = bd.split(/,\s*/); | |
var bds = ""; | |
for (i in arr) { | |
bds += "<a href='" + arr[i] + "'>" + arr[i] + "</a> "; | |
} | |
appendLiInnerHTML(ul, "<b>Bug-tracking:</b> " + bds); | |
} | |
if (project['mailing-list']) { | |
var ml = project['mailing-list']; | |
var xml = ml; | |
// email instead of link? | |
if (ml.match(/@/)) { | |
xml = "mailto:" + ml; | |
} | |
appendLiInnerHTML(ul, "<b>Mailing list(s):</b> <a href='" + xml + "'>" + ml + "</a>"); | |
} | |
// repositories | |
if (project.repository) { | |
var r; | |
for (r in project.repository) { | |
r = project.repository[r]; | |
if (r.indexOf("svn") > 0) { | |
appendLiInnerHTML(ul, "<b>Subversion repository:</b> <a target=*_blank' href='" + r + "'>" + r + "</a>"); | |
} else if (r.indexOf("git") > 0) { | |
appendLiInnerHTML(ul, "<b>Git repository:</b> <a target=*_blank' href='" + r + "'>" + r + "</a>"); | |
} else { | |
appendLiInnerHTML(ul, "<b>Repository:</b> <a target=*_blank' href='" + r + "'>" + r + "</a>"); | |
} | |
} | |
} | |
obj.appendChild(ul); | |
// releases | |
appendElementWithInnerHTML(obj,'h4',"Releases <font size='-2'>(from DOAP)</font>:"); | |
ul = document.createElement('ul'); | |
if (project['download-page']) { | |
appendLiInnerHTML(ul, "<b>Download:</b> <a href='" + project['download-page'] + "' target='_blank'>" + project['download-page'] + "</a>"); | |
} | |
if (project.release) { | |
project.release.sort(function(a,b){// reverse date order (most recent first) | |
var ac = a.created ? a.created : '1970-01-01'; | |
var bc = b.created ? b.created : '1970-01-01'; | |
if(ac < bc) return 1; | |
if(ac > bc) return -1; | |
return 0;}); | |
var r; | |
for (r in project.release) { | |
r = project.release[r]; | |
var html = "<b>" + (r.revision ? r.revision : r.version) + "</b>"; | |
html += " (" + (r.created ? r.created : 'unknown') + ")"; | |
appendLiInnerHTML(ul, html + ": " + r.name); | |
} | |
} | |
obj.appendChild(ul); | |
} | |
function buildProjectPage() { | |
var projectId = document.location.search.substr(1); | |
GetAsyncJSON("json/projects/" + projectId + ".json?" + Math.random(), projectId, renderProjectPage) | |
} | |
function renderCommitteePage(committeeId) { | |
var obj = document.getElementById('contents'); | |
if (!committees[committeeId]) { | |
obj.innerHTML = "<h2>Sorry, I don't have any information available about this committee</h2>"; | |
return; | |
} | |
var unixgroup = committeeId; // there are probably a few exceptions... | |
var committee = committees[committeeId]; | |
obj.innerHTML = "<h1>" + committee.name + " Committee <font size='-2'>(also called PMC or Top Level Project)</font></h1>"; | |
var description; | |
if (!_.isEmpty(committee.shortdesc)) { | |
description = committee.shortdesc; | |
} else { | |
description = "Missing from http://www.apache.org/#projects-list"; | |
} | |
appendElementWithInnerHTML(obj, 'h4', "Description <font size='-2'>(from <a href='http://www.apache.org/#projects-list'>projects list</a>)</a>:"); | |
appendElementWithInnerHTML(obj,'p',description.replace(/([^\r\n]+)\r?\n\r?\n/g,function(a) { return "<p>"+a+"</p>"})); | |
appendElementWithInnerHTML(obj, 'h4', "Charter <font size='-2'>(from PMC data file)</a>:"); | |
var charter; | |
if (!_.isEmpty(committee.charter)) { | |
charter = committee.charter; | |
} else { | |
charter = "Missing"; | |
} | |
appendElementWithInnerHTML(obj,'p',charter.replace(/([^\r\n]+)\r?\n\r?\n/g,function(a) { return "<p>"+a+"</p>"})); | |
var ul = document.createElement('ul'); | |
appendElementWithInnerHTML(obj, 'h4', "Committee data:"); | |
appendLiInnerHTML(ul, "<b>Website:</b> <a href='" + committee.homepage + "' target='_blank'>" + committee.homepage + "</a>"); | |
appendLiInnerHTML(ul, "<b>Committee established:</b> " + committee.established); | |
// VP | |
appendLiInnerHTML(ul, "<b>PMC Chair:</b> " + linkCommitterIndex(committee.chair)); | |
// Reporting cycle | |
var cycles = ["every month", "January, April, July, October", "February, May, August, November", "March, June, September, December"]; | |
var minutes = committee.name.substr("Apache ".length).replace(/ /g, '_'); | |
// does not work for APR and Logging Services currently | |
if (committeeId == 'apr') { | |
minutes = 'Apr'; | |
} else if (committeeId == 'logging') { | |
minutes = 'Logging'; | |
} | |
appendLiInnerHTML(ul, "<b>Reporting cycle:</b> " + cycles[committee.reporting] + ", see <a href='https://whimsy.apache.org/board/minutes/" + minutes + ".html'>minutes</a>"); | |
// PMC | |
if (committee.roster) { // check we have the data | |
var pmcl = []; | |
for (i in committee.roster) { | |
pmcl.push(linkCommitterIndex(i)); | |
} | |
if (pmcl.length > 0) { | |
appendLiInnerHTML(ul, "<b>PMC Roster <font size='-1'>(from committee-info.txt)</font> (" + pmcl.length + "):</b> <blockquote>" + pmcl.join(", ") + "</blockquote>"); | |
} else { | |
appendLiInnerHTML(ul, "<b>PMC Roster not found in committee-info.txt (check that Section 3 has been updated)</b>"); | |
} | |
} | |
// Committers | |
if (unixgroups[unixgroup]) { | |
var commitl = []; | |
var commitgroup = unixgroups[unixgroup]; | |
for (i in commitgroup) { | |
commitl.push(linkCommitterIndex(commitgroup[i])); | |
} | |
appendLiInnerHTML(ul, "<b>Committers (" + commitgroup.length + "):</b> <blockquote>" + commitl.join(", ") + "</blockquote>"); | |
} | |
// rdf | |
if (committee.rdf) { | |
appendLiInnerHTML(ul, "<b>PMC data file:</b> <a href='" + committee.rdf + "' target='_blank'>RDF Source</a>"); | |
} | |
obj.appendChild(ul); | |
var subprojects = []; | |
for (p in projects) { | |
if (projects[p].pmc == committeeId) { | |
subprojects.push(p); | |
} | |
} | |
if (subprojects.length == 0) { | |
if (committeeId != 'labs') { | |
// if a committee did not declare any project, consider there is a default one with the id of the committee | |
// only Labs doesn't manage projects | |
subprojects.push({ 'id': committeeId, 'name': committee.name, 'pmc': committeeId }); | |
} | |
} else { | |
appendElementWithInnerHTML(obj, 'h4', "Projects managed by this Committee:"); | |
ul = document.createElement('ul'); | |
for (var p in subprojects.sort()) { | |
p = subprojects[p]; | |
appendLiInnerHTML(ul, projectLink(p)); | |
} | |
obj.appendChild(ul); | |
} | |
var repos = []; | |
for (var r in repositories) { | |
if (r.startsWith(committeeId)) { | |
repos.push(r); | |
} | |
} | |
if (committeeId === "logging") { | |
for (var r in repositories) { | |
if (r.startsWith("log4")) { | |
repos.push(r); | |
} | |
} | |
} | |
if (repos.length > 0) { | |
appendElementWithInnerHTML(obj, 'h4', "Repositories managed by this Committee <font size='-2'>(from <a href='http://git.apache.org/'>ASF Git mirrors</a>)</font>:"); | |
ul = document.createElement('ul'); | |
for (var r in repos.sort()) { | |
r = repos[r]; | |
var url = repositories[r]; | |
appendLiInnerHTML(ul, r + ": <a href='" + url + "'>" + url + "</a>"); | |
} | |
obj.appendChild(ul); | |
} | |
} | |
function buildCommitteePage() { | |
var committeeId = document.location.search.substr(1); | |
renderCommitteePage(committeeId); | |
} | |
// ------------ Projects listing ------------\\ | |
function camelCase(str) { | |
return str.replace(/^([a-z])(.+)$/, function(c,a,b) { return a.toUpperCase() + b.toLowerCase() } ); | |
} | |
function committeeIcon() { | |
return "<img src='images/committee.png' title='Committee' style='vertical-align: middle; padding: 2px;'/> "; | |
} | |
function projectIcon() { | |
return "<img src='images/project.png' title='Project' style='vertical-align: middle; padding: 2px;'/> " | |
} | |
function committeeLink(id) { | |
var committee = committees[id]; | |
return "<a href='committee.html?" + id + "'>" + committee.name + "</a>"; | |
} | |
function projectLink(id) { | |
var project = projects[id]; | |
if (!project) { | |
// not project id: perhaps committee id | |
project = committees[id]; | |
} | |
return "<a href='project.html?" + id + "'>" + project.name + "</a>"; | |
} | |
function isMember(id) { | |
return _(unixgroups['member']).indexOf(id) >= 0; | |
} | |
function sortProjects() { | |
var projectsSortedX = []; | |
var projectsSorted = []; | |
for (i in projects) { | |
projectsSortedX.push([i, projects[i].name.toLowerCase()]); | |
} | |
// compare names (already lower-cased) | |
projectsSortedX.sort(function(a,b) { return a[1] > b[1] ? 1 : a[1] < b[1] ? -1 : 0 }) | |
for (i in projectsSortedX) { | |
projectsSorted.push(projectsSortedX[i][0]); | |
} | |
return projectsSorted; | |
} | |
function renderProjectsByName() { | |
var obj = document.getElementById('list'); | |
obj.innerHTML = ""; | |
var projectsSorted = sortProjects(); | |
// Project list | |
var ul = document.createElement('ul'); | |
for (var i in projectsSorted) { | |
var project = projectsSorted[i]; | |
appendLiInnerHTML(ul, projectIcon(projects[project].name) + projectLink(project)); | |
} | |
obj.appendChild(ul); | |
} | |
function renderProjectsByLanguage() { | |
var obj = document.getElementById('list'); | |
obj.innerHTML = ""; | |
var projectsSorted = sortProjects(); | |
// Compile Language array | |
var lingos = []; | |
var lcount = {}; | |
var i; | |
var x; | |
for (i in projects) { | |
if (projects[i]['programming-language']) { | |
var a = projects[i]['programming-language'].split(/,\s*/); | |
for (x in a) { | |
a[x] = camelCase(a[x]); | |
if (lingos.indexOf(a[x]) < 0) { | |
lingos.push(a[x]); | |
lcount[a[x]] = 0; | |
} | |
lcount[a[x]]++; | |
} | |
} | |
} | |
// Construct language list | |
lingos.sort(); | |
var ul = document.createElement('ul'); | |
var l; | |
for (l in lingos) { | |
var lang = lingos[l]; | |
var li = document.createElement('li'); | |
li.innerHTML = "<h3><a id='" + lang + "'>" + lang + " (" + lcount[lang] + ")</a>"+linkToHere(lang)+":</h3>"; | |
var cul = document.createElement('ul'); | |
for (i in projectsSorted) { | |
i = projectsSorted[i]; | |
if (projects[i]['programming-language']) { | |
var a = projects[i]['programming-language'].split(/,\s*/); | |
for (x in a) { | |
// Use same capitalisation as the language list | |
if (camelCase(a[x]) == lang) { | |
appendLiInnerHTML(cul, projectIcon(projects[i].name) + projectLink(i)); | |
} | |
} | |
} | |
} | |
li.appendChild(cul); | |
ul.appendChild(li); | |
} | |
obj.appendChild(ul); | |
if (location.hash.length > 1) { | |
setTimeout(function() { location.href = location.href;}, 250); | |
} | |
} | |
function renderProjectsByCategory() { | |
var obj = document.getElementById('list'); | |
obj.innerHTML = ""; | |
var projectsSorted = sortProjects(); | |
var cats = []; | |
var ccount = {}; | |
var i; | |
for (i in projects) { | |
if (projects[i].category) { | |
var a = projects[i].category.split(/,\s*/); | |
var x; | |
for (x in a) { | |
x = a[x].toLowerCase(); // must agree with downcase below | |
if (cats.indexOf(x) < 0) { | |
cats.push(x); | |
ccount[x] = 0; | |
} | |
ccount[x]++; | |
} | |
} | |
} | |
cats.sort(); | |
// Construct category list | |
var ul = document.createElement('ul'); | |
var l; | |
for (l in cats) { | |
var lang = cats[l]; | |
var li = document.createElement('li'); | |
li.innerHTML = "<h3><a id='" + lang + "'>" + lang + " (" + ccount[lang] + ")</a>"+linkToHere(lang)+":</h3>"; | |
var cul = document.createElement('ul'); | |
for (i in projectsSorted) { | |
i = projectsSorted[i]; | |
var project = projects[i]; | |
if (project.category) { | |
var a = project.category.split(/,\s*/); | |
for (x in a) { | |
x = a[x].toLowerCase(); // must agree with downcase above | |
if (x == lang) { | |
appendLiInnerHTML(cul, projectIcon(project.name) + projectLink(i)); | |
} | |
} | |
} | |
} | |
li.appendChild(cul); | |
ul.appendChild(li); | |
} | |
obj.appendChild(ul); | |
if (location.hash.length > 1) { | |
setTimeout(function() { location.href = location.href;}, 250); | |
} | |
} | |
function renderProjectsByNumber() { | |
var obj = document.getElementById('list'); | |
obj.innerHTML = ""; | |
var projectsSorted = sortProjects(); | |
var lens = []; | |
var lcount = {}; | |
for (i in projects) { | |
if (unixgroups[i] && i != 'incubator') { | |
var len = unixgroups[i].length; | |
if (lens.indexOf(len) < 0) { | |
lens.push(len); | |
lcount[len] = 0; | |
} | |
lcount[len]++; | |
} | |
} | |
lens.sort(function(a,b) { return b - a }); | |
// Construct date list | |
var ul = document.createElement('ul'); | |
for (l in lens) { | |
var len = lens[l]; | |
var i; | |
for (i in projectsSorted) { | |
i = projectsSorted[i]; | |
if (unixgroups[i]) { | |
var xlen = unixgroups[i].length; | |
if (xlen == len) { | |
var html = projectIcon(projects[i].name) + projectLink(i) + ": " + len + " committers"; | |
if (unixgroups[i+'-pmc']) { | |
html += ", " + unixgroups[i+'-pmc'].length + " PMC members"; | |
} | |
appendLiInnerHTML(ul,html); | |
} | |
} | |
} | |
} | |
obj.appendChild(ul); | |
} | |
function renderProjectsByCommittee() { | |
var obj = document.getElementById('list'); | |
obj.innerHTML = ""; | |
var projectsSorted = sortProjects(); | |
var dcount = {}; | |
for (var committee in committees) { | |
dcount[committee] = 0; | |
} | |
for (var project in projects) { | |
project = projects[project]; | |
if (committees[project.pmc]) { | |
dcount[project.pmc]++; | |
} | |
} | |
// Construct pmc list | |
var ul = document.createElement('ul'); | |
var lpmc; | |
for (lpmc in committees) { | |
var c = dcount[lpmc]; | |
var li = document.createElement('li'); | |
var cul = document.createElement('ul'); | |
if (c == 0 && lpmc != 'labs') { | |
appendLiInnerHTML(cul, projectIcon(committees[lpmc].name) + "<a href='project.html?" + lpmc + "'>" + committees[lpmc].name + "</a>"); | |
c = 1; | |
} else { | |
var i; | |
for (i in projectsSorted) { | |
i = projectsSorted[i]; | |
var project = projects[i]; | |
if (committees[project.pmc]) { | |
var xlpmc = project.pmc; | |
if (xlpmc == lpmc) { | |
appendLiInnerHTML(cul, projectIcon(project.name) + projectLink(i)); | |
} | |
} | |
} | |
} | |
li.innerHTML = "<h3>" + committeeIcon() + "<a id='" + lpmc + "' href='committee.html?"+ lpmc + "'>" + committees[lpmc].name + " Committee</a>" + (c>1?(" (" + c + ")"):"") + (c>0?":": "") + "</h3>"; | |
li.appendChild(cul); | |
ul.appendChild(li); | |
} | |
obj.appendChild(ul); | |
if (location.hash.length > 1) { | |
setTimeout(function() { location.href = location.href;}, 250); | |
} | |
} | |
function buildProjectsList() { | |
var cat = document.location.search.substr(1); | |
var types = { | |
'name': [ 'name', renderProjectsByName ], | |
'language': [ 'language', renderProjectsByLanguage ], | |
'category': [ 'category', renderProjectsByCategory ], | |
'number': [ 'number of committers', renderProjectsByNumber ], | |
'pmc': [ 'Committee', renderProjectsByCommittee ], | |
'committee': [ 'Committee', renderProjectsByCommittee ] | |
} | |
if ((cat.length == 0) || !(cat in types)) { | |
cat = "name"; | |
} | |
var type = types[cat]; | |
var obj = document.getElementById('title'); | |
obj.innerHTML = "<h1>Projects by " + type[0] + ":</h1>" | |
preloadEverything(type[1]); | |
} | |
function sortCommittees() { | |
var committeesSortedX = []; | |
var committeesSorted = []; | |
for (i in committees) { | |
committeesSortedX.push([i, committees[i].name.toLowerCase()]); | |
} | |
// compare names (already lower-cased) | |
committeesSortedX.sort(function(a,b) { return a[1] > b[1] ? 1 : a[1] < b[1] ? -1 : 0 }) | |
for (i in committeesSortedX) { | |
committeesSorted.push(committeesSortedX[i][0]); | |
} | |
return committeesSorted; | |
} | |
function renderCommitteesByName() { | |
var obj = document.getElementById('list'); | |
obj.innerHTML = ""; | |
var committeesSorted = sortCommittees(); | |
// Committee list | |
var ul = document.createElement('ul'); | |
var i; | |
for (i in committeesSorted) { | |
appendLiInnerHTML(ul, committeeIcon() + committeeLink(committeesSorted[i])); | |
} | |
obj.appendChild(ul); | |
} | |
function renderCommitteesByDate() { | |
var obj = document.getElementById('list'); | |
obj.innerHTML = ""; | |
var dates = []; | |
var dcount = {}; | |
var i; | |
for (i in committees) { | |
var date = committees[i].established; | |
if (dates.indexOf(date) < 0) { | |
dates.push(date); | |
dcount[date] = 0; | |
} | |
dcount[date]++; | |
} | |
dates.sort() | |
// Construct date list | |
var ul = document.createElement('ul'); | |
var l; | |
for (l in dates) { | |
var date = dates[l]; | |
var li = document.createElement('li'); | |
li.innerHTML = "<h3><a id='" + date + "'>" + date + " (" + dcount[date] + ")</a>:</h3>"; | |
var cul = document.createElement('ul'); | |
var i; | |
for (i in committeesByName) { | |
i = committeesByName[i]; | |
if (i.established == date) { | |
appendLiInnerHTML(cul, committeeIcon() + committeeLink(i.id)); | |
} | |
} | |
li.appendChild(cul); | |
ul.appendChild(li); | |
} | |
obj.appendChild(ul); | |
if (location.hash.length > 1) { | |
setTimeout(function() { location.href = location.href;}, 250); | |
} | |
} | |
function buildCommitteesList() { | |
var cat = document.location.search.substr(1); | |
var types = { | |
'name': [ 'name', renderCommitteesByName ], | |
'date': [ 'founding date', renderCommitteesByDate ] | |
} | |
if ((cat.length == 0) || !(cat in types)) { | |
cat = "name"; | |
} | |
var type = types[cat]; | |
var obj = document.getElementById('title'); | |
obj.innerHTML = "<h1>Committees by " + type[0] + ":</h1>" | |
preloadEverything(type[1]); | |
} | |
// Rendering project list using DataTables instead of the usual stuff: | |
function buildProjectListAsTable(json) { | |
var arr = []; | |
for (p in projects) { | |
var project = projects[p]; | |
// Get name of PMC | |
var pmc = committees[project.pmc] ? committees[project.pmc].name : "Unknown"; | |
// Get project type | |
var type = "Sub-Project"; | |
var shortp = p.split("-")[0]; | |
if (unixgroups[shortp]) { | |
type = "TLP"; | |
if ((!committeesByName[project.name] && committees[project.pmc]) || project.name.match(/incubating/i)) { | |
type = "Sub-project"; | |
} | |
} else { | |
type = "Retired"; | |
} | |
if (project.podling || project.name.match(/incubating/i)) { | |
type = "Podling"; | |
pmc = "Apache Incubator"; | |
} | |
// Programming language | |
var pl = project['programming-language'] ? project['programming-language'] : "Unknown"; | |
// Shove the result into a row | |
arr.push([ p, project.name, type, pmc, pl, project.category]) | |
} | |
// Construct the data table | |
$('#contents').html( '<table cellpadding="0" cellspacing="0" border="0" class="display" id="projectlist"></table>' ); | |
$('#projectlist').dataTable( { | |
"data": arr, | |
"columns": [ | |
{ "title": "ID", "visible": false }, | |
{ "title": "Name" }, | |
{ "title": "Type" }, | |
{ "title": "PMC" }, | |
{ "title": "Programming Language(s)" }, | |
{ "title": "Category" } | |
], | |
"fnRowCallback": function( nRow, aData, iDisplayIndex, iDisplayIndexFull) { | |
jQuery(nRow).attr('id', aData[0]); | |
jQuery(nRow).css("cursor", "pointer"); | |
return nRow; | |
} | |
} ); | |
$('#projectlist tbody').on('click', 'tr', function () { | |
var name = $(this).attr('id'); | |
location.href = "project.html?" + name | |
} ); | |
} | |
function isCommittee(name) { | |
return committeesByName[name]; | |
} | |
// ------------ Front page rendering ------------\\ | |
function renderFrontPage() { | |
var numcommittees = 0; | |
var i; | |
for (i in committees) numcommittees++; | |
var curPodlings = 0; | |
for (i in podlings) curPodlings++; | |
var nsubs = 0; // number of projects with DOAP that do not have same name than a committee | |
for (i in projects) { | |
i = projects[i]; | |
if (i.pmc != 'incubator' && !isCommittee(i.name)) { | |
nsubs++; | |
} | |
} | |
var initiatives = curPodlings + numcommittees + nsubs; // podlings + committees + sub-projects | |
initiatives -= initiatives % 50; // round down to nearest 50 | |
var obj = document.getElementById('details'); | |
obj.innerHTML = "" | |
if (urlErrors.length > 0) { | |
obj.innerHTML += "<p><span style='color: red'><b>Warning: could not load: "+urlErrors.join(', ')+"</b></span></p>" | |
} | |
obj.innerHTML | |
+= "<h3 style='text-align: center;'>There are currently <span style='color: #269;'>" + initiatives + "+</span> open source initiatives at the ASF:</h3>" | |
+ "<ul style='width: 400px; margin: 0 auto; font-size: 18px; color: #269; font-weight: bold;'>" | |
+ "<li>" + numcommittees + " committees managing " + (numcommittees + nsubs) + " projects</li>" | |
+ "<li>5 special committees*</li>" | |
+ "<li>" + curPodlings + " incubating podlings</li></ul>" | |
+ "<p><small>*Infrastructure, Travel Assistance, Security Team, Legal Affairs and Brand Management</small></p>"; | |
renderCommitteeEvolution(); | |
renderPodlingsEvolution(); | |
renderLanguageChart(); | |
} | |
// ------------ Chart functions ------------\\ | |
function htmlListTooltip(date,name,values) { | |
return '<div style="padding:8px 8px 8px 8px;"><b>' + date + '</b>' | |
+ '<br/><b>' + values.length + '</b> ' + name + ((values.length > 1) ? 's:':':') | |
+ ((values.length > 0) ? '<br/>- '+values.join('<br/>- '):'') | |
+ '</div>'; | |
} | |
function renderCommitteeEvolution() { | |
var evo = {}; // 'year-month' -> { established: [], retired: [] } | |
// init evo with empty content for the whole period | |
var maxYear = new Date().getFullYear(); | |
for (var year = 1995; year <= maxYear; year++) { | |
var maxMonth = (year < maxYear) ? 12 : (new Date().getMonth() + 1); | |
for (var month = 1; month <= maxMonth; month++) { | |
var key = year + '-' + (month < 10 ? '0':'') + month; | |
evo[key] = { 'established': [], 'retired': [] }; | |
} | |
} | |
// add current committees | |
var c; | |
for (c in committees) { | |
c = committees[c]; | |
if (evo[c.established]) { | |
evo[c.established]['established'].push(c); | |
} else { | |
console.log(c.id + ": " + c.established + " is off(?!)"); | |
} | |
} | |
// add retired committees | |
for (c in retiredCommittees) { | |
c = retiredCommittees[c]; | |
if (evo[c.established] && evo[c.retired]) { | |
evo[c.established]['established'].push(c); | |
evo[c.retired]['retired'].push(c); | |
} else { | |
console.log(c.id + ": " + c.established + " or " + c.retired + " is off(?!)"); | |
} | |
} | |
// compute data | |
var data = []; | |
var cur = 0; | |
var d; | |
for (d in evo) { | |
var established = evo[d]['established']; | |
var retired = evo[d]['retired']; | |
var estDisplay = []; | |
for (c in established) { | |
c = established[c]; | |
estDisplay.push(c.name + ((c.id in retiredCommittees) ? ' (retired ' + retiredCommittees[c.id]['retired'] + ')':'')); | |
} | |
var retDisplay = []; | |
for (c in retired) { | |
c = retired[c]; | |
retDisplay.push(c.name + ' (established ' + c['established'] + ')'); | |
} | |
cur += established.length - retired.length; | |
data.push([d, established.length, htmlListTooltip(d, 'new committee', estDisplay), -1*retired.length, htmlListTooltip(d, 'retired committee', retDisplay), cur]); | |
} | |
//narr.sort(function(a,b) { return (b[1] - a[1]) }); | |
var dataTable = new google.visualization.DataTable(); | |
dataTable.addColumn('string', 'Month'); | |
dataTable.addColumn('number', "New committees"); | |
dataTable.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}}); | |
dataTable.addColumn('number', "Retired committees"); | |
dataTable.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}}); | |
dataTable.addColumn('number', 'Current committees'); | |
dataTable.addRows(data); | |
var options = { | |
title: "Committees evolution (also called PMCs or Top Level Projects)", | |
isStacked: true, | |
height: 320, | |
width: 1160, | |
seriesType: "bars", | |
backgroundColor: 'transparent', | |
series: {2: {type: "line", targetAxisIndex: 1}}, | |
tooltip: {isHtml: true}, | |
vAxes:[ | |
{title: 'Change in states', ticks: [-3,0,3,6,9]}, | |
{title: 'Current number of committees'} | |
] | |
}; | |
var div = document.createElement('div'); | |
document.getElementById('details').appendChild(div); | |
var chart = new google.visualization.ComboChart(div); | |
chart.draw(dataTable, options); | |
} | |
function renderPodlingsEvolution(obj) { | |
var evo = {}; // 'year-month' -> { started: [], graduated: [], retired: [] } | |
// init evo with empty content for the whole period | |
var maxYear = new Date().getFullYear(); | |
for (var year = 2003; year <= maxYear; year++) { | |
var maxMonth = (year < maxYear) ? 12 : (new Date().getMonth() + 1); | |
for (var month = 1; month <= maxMonth; month++) { | |
var key = year + '-' + (month < 10 ? '0':'') + month; | |
evo[key] = { 'started': [], 'graduated': [], 'retired': [] }; | |
} | |
} | |
// add current podlings | |
var p; | |
for (p in podlings) { | |
p = podlings[p]; | |
if (p['podling']) { | |
evo[p.started]['started'].push(p); | |
} | |
} | |
// add podlings history | |
for (p in podlingsHistory) { | |
p = podlingsHistory[p]; | |
evo[p.started]['started'].push(p); | |
evo[p.ended][p.status].push(p); | |
} | |
// compute data | |
var data = []; | |
var cur = 0; | |
var d; | |
for (d in evo) { | |
var started = evo[d]['started']; | |
var graduated = evo[d]['graduated']; | |
var retired = evo[d]['retired']; | |
var startedDisplay = []; | |
for (p in started) { | |
p = started[p]; | |
startedDisplay.push(p.name + (p['ended'] ? ' (' + p.status + ' ' + p.ended + ')':'')); | |
} | |
var graduatedDisplay = []; | |
for (p in graduated) { | |
p = graduated[p]; | |
graduatedDisplay.push(p.name + ' (started ' + p.started + ')'); | |
} | |
var retiredDisplay = []; | |
for (p in retired) { | |
p = retired[p]; | |
retiredDisplay.push(p.name + ' (started ' + p.started + ')'); | |
} | |
cur += started.length - graduated.length - retired.length; | |
data.push([d, started.length, htmlListTooltip(d, 'new podling', startedDisplay), | |
-1*graduated.length, htmlListTooltip(d, 'graduated podling', graduatedDisplay), | |
-1*retired.length, htmlListTooltip(d, 'retired podling', retiredDisplay), cur]); | |
} | |
//narr.sort(function(a,b) { return (b[1] - a[1]) }); | |
var dataTable = new google.visualization.DataTable(); | |
dataTable.addColumn('string', 'Month'); | |
dataTable.addColumn('number', "New podlings"); | |
dataTable.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}}); | |
dataTable.addColumn('number', "Graduated podlings"); | |
dataTable.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}}); | |
dataTable.addColumn('number', "Retired podlings"); | |
dataTable.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}}); | |
dataTable.addColumn('number', 'Current podlings'); | |
dataTable.addRows(data); | |
var coptions = { | |
title: "Incubating projects evolution", | |
isStacked: true, | |
height: 320, | |
width: 1160, | |
seriesType: "bars", | |
backgroundColor: 'transparent', | |
colors: ['#3366cc', '#109618', '#dc3912', '#ff9900'], | |
series: {3: {type: "line", targetAxisIndex: 1}}, | |
tooltip: {isHtml: true}, | |
vAxes: [ | |
{title: 'Change in states', ticks: [-6,-3,0,3,6]}, | |
{title: 'Current number of podlings'} | |
] | |
}; | |
var div = document.createElement('div'); | |
document.getElementById('details').appendChild(div); | |
var chart = new google.visualization.ComboChart(div); | |
chart.draw(dataTable, coptions); | |
} | |
function renderLanguageChart() { | |
var obj = document.getElementById('details'); | |
// Languages | |
var lingos = []; | |
var lcount = {}; | |
for (var i in projects) { | |
i = projects[i]; | |
if (i['programming-language']) { | |
var a = i['programming-language'].split(", "); | |
for (var x in a) { | |
x = a[x]; | |
if (lingos.indexOf(x) < 0) { | |
lingos.push(x); | |
lcount[x] = 0; | |
} | |
lcount[x]++; | |
} | |
} | |
} | |
var narr = []; | |
for (i in lingos) { | |
var lang = lingos[i]; | |
narr.push([lang, lcount[lang], 'Click here to view all projects using ' + lang]); | |
} | |
narr.sort(function(a,b) { return (b[1] - a[1]) }); | |
var data = new google.visualization.DataTable(); | |
data.addColumn('string', 'Language'); | |
data.addColumn('number', "Projects using it"); | |
data.addColumn({type: 'string', role: 'tooltip'}); | |
data.addRows(narr); | |
var options = { | |
title: 'Language distribution (click on a language to view all projects using it)', | |
height: 400, | |
backgroundColor: 'transparent' | |
}; | |
var chartDiv = document.createElement('div'); | |
var chart = new google.visualization.PieChart(chartDiv); | |
obj.appendChild(chartDiv); | |
function selectHandlerLanguage() { | |
var selectedItem = chart.getSelection()[0]; | |
if (selectedItem) { | |
var value = data.getValue(selectedItem.row, 0); | |
location.href = "projects.html?language#" + value; | |
} | |
} | |
google.visualization.events.addListener(chart, 'select', selectHandlerLanguage); | |
chart.draw(data, options); | |
// Categories | |
var cats = []; | |
var ccount = {}; | |
for (i in projects) { | |
i = projects[i]; | |
if (i.category) { | |
var a = i.category.split(", "); | |
for (x in a) { | |
if (cats.indexOf(a[x]) < 0) { | |
cats.push(a[x]); | |
ccount[a[x]] = 0; | |
} | |
ccount[a[x]]++; | |
} | |
} | |
} | |
var carr = []; | |
for (i in cats) { | |
var cat = cats[i]; | |
carr.push([cat, ccount[cat], 'Click here to view all projects in the ' + cat + ' category']) | |
} | |
carr.sort(function(a,b) { return (b[1] - a[1]) }); | |
var data2 = new google.visualization.DataTable(); | |
data2.addColumn('string', 'Category'); | |
data2.addColumn('number', "Projects"); | |
data2.addColumn({type: 'string', role: 'tooltip'}); | |
data2.addRows(carr); | |
var options2 = { | |
title: 'Categories (click on a category to view projects within it)', | |
height: 400, | |
backgroundColor: 'transparent' | |
}; | |
var chartDiv2 = document.createElement('div'); | |
var chart2 = new google.visualization.PieChart(chartDiv2); | |
obj.appendChild(chartDiv2); | |
function selectHandlerCategory() { | |
var selectedItem = chart2.getSelection()[0]; | |
if (selectedItem) { | |
var value = data2.getValue(selectedItem.row, 0); | |
location.href = "projects.html?category#" + value; | |
} | |
} | |
google.visualization.events.addListener(chart2, 'select', selectHandlerCategory); | |
chart2.draw(data2, options2); | |
} | |
// This is the entry point from index.html and about.html | |
function buildFrontPage() { | |
renderFrontPage({}, null) | |
} | |
// ------- Account creation chart function -------- \\ | |
function drawAccountCreation(json) { | |
var i; | |
var j; | |
var narr = []; | |
var cdata = new google.visualization.DataTable(); | |
cdata.addColumn('string', 'Date'); | |
cdata.addColumn('number', 'New committers'); | |
cdata.addColumn('number', 'Total number of committers'); | |
var max = 0; | |
var jsort = []; | |
for (j in json) { | |
jsort.push(j); | |
} | |
jsort.sort(); | |
var c = 0; | |
for (i in jsort) { | |
i = jsort[i]; | |
var entry = json[i]; | |
var arr = i.split("-"); | |
var d = new Date(parseInt(arr[0]), parseInt(arr[1]), 1); | |
c += entry; | |
narr.push([i, entry, c]); | |
max = (max < entry) ? entry : max; | |
} | |
cdata.addRows(narr); | |
var options = { | |
title: ('Account creation timeline'), | |
backgroundColor: 'transparent', | |
height: 320, | |
width: 1160, | |
vAxes:[ | |
{title: 'New accounts', titleTextStyle: {color: '#0000DD'}, maxValue: Math.max(200,max)}, | |
{title: 'Total number of accounts', titleTextStyle: {color: '#DD0000'}}, | |
], | |
series: { | |
1: {type: "line", pointSize:3, lineWidth: 3, targetAxisIndex: 1}, | |
0: {type: "bars", targetAxisIndex: 0} | |
}, | |
seriesType: "bars", | |
tooltip: {isHtml: true} | |
}; | |
var obj = document.createElement('div'); | |
obj.style = "float: left; width: 1160px; height: 450px;"; | |
obj.setAttribute("id", 'accountchart'); | |
var contents = document.getElementById('contents'); | |
contents.innerHTML = "<h1>Timelines</h1>"; | |
contents.appendChild(obj); | |
var chart = new google.visualization.ComboChart(obj); | |
chart.draw(cdata, options); | |
} | |
// called by timelines.html | |
function buildTimelines() { | |
GetAsyncJSON("json/foundation/accounts-evolution.json", null, drawAccountCreation); | |
} | |
// called by timelines2.html | |
function buildTimelines2() { | |
GetAsyncJSON("json/foundation/accounts-evolution2.json", null, drawAccountCreation); | |
} | |
// ------------ Search feature for the site ------------\\ | |
function searchProjects(str) { | |
var obj = document.getElementById('contents'); | |
str = str.toLowerCase(); | |
var hits = {}; | |
var hitssorted = []; | |
// Search committees | |
for (p in projects) { | |
var project = projects[p]; | |
for (key in project) { | |
if (typeof project[key] == "string") { | |
var val = project[key].toLowerCase(); | |
if (val.indexOf(str) >= 0 && val.substr(0,1) != "{") { | |
if (!hits[p]) { | |
hits[p] = []; | |
} | |
var estr = new RegExp(str, "i"); | |
hits[p].push({ | |
'key': key, | |
'val': project[key].replace(estr, function(a) { return "<u style='color: #963;'>"+a+"</u>"}, "img") | |
}); | |
if (hitssorted.indexOf(p) < 0) { | |
hitssorted.push(p); | |
} | |
} | |
} | |
} | |
} | |
var title = "Search results for '" + str + "' (" + hitssorted.length + "):"; | |
obj.innerHTML = ""; | |
var h2 = document.createElement('h2'); | |
h2.appendChild(document.createTextNode(title)); | |
obj.appendChild(h2); | |
hitssorted.sort(function(a,b) { return hits[b].length - hits[a].length }); | |
var ul = document.createElement('ul'); | |
var h; | |
for (h in hitssorted) { | |
h = hitssorted[h]; | |
var project = hits[h]; | |
var html = "<h4><a href='project.html?" + h + "'>" + projects[h].name + "</a> (" + project.length + " hit(s)):</h4>"; | |
for (x in project) { | |
html += "<blockquote><b>" + project[x].key + ":</b> " + project[x].val + "</blockquote>"; | |
} | |
appendLiInnerHTML(ul,html); | |
} | |
if (hitssorted.length == 0) { | |
obj.innerHTML += "No search results found"; | |
} | |
obj.appendChild(ul); | |
} | |
// Key press monitoring for search field | |
function checkKeyPress(e, txt) { | |
if (!e) e = window.event; | |
var keyCode = e.keyCode || e.which; | |
if (keyCode == '13'){ | |
searchProjects(txt.value); | |
} | |
} | |
// ------------ Weave functions ------------\\ | |
function weaveById(list,mapById) { | |
for (var i in list) { | |
var o = list[i]; | |
mapById[o.id] = o; | |
} | |
} | |
function fixProjectName(project) { | |
// fix attic and incubator project names if necessary | |
if (project.pmc == "attic") { | |
project.name += " (in the Attic)"; | |
} else if ((project.pmc == "incubator") && !project.name.match(/incubating/i)) { | |
project.name += " (Incubating)"; | |
} | |
return project; | |
} | |
// Add content by id to projects | |
function weaveInProjects(json, pfx) { | |
if (!pfx) { pfx='' } | |
for (p in json) { | |
// Since podlings are loaded first, DOAPs take precedence | |
projects[pfx+p] = fixProjectName(json[p]); | |
} | |
} | |
function weaveInRetiredCommittees(json) { | |
weaveById(json, retiredCommittees); | |
var p; | |
var projectsPmcs = {}; | |
for (p in projects) { | |
projectsPmcs[projects[p].pmc] = p; | |
} | |
var c; | |
for (c in committees) { | |
c = committees[c]; | |
if (!projectsPmcs[c.id] && c.id != 'attic') { | |
// no DOAP file written by the PMC: creating default content | |
projects[c.id] = { | |
'name': c.name, | |
'homepage': c.homepage, | |
'pmc': c.id | |
} | |
} | |
} | |
} | |
function setCommittees(json, state) { | |
weaveById(json, committees); | |
var c; | |
for (c in json) { | |
c = json[c]; | |
// committeesByName = { name -> committee } | |
committeesByName[c.name] = c; | |
} | |
if (state) { | |
state(); | |
} | |
} | |
// Render releases using datatables | |
function renderReleases(releases) { | |
var arr = []; | |
for (p in releases) { | |
var releasedata = releases[p]; | |
for (filename in releasedata) { | |
var date = releasedata[filename]; | |
// Shove the result into a row | |
arr.push([ p, p, date, filename]); | |
} | |
} | |
// Construct the data table | |
$('#contents2').html( '<table cellpadding="0" cellspacing="0" border="0" class="display" id="releases"></table>' ); | |
$('#releases').dataTable( { | |
"data": arr, | |
"columns": [ | |
{ "title": "ID", "visible": false }, | |
{ "title": "Name" }, | |
{ "title": "Date" }, | |
{ "title": "Release name" } | |
], | |
"fnRowCallback": function( nRow, aData, iDisplayIndex, iDisplayIndexFull) { | |
jQuery(nRow).attr('id', aData[0]); | |
jQuery(nRow).css("cursor", "pointer"); | |
return nRow; | |
} | |
} ); | |
$('#releases tbody').on('click', 'tr', function () { | |
var name = $(this).attr('id').replace("incubator-","incubator/"); | |
location.href = "https://www.apache.org/dist/" + name; | |
} ); | |
} | |
// Generate a 'Link to here' pop-up marker | |
function linkToHere(id) { | |
return "<a class='sectionlink' href='#"+id+"' title='Link to here'>¶</a>" | |
} | |
// Called by releases.html | |
function buildReleases() { | |
GetAsyncJSON("json/foundation/releases.json", null, renderReleases); | |
} | |
// ------------ Async data fetching ------------\\ | |
// This function is the starter of every page, and preloads the needed files | |
// before running the final page renderer. This is roughly 1 mb of JSON, but as | |
// it gets cached after first run, it's not really a major issue. | |
function preloadEverything(callback) { | |
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith | |
if (!String.prototype.startsWith) { | |
String.prototype.startsWith = function (searchString, position) { | |
position = position || 0; | |
return this.substr(position, searchString.length) === searchString; | |
}; | |
} | |
GetAsyncJSONArray([ | |
["json/foundation/committees.json", "committees", setCommittees], | |
["json/foundation/groups.json", "groups", function(json) { unixgroups = json; }], | |
["json/foundation/people_name.json", "people", function(json) { people = json; }], | |
["json/foundation/podlings.json", "podlings", function(json) { podlings = json; weaveInProjects(json,'incubator-')}], // do this first | |
["json/foundation/projects.json", "projects", weaveInProjects], // so can replace with DOAP data where it exists | |
["json/foundation/committees-retired.json", "retired committees", weaveInRetiredCommittees], | |
["json/foundation/podlings-history.json", "podlings history", function(json) { podlingsHistory = json; }], | |
["json/foundation/repositories.json", "repositories", function(json) { repositories = json; }] | |
], | |
callback); | |
} |