contrib/nitrpg: `save_in` uses string key instead of GameEntity
[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 for this entity.
30 fun stats: GameStats is abstract
31
32 # Load statistics for this `MEntity` if any.
33 fun load_statistics: nullable GameStats do
34 var key = self.key / "statistics"
35 if not game.store.has_key(key) then return null
36 var json = game.store.load_object(key)
37 return new GameStats.from_json(game, json)
38 end
39 end
40
41 redef class Game
42
43 redef var stats is lazy do
44 return load_statistics or else new GameStats(game)
45 end
46
47 redef fun save do
48 super
49 stats.save_in(self.key)
50 end
51
52 redef fun pretty do
53 var res = new FlatBuffer
54 res.append super
55 res.append "# stats:\n"
56 res.append stats.pretty
57 return res.write_to_string
58 end
59 end
60
61 redef class Player
62
63 redef var stats is lazy do
64 return load_statistics or else new GameStats(game)
65 end
66
67 redef fun save do
68 super
69 stats.save_in(self.key)
70 end
71
72 redef fun pretty do
73 var res = new FlatBuffer
74 res.append super
75 res.append "# stats:\n"
76 res.append stats.pretty
77 return res.write_to_string
78 end
79 end
80
81 # Game statistics structure that can be saved as a `GameEntity`.
82 class GameStats
83 super GameEntity
84 super Counter[String]
85
86 redef var game
87
88 redef var key = "statistics"
89
90 # Load `self` from saved data
91 init from_json(game: Game, json: JsonObject) do
92 self.game = game
93 for k, v in json do self[k] = v.as(Int)
94 end
95
96 redef fun to_json do
97 var obj = new JsonObject
98 for k, v in self do obj[k] = v
99 return obj
100 end
101
102 # Decrements the value of `key` statistic entry by 1.
103 fun dec(key: String) do
104 if not has_key(key) then
105 self[key] = 0
106 else
107 self[key] -= 1
108 end
109 end
110
111 redef fun pretty do
112 var res = new FlatBuffer
113 for k, v in self do
114 res.append "# {v} {k}\n"
115 end
116 return res.write_to_string
117 end
118 end
119
120 # `GameReactor` that computes statistics about the game.
121 class StatisticsReactor
122 super GameReactor
123
124 redef fun react_event(game, e) do e.react_stats_event(game)
125 end
126
127 redef class GithubEvent
128 # Reacts to a statistics related event.
129 #
130 # Called by `StatisticsReactor::react_event`.
131 # No-op by default.
132 private fun react_stats_event(game: Game) do end
133 end
134
135 redef class IssuesEvent
136
137 # Count opened and closed issues.
138 redef fun react_stats_event(game) do
139 var player = issue.user.player(game)
140 if action == "opened" then
141 game.stats.inc("issues")
142 game.stats.inc("issues_open")
143 game.save
144 player.stats.inc("issues")
145 player.stats.inc("issues_open")
146 player.save
147 else if action == "reopened" then
148 game.stats.inc("issues_open")
149 game.save
150 player.stats.inc("issues_open")
151 player.save
152 else if action == "closed" then
153 game.stats.dec("issues_open")
154 game.save
155 player.stats.dec("issues_open")
156 player.save
157 end
158 end
159 end
160
161 redef class PullRequestEvent
162
163 # Count opened and closed pull requests.
164 redef fun react_stats_event(game) do
165 var player = pull.user.player(game)
166 if action == "opened" then
167 game.stats.inc("pulls")
168 game.stats.inc("pulls_open")
169 game.save
170 player.stats.inc("pulls")
171 player.stats.inc("pulls_open")
172 player.save
173 else if action == "reopened" then
174 game.stats.inc("pulls_open")
175 game.save
176 player.stats.inc("pulls_open")
177 player.save
178 else if action == "closed" then
179 game.stats.dec("pulls_open")
180 player.stats.dec("pulls_open")
181 if pull.merged then
182 game.stats["commits"] += pull.commits
183 player.stats["commits"] += pull.commits
184 end
185 game.save
186 player.save
187 end
188 end
189 end
190
191 redef class IssueCommentEvent
192
193 # Count posted comments
194 redef fun react_stats_event(game) do
195 if action == "created" then
196 var player = comment.user.player(game)
197 game.stats.inc("comments")
198 player.stats.inc("comments")
199 # FIXME use a more precise way to locate reviews
200 if comment.has_ok_review then
201 game.stats.inc("reviews")
202 player.stats.inc("reviews")
203 end
204 game.save
205 player.save
206 end
207 end
208 end
209
210 redef class IssueComment
211 # Does this comment contain a "+1"?
212 fun has_ok_review: Bool do return body.has("\\+1\\b".to_re)
213 end