Merge: Mock Github API tests
[nit.git] / contrib / nitrpg / src / statistics.nit
index 65d5a69..764616f 100644 (file)
@@ -22,24 +22,38 @@ module statistics
 
 import game
 import github::hooks
+import counter
+
+redef class GameEntity
+
+       # Statistics manager for this entity.
+       fun stats: GameStatsManager is abstract
+end
 
 redef class Game
 
-       # Statistics for this game instance.
-       var stats = new GameStats
+       redef var stats is lazy do return new GameStatsManager(game, self)
 
-       redef fun from_json(json) do
-               super
-               if json.has_key("statistics") then
-                       stats.from_json(json["statistics"].as(JsonObject))
-               end
+       redef fun pretty do
+               var res = new FlatBuffer
+               res.append super
+               res.append "# stats:\n"
+               res.append stats.pretty
+               return res.write_to_string
        end
 
-       redef fun to_json do
-               var obj = super
-               obj["statistics"] = stats.to_json
-               return obj
+       redef fun save do
+               super
+               stats.save
        end
+end
+
+redef class Player
+
+       redef var stats is lazy do return new GameStatsManager(game, self)
+
+       redef fun nitcoins do return stats["nitcoins"]
+       redef fun nitcoins=(nc) do stats["nitcoins"] = nc
 
        redef fun pretty do
                var res = new FlatBuffer
@@ -49,54 +63,128 @@ redef class Game
                return res.write_to_string
        end
 
-       redef fun clear do
+       redef fun save do
                super
-               stats.clear
+               stats.save
        end
 end
 
-# Game statistics structure that can be saved as a `GameEntity`.
-class GameStats
+# Store game stats for defined period.
+class GameStatsManager
        super GameEntity
+       super Counter[String]
+
+       redef var game
 
-       redef var key = "statistics"
+       # The GameEntity monitored by these statistics.
+       var owner: GameEntity
 
-       # Used internally to stats values.
-       private var stats = new HashMap[String, Int]
+       # Current date to extract stats
+       private var date = new Tm.gmtime
 
-       init do clear
+       # Returns the `GameStats` instance for the overall statistics.
+       var overall: GameStats = load_stats_for("all") is lazy
 
-       # Load `self` from saved data
-       private fun from_json(json: JsonObject) do
-               for k, v in json do stats[k] = v.as(Int)
+       # Returns the `GameStats` instance for the current year statistics.
+       var yearly: GameStats = load_stats_for(date.strftime("%Y")) is lazy
+
+       # Returns the `GameStats` instance for the current month statistics.
+       var monthly: GameStats = load_stats_for(date.strftime("%Y-%m")) is lazy
+
+       # Returns the `GameStats` instance for the current day statistics.
+       var daily: GameStats = load_stats_for(date.strftime("%Y-%m-%d")) is lazy
+
+       # Returns the `GameStats` instance for the current week statistics.
+       var weekly: GameStats = load_stats_for(date.strftime("%Y-W%U")) is lazy
+
+       # Load statistics for a `period` key.
+       fun load_stats_for(period: String): GameStats do
+               var req = new JsonObject
+               req["period"] = period
+               req["owner"] = owner.key
+               var obj = game.db.collection("statistics").find(req)
+               if obj isa JsonObject then
+                       return new GameStats.from_json(game, period, owner, obj)
+               else
+                       return new GameStats(game, period, owner)
+               end
        end
 
-       redef fun to_json do
-               var obj = new JsonObject
-               for k, v in stats do obj[k] = v
-               return obj
+       redef fun [](key) do return overall[key]
+
+       redef fun []=(key, value) do
+               overall[key] = value
+               yearly[key] = value
+               monthly[key] = value
+               daily[key] = value
+               weekly[key] = value
+       end
+
+       redef fun inc(e) do
+               overall.inc(e)
+               yearly.inc(e)
+               monthly.inc(e)
+               daily.inc(e)
+               weekly.inc(e)
+       end
+
+       redef fun dec(e) do
+               overall.dec(e)
+               yearly.dec(e)
+               monthly.dec(e)
+               daily.dec(e)
+               weekly.dec(e)
+       end
+
+       redef fun save do
+               overall.save
+               yearly.save
+               monthly.save
+               daily.save
+               weekly.save
        end
 
-       # Retrieves the current value of `key` statistic entry.
-       fun [](key: String): Int do return stats[key]
+       redef fun pretty do return overall.pretty
+end
+
+# Game statistics structure that can be saved as a `GameEntity`.
+class GameStats
+       super GameEntity
+       super Counter[String]
+
+       redef var game
 
-       # Increments the value of `key` statistic entry by 1.
-       fun incr(key: String) do stats[key] += 1
+       redef var collection_name = "statistics"
 
-       # Decrements the value of `key` statistic entry by 1.
-       fun decr(key: String) do stats[key] -= 1
+       # The period these stats are about.
+       var period: String
 
-       # Reset game stats.
-       fun clear do
-               stats["issues"] = 0
-               stats["issues_open"] = 0
-               stats["pulls"] = 0
-               stats["pulls_open"] = 0
+       # The game entity these stats are about.
+       var owner: GameEntity
+
+       redef var key = "{owner.key}-{period}" is lazy
+
+       # Load `self` from saved data.
+       init from_json(game: Game, period: String, owner: GameEntity, json: JsonObject) do
+               init(game, period, owner)
+               var values = json.get_or_null("values")
+               if not values isa JsonObject then return
+               for k, v in values do self[k] = v.as(Int)
+       end
+
+       redef fun to_json_object do
+               var obj = super
+               obj["period"] = period
+               obj["owner"] = owner.key
+               var values = new JsonObject
+               values.add_all(self)
+               obj["values"] = values
+               return obj
        end
 
        redef fun pretty do
                var res = new FlatBuffer
-               for k, v in stats do
+               for k, v in self do
                        res.append "# {v} {k}\n"
                end
                return res.write_to_string
@@ -107,11 +195,7 @@ end
 class StatisticsReactor
        super GameReactor
 
-       redef fun react_event(game, e) do
-               super # log events
-               e.react_stats_event(game)
-               game.save
-       end
+       redef fun react_event(game, e) do e.react_stats_event(game)
 end
 
 redef class GithubEvent
@@ -126,13 +210,24 @@ redef class IssuesEvent
 
        # Count opened and closed issues.
        redef fun react_stats_event(game) do
+               var player = issue.user.player(game)
                if action == "opened" then
-                       game.stats.incr("issues")
-                       game.stats.incr("issues_open")
+                       game.stats.inc("issues")
+                       game.stats.inc("issues_open")
+                       game.save
+                       player.stats.inc("issues")
+                       player.stats.inc("issues_open")
+                       player.save
                else if action == "reopened" then
-                       game.stats.incr("issues_open")
+                       game.stats.inc("issues_open")
+                       game.save
+                       player.stats.inc("issues_open")
+                       player.save
                else if action == "closed" then
-                       game.stats.decr("issues_open")
+                       game.stats.dec("issues_open")
+                       game.save
+                       player.stats.dec("issues_open")
+                       player.save
                end
        end
 end
@@ -141,13 +236,47 @@ redef class PullRequestEvent
 
        # Count opened and closed pull requests.
        redef fun react_stats_event(game) do
+               var player = pull.user.player(game)
                if action == "opened" then
-                       game.stats.incr("pulls")
-                       game.stats.incr("pulls_open")
+                       game.stats.inc("pulls")
+                       game.stats.inc("pulls_open")
+                       game.save
+                       player.stats.inc("pulls")
+                       player.stats.inc("pulls_open")
+                       player.save
                else if action == "reopened" then
-                       game.stats.incr("pulls_open")
+                       game.stats.inc("pulls_open")
+                       game.save
+                       player.stats.inc("pulls_open")
+                       player.save
                else if action == "closed" then
-                       game.stats.decr("pulls_open")
+                       game.stats.dec("pulls_open")
+                       player.stats.dec("pulls_open")
+                       if pull.merged then
+                               game.stats["commits"] += pull.commits
+                               player.stats["commits"] += pull.commits
+                       end
+                       game.save
+                       player.save
+               end
+       end
+end
+
+redef class IssueCommentEvent
+
+       # Count posted comments
+       redef fun react_stats_event(game) do
+               if action == "created" then
+                       var player = comment.user.player(game)
+                       game.stats.inc("comments")
+                       player.stats.inc("comments")
+                       # FIXME use a more precise way to locate reviews
+                       if comment.is_ack then
+                               game.stats.inc("reviews")
+                               player.stats.inc("reviews")
+                       end
+                       game.save
+                       player.save
                end
        end
 end