blob: e2760a9da09715a556d9d7d1f284a25c8865e42a [file] [log] [blame]
/*
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> &nbsp; ";
}
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(", &nbsp;") + "</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) + "&nbsp; ";
} else {
maintainers += "<a href='" + mt.mbox + "'>" + mt.name + "</a>&nbsp; ";
}
} else {
maintainers += mt.name + "&nbsp; ";
}
}
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>&nbsp; ";
}
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>&nbsp; ";
}
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(",&nbsp; ") + "</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(",&nbsp; ") + "</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'>&para;</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);
}