Merge: concurrent_collections: Add implementation of has method
[nit.git] / contrib / nitrpg / src / statistics.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2014-2015 Alexandre Terrasa <alexandre@moz-code.org>
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 # Statistics about the Game.
18 #
19 # This module uses `GameReactor` to extract statistics about the game from
20 # triggered `Github::Event`.
21 module statistics
22
23 import game
24 import github::hooks
25 import counter
26
27 redef class GameEntity
28
29 # Statistics manager for this entity.
30 fun stats: GameStatsManager is abstract
31 end
32
33 redef class Game
34
35 redef var stats is lazy do return new GameStatsManager(game, self)
36
37 redef fun pretty do
38 var res = new FlatBuffer
39 res.append super
40 res.append "# stats:\n"
41 res.append stats.pretty
42 return res.write_to_string
43 end
44
45 redef fun save do
46 super
47 stats.save
48 end
49 end
50
51 redef class Player
52
53 redef var stats is lazy do return new GameStatsManager(game, self)
54
55 redef fun nitcoins do return stats["nitcoins"]
56 redef fun nitcoins=(nc) do stats["nitcoins"] = nc
57
58 redef fun pretty do
59 var res = new FlatBuffer
60 res.append super
61 res.append "# stats:\n"
62 res.append stats.pretty
63 return res.write_to_string
64 end
65
66 redef fun save do
67 super
68 stats.save
69 end
70 end
71
72 # Store game stats for defined period.
73 class GameStatsManager
74 super GameEntity
75 super Counter[String]
76
77 redef var game
78
79 # The GameEntity monitored by these statistics.
80 var owner: GameEntity
81
82 # Current date to extract stats
83 private var date = new Tm.gmtime
84
85 # Returns the `GameStats` instance for the overall statistics.
86 var overall: GameStats = load_stats_for("all") is lazy
87
88 # Returns the `GameStats` instance for the current year statistics.
89 var yearly: GameStats = load_stats_for(date.strftime("%Y")) is lazy
90
91 # Returns the `GameStats` instance for the current month statistics.
92 var monthly: GameStats = load_stats_for(date.strftime("%Y-%m")) is lazy
93
94 # Returns the `GameStats` instance for the current day statistics.
95 var daily: GameStats = load_stats_for(date.strftime("%Y-%m-%d")) is lazy
96
97 # Returns the `GameStats` instance for the current week statistics.
98 var weekly: GameStats = load_stats_for(date.strftime("%Y-W%U")) is lazy
99
100 # Load statistics for a `period` key.
101 fun load_stats_for(period: String): GameStats do
102 var req = new JsonObject
103 req["period"] = period
104 req["owner"] = owner.key
105 var obj = game.db.collection("statistics").find(req)
106 if obj isa JsonObject then
107 return new GameStats.from_json(game, period, owner, obj)
108 else
109 return new GameStats(game, period, owner)
110 end
111 end
112
113 redef fun [](key) do return overall[key]
114
115 redef fun []=(key, value) do
116 overall[key] = value
117 yearly[key] = value
118 monthly[key] = value
119 daily[key] = value
120 weekly[key] = value
121 end
122
123 redef fun inc(e) do
124 overall.inc(e)
125 yearly.inc(e)
126 monthly.inc(e)
127 daily.inc(e)
128 weekly.inc(e)
129 end
130
131 redef fun dec(e) do
132 overall.dec(e)
133 yearly.dec(e)
134 monthly.dec(e)
135 daily.dec(e)
136 weekly.dec(e)
137 end
138
139 redef fun save do
140 overall.save
141 yearly.save
142 monthly.save
143 daily.save
144 weekly.save
145 end
146
147 redef fun pretty do return overall.pretty
148 end
149
150 # Game statistics structure that can be saved as a `GameEntity`.
151 class GameStats
152 super GameEntity
153 super Counter[String]
154
155 redef var game
156
157 redef var collection_name = "statistics"
158
159 # The period these stats are about.
160 var period: String
161
162 # The game entity these stats are about.
163 var owner: GameEntity
164
165 redef var key = "{owner.key}-{period}" is lazy
166
167 # Load `self` from saved data.
168 init from_json(game: Game, period: String, owner: GameEntity, json: JsonObject) do
169 init(game, period, owner)
170 var values = json.get_or_null("values")
171 if not values isa JsonObject then return
172 for k, v in values do self[k] = v.as(Int)
173 end
174
175 redef fun to_json_object do
176 var obj = super
177 obj["period"] = period
178 obj["owner"] = owner.key
179 var values = new JsonObject
180 values.add_all(self)
181 obj["values"] = values
182 return obj
183 end
184
185 redef fun pretty do
186 var res = new FlatBuffer
187 for k, v in self do
188 res.append "# {v} {k}\n"
189 end
190 return res.write_to_string
191 end
192 end
193
194 # `GameReactor` that computes statistics about the game.
195 class StatisticsReactor
196 super GameReactor
197
198 redef fun react_event(game, e) do e.react_stats_event(game)
199 end
200
201 redef class GithubEvent
202 # Reacts to a statistics related event.
203 #
204 # Called by `StatisticsReactor::react_event`.
205 # No-op by default.
206 private fun react_stats_event(game: Game) do end
207 end
208
209 redef class IssuesEvent
210
211 # Count opened and closed issues.
212 redef fun react_stats_event(game) do
213 var player = issue.user.player(game)
214 if action == "opened" then
215 game.stats.inc("issues")
216 game.stats.inc("issues_open")
217 game.save
218 player.stats.inc("issues")
219 player.stats.inc("issues_open")
220 player.save
221 else if action == "reopened" then
222 game.stats.inc("issues_open")
223 game.save
224 player.stats.inc("issues_open")
225 player.save
226 else if action == "closed" then
227 game.stats.dec("issues_open")
228 game.save
229 player.stats.dec("issues_open")
230 player.save
231 end
232 end
233 end
234
235 redef class PullRequestEvent
236
237 # Count opened and closed pull requests.
238 redef fun react_stats_event(game) do
239 var player = pull.user.player(game)
240 if action == "opened" then
241 game.stats.inc("pulls")
242 game.stats.inc("pulls_open")
243 game.save
244 player.stats.inc("pulls")
245 player.stats.inc("pulls_open")
246 player.save
247 else if action == "reopened" then
248 game.stats.inc("pulls_open")
249 game.save
250 player.stats.inc("pulls_open")
251 player.save
252 else if action == "closed" then
253 game.stats.dec("pulls_open")
254 player.stats.dec("pulls_open")
255 if pull.merged then
256 game.stats["commits"] += pull.commits
257 player.stats["commits"] += pull.commits
258 end
259 game.save
260 player.save
261 end
262 end
263 end
264
265 redef class IssueCommentEvent
266
267 # Count posted comments
268 redef fun react_stats_event(game) do
269 if action == "created" then
270 var player = comment.user.player(game)
271 game.stats.inc("comments")
272 player.stats.inc("comments")
273 # FIXME use a more precise way to locate reviews
274 if comment.is_ack then
275 game.stats.inc("reviews")
276 player.stats.inc("reviews")
277 end
278 game.save
279 player.save
280 end
281 end
282 end