From db91a34ccb5135f733c7431fdba49669131e97da Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Sat, 19 Nov 2011 07:33:59 -0500
Subject: [PATCH] Reorganizing the recent activity feature

---
 /dev/null                                           |   86 -------
 src/com/gitblit/wicket/GitBlitWebApp.properties     |    3 
 src/com/gitblit/wicket/WicketUtils.java             |   23 +
 src/com/gitblit/wicket/panels/ActivityPanel.java    |   32 +-
 src/com/gitblit/utils/ActivityUtils.java            |  130 +++++++++++
 src/com/gitblit/wicket/pages/ActivityPage.html      |    2 
 src/com/gitblit/wicket/charting/GooglePieChart.java |   14 +
 src/com/gitblit/models/Activity.java                |  169 +++++++++++++++
 src/com/gitblit/wicket/charting/GoogleCharts.java   |    4 
 src/com/gitblit/wicket/panels/ActivityPanel.html    |    4 
 src/com/gitblit/wicket/pages/ActivityPage.java      |  187 ++++++----------
 11 files changed, 422 insertions(+), 232 deletions(-)

diff --git a/src/com/gitblit/models/Activity.java b/src/com/gitblit/models/Activity.java
new file mode 100644
index 0000000..f24a5ab
--- /dev/null
+++ b/src/com/gitblit/models/Activity.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed 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.
+ */
+package com.gitblit.models;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+import com.gitblit.utils.TimeUtils;
+
+/**
+ * Model class to represent the commit activity across many repositories. This
+ * class is used by the Activity page.
+ * 
+ * @author James Moger
+ */
+public class Activity implements Serializable, Comparable<Activity> {
+
+	private static final long serialVersionUID = 1L;
+
+	public final Date startDate;
+
+	public final Date endDate;
+
+	public final List<RepositoryCommit> commits;
+
+	private final Map<String, Metric> authorMetrics;
+
+	private final Map<String, Metric> repositoryMetrics;
+
+	/**
+	 * Constructor for one day of activity.
+	 * 
+	 * @param date
+	 */
+	public Activity(Date date) {
+		this(date, TimeUtils.ONEDAY - 1);
+	}
+
+	/**
+	 * Constructor for specified duration of activity from start date.
+	 * 
+	 * @param date
+	 *            the start date of the activity
+	 * @param duration
+	 *            the duration of the period in milliseconds
+	 */
+	public Activity(Date date, long duration) {
+		startDate = date;
+		endDate = new Date(date.getTime() + duration);
+		commits = new ArrayList<RepositoryCommit>();
+		authorMetrics = new HashMap<String, Metric>();
+		repositoryMetrics = new HashMap<String, Metric>();
+	}
+
+	public RepositoryCommit addCommit(String repository, String branch, RevCommit commit) {
+		RepositoryCommit commitModel = new RepositoryCommit(repository, branch, commit);
+		commits.add(commitModel);
+
+		if (!repositoryMetrics.containsKey(repository)) {
+			repositoryMetrics.put(repository, new Metric(repository));
+		}
+		repositoryMetrics.get(repository).count++;
+
+		String author = commit.getAuthorIdent().getEmailAddress().toLowerCase();
+		if (!authorMetrics.containsKey(author)) {
+			authorMetrics.put(author, new Metric(author));
+		}
+		authorMetrics.get(author).count++;
+		return commitModel;
+	}
+
+	public Map<String, Metric> getAuthorMetrics() {
+		return authorMetrics;
+	}
+
+	public Map<String, Metric> getRepositoryMetrics() {
+		return repositoryMetrics;
+	}
+
+	@Override
+	public int compareTo(Activity o) {
+		// reverse chronological order
+		return o.startDate.compareTo(startDate);
+	}
+
+	/**
+	 * Model class to represent a RevCommit, it's source repository, and the
+	 * branch. This class is used by the activity page.
+	 * 
+	 * @author James Moger
+	 */
+	public static class RepositoryCommit implements Serializable, Comparable<RepositoryCommit> {
+
+		private static final long serialVersionUID = 1L;
+
+		public final String repository;
+
+		public final String branch;
+
+		private final RevCommit commit;
+
+		private List<RefModel> refs;
+
+		public RepositoryCommit(String repository, String branch, RevCommit commit) {
+			this.repository = repository;
+			this.branch = branch;
+			this.commit = commit;
+		}
+
+		public void setRefs(List<RefModel> refs) {
+			this.refs = refs;
+		}
+
+		public List<RefModel> getRefs() {
+			return refs;
+		}
+
+		public String getName() {
+			return commit.getName();
+		}
+
+		public String getShortName() {
+			return commit.getName().substring(0, 8);
+		}
+
+		public String getShortMessage() {
+			return commit.getShortMessage();
+		}
+
+		public int getParentCount() {
+			return commit.getParentCount();
+		}
+
+		public PersonIdent getAuthorIdent() {
+			return commit.getAuthorIdent();
+		}
+
+		@Override
+		public int compareTo(RepositoryCommit o) {
+			// reverse-chronological order
+			if (commit.getCommitTime() > o.commit.getCommitTime()) {
+				return -1;
+			} else if (commit.getCommitTime() < o.commit.getCommitTime()) {
+				return 1;
+			}
+			return 0;
+		}
+	}
+}
diff --git a/src/com/gitblit/models/DailyActivity.java b/src/com/gitblit/models/DailyActivity.java
deleted file mode 100644
index f2e816c..0000000
--- a/src/com/gitblit/models/DailyActivity.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed 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.
- */
-package com.gitblit.models;
-
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-
-/**
- * Model class to represent the commit activity across many repositories. This
- * class is used by the Activity page.
- * 
- * @author James Moger
- */
-public class DailyActivity implements Serializable, Comparable<DailyActivity> {
-
-	private static final long serialVersionUID = 1L;
-
-	public final Date date;
-
-	public final List<RepositoryCommit> commits;
-
-	public DailyActivity(Date date) {
-		this.date = date;
-		commits = new ArrayList<RepositoryCommit>();
-	}
-
-	@Override
-	public int compareTo(DailyActivity o) {
-		// reverse chronological order
-		return o.date.compareTo(date);
-	}
-}
diff --git a/src/com/gitblit/models/RepositoryCommit.java b/src/com/gitblit/models/RepositoryCommit.java
deleted file mode 100644
index 8f5b2bd..0000000
--- a/src/com/gitblit/models/RepositoryCommit.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed 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.
- */
-package com.gitblit.models;
-
-import java.io.Serializable;
-import java.util.List;
-
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.revwalk.RevCommit;
-
-/**
- * Model class to represent a RevCommit, it's source repository, and the branch.
- * This class is used by the activity page.
- * 
- * @author James Moger
- */
-public class RepositoryCommit implements Serializable, Comparable<RepositoryCommit> {
-
-	private static final long serialVersionUID = 1L;
-
-	public final String repository;
-
-	public final String branch;
-
-	private final RevCommit commit;
-
-	private List<RefModel> refs;
-
-	public RepositoryCommit(String repository, String branch, RevCommit commit) {
-		this.repository = repository;
-		this.branch = branch;
-		this.commit = commit;
-	}
-
-	public void setRefs(List<RefModel> refs) {
-		this.refs = refs;
-	}
-
-	public List<RefModel> getRefs() {
-		return refs;
-	}
-
-	public String getName() {
-		return commit.getName();
-	}
-
-	public String getShortName() {
-		return commit.getName().substring(0, 8);
-	}
-
-	public String getShortMessage() {
-		return commit.getShortMessage();
-	}
-
-	public int getParentCount() {
-		return commit.getParentCount();
-	}
-
-	public PersonIdent getAuthorIdent() {
-		return commit.getAuthorIdent();
-	}
-
-	@Override
-	public int compareTo(RepositoryCommit o) {
-		// reverse-chronological order
-		if (commit.getCommitTime() > o.commit.getCommitTime()) {
-			return -1;
-		} else if (commit.getCommitTime() < o.commit.getCommitTime()) {
-			return 1;
-		}
-		return 0;
-	}
-}
\ No newline at end of file
diff --git a/src/com/gitblit/utils/ActivityUtils.java b/src/com/gitblit/utils/ActivityUtils.java
new file mode 100644
index 0000000..8c8a7ec
--- /dev/null
+++ b/src/com/gitblit/utils/ActivityUtils.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed 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.
+ */
+package com.gitblit.utils;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+import com.gitblit.GitBlit;
+import com.gitblit.models.Activity;
+import com.gitblit.models.Activity.RepositoryCommit;
+import com.gitblit.models.RefModel;
+import com.gitblit.models.RepositoryModel;
+
+/**
+ * Utility class for building activity information from repositories.
+ * 
+ * @author James Moger
+ * 
+ */
+public class ActivityUtils {
+
+	/**
+	 * Gets the recent activity from the repositories for the last daysBack days
+	 * on the specified branch.
+	 * 
+	 * @param models
+	 *            the list of repositories to query
+	 * @param daysBack
+	 *            the number of days back from Now to collect
+	 * @param objectId
+	 *            the branch to retrieve. If this value is null the default
+	 *            branch of the repository is used.
+	 * @return
+	 */
+	public static List<Activity> getRecentActivity(List<RepositoryModel> models, int daysBack,
+			String objectId) {
+
+		// Activity panel shows last daysBack of activity across all
+		// repositories.
+		Date thresholdDate = new Date(System.currentTimeMillis() - daysBack * TimeUtils.ONEDAY);
+
+		// Build a map of DailyActivity from the available repositories for the
+		// specified threshold date.
+		DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
+		Calendar cal = Calendar.getInstance();
+
+		Map<String, Activity> activity = new HashMap<String, Activity>();
+		for (RepositoryModel model : models) {
+			if (model.hasCommits && model.lastChange.after(thresholdDate)) {
+				Repository repository = GitBlit.self().getRepository(model.name);
+				List<RevCommit> commits = JGitUtils.getRevLog(repository, objectId, thresholdDate);
+				Map<ObjectId, List<RefModel>> allRefs = JGitUtils.getAllRefs(repository);
+				repository.close();
+
+				// determine commit branch
+				String branch = objectId;
+				if (StringUtils.isEmpty(branch)) {
+					List<RefModel> headRefs = allRefs.get(commits.get(0).getId());
+					List<String> localBranches = new ArrayList<String>();
+					for (RefModel ref : headRefs) {
+						if (ref.getName().startsWith(Constants.R_HEADS)) {
+							localBranches.add(ref.getName().substring(Constants.R_HEADS.length()));
+						}
+					}
+					// determine branch
+					if (localBranches.size() == 1) {
+						// only one branch, choose it
+						branch = localBranches.get(0);
+					} else if (localBranches.size() > 1) {
+						if (localBranches.contains("master")) {
+							// choose master
+							branch = "master";
+						} else {
+							// choose first branch
+							branch = localBranches.get(0);
+						}
+					}
+				}
+
+				for (RevCommit commit : commits) {
+					Date date = JGitUtils.getCommitDate(commit);
+					String dateStr = df.format(date);
+					if (!activity.containsKey(dateStr)) {
+						// Normalize the date to midnight
+						cal.setTime(date);
+						cal.set(Calendar.HOUR_OF_DAY, 0);
+						cal.set(Calendar.MINUTE, 0);
+						cal.set(Calendar.SECOND, 0);
+						cal.set(Calendar.MILLISECOND, 0);
+						activity.put(dateStr, new Activity(cal.getTime()));
+					}
+					RepositoryCommit commitModel = activity.get(dateStr).addCommit(model.name,
+							branch, commit);
+					commitModel.setRefs(allRefs.get(commit.getId()));
+				}
+			}
+		}
+
+		List<Activity> recentActivity = new ArrayList<Activity>(activity.values());
+		for (Activity daily : recentActivity) {
+			Collections.sort(daily.commits);
+		}
+		return recentActivity;
+	}
+}
diff --git a/src/com/gitblit/wicket/GitBlitWebApp.properties b/src/com/gitblit/wicket/GitBlitWebApp.properties
index f157a7e..7ffdb59 100644
--- a/src/com/gitblit/wicket/GitBlitWebApp.properties
+++ b/src/com/gitblit/wicket/GitBlitWebApp.properties
@@ -182,7 +182,8 @@
 gb.branch = branch
 gb.maxHits = max hits
 gb.recentActivity = recent activity
-gb.recentActivitySubheader = last {0} days / {1} commits by {2} authors
+gb.recentActivityStats = last {0} days / {1} commits by {2} authors
+gb.recentActivityNone = last {0} days / none
 gb.dailyActivity = daily activity
 gb.activeRepositories = active repositories
 gb.activeAuthors = active authors
diff --git a/src/com/gitblit/wicket/WicketUtils.java b/src/com/gitblit/wicket/WicketUtils.java
index 6157a44..0a20718 100644
--- a/src/com/gitblit/wicket/WicketUtils.java
+++ b/src/com/gitblit/wicket/WicketUtils.java
@@ -16,6 +16,7 @@
 package com.gitblit.wicket;
 
 import java.text.DateFormat;
+import java.text.MessageFormat;
 import java.text.SimpleDateFormat;
 import java.util.Collection;
 import java.util.Date;
@@ -57,6 +58,12 @@
 
 	public static void setCssStyle(Component container, String value) {
 		container.add(new SimpleAttributeModifier("style", value));
+	}
+
+	public static void setCssBackground(Component container, String value) {
+		String background = MessageFormat.format("background-color:{0};",
+				StringUtils.getColor(value));
+		container.add(new SimpleAttributeModifier("style", background));
 	}
 
 	public static void setHtmlTooltip(Component container, String value) {
@@ -129,7 +136,7 @@
 			filename = "bullet_white.png";
 			break;
 		case PENDING:
-		case NOCHANGE:			
+		case NOCHANGE:
 		default:
 			filename = "bullet_black.png";
 		}
@@ -239,11 +246,11 @@
 			}
 		});
 	}
-	
+
 	public static PageParameters newTokenParameter(String token) {
 		return new PageParameters("t=" + token);
 	}
-	
+
 	public static PageParameters newRegistrationParameter(String url, String name) {
 		return new PageParameters("u=" + url + ",n=" + name);
 	}
@@ -297,8 +304,8 @@
 		if (StringUtils.isEmpty(objectId)) {
 			return new PageParameters("r=" + repositoryName + ",f=" + path + ",pg=" + pageNumber);
 		}
-		return new PageParameters("r=" + repositoryName + ",h=" + objectId + ",f=" + path
-				+ ",pg=" + pageNumber);
+		return new PageParameters("r=" + repositoryName + ",h=" + objectId + ",f=" + path + ",pg="
+				+ pageNumber);
 	}
 
 	public static PageParameters newBlobDiffParameter(String repositoryName, String baseCommitId,
@@ -373,7 +380,7 @@
 	public static String getToken(PageParameters params) {
 		return params.getString("t", "");
 	}
-	
+
 	public static String getUrlParameter(PageParameters params) {
 		return params.getString("u", "");
 	}
@@ -411,7 +418,7 @@
 		}
 		return label;
 	}
-	
+
 	public static Label createTimeLabel(String wicketId, Date date, TimeZone timeZone) {
 		String format = GitBlit.getString(Keys.web.timeFormat, "HH:mm");
 		DateFormat df = new SimpleDateFormat(format);
@@ -432,7 +439,7 @@
 		}
 		return label;
 	}
-	
+
 	public static Label createDatestampLabel(String wicketId, Date date, TimeZone timeZone) {
 		String format = GitBlit.getString(Keys.web.datestampLongFormat, "EEEE, MMMM d, yyyy");
 		DateFormat df = new SimpleDateFormat(format);
diff --git a/src/com/gitblit/wicket/charting/GoogleCharts.java b/src/com/gitblit/wicket/charting/GoogleCharts.java
index 9e1572e..77c522b 100644
--- a/src/com/gitblit/wicket/charting/GoogleCharts.java
+++ b/src/com/gitblit/wicket/charting/GoogleCharts.java
@@ -1,5 +1,5 @@
 /*
- Copyright 2011 comSysto GmbH
+ Copyright 2011 gitblit.com.
 
  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
@@ -34,7 +34,7 @@
 
 	private static final long serialVersionUID = 1L;
 
-	private List<GoogleChart> charts = new ArrayList<GoogleChart>();
+	public final List<GoogleChart> charts = new ArrayList<GoogleChart>();
 
 	public void addChart(GoogleChart chart) {
 		charts.add(chart);
diff --git a/src/com/gitblit/wicket/charting/GooglePieChart.java b/src/com/gitblit/wicket/charting/GooglePieChart.java
index 8367c42..119a824 100644
--- a/src/com/gitblit/wicket/charting/GooglePieChart.java
+++ b/src/com/gitblit/wicket/charting/GooglePieChart.java
@@ -18,6 +18,8 @@
 import java.text.MessageFormat;
 import java.util.Collections;
 
+import com.gitblit.utils.StringUtils;
+
 /**
  * Builds an interactive pie chart using the Visualization API.
  * 
@@ -43,13 +45,21 @@
 
 		Collections.sort(values);
 
+		StringBuilder colors = new StringBuilder("colors:[");
 		for (int i = 0; i < values.size(); i++) {
 			ChartValue value = values.get(i);
+			colors.append('\'');
+			colors.append(StringUtils.getColor(value.name));
+			colors.append('\'');
+			if (i < values.size() - 1) {
+				colors.append(',');
+			}
 			line(sb, MessageFormat.format("{0}.setValue({1,number,0}, 0, ''{2}'');", dName, i,
 					value.name));
 			line(sb, MessageFormat.format("{0}.setValue({1,number,0}, 1, {2,number,0.0});", dName,
 					i, value.value));
 		}
+		colors.append(']');
 
 		// instantiate chart
 		String cName = "chart_" + dataName;
@@ -58,8 +68,8 @@
 				cName, tagId));
 		line(sb,
 				MessageFormat
-						.format("{0}.draw({1}, '{'width: {2,number,0}, height: {3,number,0}, chartArea:'{'left:20,top:20'}', title: ''{4}'' '}');",
-								cName, dName, width, height, title));
+						.format("{0}.draw({1}, '{'width: {2,number,0}, height: {3,number,0}, chartArea:'{'left:20,top:20'}', title: ''{4}'', {5} '}');",
+								cName, dName, width, height, title, colors.toString()));
 		line(sb, "");
 	}
 }
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/ActivityPage.html b/src/com/gitblit/wicket/pages/ActivityPage.html
index c86028f..dff5db2 100644
--- a/src/com/gitblit/wicket/pages/ActivityPage.html
+++ b/src/com/gitblit/wicket/pages/ActivityPage.html
@@ -8,7 +8,7 @@
 	<div class="page-header">
 		<h2><wicket:message key="gb.recentActivity"></wicket:message><small> / <span wicket:id="subheader">[days back]</span></small></h2>
 	</div>
-	<div style="text-align: center;">
+	<div style="height: 155px;text-align: center;">
 		<span id="chartDaily"></span>		
 		<span id="chartRepositories"></span>
 		<span id="chartAuthors"></span>
diff --git a/src/com/gitblit/wicket/pages/ActivityPage.java b/src/com/gitblit/wicket/pages/ActivityPage.java
index 53c7f41..011eef1 100644
--- a/src/com/gitblit/wicket/pages/ActivityPage.java
+++ b/src/com/gitblit/wicket/pages/ActivityPage.java
@@ -15,35 +15,27 @@
  */
 package com.gitblit.wicket.pages;
 
-import java.text.DateFormat;
 import java.text.MessageFormat;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
-import java.util.Calendar;
 import java.util.Collections;
-import java.util.Date;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import org.apache.wicket.PageParameters;
 import org.apache.wicket.behavior.HeaderContributor;
 import org.apache.wicket.markup.html.basic.Label;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
 
 import com.gitblit.GitBlit;
-import com.gitblit.models.DailyActivity;
+import com.gitblit.models.Activity;
 import com.gitblit.models.Metric;
-import com.gitblit.models.RefModel;
-import com.gitblit.models.RepositoryCommit;
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.models.UserModel;
-import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.ActivityUtils;
 import com.gitblit.utils.StringUtils;
-import com.gitblit.utils.TimeUtils;
 import com.gitblit.wicket.GitBlitWebSession;
 import com.gitblit.wicket.WicketUtils;
 import com.gitblit.wicket.charting.GoogleChart;
@@ -64,16 +56,50 @@
 	public ActivityPage(PageParameters params) {
 		super();
 		setupPage("", "");
-		final UserModel user = GitBlitWebSession.get().getUser();
 
 		// parameters
 		int daysBack = WicketUtils.getDaysBack(params);
 		if (daysBack < 1) {
 			daysBack = 14;
-		}		
+		}
+		String objectId = WicketUtils.getObject(params);
+
+		// determine repositories to view and retrieve the activity
+		List<RepositoryModel> models = getRepositories(params);
+		List<Activity> recentActivity = ActivityUtils.getRecentActivity(models, daysBack, objectId);
+
+		if (recentActivity.size() == 0) {
+			// no activity, skip graphs and activity panel
+			add(new Label("subheader", MessageFormat.format(getString("gb.recentActivityNone"),
+					daysBack)));
+			add(new Label("activityPanel"));
+		} else {
+			// calculate total commits and total authors
+			int totalCommits = 0;
+			Set<String> uniqueAuthors = new HashSet<String>();
+			for (Activity activity : recentActivity) {
+				totalCommits += activity.commits.size();
+				uniqueAuthors.addAll(activity.getAuthorMetrics().keySet());
+			}
+			int totalAuthors = uniqueAuthors.size();
+
+			// add the subheader with stat numbers
+			add(new Label("subheader", MessageFormat.format(getString("gb.recentActivityStats"),
+					daysBack, totalCommits, totalAuthors)));
+
+			// create the activity charts
+			GoogleCharts charts = createCharts(recentActivity);
+			add(new HeaderContributor(charts));
+
+			// add activity panel
+			add(new ActivityPanel("activityPanel", recentActivity));
+		}
+	}
+
+	private List<RepositoryModel> getRepositories(PageParameters params) {
+		final UserModel user = GitBlitWebSession.get().getUser();
 		String set = WicketUtils.getSet(params);
 		String repositoryName = WicketUtils.getRepositoryName(params);
-		String objectId = WicketUtils.getObject(params);
 
 		List<RepositoryModel> models = null;
 		if (!StringUtils.isEmpty(repositoryName)) {
@@ -103,110 +129,41 @@
 			}
 			models = setModels;
 		}
+		return models;
+	}
 
-		// Activity panel shows last daysBack of activity across all
-		// repositories.
-		Date thresholdDate = new Date(System.currentTimeMillis() - daysBack * TimeUtils.ONEDAY);
-
-		// Build a map of DailyActivity from the available repositories for the
-		// specified threshold date.
-		DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
-		Calendar cal = Calendar.getInstance();
-
-		Map<String, DailyActivity> activity = new HashMap<String, DailyActivity>();
-		for (RepositoryModel model : models) {
-			if (model.hasCommits && model.lastChange.after(thresholdDate)) {
-				Repository repository = GitBlit.self().getRepository(model.name);
-				List<RevCommit> commits = JGitUtils.getRevLog(repository, objectId, thresholdDate);
-				Map<ObjectId, List<RefModel>> allRefs = JGitUtils.getAllRefs(repository);
-				repository.close();
-
-				// determine commit branch
-				String branch = objectId;
-				if (StringUtils.isEmpty(branch)) {
-					List<RefModel> headRefs = allRefs.get(commits.get(0).getId());
-					List<String> localBranches = new ArrayList<String>();
-					for (RefModel ref : headRefs) {
-						if (ref.getName().startsWith(Constants.R_HEADS)) {
-							localBranches.add(ref.getName().substring(Constants.R_HEADS.length()));
-						}
-					}
-					// determine branch
-					if (localBranches.size() == 1) {
-						// only one branch, choose it
-						branch = localBranches.get(0);
-					} else if (localBranches.size() > 1) {
-						if (localBranches.contains("master")) {
-							// choose master
-							branch = "master";
-						} else {
-							// choose first branch
-							branch = localBranches.get(0);
-						}
-					}
-				}
-
-				for (RevCommit commit : commits) {
-					Date date = JGitUtils.getCommitDate(commit);
-					String dateStr = df.format(date);
-					if (!activity.containsKey(dateStr)) {
-						// Normalize the date to midnight
-						cal.setTime(date);
-						cal.set(Calendar.HOUR_OF_DAY, 0);
-						cal.set(Calendar.MINUTE, 0);
-						cal.set(Calendar.SECOND, 0);
-						cal.set(Calendar.MILLISECOND, 0);
-						activity.put(dateStr, new DailyActivity(cal.getTime()));
-					}
-					RepositoryCommit commitModel = new RepositoryCommit(model.name, branch, commit);
-					commitModel.setRefs(allRefs.get(commit.getId()));
-					activity.get(dateStr).commits.add(commitModel);
-				}
-			}
-		}
-
+	/**
+	 * Creates the daily activity line chart, the active repositories pie chart,
+	 * and the active authors pie chart
+	 * 
+	 * @param recentActivity
+	 * @return
+	 */
+	private GoogleCharts createCharts(List<Activity> recentActivity) {
 		// activity metrics
-		Map<String, Metric> dayMetrics = new HashMap<String, Metric>();
 		Map<String, Metric> repositoryMetrics = new HashMap<String, Metric>();
 		Map<String, Metric> authorMetrics = new HashMap<String, Metric>();
 
-		// prepare day metrics
-		cal.setTimeInMillis(System.currentTimeMillis());
-		for (int i = 0; i < daysBack; i++) {
-			cal.add(Calendar.DATE, -1);
-			String key = df.format(cal.getTime());
-			dayMetrics.put(key, new Metric(key));
-		}
+		// aggregate repository and author metrics
+		for (Activity activity : recentActivity) {
 
-		// calculate activity metrics
-		for (Map.Entry<String, DailyActivity> entry : activity.entrySet()) {
-			// day metrics
-			Metric day = dayMetrics.get(entry.getKey());
-			day.count = entry.getValue().commits.size();
-
-			for (RepositoryCommit commit : entry.getValue().commits) {
-				// repository metrics
-				String repository = commit.repository;
-				if (!repositoryMetrics.containsKey(repository)) {
-					repositoryMetrics.put(repository, new Metric(repository));
-				}
-				repositoryMetrics.get(repository).count++;
-
-				// author metrics
-				String author = commit.getAuthorIdent().getEmailAddress().toLowerCase();
+			// aggregate author metrics
+			for (Map.Entry<String, Metric> entry : activity.getAuthorMetrics().entrySet()) {
+				String author = entry.getKey();
 				if (!authorMetrics.containsKey(author)) {
 					authorMetrics.put(author, new Metric(author));
 				}
-				authorMetrics.get(author).count++;
+				authorMetrics.get(author).count += entry.getValue().count;
 			}
-		}
 
-		// sort the activity groups and their commit contents
-		int totalCommits = 0;
-		List<DailyActivity> recentActivity = new ArrayList<DailyActivity>(activity.values());
-		for (DailyActivity daily : recentActivity) {
-			Collections.sort(daily.commits);
-			totalCommits += daily.commits.size();
+			// aggregate repository metrics
+			for (Map.Entry<String, Metric> entry : activity.getRepositoryMetrics().entrySet()) {
+				String repository = entry.getKey();
+				if (!repositoryMetrics.containsKey(repository)) {
+					repositoryMetrics.put(repository, new Metric(repository));
+				}
+				repositoryMetrics.get(repository).count += entry.getValue().count;
+			}
 		}
 
 		// build google charts
@@ -221,9 +178,9 @@
 		// daily line chart
 		GoogleChart chart = new GoogleLineChart("chartDaily", getString("gb.dailyActivity"), "day",
 				getString("gb.commits"));
-		df = new SimpleDateFormat("MMM dd");
-		for (DailyActivity metric : recentActivity) {
-			chart.addValue(df.format(metric.date), metric.commits.size());
+		SimpleDateFormat df = new SimpleDateFormat("MMM dd");
+		for (Activity metric : recentActivity) {
+			chart.addValue(df.format(metric.startDate), metric.commits.size());
 		}
 		chart.setWidth(w);
 		chart.setHeight(h);
@@ -249,12 +206,6 @@
 		chart.setHeight(h);
 		charts.addChart(chart);
 
-		add(new HeaderContributor(charts));
-
-		add(new Label("subheader", MessageFormat.format(getString("gb.recentActivitySubheader"),
-				daysBack, totalCommits, authorMetrics.size())));
-
-		// add activity panel
-		add(new ActivityPanel("activityPanel", recentActivity));
+		return charts;
 	}
 }
diff --git a/src/com/gitblit/wicket/panels/ActivityPanel.html b/src/com/gitblit/wicket/panels/ActivityPanel.html
index 703dc00..668e7c9 100644
--- a/src/com/gitblit/wicket/panels/ActivityPanel.html
+++ b/src/com/gitblit/wicket/panels/ActivityPanel.html
@@ -16,6 +16,9 @@
 	
 	<wicket:fragment wicket:id="commitFragment">
 		<td class="date" style="width:40px; vertical-align: middle;" ><span wicket:id="time">[time of day]</span></td>
+		<td style="width:10em;text-align:left;vertical-align: middle;">
+			<span wicket:id="repository" class="repositorySwatch">[repository link]</span>
+		</td>
 		<td style="width:30px;vertical-align: middle;"><img wicket:id="avatar" style="vertical-align: middle;"></img></td>
 		<td class="author" style="vertical-align: middle;">
 			<img wicket:id="commitIcon" style="vertical-align: middle;"></img>
@@ -24,7 +27,6 @@
 		</td>
 		<td style="text-align:right;vertical-align: middle;">
 			<div wicket:id="commitRefs">[commit refs]</div>
-			<span wicket:id="repository">[repository link]</span>
 		</td>
 		<td class="rightAlign" style="width:7em;vertical-align: middle;">
         	<span class="link">
diff --git a/src/com/gitblit/wicket/panels/ActivityPanel.java b/src/com/gitblit/wicket/panels/ActivityPanel.java
index 128ef2b..e9ec074 100644
--- a/src/com/gitblit/wicket/panels/ActivityPanel.java
+++ b/src/com/gitblit/wicket/panels/ActivityPanel.java
@@ -25,8 +25,9 @@
 import org.apache.wicket.markup.repeater.data.ListDataProvider;
 
 import com.gitblit.Constants;
-import com.gitblit.models.DailyActivity;
-import com.gitblit.models.RepositoryCommit;
+import com.gitblit.models.Activity;
+import com.gitblit.models.Activity.RepositoryCommit;
+import com.gitblit.utils.StringUtils;
 import com.gitblit.wicket.GitBlitWebSession;
 import com.gitblit.wicket.GravatarImage;
 import com.gitblit.wicket.WicketUtils;
@@ -47,18 +48,18 @@
 
 	private static final long serialVersionUID = 1L;
 
-	public ActivityPanel(String wicketId, List<DailyActivity> recentActivity) {
+	public ActivityPanel(String wicketId, List<Activity> recentActivity) {
 		super(wicketId);
 
 		Collections.sort(recentActivity);
 
-		DataView<DailyActivity> activityView = new DataView<DailyActivity>("activity",
-				new ListDataProvider<DailyActivity>(recentActivity)) {
+		DataView<Activity> activityView = new DataView<Activity>("activity",
+				new ListDataProvider<Activity>(recentActivity)) {
 			private static final long serialVersionUID = 1L;
 
-			public void populateItem(final Item<DailyActivity> item) {
-				final DailyActivity entry = item.getModelObject();
-				item.add(WicketUtils.createDatestampLabel("title", entry.date, GitBlitWebSession
+			public void populateItem(final Item<Activity> item) {
+				final Activity entry = item.getModelObject();
+				item.add(WicketUtils.createDatestampLabel("title", entry.startDate, GitBlitWebSession
 						.get().getTimezone()));
 
 				// display the commits in chronological order
@@ -93,10 +94,11 @@
 						setPersonSearchTooltip(authorLink, author, Constants.SearchType.AUTHOR);
 						fragment.add(authorLink);
 
-						// repository summary page link
-						LinkPanel repositoryLink = new LinkPanel("repository", "list",
+						// repository
+						LinkPanel repositoryLink = new LinkPanel("repository", null,
 								commit.repository, SummaryPage.class,
 								WicketUtils.newRepositoryParameter(commit.repository));
+						WicketUtils.setCssBackground(repositoryLink, commit.repository);
 						fragment.add(repositoryLink);
 
 						// repository branch
@@ -113,9 +115,13 @@
 
 						// message/commit link
 						String shortMessage = commit.getShortMessage();
-						LinkPanel shortlog = new LinkPanel("message", "list subject", shortMessage,
-								CommitPage.class, WicketUtils.newObjectParameter(commit.repository,
-										commit.getName()));
+						String trimmedMessage = StringUtils.trimShortLog(shortMessage);
+						LinkPanel shortlog = new LinkPanel("message", "list subject",
+								trimmedMessage, CommitPage.class, WicketUtils.newObjectParameter(
+										commit.repository, commit.getName()));
+						if (!shortMessage.equals(trimmedMessage)) {
+							WicketUtils.setHtmlTooltip(shortlog, shortMessage);
+						}
 						fragment.add(shortlog);
 
 						// refs

--
Gitblit v1.9.1