Merge: nitweb: stars rating system
authorJean Privat <jean@pryen.org>
Mon, 4 Jul 2016 19:16:52 +0000 (15:16 -0400)
committerJean Privat <jean@pryen.org>
Mon, 4 Jul 2016 19:16:52 +0000 (15:16 -0400)
Add discrete stars rating system in top right corner of each documentation card.

Can be seen here: http://nitweb.moz-code.org/doc/core::Array

Pull-Request: #2215
Reviewed-by: Jean Privat <jean@pryen.org>

share/nitweb/directives/entity/doc.html
share/nitweb/directives/entity/stars.html [new file with mode: 0644]
share/nitweb/javascripts/entities.js
share/nitweb/javascripts/model.js
share/nitweb/stylesheets/nitweb.css
share/nitweb/views/class.html
share/nitweb/views/property.html
src/nitweb.nit
src/web/api_feedback.nit [new file with mode: 0644]
src/web/api_model.nit [moved from src/web/model_api.nit with 99% similarity]
src/web/web.nit

index 9d0ffb0..bf271c3 100644 (file)
@@ -1,5 +1,8 @@
 <div class='card' ng-if='mentity.mdoc'>
        <div class='card-body'>
+               <div class='pull-right'>
+                       <entity-rating mentity='mentity' />
+               </div>
                <div ng-bind-html='mentity.mdoc.html_documentation'></div>
        </div>
 </div>
diff --git a/share/nitweb/directives/entity/stars.html b/share/nitweb/directives/entity/stars.html
new file mode 100644 (file)
index 0000000..796bc3b
--- /dev/null
@@ -0,0 +1,6 @@
+<span class='stars' ng-repeat='star in [1, 2, 3, 4, 5]' ng-if='ratings' title='mean: {{ratings.mean}} ({{ratings.ratings.length}} stars)'>
+       <span
+               class='star glyphicon'
+               ng-class='star <= ratings.mean? "glyphicon-star": "glyphicon-star-empty"'
+               ng-click='postStar(star)' />
+</span>
index 9333050..2438d84 100644 (file)
                                }
                        };
                }])
+
+               .directive('entityRating', ['Feedback', function(Feedback, Code) {
+                       return {
+                               restrict: 'E',
+                               scope: {
+                                       mentity: '='
+                               },
+                               templateUrl: '/directives/entity/stars.html',
+                               link: function ($scope, element, attrs) {
+                                       $scope.postStar = function(rating) {
+                                               Feedback.postEntityStar($scope.mentity.full_name, rating,
+                                               function(data) {
+                                                       $scope.ratings = data;
+                                               }, function(err) {
+                                                       $scope.err = err;
+                                               });
+                                       }
+
+                                       Feedback.loadEntityStars($scope.mentity.full_name,
+                                               function(data) {
+                                                       $scope.ratings = data;
+                                               }, function(err) {
+                                                       $scope.err = err;
+                                               });
+                               }
+                       };
+               }])
 })();
index 3accab2..efcecd5 100644 (file)
                        }
                }])
 
+               .factory('Feedback', [ '$http', function($http) {
+                       return {
+                               loadEntityStars: function(id, cb, cbErr) {
+                                       $http.get(apiUrl + '/feedback/stars/' + id)
+                                               .success(cb)
+                                               .error(cbErr);
+                               },
+                               postEntityStar: function(id, rating, cb, cbErr) {
+                                       $http.post(apiUrl + '/feedback/stars/' + id, {rating: rating})
+                                               .success(cb)
+                                               .error(cbErr);
+                               }
+                       }
+               }])
+
                .factory('DocDown', [ '$http', function($http) {
                        return {
                                postMarkdown: function(md, cb, cbErr) {
index 9471a5d..db8a03d 100644 (file)
@@ -214,6 +214,32 @@ entity-list:hover .btn-filter {
        margin-top: 8px;
        margin-bottom: 0px;
 }
+
+/*
+ * Ratings
+ */
+
+.card .stars {
+       visibility: hidden
+}
+
+.card:hover .stars {
+       visibility: visible
+}
+
+.star {
+       color: grey;
+       cursor: pointer;
+}
+
+.star:hover, .star.active:hover {
+       color: #FF8100
+}
+
+.star.active {
+       color: #FFC000
+}
+
 /*
  * Users
  */
index 54da8af..2af7b47 100644 (file)
@@ -23,7 +23,7 @@
 
 <div class='tab-content'>
        <div role='tabpanel' class='tab-pane fade in active' id='doc'>
-               <entity-doc mentity='mentity.intro'/>
+               <entity-doc mentity='mentity'/>
 
                <entity-list list-title='Parents'
                        list-entities='mentity.parents'
index 44e439e..6d8d483 100644 (file)
@@ -13,7 +13,7 @@
 
 <div class='tab-content'>
        <div role='tabpanel' class='tab-pane fade in active' id='doc'>
-               <entity-doc mentity='mentity.intro'/>
+               <entity-doc mentity='mentity'/>
        </div>
        <div role='tabpanel' class='tab-pane fade' id='linearization'>
                <entity-linearization
index 3897c5c..af58044 100644 (file)
@@ -60,6 +60,11 @@ private class NitwebPhase
                        catalog.package_page(mpackage)
                end
 
+               # Prepare mongo connection
+               var mongo = new MongoClient("mongodb://localhost:27017/")
+               var db = mongo.database("nitweb")
+               var collection = db.collection("stars")
+
                # Run the server
                var host = toolcontext.opt_host.value or else "localhost"
                var port = toolcontext.opt_port.value
@@ -67,7 +72,7 @@ private class NitwebPhase
                var app = new App
 
                app.use_before("/*", new RequestClock)
-               app.use("/api", new APIRouter(model, modelbuilder, mainmodule, catalog))
+               app.use("/api", new APIRouter(model, modelbuilder, mainmodule, catalog, collection))
                app.use("/*", new StaticHandler(toolcontext.share_dir / "nitweb", "index.html"))
                app.use_after("/*", new ConsoleLog)
 
@@ -91,6 +96,9 @@ class APIRouter
        # Catalog to pass to handlers.
        var catalog: Catalog
 
+       # Mongo collection used to store ratings.
+       var collection: MongoCollection
+
        init do
                use("/catalog", new APICatalogRouter(model, mainmodule, catalog))
                use("/list", new APIList(model, mainmodule))
@@ -101,6 +109,7 @@ class APIRouter
                use("/uml/:id", new APIEntityUML(model, mainmodule))
                use("/linearization/:id", new APIEntityLinearization(model, mainmodule))
                use("/defs/:id", new APIEntityDefs(model, mainmodule))
+               use("/feedback/", new APIFeedbackRouter(model, mainmodule, collection))
                use("/inheritance/:id", new APIEntityInheritance(model, mainmodule))
                use("/graph/", new APIGraphRouter(model, mainmodule))
                use("/docdown/", new APIDocdown(model, mainmodule, modelbuilder))
diff --git a/src/web/api_feedback.nit b/src/web/api_feedback.nit
new file mode 100644 (file)
index 0000000..61ba756
--- /dev/null
@@ -0,0 +1,150 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# 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.
+
+# Feedback related features
+module api_feedback
+
+import web_base
+import mongodb
+
+# Group all api handlers in one router
+class APIFeedbackRouter
+       super Router
+
+       # Model to pass to handlers
+       var model: Model
+
+       # Mainmodule to pass to handlers
+       var mainmodule: MModule
+
+       # Mongo collection used to store ratings
+       var collection: MongoCollection
+
+       init do
+               use("/stars/:id", new APIStars(model, mainmodule, collection))
+       end
+end
+
+# Stars attributed to mentities
+class APIStars
+       super APIHandler
+
+       # Collection used to store ratings
+       var collection: MongoCollection
+
+       redef fun get(req, res) do
+               var mentity = mentity_from_uri(req, res)
+               if mentity == null then
+                       res.error 404
+                       return
+               end
+
+               res.json mentity_ratings(mentity)
+       end
+
+       redef fun post(req, res) do
+               var mentity = mentity_from_uri(req, res)
+               if mentity == null then
+                       res.error 404
+                       return
+               end
+               var obj = req.body.parse_json
+               if not obj isa JsonObject then
+                       res.error 400
+                       return
+               end
+               var rating = obj["rating"]
+               if not rating isa Int then
+                       res.error 400
+                       return
+               end
+
+               var val = new MEntityRating(mentity.full_name, rating, get_time)
+               collection.insert(val.json)
+
+               res.json mentity_ratings(mentity)
+       end
+
+       # Get the ratings of a `mentity`
+       fun mentity_ratings(mentity: MEntity): MEntityRatings do
+               var ratings = new MEntityRatings(mentity)
+
+               var req = new JsonObject
+               req["mentity"] = mentity.full_name
+               var rs = collection.find_all(req)
+               for r in rs do ratings.ratings.add new MEntityRating.from_json(r)
+               return ratings
+       end
+end
+
+# Ratings representation for a mentity
+class MEntityRatings
+       super Jsonable
+
+       # MEntity rated
+       var mentity: MEntity
+
+       # List of ratings
+       var ratings = new Array[MEntityRating]
+
+       # Mean of all ratings or 0
+       fun mean: Float do
+               if ratings.is_empty then return 0.0
+               var sum = 0.0
+               for r in ratings do sum += r.rating.to_f
+               var res = sum / ratings.length.to_f
+               return res
+       end
+
+       # Json representation of `self`
+       fun json: JsonObject do
+               var obj = new JsonObject
+               obj["mentity"] = mentity.full_name
+               obj["ratings"] = new JsonArray.from(ratings)
+               obj["mean"] = mean
+               return obj
+       end
+
+       redef fun to_json do return json.to_json
+end
+
+# Rating value of a MEntity
+class MEntityRating
+       super Jsonable
+
+       # MEntity this rating is about
+       var mentity: String
+
+       # Rating value (between 1 and 5)
+       var rating: Int
+
+       # Timestamp of this rating
+       var timestamp: Int
+
+       # Init this rating value from a JsonObject
+       init from_json(obj: JsonObject) do
+               init(obj["mentity"].as(String), obj["rating"].as(Int), obj["timestamp"].as(Int))
+       end
+
+       # Translate this rating value to a JsonObject
+       fun json: JsonObject do
+               var obj = new JsonObject
+               obj["mentity"] = mentity
+               obj["rating"] = rating
+               obj["timestamp"] = timestamp
+               return obj
+       end
+
+       redef fun to_json do return json.to_json
+end
similarity index 99%
rename from src/web/model_api.nit
rename to src/web/api_model.nit
index 6a7d0a5..f8c6eeb 100644 (file)
@@ -12,7 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-module model_api
+module api_model
 
 import web_base
 import highlight
index e82a0f5..c6bb476 100644 (file)
@@ -15,7 +15,8 @@
 # Components required to build a web server about the nit model.
 module web
 
-import model_api
+import api_model
 import api_catalog
 import api_graph
 import api_docdown
+import api_feedback