@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.

When viewing a live build log, trap users in a small personal hell where nothing but slavish devotion to the log exists

Summary: Depends on D19152. Ref T13088. This adds live log tailing. It is probably not the final version of this feature because it prevents escape once you begin tailing a log.

Test Plan: Used `bin/harbormaster write-log --rate ...` to write a log slowly. Viewed it in the web UI. Clicked "Follow Log". Followed the log until the write finished, a lifetime later.

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T13088

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

+125 -20
+7 -7
resources/celerity/map.php
··· 78 78 'rsrc/css/application/feed/feed.css' => 'ecd4ec57', 79 79 'rsrc/css/application/files/global-drag-and-drop.css' => 'b556a948', 80 80 'rsrc/css/application/flag/flag.css' => 'bba8f811', 81 - 'rsrc/css/application/harbormaster/harbormaster.css' => 'c7e29d9e', 81 + 'rsrc/css/application/harbormaster/harbormaster.css' => '5dd4c2de', 82 82 'rsrc/css/application/herald/herald-test.css' => 'a52e323e', 83 83 'rsrc/css/application/herald/herald.css' => 'cd8d0134', 84 84 'rsrc/css/application/maniphest/report.css' => '9b9580b7', ··· 416 416 'rsrc/js/application/drydock/drydock-live-operation-status.js' => '901935ef', 417 417 'rsrc/js/application/files/behavior-icon-composer.js' => '8499b6ab', 418 418 'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888', 419 - 'rsrc/js/application/harbormaster/behavior-harbormaster-log.js' => 'be6974cc', 419 + 'rsrc/js/application/harbormaster/behavior-harbormaster-log.js' => '796a8803', 420 420 'rsrc/js/application/herald/HeraldRuleEditor.js' => 'dca75c0e', 421 421 'rsrc/js/application/herald/PathTypeahead.js' => 'f7fc67ec', 422 422 'rsrc/js/application/herald/herald-rule-editor.js' => '7ebaeed3', ··· 579 579 'font-fontawesome' => 'e838e088', 580 580 'font-lato' => 'c7ccd872', 581 581 'global-drag-and-drop-css' => 'b556a948', 582 - 'harbormaster-css' => 'c7e29d9e', 582 + 'harbormaster-css' => '5dd4c2de', 583 583 'herald-css' => 'cd8d0134', 584 584 'herald-rule-editor' => 'dca75c0e', 585 585 'herald-test-css' => 'a52e323e', ··· 636 636 'javelin-behavior-event-all-day' => 'b41537c9', 637 637 'javelin-behavior-fancy-datepicker' => 'ecf4e799', 638 638 'javelin-behavior-global-drag-and-drop' => '960f6a39', 639 - 'javelin-behavior-harbormaster-log' => 'be6974cc', 639 + 'javelin-behavior-harbormaster-log' => '796a8803', 640 640 'javelin-behavior-herald-rule-editor' => '7ebaeed3', 641 641 'javelin-behavior-high-security-warning' => 'a464fe03', 642 642 'javelin-behavior-history-install' => '7ee2b591', ··· 1535 1535 'javelin-behavior', 1536 1536 'javelin-quicksand', 1537 1537 ), 1538 + '796a8803' => array( 1539 + 'javelin-behavior', 1540 + ), 1538 1541 '7a68dda3' => array( 1539 1542 'owners-path-editor', 1540 1543 'javelin-behavior', ··· 1888 1891 'javelin-dom', 1889 1892 'javelin-util', 1890 1893 'javelin-request', 1891 - ), 1892 - 'be6974cc' => array( 1893 - 'javelin-behavior', 1894 1894 ), 1895 1895 'bea6e7f4' => array( 1896 1896 'javelin-install',
+65 -9
src/applications/harbormaster/controller/HarbormasterBuildLogRenderController.php
··· 22 22 if ($head_lines === null) { 23 23 $head_lines = 8; 24 24 } 25 - $head_lines = min($head_lines, 100); 25 + $head_lines = min($head_lines, 1024); 26 26 $head_lines = max($head_lines, 0); 27 27 28 28 $tail_lines = $request->getInt('tail'); 29 29 if ($tail_lines === null) { 30 30 $tail_lines = 16; 31 31 } 32 - $tail_lines = min($tail_lines, 100); 32 + $tail_lines = min($tail_lines, 1024); 33 33 $tail_lines = max($tail_lines, 0); 34 34 35 35 $head_offset = $request->getInt('headOffset'); ··· 301 301 ); 302 302 } 303 303 304 - 305 304 foreach ($views as $view) { 306 305 if ($spacer) { 307 306 $spacer['tail'] = $view['viewOffset']; ··· 360 359 } 361 360 } 362 361 362 + if ($log->getLive()) { 363 + $last_view = last($views); 364 + $last_line = last($last_view['viewData']); 365 + if ($last_line) { 366 + $last_offset = $last_line['offset']; 367 + } else { 368 + $last_offset = 0; 369 + } 370 + 371 + $last_tail = $last_view['viewOffset'] + $last_view['viewLength']; 372 + $show_live = ($last_tail === $log_size); 373 + if ($show_live) { 374 + $rows[] = $this->renderLiveRow($last_offset); 375 + } 376 + } 377 + 363 378 $table = phutil_tag( 364 379 'table', 365 380 array( ··· 650 665 ), 651 666 $expand_down), 652 667 ); 653 - $expand_row = phutil_tag('tr', array(), $expand_cells); 654 - $expand_table = phutil_tag( 668 + 669 + return $this->renderActionTable($expand_cells); 670 + } 671 + 672 + private function renderLiveRow($log_size) { 673 + $icon_down = id(new PHUIIconView()) 674 + ->setIcon('fa-chevron-down'); 675 + 676 + $follow = javelin_tag( 677 + 'a', 678 + array( 679 + 'sigil' => 'harbormaster-log-expand harbormaster-log-live', 680 + 'meta' => array( 681 + 'headOffset' => $log_size, 682 + 'head' => 0, 683 + 'tail' => 1024, 684 + 'live' => true, 685 + ), 686 + ), 687 + array( 688 + $icon_down, 689 + ' ', 690 + pht('Follow Log'), 691 + ' ', 692 + $icon_down, 693 + )); 694 + 695 + $expand_cells = array( 696 + phutil_tag( 697 + 'td', 698 + array( 699 + 'class' => 'harbormaster-log-follow', 700 + ), 701 + $follow), 702 + ); 703 + 704 + return $this->renderActionTable($expand_cells); 705 + } 706 + 707 + private function renderActionTable(array $action_cells) { 708 + $action_row = phutil_tag('tr', array(), $action_cells); 709 + 710 + $action_table = phutil_tag( 655 711 'table', 656 712 array( 657 713 'class' => 'harbormaster-log-expand-table', 658 714 ), 659 - $expand_row); 715 + $action_row); 660 716 661 - $cells = array( 717 + $format_cells = array( 662 718 phutil_tag('th', array()), 663 719 phutil_tag( 664 720 'td', 665 721 array( 666 722 'class' => 'harbormaster-log-expand-cell', 667 723 ), 668 - $expand_table), 724 + $action_table), 669 725 ); 670 726 671 - return phutil_tag('tr', array(), $cells); 727 + return phutil_tag('tr', array(), $format_cells); 672 728 } 673 729 674 730 }
+8 -1
webroot/rsrc/css/application/harbormaster/harbormaster.css
··· 97 97 'Helvetica Neue', Helvetica, Arial, sans-serif; 98 98 } 99 99 100 - 101 100 .harbormaster-log-expand-up { 102 101 text-align: right; 103 102 width: 50%; ··· 105 104 106 105 .harbormaster-log-expand-up .phui-icon-view { 107 106 margin: 0 0 0px 4px; 107 + } 108 + 109 + .harbormaster-log-follow { 110 + text-align: center; 111 + } 112 + 113 + .harbormaster-log-follow .phui-icon-view { 114 + margin: 0 4px; 108 115 } 109 116 110 117 .harbormaster-log-expand-mid {
+45 -3
webroot/rsrc/js/application/harbormaster/behavior-harbormaster-log.js
··· 5 5 6 6 JX.behavior('harbormaster-log', function(config) { 7 7 var contentNode = JX.$(config.contentNodeID); 8 + var following = false; 8 9 9 10 JX.DOM.listen(contentNode, 'click', 'harbormaster-log-expand', function(e) { 10 11 if (!e.isNormalClick()) { ··· 13 14 14 15 e.kill(); 15 16 16 - var row = e.getNode('tag:tr'); 17 + expand(e.getTarget()); 18 + }); 19 + 20 + function expand(node) { 21 + var row = JX.DOM.findAbove(node, 'tr'); 17 22 row = JX.DOM.findAbove(row, 'tr'); 18 23 19 - var data = e.getNodeData('harbormaster-log-expand'); 24 + var data = JX.Stratcom.getData(node); 20 25 21 26 var uri = new JX.URI(config.renderURI) 22 27 .addQueryParams(data); 23 28 29 + if (data.live) { 30 + following = true; 31 + } 32 + 24 33 var request = new JX.Request(uri, function(r) { 25 34 var result = JX.$H(r.markup).getNode(); 26 35 var rows = [].slice.apply(result.firstChild.childNodes); 27 36 37 + // If we're following the bottom of the log, the result always includes 38 + // the last line from the previous render. Throw it away, then add the 39 + // new data. 40 + if (data.live && row.previousSibling) { 41 + JX.DOM.remove(row.previousSibling); 42 + } 43 + 28 44 JX.DOM.replace(row, rows); 45 + 46 + if (data.live) { 47 + // If this was a live follow, scroll the new data into view. This is 48 + // probably intensely annoying in practice but seems cool for now. 49 + var last_row = rows[rows.length - 1]; 50 + var tail_pos = JX.$V(last_row).y + JX.Vector.getDim(last_row).y; 51 + var view_y = JX.Vector.getViewport().y; 52 + JX.DOM.scrollToPosition(null, (tail_pos - view_y) + 32); 53 + 54 + setTimeout(follow, 500); 55 + } 29 56 }); 30 57 31 58 request.send(); 32 - }); 59 + } 60 + 61 + function follow() { 62 + if (!following) { 63 + return; 64 + } 65 + 66 + var live; 67 + try { 68 + live = JX.DOM.find(contentNode, 'a', 'harbormaster-log-live'); 69 + } catch (e) { 70 + return; 71 + } 72 + 73 + expand(live); 74 + } 33 75 34 76 function onresponse(r) { 35 77 JX.DOM.alterClass(contentNode, 'harbormaster-log-view-loading', false);