James Moger
2011-11-16 6e6f9fe4d0c42a40ba7981606fb526391be23f6d
Working draft of the aggregate activity page
6 files added
6 files modified
685 ■■■■■ changed files
distrib/gitblit.properties 17 ●●●●● patch | view | raw | blame | history
docs/04_releases.mkd 5 ●●●●● patch | view | raw | blame | history
src/com/gitblit/models/DailyActivity.java 47 ●●●●● patch | view | raw | blame | history
src/com/gitblit/models/RepositoryCommit.java 86 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/GitBlitWebApp.java 3 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/GitBlitWebApp.properties 8 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/WicketUtils.java 59 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/ActivityPage.html 19 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/ActivityPage.java 260 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/RootPage.java 1 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/ActivityPanel.html 38 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/ActivityPanel.java 142 ●●●●● patch | view | raw | blame | history
distrib/gitblit.properties
@@ -161,11 +161,22 @@
# RESTART REQUIRED
web.useClientTimezone = false
# Time format
# <http://download.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html>
#
# SINCE 0.8.0
web.timeFormat = HH:mm
# Short date format
# <http://download.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html>
#
# SINCE 0.5.0
web.datestampShortFormat = yyyy-MM-dd
# Long date format
#
# SINCE 0.8.0
web.datestampLongFormat = EEEE, MMMM d, yyyy
# Long timestamp format
# <http://download.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html>
@@ -239,6 +250,12 @@
# SINCE 0.5.0 
web.generateActivityGraph = true
# The number of days to show on the activity page.
# Value must exceed 0 else default of 14 is used
#
# SINCE 0.8.0
web.activityDuration = 14
# The number of commits to display on the summary page
# Value must exceed 0 else default of 20 is used
#
docs/04_releases.mkd
@@ -5,6 +5,11 @@
- added: optional Gravatar integration  
    **New:** *web.allowGravatar = true*   
- added: multi-repository activity page.  this is a timeline of commit activity over the last N days for one or more repositories.
   **New:** *web.activityDuration = 14*
   **New:** *web.timeFormat = HH:mm*
   **New:** *web.datestampLongFormat = EEEE, MMMM d, yyyy*
### Older Releases
src/com/gitblit/models/DailyActivity.java
New file
@@ -0,0 +1,47 @@
/*
 * 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);
    }
}
src/com/gitblit/models/RepositoryCommit.java
New file
@@ -0,0 +1,86 @@
/*
 * 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;
    }
}
src/com/gitblit/wicket/GitBlitWebApp.java
@@ -25,6 +25,7 @@
import com.gitblit.GitBlit;
import com.gitblit.Keys;
import com.gitblit.wicket.pages.ActivityPage;
import com.gitblit.wicket.pages.BlamePage;
import com.gitblit.wicket.pages.BlobDiffPage;
import com.gitblit.wicket.pages.BlobPage;
@@ -103,6 +104,8 @@
        // federation urls
        mount("/proposal", ReviewProposalPage.class, "t");
        mount("/registration", FederationRegistrationPage.class, "u", "n");
        mount("/activity", ActivityPage.class, "r", "h");
    }
    private void mount(String location, Class<? extends WebPage> clazz, String... parameters) {
src/com/gitblit/wicket/GitBlitWebApp.properties
@@ -180,4 +180,10 @@
gb.activity = activity
gb.subscribe = subscribe
gb.branch = branch
gb.maxHits = max hits
gb.maxHits = max hits
gb.recentActivity = recent activity
gb.recentActivitySubheader = last {0} days / {1} commits by {2} authors
gb.dailyActivity = daily activity
gb.activeRepositories = active repositories
gb.activeAuthors = active authors
gb.commits = commits
src/com/gitblit/wicket/WicketUtils.java
@@ -358,6 +358,14 @@
        return params.getInt("pg", 1);
    }
    public static String getSet(PageParameters params) {
        return params.getString("set", "");
    }
    public static int getDaysBack(PageParameters params) {
        return params.getInt("db", 14);
    }
    public static String getUsername(PageParameters params) {
        return params.getString("user", "");
    }
@@ -403,6 +411,57 @@
        }
        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);
        if (timeZone != null) {
            df.setTimeZone(timeZone);
        }
        String timeString;
        if (date.getTime() == 0) {
            timeString = "--";
        } else {
            timeString = df.format(date);
        }
        String title = TimeUtils.timeAgo(date);
        Label label = new Label(wicketId, timeString);
        WicketUtils.setCssClass(label, TimeUtils.timeAgoCss(date));
        if (!StringUtils.isEmpty(title)) {
            WicketUtils.setHtmlTooltip(label, title);
        }
        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);
        if (timeZone != null) {
            df.setTimeZone(timeZone);
        }
        String dateString;
        if (date.getTime() == 0) {
            dateString = "--";
        } else {
            dateString = df.format(date);
        }
        String title = null;
        if (date.getTime() <= System.currentTimeMillis()) {
            // past
            title = TimeUtils.timeAgo(date);
        }
        if ((System.currentTimeMillis() - date.getTime()) < 10 * 24 * 60 * 60 * 1000L) {
            String tmp = dateString;
            dateString = title;
            title = tmp;
        }
        Label label = new Label(wicketId, dateString);
        WicketUtils.setCssClass(label, TimeUtils.timeAgoCss(date));
        if (!StringUtils.isEmpty(title)) {
            WicketUtils.setHtmlTooltip(label, title);
        }
        return label;
    }
    public static Label createTimestampLabel(String wicketId, Date date, TimeZone timeZone) {
        String format = GitBlit.getString(Keys.web.datetimestampLongFormat,
src/com/gitblit/wicket/pages/ActivityPage.html
New file
@@ -0,0 +1,19 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"
      xml:lang="en"
      lang="en">
<body>
<wicket:extend>
    <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;">
        <span id="chartDaily"></span>
        <span id="chartRepositories"></span>
        <span id="chartAuthors"></span>
    </div>
    <div wicket:id="activityPanel" style="padding-top:5px;" >[activity panel]</div>
</wicket:extend>
</body>
</html>
src/com/gitblit/wicket/pages/ActivityPage.java
New file
@@ -0,0 +1,260 @@
/*
 * 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.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.List;
import java.util.Map;
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.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.StringUtils;
import com.gitblit.utils.TimeUtils;
import com.gitblit.wicket.GitBlitWebSession;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.charting.GoogleChart;
import com.gitblit.wicket.charting.GoogleCharts;
import com.gitblit.wicket.charting.GoogleLineChart;
import com.gitblit.wicket.charting.GooglePieChart;
import com.gitblit.wicket.panels.ActivityPanel;
/**
 * Activity Page shows a list of recent commits across all visible Gitblit
 * repositories.
 *
 * @author James Moger
 *
 */
public class ActivityPage extends RootPage {
    public ActivityPage(PageParameters params) {
        super();
        setupPage("", "");
        final UserModel user = GitBlitWebSession.get().getUser();
        // parameters
        int daysBack = WicketUtils.getDaysBack(params);
        if (daysBack < 1) {
            daysBack = 14;
        }
        String set = WicketUtils.getSet(params);
        String repositoryName = WicketUtils.getRepositoryName(params);
        String objectId = WicketUtils.getObject(params);
        List<RepositoryModel> models = null;
        if (!StringUtils.isEmpty(repositoryName)) {
            // named repository
            models = new ArrayList<RepositoryModel>();
            RepositoryModel model = GitBlit.self().getRepositoryModel(repositoryName);
            if (user.canAccessRepository(model)) {
                models.add(model);
            }
        }
        // get all user accessible repositories
        if (models == null) {
            models = GitBlit.self().getRepositoryModels(user);
        }
        // filter the repositories by the specified set
        if (!StringUtils.isEmpty(set)) {
            List<String> sets = StringUtils.getStringsFromValue(set, ",");
            List<RepositoryModel> setModels = new ArrayList<RepositoryModel>();
            for (RepositoryModel model : models) {
                for (String curr : sets) {
                    if (model.federationSets.contains(curr)) {
                        setModels.add(model);
                    }
                }
            }
            models = setModels;
        }
        // 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);
                }
            }
        }
        // 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));
        }
        // 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();
                if (!authorMetrics.containsKey(author)) {
                    authorMetrics.put(author, new Metric(author));
                }
                authorMetrics.get(author).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();
        }
        // build google charts
        int w = 310;
        int h = 150;
        GoogleCharts charts = new GoogleCharts();
        // sort in reverse-chronological order and then reverse that
        Collections.sort(recentActivity);
        Collections.reverse(recentActivity);
        // 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());
        }
        chart.setWidth(w);
        chart.setHeight(h);
        charts.addChart(chart);
        // active repositories pie chart
        chart = new GooglePieChart("chartRepositories", getString("gb.activeRepositories"),
                getString("gb.repository"), getString("gb.commits"));
        for (Metric metric : repositoryMetrics.values()) {
            chart.addValue(metric.name, metric.count);
        }
        chart.setWidth(w);
        chart.setHeight(h);
        charts.addChart(chart);
        // active authors pie chart
        chart = new GooglePieChart("chartAuthors", getString("gb.activeAuthors"),
                getString("gb.author"), getString("gb.commits"));
        for (Metric metric : authorMetrics.values()) {
            chart.addValue(metric.name, metric.count);
        }
        chart.setWidth(w);
        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));
    }
}
src/com/gitblit/wicket/pages/RootPage.java
@@ -82,6 +82,7 @@
        // navigation links
        List<PageRegistration> pages = new ArrayList<PageRegistration>();
        pages.add(new PageRegistration("gb.repositories", RepositoriesPage.class));
        pages.add(new PageRegistration("gb.activity", ActivityPage.class));
        if (showAdmin) {
            pages.add(new PageRegistration("gb.users", UsersPage.class));
        }
src/com/gitblit/wicket/panels/ActivityPanel.html
New file
@@ -0,0 +1,38 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"
      xml:lang="en"
      lang="en">
<body>
<wicket:panel>
    <div wicket:id="activity">
        <div class="header"><span wicket:id="title">[title]</span></div>
        <table wicket:id="commits">
            <tr wicket:id="commit"></tr>
        </table>
    </div>
    <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: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>
            <span wicket:id="message">[shortlog commit link]</span><br/>
            <span wicket:id="author" style="padding-left:20px;">[author link]</span> committed <span wicket:id="commitid">[commit id]</span> to <span wicket:id="branch"></span>
        </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">
                <a wicket:id="view"><wicket:message key="gb.view"></wicket:message></a> | <a wicket:id="diff"><wicket:message key="gb.diff"></wicket:message></a> | <a wicket:id="tree"><wicket:message key="gb.tree"></wicket:message></a>
            </span>
        </td>
    </wicket:fragment>
</wicket:panel>
</body>
</html>
src/com/gitblit/wicket/panels/ActivityPanel.java
New file
@@ -0,0 +1,142 @@
/*
 * 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.wicket.panels;
import java.util.Collections;
import java.util.List;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import org.apache.wicket.markup.html.panel.Fragment;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.data.DataView;
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.wicket.GitBlitWebSession;
import com.gitblit.wicket.GravatarImage;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.pages.CommitDiffPage;
import com.gitblit.wicket.pages.CommitPage;
import com.gitblit.wicket.pages.LogPage;
import com.gitblit.wicket.pages.SearchPage;
import com.gitblit.wicket.pages.SummaryPage;
import com.gitblit.wicket.pages.TreePage;
/**
 * Renders activity in day-blocks in reverse-chronological order.
 *
 * @author James Moger
 *
 */
public class ActivityPanel extends BasePanel {
    private static final long serialVersionUID = 1L;
    public ActivityPanel(String wicketId, List<DailyActivity> recentActivity) {
        super(wicketId);
        Collections.sort(recentActivity);
        DataView<DailyActivity> activityView = new DataView<DailyActivity>("activity",
                new ListDataProvider<DailyActivity>(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
                        .get().getTimezone()));
                // display the commits in chronological order
                DataView<RepositoryCommit> commits = new DataView<RepositoryCommit>("commits",
                        new ListDataProvider<RepositoryCommit>(entry.commits)) {
                    private static final long serialVersionUID = 1L;
                    public void populateItem(final Item<RepositoryCommit> item) {
                        final RepositoryCommit commit = item.getModelObject();
                        Fragment fragment = new Fragment("commit", "commitFragment", this);
                        // time of day
                        fragment.add(WicketUtils.createTimeLabel("time", commit.getAuthorIdent()
                                .getWhen(), GitBlitWebSession.get().getTimezone()));
                        // avatar
                        fragment.add(new GravatarImage("avatar", commit.getAuthorIdent(), 36));
                        // merge icon
                        if (commit.getParentCount() > 1) {
                            fragment.add(WicketUtils.newImage("commitIcon",
                                    "commit_merge_16x16.png"));
                        } else {
                            fragment.add(WicketUtils.newBlankImage("commitIcon"));
                        }
                        // author search link
                        String author = commit.getAuthorIdent().getName();
                        LinkPanel authorLink = new LinkPanel("author", "list", author,
                                SearchPage.class, WicketUtils.newSearchParameter(commit.repository,
                                        commit.getName(), author, Constants.SearchType.AUTHOR));
                        setPersonSearchTooltip(authorLink, author, Constants.SearchType.AUTHOR);
                        fragment.add(authorLink);
                        // repository summary page link
                        LinkPanel repositoryLink = new LinkPanel("repository", "list",
                                commit.repository, SummaryPage.class,
                                WicketUtils.newRepositoryParameter(commit.repository));
                        fragment.add(repositoryLink);
                        // repository branch
                        LinkPanel branchLink = new LinkPanel("branch", "list", commit.branch,
                                LogPage.class, WicketUtils.newObjectParameter(commit.repository,
                                        commit.branch));
                        WicketUtils.setCssStyle(branchLink, "color: #008000;");
                        fragment.add(branchLink);
                        LinkPanel commitid = new LinkPanel("commitid", "list subject",
                                commit.getShortName(), CommitPage.class,
                                WicketUtils.newObjectParameter(commit.repository, commit.getName()));
                        fragment.add(commitid);
                        // message/commit link
                        String shortMessage = commit.getShortMessage();
                        LinkPanel shortlog = new LinkPanel("message", "list subject", shortMessage,
                                CommitPage.class, WicketUtils.newObjectParameter(commit.repository,
                                        commit.getName()));
                        fragment.add(shortlog);
                        // refs
                        fragment.add(new RefsPanel("commitRefs", commit.repository, commit
                                .getRefs()));
                        // view, diff, tree links
                        fragment.add(new BookmarkablePageLink<Void>("view", CommitPage.class,
                                WicketUtils.newObjectParameter(commit.repository, commit.getName())));
                        fragment.add(new BookmarkablePageLink<Void>("diff", CommitDiffPage.class,
                                WicketUtils.newObjectParameter(commit.repository, commit.getName()))
                                .setEnabled(commit.getParentCount() > 0));
                        fragment.add(new BookmarkablePageLink<Void>("tree", TreePage.class,
                                WicketUtils.newObjectParameter(commit.repository, commit.getName())));
                        item.add(fragment);
                    }
                };
                item.add(commits);
            }
        };
        add(activityView);
    }
}