Source file contrib.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
(** *)
module S = Statocaml
module T = Statocaml_profile.T
module W = Ocf.Wrapper
module GH = Statocaml_github
module XR = Xtmpl.Rewrite
module Make (P:T.S) =
struct
let point_char = "⬤"
let xml_text_node_contents xmls =
match List.find_opt
(function XR.E { name = ("","text") } -> true | _ -> false)
xmls
with
| None -> None
| Some (E node) -> Some (XR.text_of_xmls node.subs)
| Some _ -> assert false
let svg_add_info =
let re = Re.(compile (seq [
group ~name:"name" (rep1 (compl [ char ','])) ;
char ',' ;
group ~name:"id" (rep1 digit) ;
]))
in
let gnames = Re.group_names re in
let get gname =
let n = List.assoc gname gnames in
fun g -> Re.Group.get g n
in
let get_name = get "name" in
let get_id = get "id" in
let rec iter f_href = function
| (XR.E ({ name = ("","g") ; subs } as node)) as x ->
(
match xml_text_node_contents subs with
| None ->
let subs = List.map (iter f_href) subs in
XR.E { node with subs }
| Some text ->
match Re.exec_opt re text with
| None -> x
| Some g ->
let name = get_name g in
let id = int_of_string (get_id g) in
let href = Iri.to_string (f_href id) in
let subs = [ XR.node ("","a")
~atts:(XR.atts_one ("xlink","href") [XR.cdata href])
[
XR.node ("","title") [XR.cdata name] ;
XR.node ("","text") [XR.cdata point_char] ;
]
]
in
XR.E { node with subs }
)
| XR.E node ->
let subs = List.map (iter f_href) node.subs in
XR.E { node with subs }
| x -> x
in
fun f_href svg -> List.map (iter f_href) svg
let prepare_data ?after ?before ?release data =
let pred : (GH.Types.issue -> bool) * (P.pr -> bool) =
let f_true = fun _ -> true in
let f_true2 = f_true, f_true in
match after, before, release, data.P.orig_changelog with
| None, None, None, _ -> f_true2
| _, _, Some _release, None ->
failwith "Filtering according to release but data has no changelog"
| _, _, None, _ ->
let pafter = match after with
| None -> f_true
| Some than -> (fun i -> Ptime.is_later ~than i.GH.Types.created_at)
in
let pbefore = match before with
| None -> f_true
| Some than -> (fun i -> Ptime.is_earlier ~than i.GH.Types.created_at)
in
(fun i -> pafter i && pbefore i),
(fun pr -> pafter pr.issue && pbefore pr.issue)
| _, _, Some release, Some changelog ->
match Changelog.release_of_version changelog release with
| None ->
S.Log.err (fun m -> m "No release %S in changelog" release);
(fun _ -> false), (fun _ -> false)
| Some rel ->
let d = S.Period.(Map.find All data.stats) in
let f_ref (issues,prs) id =
match S.Imap.find_opt id d.g_issues with
| Some _ -> (S.Iset.add id issues, prs)
| None ->
match S.Imap.find_opt id d.g_prs with
| Some _ -> (issues, S.Iset.add id prs)
| None ->
S.Log.err (fun m -> m "No issue or PR %d" id);
(issues, prs)
in
let f_entry acc entry = List.fold_left f_ref acc entry.Changelog.references in
let f_cat _ entries acc = List.fold_left f_entry acc entries in
let issues, prs = S.Smap.fold f_cat rel.categories (S.Iset.empty, S.Iset.empty) in
(fun (i:GH.Types.issue) -> S.Iset.mem i.GH.Types.number issues),
(fun pr -> S.Iset.mem pr.P.issue.number prs)
in
let pred_issues, pred_prs = pred in
let period_issues, period_prs =
let d = S.Period.(Map.find All data.stats) in
let filter pred map =
S.Imap.fold (fun id x acc -> if pred x then S.Iset.add id acc else acc)
map S.Iset.empty
in
filter pred_issues d.g_issues,
filter pred_prs d.g_prs
in
let profiles = P.profiles data in
let f _id p acc =
match P.opt_dated p S.Period.All with
| None -> acc
| Some d ->
let filter in_set set =
S.Iset.filter (fun id -> S.Iset.mem id in_set) set
in
let i_created = filter period_issues d.issues_created in
let i_reviewed = filter period_issues d.issues_reviewed in
let pr_created = filter period_prs d.prs_created in
let pr_reviewed = filter period_prs
(if data.has_changelog
then d.prs_reviewed_from_changelog
else d.prs_reviewed)
in
(p, i_created, i_reviewed, pr_created, pr_reviewed) :: acc
in
let list = S.Imap.fold f profiles [] in
list
let plot (gp:Plot.gp) ?(with_node_info=false) ?after ?before ?period_name ?release (data: P.t) =
let pdata = prepare_data ?after ?before ?release data in
let pdata =
let c = S.Iset.cardinal in
List.filter_map
(fun (p,ic,ir,pc,pr) ->
let created = c ic + c pc in
let reviewed = c ir + c pr in
if created = 0 && reviewed = 0 then
None
else
Some (float created, float reviewed, (created+reviewed) mod 12,
if with_node_info
then Printf.sprintf "%s (%d/%d),%d" p.P.name reviewed created p.id
else point_char))
pdata
in
ignore(period_name);
let xmax, ymax = List.fold_left
(fun (x,y) (created,reviewed,_,_) -> (max x created, max y reviewed))
(1., 1.) pdata
in
match pdata with
| [] -> Lwt.return_unit
| _ ->
Plot.p gp "set encoding utf8";
Plot.p gp "set xlabel \"number of authored contributions\"" ;
Plot.p gp "set ylabel \"number of reviews\"" ;
Plot.p gp "xmax = %f" xmax ;
Plot.p gp "ymax = %f" ymax ;
Plot.p gp "xzero = 0.7" ;
Plot.p gp "yzero = 0.7" ;
Plot.p gp "set xrange [0.5:(1+log10(xmax))]" ;
Plot.p gp "set yrange [0.5:(1+log10(ymax))]" ;
Plot.p gp "unset ytics
set ytics 1 add (\"0\" yzero, \"1\" 1)
set for [i=2:(1+log10(ymax+1))] ytics add (sprintf(\"%%g\",10**(i-1)) i) # Add major tics
set for [i=1:(1+log10(ymax+1))] for [j=2:9] ytics add (\"\" log10(10**i*j) 1) # Add minor tics
unset xtics
set xtics 1 add (\"0\" xzero, \"1\" 1)
set for [i=2:(1+log10(xmax+1))] xtics add (sprintf(\"%%g\",10**(i-1)) i) # Add major tics
set for [i=1:(1+log10(xmax+1))] for [j=2:9] xtics add (\"\" log10(10**i*j) 1) # Add minor tics
set grid
set grid mxtics mytics";
Plot.define_float_float_int_string_data gp "data" pdata ;
Plot.p gp "set dataf sep \"\\t\"";
Plot.p gp "f(x) = (x)";
Plot.p gp "plot \
for[i=0:%d] $data every 1:1:i:0:i:0 \
using ($1<1?xzero+(1-xzero)*$1:1 + log10($1)):($2<1?yzero+(1-yzero)*$2:1 + log10($2)):(stringcolumn(4)) with labels tc lt (i %% 12 + 1) notitle, \
f(x) notitle with lines dt 7 lc rgb \"grey\""
(List.length pdata - 1);
S.Log.info (fun m -> m "Contrib.plot:%s" (Plot.code gp));
Lwt.return_unit
type param =
{ after: Ptime.t option [@ocf W.option S.Types.ptime_date_wrapper, None];
before: Ptime.t option [@ocf W.option S.Types.ptime_date_wrapper, None];
period_name : string option [@ocf W.option W.string, None];
release : string option [@ocf W.option W.string, None];
with_node_info : bool [@ocf W.bool, false] ;
}[@@ocf]
let wrap_param f =
Json.to_plotter param_wrapper
(fun gp p -> f gp
?with_node_info:(Some p.with_node_info)
?after:p.after ?before:p.before
?period_name:p.period_name ?release:p.release
)
let plot_json = wrap_param plot
end