1
- use catalyst_toolbox:: ideascale:: {
2
- build_challenges, build_fund, build_proposals, fetch_all, CustomFieldTags , Scores , Sponsors ,
1
+ use catalyst_toolbox:: {
2
+ community_advisors:: models:: { ReviewRanking , VeteranRankingRow } ,
3
+ ideascale:: {
4
+ build_challenges, build_fund, build_proposals, fetch_all, CustomFieldTags , Scores , Sponsors ,
5
+ } ,
6
+ utils:: csv:: { dump_data_to_csv, load_data_from_csv} ,
3
7
} ;
4
8
use color_eyre:: Report ;
9
+ use itertools:: Itertools ;
5
10
use jcli_lib:: utils:: io as io_utils;
6
11
use jormungandr_lib:: interfaces:: VotePrivacy ;
7
- use std:: collections:: HashSet ;
12
+ use std:: { collections:: HashSet , ffi :: OsStr } ;
8
13
9
14
use structopt:: StructOpt ;
10
15
@@ -17,6 +22,7 @@ use std::path::{Path, PathBuf};
17
22
#[ derive( Debug , StructOpt ) ]
18
23
pub enum Ideascale {
19
24
Import ( Import ) ,
25
+ Filter ( Filter ) ,
20
26
}
21
27
22
28
// We need this type because structopt uses Vec<String> as a special type, so it is not compatible
@@ -75,10 +81,21 @@ pub struct Import {
75
81
stages_filters : Filters ,
76
82
}
77
83
84
+ #[ derive( Debug , StructOpt ) ]
85
+ #[ structopt( rename_all = "kebab" ) ]
86
+ pub struct Filter {
87
+ #[ structopt( long) ]
88
+ input : PathBuf ,
89
+
90
+ #[ structopt( long) ]
91
+ output : Option < PathBuf > ,
92
+ }
93
+
78
94
impl Ideascale {
79
95
pub fn exec ( & self ) -> Result < ( ) , Report > {
80
96
match self {
81
97
Ideascale :: Import ( import) => import. exec ( ) ,
98
+ Ideascale :: Filter ( filter) => filter. exec ( ) ,
82
99
}
83
100
}
84
101
}
@@ -166,6 +183,71 @@ impl Import {
166
183
}
167
184
}
168
185
186
+ impl Filter {
187
+ fn output_file ( input : & Path , output : Option < & Path > ) -> PathBuf {
188
+ if let Some ( output) = output {
189
+ output. to_path_buf ( )
190
+ } else {
191
+ let name = input. file_name ( ) . and_then ( OsStr :: to_str) . unwrap_or ( "" ) ;
192
+ let name = format ! ( "{name}.output" ) ;
193
+ let temp = input. with_file_name ( name) ;
194
+ println ! ( "no output specified, writing to {}" , temp. to_string_lossy( ) ) ;
195
+ temp
196
+ }
197
+ }
198
+
199
+ fn filter_rows ( rows : & [ VeteranRankingRow ] ) -> Vec < VeteranRankingRow > {
200
+ let groups = rows
201
+ . iter ( )
202
+ . group_by ( |row| ( & row. assessor , & row. proposal_id ) ) ;
203
+ groups
204
+ . into_iter ( )
205
+ . flat_map ( |( _, group) | {
206
+ let group = group. collect_vec ( ) ;
207
+ let excellent = group
208
+ . iter ( )
209
+ . filter ( |row| row. score ( ) == ReviewRanking :: Excellent )
210
+ . count ( ) ;
211
+ let good = group
212
+ . iter ( )
213
+ . filter ( |row| row. score ( ) == ReviewRanking :: Good )
214
+ . count ( ) ;
215
+ let filtered = group
216
+ . iter ( )
217
+ . filter ( |row| row. score ( ) == ReviewRanking :: FilteredOut )
218
+ . count ( ) ;
219
+
220
+ use std:: cmp:: max;
221
+ let max_count = max ( excellent, max ( good, filtered) ) ;
222
+
223
+ let include_excellent = excellent == max_count;
224
+ let include_good = good == max_count;
225
+ let include_filtered = filtered == max_count;
226
+
227
+ group. into_iter ( ) . filter ( move |row| match row. score ( ) {
228
+ ReviewRanking :: Excellent => include_excellent,
229
+ ReviewRanking :: Good => include_good,
230
+ ReviewRanking :: FilteredOut => include_filtered,
231
+ ReviewRanking :: NA => true , // if unknown, ignore
232
+ } )
233
+ } )
234
+ . cloned ( )
235
+ . collect ( )
236
+ }
237
+
238
+ fn exec ( & self ) -> Result < ( ) , Report > {
239
+ let Self { input, output } = self ;
240
+ let output = Self :: output_file ( input, output. as_deref ( ) ) ;
241
+
242
+ let rows = load_data_from_csv :: < _ , b',' > ( input) ?;
243
+ let rows = Self :: filter_rows ( & rows) ;
244
+
245
+ dump_data_to_csv ( & rows, & output) ?;
246
+
247
+ Ok ( ( ) )
248
+ }
249
+ }
250
+
169
251
fn dump_content_to_file ( content : impl Serialize , file_path : & Path ) -> Result < ( ) , Report > {
170
252
let writer = jcli_lib:: utils:: io:: open_file_write ( & Some ( file_path) ) ?;
171
253
serde_json:: to_writer_pretty ( writer, & content) ?;
@@ -223,3 +305,36 @@ fn read_sponsors_file(path: &Option<PathBuf>) -> Result<Sponsors, Report> {
223
305
}
224
306
Ok ( sponsors)
225
307
}
308
+
309
+ #[ cfg( test) ]
310
+ mod tests {
311
+ use super :: * ;
312
+
313
+ #[ test]
314
+ fn correctly_formats_output_file_for_filter ( ) {
315
+ let input = PathBuf :: from ( "/foo/bar/file.txt" ) ;
316
+ let output = PathBuf :: from ( "/baz/qux/output.txt" ) ;
317
+
318
+ let result = Filter :: output_file ( & input, Some ( & output) ) ;
319
+ assert_eq ! ( result, output) ;
320
+
321
+ let result = Filter :: output_file ( & input, None ) ;
322
+ assert_eq ! ( result, PathBuf :: from( "/foo/bar/file.txt.output" ) ) ;
323
+ }
324
+
325
+ #[ test]
326
+ fn filters_rows_correctly ( ) {
327
+ use ReviewRanking :: * ;
328
+
329
+ let pid = String :: from ( "pid" ) ;
330
+ let assessor = String :: from ( "assessor" ) ;
331
+ let first = VeteranRankingRow :: new ( pid. clone ( ) , assessor. clone ( ) , "1" . into ( ) , Excellent ) ;
332
+ let second = VeteranRankingRow :: new ( pid. clone ( ) , assessor. clone ( ) , "2" . into ( ) , Excellent ) ;
333
+ let third = VeteranRankingRow :: new ( pid. clone ( ) , assessor. clone ( ) , "3" . into ( ) , Good ) ;
334
+
335
+ let rows = vec ! [ first. clone( ) , second. clone( ) , third] ;
336
+ let expected_rows = vec ! [ first, second] ;
337
+
338
+ assert_eq ! ( Filter :: filter_rows( & rows) , expected_rows) ;
339
+ }
340
+ }
0 commit comments