From e80119fe6c37abc0c1b99bf0e51a6cfdc102bbcc Mon Sep 17 00:00:00 2001 From: Neil McErlean Date: Mon, 12 Jul 2010 16:07:08 +0000 Subject: [PATCH] RatingService GET node ratings statistics via REST. Added node rating stats to the ratings.get webscript (for each scheme): average (mean) of all ratings for this node. total (sum) of all ratings for this node. count of all ratings for this node. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@21108 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../repository/rating/ratings.get.desc.xml | 40 ++++++- .../repository/rating/ratings.get.json.ftl | 13 +- .../web/scripts/rating/RatingRestApiTest.java | 111 ++++++++++++------ .../repo/web/scripts/rating/RatingsGet.java | 30 ++++- 4 files changed, 148 insertions(+), 46 deletions(-) diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/rating/ratings.get.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/repository/rating/ratings.get.desc.xml index 59266077f7..e62fe03b1b 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/rating/ratings.get.desc.xml +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/rating/ratings.get.desc.xml @@ -1,11 +1,41 @@ GET rating - The ratings returned will be those applied by the currently authenticated user. There - will be one for each rating scheme in which the user has submitted a rating. If the - currently authenticated user has not applied any ratings to this content the list - will be empty. + Gets the list of ratings for the specified NodeRef (if there are any) as well as some + general statistics on ratings for the specified node.
+ The ratings returned will only include those applied by the currently authenticated user. There + will be one for each rating scheme in which the user has submitted a rating.
+ The returned data will be of the form:
+ {
+   "nodeRef": "workspace:\/\/SpacesStore\/d0b163fe-050d-43f5-88e4-db1794a3e5cd",
+   "ratings":
+   [
+     {
+       "ratingScheme": "fiveStarRatingScheme",
+       "rating": 5,
+       "appliedAt": "12 Jul 2010 16:38:05 GMT+0100 (BST)",
+       "appliedBy": "UserOne"
+     }
+   ],
+   "nodeStatistics":
+   {
+     "likesRatingScheme":
+     {
+       "averageRating": 3,
+       "ratingsTotal": 3,
+       "ratingsCount": 1
+     }
+   }
+ }
+ with the following meanings:
+ nodeRef: the nodeRef whose ratings are requested.
+ ratings/ratingScheme: the rating scheme name
+ ratings/rating: the rating applied to the named noderef in the above scheme by the current user
+ ratings/appliedAt: the datetime when the above rating was applied
+ ratings/appliedBy: the user who applied the above rating (the current user)
+ nodeStatistics/averageRating: the average (mean) rating on this node in the named rating scheme for all users
+ nodeStatistics/ratingsTotal: the total score (sum) of all ratings on this node in the named rating scheme
+ nodeStatistics/ratingsCount: the total number of applied ratings on this node in the named rating scheme.
]]>
/api/node/{store_type}/{store_id}/{id}/ratings diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/rating/ratings.get.json.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/rating/ratings.get.json.ftl index 97f2be3d6a..f6ebe1d378 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/rating/ratings.get.json.ftl +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/rating/ratings.get.json.ftl @@ -14,7 +14,18 @@ "appliedBy": "${rating.appliedBy!""}" }<#if rating_has_next>, - ] + ], + "nodeStatistics": + { + <#list averageRatings?keys as schemeName> + "${schemeName!""}": + { + "averageRating": ${averageRatings[schemeName]?c}, + "ratingsTotal": ${ratingsTotals[schemeName]?c}, + "ratingsCount": ${ratingsCounts[schemeName]?c} + }<#if schemeName_has_next>, + + } } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/web/scripts/rating/RatingRestApiTest.java b/source/java/org/alfresco/repo/web/scripts/rating/RatingRestApiTest.java index e09c09ff1d..304e28a8ba 100644 --- a/source/java/org/alfresco/repo/web/scripts/rating/RatingRestApiTest.java +++ b/source/java/org/alfresco/repo/web/scripts/rating/RatingRestApiTest.java @@ -47,9 +47,25 @@ import org.springframework.extensions.webscripts.TestWebScriptServer.Response; */ public class RatingRestApiTest extends BaseWebScriptTest { + // Miscellaneous constants used throughout this test class. + private static final String FIVE_STAR_RATING_SCHEME = "fiveStarRatingScheme"; + private static final String LIKES_RATING_SCHEME = "likesRatingScheme"; + private static final String USER_ONE = "UserOne"; private static final String USER_TWO = "UserTwo"; + private static final String RATING_SCHEMES = "ratingSchemes"; + private static final String NAME = "name"; + private static final String MIN_RATING = "minRating"; + private static final String MAX_RATING = "maxRating"; + private static final String RATINGS = "ratings"; + private static final String NODE_REF = "nodeRef"; + private static final String DATA = "data"; + private static final String RATINGS_TOTAL = "ratingsTotal"; + private static final String RATINGS_COUNT = "ratingsCount"; + private static final String AVERAGE_RATING = "averageRating"; + private static final String NODE_STATISTICS = "nodeStatistics"; + private final static String NODE_RATINGS_URL_FORMAT = "/api/node/{0}/ratings"; private final static String GET_RATING_DEFS_URL = "/api/rating/schemedefinitions"; @@ -122,22 +138,22 @@ public class RatingRestApiTest extends BaseWebScriptTest JSONObject jsonRsp = new JSONObject(new JSONTokener(rsp.getContentAsString())); - JSONObject dataObj = (JSONObject)jsonRsp.get("data"); + JSONObject dataObj = (JSONObject)jsonRsp.get(DATA); assertNotNull("JSON 'data' object was null", dataObj); - JSONArray ratingSchemesArray = (JSONArray)dataObj.get("ratingSchemes"); + JSONArray ratingSchemesArray = (JSONArray)dataObj.get(RATING_SCHEMES); assertNotNull("JSON 'ratingSchemesArray' object was null", ratingSchemesArray); assertEquals(2, ratingSchemesArray.length()); JSONObject scheme1 = ratingSchemesArray.getJSONObject(0); JSONObject scheme2 = ratingSchemesArray.getJSONObject(1); - assertEquals("likesRatingScheme", scheme1.getString("name")); - assertEquals(1, scheme1.getInt("minRating")); - assertEquals(1, scheme1.getInt("maxRating")); - assertEquals("fiveStarRatingScheme", scheme2.getString("name")); - assertEquals(1, scheme2.getInt("minRating")); - assertEquals(5, scheme2.getInt("maxRating")); + assertEquals(LIKES_RATING_SCHEME, scheme1.getString(NAME)); + assertEquals(1, scheme1.getInt(MIN_RATING)); + assertEquals(1, scheme1.getInt(MAX_RATING)); + assertEquals(FIVE_STAR_RATING_SCHEME, scheme2.getString(NAME)); + assertEquals(1, scheme2.getInt(MIN_RATING)); + assertEquals(5, scheme2.getInt(MAX_RATING)); } public void testGetRatingsFromUnratedNodeRef() throws Exception @@ -150,29 +166,41 @@ public class RatingRestApiTest extends BaseWebScriptTest JSONObject jsonRsp = new JSONObject(new JSONTokener(rsp.getContentAsString())); - JSONObject dataObj = (JSONObject)jsonRsp.get("data"); + JSONObject dataObj = (JSONObject)jsonRsp.get(DATA); assertNotNull("JSON 'data' object was null", dataObj); - assertEquals(testNode.toString(), dataObj.getString("nodeRef")); - final JSONArray ratingsArray = dataObj.getJSONArray("ratings"); + assertEquals(testNode.toString(), dataObj.getString(NODE_REF)); + final JSONArray ratingsArray = dataObj.getJSONArray(RATINGS); assertEquals(0, ratingsArray.length()); + + // Unrated content + JSONObject statsObject = dataObj.getJSONObject(NODE_STATISTICS); + JSONObject likesStats = statsObject.getJSONObject(LIKES_RATING_SCHEME); + assertEquals("Average rating was wrong.", -1.0, likesStats.getDouble(AVERAGE_RATING)); + assertEquals("Ratings count rating was wrong.", 0, likesStats.getInt(RATINGS_COUNT)); + assertEquals("Ratings total was wrong.", 0, likesStats.getInt(RATINGS_TOTAL)); } - public void testApplyRatingAndRetrieve() throws Exception + /** + * This test method applies ratings from multiple users in a single rating + * scheme to a single test node. It then retrieves those ratings to ensure they + * were persisted correctly. + */ + public void testApplyRatingsAsMultipleUsersAndRetrieve() throws Exception { // POST a new rating to the testNode - as User One. AuthenticationUtil.setFullyAuthenticatedUser(USER_ONE); - final String ratingUrl = getRatingUrl(testNode); + final String testNodeRatingUrl = getRatingUrl(testNode); - final int ratingValue = 5; + final int userOneRatingValue = 5; String jsonString = new JSONStringer().object() - .key("rating").value(ratingValue) - .key("ratingScheme").value("fiveStarRatingScheme") + .key("rating").value(userOneRatingValue) + .key("ratingScheme").value(FIVE_STAR_RATING_SCHEME) .endObject() .toString(); - Response rsp = sendRequest(new PostRequest(ratingUrl, + Response rsp = sendRequest(new PostRequest(testNodeRatingUrl, jsonString, APPLICATION_JSON), 200); String rspContent = rsp.getContentAsString(); @@ -180,28 +208,34 @@ public class RatingRestApiTest extends BaseWebScriptTest // Get the returned URL and validate JSONObject jsonRsp = new JSONObject(new JSONTokener(rspContent)); - JSONObject dataObj = (JSONObject)jsonRsp.get("data"); + JSONObject dataObj = (JSONObject)jsonRsp.get(DATA); assertNotNull("JSON 'data' object was null", dataObj); String returnedUrl = dataObj.getString("ratedNodeUrl"); - assertEquals(ratingUrl, returnedUrl); - assertEquals("fiveStarRatingScheme", dataObj.getString("ratingScheme")); - assertEquals(ratingValue, dataObj.getInt("rating")); + assertEquals(testNodeRatingUrl, returnedUrl); + assertEquals(FIVE_STAR_RATING_SCHEME, dataObj.getString("ratingScheme")); + assertEquals(userOneRatingValue, dataObj.getInt("rating")); // Now GET the ratings via that returned URL - rsp = sendRequest(new GetRequest(ratingUrl), 200); + rsp = sendRequest(new GetRequest(testNodeRatingUrl), 200); jsonRsp = new JSONObject(new JSONTokener(rsp.getContentAsString())); - dataObj = (JSONObject)jsonRsp.get("data"); + dataObj = (JSONObject)jsonRsp.get(DATA); assertNotNull("JSON 'data' object was null", dataObj); // There should only be the one rating in there. - final JSONArray ratingsArray = dataObj.getJSONArray("ratings"); + final JSONArray ratingsArray = dataObj.getJSONArray(RATINGS); assertEquals(1, ratingsArray.length()); - JSONObject firstRating = (JSONObject)ratingsArray.get(0); - assertEquals(ratingValue, firstRating.getInt("rating")); - assertEquals("fiveStarRatingScheme", firstRating.getString("ratingScheme")); + JSONObject recoveredRating = (JSONObject)ratingsArray.get(0); + assertEquals(userOneRatingValue, recoveredRating.getInt("rating")); + assertEquals(FIVE_STAR_RATING_SCHEME, recoveredRating.getString("ratingScheme")); + // As well as the average, total ratings. + JSONObject statsObject = dataObj.getJSONObject(NODE_STATISTICS); + JSONObject fiveStarStats = statsObject.getJSONObject(FIVE_STAR_RATING_SCHEME); + assertEquals("Average rating was wrong.", (double)userOneRatingValue, fiveStarStats.getDouble(AVERAGE_RATING)); + assertEquals("Ratings count rating was wrong.", 1, fiveStarStats.getInt(RATINGS_COUNT)); + assertEquals("Ratings total was wrong.", userOneRatingValue, fiveStarStats.getInt(RATINGS_TOTAL)); // Now POST a second new rating to the testNode - as User Two. @@ -210,18 +244,18 @@ public class RatingRestApiTest extends BaseWebScriptTest final int userTwoRatingValue = 3; jsonString = new JSONStringer().object() .key("rating").value(userTwoRatingValue) - .key("ratingScheme").value("fiveStarRatingScheme") + .key("ratingScheme").value(FIVE_STAR_RATING_SCHEME) .endObject() .toString(); - rsp = sendRequest(new PostRequest(ratingUrl, + rsp = sendRequest(new PostRequest(testNodeRatingUrl, jsonString, APPLICATION_JSON), 200); rspContent = rsp.getContentAsString(); // Get the returned URL and validate jsonRsp = new JSONObject(new JSONTokener(rsp.getContentAsString())); - dataObj = (JSONObject)jsonRsp.get("data"); + dataObj = (JSONObject)jsonRsp.get(DATA); assertNotNull("JSON 'data' object was null", dataObj); returnedUrl = dataObj.getString("ratedNodeUrl"); @@ -230,18 +264,25 @@ public class RatingRestApiTest extends BaseWebScriptTest jsonRsp = new JSONObject(new JSONTokener(rsp.getContentAsString())); - dataObj = (JSONObject)jsonRsp.get("data"); + dataObj = (JSONObject)jsonRsp.get(DATA); assertNotNull("JSON 'data' object was null", dataObj); // There should still only be the one rating in the results - because we're running // as UserTwo and should not see UserOne's rating. - final JSONArray userTwoRatingsArray = dataObj.getJSONArray("ratings"); + final JSONArray userTwoRatingsArray = dataObj.getJSONArray(RATINGS); assertEquals(1, userTwoRatingsArray.length()); JSONObject secondRating = (JSONObject)userTwoRatingsArray.get(0); assertEquals(userTwoRatingValue, secondRating.getInt("rating")); - assertEquals("fiveStarRatingScheme", secondRating.getString("ratingScheme")); - - //TODO Could probably put the GET average call here then. + assertEquals(FIVE_STAR_RATING_SCHEME, secondRating.getString("ratingScheme")); + + // Now the average should have changed. + statsObject = dataObj.getJSONObject(NODE_STATISTICS); + fiveStarStats = statsObject.getJSONObject(FIVE_STAR_RATING_SCHEME); + assertEquals("Average rating was wrong.", (userOneRatingValue + userTwoRatingValue) / 2.0, + fiveStarStats.getDouble(AVERAGE_RATING)); + assertEquals("Ratings count rating was wrong.", 2, fiveStarStats.getInt(RATINGS_COUNT)); + assertEquals("Ratings total was wrong.", userOneRatingValue + userTwoRatingValue, + fiveStarStats.getInt(RATINGS_TOTAL)); } /** diff --git a/source/java/org/alfresco/repo/web/scripts/rating/RatingsGet.java b/source/java/org/alfresco/repo/web/scripts/rating/RatingsGet.java index 68da83c52e..313657e92a 100644 --- a/source/java/org/alfresco/repo/web/scripts/rating/RatingsGet.java +++ b/source/java/org/alfresco/repo/web/scripts/rating/RatingsGet.java @@ -37,6 +37,13 @@ import org.springframework.extensions.webscripts.WebScriptRequest; */ public class RatingsGet extends AbstractRatingWebScript { + private static final String NODE_REF = "nodeRef"; + private static final String RATINGS = "ratings"; + + private static final String AVERAGE_RATINGS = "averageRatings"; + private static final String RATINGS_TOTALS = "ratingsTotals"; + private static final String RATINGS_COUNTS = "ratingsCounts"; + @Override protected Map executeImpl(WebScriptRequest req, Status status, Cache cache) { @@ -44,20 +51,33 @@ public class RatingsGet extends AbstractRatingWebScript NodeRef nodeRef = parseRequestForNodeRef(req); - List ratings = new ArrayList(); + // These are the data for the current user's ratings of this node, if any. + List myRatings = new ArrayList(); + + // These maps hold the average rating, accumulated total of all ratings and + // the number of ratings applied for this node as a function of rating scheme. + Map averageRatings = new HashMap(); + Map ratingsTotals = new HashMap(); + Map ratingsCounts = new HashMap(); for (String schemeName : ratingService.getRatingSchemes().keySet()) { final Rating ratingByCurrentUser = ratingService.getRatingByCurrentUser(nodeRef, schemeName); if (ratingByCurrentUser != null) { - ratings.add(ratingByCurrentUser); + myRatings.add(ratingByCurrentUser); } + averageRatings.put(schemeName, ratingService.getAverageRating(nodeRef, schemeName)); + ratingsTotals.put(schemeName, ratingService.getTotalRating(nodeRef, schemeName)); + ratingsCounts.put(schemeName, ratingService.getRatingsCount(nodeRef, schemeName)); } - model.put("nodeRef", nodeRef.toString()); - model.put("ratings", ratings); - + model.put(NODE_REF, nodeRef.toString()); + model.put(RATINGS, myRatings); + model.put(AVERAGE_RATINGS, averageRatings); + model.put(RATINGS_TOTALS, ratingsTotals); + model.put(RATINGS_COUNTS, ratingsCounts); + return model; } }