153b5cd0329d38a314fa920b62a4ec666dd5a599
[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 save do
38 super
39 stats.save_in(self.key)
40 end
41
42 redef fun pretty do
43 var res = new FlatBuffer
44 res.append super
45 res.append "# stats:\n"
46 res.append stats.pretty
47 return res.write_to_string
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 save do
56 super
57 stats.save_in(self.key)
58 end
59
60 redef fun nitcoins do return stats["nitcoins"]
61 redef fun nitcoins=(nc) do stats["nitcoins"] = nc
62
63 redef fun pretty do
64 var res = new FlatBuffer
65 res.append super
66 res.append "# stats:\n"
67 res.append stats.pretty
68 return res.write_to_string
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 key = owner.key / self.key / period
103 if not game.store.has_key(key) then
104 return new GameStats(game, period, owner)
105 end
106 var json = game.store.load_object(key)
107 return new GameStats.from_json(game, period, owner, json)
108 end
109
110 redef fun [](key) do return overall[key]
111
112 redef fun []=(key, value) do
113 overall[key] = value
114 yearly[key] = value
115 monthly[key] = value
116 daily[key] = value
117 weekly[key] = value
118 end
119
120 redef fun inc(e) do
121 overall.inc(e)
122 yearly.inc(e)
123 monthly.inc(e)
124 daily.inc(e)
125 weekly.inc(e)
126 end
127
128 redef fun dec(e) do
129 overall.dec(e)
130 yearly.dec(e)
131 monthly.dec(e)
132 daily.dec(e)
133 weekly.dec(e)
134 end
135
136 redef fun save_in(key) do
137 overall.save_in(key / self.key)
138 yearly.save_in(key / self.key)
139 monthly.save_in(key / self.key)
140 daily.save_in(key / self.key)
141 weekly.save_in(key / self.key)
142 end
143
144 redef fun pretty do return overall.pretty
145 end
146
147 # Game statistics structure that can be saved as a `GameEntity`.
148 class GameStats
149 super GameEntity
150 super Counter[String]
151
152 redef var game
153
154 # The period these stats are about.
155 var period: String
156
157 # The game entity these stats are about.
158 var owner: GameEntity
159
160 redef var key = "{owner.key}-{period}" is lazy
161
162 # Load `self` from saved data.
163 init from_json(game: Game, period: String, owner: GameEntity, json: JsonObject) do
164 var values = json.get_or_null("values")
165 if not values isa JsonObject then return
166 for k, v in values do self[k] = v.as(Int)
167 end
168
169 redef fun to_json do
170 var obj = super
171 obj["period"] = period
172 obj["owner"] = owner.key
173 var values = new JsonObject
174 values.recover_with(self)
175 obj["values"] = values
176 return obj
177 end
178
179 redef fun pretty do
180 var res = new FlatBuffer
181 for k, v in self do
182 res.append "# {v} {k}\n"
183 end
184 return res.write_to_string
185 end
186 end
187
188 # `GameReactor` that computes statistics about the game.
189 class StatisticsReactor
190 super GameReactor
191
192 redef fun react_event(game, e) do e.react_stats_event(game)
193 end
194
195 redef class GithubEvent
196 # Reacts to a statistics related event.
197 #
198 # Called by `StatisticsReactor::react_event`.
199 # No-op by default.
200 private fun react_stats_event(game: Game) do end
201 end
202
203 redef class IssuesEvent
204
205 # Count opened and closed issues.
206 redef fun react_stats_event(game) do
207 var player = issue.user.player(game)
208 if action == "opened" then
209 game.stats.inc("issues")
210 game.stats.inc("issues_open")
211 game.save
212 player.stats.inc("issues")
213 player.stats.inc("issues_open")
214 player.save
215 else if action == "reopened" then
216 game.stats.inc("issues_open")
217 game.save
218 player.stats.inc("issues_open")
219 player.save
220 else if action == "closed" then
221 game.stats.dec("issues_open")
222 game.save
223 player.stats.dec("issues_open")
224 player.save
225 end
226 end
227 end
228
229 redef class PullRequestEvent
230
231 # Count opened and closed pull requests.
232 redef fun react_stats_event(game) do
233 var player = pull.user.player(game)
234 if action == "opened" then
235 game.stats.inc("pulls")
236 game.stats.inc("pulls_open")
237 game.save
238 player.stats.inc("pulls")
239 player.stats.inc("pulls_open")
240 player.save
241 else if action == "reopened" then
242 game.stats.inc("pulls_open")
243 game.save
244 player.stats.inc("pulls_open")
245 player.save
246 else if action == "closed" then
247 game.stats.dec("pulls_open")
248 player.stats.dec("pulls_open")
249 if pull.merged then
250 game.stats["commits"] += pull.commits
251 player.stats["commits"] += pull.commits
252 end
253 game.save
254 player.save
255 end
256 end
257 end
258
259 redef class IssueCommentEvent
260
261 # Count posted comments
262 redef fun react_stats_event(game) do
263 if action == "created" then
264 var player = comment.user.player(game)
265 game.stats.inc("comments")
266 player.stats.inc("comments")
267 # FIXME use a more precise way to locate reviews
268 if comment.is_ack then
269 game.stats.inc("reviews")
270 player.stats.inc("reviews")
271 end
272 game.save
273 player.save
274 end
275 end
276 end