Merge: model: Fix a spelling mistake
authorJean Privat <jean@pryen.org>
Mon, 4 Jul 2016 19:16:55 +0000 (15:16 -0400)
committerJean Privat <jean@pryen.org>
Mon, 4 Jul 2016 19:16:55 +0000 (15:16 -0400)
Signed-off-by: Jean-Christophe Beaupré <jcbrinfo@users.noreply.github.com>

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

15 files changed:
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/examples/nitwebcrawl.nit [new file with mode: 0644]
src/model/model.nit
src/model/model_collect.nit
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
tests/sav/nitwebcrawl.res [new file with mode: 0644]

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
diff --git a/src/examples/nitwebcrawl.nit b/src/examples/nitwebcrawl.nit
new file mode 100644 (file)
index 0000000..1187476
--- /dev/null
@@ -0,0 +1,90 @@
+# 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.
+
+# Crawler on the nitweb web API
+module nitwebcrawl
+
+import json
+
+# Download a HTTP resource
+fun curl(url: String): String do
+       # TODO: use curl.nit
+       var p = new ProcessReader("curl", "-s", url)
+       var res = p.read_all
+       p.close
+       p.wait
+       # TODO: process HTTP error codes
+       if p.status != 0 then
+               print "Error with {url}"
+       end
+       return res
+end
+
+# Recursively collect all string values in a json value associated to a given key.
+fun search_json(json: nullable Jsonable, key: String, result: nullable Array[String]): Array[String]
+do
+       if result == null then result = new Array[String]
+       if json isa JsonObject then
+               for k, v in json do
+                       search_json(v, key, result)
+               end
+               var v = json.get_or_null(key)
+               if v != null then
+                       assert v isa String
+                       result.add v
+               end
+       else if json isa JsonArray then
+               for e in json do search_json(e, key, result)
+       else if json == null or json isa String or json isa Int or json isa Bool or json isa Float then
+               # nop
+       else
+               print json.class_name
+               abort
+       end
+       return result
+end
+
+var server = "http://localhost:3000"
+var todo = ["/api/entity/core"]
+
+if args.not_empty then
+       server = args.pop
+       if args.not_empty then
+               todo.clear
+               todo.add_all args
+       end
+end
+
+var seen = new Set[String]
+seen.add_all todo
+
+var cpt = 0
+while todo.not_empty do
+       cpt += 1
+       var url = todo.pop
+       url = server + url
+       print "process {url}. {cpt}+{todo.length}/{seen.length}"
+
+       var js = curl(url)
+       var txt = js.parse_json
+       if txt isa Error then
+               print "{url}: {txt.message}"
+               continue
+       end
+       for x in search_json(txt, "api_url") do
+               if seen.has(x) then continue
+               todo.add x
+               seen.add x
+       end
+end
index 573b52a..5349485 100644 (file)
@@ -75,7 +75,7 @@ redef class Model
        # Collections of classes grouped by their short name
        private var mclasses_by_name = new MultiHashMap[String, MClass]
 
-       # Return all class named `name`.
+       # Return all classes named `name`.
        #
        # If such a class does not exist, null is returned
        # (instead of an empty array)
@@ -301,14 +301,14 @@ redef class MModule
                                cladef.add_in_hierarchy
                                return c
                        end
-                       print("Fatal Error: no primitive class {name} in {self}")
+                       print_error("Fatal Error: no primitive class {name} in {self}")
                        exit(1)
                        abort
                end
                if cla.length != 1 then
                        var msg = "Fatal Error: more than one primitive class {name} in {self}:"
                        for c in cla do msg += " {c.full_name}"
-                       print msg
+                       print_error msg
                        #exit(1)
                end
                return cla.first
@@ -327,7 +327,7 @@ redef class MModule
                        if res == null then
                                res = mprop
                        else if res != mprop then
-                               print("Fatal Error: ambigous property name '{name}'; conflict between {mprop.full_name} and {res.full_name}")
+                               print_error("Fatal Error: ambigous property name '{name}'; conflict between {mprop.full_name} and {res.full_name}")
                                abort
                        end
                end
@@ -836,7 +836,7 @@ abstract class MType
                        return true
                end
 
-               assert sub isa MClassType else print "{sub} <? {sub}" # It is the only remaining type
+               assert sub isa MClassType else print_error "{sub} <? {sup}" # It is the only remaining type
 
                # Handle sup-type when the sub-type is class-based (other cases must have be identified before).
                if sup isa MFormalType or sup isa MNullType or sup isa MBottomType then
@@ -844,7 +844,7 @@ abstract class MType
                        return false
                end
 
-               assert sup isa MClassType else print "got {sup} {sub.inspect}" # It is the only remaining type
+               assert sup isa MClassType else print_error "got {sup} {sub.inspect}" # It is the only remaining type
 
                # Now both are MClassType, we need to dig
 
@@ -2136,7 +2136,7 @@ abstract class MProperty
                        end
                end
                if res.is_empty then
-                       print "All lost! {candidates.join(", ")}"
+                       print_error "All lost! {candidates.join(", ")}"
                        # FIXME: should be abort!
                end
                return res
index a4f530c..ab1296c 100644 (file)
@@ -25,7 +25,7 @@
 #
 # This is usefull for tools that need a global view of a model like `nitdoc`,
 # `nitx` or `nituml`.
-# It shoul not be used for compiling stuffs like computing VFT, where the listed
+# It should not be used for compiling stuffs like computing VFT, where the listed
 # entities could not be reachable depending on the modules really imported.
 module model_collect
 
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
diff --git a/tests/sav/nitwebcrawl.res b/tests/sav/nitwebcrawl.res
new file mode 100644 (file)
index 0000000..caa407f
--- /dev/null
@@ -0,0 +1,3 @@
+process http://localhost:3000/api/entity/core. 1+0/1
+Error with http://localhost:3000/api/entity/core
+http://localhost:3000/api/entity/core: Unexpected Eof; is acceptable instead: value