@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Implement very rough message context for Conpherence search

Summary: Ref T3165. This is pretty awful looking, but should pull the correct data.

Test Plan: {F387567}

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T3165

Differential Revision: https://secure.phabricator.com/D12589

+281
+1
src/__phutil_library_map__.php
··· 4807 4807 'PhabricatorPolicyInterface', 4808 4808 'PhabricatorMarkupInterface', 4809 4809 'PhabricatorApplicationTransactionInterface', 4810 + 'PhabricatorSubscribableInterface', 4810 4811 ), 4811 4812 'PhabricatorCalendarEventDeleteController' => 'PhabricatorCalendarController', 4812 4813 'PhabricatorCalendarEventEditController' => 'PhabricatorCalendarController',
+13
src/applications/conpherence/query/ConpherenceFulltextQuery.php
··· 4 4 extends PhabricatorOffsetPagedQuery { 5 5 6 6 private $threadPHIDs; 7 + private $previousTransactionPHIDs; 7 8 private $fulltext; 8 9 9 10 public function withThreadPHIDs(array $phids) { 10 11 $this->threadPHIDs = $phids; 12 + return $this; 13 + } 14 + 15 + public function withPreviousTransactionPHIDs(array $phids) { 16 + $this->previousTransactionPHIDs = $phids; 11 17 return $this; 12 18 } 13 19 ··· 40 46 $conn_r, 41 47 'i.threadPHID IN (%Ls)', 42 48 $this->threadPHIDs); 49 + } 50 + 51 + if ($this->previousTransactionPHIDs !== null) { 52 + $where[] = qsprintf( 53 + $conn_r, 54 + 'i.previousTransactionPHID IN (%Ls)', 55 + $this->previousTransactionPHIDs); 43 56 } 44 57 45 58 if (strlen($this->fulltext)) {
+267
src/applications/conpherence/query/ConpherenceThreadSearchEngine.php
··· 149 149 $viewer, 150 150 $conpherences); 151 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 + 152 173 $list = new PHUIObjectItemListView(); 153 174 $list->setUser($viewer); 154 175 foreach ($conpherences as $conpherence) { ··· 181 202 phabricator_datetime($conpherence->getDateModified(), $viewer)), 182 203 )); 183 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 + 184 246 $list->addItem($item); 185 247 } 186 248 ··· 193 255 'messages' => pht('Messages'), 194 256 'both' => pht('Both'), 195 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; 196 463 } 197 464 198 465 }