summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main/java/com/android/vts/api/UserFavoriteRestServlet.java133
-rw-r--r--src/main/java/com/android/vts/entity/UserFavoriteEntity.java34
-rw-r--r--src/main/java/com/android/vts/servlet/DashboardMainServlet.java27
-rw-r--r--src/main/java/com/android/vts/util/EmailHelper.java6
-rw-r--r--src/main/webapp/WEB-INF/jsp/dashboard_main.jsp70
-rw-r--r--src/main/webapp/css/dashboard_main.css6
6 files changed, 225 insertions, 51 deletions
diff --git a/src/main/java/com/android/vts/api/UserFavoriteRestServlet.java b/src/main/java/com/android/vts/api/UserFavoriteRestServlet.java
index f863051..bbb95da 100644
--- a/src/main/java/com/android/vts/api/UserFavoriteRestServlet.java
+++ b/src/main/java/com/android/vts/api/UserFavoriteRestServlet.java
@@ -25,11 +25,11 @@ import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Query;
-import com.google.appengine.api.datastore.Transaction;
import com.google.appengine.api.datastore.Query.CompositeFilterOperator;
import com.google.appengine.api.datastore.Query.Filter;
import com.google.appengine.api.datastore.Query.FilterOperator;
import com.google.appengine.api.datastore.Query.FilterPredicate;
+import com.google.appengine.api.datastore.Transaction;
import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
@@ -50,40 +50,37 @@ public class UserFavoriteRestServlet extends HttpServlet {
Logger.getLogger(UserFavoriteRestServlet.class.getName());
/**
- * Add a test to the user's favorites.
+ * Add a new favorite entity.
+ *
+ * @param user The user for which to add a favorite.
+ * @param test The name of the test.
+ * @param muteNotifications True if the subscriber has muted notifications, false otherwise.
+ * @param response The servlet response object.
+ * @return a json object with the generated key to the new favorite entity.
+ * @throws IOException
*/
- @Override
- public void doPost(HttpServletRequest request, HttpServletResponse response)
+ private static JsonObject addFavorite(
+ User user, String test, boolean muteNotifications, HttpServletResponse response)
throws IOException {
- UserService userService = UserServiceFactory.getUserService();
- User currentUser = userService.getCurrentUser();
DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
-
- // Retrieve the added tests from the request.
- String test = request.getPathInfo();
- if (test == null) {
- response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
- return;
- }
- if (test.startsWith("/")) {
- test = test.substring(1);
- }
Key addedTestKey = KeyFactory.createKey(TestEntity.KIND, test);
// Filter the tests that exist from the set of tests to add
try {
datastore.get(addedTestKey);
} catch (EntityNotFoundException e) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
- return;
+ return null;
}
Filter userFilter =
- new FilterPredicate(UserFavoriteEntity.USER, FilterOperator.EQUAL, currentUser);
- Filter testFilter = new FilterPredicate(
- UserFavoriteEntity.TEST_KEY, FilterOperator.EQUAL, addedTestKey);
- Query q = new Query(UserFavoriteEntity.KIND)
- .setFilter(CompositeFilterOperator.and(userFilter, testFilter))
- .setKeysOnly();
+ new FilterPredicate(UserFavoriteEntity.USER, FilterOperator.EQUAL, user);
+ Filter testFilter =
+ new FilterPredicate(
+ UserFavoriteEntity.TEST_KEY, FilterOperator.EQUAL, addedTestKey);
+ Query q =
+ new Query(UserFavoriteEntity.KIND)
+ .setFilter(CompositeFilterOperator.and(userFilter, testFilter))
+ .setKeysOnly();
Key favoriteKey = null;
@@ -94,7 +91,8 @@ public class UserFavoriteRestServlet extends HttpServlet {
break;
}
if (favoriteKey == null) {
- UserFavoriteEntity favorite = new UserFavoriteEntity(currentUser, addedTestKey);
+ UserFavoriteEntity favorite =
+ new UserFavoriteEntity(user, addedTestKey, muteNotifications);
Entity entity = favorite.toEntity();
datastore.put(entity);
favoriteKey = entity.getKey();
@@ -102,24 +100,95 @@ public class UserFavoriteRestServlet extends HttpServlet {
txn.commit();
} finally {
if (txn.isActive()) {
- logger.log(Level.WARNING,
+ logger.log(
+ Level.WARNING,
"Transaction rollback forced for favorite creation: " + test);
txn.rollback();
}
}
-
- response.setContentType("application/json");
- PrintWriter writer = response.getWriter();
JsonObject json = new JsonObject();
json.add("key", new JsonPrimitive(KeyFactory.keyToString(favoriteKey)));
- writer.print(new Gson().toJson(json));
- writer.flush();
- response.setStatus(HttpServletResponse.SC_OK);
+ return json;
}
/**
- * Remove a test from the user's favorites.
+ * @param user The user for which to add a favorite.
+ * @param favoriteKey The database key to the favorite entity to update.
+ * @param muteNotifications True if the subscriber has muted notifications, false otherwise.
+ * @param response The servlet response object.
+ * @return a json object with the generated key to the new favorite entity.
+ * @throws IOException
*/
+ private static JsonObject updateFavorite(
+ User user, Key favoriteKey, boolean muteNotifications, HttpServletResponse response)
+ throws IOException {
+ DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
+ Entity favoriteEntity;
+ try {
+ favoriteEntity = datastore.get(favoriteKey);
+ } catch (EntityNotFoundException e) {
+ response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+ return null;
+ }
+ UserFavoriteEntity favorite = UserFavoriteEntity.fromEntity(favoriteEntity);
+ if (favorite.user.getUserId() == user.getUserId()) {
+ response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+ return null;
+ }
+ if (favorite.muteNotifications != muteNotifications) {
+ Transaction txn = datastore.beginTransaction();
+ try {
+ favorite.muteNotifications = muteNotifications;
+ datastore.put(favorite.toEntity());
+ txn.commit();
+ } finally {
+ if (txn.isActive()) {
+ logger.log(
+ Level.WARNING,
+ "Transaction rollback forced for favorite update: " + favoriteKey);
+ txn.rollback();
+ }
+ }
+ }
+ JsonObject json = new JsonObject();
+ json.add("key", new JsonPrimitive(KeyFactory.keyToString(favoriteKey)));
+ return json;
+ }
+
+ /** Add a test to the user's favorites. */
+ @Override
+ public void doPost(HttpServletRequest request, HttpServletResponse response)
+ throws IOException {
+ UserService userService = UserServiceFactory.getUserService();
+ User currentUser = userService.getCurrentUser();
+
+ boolean muteNotifications = false;
+ if (request.getParameter(UserFavoriteEntity.MUTE_NOTIFICATIONS) != null) {
+ muteNotifications =
+ Boolean.parseBoolean(
+ request.getParameter(UserFavoriteEntity.MUTE_NOTIFICATIONS));
+ }
+
+ String userFavoritesKeyString = request.getParameter("userFavoritesKey");
+ String testName = request.getParameter("testName");
+
+ JsonObject returnData = null;
+ if (userFavoritesKeyString != null) {
+ Key userFavoritesKey = KeyFactory.stringToKey(userFavoritesKeyString);
+ returnData = updateFavorite(currentUser, userFavoritesKey, muteNotifications, response);
+ } else if (testName != null) {
+ returnData = addFavorite(currentUser, testName, muteNotifications, response);
+ }
+
+ if (returnData != null) {
+ response.setContentType("application/json");
+ PrintWriter writer = response.getWriter();
+ writer.print(new Gson().toJson(returnData));
+ writer.flush();
+ }
+ }
+
+ /** Remove a test from the user's favorites. */
@Override
public void doDelete(HttpServletRequest request, HttpServletResponse response)
throws IOException {
diff --git a/src/main/java/com/android/vts/entity/UserFavoriteEntity.java b/src/main/java/com/android/vts/entity/UserFavoriteEntity.java
index a2820a6..132d9b4 100644
--- a/src/main/java/com/android/vts/entity/UserFavoriteEntity.java
+++ b/src/main/java/com/android/vts/entity/UserFavoriteEntity.java
@@ -31,26 +31,50 @@ public class UserFavoriteEntity implements DashboardEntity {
// Property keys
public static final String USER = "user";
public static final String TEST_KEY = "testKey";
+ public static final String MUTE_NOTIFICATIONS = "muteNotifications";
+ private final Key key;
public final User user;
public final Key testKey;
+ public boolean muteNotifications;
/**
* Create a user favorite relationship.
*
+ * @param key The key of the entity in the database.
* @param user The User object for the subscribing user.
* @param testKey The key of the TestEntity object describing the test.
+ * @param muteNotifications True if the subscriber has muted notifications, false otherwise.
*/
- public UserFavoriteEntity(User user, Key testKey) {
+ private UserFavoriteEntity(Key key, User user, Key testKey, boolean muteNotifications) {
+ this.key = key;
this.user = user;
this.testKey = testKey;
+ this.muteNotifications = muteNotifications;
+ }
+
+ /**
+ * Create a user favorite relationship.
+ *
+ * @param user The User object for the subscribing user.
+ * @param testKey The key of the TestEntity object describing the test.
+ * @param muteNotifications True if the subscriber has muted notifications, false otherwise.
+ */
+ public UserFavoriteEntity(User user, Key testKey, boolean muteNotifications) {
+ this(null, user, testKey, muteNotifications);
}
@Override
public Entity toEntity() {
- Entity favoriteEntity = new Entity(KIND);
+ Entity favoriteEntity;
+ if (this.key != null) {
+ favoriteEntity = new Entity(key);
+ } else {
+ favoriteEntity = new Entity(KIND);
+ }
favoriteEntity.setProperty(USER, this.user);
favoriteEntity.setProperty(TEST_KEY, this.testKey);
+ favoriteEntity.setProperty(MUTE_NOTIFICATIONS, this.muteNotifications);
return favoriteEntity;
}
@@ -69,7 +93,11 @@ public class UserFavoriteEntity implements DashboardEntity {
try {
User user = (User) e.getProperty(USER);
Key testKey = (Key) e.getProperty(TEST_KEY);
- return new UserFavoriteEntity(user, testKey);
+ boolean muteNotifications = false;
+ if (e.hasProperty(MUTE_NOTIFICATIONS)) {
+ muteNotifications = (boolean) e.getProperty(MUTE_NOTIFICATIONS);
+ }
+ return new UserFavoriteEntity(e.getKey(), user, testKey, muteNotifications);
} catch (ClassCastException exception) {
// Invalid cast
logger.log(Level.WARNING, "Error parsing user favorite entity.", exception);
diff --git a/src/main/java/com/android/vts/servlet/DashboardMainServlet.java b/src/main/java/com/android/vts/servlet/DashboardMainServlet.java
index e0eded1..17b9b38 100644
--- a/src/main/java/com/android/vts/servlet/DashboardMainServlet.java
+++ b/src/main/java/com/android/vts/servlet/DashboardMainServlet.java
@@ -74,6 +74,7 @@ public class DashboardMainServlet extends BaseServlet {
private final Key testKey;
private final int passCount;
private final int failCount;
+ private boolean muteNotifications;
/**
* Test display constructor.
@@ -86,6 +87,7 @@ public class DashboardMainServlet extends BaseServlet {
this.testKey = testKey;
this.passCount = passCount;
this.failCount = failCount;
+ this.muteNotifications = false;
}
/**
@@ -115,6 +117,20 @@ public class DashboardMainServlet extends BaseServlet {
return this.failCount;
}
+ /**
+ * Get the notification mute status.
+ *
+ * @return True if the subscriber has muted notifications, false otherwise.
+ */
+ public boolean getMuteNotifications() {
+ return this.muteNotifications;
+ }
+
+ /** Set the notification mute status. */
+ public void setMuteNotifications(boolean muteNotifications) {
+ this.muteNotifications = muteNotifications;
+ }
+
@Override
public int compareTo(TestDisplay test) {
return this.testKey.getName().compareTo(test.getName());
@@ -199,14 +215,17 @@ public class DashboardMainServlet extends BaseServlet {
UserFavoriteEntity.USER, FilterOperator.EQUAL, currentUser);
query = new Query(UserFavoriteEntity.KIND).setFilter(userFilter);
- for (Entity favorite : datastore.prepare(query).asIterable()) {
- Key testKey = (Key) favorite.getProperty(UserFavoriteEntity.TEST_KEY);
+ for (Entity favoriteEntity : datastore.prepare(query).asIterable()) {
+ UserFavoriteEntity favorite = UserFavoriteEntity.fromEntity(favoriteEntity);
+ Key testKey = favorite.testKey;
if (!testMap.containsKey(testKey)) {
continue;
}
- displayedTests.add(testMap.get(testKey));
+ TestDisplay display = testMap.get(testKey);
+ display.setMuteNotifications(favorite.muteNotifications);
+ displayedTests.add(display);
subscriptionMap.put(
- testKey.getName(), KeyFactory.keyToString(favorite.getKey()));
+ testKey.getName(), KeyFactory.keyToString(favoriteEntity.getKey()));
}
}
header = FAVORITES_HEADER;
diff --git a/src/main/java/com/android/vts/util/EmailHelper.java b/src/main/java/com/android/vts/util/EmailHelper.java
index dd14d19..1277306 100644
--- a/src/main/java/com/android/vts/util/EmailHelper.java
+++ b/src/main/java/com/android/vts/util/EmailHelper.java
@@ -65,8 +65,10 @@ public class EmailHelper {
}
for (Entity favorite : datastore.prepare(favoritesQuery).asIterable()) {
UserFavoriteEntity favoriteEntity = UserFavoriteEntity.fromEntity(favorite);
- if (favoriteEntity != null && favoriteEntity.user != null
- && favoriteEntity.user.getEmail().endsWith(EMAIL_DOMAIN)) {
+ if (favoriteEntity != null
+ && favoriteEntity.user != null
+ && favoriteEntity.user.getEmail().endsWith(EMAIL_DOMAIN)
+ && !favoriteEntity.muteNotifications) {
emailSet.add(favoriteEntity.user.getEmail());
}
}
diff --git a/src/main/webapp/WEB-INF/jsp/dashboard_main.jsp b/src/main/webapp/WEB-INF/jsp/dashboard_main.jsp
index e0e8f57..796e295 100644
--- a/src/main/webapp/WEB-INF/jsp/dashboard_main.jsp
+++ b/src/main/webapp/WEB-INF/jsp/dashboard_main.jsp
@@ -35,7 +35,7 @@
return;
}
$('#add-button').addClass('disabled');
- $.post('/api/favorites/' + test).then(function(data) {
+ $.post('/api/favorites', { testName: test}).then(function(data) {
if (!data.key) {
return;
}
@@ -49,12 +49,24 @@
var span = $('<span class="entry valign"></span>').text(test);
span.appendTo(div);
a.appendTo(wrapper);
- var clear = $('<a class="col s1 btn-flat center"></a>');
- clear.addClass('clear-button');
+
+ var btnContainer = $('<div class="col s1 center btn-container"></div>');
+ var silence = $('<a class="col s6 btn-flat notification-button active"></a>');
+ silence.append('<i class="material-icons">notifications_active</i>');
+ silence.attr('test', test);
+ silence.attr('title', 'Disable notifications');
+ silence.appendTo(btnContainer);
+ silence.click(toggleNotifications);
+
+ var clear = $('<a class="col s6 btn-flat remove-button"></a>');
clear.append('<i class="material-icons">clear</i>');
clear.attr('test', test);
- clear.appendTo(wrapper);
+ clear.attr('title', 'Remove favorite');
+ clear.appendTo(btnContainer);
clear.click(removeFavorite);
+
+ btnContainer.appendTo(wrapper);
+
wrapper.prependTo('#options').hide()
.slideDown(150);
$('#input-box').val(null);
@@ -64,6 +76,40 @@
});
}
+ var toggleNotifications = function() {
+ var self = $(this);
+ if (self.hasClass('disabled')) {
+ return;
+ }
+ self.addClass('disabled');
+ var test = self.attr('test');
+ if (!(test in subscriptionMap)) {
+ return;
+ }
+ var muteStatus = self.hasClass('active');
+ var element = self;
+ $.post('/api/favorites', { userFavoritesKey: subscriptionMap[test], muteNotifications: muteStatus}).then(function(data) {
+ element = self.clone();
+ if (element.hasClass('active')) {
+ element.find('i').text('notifications_off')
+ element.removeClass('active');
+ element.addClass('inactive');
+ element.attr('title', 'Enable notifications');
+ } else {
+ element.find('i').text('notifications_active')
+ element.removeClass('inactive');
+ element.addClass('active');
+ element.attr('title', 'Disable notifications');
+ }
+ element.click(toggleNotifications);
+ self.replaceWith(function() {
+ return element;
+ });
+ }).always(function() {
+ element.removeClass('disabled');
+ });
+ }
+
var removeFavorite = function() {
var self = $(this);
if (self.hasClass('disabled')) {
@@ -81,7 +127,7 @@
self.removeClass('disabled');
}).then(function() {
delete subscriptionMap[test];
- self.parent().slideUp(150, function() {
+ self.parent().parent().slideUp(150, function() {
self.remove();
});
});
@@ -107,7 +153,8 @@
}
});
- $('.clear-button').click(removeFavorite);
+ $('.remove-button').click(removeFavorite);
+ $('.notification-button').click(toggleNotifications);
$('#add-button').click(addFavorite);
});
</script>
@@ -154,9 +201,14 @@
</div>
</a>
<c:if test='${not showAll}'>
- <a class='col s1 btn-flat center clear-button' test='${test.name}'>
- <i class='material-icons'>clear</i>
- </a>
+ <div class='col s1 center btn-container'>
+ <a class='col s6 btn-flat notification-button ${test.muteNotifications ? "inactive" : "active"}' test='${test.name}' title='${test.muteNotifications ? "Enable" : "Disable"} notifications'>
+ <i class='material-icons'>notifications_${test.muteNotifications ? "off" : "active"}</i>
+ </a>
+ <a class='col s6 btn-flat remove-button' test='${test.name}' title='Remove favorite'>
+ <i class='material-icons'>clear</i>
+ </a>
+ </div>
</c:if>
</div>
</c:forEach>
diff --git a/src/main/webapp/css/dashboard_main.css b/src/main/webapp/css/dashboard_main.css
index e6e899f..0db4c94 100644
--- a/src/main/webapp/css/dashboard_main.css
+++ b/src/main/webapp/css/dashboard_main.css
@@ -27,12 +27,16 @@
height: 61px;
}
-.btn-flat.clear-button {
+.btn-container > .btn-flat {
margin-top: 8px;
user-select: none;
color: grey;
}
+.btn-container > .btn-flat.inactive {
+ color: lightgray;
+}
+
.row .col.card.option {
padding: 6px 15px 6px 15px;
margin: 5px 0;