Option to show relative times in changes table
Derived from the original implementation in JGit's RelativeDateFormatter and adapted to the GWT environment. A new preference setting allows the user to decide if he wants absolute or relative dates. Myself as the original author and SAP agree to relicense the code borrowed from JGit under Apache 2 license for use in Gerrit Code Review. Change-Id: Id130f76a5937ad0d4f2e9a1b5f9f805f301b782c Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding//src/main/java=UTF-8
|
||||
encoding//src/test/java=UTF-8
|
||||
encoding/<project>=UTF-8
|
||||
|
@@ -119,6 +119,11 @@ public class FormatUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/** Format a date using git log's relative date format. */
|
||||
public static String relativeFormat(Date dt) {
|
||||
return RelativeDateFormatter.format(dt);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static String nameEmail(com.google.gerrit.common.data.AccountInfo acct) {
|
||||
return nameEmail(asInfo(acct));
|
||||
|
@@ -0,0 +1,109 @@
|
||||
// Copyright (C) 2013 The Android Open Source Project
|
||||
//
|
||||
// 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.google.gerrit.client;
|
||||
|
||||
import com.google.gerrit.client.changes.Util;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Formatter to format timestamps relative to the current time using time units
|
||||
* in the format defined by {@code git log --relative-date}.
|
||||
*/
|
||||
public class RelativeDateFormatter {
|
||||
final static long SECOND_IN_MILLIS = 1000;
|
||||
|
||||
final static long MINUTE_IN_MILLIS = 60 * SECOND_IN_MILLIS;
|
||||
|
||||
final static long HOUR_IN_MILLIS = 60 * MINUTE_IN_MILLIS;
|
||||
|
||||
final static long DAY_IN_MILLIS = 24 * HOUR_IN_MILLIS;
|
||||
|
||||
final static long WEEK_IN_MILLIS = 7 * DAY_IN_MILLIS;
|
||||
|
||||
final static long MONTH_IN_MILLIS = 30 * DAY_IN_MILLIS;
|
||||
|
||||
final static long YEAR_IN_MILLIS = 365 * DAY_IN_MILLIS;
|
||||
|
||||
/**
|
||||
* @param when {@link Date} to format
|
||||
* @return age of given {@link Date} compared to now formatted in the same
|
||||
* relative format as returned by {@code git log --relative-date}
|
||||
*/
|
||||
@SuppressWarnings("boxing")
|
||||
public static String format(Date when) {
|
||||
long ageMillis = (new Date()).getTime() - when.getTime();
|
||||
|
||||
// shouldn't happen in a perfect world
|
||||
if (ageMillis < 0) return Util.C.inTheFuture();
|
||||
|
||||
// seconds
|
||||
if (ageMillis < upperLimit(MINUTE_IN_MILLIS)) {
|
||||
return Util.M.secondsAgo(round(ageMillis, SECOND_IN_MILLIS));
|
||||
}
|
||||
|
||||
// minutes
|
||||
if (ageMillis < upperLimit(HOUR_IN_MILLIS)) {
|
||||
return Util.M.minutesAgo(round(ageMillis, MINUTE_IN_MILLIS));
|
||||
}
|
||||
|
||||
// hours
|
||||
if (ageMillis < upperLimit(DAY_IN_MILLIS)) {
|
||||
return Util.M.hoursAgo(round(ageMillis, HOUR_IN_MILLIS));
|
||||
}
|
||||
|
||||
// up to 14 days use days
|
||||
if (ageMillis < 14 * DAY_IN_MILLIS) {
|
||||
return Util.M.daysAgo(round(ageMillis, DAY_IN_MILLIS));
|
||||
}
|
||||
|
||||
// up to 10 weeks use weeks
|
||||
if (ageMillis < 10 * WEEK_IN_MILLIS) {
|
||||
return Util.M.weeksAgo(round(ageMillis, WEEK_IN_MILLIS));
|
||||
}
|
||||
|
||||
// months
|
||||
if (ageMillis < YEAR_IN_MILLIS) {
|
||||
return Util.M.monthsAgo(round(ageMillis, MONTH_IN_MILLIS));
|
||||
}
|
||||
|
||||
// up to 5 years use "year, months" rounded to months
|
||||
if (ageMillis < 5 * YEAR_IN_MILLIS) {
|
||||
long years = ageMillis / YEAR_IN_MILLIS;
|
||||
String yearLabel = (years > 1) ? Util.C.years() : Util.C.year();
|
||||
long months = round(ageMillis % YEAR_IN_MILLIS, MONTH_IN_MILLIS);
|
||||
String monthLabel =
|
||||
(months > 1) ? Util.C.months() : (months == 1 ? Util.C.month() : "");
|
||||
if (months == 0) {
|
||||
return Util.M.years0MonthsAgo(years, yearLabel);
|
||||
} else {
|
||||
return Util.M.yearsMonthsAgo(years, yearLabel, months, monthLabel);
|
||||
}
|
||||
}
|
||||
|
||||
// years
|
||||
return Util.M.yearsAgo(round(ageMillis, YEAR_IN_MILLIS));
|
||||
}
|
||||
|
||||
private static long upperLimit(long unit) {
|
||||
long limit = unit + unit / 2;
|
||||
return limit;
|
||||
}
|
||||
|
||||
private static long round(long n, long unit) {
|
||||
long rounded = (n + unit / 2) / unit;
|
||||
return rounded;
|
||||
}
|
||||
}
|
@@ -33,6 +33,7 @@ public interface AccountConstants extends Constants {
|
||||
String reversePatchSetOrder();
|
||||
String showUsernameInReviewCategory();
|
||||
String buttonSaveChanges();
|
||||
String showRelativeDateInChangeTable();
|
||||
|
||||
String tabAccountSummary();
|
||||
String tabPreferences();
|
||||
|
@@ -14,6 +14,7 @@ maximumPageSizeFieldLabel = Maximum Page Size:
|
||||
dateFormatLabel = Date/Time Format:
|
||||
contextWholeFile = Whole File
|
||||
buttonSaveChanges = Save Changes
|
||||
showRelativeDateInChangeTable = Show Relative Dates in Changes Table
|
||||
|
||||
tabAccountSummary = Profile
|
||||
tabPreferences = Preferences
|
||||
|
@@ -42,6 +42,7 @@ public class MyPreferencesScreen extends SettingsScreen {
|
||||
private CheckBox copySelfOnEmails;
|
||||
private CheckBox reversePatchSetOrder;
|
||||
private CheckBox showUsernameInReviewCategory;
|
||||
private CheckBox relativeDateInChangeTable;
|
||||
private ListBox maximumPageSize;
|
||||
private ListBox dateFormat;
|
||||
private ListBox timeFormat;
|
||||
@@ -94,7 +95,10 @@ public class MyPreferencesScreen extends SettingsScreen {
|
||||
dateTimePanel.add(dateFormat);
|
||||
dateTimePanel.add(timeFormat);
|
||||
}
|
||||
final Grid formGrid = new Grid(7, 2);
|
||||
|
||||
relativeDateInChangeTable = new CheckBox(Util.C.showRelativeDateInChangeTable());
|
||||
|
||||
final Grid formGrid = new Grid(8, 2);
|
||||
|
||||
int row = 0;
|
||||
formGrid.setText(row, labelIdx, "");
|
||||
@@ -125,6 +129,10 @@ public class MyPreferencesScreen extends SettingsScreen {
|
||||
formGrid.setWidget(row, fieldIdx, dateTimePanel);
|
||||
row++;
|
||||
|
||||
formGrid.setText(row, labelIdx, "");
|
||||
formGrid.setWidget(row, fieldIdx, relativeDateInChangeTable);
|
||||
row++;
|
||||
|
||||
add(formGrid);
|
||||
|
||||
save = new Button(Util.C.buttonSaveChanges());
|
||||
@@ -146,6 +154,7 @@ public class MyPreferencesScreen extends SettingsScreen {
|
||||
e.listenTo(maximumPageSize);
|
||||
e.listenTo(dateFormat);
|
||||
e.listenTo(timeFormat);
|
||||
e.listenTo(relativeDateInChangeTable);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -167,6 +176,7 @@ public class MyPreferencesScreen extends SettingsScreen {
|
||||
maximumPageSize.setEnabled(on);
|
||||
dateFormat.setEnabled(on);
|
||||
timeFormat.setEnabled(on);
|
||||
relativeDateInChangeTable.setEnabled(on);
|
||||
}
|
||||
|
||||
private void display(final AccountGeneralPreferences p) {
|
||||
@@ -180,6 +190,7 @@ public class MyPreferencesScreen extends SettingsScreen {
|
||||
p.getDateFormat());
|
||||
setListBox(timeFormat, AccountGeneralPreferences.TimeFormat.HHMM_12, //
|
||||
p.getTimeFormat());
|
||||
relativeDateInChangeTable.setValue(p.isRelativeDateInChangeTable());
|
||||
}
|
||||
|
||||
private void setListBox(final ListBox f, final short defaultValue,
|
||||
@@ -243,6 +254,7 @@ public class MyPreferencesScreen extends SettingsScreen {
|
||||
p.setTimeFormat(getListBox(timeFormat,
|
||||
AccountGeneralPreferences.TimeFormat.HHMM_12,
|
||||
AccountGeneralPreferences.TimeFormat.values()));
|
||||
p.setRelativeDateInChangeTable(relativeDateInChangeTable.getValue());
|
||||
|
||||
enable(false);
|
||||
save.setEnabled(false);
|
||||
|
@@ -171,4 +171,10 @@ public interface ChangeConstants extends Constants {
|
||||
|
||||
String diffAllSideBySide();
|
||||
String diffAllUnified();
|
||||
|
||||
String inTheFuture();
|
||||
String month();
|
||||
String months();
|
||||
String year();
|
||||
String years();
|
||||
}
|
||||
|
@@ -152,3 +152,9 @@ buttonClose = Close
|
||||
|
||||
diffAllSideBySide = All Side-by-Side
|
||||
diffAllUnified = All Unified
|
||||
|
||||
inTheFuture = in the future
|
||||
month = month
|
||||
months = months
|
||||
years = years
|
||||
year = year
|
||||
|
@@ -56,4 +56,15 @@ public interface ChangeMessages extends Messages {
|
||||
String groupIsNotAllowed(String group);
|
||||
String groupHasTooManyMembers(String group);
|
||||
String groupManyMembersConfirmation(String group, int memberCount);
|
||||
|
||||
String secondsAgo(long seconds);
|
||||
String minutesAgo(long minutes);
|
||||
String hoursAgo(long hours);
|
||||
String daysAgo(long days);
|
||||
String weeksAgo(long weeks);
|
||||
String monthsAgo(long months);
|
||||
String yearsAgo(long years);
|
||||
String years0MonthsAgo(long years, String yearLabel);
|
||||
String yearsMonthsAgo(long years, String yearLabel, long months,
|
||||
String monthLabel);
|
||||
}
|
||||
|
@@ -39,3 +39,13 @@ groupIsEmpty = The group {0} does not have any members to add as reviewers.
|
||||
groupIsNotAllowed = The group {0} cannot be added as reviewer.
|
||||
groupHasTooManyMembers = The group {0} has too many members to add them all as reviewers.
|
||||
groupManyMembersConfirmation = The group {0} has {1} members. Do you want to add them all as reviewers?
|
||||
|
||||
secondsAgo = {0} seconds ago
|
||||
minutesAgo = {0} minutes ago
|
||||
hoursAgo = {0} hours ago
|
||||
daysAgo = {0} days ago
|
||||
weeksAgo = {0} weeks ago
|
||||
monthsAgo = {0} months ago
|
||||
years0MonthsAgo = {0} {1} ago
|
||||
yearsMonthsAgo = {0} {1}, {2} {3} ago
|
||||
yearsAgo = {0} years ago
|
||||
|
@@ -14,6 +14,7 @@
|
||||
|
||||
package com.google.gerrit.client.changes;
|
||||
|
||||
import static com.google.gerrit.client.FormatUtil.relativeFormat;
|
||||
import static com.google.gerrit.client.FormatUtil.shortFormat;
|
||||
|
||||
import com.google.gerrit.client.Gerrit;
|
||||
@@ -206,7 +207,13 @@ public class ChangeTable2 extends NavigationTable<ChangeInfo> {
|
||||
row, C_PROJECT, new ProjectLink(c.project_name_key(), c.status()));
|
||||
table.setWidget(row, C_BRANCH, new BranchLink(c.project_name_key(), c
|
||||
.status(), c.branch(), c.topic()));
|
||||
table.setText(row, C_LAST_UPDATE, shortFormat(c.updated()));
|
||||
if (Gerrit.isSignedIn()
|
||||
&& Gerrit.getUserAccount().getGeneralPreferences()
|
||||
.isRelativeDateInChangeTable()) {
|
||||
table.setText(row, C_LAST_UPDATE, relativeFormat(c.updated()));
|
||||
} else {
|
||||
table.setText(row, C_LAST_UPDATE, shortFormat(c.updated()));
|
||||
}
|
||||
|
||||
boolean displayName = Gerrit.isSignedIn() && Gerrit.getUserAccount()
|
||||
.getGeneralPreferences().isShowUsernameInReviewCategory();
|
||||
|
@@ -0,0 +1,98 @@
|
||||
// Copyright (C) 2013 The Android Open Source Project
|
||||
//
|
||||
// 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.google.gerrit.client;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static com.google.gerrit.client.RelativeDateFormatter.YEAR_IN_MILLIS;
|
||||
import static com.google.gerrit.client.RelativeDateFormatter.SECOND_IN_MILLIS;
|
||||
import static com.google.gerrit.client.RelativeDateFormatter.MINUTE_IN_MILLIS;
|
||||
import static com.google.gerrit.client.RelativeDateFormatter.HOUR_IN_MILLIS;
|
||||
import static com.google.gerrit.client.RelativeDateFormatter.DAY_IN_MILLIS;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.eclipse.jgit.util.RelativeDateFormatter;
|
||||
import org.junit.Test;
|
||||
|
||||
public class RelativeDateFormatterTest {
|
||||
|
||||
private static void assertFormat(long ageFromNow, long timeUnit,
|
||||
String expectedFormat) {
|
||||
Date d = new Date(System.currentTimeMillis() - ageFromNow * timeUnit);
|
||||
String s = RelativeDateFormatter.format(d);
|
||||
assertEquals(expectedFormat, s);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFuture() {
|
||||
assertFormat(-100, YEAR_IN_MILLIS, "in the future");
|
||||
assertFormat(-1, SECOND_IN_MILLIS, "in the future");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatSeconds() {
|
||||
assertFormat(1, SECOND_IN_MILLIS, "1 seconds ago");
|
||||
assertFormat(89, SECOND_IN_MILLIS, "89 seconds ago");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatMinutes() {
|
||||
assertFormat(90, SECOND_IN_MILLIS, "2 minutes ago");
|
||||
assertFormat(3, MINUTE_IN_MILLIS, "3 minutes ago");
|
||||
assertFormat(60, MINUTE_IN_MILLIS, "60 minutes ago");
|
||||
assertFormat(89, MINUTE_IN_MILLIS, "89 minutes ago");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatHours() {
|
||||
assertFormat(90, MINUTE_IN_MILLIS, "2 hours ago");
|
||||
assertFormat(149, MINUTE_IN_MILLIS, "2 hours ago");
|
||||
assertFormat(35, HOUR_IN_MILLIS, "35 hours ago");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatDays() {
|
||||
assertFormat(36, HOUR_IN_MILLIS, "2 days ago");
|
||||
assertFormat(13, DAY_IN_MILLIS, "13 days ago");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatWeeks() {
|
||||
assertFormat(14, DAY_IN_MILLIS, "2 weeks ago");
|
||||
assertFormat(69, DAY_IN_MILLIS, "10 weeks ago");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatMonths() {
|
||||
assertFormat(70, DAY_IN_MILLIS, "2 months ago");
|
||||
assertFormat(75, DAY_IN_MILLIS, "3 months ago");
|
||||
assertFormat(364, DAY_IN_MILLIS, "12 months ago");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatYearsMonths() {
|
||||
assertFormat(366, DAY_IN_MILLIS, "1 year ago");
|
||||
assertFormat(380, DAY_IN_MILLIS, "1 year, 1 month ago");
|
||||
assertFormat(410, DAY_IN_MILLIS, "1 year, 2 months ago");
|
||||
assertFormat(2, YEAR_IN_MILLIS, "2 years ago");
|
||||
assertFormat(1824, DAY_IN_MILLIS, "4 years, 12 months ago");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatYears() {
|
||||
assertFormat(5, YEAR_IN_MILLIS, "5 years ago");
|
||||
assertFormat(60, YEAR_IN_MILLIS, "60 years ago");
|
||||
}
|
||||
}
|
@@ -123,6 +123,9 @@ public final class AccountGeneralPreferences {
|
||||
@Column(id = 11)
|
||||
protected boolean showUsernameInReviewCategory;
|
||||
|
||||
@Column(id = 12)
|
||||
protected boolean relativeDateInChangeTable;
|
||||
|
||||
public AccountGeneralPreferences() {
|
||||
}
|
||||
|
||||
@@ -226,6 +229,14 @@ public final class AccountGeneralPreferences {
|
||||
timeFormat = fmt.name();
|
||||
}
|
||||
|
||||
public boolean isRelativeDateInChangeTable() {
|
||||
return relativeDateInChangeTable;
|
||||
}
|
||||
|
||||
public void setRelativeDateInChangeTable(final boolean relativeDateInChangeTable) {
|
||||
this.relativeDateInChangeTable = relativeDateInChangeTable;
|
||||
}
|
||||
|
||||
public void resetToDefaults() {
|
||||
maximumPageSize = DEFAULT_PAGESIZE;
|
||||
showSiteHeader = true;
|
||||
@@ -237,5 +248,6 @@ public final class AccountGeneralPreferences {
|
||||
downloadCommand = null;
|
||||
dateFormat = null;
|
||||
timeFormat = null;
|
||||
relativeDateInChangeTable = false;
|
||||
}
|
||||
}
|
||||
|
@@ -32,7 +32,7 @@ import java.util.List;
|
||||
/** A version of the database schema. */
|
||||
public abstract class SchemaVersion {
|
||||
/** The current schema version. */
|
||||
public static final Class<Schema_77> C = Schema_77.class;
|
||||
public static final Class<Schema_78> C = Schema_78.class;
|
||||
|
||||
public static class Module extends AbstractModule {
|
||||
@Override
|
||||
|
@@ -0,0 +1,26 @@
|
||||
// Copyright (C) 2013 The Android Open Source Project
|
||||
//
|
||||
// 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.google.gerrit.server.schema;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
|
||||
public class Schema_78 extends SchemaVersion {
|
||||
|
||||
@Inject
|
||||
Schema_78(Provider<Schema_77> prior) {
|
||||
super(prior);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user