File Coverage

blib/lib/App/Prove/Elasticsearch/Indexer.pm
Criterion Covered Total %
statement 81 88 92.0
branch 31 36 86.1
condition 10 23 43.4
subroutine 11 12 91.6
pod 5 5 100.0
total 138 164 84.1


line stmt bran cond sub pod time code
1             # ABSTRACT: Define what data is to be uploaded to elasticsearch, and handle it's uploading
2             # PODNAME: App::Prove::Elasticsearch::Indexer
3              
4             package App::Prove::Elasticsearch::Indexer;
5             $App::Prove::Elasticsearch::Indexer::VERSION = '0.001';
6 7     7   198646 use strict;
  7         33  
  7         170  
7 7     7   32 use warnings;
  7         13  
  7         167  
8              
9 7     7   1812 use App::Prove::Elasticsearch::Utils();
  7         19  
  7         130  
10              
11 7     7   2807 use Search::Elasticsearch();
  7         247309  
  7         195  
12 7     7   50 use List::Util 1.33;
  7         118  
  7         7989  
13              
14             our $index = 'testsuite';
15              
16             sub index {
17 3     3 1 17 return $index;
18             }
19              
20             our $max_query_size = 1000;
21             our $e;
22             our $bulk_helper;
23             our $idx;
24              
25             sub check_index {
26 5     5 1 1768 my $conf = shift;
27              
28 5 100       21 my $port = $conf->{'server.port'} ? ':' . $conf->{'server.port'} : '';
29 5 100       24 die "server must be specified" unless $conf->{'server.host'};
30 3 100       11 die("port must be specified") unless $port;
31 2         5 my $serveraddress = "$conf->{'server.host'}$port";
32 2   66     10 $e //= Search::Elasticsearch->new(
33             nodes => $serveraddress,
34             );
35              
36             #XXX for debugging
37             #$e->indices->delete( index => $index );
38              
39 2 100       15 if (!$e->indices->exists(index => $index)) {
40 1         7 $e->indices->create(
41             index => $index,
42             body => {
43             index => {
44             number_of_shards => "3",
45             number_of_replicas => "2",
46             similarity => {default => {type => "classic"}}
47             },
48             analysis => {
49             analyzer => {
50             default => {
51             type => "custom",
52             tokenizer => "whitespace",
53             filter => [
54             'lowercase', 'std_english_stop', 'custom_stop'
55             ]
56             }
57             },
58             filter => {
59             std_english_stop => {
60             type => "stop",
61             stopwords => "_english_"
62             },
63             custom_stop => {
64             type => "stop",
65             stopwords => [ "test", "ok", "not" ]
66             }
67             }
68             },
69             mappings => {
70             testsuite => {
71             properties => {
72             id => {type => "integer"},
73             elapsed => {type => "integer"},
74             occurred => {
75             type => "date",
76             format => "yyyy-MM-dd HH:mm:ss"
77             },
78             executor => {
79             type => "text",
80             analyzer => "default",
81             fielddata => "true",
82             term_vector => "yes",
83             similarity => "classic",
84             fields => {keyword => {type => "keyword"}}
85             },
86             status => {
87             type => "text",
88             analyzer => "default",
89             fielddata => "true",
90             term_vector => "yes",
91             similarity => "classic",
92             fields => {keyword => {type => "keyword"}}
93             },
94             version => {
95             type => "text",
96             analyzer => "default",
97             fielddata => "true",
98             term_vector => "yes",
99             similarity => "classic",
100             fields => {keyword => {type => "keyword"}}
101             },
102             test_version => {
103             type => "text",
104             analyzer => "default",
105             fielddata => "true",
106             term_vector => "yes",
107             similarity => "classic",
108             fields => {keyword => {type => "keyword"}}
109             },
110             platform => {
111             type => "text",
112             analyzer => "default",
113             fielddata => "true",
114             term_vector => "yes",
115             similarity => "classic",
116             fields => {keyword => {type => "keyword"}}
117             },
118             path => {
119             type => "text",
120             analyzer => "default",
121             fielddata => "true",
122             term_vector => "yes",
123             similarity => "classic",
124             fields => {keyword => {type => "keyword"}}
125             },
126             defect => {
127             type => "text",
128             analyzer => "default",
129             fielddata => "true",
130             term_vector => "yes",
131             similarity => "classic",
132             fields => {keyword => {type => "keyword"}}
133             },
134             steps_planned => {type => "integer"},
135             body => {
136             type => "text",
137             analyzer => "default",
138             fielddata => "true",
139             term_vector => "yes",
140             similarity => "classic",
141             },
142             name => {
143             type => "text",
144             analyzer => "default",
145             fielddata => "true",
146             term_vector => "yes",
147             similarity => "classic",
148             fields => {keyword => {type => "keyword"}}
149             },
150             steps => {
151             properties => {
152             number => {type => "integer"},
153             text => {type => "text"},
154             status => {type => "text"},
155             elapsed => {type => "integer"},
156             }
157             },
158             }
159             }
160             }
161             }
162             );
163 1         64 return 1;
164             }
165 1         14 return 0;
166             }
167              
168             sub index_results {
169 3     3 1 923 my ($result) = @_;
170              
171 3 100       14 die("check_index must be run first") unless $e;
172              
173 2   66     9 $idx //= App::Prove::Elasticsearch::Utils::get_last_index($e, $index);
174 2         5 $idx++;
175              
176 2         7 $e->index(
177             index => $index,
178             id => $idx,
179             type => $index,
180             body => $result,
181             );
182              
183 2         5 my $doc_exists =
184             $e->exists(index => $index, type => 'testsuite', id => $idx);
185 2 100 66     14 if (!defined($doc_exists) || !int($doc_exists)) {
186 1         6 die
187             "Failed to Index $result->{'name'}, could find no record with ID $idx\n";
188             } else {
189 1         30 print
190             "Successfully Indexed test: $result->{'name'} with result ID $idx\n";
191             }
192             }
193              
194             sub bulk_index_results {
195 0     0 1 0 my @results = @_;
196 0   0     0 $bulk_helper //= $e->bulk_helper(
197             index => $index,
198             type => $index,
199             );
200              
201 0   0     0 $idx //= App::Prove::Elasticsearch::Utils::get_last_index($e, $index);
202              
203 0         0 $bulk_helper->index(map { $idx++; {id => $idx, source => $_} } @results);
  0         0  
  0         0  
204 0         0 $bulk_helper->flush();
205             }
206              
207             sub associate_case_with_result {
208 7     7 1 12565 my %opts = @_;
209              
210 7 100       28 die("check_index must be run first") unless $e;
211              
212             my %q = (
213             index => $index,
214             body => {
215             query => {
216             bool => {
217             must => [
218             {
219             match => {
220             name => $opts{case},
221             }
222             },
223 6         32 ],
224             },
225             },
226             },
227             );
228              
229             #It's normal to have multiple platforms in a document.
230 6         10 foreach my $plat (@{$opts{platforms}}) {
  6         15  
231 11         14 push(@{$q{body}{query}{bool}{must}}, {match => {platform => $plat}});
  11         32  
232             }
233              
234             #It's NOT normal to have multiple versions in a document.
235 6         7 foreach my $version (@{$opts{versions}}) {
  6         9  
236             push(
237 6         11 @{$q{body}{query}{bool}{should}},
  6         19  
238             {match => {version => $version}}
239             );
240             }
241              
242             #Paginate the query, TODO short-circuit when we stop getting results?
243 6         18 my $hits = App::Prove::Elasticsearch::Utils::do_paginated_query(
244             $e, $max_query_size,
245             %q
246             );
247 6 50       13 return 0 unless scalar(@$hits);
248              
249             #Now, update w/ the defect.
250 6         8 my $failures = 0;
251 6         7 my $attempts = 0;
252 6         8 foreach my $hit (@$hits) {
253             $hit->{_source}->{platform} = [ $hit->{_source}->{platform} ]
254 6 50       19 if ref($hit->{_source}->{platform}) ne 'ARRAY';
255 6 50 33     8 next if (scalar(@{$opts{versions}}) && !$hit->{_source}->{version});
  6         24  
256             next
257 6     6   16 unless List::Util::any { $hit->{_source}->{version} eq $_ }
258 6 100       20 @{$opts{versions}};
  6         20  
259 5 50 33     12 next if (scalar(@{$opts{platforms}}) && !$hit->{_source}->{platform});
  5         18  
260             next unless List::Util::all {
261 9     9   13 my $p = $_;
262 9         10 grep { $_ eq $p } @{$hit->{_source}->{platform}}
  18         34  
  9         14  
263             }
264 5 100       13 @{$opts{platforms}};
  5         11  
265 4 100       17 next unless $hit->{_source}->{name} eq $opts{case};
266              
267 3         3 $attempts++;
268              
269             #Merge the existing defects with the ones we are adding in
270 3   100     16 $hit->{defect} //= [];
271             my @df_merged =
272 3         4 List::Util::uniq((@{$hit->{defect}}, @{$opts{defects}}));
  3         6  
  3         9  
273              
274             my %update = (
275             index => $index,
276             id => $hit->{_id},
277 3         15 type => 'result',
278             body => {
279             doc => {
280             defect => \@df_merged,
281             },
282             }
283             );
284 3 50       7 $update{body}{doc}{status} = $opts{status} if $opts{status};
285              
286 3         10 my $res = $e->update(%update);
287              
288             print "Associated cases to document $hit->{_id}\n"
289 3 100       74 if $res->{result} eq 'updated';
290 3 100       6 if (!grep { $res->{result} eq $_ } qw{updated noop}) {
  6         19  
291 1         27 print
292             "Something went wrong associating cases to document $hit->{_id}!\n$res->{result}\n";
293 1         6 $failures++;
294             }
295             }
296              
297 6 100       78 print "No cases matching your query could be found. No action was taken.\n"
298             unless $attempts;
299              
300 6         34 return $failures;
301             }
302              
303             1;
304              
305             __END__