From cedf138f3c9afeae7bcbda5dbb0511ebec297d10 Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Mon, 10 Jun 2013 18:45:32 -0400
Subject: [PATCH] Globl and per-repository setting to exclude authors form metrics (issue-251)

---
 src/main/java/com/gitblit/wicket/charting/GooglePieChart.java  |   25 +++++++-
 src/main/java/com/gitblit/models/Activity.java                 |   30 ++++++++-
 src/main/java/com/gitblit/models/RepositoryModel.java          |    3 
 src/main/java/com/gitblit/wicket/pages/DashboardPage.java      |   30 ++++++++--
 src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.html |   15 ++--
 releases.moxie                                                 |    2 
 src/main/java/com/gitblit/utils/ActivityUtils.java             |   16 +++++
 src/main/distrib/data/gitblit.properties                       |    7 ++
 src/main/java/com/gitblit/GitBlit.java                         |    3 +
 src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.java |   23 +++++++
 10 files changed, 131 insertions(+), 23 deletions(-)

diff --git a/releases.moxie b/releases.moxie
index 0a73d5b..d5c04ae 100644
--- a/releases.moxie
+++ b/releases.moxie
@@ -42,6 +42,7 @@
 	 - Updated Japanese translation
 	 
     additions: 
+	 - Global and per-repository setting to exclude authors from metrics (issue-251)
 	 - Added SalesForce.com user service
      - Added simple star/unstar function to flag or bookmark interesting repositories
      - Added Dashboard page which shows a news feed for starred repositories and offers a filterable list of repositories you care about
@@ -110,6 +111,7 @@
 	- { name: 'web.activityDurationChoices', defaultValue: '7 14 28 60 90 180' }
 	- { name: 'web.allowAppCloneLinks', defaultValue: true }
 	- { name: 'web.forceDefaultLocale', defaultValue: ' ' }
+	- { name: 'web.metricAuthorExclusions', defaultValue: ' ' }
 	- { name: 'web.overviewPushCount', defaultValue: 5 }
 	- { name: 'web.pushesPerPage', defaultValue: 10 }
 	- { name: 'server.nioThreadPoolSize', defaultValue: 50 }
diff --git a/src/main/distrib/data/gitblit.properties b/src/main/distrib/data/gitblit.properties
index a6dc315..1671507 100644
--- a/src/main/distrib/data/gitblit.properties
+++ b/src/main/distrib/data/gitblit.properties
@@ -826,6 +826,13 @@
 # SINCE 1.3.0
 web.activityDurationChoices = 7 14 28 60 90 180
 
+# Case-insensitive list of authors to exclude from metrics.  Useful for
+# eliminating bots.
+#
+# SPACE-DELIMITED
+# SINCE 1.3.0
+web.metricAuthorExclusions =
+
 # The number of commits to display on the summary page
 # Value must exceed 0 else default of 20 is used
 #
diff --git a/src/main/java/com/gitblit/GitBlit.java b/src/main/java/com/gitblit/GitBlit.java
index ceb40c1..df17edd 100644
--- a/src/main/java/com/gitblit/GitBlit.java
+++ b/src/main/java/com/gitblit/GitBlit.java
@@ -1939,6 +1939,8 @@
 					Constants.CONFIG_GITBLIT, null, "mailingList")));
 			model.indexedBranches = new ArrayList<String>(Arrays.asList(config.getStringList(
 					Constants.CONFIG_GITBLIT, null, "indexBranch")));
+			model.metricAuthorExclusions = new ArrayList<String>(Arrays.asList(config.getStringList(
+					Constants.CONFIG_GITBLIT, null, "metricAuthorExclusions")));
 			
 			// Custom defined properties
 			model.customFields = new LinkedHashMap<String, String>();
@@ -2465,6 +2467,7 @@
 		updateList(config, "postReceiveScript", repository.postReceiveScripts);
 		updateList(config, "mailingList", repository.mailingLists);
 		updateList(config, "indexBranch", repository.indexedBranches);
+		updateList(config, "metricAuthorExclusions", repository.metricAuthorExclusions);
 		
 		// User Defined Properties
 		if (repository.customFields != null) {
diff --git a/src/main/java/com/gitblit/models/Activity.java b/src/main/java/com/gitblit/models/Activity.java
index 59405c7..547c348 100644
--- a/src/main/java/com/gitblit/models/Activity.java
+++ b/src/main/java/com/gitblit/models/Activity.java
@@ -17,6 +17,7 @@
 
 import java.io.Serializable;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
@@ -24,6 +25,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeSet;
 
 import org.eclipse.jgit.revwalk.RevCommit;
 
@@ -43,12 +45,14 @@
 	public final Date startDate;
 
 	public final Date endDate;
-
+	
 	private final Set<RepositoryCommit> commits;
 
 	private final Map<String, Metric> authorMetrics;
 
 	private final Map<String, Metric> repositoryMetrics;
+
+	private final Set<String> authorExclusions;
 
 	/**
 	 * Constructor for one day of activity.
@@ -73,6 +77,18 @@
 		commits = new LinkedHashSet<RepositoryCommit>();
 		authorMetrics = new HashMap<String, Metric>();
 		repositoryMetrics = new HashMap<String, Metric>();
+		authorExclusions = new TreeSet<String>();
+	}
+	
+	/**
+	 * Exclude the specified authors from the metrics.
+	 * 
+	 * @param authors
+	 */
+	public void excludeAuthors(Collection<String> authors) {
+		for (String author : authors) {
+			authorExclusions.add(author.toLowerCase());
+		}
 	}
 
 	/**
@@ -88,16 +104,20 @@
 	public RepositoryCommit addCommit(String repository, String branch, RevCommit commit) {
 		RepositoryCommit commitModel = new RepositoryCommit(repository, branch, commit);
 		if (commits.add(commitModel)) {
+			String author = StringUtils.removeNewlines(commit.getAuthorIdent().getName());
+			String authorName = author.toLowerCase();
+			String authorEmail = StringUtils.removeNewlines(commit.getAuthorIdent().getEmailAddress()).toLowerCase();
 			if (!repositoryMetrics.containsKey(repository)) {
 				repositoryMetrics.put(repository, new Metric(repository));
 			}
 			repositoryMetrics.get(repository).count++;
 
-			String author = StringUtils.removeNewlines(commit.getAuthorIdent().getEmailAddress()).toLowerCase();			
-			if (!authorMetrics.containsKey(author)) {
-				authorMetrics.put(author, new Metric(author));
+			if (!authorExclusions.contains(authorName) && !authorExclusions.contains(authorEmail)) {
+				if (!authorMetrics.containsKey(author)) {
+					authorMetrics.put(author, new Metric(author));
+				}
+				authorMetrics.get(author).count++;
 			}
-			authorMetrics.get(author).count++;
 			return commitModel;
 		}
 		return null;
diff --git a/src/main/java/com/gitblit/models/RepositoryModel.java b/src/main/java/com/gitblit/models/RepositoryModel.java
index 0e39d91..5eb51b4 100644
--- a/src/main/java/com/gitblit/models/RepositoryModel.java
+++ b/src/main/java/com/gitblit/models/RepositoryModel.java
@@ -81,7 +81,8 @@
 	public boolean verifyCommitter;
 	public String gcThreshold;
 	public int gcPeriod;
-	public int maxActivityCommits;
+	public int maxActivityCommits;	
+	public List<String> metricAuthorExclusions;
 	
 	public transient boolean isCollectingGarbage;
 	public Date lastGC;
diff --git a/src/main/java/com/gitblit/utils/ActivityUtils.java b/src/main/java/com/gitblit/utils/ActivityUtils.java
index 015e8d3..edeb01a 100644
--- a/src/main/java/com/gitblit/utils/ActivityUtils.java
+++ b/src/main/java/com/gitblit/utils/ActivityUtils.java
@@ -27,7 +27,9 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.TimeZone;
+import java.util.TreeSet;
 
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
@@ -35,6 +37,7 @@
 import org.eclipse.jgit.revwalk.RevCommit;
 
 import com.gitblit.GitBlit;
+import com.gitblit.Keys;
 import com.gitblit.models.Activity;
 import com.gitblit.models.GravatarProfile;
 import com.gitblit.models.RefModel;
@@ -78,6 +81,15 @@
 		df.setTimeZone(timezone);
 		Calendar cal = Calendar.getInstance();
 		cal.setTimeZone(timezone);
+		
+		// aggregate author exclusions
+		Set<String> authorExclusions = new TreeSet<String>();
+		authorExclusions.addAll(GitBlit.getStrings(Keys.web.metricAuthorExclusions));
+		for (RepositoryModel model : models) {
+			if (!ArrayUtils.isEmpty(model.metricAuthorExclusions)) {
+				authorExclusions.addAll(model.metricAuthorExclusions);
+			}
+		}
 
 		Map<String, Activity> activity = new HashMap<String, Activity>();
 		for (RepositoryModel model : models) {
@@ -124,7 +136,9 @@
 							cal.set(Calendar.MINUTE, 0);
 							cal.set(Calendar.SECOND, 0);
 							cal.set(Calendar.MILLISECOND, 0);
-							activity.put(dateStr, new Activity(cal.getTime()));
+							Activity a = new Activity(cal.getTime());
+							a.excludeAuthors(authorExclusions);
+							activity.put(dateStr, a);
 						}
 						RepositoryCommit commitModel = activity.get(dateStr)
 								.addCommit(model.name, shortName, commit);
diff --git a/src/main/java/com/gitblit/wicket/charting/GooglePieChart.java b/src/main/java/com/gitblit/wicket/charting/GooglePieChart.java
index 945e08b..a9b4667 100644
--- a/src/main/java/com/gitblit/wicket/charting/GooglePieChart.java
+++ b/src/main/java/com/gitblit/wicket/charting/GooglePieChart.java
@@ -16,7 +16,9 @@
 package com.gitblit.wicket.charting;
 
 import java.text.MessageFormat;
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.List;
 
 import com.gitblit.utils.StringUtils;
 
@@ -44,10 +46,27 @@
 		line(sb, MessageFormat.format("{0}.addRows({1,number,0});", dName, values.size()));
 
 		Collections.sort(values);
-
-		StringBuilder colors = new StringBuilder("colors:[");
-		for (int i = 0; i < values.size(); i++) {
+		List<ChartValue> list = new ArrayList<ChartValue>();
+		
+		int maxSlices = 10;
+		int maxCount = Math.min(maxSlices - 1,  values.size());
+		
+		for (int i = 0; i < maxCount; i++) {
 			ChartValue value = values.get(i);
+			list.add(value);
+		}
+		if (values.size() >= maxSlices) {
+			float others = 0;
+			for (int i = maxSlices - 1; i < values.size(); i++) {
+				others += values.get(i).value;	
+			}
+			ChartValue other = new ChartValue("other", others);
+			list.add(other);
+		}
+		
+		StringBuilder colors = new StringBuilder("colors:[");
+		for (int i = 0; i < list.size(); i++) {
+			ChartValue value = list.get(i);
 			colors.append('\'');
 			colors.append(StringUtils.getColor(value.name));
 			colors.append('\'');
diff --git a/src/main/java/com/gitblit/wicket/pages/DashboardPage.java b/src/main/java/com/gitblit/wicket/pages/DashboardPage.java
index 39771ae..66bbf73 100644
--- a/src/main/java/com/gitblit/wicket/pages/DashboardPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/DashboardPage.java
@@ -34,6 +34,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.TimeZone;
+import java.util.TreeSet;
 
 import org.apache.wicket.Component;
 import org.apache.wicket.PageParameters;
@@ -187,7 +188,20 @@
 		
 		// add the nifty charts
 		if (!ArrayUtils.isEmpty(pushes)) {
-			GoogleCharts charts = createCharts(pushes);
+			// aggregate author exclusions
+			Set<String> authorExclusions = new TreeSet<String>();
+			for (String author : GitBlit.getStrings(Keys.web.metricAuthorExclusions)) {
+				authorExclusions.add(author.toLowerCase());
+			}
+			for (RepositoryModel model : feedSources) {
+				if (!ArrayUtils.isEmpty(model.metricAuthorExclusions)) {
+					for (String author : model.metricAuthorExclusions) {
+						authorExclusions.add(author.toLowerCase());
+					}
+				}
+			}
+
+			GoogleCharts charts = createCharts(pushes, authorExclusions);
 			add(new HeaderContributor(charts));
 		}
 		
@@ -359,7 +373,7 @@
 	 * @param recentPushes
 	 * @return
 	 */
-	private GoogleCharts createCharts(List<PushLogEntry> recentPushes) {
+	private GoogleCharts createCharts(List<PushLogEntry> recentPushes, Set<String> authorExclusions) {
 		// activity metrics
 		Map<String, Metric> repositoryMetrics = new HashMap<String, Metric>();
 		Map<String, Metric> authorMetrics = new HashMap<String, Metric>();
@@ -375,11 +389,15 @@
 			repositoryMetrics.get(repository).count += 1;
 			
 			for (RepositoryCommit commit : push.getCommits()) {
-				String author = commit.getAuthorIdent().getName();
-				if (!authorMetrics.containsKey(author)) {
-					authorMetrics.put(author, new Metric(author));
+				String author = StringUtils.removeNewlines(commit.getAuthorIdent().getName());
+				String authorName = author.toLowerCase();
+				String authorEmail = StringUtils.removeNewlines(commit.getAuthorIdent().getEmailAddress()).toLowerCase();
+				if (!authorExclusions.contains(authorName) && !authorExclusions.contains(authorEmail)) {
+					if (!authorMetrics.containsKey(author)) {
+						authorMetrics.put(author, new Metric(author));
+					}
+					authorMetrics.get(author).count += 1;
 				}
-				authorMetrics.get(author).count += 1;
 			}
 		}
 
diff --git a/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.html b/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.html
index f8ecb99..3807027 100644
--- a/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.html
@@ -41,8 +41,9 @@
 				<tr><th><wicket:message key="gb.skipSizeCalculation"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="skipSizeCalculation" tabindex="11" /> &nbsp;<span class="help-inline"><wicket:message key="gb.skipSizeCalculationDescription"></wicket:message></span></label></td></tr>
 				<tr><th><wicket:message key="gb.skipSummaryMetrics"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="skipSummaryMetrics" tabindex="12" /> &nbsp;<span class="help-inline"><wicket:message key="gb.skipSummaryMetricsDescription"></wicket:message></span></label></td></tr>
 				<tr><th><wicket:message key="gb.maxActivityCommits"></wicket:message></th><td class="edit"><select class="span2" wicket:id="maxActivityCommits" tabindex="13" /> &nbsp;<span class="help-inline"><wicket:message key="gb.maxActivityCommitsDescription"></wicket:message></span></td></tr>
+				<tr><th><wicket:message key="gb.metricAuthorExclusions"></wicket:message></th><td class="edit"><input class="span8" type="text" wicket:id="metricAuthorExclusions" size="40" tabindex="14" /></td></tr>
 				<tr><th colspan="2"><hr/></th></tr>
-				<tr><th><wicket:message key="gb.mailingLists"></wicket:message></th><td class="edit"><input class="span8" type="text" wicket:id="mailingLists" size="40" tabindex="14" /></td></tr>
+				<tr><th><wicket:message key="gb.mailingLists"></wicket:message></th><td class="edit"><input class="span8" type="text" wicket:id="mailingLists" size="40" tabindex="15" /></td></tr>
 			</tbody>
 		</table>
 		</div>
@@ -51,15 +52,15 @@
 		<div class="tab-pane" id="permissions">
 			<table class="plain">
 				<tbody class="settings">
-					<tr><th><wicket:message key="gb.owners"></wicket:message></th><td class="edit"><span wicket:id="owners" tabindex="15" /> </td></tr>
+					<tr><th><wicket:message key="gb.owners"></wicket:message></th><td class="edit"><span wicket:id="owners" tabindex="16" /> </td></tr>
 					<tr><th colspan="2"><hr/></th></tr>
-					<tr><th><wicket:message key="gb.accessRestriction"></wicket:message></th><td class="edit"><select class="span4" wicket:id="accessRestriction" tabindex="16" /></td></tr>
+					<tr><th><wicket:message key="gb.accessRestriction"></wicket:message></th><td class="edit"><select class="span4" wicket:id="accessRestriction" tabindex="17" /></td></tr>
 					<tr><th colspan="2"><hr/></th></tr>
 					<tr><th><wicket:message key="gb.authorizationControl"></wicket:message></th><td style="padding:2px;"><span class="authorizationControl" wicket:id="authorizationControl"></span></td></tr>
 					<tr><th colspan="2"><hr/></th></tr>
-					<tr><th><wicket:message key="gb.isFrozen"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="isFrozen" tabindex="17" /> &nbsp;<span class="help-inline"><wicket:message key="gb.isFrozenDescription"></wicket:message></span></label></td></tr>
-					<tr><th><wicket:message key="gb.allowForks"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="allowForks" tabindex="18" /> &nbsp;<span class="help-inline"><wicket:message key="gb.allowForksDescription"></wicket:message></span></label></td></tr>
-					<tr><th><wicket:message key="gb.verifyCommitter"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="verifyCommitter" tabindex="19" /> &nbsp;<span class="help-inline"><wicket:message key="gb.verifyCommitterDescription"></wicket:message></span><br/><span class="help-inline" style="padding-left:10px;"><wicket:message key="gb.verifyCommitterNote"></wicket:message></span></label></td></tr>
+					<tr><th><wicket:message key="gb.isFrozen"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="isFrozen" tabindex="18" /> &nbsp;<span class="help-inline"><wicket:message key="gb.isFrozenDescription"></wicket:message></span></label></td></tr>
+					<tr><th><wicket:message key="gb.allowForks"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="allowForks" tabindex="19" /> &nbsp;<span class="help-inline"><wicket:message key="gb.allowForksDescription"></wicket:message></span></label></td></tr>
+					<tr><th><wicket:message key="gb.verifyCommitter"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="verifyCommitter" tabindex="20" /> &nbsp;<span class="help-inline"><wicket:message key="gb.verifyCommitterDescription"></wicket:message></span><br/><span class="help-inline" style="padding-left:10px;"><wicket:message key="gb.verifyCommitterNote"></wicket:message></span></label></td></tr>
 					<tr><th colspan="2"><hr/></th></tr>
 					<tr><th><wicket:message key="gb.userPermissions"></wicket:message></th><td style="padding:2px;"><span wicket:id="users"></span></td></tr>
 					<tr><th colspan="2"><hr/></th></tr>
@@ -72,7 +73,7 @@
 		<div class="tab-pane" id="federation">
 			<table class="plain">
 				<tbody class="settings">
-					<tr><th><wicket:message key="gb.federationStrategy"></wicket:message></th><td class="edit"><select class="span4" wicket:id="federationStrategy" tabindex="20" /></td></tr>
+					<tr><th><wicket:message key="gb.federationStrategy"></wicket:message></th><td class="edit"><select class="span4" wicket:id="federationStrategy" tabindex="21" /></td></tr>
 					<tr><th><wicket:message key="gb.federationSets"></wicket:message></th><td style="padding:2px;"><span wicket:id="federationSets"></span></td></tr>
 				</tbody>
 			</table>
diff --git a/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.java b/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.java
index 938b05e..4c471a1 100644
--- a/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.java
@@ -76,6 +76,8 @@
 	
 	RepositoryModel repositoryModel;
 
+	private IModel<String> metricAuthorExclusions;
+	
 	private IModel<String> mailingLists;
 
 	public EditRepositoryPage() {
@@ -316,6 +318,23 @@
 						}
 					}
 
+					// set author metric exclusions
+					String ax = metricAuthorExclusions.getObject();
+					if (!StringUtils.isEmpty(ax)) {
+						Set<String> list = new HashSet<String>();
+						for (String exclusion : StringUtils.getStringsFromValue(ax,  " ")) {
+							if (StringUtils.isEmpty(exclusion)) {
+								continue;
+							}
+							if (exclusion.indexOf(' ') > -1) {
+								list.add("\"" + exclusion + "\"");	
+							} else {
+								list.add(exclusion);
+							}
+						}
+						repositoryModel.metricAuthorExclusions = new ArrayList<String>(list);
+					}
+
 					// set mailing lists
 					String ml = mailingLists.getObject();
 					if (!StringUtils.isEmpty(ml)) {
@@ -435,6 +454,10 @@
 		List<Integer> maxActivityCommits  = Arrays.asList(-1, 0, 25, 50, 75, 100, 150, 200, 250, 500 );
 		form.add(new DropDownChoice<Integer>("maxActivityCommits", maxActivityCommits, new MaxActivityCommitsRenderer()));
 
+		metricAuthorExclusions = new Model<String>(ArrayUtils.isEmpty(repositoryModel.metricAuthorExclusions) ? ""
+				: StringUtils.flattenStrings(repositoryModel.metricAuthorExclusions, " "));
+		form.add(new TextField<String>("metricAuthorExclusions", metricAuthorExclusions));
+
 		mailingLists = new Model<String>(ArrayUtils.isEmpty(repositoryModel.mailingLists) ? ""
 				: StringUtils.flattenStrings(repositoryModel.mailingLists, " "));
 		form.add(new TextField<String>("mailingLists", mailingLists));

--
Gitblit v1.9.1