~ubuntu-branches/ubuntu/wily/phabricator/wily

« back to all changes in this revision

Viewing changes to phabricator/src/applications/conpherence/query/ConpherenceThreadSearchEngine.php

  • Committer: Package Import Robot
  • Author(s): Richard Sellam
  • Date: 2015-04-28 03:01:19 UTC
  • mfrom: (0.22.1) (0.21.1) (0.17.2) (2.1.7 sid)
  • Revision ID: package-import@ubuntu.com-20150428030119-36t1maxz1gh3ojyg
Tags: 0~git20150428-1
* New snapshot release
* fixed nginx configuration (closes: #779705)
* start mysql before phd daemons, if available (closes: #780260, #780265)
* fixed log files permissions (closes: #781825)
* added spanish translation (closes: #783566)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
<?php
 
2
 
 
3
final class ConpherenceThreadSearchEngine
 
4
  extends PhabricatorApplicationSearchEngine {
 
5
 
 
6
  public function getResultTypeDescription() {
 
7
    return pht('Threads');
 
8
  }
 
9
 
 
10
  public function getApplicationClassName() {
 
11
    return 'PhabricatorConpherenceApplication';
 
12
  }
 
13
 
 
14
  public function buildSavedQueryFromRequest(AphrontRequest $request) {
 
15
    $saved = new PhabricatorSavedQuery();
 
16
 
 
17
    $saved->setParameter(
 
18
      'participantPHIDs',
 
19
      $this->readUsersFromRequest($request, 'participants'));
 
20
 
 
21
    $saved->setParameter('fulltext', $request->getStr('fulltext'));
 
22
 
 
23
    $saved->setParameter(
 
24
      'threadType',
 
25
      $request->getStr('threadType'));
 
26
 
 
27
    return $saved;
 
28
  }
 
29
 
 
30
  public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
 
31
    $query = id(new ConpherenceThreadQuery())
 
32
      ->needParticipantCache(true);
 
33
 
 
34
    $participant_phids = $saved->getParameter('participantPHIDs', array());
 
35
    if ($participant_phids && is_array($participant_phids)) {
 
36
      $query->withParticipantPHIDs($participant_phids);
 
37
    }
 
38
 
 
39
    $fulltext = $saved->getParameter('fulltext');
 
40
    if (strlen($fulltext)) {
 
41
      $query->withFulltext($fulltext);
 
42
    }
 
43
 
 
44
    $thread_type = $saved->getParameter('threadType');
 
45
    if (idx($this->getTypeOptions(), $thread_type)) {
 
46
      switch ($thread_type) {
 
47
        case 'rooms':
 
48
          $query->withIsRoom(true);
 
49
          break;
 
50
        case 'messages':
 
51
          $query->withIsRoom(false);
 
52
          break;
 
53
        case 'both':
 
54
          $query->withIsRoom(null);
 
55
          break;
 
56
      }
 
57
    }
 
58
 
 
59
    return $query;
 
60
  }
 
61
 
 
62
  public function buildSearchForm(
 
63
    AphrontFormView $form,
 
64
    PhabricatorSavedQuery $saved) {
 
65
 
 
66
    $participant_phids = $saved->getParameter('participantPHIDs', array());
 
67
    $fulltext = $saved->getParameter('fulltext');
 
68
 
 
69
    $form
 
70
      ->appendControl(
 
71
        id(new AphrontFormTokenizerControl())
 
72
          ->setDatasource(new PhabricatorPeopleDatasource())
 
73
          ->setName('participants')
 
74
          ->setLabel(pht('Participants'))
 
75
          ->setValue($participant_phids))
 
76
      ->appendControl(
 
77
        id(new AphrontFormTextControl())
 
78
          ->setName('fulltext')
 
79
          ->setLabel(pht('Contains Words'))
 
80
          ->setValue($fulltext))
 
81
      ->appendControl(
 
82
        id(new AphrontFormSelectControl())
 
83
        ->setLabel(pht('Type'))
 
84
        ->setName('threadType')
 
85
        ->setOptions($this->getTypeOptions())
 
86
        ->setValue($saved->getParameter('threadType')));
 
87
  }
 
88
 
 
89
  protected function getURI($path) {
 
90
    return '/conpherence/search/'.$path;
 
91
  }
 
92
 
 
93
  protected function getBuiltinQueryNames() {
 
94
    $names = array();
 
95
 
 
96
    $names = array(
 
97
      'all' => pht('All Rooms'),
 
98
    );
 
99
 
 
100
    if ($this->requireViewer()->isLoggedIn()) {
 
101
      $names['participant'] = pht('Joined Rooms');
 
102
      $names['messages'] = pht('All Messages');
 
103
    }
 
104
 
 
105
    return $names;
 
106
  }
 
107
 
 
108
  public function buildSavedQueryFromBuiltin($query_key) {
 
109
 
 
110
    $query = $this->newSavedQuery();
 
111
    $query->setQueryKey($query_key);
 
112
 
 
113
    switch ($query_key) {
 
114
      case 'all':
 
115
        $query->setParameter('threadType', 'rooms');
 
116
        return $query;
 
117
      case 'participant':
 
118
        $query->setParameter('threadType', 'rooms');
 
119
        return $query->setParameter(
 
120
          'participantPHIDs',
 
121
          array($this->requireViewer()->getPHID()));
 
122
      case 'messages':
 
123
        $query->setParameter('threadType', 'messages');
 
124
        return $query->setParameter(
 
125
          'participantPHIDs',
 
126
          array($this->requireViewer()->getPHID()));
 
127
    }
 
128
 
 
129
    return parent::buildSavedQueryFromBuiltin($query_key);
 
130
  }
 
131
 
 
132
  protected function getRequiredHandlePHIDsForResultList(
 
133
    array $conpherences,
 
134
    PhabricatorSavedQuery $query) {
 
135
 
 
136
    $recent = mpull($conpherences, 'getRecentParticipantPHIDs');
 
137
    return array_unique(array_mergev($recent));
 
138
  }
 
139
 
 
140
  protected function renderResultList(
 
141
    array $conpherences,
 
142
    PhabricatorSavedQuery $query,
 
143
    array $handles) {
 
144
    assert_instances_of($conpherences, 'ConpherenceThread');
 
145
 
 
146
    $viewer = $this->requireViewer();
 
147
 
 
148
    $policy_objects = ConpherenceThread::loadPolicyObjects(
 
149
      $viewer,
 
150
      $conpherences);
 
151
 
 
152
    $fulltext = $query->getParameter('fulltext');
 
153
    if (strlen($fulltext) && $conpherences) {
 
154
      $context = $this->loadContextMessages($conpherences, $fulltext);
 
155
 
 
156
      $author_phids = array();
 
157
      foreach ($context as $messages) {
 
158
        foreach ($messages as $group) {
 
159
          foreach ($group as $message) {
 
160
            $xaction = $message['xaction'];
 
161
            if ($xaction) {
 
162
              $author_phids[] = $xaction->getAuthorPHID();
 
163
            }
 
164
          }
 
165
        }
 
166
      }
 
167
 
 
168
      $handles = $viewer->loadHandles($author_phids);
 
169
    } else {
 
170
      $context = array();
 
171
    }
 
172
 
 
173
    $list = new PHUIObjectItemListView();
 
174
    $list->setUser($viewer);
 
175
    foreach ($conpherences as $conpherence) {
 
176
      $created = phabricator_date($conpherence->getDateCreated(), $viewer);
 
177
      $data = $conpherence->getDisplayData($viewer);
 
178
      $title = $data['title'];
 
179
 
 
180
      if ($conpherence->getIsRoom()) {
 
181
        $icon_name = $conpherence->getPolicyIconName($policy_objects);
 
182
      } else {
 
183
        $icon_name = 'fa-envelope-o';
 
184
      }
 
185
      $icon = id(new PHUIIconView())
 
186
        ->setIconFont($icon_name);
 
187
      $item = id(new PHUIObjectItemView())
 
188
        ->setObjectName($conpherence->getMonogram())
 
189
        ->setHeader($title)
 
190
        ->setHref('/conpherence/'.$conpherence->getID().'/')
 
191
        ->setObject($conpherence)
 
192
        ->addIcon('none', $created)
 
193
        ->addIcon(
 
194
          'none',
 
195
          pht('Messages: %d', $conpherence->getMessageCount()))
 
196
        ->addAttribute(
 
197
          array(
 
198
            $icon,
 
199
            ' ',
 
200
            pht(
 
201
              'Last updated %s',
 
202
              phabricator_datetime($conpherence->getDateModified(), $viewer)),
 
203
          ));
 
204
 
 
205
      $messages = idx($context, $conpherence->getPHID());
 
206
      if ($messages) {
 
207
 
 
208
        // TODO: This is egregiously under-designed.
 
209
 
 
210
        foreach ($messages as $group) {
 
211
          $rows = array();
 
212
          $rowc = array();
 
213
          foreach ($group as $message) {
 
214
            $xaction = $message['xaction'];
 
215
            if (!$xaction) {
 
216
              continue;
 
217
            }
 
218
 
 
219
            $rowc[] = ($message['match'] ? 'highlighted' : null);
 
220
            $rows[] = array(
 
221
              $handles->renderHandle($xaction->getAuthorPHID()),
 
222
              $xaction->getComment()->getContent(),
 
223
              phabricator_datetime($xaction->getDateCreated(), $viewer),
 
224
            );
 
225
          }
 
226
          $table = id(new AphrontTableView($rows))
 
227
            ->setHeaders(
 
228
              array(
 
229
                pht('User'),
 
230
                pht('Message'),
 
231
                pht('At'),
 
232
              ))
 
233
            ->setRowClasses($rowc)
 
234
            ->setColumnClasses(
 
235
              array(
 
236
                '',
 
237
                'wide',
 
238
              ));
 
239
          $box = id(new PHUIBoxView())
 
240
            ->appendChild($table)
 
241
            ->addMargin(PHUI::MARGIN_SMALL);
 
242
          $item->appendChild($box);
 
243
        }
 
244
      }
 
245
 
 
246
      $list->addItem($item);
 
247
    }
 
248
 
 
249
    return $list;
 
250
  }
 
251
 
 
252
  private function getTypeOptions() {
 
253
    return array(
 
254
      'rooms' => pht('Rooms'),
 
255
      'messages' => pht('Messages'),
 
256
      'both' => pht('Both'),
 
257
    );
 
258
  }
 
259
 
 
260
  private function loadContextMessages(array $threads, $fulltext) {
 
261
    $phids = mpull($threads, 'getPHID');
 
262
 
 
263
    // We want to load a few messages for each thread in the result list, to
 
264
    // show some of the actual content hits to help the user find what they
 
265
    // are looking for.
 
266
 
 
267
    // This method is trying to batch this lookup in most cases, so we do
 
268
    // between one and "a handful" of queries instead of one per thread in
 
269
    // most cases. To do this:
 
270
    //
 
271
    //   - Load a big block of results for all of the threads.
 
272
    //   - If we didn't get a full block back, we have everything that matches
 
273
    //     the query. Sort it out and exit.
 
274
    //   - Otherwise, some threads had a ton of hits, so we might not be
 
275
    //     getting everything we want (we could be getting back 1,000 hits for
 
276
    //     the first thread). Remove any threads which we have enough results
 
277
    //     for and try again.
 
278
    //   - Repeat until we have everything or every thread has enough results.
 
279
    //
 
280
    // In the worst case, we could end up degrading to one query per thread,
 
281
    // but this is incredibly unlikely on real data.
 
282
 
 
283
    // Size of the result blocks we're going to load.
 
284
    $limit = 1000;
 
285
 
 
286
    // Number of messages we want for each thread.
 
287
    $want = 3;
 
288
 
 
289
    $need = $phids;
 
290
    $hits = array();
 
291
    while ($need) {
 
292
      $rows = id(new ConpherenceFulltextQuery())
 
293
        ->withThreadPHIDs($need)
 
294
        ->withFulltext($fulltext)
 
295
        ->setLimit($limit)
 
296
        ->execute();
 
297
 
 
298
      foreach ($rows as $row) {
 
299
        $hits[$row['threadPHID']][] = $row;
 
300
      }
 
301
 
 
302
      if (count($rows) < $limit) {
 
303
        break;
 
304
      }
 
305
 
 
306
      foreach ($need as $key => $phid) {
 
307
        if (count($hits[$phid]) >= $want) {
 
308
          unset($need[$key]);
 
309
        }
 
310
      }
 
311
    }
 
312
 
 
313
    // Now that we have all the fulltext matches, throw away any extras that we
 
314
    // aren't going to render so we don't need to do lookups on them.
 
315
    foreach ($hits as $phid => $rows) {
 
316
      if (count($rows) > $want) {
 
317
        $hits[$phid] = array_slice($rows, 0, $want);
 
318
      }
 
319
    }
 
320
 
 
321
    // For each fulltext match, we want to render a message before and after
 
322
    // the match to give it some context. We already know the transactions
 
323
    // before each match because the rows have a "previousTransactionPHID",
 
324
    // but we need to do one more query to figure out the transactions after
 
325
    // each match.
 
326
 
 
327
    // Collect the transactions we want to find the next transactions for.
 
328
    $after = array();
 
329
    foreach ($hits as $phid => $rows) {
 
330
      foreach ($rows as $row) {
 
331
        $after[] = $row['transactionPHID'];
 
332
      }
 
333
    }
 
334
 
 
335
    // Look up the next transactions.
 
336
    if ($after) {
 
337
      $after_rows = id(new ConpherenceFulltextQuery())
 
338
        ->withPreviousTransactionPHIDs($after)
 
339
        ->execute();
 
340
    } else {
 
341
      $after_rows = array();
 
342
    }
 
343
 
 
344
    // Build maps from PHIDs to the previous and next PHIDs.
 
345
    $prev_map = array();
 
346
    $next_map = array();
 
347
    foreach ($after_rows as $row) {
 
348
      $next_map[$row['previousTransactionPHID']] = $row['transactionPHID'];
 
349
    }
 
350
 
 
351
    foreach ($hits as $phid => $rows) {
 
352
      foreach ($rows as $row) {
 
353
        $prev = $row['previousTransactionPHID'];
 
354
        if ($prev) {
 
355
          $prev_map[$row['transactionPHID']] = $prev;
 
356
          $next_map[$prev] = $row['transactionPHID'];
 
357
        }
 
358
      }
 
359
    }
 
360
 
 
361
    // Now we're going to collect the actual transaction PHIDs, in order, that
 
362
    // we want to show for each thread.
 
363
    $groups = array();
 
364
    foreach ($hits as $thread_phid => $rows) {
 
365
      $rows = ipull($rows, null, 'transactionPHID');
 
366
      foreach ($rows as $phid => $row) {
 
367
        unset($rows[$phid]);
 
368
 
 
369
        $group = array();
 
370
 
 
371
        // Walk backward, finding all the previous results. We can just keep
 
372
        // going until we run out of results because we've only loaded things
 
373
        // that we want to show.
 
374
        $prev = $phid;
 
375
        while (true) {
 
376
          if (!isset($prev_map[$prev])) {
 
377
            // No previous transaction, so we're done.
 
378
            break;
 
379
          }
 
380
 
 
381
          $prev = $prev_map[$prev];
 
382
 
 
383
          if (isset($rows[$prev])) {
 
384
            $match = true;
 
385
            unset($rows[$prev]);
 
386
          } else {
 
387
            $match = false;
 
388
          }
 
389
 
 
390
          $group[] = array(
 
391
            'phid' => $prev,
 
392
            'match' => $match,
 
393
          );
 
394
        }
 
395
 
 
396
        if (count($group) > 1) {
 
397
          $group = array_reverse($group);
 
398
        }
 
399
 
 
400
        $group[] = array(
 
401
          'phid' => $phid,
 
402
          'match' => true,
 
403
        );
 
404
 
 
405
        $next = $phid;
 
406
        while (true) {
 
407
          if (!isset($next_map[$next])) {
 
408
            break;
 
409
          }
 
410
 
 
411
          $next = $next_map[$next];
 
412
 
 
413
          if (isset($rows[$next])) {
 
414
            $match = true;
 
415
            unset($rows[$next]);
 
416
          } else {
 
417
            $match = false;
 
418
          }
 
419
 
 
420
          $group[] = array(
 
421
            'phid' => $next,
 
422
            'match' => $match,
 
423
          );
 
424
        }
 
425
 
 
426
        $groups[$thread_phid][] = $group;
 
427
      }
 
428
    }
 
429
 
 
430
    // Load all the actual transactions we need.
 
431
    $xaction_phids = array();
 
432
    foreach ($groups as $thread_phid => $group) {
 
433
      foreach ($group as $list) {
 
434
        foreach ($list as $item) {
 
435
          $xaction_phids[] = $item['phid'];
 
436
        }
 
437
      }
 
438
    }
 
439
 
 
440
    if ($xaction_phids) {
 
441
      $xactions = id(new ConpherenceTransactionQuery())
 
442
        ->setViewer($this->requireViewer())
 
443
        ->withPHIDs($xaction_phids)
 
444
        ->needComments(true)
 
445
        ->execute();
 
446
      $xactions = mpull($xactions, null, 'getPHID');
 
447
    } else {
 
448
      $xactions = array();
 
449
    }
 
450
 
 
451
    foreach ($groups as $thread_phid => $group) {
 
452
      foreach ($group as $key => $list) {
 
453
        foreach ($list as $lkey => $item) {
 
454
          $xaction = idx($xactions, $item['phid']);
 
455
          $groups[$thread_phid][$key][$lkey]['xaction'] = $xaction;
 
456
        }
 
457
      }
 
458
    }
 
459
 
 
460
    // TODO: Sort the groups chronologically?
 
461
 
 
462
    return $groups;
 
463
  }
 
464
 
 
465
}