| /* |
| |
| 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 |
| |
| https://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(); |
| } |
| } |
| |
| // See project_editor.js (not currently used) |
| |
| // ------------ Project information page ------------\\ |
| |
| function linkCommitterIndex(cid) { |
| var fullname = people[cid]; |
| var cl = isMember(cid) ? "member" : "committer"; |
| return "<a class='" + cl + "' title='" + cid + "' href='https://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 projectIdToUnixGroup(projectId, pmcName) { |
| // 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 (pmcName === "attic") { |
| unixgroup = "attic"; |
| } |
| return unixgroup; |
| } |
| |
| 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')); |
| |
| var unixgroup = projectIdToUnixGroup(projectId, project && project.pmc); |
| |
| 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]; |
| // categories are downcased so fix up the anchor |
| pls += "<a href='projects.html?category#" + cat.toLowerCase() + "'>" + 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) |
| } |
| |
| // extract committee name from repo name |
| function repoToCommittee(reponame) { |
| if (reponame.startsWith('empire-db')) { |
| return 'empire-db'; |
| } |
| return reponame.split('-')[0]; |
| } |
| |
| 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 <a href='https://www.apache.org/#projects-list' target='_blank'>Top Level Project</a>)</font></h1>"; |
| |
| var description; |
| if (!_.isEmpty(committee.shortdesc)) { |
| description = committee.shortdesc; |
| } else { |
| description = "Missing from https://whimsy.apache.org/public/ committee-info.json"; |
| } |
| |
| appendElementWithInnerHTML(obj, 'h4', "Description <font size='-2'>(from <a href='https://whimsy.apache.org/public/' target='_blank'>committee-info</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' target='_blank'>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 <a href='https://whimsy.apache.org/public/' target='_blank'>committee-info</a>; updated daily)</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; updated daily (" + 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 (DOAP), 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 (committeeId == repoToCommittee(r)) { |
| repos.push(r); |
| } |
| } |
| if (repos.length > 0) { |
| appendElementWithInnerHTML(obj, 'h4', |
| "Source repositories managed by this Committee <font size='-2'>" + |
| "(from <a href='https://gitbox.apache.org/repositories.json'>ASF Git repos</a>" + |
| " and <a href='https://svn.apache.org/repos/asf/'>ASF SVN repos</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> - " + committee.shortdesc; |
| } |
| |
| 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, compress_spaces(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 toc = document.createElement('p'); |
| var toch = document.createElement('h3'); |
| toch.textContent = 'TOC'; |
| toc.appendChild(toch); |
| var ul = document.createElement('ul'); |
| |
| var l; |
| for (l in lingos) { |
| var lang = lingos[l]; |
| var tocitem = document.createElement('a'); |
| tocitem.href="#" + lang; |
| tocitem.innerHTML=lang; |
| if (l > 0) { // divider |
| toc.appendChild(document.createTextNode(', ')); |
| } |
| toc.appendChild(tocitem); |
| 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(toc); |
| 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 toc = document.createElement('p'); |
| var toch = document.createElement('h3'); |
| toch.textContent = 'TOC'; |
| toc.appendChild(toch); |
| var ul = document.createElement('ul'); |
| |
| var l; |
| for (l in cats) { |
| var cat = cats[l]; |
| var tocitem = document.createElement('a'); |
| tocitem.href="#" + cat; |
| tocitem.innerHTML=cat; |
| if (l > 0) { // divider |
| toc.appendChild(document.createTextNode(', ')); |
| } |
| toc.appendChild(tocitem); |
| var li = document.createElement('li'); |
| li.innerHTML = "<h3><a id='" + cat + "'>" + cat + " (" + ccount[cat] + ")</a>"+linkToHere(cat)+":</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 == cat) { |
| appendLiInnerHTML(cul, projectIcon(project.name) + projectLink(i)); |
| } |
| } |
| } |
| } |
| li.appendChild(cul); |
| ul.appendChild(li); |
| } |
| |
| obj.appendChild(toc); |
| 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 (projectId in projects) { |
| let unixGroup = projectIdToUnixGroup(projectId); |
| if (unixgroups[unixGroup] && projectId !== 'incubator') { |
| let len = unixgroups[unixGroup].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 projectId; |
| for (projectId in projectsSorted) { |
| projectId = projectsSorted[projectId]; |
| let unixGroup = projectIdToUnixGroup(projectId); |
| if (unixgroups[unixGroup]) { |
| var xlen = unixgroups[unixGroup].length; |
| if (xlen == len) { |
| var html = projectIcon(projects[projectId].name) + projectLink(projectId) + ": " + len + " committers"; |
| if (unixgroups[unixGroup+'-pmc']) { |
| html += ", " + unixgroups[unixGroup+'-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) { |
| if (project.doap) { |
| appendLiInnerHTML(cul, projectIcon(project.name) + projectLink(i)); |
| } else { |
| c=0; |
| if (xlpmc == 'incubator') { |
| appendLiInnerHTML(cul, "<b>"+ project.name + ": please <a href='https://projects.apache.org/create.html'>create a DOAP</a> file</b>"); |
| } else { |
| appendLiInnerHTML(cul, "<b>Please <a href='https://projects.apache.org/create.html'>create a DOAP</a> file</b>"); |
| } |
| } |
| } |
| } |
| } |
| } |
| 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 compress_spaces(s) { |
| return s.replace(/ +/g, ' '); |
| } |
| |
| 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 update_id(id, val) { |
| var obj = document.getElementById(id); |
| if (obj) { |
| obj.innerHTML = val; |
| } |
| } |
| function renderFrontPage() { |
| var numcommittees = 0; |
| var i; |
| for (i in committees) numcommittees++; |
| var curPodlings = 0; |
| for (i in podlings) curPodlings++; |
| |
| // The projects list contains 1 entry for each podling, as well as 1 entry for each DOAP. |
| // Each podling relates to a single project, but a PMC may have one or more projects. |
| // However not all projects may have registered DOAPs. |
| // In order to find these missing projects, we need to find projects that have not registered DOAPs |
| |
| var projectsWithDoaps = {}; // ids of projects which have registered DOAPS |
| var numProjects = 0; // total projects run by active PMCs |
| for (j in projects) { |
| i = projects[j]; |
| projectsWithDoaps[i.pmc] = 1; // which projects have got DOAPs |
| if (i.pmc != 'attic' && i.pmc != 'incubator') { |
| numProjects++; // found a project run by an active PMC (not podling or retired) |
| } |
| } |
| var numprojectsWithDoaps = 0; // how many projects have registered DOAPs |
| for (i in projectsWithDoaps) numprojectsWithDoaps++; |
| numProjects += (numcommittees - numprojectsWithDoaps); // Add in projects without DOAPs |
| var obj = document.getElementById('details'); |
| if (urlErrors.length > 0) { |
| obj.innerHTML += "<p><span style='color: red'><b>Warning: could not load: "+urlErrors.join(', ')+"</b></span></p>" |
| } |
| update_id('loading', ''); // clear the loading messages |
| // N.B. These ids must agree with the 'details' div in index.html |
| update_id('committees', numcommittees); |
| update_id('projects', numProjects); |
| update_id('podlings', curPodlings); |
| 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]) }); |
| |
| // ================= echarts start ================== |
| |
| var chartDom = document.createElement('div'); |
| |
| document.getElementById('details').appendChild(chartDom); |
| var myChart = echarts.init(chartDom, null, {width: 1160, height: 320}); |
| |
| var months = []; // x-axis |
| var newPMCs = []; |
| var retired = []; |
| var current = []; |
| |
| // pre-generated tooltips |
| var tips = [[],[],[]]; // indexed by series index and data index |
| |
| // extract the data for echarts |
| for (d in data) { |
| var e = data[d]; |
| months.push(e[0]); |
| newPMCs.push(e[1]); |
| tips[0].push(e[2]); |
| retired.push(e[3]); |
| tips[1].push(e[4]); |
| current.push(e[5]); |
| } |
| var option = { |
| title: { |
| text: "Evolution of Committees (also called PMCs or Top Level Projects)", |
| left: 125, // should agree with left below |
| }, |
| legend: { |
| top: 25, // so does not overwrite title |
| left: 125, // should agree with left above |
| }, |
| tooltip: { |
| type: 'item', |
| formatter: function(obj) { |
| if (obj.seriesName == 'Current committees') { |
| return `<b>${obj.name}</b><br/><br/>Current committees: <b>${obj.value}</b>`; |
| } |
| var idx = parseInt(obj.dataIndex); |
| var sidx = parseInt(obj.seriesIndex); |
| var value = tips[sidx][idx]; |
| return value; |
| } |
| }, |
| xAxis: [ |
| { |
| type: 'category', |
| axisTick: { |
| alignWithLabel: true |
| }, |
| axisLabel: { |
| rotate: 30 |
| }, |
| data: months |
| } |
| ], |
| yAxis: [ |
| { |
| type: 'value', |
| name: 'Change in states', |
| nameTextStyle: { |
| fontStyle: 'italic', |
| fontSize: 15, |
| }, |
| axisLabel: { |
| customValues: [-3, 0, 3, 6, 9], |
| }, |
| nameLocation: 'middle', |
| nameGap: 50, |
| min: -3, |
| max: 9, |
| }, |
| { |
| type: 'value', |
| name: 'Current number of committees', |
| nameTextStyle: { |
| fontStyle: 'italic', |
| fontSize: 15, |
| }, |
| nameLocation: 'middle', |
| nameGap: 50, |
| min: 0, |
| max: 300, |
| position: 'right', |
| } |
| ], |
| series: [ |
| { |
| name: 'New committees', |
| type: 'bar', |
| stack: 'Total', |
| yAxisIndex: 0, |
| data: newPMCs |
| }, |
| { |
| name: 'Retired commitees', |
| type: 'bar', |
| barWidth: 1, // must be on last bar series |
| stack: 'Total', |
| yAxisIndex: 0, |
| data: retired |
| }, |
| { |
| name: 'Current committees', |
| type: 'line', |
| showAllSymbol: true, // ensure all points have tooltips |
| itemStyle: { |
| opacity: 0 // don't want circles showing |
| }, |
| yAxisIndex: 1, |
| data: current |
| } |
| ], |
| |
| // Same colours as Google charts |
| color: ['#3366CC', '#DC3912', '#FF9900'] |
| }; |
| myChart.setOption(option); |
| |
| // ================= echarts end ================== |
| |
| } |
| |
| 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]) }); |
| |
| // ================= echarts start ================== |
| |
| var chartDom = document.createElement('div'); |
| |
| document.getElementById('details').appendChild(chartDom); |
| var myChart = echarts.init(chartDom, null, {width: 1160, height: 320}); |
| |
| var months = []; // x-axis |
| var newPods = []; |
| var retired = []; |
| var graduated = []; |
| var current = []; |
| |
| // pre-generated tooltips |
| var tips = [[],[],[]]; // indexed by series index and data index |
| |
| // extract the data for echarts |
| for (d in data) { |
| var e = data[d]; |
| months.push(e[0]); |
| newPods.push(e[1]); |
| tips[0].push(e[2]); |
| graduated.push(e[3]); |
| tips[1].push(e[4]); |
| retired.push(e[5]); |
| tips[2].push(e[6]); |
| current.push(e[7]); |
| } |
| var option = { |
| title: { |
| text: "Evolution of Incubating projects ('podlings')", |
| left: 125, // should agree with left below |
| }, |
| legend: { |
| top: 25, // so does not overwrite title |
| left: 125, // should agree with left above |
| }, |
| tooltip: { |
| type: 'item', |
| formatter: function(obj) { |
| if (obj.seriesName == 'Current podlings') { |
| return `<b>${obj.name}</b><br/><br/>Current podlings: <b>${obj.value}</b>`; |
| } |
| var idx = parseInt(obj.dataIndex); |
| var sidx = parseInt(obj.seriesIndex); |
| var value = tips[sidx][idx]; |
| return value; |
| } |
| }, |
| xAxis: [ |
| { |
| type: 'category', |
| axisTick: { |
| alignWithLabel: true |
| }, |
| axisLabel: { |
| rotate: 30 |
| }, |
| data: months |
| } |
| ], |
| yAxis: [ |
| { |
| type: 'value', |
| name: 'Change in states', |
| nameTextStyle: { |
| fontStyle: 'italic', |
| fontSize: 15, |
| }, |
| axisLabel: { |
| customValues: [-6,-3,0,3,6], |
| }, |
| nameLocation: 'middle', |
| nameGap: 50, |
| min: -9, |
| max: 6, |
| }, |
| { |
| type: 'value', |
| name: 'Current number of podlings', |
| nameTextStyle: { |
| fontStyle: 'italic', |
| fontSize: 15, |
| }, |
| nameLocation: 'middle', |
| nameGap: 50, |
| min: 0, |
| max: 80, |
| position: 'right', |
| } |
| ], |
| series: [ |
| { |
| name: 'New podlings', |
| type: 'bar', |
| stack: 'Total', |
| yAxisIndex: 0, |
| data: newPods |
| }, |
| { |
| name: 'Graduated podlings', |
| type: 'bar', |
| stack: 'Total', |
| yAxisIndex: 0, |
| data: graduated |
| }, |
| { |
| name: 'Retired podlings', |
| type: 'bar', |
| stack: 'Total', |
| yAxisIndex: 0, |
| data: retired |
| }, |
| { |
| name: 'Current podlings', |
| type: 'line', |
| showAllSymbol: true, // ensure all points have tooltips |
| itemStyle: { |
| opacity: 0 // don't want circles showing |
| }, |
| yAxisIndex: 1, |
| data: current |
| } |
| ], |
| |
| // Same colours as Google charts |
| color: ['#3366CC', '#109618', '#DC3912', '#FF9900'] |
| }; |
| myChart.setOption(option); |
| |
| // ================= echarts end ================== |
| |
| } |
| |
| 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(/,\s*/); |
| 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 declared projects using ' + lang]); |
| } |
| narr.sort(function(a,b) { return (b[1] - a[1]) }); |
| |
| // ================= echarts start ================== |
| |
| var chartDom1 = document.createElement('div'); |
| |
| document.getElementById('details').appendChild(chartDom1); |
| var myChart1 = echarts.init(chartDom1, null, {width: 800, height: 400}); |
| |
| const leg1 = []; |
| const num1 = []; |
| for (i in narr) { |
| leg1.push(narr[i][0]); |
| num1.push({name: narr[i][0], value: narr[i][1]}); |
| } |
| |
| var option = { |
| title: { |
| text: 'Language distribution (click on a language to view declared projects using it)', |
| textStyle: { |
| fontSize: 15 |
| }, |
| left: 130 |
| }, |
| tooltip: { |
| trigger: 'item', |
| // formatter: '{a} {b} ({d}%)' |
| }, |
| legend: { |
| type: 'scroll', |
| orient: 'vertical', |
| right: 10, |
| top: 30, |
| bottom: 300, |
| data: leg1 |
| }, |
| series: [ |
| { |
| name: 'Click here to view declared projects using ', |
| type: 'pie', |
| radius: '65%', |
| center: ['50%', '50%'], |
| data: num1, |
| label: { |
| position: 'inner', |
| textStyle: { |
| fontSize: 15 |
| }, |
| formatter: function(obj) { |
| if (obj.percent > 8) { |
| return Math.round(obj.percent*10)/10 + '%'; // round to one decimal point |
| } else { |
| return ''; |
| } |
| } |
| } |
| } |
| ] |
| }; |
| |
| myChart1.setOption(option); |
| myChart1.on('click', function(params) { |
| var name = params.data.name; |
| window.location.href = 'projects.html?language#'+ name; |
| }); |
| |
| // ================= echarts end ==================== |
| |
| // Categories |
| var cats = []; |
| var ccount = {}; |
| for (i in projects) { |
| i = projects[i]; |
| if (i.category) { |
| var a = i.category.split(/,\s*/); |
| 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 declared projects in the ' + cat + ' category']) |
| } |
| carr.sort(function(a,b) { return (b[1] - a[1]) }); |
| |
| // ================= echarts start ================== |
| |
| var chartDom2 = document.createElement('div'); |
| |
| document.getElementById('details').appendChild(chartDom2); |
| var myChart2 = echarts.init(chartDom2, null, {width: 800, height: 400}); |
| |
| const leg2 = []; |
| const num2 = []; |
| for (i in carr) { |
| leg2.push(carr[i][0]); |
| num2.push({name: carr[i][0], value: carr[i][1]}); |
| } |
| |
| var option = { |
| title: { |
| text: 'Categories (click on a category to view declared projects within it)', |
| textStyle: { |
| fontSize: 15 |
| }, |
| left: 130 |
| }, |
| tooltip: { |
| trigger: 'item', |
| }, |
| legend: { |
| type: 'scroll', |
| orient: 'vertical', |
| right: 10, |
| top: 30, |
| bottom: 300, |
| data: leg2 |
| }, |
| series: [ |
| { |
| name: 'Click here to view declared projects in category ', |
| type: 'pie', |
| radius: '65%', |
| center: ['50%', '50%'], |
| data: num2, |
| label: { |
| position: 'inner', |
| textStyle: { |
| fontSize: 15 |
| }, |
| formatter: function(obj) { |
| if (obj.percent > 8) { |
| return Math.round(obj.percent*10)/10 + '%'; // round to one decimal point |
| } else { |
| return ''; |
| } |
| } |
| } |
| } |
| ] |
| }; |
| |
| myChart2.setOption(option); |
| myChart2.on('click', function(params) { |
| var name = params.data.name; |
| window.location.href = 'projects.html?category#'+ name; |
| }); |
| |
| // ================= echarts end ==================== |
| |
| |
| } |
| |
| // 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 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; |
| } |
| |
| var chartDom = document.createElement('div'); |
| chartDom.style = "float: left; width: 1160px; height: 450px;"; |
| chartDom.setAttribute("id", 'accountchart'); |
| var contents = document.getElementById('contents'); |
| contents.innerHTML = "<h1>Timelines</h1>"; |
| contents.appendChild(chartDom); |
| |
| // ================= echarts start ================== |
| |
| var myChart = echarts.init(chartDom, null, {width: 1160, height: 450}); |
| |
| var months = []; // x-axis |
| var newAccounts = []; |
| var current = []; |
| |
| // pre-generated tooltips |
| var tips = [[],[],[]]; // indexed by series index and data index |
| |
| // extract the data for echarts |
| for (d in narr) { |
| var e = narr[d]; |
| months.push(e[0]); |
| newAccounts.push(e[1]); |
| current.push(e[2]); |
| } |
| var option = { |
| title: { |
| text: "Account creation timeline", |
| left: 125, // should agree with left below |
| }, |
| legend: { |
| top: 25, // so does not overwrite title |
| left: 125, // should agree with left above |
| }, |
| tooltip: { |
| type: 'item', |
| }, |
| xAxis: [ |
| { |
| type: 'category', |
| axisTick: { |
| alignWithLabel: true |
| }, |
| axisLabel: { |
| rotate: 30 |
| }, |
| data: months |
| } |
| ], |
| yAxis: [ |
| { |
| type: 'value', |
| name: 'New accounts', |
| nameTextStyle: { |
| fontStyle: 'italic', |
| fontSize: 15, |
| }, |
| nameLocation: 'middle', |
| nameGap: 50, |
| // min: 0, |
| // max: 200, |
| }, |
| { |
| type: 'value', |
| name: 'Total number of accounts', |
| nameTextStyle: { |
| fontStyle: 'italic', |
| fontSize: 15, |
| }, |
| nameLocation: 'middle', |
| position: 'right', |
| nameGap: 50, |
| // min: 0, |
| // max: 15000, |
| } |
| ], |
| series: [ |
| { |
| name: 'New accounts', |
| type: 'bar', |
| barWidth: 1, |
| yAxisIndex: 0, |
| data: newAccounts |
| }, |
| { |
| name: 'Total number of accounts', |
| type: 'line', |
| showAllSymbol: true, // ensure all points have tooltips |
| itemStyle: { |
| opacity: 0 // don't want circles showing |
| }, |
| yAxisIndex: 1, |
| data: current |
| } |
| ], |
| |
| // Same colours as Google charts |
| color: ['#3366CC', '#DC3912', '#FF9900'] |
| }; |
| myChart.setOption(option); |
| |
| // ================= echarts end ================== |
| |
| } |
| |
| // called by timelines.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; |
| } |
| } |
| |
| 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-",""); |
| location.href = `https://${name}.apache.org/`; |
| } ); |
| } |
| |
| // 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?" + Math.random(), 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); |
| } |