diff options
Diffstat (limited to 'src')
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; |