src/com/gitblit/models/Activity.java
New file @@ -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; } } } src/com/gitblit/models/DailyActivity.java
File was deleted src/com/gitblit/models/RepositoryCommit.java
File was deleted src/com/gitblit/utils/ActivityUtils.java
New file @@ -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; } } 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 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); 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); 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, ""); } } 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> 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; } } 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"> 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