diff --git a/docs/_data/navigation.yml b/docs/_data/navigation.yml
index 6295bb48..dfbc1c17 100644
--- a/docs/_data/navigation.yml
+++ b/docs/_data/navigation.yml
@@ -23,6 +23,8 @@
url: "/repos-activity"
- title: "Total"
url: "/repos-total"
+- title: "Organizations"
+ url: "/org-owners"
- title: "Housekeeping"
url: "/housekeeping-git-traffic"
subnavigation:
diff --git a/docs/assets/js/charts.js b/docs/assets/js/charts.js
index c21b8327..d6c02779 100644
--- a/docs/assets/js/charts.js
+++ b/docs/assets/js/charts.js
@@ -362,20 +362,30 @@ function createTable(table)
.enter()
.append("td")
.each(function(d, i) {
- var cell = d3.select(this);
- switch (data.columns[i].toLowerCase()) {
- case "user":
- case "organization":
- case "repository":
- case "resource":
- case "fork":
- cell = cell
- .append("a")
- .attr("target", "_blank")
- .attr("href", gheUrl() + "/" + d);
- break;
+ const cell = d3.select(this);
+ const entries = d.split(/[\s,]+/);
+ const column = data.columns[i].toLowerCase();
+
+ for (let j = 0; j < entries.length; j++) {
+ if (j > 0)
+ cell.append().text(", ")
+ const entry = entries[j];
+ switch (column) {
+ case "fork":
+ case "organization":
+ case "owner(s)":
+ case "repository":
+ case "resource":
+ case "user":
+ cell.append("a")
+ .attr("target", "_blank")
+ .attr("href", gheUrl() + "/" + entry)
+ .text(entry);
+ break;
+ default:
+ cell.append().text(entry);
+ }
}
- cell.text(function(d) { return d; })
});
});
}
diff --git a/docs/demo-data/org-owners.tsv b/docs/demo-data/org-owners.tsv
new file mode 100644
index 00000000..f3b480a8
--- /dev/null
+++ b/docs/demo-data/org-owners.tsv
@@ -0,0 +1,10 @@
+organization owner(s)
+Alpha user39, user72, user81, user20
+Bravo user50, user39
+Charlie user80
+Delta user78, user59, user86
+Echo user70
+Foxtrot user24, user87
+Golf user36, user90, user27, user40, user67
+Hotel user67, user90
+India user65
diff --git a/docs/org-owners.html b/docs/org-owners.html
new file mode 100644
index 00000000..1fd3537b
--- /dev/null
+++ b/docs/org-owners.html
@@ -0,0 +1,21 @@
+---
+layout: default
+title: Organization Owners
+permalink: /org-owners
+---
+
+Organization Owners
+
+
+
+
+
+
+
Owners for each GitHub organization.
+
+
+
diff --git a/updater/reports/Report.py b/updater/reports/Report.py
index b1e759d3..16b0870b 100644
--- a/updater/reports/Report.py
+++ b/updater/reports/Report.py
@@ -100,6 +100,15 @@ def executeScript(self, script, stdin = None):
return stdout
+ def executeGHEConsole(self, rubyCode):
+ escapedRubyCode = rubyCode.replace('\\t', '\\\\t') \
+ .replace('\\n', '\\\\n') \
+ .replace('"', '\\"')
+ return self.executeScript(
+ ["bash -s", "--"],
+ "github-env bin/runner -e production \"'" + escapedRubyCode + "'\""
+ )
+
# Executes a database query, given as a string
def executeQuery(self, query):
return self.executeScript(self.configuration["databaseCommand"], stdin = query)
@@ -179,27 +188,22 @@ def update(self):
print("Finished update of " + self.name() + ".tsv (runtime: " + str(timeElapsed) + " s)", file = sys.stderr)
sys.stderr.flush()
- def andExcludedEntities(self, org, repo):
+ def andExcludedEntities(self, column, delimiter = "AND"):
query = ""
if ("excludedEntities" in self.configuration and
len(self.configuration["excludedEntities"]) > 0):
- if org:
- query += ' AND ' + org + '.login not LIKE "' + \
- self.configuration["excludedEntities"] + '" '
- if repo:
- query += ' AND ' + repo + '.name not LIKE "' + \
- self.configuration["excludedEntities"] +'" '
+ query += " " + delimiter + " " + column + ' NOT LIKE "' + self.configuration["excludedEntities"] + '" '
return query
- def andExcludedUsers(self, users, delimiter = "AND"):
+ def andExcludedUsers(self, column, delimiter = "AND"):
query = ""
for excludedUser in self.configuration["excludedUsers"]:
- query += " " + delimiter + " " + users + '.login NOT LIKE "' + excludedUser + '" '
+ query += " " + delimiter + " " + column + ' NOT LIKE "' + excludedUser + '" '
delimiter = "AND"
return query
- def whereExcludedUsers(self, users):
- return self.andExcludedUsers(users, "WHERE")
+ def whereExcludedUsers(self, column):
+ return self.andExcludedUsers(column, "WHERE")
def andExcludeMemberlessOrganizations(self, orgs):
query = ""
diff --git a/updater/reports/ReportContributorsByOrg.py b/updater/reports/ReportContributorsByOrg.py
index d2060e0f..2f40f344 100644
--- a/updater/reports/ReportContributorsByOrg.py
+++ b/updater/reports/ReportContributorsByOrg.py
@@ -27,7 +27,7 @@ def query(self, timeRange):
JOIN users ON users.id = repositories.owner_id
WHERE
pull_requests.created_at IS NOT NULL AND CAST(pull_requests.created_at AS date) BETWEEN "''' + str(timeRange[0]) + '''" AND "''' + str(timeRange[1]) + '''"
- ''' + self.andExcludedUsers("users") + '''
+ ''' + self.andExcludedUsers("users.login") + '''
GROUP by
users.id
ORDER by
diff --git a/updater/reports/ReportContributorsByRepo.py b/updater/reports/ReportContributorsByRepo.py
index b36f36d3..a19e66ce 100644
--- a/updater/reports/ReportContributorsByRepo.py
+++ b/updater/reports/ReportContributorsByRepo.py
@@ -27,7 +27,7 @@ def query(self, timeRange):
JOIN users on users.id = repositories.owner_id
WHERE
pull_requests.created_at IS NOT NULL AND CAST(pull_requests.created_at AS date) BETWEEN "''' + str(timeRange[0]) + '''" AND "''' + str(timeRange[1]) + '''"
- ''' + self.andExcludedUsers("users") + '''
+ ''' + self.andExcludedUsers("users.login") + '''
GROUP BY
repositories.id
ORDER BY
diff --git a/updater/reports/ReportForksToOrgs.py b/updater/reports/ReportForksToOrgs.py
index 27667d22..06c5aab0 100644
--- a/updater/reports/ReportForksToOrgs.py
+++ b/updater/reports/ReportForksToOrgs.py
@@ -34,8 +34,10 @@ def query(self):
parentOrgs.type = "organization" AND
parentRepos.owner_id = parentOrgs.id AND
parentRepos.id = repos.parent_id
- ''' + self.andExcludedEntities("orgs", "repos") \
- + self.andExcludedEntities("parentOrgs", "parentRepos") + '''
+ ''' + self.andExcludedEntities("orgs.login") \
+ + self.andExcludedEntities("repos.name") \
+ + self.andExcludedEntities("parentOrgs.login") \
+ + self.andExcludedEntities("parentRepos.name") + '''
ORDER BY
repos.created_at DESC
'''
diff --git a/updater/reports/ReportOrgCollaboration.py b/updater/reports/ReportOrgCollaboration.py
index bdac2a8c..2200f968 100644
--- a/updater/reports/ReportOrgCollaboration.py
+++ b/updater/reports/ReportOrgCollaboration.py
@@ -52,7 +52,8 @@ def pushCountQuery(self, timeRange):
AND orgs.id = repositories.owner_id
AND repositories.id = pushes.repository_id
AND cast(pushes.created_at AS DATE) BETWEEN "''' + str(timeRange[0]) + '''" and "''' + str(timeRange[1]) + '''"
- ''' + self.andExcludedEntities("orgs", "repositories") \
+ ''' + self.andExcludedEntities("orgs.login") \
+ + self.andExcludedEntities("repositories.name") \
+ self.andExcludeMemberlessOrganizations("orgs") + '''
GROUP BY org_id, pusher_id
'''
@@ -70,7 +71,7 @@ def homeOrgQuery(self, timeRange):
OR (a.push_count = b.push_count AND a.pusher_id != b.pusher_id)
)
WHERE b.push_count is NULL AND a.pusher_id = users.id ''' \
- + self.andExcludedUsers("users")
+ + self.andExcludedUsers("users.login")
return query
# Calculates a table that contains all users that have contributed to a
@@ -90,7 +91,8 @@ def contributorsToOrgQuery(self, timeRange):
AND orgs.id = repositories.owner_id
AND repositories.id = pushes.repository_id
AND cast(pushes.created_at AS DATE) BETWEEN "''' + str(timeRange[0]) + '''" and "''' + str(timeRange[1]) + '''"
- ''' + self.andExcludedEntities("orgs", "repositories") + '''
+ ''' + self.andExcludedEntities("orgs.login") \
+ + self.andExcludedEntities("repositories.name") + '''
UNION
@@ -105,7 +107,8 @@ def contributorsToOrgQuery(self, timeRange):
AND repositories.id = pull_requests.repository_id
AND pull_requests.merged_at IS NOT NULL
AND cast(pull_requests.created_at AS DATE) BETWEEN "''' + str(timeRange[0]) + '''" and "''' + str(timeRange[1]) + '''"
- ''' + self.andExcludedEntities("orgs", "repositories") + '''
+ ''' + self.andExcludedEntities("orgs.login") \
+ + self.andExcludedEntities("repositories.name") + '''
) contributors
GROUP BY org_id, contributor_id
'''
diff --git a/updater/reports/ReportOrgOwners.py b/updater/reports/ReportOrgOwners.py
new file mode 100644
index 00000000..01536c57
--- /dev/null
+++ b/updater/reports/ReportOrgOwners.py
@@ -0,0 +1,27 @@
+from .Report import *
+
+# Lists all orgs and their owners without site admins or suspended users
+class ReportOrgOwners(Report):
+ def name(self):
+ return "org-owners"
+
+ # The data is overwritten every day, so skip reading the old data
+ def readData(self):
+ pass
+
+ def updateData(self):
+ self.header, self.data = self.parseData(
+ self.executeGHEConsole('''
+ puts "organization\towner(s)\n"
+ User.where(:type => "Organization")
+ .where("''' + self.andExcludedEntities("login", "").replace('"', '\\\\"') + '''")
+ .order("login")
+ .each do |org|
+ owners = org.admins.where(:disabled => false, :gh_role => nil)
+ .where("''' + self.andExcludedUsers("login", "").replace('"', '\\\\"') + '''")
+ .order("login")
+ .join(",")
+ puts "#{org.login}\t#{owners}\n"
+ end
+ ''')
+ )
diff --git a/updater/reports/ReportPRByOrg.py b/updater/reports/ReportPRByOrg.py
index 712fc7ad..3505d2a7 100644
--- a/updater/reports/ReportPRByOrg.py
+++ b/updater/reports/ReportPRByOrg.py
@@ -32,7 +32,7 @@ def query(self, timeRange):
pull_requests
JOIN repositories ON repositories.id = pull_requests.repository_id
JOIN users ON users.id = repositories.owner_id
- ''' + self.whereExcludedUsers("users") + '''
+ ''' + self.whereExcludedUsers("users.login") + '''
GROUP BY
users.id
ORDER BY
diff --git a/updater/reports/ReportPRByRepo.py b/updater/reports/ReportPRByRepo.py
index 661495ff..009fbd35 100644
--- a/updater/reports/ReportPRByRepo.py
+++ b/updater/reports/ReportPRByRepo.py
@@ -32,7 +32,7 @@ def query(self, timeRange):
pull_requests
join repositories ON repositories.id = pull_requests.repository_id
join users on users.id = repositories.owner_id
- ''' + self.whereExcludedUsers("users") + '''
+ ''' + self.whereExcludedUsers("users.login") + '''
GROUP BY
repositories.id
ORDER BY
diff --git a/updater/reports/ReportPRHistory.py b/updater/reports/ReportPRHistory.py
index f112e14a..095b21f1 100644
--- a/updater/reports/ReportPRHistory.py
+++ b/updater/reports/ReportPRHistory.py
@@ -27,7 +27,7 @@ def subquery(self, type, timeRange):
WHERE
CAST(pull_requests.''' + type + ''' AS date) BETWEEN "''' + str(timeRange[0]) + '''" AND "''' + str(timeRange[1]) + '''" AND
pull_requests.user_id = users.id
- ''' + self.andExcludedUsers("users") + '''
+ ''' + self.andExcludedUsers("users.login") + '''
GROUP BY
date_format(pull_requests.''' + type + ''', "%Y-%m-%d")
ORDER BY
diff --git a/updater/reports/ReportRepoActivity.py b/updater/reports/ReportRepoActivity.py
index c13eafaa..ba9d95bb 100644
--- a/updater/reports/ReportRepoActivity.py
+++ b/updater/reports/ReportRepoActivity.py
@@ -31,7 +31,8 @@ def subquery(self, userType, timeRange):
WHERE
CAST(pushes.created_at AS DATE) BETWEEN "''' + str(timeRange[0]) + '''" AND "''' + str(timeRange[1]) + '''"'''
- query += self.andExcludedEntities("users", "repositories")
+ query += self.andExcludedEntities("users.login") \
+ + self.andExcludedEntities("repositories.name")
if userType != None:
query += ''' AND
diff --git a/updater/reports/ReportRepositoryHistory.py b/updater/reports/ReportRepositoryHistory.py
index 1e2bc7b6..4659a987 100644
--- a/updater/reports/ReportRepositoryHistory.py
+++ b/updater/reports/ReportRepositoryHistory.py
@@ -23,7 +23,8 @@ def subquery(self, userType):
repositories
JOIN users ON repositories.owner_id = users.id
WHERE
- TRUE ''' + self.andExcludedEntities("users", "repositories")
+ TRUE ''' + self.andExcludedEntities("users.login") \
+ + self.andExcludedEntities("repositories.name")
if userType != None:
query += ''' AND
diff --git a/updater/reports/ReportUsers.py b/updater/reports/ReportUsers.py
index 60a1a22a..1917c1b6 100644
--- a/updater/reports/ReportUsers.py
+++ b/updater/reports/ReportUsers.py
@@ -24,7 +24,7 @@ def usersPushingSubquery(self, timeRange):
JOIN users ON users.id = pushes.pusher_id
WHERE
CAST(pushes.created_at AS DATE) BETWEEN "''' + str(timeRange[0]) + '''" AND "''' + str(timeRange[1]) + '''"
- ''' + self.andExcludedUsers("users")
+ ''' + self.andExcludedUsers("users.login")
return query
# Collects the number of users who currently use up a seat
diff --git a/updater/update-stats.py b/updater/update-stats.py
index bb0a466c..61e6c4fc 100755
--- a/updater/update-stats.py
+++ b/updater/update-stats.py
@@ -14,6 +14,7 @@
from reports.ReportGitRequests import *
from reports.ReportGitVersions import *
from reports.ReportOrgCollaboration import *
+from reports.ReportOrgOwners import *
from reports.ReportPRByOrg import *
from reports.ReportPRByRepo import *
from reports.ReportPRHistory import *
@@ -76,6 +77,7 @@ def main():
ReportGitRequests(configuration, dataDirectory, metaStats).update()
ReportGitVersions(configuration, dataDirectory, metaStats).update()
ReportOrgCollaboration(configuration, dataDirectory, metaStats).update()
+ ReportOrgOwners(configuration, dataDirectory, metaStats).update()
ReportPRByOrg(configuration, dataDirectory, metaStats).update()
ReportPRByRepo(configuration, dataDirectory, metaStats).update()
ReportPRHistory(configuration, dataDirectory, metaStats).update()