diff --git a/resources/celerity/map.php b/resources/celerity/map.php index e4d5c8928c..d01bca8f99 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,8 +9,8 @@ return array( 'names' => array( 'conpherence.pkg.css' => '3c8a0668', 'conpherence.pkg.js' => '020aebcf', - 'core.pkg.css' => 'd81a600e', - 'core.pkg.js' => 'a747b035', + 'core.pkg.css' => '03808839', + 'core.pkg.js' => '9ac8af68', 'differential.pkg.css' => '8d8360fb', 'differential.pkg.js' => '67e02996', 'diffusion.pkg.css' => '42c75c37', @@ -30,14 +30,14 @@ return array( 'rsrc/css/aphront/notification.css' => '30240bd2', 'rsrc/css/aphront/panel-view.css' => '46923d46', 'rsrc/css/aphront/phabricator-nav-view.css' => 'f8a0c1bf', - 'rsrc/css/aphront/table-view.css' => '7dc3a9c2', + 'rsrc/css/aphront/table-view.css' => '5f13a9e4', 'rsrc/css/aphront/tokenizer.css' => 'b52d0668', 'rsrc/css/aphront/tooltip.css' => 'e3f2412f', 'rsrc/css/aphront/typeahead-browse.css' => 'b7ed02d2', 'rsrc/css/aphront/typeahead.css' => '8779483d', 'rsrc/css/application/almanac/almanac.css' => '2e050f4f', 'rsrc/css/application/auth/auth.css' => 'add92fd8', - 'rsrc/css/application/base/main-menu-view.css' => '69c1c2c1', + 'rsrc/css/application/base/main-menu-view.css' => 'c6d35b35', 'rsrc/css/application/base/notification-menu.css' => '4df1ee30', 'rsrc/css/application/base/phui-theme.css' => '35883b37', 'rsrc/css/application/base/standard-page-view.css' => '8a295cb9', @@ -58,7 +58,7 @@ return array( 'rsrc/css/application/contentsource/content-source-view.css' => 'cdf0d579', 'rsrc/css/application/countdown/timer.css' => 'bff8012f', 'rsrc/css/application/daemon/bulk-job.css' => '73af99f5', - 'rsrc/css/application/dashboard/dashboard.css' => '4267d6c6', + 'rsrc/css/application/dashboard/dashboard.css' => '5a205b9d', 'rsrc/css/application/diff/inline-comment-summary.css' => '81eb368d', 'rsrc/css/application/differential/add-comment.css' => '7e5900d9', 'rsrc/css/application/differential/changeset-view.css' => 'bde53589', @@ -86,7 +86,7 @@ return array( 'rsrc/css/application/paste/paste.css' => 'b37bcd38', 'rsrc/css/application/people/people-picture-menu-item.css' => 'fe8e07cf', 'rsrc/css/application/people/people-profile.css' => '2ea2daa1', - 'rsrc/css/application/phame/phame.css' => '799febf9', + 'rsrc/css/application/phame/phame.css' => 'bb442327', 'rsrc/css/application/pholio/pholio-edit.css' => '4df55b3b', 'rsrc/css/application/pholio/pholio-inline-comments.css' => '722b48c2', 'rsrc/css/application/pholio/pholio.css' => '88ef5ef1', @@ -100,7 +100,7 @@ return array( 'rsrc/css/application/policy/policy.css' => 'ceb56a08', 'rsrc/css/application/ponder/ponder-view.css' => '05a09d0a', 'rsrc/css/application/project/project-card-view.css' => '4e7371cd', - 'rsrc/css/application/project/project-triggers.css' => 'cb866c2d', + 'rsrc/css/application/project/project-triggers.css' => 'cd9c8bb9', 'rsrc/css/application/project/project-view.css' => '567858b3', 'rsrc/css/application/releeph/releeph-core.css' => 'f81ff2db', 'rsrc/css/application/releeph/releeph-preview-branch.css' => '22db5c07', @@ -128,13 +128,13 @@ return array( 'rsrc/css/phui/calendar/phui-calendar-list.css' => 'ccd7e4e2', 'rsrc/css/phui/calendar/phui-calendar-month.css' => 'cb758c42', 'rsrc/css/phui/calendar/phui-calendar.css' => 'f11073aa', - 'rsrc/css/phui/object-item/phui-oi-big-ui.css' => '534f1757', + 'rsrc/css/phui/object-item/phui-oi-big-ui.css' => 'fa74cc35', 'rsrc/css/phui/object-item/phui-oi-color.css' => 'b517bfa0', 'rsrc/css/phui/object-item/phui-oi-drag-ui.css' => 'da15d3dc', 'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '490e2e2e', - 'rsrc/css/phui/object-item/phui-oi-list-view.css' => 'f14f2422', + 'rsrc/css/phui/object-item/phui-oi-list-view.css' => 'd7723ecc', 'rsrc/css/phui/object-item/phui-oi-simple-ui.css' => '6a30fa46', - 'rsrc/css/phui/phui-action-list.css' => 'c4972757', + 'rsrc/css/phui/phui-action-list.css' => 'e820263c', 'rsrc/css/phui/phui-action-panel.css' => '6c386cbf', 'rsrc/css/phui/phui-badge.css' => '666e25ad', 'rsrc/css/phui/phui-basic-nav-view.css' => '56ebd66d', @@ -160,11 +160,11 @@ return array( 'rsrc/css/phui/phui-icon-set-selector.css' => '7aa5f3ec', 'rsrc/css/phui/phui-icon.css' => '4cbc684a', 'rsrc/css/phui/phui-image-mask.css' => '62c7f4d2', - 'rsrc/css/phui/phui-info-view.css' => '37b8d9ce', + 'rsrc/css/phui/phui-info-view.css' => 'a10a909b', 'rsrc/css/phui/phui-invisible-character-view.css' => 'c694c4a4', 'rsrc/css/phui/phui-left-right.css' => '68513c34', 'rsrc/css/phui/phui-lightbox.css' => '4ebf22da', - 'rsrc/css/phui/phui-list.css' => '470b1adb', + 'rsrc/css/phui/phui-list.css' => 'b05144dd', 'rsrc/css/phui/phui-object-box.css' => 'f434b6be', 'rsrc/css/phui/phui-pager.css' => 'd022c7ad', 'rsrc/css/phui/phui-pinboard-view.css' => '1f08f5d8', @@ -173,7 +173,7 @@ return array( 'rsrc/css/phui/phui-segment-bar-view.css' => '5166b370', 'rsrc/css/phui/phui-spacing.css' => 'b05cadc3', 'rsrc/css/phui/phui-status.css' => 'e5ff8be0', - 'rsrc/css/phui/phui-tag-view.css' => '29409667', + 'rsrc/css/phui/phui-tag-view.css' => '8519160a', 'rsrc/css/phui/phui-timeline-view.css' => '1e348e4b', 'rsrc/css/phui/phui-two-column-view.css' => '01e6991e', 'rsrc/css/phui/workboards/phui-workboard-color.css' => 'e86de308', @@ -219,7 +219,7 @@ return array( 'rsrc/externals/javelin/core/init.js' => '98e6504a', 'rsrc/externals/javelin/core/init_node.js' => '16961339', 'rsrc/externals/javelin/core/install.js' => '5902260c', - 'rsrc/externals/javelin/core/util.js' => '22ae1776', + 'rsrc/externals/javelin/core/util.js' => 'edb4d8c9', 'rsrc/externals/javelin/docs/Base.js' => '5a401d7d', 'rsrc/externals/javelin/docs/onload.js' => 'ee58fb62', 'rsrc/externals/javelin/ext/fx/Color.js' => '78f811c9', @@ -261,7 +261,7 @@ return array( 'rsrc/externals/javelin/lib/__tests__/JSON.js' => '710377ae', 'rsrc/externals/javelin/lib/__tests__/URI.js' => '6fff0c2b', 'rsrc/externals/javelin/lib/__tests__/behavior.js' => '8426ebeb', - 'rsrc/externals/javelin/lib/behavior.js' => 'fce5d170', + 'rsrc/externals/javelin/lib/behavior.js' => '1b6acc2a', 'rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js' => '89a1ae3a', 'rsrc/externals/javelin/lib/control/typeahead/Typeahead.js' => 'a4356cde', 'rsrc/externals/javelin/lib/control/typeahead/normalizer/TypeaheadNormalizer.js' => 'a241536a', @@ -373,10 +373,10 @@ return array( 'rsrc/js/application/conpherence/behavior-toggle-widget.js' => '8f959ad0', 'rsrc/js/application/countdown/timer.js' => '6a162524', 'rsrc/js/application/daemon/behavior-bulk-job-reload.js' => '3829a3cf', - 'rsrc/js/application/dashboard/behavior-dashboard-async-panel.js' => '09ecf50c', - 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '076bd092', + 'rsrc/js/application/dashboard/behavior-dashboard-async-panel.js' => '9c01e364', + 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => 'a2ab19be', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '1e413dc9', - 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => '9b1cbd76', + 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => '0116d3e8', 'rsrc/js/application/diff/DiffChangeset.js' => 'd0a85a85', 'rsrc/js/application/diff/DiffChangesetList.js' => '04023d82', 'rsrc/js/application/diff/DiffInline.js' => 'a4a14a94', @@ -391,6 +391,7 @@ return array( 'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'c715c123', 'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => '6a85bc5a', 'rsrc/js/application/drydock/drydock-live-operation-status.js' => '47a0728b', + 'rsrc/js/application/fact/Chart.js' => 'fcb0c07d', 'rsrc/js/application/files/behavior-document-engine.js' => '243d6c22', 'rsrc/js/application/files/behavior-icon-composer.js' => '38a6cedb', 'rsrc/js/application/files/behavior-launch-icon-composer.js' => 'a17b84f1', @@ -399,7 +400,8 @@ return array( 'rsrc/js/application/herald/PathTypeahead.js' => 'ad486db3', 'rsrc/js/application/herald/herald-rule-editor.js' => '0922e81d', 'rsrc/js/application/maniphest/behavior-batch-selector.js' => '139ef688', - 'rsrc/js/application/maniphest/behavior-line-chart.js' => 'c8147a20', + 'rsrc/js/application/maniphest/behavior-line-chart-legacy.js' => 'faf3ab6b', + 'rsrc/js/application/maniphest/behavior-line-chart.js' => 'ad258e28', 'rsrc/js/application/maniphest/behavior-list-edit.js' => 'c687e867', 'rsrc/js/application/owners/OwnersPathEditor.js' => '2a8b62d9', 'rsrc/js/application/owners/owners-path-editor.js' => 'ff688a7a', @@ -435,7 +437,7 @@ return array( 'rsrc/js/application/transactions/behavior-show-older-transactions.js' => '600f440c', 'rsrc/js/application/transactions/behavior-transaction-comment-form.js' => '2bdadf1a', 'rsrc/js/application/transactions/behavior-transaction-list.js' => '9cec214e', - 'rsrc/js/application/trigger/TriggerRule.js' => '1c60c3fc', + 'rsrc/js/application/trigger/TriggerRule.js' => '41b7b4f6', 'rsrc/js/application/trigger/TriggerRuleControl.js' => '5faf27b9', 'rsrc/js/application/trigger/TriggerRuleEditor.js' => 'b49fd60c', 'rsrc/js/application/trigger/TriggerRuleType.js' => '4feea7d3', @@ -520,7 +522,7 @@ return array( 'rsrc/js/phuix/PHUIXActionView.js' => 'aaa08f3b', 'rsrc/js/phuix/PHUIXAutocomplete.js' => '2fbe234d', 'rsrc/js/phuix/PHUIXButtonView.js' => '55a24e84', - 'rsrc/js/phuix/PHUIXDropdownMenu.js' => 'bdce4d78', + 'rsrc/js/phuix/PHUIXDropdownMenu.js' => '7acfd98b', 'rsrc/js/phuix/PHUIXExample.js' => 'c2c500a7', 'rsrc/js/phuix/PHUIXFormControl.js' => '38c1f3fb', 'rsrc/js/phuix/PHUIXIconView.js' => 'a5257c4e', @@ -533,7 +535,7 @@ return array( 'aphront-list-filter-view-css' => 'feb64255', 'aphront-multi-column-view-css' => 'fbc00ba3', 'aphront-panel-view-css' => '46923d46', - 'aphront-table-view-css' => '7dc3a9c2', + 'aphront-table-view-css' => '5f13a9e4', 'aphront-tokenizer-control-css' => 'b52d0668', 'aphront-tooltip-css' => 'e3f2412f', 'aphront-typeahead-control-css' => '8779483d', @@ -573,7 +575,7 @@ return array( 'herald-test-css' => 'e004176f', 'inline-comment-summary-css' => '81eb368d', 'javelin-aphlict' => '022516b4', - 'javelin-behavior' => 'fce5d170', + 'javelin-behavior' => '1b6acc2a', 'javelin-behavior-aphlict-dropdown' => 'e9a2940f', 'javelin-behavior-aphlict-listen' => '4e61fa88', 'javelin-behavior-aphlict-status' => 'c3703a16', @@ -596,10 +598,10 @@ return array( 'javelin-behavior-conpherence-search' => '91befbcc', 'javelin-behavior-countdown-timer' => '6a162524', 'javelin-behavior-dark-console' => 'f39d968b', - 'javelin-behavior-dashboard-async-panel' => '09ecf50c', - 'javelin-behavior-dashboard-move-panels' => '076bd092', + 'javelin-behavior-dashboard-async-panel' => '9c01e364', + 'javelin-behavior-dashboard-move-panels' => 'a2ab19be', 'javelin-behavior-dashboard-query-panel-select' => '1e413dc9', - 'javelin-behavior-dashboard-tab-panel' => '9b1cbd76', + 'javelin-behavior-dashboard-tab-panel' => '0116d3e8', 'javelin-behavior-day-view' => '727a5a61', 'javelin-behavior-desktop-notifications-control' => '070679fe', 'javelin-behavior-detect-timezone' => '78bc5d94', @@ -627,7 +629,8 @@ return array( 'javelin-behavior-icon-composer' => '38a6cedb', 'javelin-behavior-launch-icon-composer' => 'a17b84f1', 'javelin-behavior-lightbox-attachments' => 'c7e748bf', - 'javelin-behavior-line-chart' => 'c8147a20', + 'javelin-behavior-line-chart' => 'ad258e28', + 'javelin-behavior-line-chart-legacy' => 'faf3ab6b', 'javelin-behavior-linked-container' => '74446546', 'javelin-behavior-maniphest-batch-selector' => '139ef688', 'javelin-behavior-maniphest-list-editor' => 'c687e867', @@ -697,6 +700,7 @@ return array( 'javelin-behavior-user-menu' => '60cd9241', 'javelin-behavior-view-placeholder' => 'a9942052', 'javelin-behavior-workflow' => '9623adc1', + 'javelin-chart' => 'fcb0c07d', 'javelin-color' => '78f811c9', 'javelin-cookie' => '05d290ef', 'javelin-diffusion-locate-file-source' => '94243d89', @@ -731,7 +735,7 @@ return array( 'javelin-typeahead-source' => '8badee71', 'javelin-typeahead-static-source' => '80bff3af', 'javelin-uri' => '2e255291', - 'javelin-util' => '22ae1776', + 'javelin-util' => 'edb4d8c9', 'javelin-vector' => 'e9c80beb', 'javelin-view' => '289bf236', 'javelin-view-html' => 'f8c4e135', @@ -759,7 +763,7 @@ return array( 'path-typeahead' => 'ad486db3', 'people-picture-menu-item-css' => 'fe8e07cf', 'people-profile-css' => '2ea2daa1', - 'phabricator-action-list-view-css' => 'c4972757', + 'phabricator-action-list-view-css' => 'e820263c', 'phabricator-busy' => '5202e831', 'phabricator-chatlog-css' => 'abdc76ee', 'phabricator-content-source-view-css' => 'cdf0d579', @@ -767,7 +771,7 @@ return array( 'phabricator-countdown-css' => 'bff8012f', 'phabricator-darklog' => '3b869402', 'phabricator-darkmessage' => '26cd4b73', - 'phabricator-dashboard-css' => '4267d6c6', + 'phabricator-dashboard-css' => '5a205b9d', 'phabricator-diff-changeset' => 'd0a85a85', 'phabricator-diff-changeset-list' => '04023d82', 'phabricator-diff-inline' => 'a4a14a94', @@ -781,7 +785,7 @@ return array( 'phabricator-flag-css' => '2b77be8d', 'phabricator-keyboard-shortcut' => 'c9749dcd', 'phabricator-keyboard-shortcut-manager' => '37b8a04a', - 'phabricator-main-menu-view' => '69c1c2c1', + 'phabricator-main-menu-view' => 'c6d35b35', 'phabricator-nav-view-css' => 'f8a0c1bf', 'phabricator-notification' => 'a9b91e3f', 'phabricator-notification-css' => '30240bd2', @@ -801,7 +805,7 @@ return array( 'phabricator-ui-example-css' => 'b4795059', 'phabricator-welcome-page' => 'a641fcc9', 'phabricator-zindex-css' => '99c0f5eb', - 'phame-css' => '799febf9', + 'phame-css' => 'bb442327', 'pholio-css' => '88ef5ef1', 'pholio-edit-css' => '4df55b3b', 'pholio-inline-comments-css' => '722b48c2', @@ -845,18 +849,18 @@ return array( 'phui-icon-set-selector-css' => '7aa5f3ec', 'phui-icon-view-css' => '4cbc684a', 'phui-image-mask-css' => '62c7f4d2', - 'phui-info-view-css' => '37b8d9ce', + 'phui-info-view-css' => 'a10a909b', 'phui-inline-comment-view-css' => '48acce5b', 'phui-invisible-character-view-css' => 'c694c4a4', 'phui-left-right-css' => '68513c34', 'phui-lightbox-css' => '4ebf22da', - 'phui-list-view-css' => '470b1adb', + 'phui-list-view-css' => 'b05144dd', 'phui-object-box-css' => 'f434b6be', - 'phui-oi-big-ui-css' => '534f1757', + 'phui-oi-big-ui-css' => 'fa74cc35', 'phui-oi-color-css' => 'b517bfa0', 'phui-oi-drag-ui-css' => 'da15d3dc', 'phui-oi-flush-ui-css' => '490e2e2e', - 'phui-oi-list-view-css' => 'f14f2422', + 'phui-oi-list-view-css' => 'd7723ecc', 'phui-oi-simple-ui-css' => '6a30fa46', 'phui-pager-css' => 'd022c7ad', 'phui-pinboard-view-css' => '1f08f5d8', @@ -865,7 +869,7 @@ return array( 'phui-segment-bar-view-css' => '5166b370', 'phui-spacing-css' => 'b05cadc3', 'phui-status-list-view-css' => 'e5ff8be0', - 'phui-tag-view-css' => '29409667', + 'phui-tag-view-css' => '8519160a', 'phui-theme-css' => '35883b37', 'phui-timeline-view-css' => '1e348e4b', 'phui-two-column-view-css' => '01e6991e', @@ -877,7 +881,7 @@ return array( 'phuix-action-view' => 'aaa08f3b', 'phuix-autocomplete' => '2fbe234d', 'phuix-button-view' => '55a24e84', - 'phuix-dropdown-menu' => 'bdce4d78', + 'phuix-dropdown-menu' => '7acfd98b', 'phuix-form-control-view' => '38c1f3fb', 'phuix-icon-view' => 'a5257c4e', 'policy-css' => 'ceb56a08', @@ -885,7 +889,7 @@ return array( 'policy-transaction-detail-css' => 'c02b8384', 'ponder-view-css' => '05a09d0a', 'project-card-view-css' => '4e7371cd', - 'project-triggers-css' => 'cb866c2d', + 'project-triggers-css' => 'cd9c8bb9', 'project-view-css' => '567858b3', 'releeph-core' => 'f81ff2db', 'releeph-preview-branch' => '22db5c07', @@ -897,7 +901,7 @@ return array( 'syntax-default-css' => '055fc231', 'syntax-highlighting-css' => '4234f572', 'tokens-css' => 'ce5a50bd', - 'trigger-rule' => '1c60c3fc', + 'trigger-rule' => '41b7b4f6', 'trigger-rule-control' => '5faf27b9', 'trigger-rule-editor' => 'b49fd60c', 'trigger-rule-type' => '4feea7d3', @@ -905,6 +909,11 @@ return array( 'unhandled-exception-css' => '9ecfc00d', ), 'requires' => array( + '0116d3e8' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + ), '01384686' => array( 'javelin-behavior', 'javelin-uri', @@ -962,14 +971,6 @@ return array( 'javelin-request', 'javelin-uri', ), - '076bd092' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-workflow', - 'phabricator-draggable-list', - ), '0889b835' => array( 'javelin-install', 'javelin-event', @@ -980,11 +981,6 @@ return array( 'herald-rule-editor', 'javelin-behavior', ), - '09ecf50c' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-workflow', - ), '0ad8d31f' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1032,6 +1028,10 @@ return array( 'javelin-stratcom', 'javelin-util', ), + '1b6acc2a' => array( + 'javelin-magical-init', + 'javelin-util', + ), '1c850a26' => array( 'javelin-install', 'javelin-util', @@ -1378,9 +1378,6 @@ return array( 'javelin-dom', 'javelin-fx', ), - '534f1757' => array( - 'phui-oi-list-view-css', - ), '541f81c3' => array( 'javelin-install', ), @@ -1474,9 +1471,6 @@ return array( 'javelin-stratcom', 'javelin-dom', ), - '69c1c2c1' => array( - 'phui-theme-css', - ), '6a1583a8' => array( 'javelin-behavior', 'javelin-history', @@ -1568,6 +1562,13 @@ return array( 'javelin-install', 'javelin-dom', ), + '7acfd98b' => array( + 'javelin-install', + 'javelin-util', + 'javelin-dom', + 'javelin-vector', + 'javelin-stratcom', + ), '7ad020a5' => array( 'javelin-behavior', 'javelin-dom', @@ -1731,10 +1732,10 @@ return array( 'javelin-install', 'javelin-util', ), - '9b1cbd76' => array( + '9c01e364' => array( 'javelin-behavior', 'javelin-dom', - 'javelin-stratcom', + 'javelin-workflow', ), '9cec214e' => array( 'javelin-behavior', @@ -1760,6 +1761,14 @@ return array( 'a241536a' => array( 'javelin-install', ), + 'a2ab19be' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-workflow', + 'phabricator-draggable-list', + ), 'a4356cde' => array( 'javelin-install', 'javelin-dom', @@ -1850,6 +1859,11 @@ return array( 'javelin-request', 'javelin-router', ), + 'ad258e28' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-chart', + ), 'ad486db3' => array( 'javelin-install', 'javelin-typeahead', @@ -1924,13 +1938,6 @@ return array( 'javelin-uri', 'phabricator-notification', ), - 'bdce4d78' => array( - 'javelin-install', - 'javelin-util', - 'javelin-dom', - 'javelin-vector', - 'javelin-stratcom', - ), 'bde53589' => array( 'phui-inline-comment-view-css', ), @@ -1983,6 +1990,9 @@ return array( 'javelin-install', 'javelin-dom', ), + 'c6d35b35' => array( + 'phui-theme-css', + ), 'c715c123' => array( 'javelin-behavior', 'javelin-dom', @@ -1999,12 +2009,6 @@ return array( 'phuix-icon-view', 'phabricator-busy', ), - 'c8147a20' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-vector', - 'phui-chart-css', - ), 'c9749dcd' => array( 'javelin-install', 'javelin-util', @@ -2178,9 +2182,18 @@ return array( 'phabricator-keyboard-shortcut', 'conpherence-thread-manager', ), - 'fce5d170' => array( - 'javelin-magical-init', - 'javelin-util', + 'fa74cc35' => array( + 'phui-oi-list-view-css', + ), + 'faf3ab6b' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-vector', + 'phui-chart-css', + ), + 'fcb0c07d' => array( + 'phui-chart-css', + 'd3', ), 'fdc13e4e' => array( 'javelin-install', diff --git a/resources/sql/autopatches/20190410.portals.01.ferret.doc.sql b/resources/sql/autopatches/20190410.portals.01.ferret.doc.sql new file mode 100644 index 0000000000..984f3196f9 --- /dev/null +++ b/resources/sql/autopatches/20190410.portals.01.ferret.doc.sql @@ -0,0 +1,9 @@ +CREATE TABLE {$NAMESPACE}_dashboard.dashboard_portal_fdocument ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectPHID VARBINARY(64) NOT NULL, + isClosed BOOL NOT NULL, + authorPHID VARBINARY(64), + ownerPHID VARBINARY(64), + epochCreated INT UNSIGNED NOT NULL, + epochModified INT UNSIGNED NOT NULL +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20190410.portals.02.ferret.field.sql b/resources/sql/autopatches/20190410.portals.02.ferret.field.sql new file mode 100644 index 0000000000..af02b8f0d6 --- /dev/null +++ b/resources/sql/autopatches/20190410.portals.02.ferret.field.sql @@ -0,0 +1,8 @@ +CREATE TABLE {$NAMESPACE}_dashboard.dashboard_portal_ffield ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + fieldKey VARCHAR(4) NOT NULL COLLATE {$COLLATE_TEXT}, + rawCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + termCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + normalCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20190410.portals.03.ferret.ngrams.sql b/resources/sql/autopatches/20190410.portals.03.ferret.ngrams.sql new file mode 100644 index 0000000000..37a5fc80ab --- /dev/null +++ b/resources/sql/autopatches/20190410.portals.03.ferret.ngrams.sql @@ -0,0 +1,5 @@ +CREATE TABLE {$NAMESPACE}_dashboard.dashboard_portal_fngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20190410.portals.04.ferret.cngrams.sql b/resources/sql/autopatches/20190410.portals.04.ferret.cngrams.sql new file mode 100644 index 0000000000..678af664bf --- /dev/null +++ b/resources/sql/autopatches/20190410.portals.04.ferret.cngrams.sql @@ -0,0 +1,7 @@ +CREATE TABLE {$NAMESPACE}_dashboard.dashboard_portal_fngrams_common ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT}, + needsCollection BOOL NOT NULL, + UNIQUE KEY `key_ngram` (ngram), + KEY `key_collect` (needsCollection) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20190412.dashboard.01.panels.php b/resources/sql/autopatches/20190412.dashboard.01.panels.php new file mode 100644 index 0000000000..dad132356b --- /dev/null +++ b/resources/sql/autopatches/20190412.dashboard.01.panels.php @@ -0,0 +1,81 @@ +establishConnection('r'); +$table_name = $dashboard_table->getTableName(); + +$rows = new LiskRawMigrationIterator($conn, $table_name); +foreach ($rows as $row) { + $config = $row['layoutConfig']; + + try { + $config = phutil_json_decode($config); + } catch (Exception $ex) { + $config = array(); + } + + if (!is_array($config)) { + $config = array(); + } + + $panels = idx($config, 'panelLocations'); + if (!is_array($panels)) { + $panels = array(); + } + + if (idx($config, 'layoutMode') === 'layout-mode-full') { + $column_map = array( + 0 => 'main', + ); + } else { + $column_map = array( + 0 => 'left', + 1 => 'right', + ); + } + + $panel_list = array(); + foreach ($panels as $column_idx => $panel_phids) { + $column_key = idx($column_map, $column_idx, 'unknown'); + foreach ($panel_phids as $panel_phid) { + $panel_list[] = array( + 'panelKey' => Filesystem::readRandomCharacters(8), + 'columnKey' => $column_key, + 'panelPHID' => $panel_phid, + ); + } + } + unset($config['panelLocations']); + $config['panels'] = $panel_list; + + queryfx( + $conn, + 'UPDATE %R SET layoutConfig = %s WHERE id = %d', + $dashboard_table, + phutil_json_encode($config), + $row['id']); +} diff --git a/resources/sql/autopatches/20190412.dashboard.02.install.sql b/resources/sql/autopatches/20190412.dashboard.02.install.sql new file mode 100644 index 0000000000..be3363d5c3 --- /dev/null +++ b/resources/sql/autopatches/20190412.dashboard.02.install.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS {$NAMESPACE}_dashboard.dashboard_install; diff --git a/resources/sql/autopatches/20190412.dashboard.03.dashngrams.sql b/resources/sql/autopatches/20190412.dashboard.03.dashngrams.sql new file mode 100644 index 0000000000..2f1b572a8b --- /dev/null +++ b/resources/sql/autopatches/20190412.dashboard.03.dashngrams.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS {$NAMESPACE}_dashboard.dashboard_dashboard_ngrams; diff --git a/resources/sql/autopatches/20190412.dashboard.04.panelngrams.sql b/resources/sql/autopatches/20190412.dashboard.04.panelngrams.sql new file mode 100644 index 0000000000..e06d817c6e --- /dev/null +++ b/resources/sql/autopatches/20190412.dashboard.04.panelngrams.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS {$NAMESPACE}_dashboard.dashboard_dashboardpanel_ngrams; diff --git a/resources/sql/autopatches/20190412.dashboard.05.dferret.doc.sql b/resources/sql/autopatches/20190412.dashboard.05.dferret.doc.sql new file mode 100644 index 0000000000..2073a5b578 --- /dev/null +++ b/resources/sql/autopatches/20190412.dashboard.05.dferret.doc.sql @@ -0,0 +1,9 @@ +CREATE TABLE {$NAMESPACE}_dashboard.dashboard_dashboard_fdocument ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectPHID VARBINARY(64) NOT NULL, + isClosed BOOL NOT NULL, + authorPHID VARBINARY(64), + ownerPHID VARBINARY(64), + epochCreated INT UNSIGNED NOT NULL, + epochModified INT UNSIGNED NOT NULL +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20190412.dashboard.06.dferret.field.sql b/resources/sql/autopatches/20190412.dashboard.06.dferret.field.sql new file mode 100644 index 0000000000..b8845f5686 --- /dev/null +++ b/resources/sql/autopatches/20190412.dashboard.06.dferret.field.sql @@ -0,0 +1,8 @@ +CREATE TABLE {$NAMESPACE}_dashboard.dashboard_dashboard_ffield ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + fieldKey VARCHAR(4) NOT NULL COLLATE {$COLLATE_TEXT}, + rawCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + termCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + normalCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20190412.dashboard.07.dferret.ngrams.sql b/resources/sql/autopatches/20190412.dashboard.07.dferret.ngrams.sql new file mode 100644 index 0000000000..3279e7dc27 --- /dev/null +++ b/resources/sql/autopatches/20190412.dashboard.07.dferret.ngrams.sql @@ -0,0 +1,5 @@ +CREATE TABLE {$NAMESPACE}_dashboard.dashboard_dashboard_fngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20190412.dashboard.08.dferret.cngrams.sql b/resources/sql/autopatches/20190412.dashboard.08.dferret.cngrams.sql new file mode 100644 index 0000000000..0ee815d175 --- /dev/null +++ b/resources/sql/autopatches/20190412.dashboard.08.dferret.cngrams.sql @@ -0,0 +1,7 @@ +CREATE TABLE {$NAMESPACE}_dashboard.dashboard_dashboard_fngrams_common ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT}, + needsCollection BOOL NOT NULL, + UNIQUE KEY `key_ngram` (ngram), + KEY `key_collect` (needsCollection) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20190412.dashboard.09.pferret.doc.sql b/resources/sql/autopatches/20190412.dashboard.09.pferret.doc.sql new file mode 100644 index 0000000000..827a4245ed --- /dev/null +++ b/resources/sql/autopatches/20190412.dashboard.09.pferret.doc.sql @@ -0,0 +1,9 @@ +CREATE TABLE {$NAMESPACE}_dashboard.dashboard_panel_fdocument ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectPHID VARBINARY(64) NOT NULL, + isClosed BOOL NOT NULL, + authorPHID VARBINARY(64), + ownerPHID VARBINARY(64), + epochCreated INT UNSIGNED NOT NULL, + epochModified INT UNSIGNED NOT NULL +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20190412.dashboard.10.pferret.field.sql b/resources/sql/autopatches/20190412.dashboard.10.pferret.field.sql new file mode 100644 index 0000000000..f63521d87b --- /dev/null +++ b/resources/sql/autopatches/20190412.dashboard.10.pferret.field.sql @@ -0,0 +1,8 @@ +CREATE TABLE {$NAMESPACE}_dashboard.dashboard_panel_ffield ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + fieldKey VARCHAR(4) NOT NULL COLLATE {$COLLATE_TEXT}, + rawCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + termCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + normalCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20190412.dashboard.11.pferret.ngrams.sql b/resources/sql/autopatches/20190412.dashboard.11.pferret.ngrams.sql new file mode 100644 index 0000000000..a197ec0272 --- /dev/null +++ b/resources/sql/autopatches/20190412.dashboard.11.pferret.ngrams.sql @@ -0,0 +1,5 @@ +CREATE TABLE {$NAMESPACE}_dashboard.dashboard_panel_fngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20190412.dashboard.12.pferret.cngrams.sql b/resources/sql/autopatches/20190412.dashboard.12.pferret.cngrams.sql new file mode 100644 index 0000000000..95426fcf1d --- /dev/null +++ b/resources/sql/autopatches/20190412.dashboard.12.pferret.cngrams.sql @@ -0,0 +1,7 @@ +CREATE TABLE {$NAMESPACE}_dashboard.dashboard_panel_fngrams_common ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT}, + needsCollection BOOL NOT NULL, + UNIQUE KEY `key_ngram` (ngram), + KEY `key_collect` (needsCollection) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20190412.dashboard.13.rebuild.php b/resources/sql/autopatches/20190412.dashboard.13.rebuild.php new file mode 100644 index 0000000000..ed32a9137f --- /dev/null +++ b/resources/sql/autopatches/20190412.dashboard.13.rebuild.php @@ -0,0 +1,7 @@ + 'applications/differential/field/DifferentialRevisionIDCommitMessageField.php', 'DifferentialRevisionInlineTransaction' => 'applications/differential/xaction/DifferentialRevisionInlineTransaction.php', 'DifferentialRevisionInlinesController' => 'applications/differential/controller/DifferentialRevisionInlinesController.php', + 'DifferentialRevisionJIRAIssueURIsHeraldField' => 'applications/differential/herald/DifferentialRevisionJIRAIssueURIsHeraldField.php', 'DifferentialRevisionListController' => 'applications/differential/controller/DifferentialRevisionListController.php', 'DifferentialRevisionListView' => 'applications/differential/view/DifferentialRevisionListView.php', 'DifferentialRevisionMailReceiver' => 'applications/differential/mail/DifferentialRevisionMailReceiver.php', @@ -687,7 +688,6 @@ phutil_register_library_map(array( 'DiffusionBranchListView' => 'applications/diffusion/view/DiffusionBranchListView.php', 'DiffusionBranchQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionBranchQueryConduitAPIMethod.php', 'DiffusionBranchTableController' => 'applications/diffusion/controller/DiffusionBranchTableController.php', - 'DiffusionBranchTableView' => 'applications/diffusion/view/DiffusionBranchTableView.php', 'DiffusionBrowseController' => 'applications/diffusion/controller/DiffusionBrowseController.php', 'DiffusionBrowseQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionBrowseQueryConduitAPIMethod.php', 'DiffusionBrowseResultSet' => 'applications/diffusion/data/DiffusionBrowseResultSet.php', @@ -764,6 +764,7 @@ phutil_register_library_map(array( 'DiffusionCommitRevisionAcceptedHeraldField' => 'applications/diffusion/herald/DiffusionCommitRevisionAcceptedHeraldField.php', 'DiffusionCommitRevisionAcceptingReviewersHeraldField' => 'applications/diffusion/herald/DiffusionCommitRevisionAcceptingReviewersHeraldField.php', 'DiffusionCommitRevisionHeraldField' => 'applications/diffusion/herald/DiffusionCommitRevisionHeraldField.php', + 'DiffusionCommitRevisionQuery' => 'applications/diffusion/query/DiffusionCommitRevisionQuery.php', 'DiffusionCommitRevisionReviewersHeraldField' => 'applications/diffusion/herald/DiffusionCommitRevisionReviewersHeraldField.php', 'DiffusionCommitRevisionSubscribersHeraldField' => 'applications/diffusion/herald/DiffusionCommitRevisionSubscribersHeraldField.php', 'DiffusionCommitSearchConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionCommitSearchConduitAPIMethod.php', @@ -812,6 +813,11 @@ phutil_register_library_map(array( 'DiffusionGitResponse' => 'applications/diffusion/response/DiffusionGitResponse.php', 'DiffusionGitSSHWorkflow' => 'applications/diffusion/ssh/DiffusionGitSSHWorkflow.php', 'DiffusionGitUploadPackSSHWorkflow' => 'applications/diffusion/ssh/DiffusionGitUploadPackSSHWorkflow.php', + 'DiffusionGitUploadPackWireProtocol' => 'applications/diffusion/protocol/DiffusionGitUploadPackWireProtocol.php', + 'DiffusionGitWireProtocol' => 'applications/diffusion/protocol/DiffusionGitWireProtocol.php', + 'DiffusionGitWireProtocolCapabilities' => 'applications/diffusion/protocol/DiffusionGitWireProtocolCapabilities.php', + 'DiffusionGitWireProtocolRef' => 'applications/diffusion/protocol/DiffusionGitWireProtocolRef.php', + 'DiffusionGitWireProtocolRefList' => 'applications/diffusion/protocol/DiffusionGitWireProtocolRefList.php', 'DiffusionGraphController' => 'applications/diffusion/controller/DiffusionGraphController.php', 'DiffusionHistoryController' => 'applications/diffusion/controller/DiffusionHistoryController.php', 'DiffusionHistoryListView' => 'applications/diffusion/view/DiffusionHistoryListView.php', @@ -929,7 +935,6 @@ phutil_register_library_map(array( 'DiffusionRefTableController' => 'applications/diffusion/controller/DiffusionRefTableController.php', 'DiffusionRefsQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionRefsQueryConduitAPIMethod.php', 'DiffusionRenameHistoryQuery' => 'applications/diffusion/query/DiffusionRenameHistoryQuery.php', - 'DiffusionRepositoryActionsManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryActionsManagementPanel.php', 'DiffusionRepositoryAutomationManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryAutomationManagementPanel.php', 'DiffusionRepositoryBasicsManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php', 'DiffusionRepositoryBranchesManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php', @@ -946,6 +951,7 @@ phutil_register_library_map(array( 'DiffusionRepositoryEditDeleteController' => 'applications/diffusion/controller/DiffusionRepositoryEditDeleteController.php', 'DiffusionRepositoryEditEngine' => 'applications/diffusion/editor/DiffusionRepositoryEditEngine.php', 'DiffusionRepositoryEditEnormousController' => 'applications/diffusion/controller/DiffusionRepositoryEditEnormousController.php', + 'DiffusionRepositoryEditPublishingController' => 'applications/diffusion/controller/DiffusionRepositoryEditPublishingController.php', 'DiffusionRepositoryEditUpdateController' => 'applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php', 'DiffusionRepositoryFunctionDatasource' => 'applications/diffusion/typeahead/DiffusionRepositoryFunctionDatasource.php', 'DiffusionRepositoryHistoryManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryHistoryManagementPanel.php', @@ -1013,6 +1019,7 @@ phutil_register_library_map(array( 'DiffusionURIEditor' => 'applications/diffusion/editor/DiffusionURIEditor.php', 'DiffusionURITestCase' => 'applications/diffusion/request/__tests__/DiffusionURITestCase.php', 'DiffusionUpdateCoverageConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionUpdateCoverageConduitAPIMethod.php', + 'DiffusionUpdateObjectAfterCommitWorker' => 'applications/diffusion/worker/DiffusionUpdateObjectAfterCommitWorker.php', 'DiffusionView' => 'applications/diffusion/view/DiffusionView.php', 'DivinerArticleAtomizer' => 'applications/diviner/atomizer/DivinerArticleAtomizer.php', 'DivinerAtom' => 'applications/diviner/atom/DivinerAtom.php', @@ -2308,12 +2315,14 @@ phutil_register_library_map(array( 'PhabricatorAuthManagementLDAPWorkflow' => 'applications/auth/management/PhabricatorAuthManagementLDAPWorkflow.php', 'PhabricatorAuthManagementListFactorsWorkflow' => 'applications/auth/management/PhabricatorAuthManagementListFactorsWorkflow.php', 'PhabricatorAuthManagementListMFAProvidersWorkflow' => 'applications/auth/management/PhabricatorAuthManagementListMFAProvidersWorkflow.php', + 'PhabricatorAuthManagementLockWorkflow' => 'applications/auth/management/PhabricatorAuthManagementLockWorkflow.php', 'PhabricatorAuthManagementRecoverWorkflow' => 'applications/auth/management/PhabricatorAuthManagementRecoverWorkflow.php', 'PhabricatorAuthManagementRefreshWorkflow' => 'applications/auth/management/PhabricatorAuthManagementRefreshWorkflow.php', 'PhabricatorAuthManagementRevokeWorkflow' => 'applications/auth/management/PhabricatorAuthManagementRevokeWorkflow.php', 'PhabricatorAuthManagementStripWorkflow' => 'applications/auth/management/PhabricatorAuthManagementStripWorkflow.php', 'PhabricatorAuthManagementTrustOAuthClientWorkflow' => 'applications/auth/management/PhabricatorAuthManagementTrustOAuthClientWorkflow.php', 'PhabricatorAuthManagementUnlimitWorkflow' => 'applications/auth/management/PhabricatorAuthManagementUnlimitWorkflow.php', + 'PhabricatorAuthManagementUnlockWorkflow' => 'applications/auth/management/PhabricatorAuthManagementUnlockWorkflow.php', 'PhabricatorAuthManagementUntrustOAuthClientWorkflow' => 'applications/auth/management/PhabricatorAuthManagementUntrustOAuthClientWorkflow.php', 'PhabricatorAuthManagementVerifyWorkflow' => 'applications/auth/management/PhabricatorAuthManagementVerifyWorkflow.php', 'PhabricatorAuthManagementWorkflow' => 'applications/auth/management/PhabricatorAuthManagementWorkflow.php', @@ -2645,6 +2654,11 @@ phutil_register_library_map(array( 'PhabricatorChangeParserTestCase' => 'applications/repository/worker/__tests__/PhabricatorChangeParserTestCase.php', 'PhabricatorChangesetCachePurger' => 'applications/cache/purger/PhabricatorChangesetCachePurger.php', 'PhabricatorChangesetResponse' => 'infrastructure/diff/PhabricatorChangesetResponse.php', + 'PhabricatorChartAxis' => 'applications/fact/chart/PhabricatorChartAxis.php', + 'PhabricatorChartDataQuery' => 'applications/fact/chart/PhabricatorChartDataQuery.php', + 'PhabricatorChartFunction' => 'applications/fact/chart/PhabricatorChartFunction.php', + 'PhabricatorChartFunctionArgument' => 'applications/fact/chart/PhabricatorChartFunctionArgument.php', + 'PhabricatorChartFunctionArgumentParser' => 'applications/fact/chart/PhabricatorChartFunctionArgumentParser.php', 'PhabricatorChatLogApplication' => 'applications/chatlog/application/PhabricatorChatLogApplication.php', 'PhabricatorChatLogChannel' => 'applications/chatlog/storage/PhabricatorChatLogChannel.php', 'PhabricatorChatLogChannelListController' => 'applications/chatlog/controller/PhabricatorChatLogChannelListController.php', @@ -2796,6 +2810,7 @@ phutil_register_library_map(array( 'PhabricatorConpherenceWidgetVisibleSetting' => 'applications/settings/setting/PhabricatorConpherenceWidgetVisibleSetting.php', 'PhabricatorConsoleApplication' => 'applications/console/application/PhabricatorConsoleApplication.php', 'PhabricatorConsoleContentSource' => 'infrastructure/contentsource/PhabricatorConsoleContentSource.php', + 'PhabricatorConstantChartFunction' => 'applications/fact/chart/PhabricatorConstantChartFunction.php', 'PhabricatorContactNumbersSettingsPanel' => 'applications/settings/panel/PhabricatorContactNumbersSettingsPanel.php', 'PhabricatorContentSource' => 'infrastructure/contentsource/PhabricatorContentSource.php', 'PhabricatorContentSourceModule' => 'infrastructure/contentsource/PhabricatorContentSourceModule.php', @@ -2807,6 +2822,7 @@ phutil_register_library_map(array( 'PhabricatorCoreCreateTransaction' => 'applications/transactions/xaction/PhabricatorCoreCreateTransaction.php', 'PhabricatorCoreTransactionType' => 'applications/transactions/xaction/PhabricatorCoreTransactionType.php', 'PhabricatorCoreVoidTransaction' => 'applications/transactions/xaction/PhabricatorCoreVoidTransaction.php', + 'PhabricatorCosChartFunction' => 'applications/fact/chart/PhabricatorCosChartFunction.php', 'PhabricatorCountFact' => 'applications/fact/fact/PhabricatorCountFact.php', 'PhabricatorCountdown' => 'applications/countdown/storage/PhabricatorCountdown.php', 'PhabricatorCountdownApplication' => 'applications/countdown/application/PhabricatorCountdownApplication.php', @@ -2907,56 +2923,74 @@ phutil_register_library_map(array( 'PhabricatorDarkConsoleTabSetting' => 'applications/settings/setting/PhabricatorDarkConsoleTabSetting.php', 'PhabricatorDarkConsoleVisibleSetting' => 'applications/settings/setting/PhabricatorDarkConsoleVisibleSetting.php', 'PhabricatorDashboard' => 'applications/dashboard/storage/PhabricatorDashboard.php', - 'PhabricatorDashboardAddPanelController' => 'applications/dashboard/controller/PhabricatorDashboardAddPanelController.php', + 'PhabricatorDashboardAdjustController' => 'applications/dashboard/controller/dashboard/PhabricatorDashboardAdjustController.php', 'PhabricatorDashboardApplication' => 'applications/dashboard/application/PhabricatorDashboardApplication.php', - 'PhabricatorDashboardArchiveController' => 'applications/dashboard/controller/PhabricatorDashboardArchiveController.php', - 'PhabricatorDashboardArrangeController' => 'applications/dashboard/controller/PhabricatorDashboardArrangeController.php', + 'PhabricatorDashboardApplicationInstallWorkflow' => 'applications/dashboard/install/PhabricatorDashboardApplicationInstallWorkflow.php', + 'PhabricatorDashboardArchiveController' => 'applications/dashboard/controller/dashboard/PhabricatorDashboardArchiveController.php', + 'PhabricatorDashboardColumn' => 'applications/dashboard/layoutconfig/PhabricatorDashboardColumn.php', 'PhabricatorDashboardConsoleController' => 'applications/dashboard/controller/PhabricatorDashboardConsoleController.php', 'PhabricatorDashboardController' => 'applications/dashboard/controller/PhabricatorDashboardController.php', 'PhabricatorDashboardDAO' => 'applications/dashboard/storage/PhabricatorDashboardDAO.php', - 'PhabricatorDashboardDashboardHasPanelEdgeType' => 'applications/dashboard/edge/PhabricatorDashboardDashboardHasPanelEdgeType.php', 'PhabricatorDashboardDashboardPHIDType' => 'applications/dashboard/phid/PhabricatorDashboardDashboardPHIDType.php', 'PhabricatorDashboardDatasource' => 'applications/dashboard/typeahead/PhabricatorDashboardDatasource.php', - 'PhabricatorDashboardEditController' => 'applications/dashboard/controller/PhabricatorDashboardEditController.php', + 'PhabricatorDashboardEditController' => 'applications/dashboard/controller/dashboard/PhabricatorDashboardEditController.php', + 'PhabricatorDashboardEditEngine' => 'applications/dashboard/editor/PhabricatorDashboardEditEngine.php', + 'PhabricatorDashboardFavoritesInstallWorkflow' => 'applications/dashboard/install/PhabricatorDashboardFavoritesInstallWorkflow.php', + 'PhabricatorDashboardFerretEngine' => 'applications/dashboard/engine/PhabricatorDashboardFerretEngine.php', + 'PhabricatorDashboardFullLayoutMode' => 'applications/dashboard/layoutconfig/PhabricatorDashboardFullLayoutMode.php', + 'PhabricatorDashboardFulltextEngine' => 'applications/dashboard/engine/PhabricatorDashboardFulltextEngine.php', + 'PhabricatorDashboardHalfLayoutMode' => 'applications/dashboard/layoutconfig/PhabricatorDashboardHalfLayoutMode.php', + 'PhabricatorDashboardHomeInstallWorkflow' => 'applications/dashboard/install/PhabricatorDashboardHomeInstallWorkflow.php', 'PhabricatorDashboardIconSet' => 'applications/dashboard/icon/PhabricatorDashboardIconSet.php', - 'PhabricatorDashboardInstall' => 'applications/dashboard/storage/PhabricatorDashboardInstall.php', - 'PhabricatorDashboardInstallController' => 'applications/dashboard/controller/PhabricatorDashboardInstallController.php', - 'PhabricatorDashboardLayoutConfig' => 'applications/dashboard/layoutconfig/PhabricatorDashboardLayoutConfig.php', + 'PhabricatorDashboardIconTransaction' => 'applications/dashboard/xaction/dashboard/PhabricatorDashboardIconTransaction.php', + 'PhabricatorDashboardInstallController' => 'applications/dashboard/controller/dashboard/PhabricatorDashboardInstallController.php', + 'PhabricatorDashboardInstallWorkflow' => 'applications/dashboard/install/PhabricatorDashboardInstallWorkflow.php', + 'PhabricatorDashboardLayoutMode' => 'applications/dashboard/layoutconfig/PhabricatorDashboardLayoutMode.php', + 'PhabricatorDashboardLayoutTransaction' => 'applications/dashboard/xaction/dashboard/PhabricatorDashboardLayoutTransaction.php', 'PhabricatorDashboardListController' => 'applications/dashboard/controller/PhabricatorDashboardListController.php', - 'PhabricatorDashboardManageController' => 'applications/dashboard/controller/PhabricatorDashboardManageController.php', - 'PhabricatorDashboardMovePanelController' => 'applications/dashboard/controller/PhabricatorDashboardMovePanelController.php', - 'PhabricatorDashboardNgrams' => 'applications/dashboard/storage/PhabricatorDashboardNgrams.php', + 'PhabricatorDashboardNameTransaction' => 'applications/dashboard/xaction/dashboard/PhabricatorDashboardNameTransaction.php', + 'PhabricatorDashboardObjectInstallWorkflow' => 'applications/dashboard/install/PhabricatorDashboardObjectInstallWorkflow.php', + 'PhabricatorDashboardOneThirdLayoutMode' => 'applications/dashboard/layoutconfig/PhabricatorDashboardOneThirdLayoutMode.php', 'PhabricatorDashboardPanel' => 'applications/dashboard/storage/PhabricatorDashboardPanel.php', - 'PhabricatorDashboardPanelArchiveController' => 'applications/dashboard/controller/PhabricatorDashboardPanelArchiveController.php', - 'PhabricatorDashboardPanelCoreCustomField' => 'applications/dashboard/customfield/PhabricatorDashboardPanelCoreCustomField.php', - 'PhabricatorDashboardPanelCustomField' => 'applications/dashboard/customfield/PhabricatorDashboardPanelCustomField.php', + 'PhabricatorDashboardPanelArchiveController' => 'applications/dashboard/controller/panel/PhabricatorDashboardPanelArchiveController.php', + 'PhabricatorDashboardPanelContainerIndexEngineExtension' => 'applications/dashboard/engineextension/PhabricatorDashboardPanelContainerIndexEngineExtension.php', + 'PhabricatorDashboardPanelContainerInterface' => 'applications/dashboard/interface/PhabricatorDashboardPanelContainerInterface.php', 'PhabricatorDashboardPanelDatasource' => 'applications/dashboard/typeahead/PhabricatorDashboardPanelDatasource.php', 'PhabricatorDashboardPanelEditConduitAPIMethod' => 'applications/dashboard/conduit/PhabricatorDashboardPanelEditConduitAPIMethod.php', - 'PhabricatorDashboardPanelEditController' => 'applications/dashboard/controller/PhabricatorDashboardPanelEditController.php', + 'PhabricatorDashboardPanelEditController' => 'applications/dashboard/controller/panel/PhabricatorDashboardPanelEditController.php', 'PhabricatorDashboardPanelEditEngine' => 'applications/dashboard/editor/PhabricatorDashboardPanelEditEngine.php', - 'PhabricatorDashboardPanelEditproController' => 'applications/dashboard/controller/PhabricatorDashboardPanelEditproController.php', - 'PhabricatorDashboardPanelHasDashboardEdgeType' => 'applications/dashboard/edge/PhabricatorDashboardPanelHasDashboardEdgeType.php', - 'PhabricatorDashboardPanelListController' => 'applications/dashboard/controller/PhabricatorDashboardPanelListController.php', - 'PhabricatorDashboardPanelNgrams' => 'applications/dashboard/storage/PhabricatorDashboardPanelNgrams.php', + 'PhabricatorDashboardPanelFerretEngine' => 'applications/dashboard/engine/PhabricatorDashboardPanelFerretEngine.php', + 'PhabricatorDashboardPanelFulltextEngine' => 'applications/dashboard/engine/PhabricatorDashboardPanelFulltextEngine.php', + 'PhabricatorDashboardPanelListController' => 'applications/dashboard/controller/panel/PhabricatorDashboardPanelListController.php', + 'PhabricatorDashboardPanelNameTransaction' => 'applications/dashboard/xaction/panel/PhabricatorDashboardPanelNameTransaction.php', 'PhabricatorDashboardPanelPHIDType' => 'applications/dashboard/phid/PhabricatorDashboardPanelPHIDType.php', + 'PhabricatorDashboardPanelPropertyTransaction' => 'applications/dashboard/xaction/panel/PhabricatorDashboardPanelPropertyTransaction.php', 'PhabricatorDashboardPanelQuery' => 'applications/dashboard/query/PhabricatorDashboardPanelQuery.php', - 'PhabricatorDashboardPanelRenderController' => 'applications/dashboard/controller/PhabricatorDashboardPanelRenderController.php', + 'PhabricatorDashboardPanelRef' => 'applications/dashboard/layoutconfig/PhabricatorDashboardPanelRef.php', + 'PhabricatorDashboardPanelRefList' => 'applications/dashboard/layoutconfig/PhabricatorDashboardPanelRefList.php', + 'PhabricatorDashboardPanelRenderController' => 'applications/dashboard/controller/panel/PhabricatorDashboardPanelRenderController.php', 'PhabricatorDashboardPanelRenderingEngine' => 'applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php', - 'PhabricatorDashboardPanelSearchApplicationCustomField' => 'applications/dashboard/customfield/PhabricatorDashboardPanelSearchApplicationCustomField.php', 'PhabricatorDashboardPanelSearchEngine' => 'applications/dashboard/query/PhabricatorDashboardPanelSearchEngine.php', - 'PhabricatorDashboardPanelSearchQueryCustomField' => 'applications/dashboard/customfield/PhabricatorDashboardPanelSearchQueryCustomField.php', - 'PhabricatorDashboardPanelTabsCustomField' => 'applications/dashboard/customfield/PhabricatorDashboardPanelTabsCustomField.php', + 'PhabricatorDashboardPanelStatusTransaction' => 'applications/dashboard/xaction/panel/PhabricatorDashboardPanelStatusTransaction.php', + 'PhabricatorDashboardPanelTabsController' => 'applications/dashboard/controller/panel/PhabricatorDashboardPanelTabsController.php', 'PhabricatorDashboardPanelTransaction' => 'applications/dashboard/storage/PhabricatorDashboardPanelTransaction.php', 'PhabricatorDashboardPanelTransactionEditor' => 'applications/dashboard/editor/PhabricatorDashboardPanelTransactionEditor.php', 'PhabricatorDashboardPanelTransactionQuery' => 'applications/dashboard/query/PhabricatorDashboardPanelTransactionQuery.php', + 'PhabricatorDashboardPanelTransactionType' => 'applications/dashboard/xaction/panel/PhabricatorDashboardPanelTransactionType.php', 'PhabricatorDashboardPanelType' => 'applications/dashboard/paneltype/PhabricatorDashboardPanelType.php', - 'PhabricatorDashboardPanelViewController' => 'applications/dashboard/controller/PhabricatorDashboardPanelViewController.php', + 'PhabricatorDashboardPanelUsedByObjectEdgeType' => 'applications/search/edge/PhabricatorDashboardPanelUsedByObjectEdgeType.php', + 'PhabricatorDashboardPanelViewController' => 'applications/dashboard/controller/panel/PhabricatorDashboardPanelViewController.php', + 'PhabricatorDashboardPanelsTransaction' => 'applications/dashboard/xaction/dashboard/PhabricatorDashboardPanelsTransaction.php', 'PhabricatorDashboardPortal' => 'applications/dashboard/storage/PhabricatorDashboardPortal.php', 'PhabricatorDashboardPortalController' => 'applications/dashboard/controller/portal/PhabricatorDashboardPortalController.php', + 'PhabricatorDashboardPortalDatasource' => 'applications/dashboard/typeahead/PhabricatorDashboardPortalDatasource.php', 'PhabricatorDashboardPortalEditConduitAPIMethod' => 'applications/dashboard/conduit/PhabricatorDashboardPortalEditConduitAPIMethod.php', 'PhabricatorDashboardPortalEditController' => 'applications/dashboard/controller/portal/PhabricatorDashboardPortalEditController.php', 'PhabricatorDashboardPortalEditEngine' => 'applications/dashboard/editor/PhabricatorDashboardPortalEditEngine.php', 'PhabricatorDashboardPortalEditor' => 'applications/dashboard/editor/PhabricatorDashboardPortalEditor.php', + 'PhabricatorDashboardPortalFerretEngine' => 'applications/dashboard/engine/PhabricatorDashboardPortalFerretEngine.php', + 'PhabricatorDashboardPortalFulltextEngine' => 'applications/dashboard/engine/PhabricatorDashboardPortalFulltextEngine.php', + 'PhabricatorDashboardPortalInstallWorkflow' => 'applications/dashboard/install/PhabricatorDashboardPortalInstallWorkflow.php', 'PhabricatorDashboardPortalListController' => 'applications/dashboard/controller/portal/PhabricatorDashboardPortalListController.php', 'PhabricatorDashboardPortalMenuItem' => 'applications/dashboard/menuitem/PhabricatorDashboardPortalMenuItem.php', 'PhabricatorDashboardPortalNameTransaction' => 'applications/dashboard/xaction/portal/PhabricatorDashboardPortalNameTransaction.php', @@ -2972,20 +3006,29 @@ phutil_register_library_map(array( 'PhabricatorDashboardPortalViewController' => 'applications/dashboard/controller/portal/PhabricatorDashboardPortalViewController.php', 'PhabricatorDashboardProfileController' => 'applications/dashboard/controller/PhabricatorDashboardProfileController.php', 'PhabricatorDashboardProfileMenuItem' => 'applications/search/menuitem/PhabricatorDashboardProfileMenuItem.php', + 'PhabricatorDashboardProjectInstallWorkflow' => 'applications/dashboard/install/PhabricatorDashboardProjectInstallWorkflow.php', 'PhabricatorDashboardQuery' => 'applications/dashboard/query/PhabricatorDashboardQuery.php', + 'PhabricatorDashboardQueryPanelApplicationEditField' => 'applications/dashboard/editfield/PhabricatorDashboardQueryPanelApplicationEditField.php', + 'PhabricatorDashboardQueryPanelApplicationTransaction' => 'applications/dashboard/xaction/panel/PhabricatorDashboardQueryPanelApplicationTransaction.php', 'PhabricatorDashboardQueryPanelInstallController' => 'applications/dashboard/controller/PhabricatorDashboardQueryPanelInstallController.php', + 'PhabricatorDashboardQueryPanelQueryEditField' => 'applications/dashboard/editfield/PhabricatorDashboardQueryPanelQueryEditField.php', + 'PhabricatorDashboardQueryPanelQueryTransaction' => 'applications/dashboard/xaction/panel/PhabricatorDashboardQueryPanelQueryTransaction.php', 'PhabricatorDashboardQueryPanelType' => 'applications/dashboard/paneltype/PhabricatorDashboardQueryPanelType.php', 'PhabricatorDashboardRemarkupRule' => 'applications/dashboard/remarkup/PhabricatorDashboardRemarkupRule.php', - 'PhabricatorDashboardRemovePanelController' => 'applications/dashboard/controller/PhabricatorDashboardRemovePanelController.php', 'PhabricatorDashboardRenderingEngine' => 'applications/dashboard/engine/PhabricatorDashboardRenderingEngine.php', 'PhabricatorDashboardSchemaSpec' => 'applications/dashboard/storage/PhabricatorDashboardSchemaSpec.php', 'PhabricatorDashboardSearchEngine' => 'applications/dashboard/query/PhabricatorDashboardSearchEngine.php', + 'PhabricatorDashboardStatusTransaction' => 'applications/dashboard/xaction/dashboard/PhabricatorDashboardStatusTransaction.php', + 'PhabricatorDashboardTabsPanelTabsTransaction' => 'applications/dashboard/xaction/panel/PhabricatorDashboardTabsPanelTabsTransaction.php', 'PhabricatorDashboardTabsPanelType' => 'applications/dashboard/paneltype/PhabricatorDashboardTabsPanelType.php', + 'PhabricatorDashboardTextPanelTextTransaction' => 'applications/dashboard/xaction/panel/PhabricatorDashboardTextPanelTextTransaction.php', 'PhabricatorDashboardTextPanelType' => 'applications/dashboard/paneltype/PhabricatorDashboardTextPanelType.php', 'PhabricatorDashboardTransaction' => 'applications/dashboard/storage/PhabricatorDashboardTransaction.php', 'PhabricatorDashboardTransactionEditor' => 'applications/dashboard/editor/PhabricatorDashboardTransactionEditor.php', 'PhabricatorDashboardTransactionQuery' => 'applications/dashboard/query/PhabricatorDashboardTransactionQuery.php', - 'PhabricatorDashboardViewController' => 'applications/dashboard/controller/PhabricatorDashboardViewController.php', + 'PhabricatorDashboardTransactionType' => 'applications/dashboard/xaction/dashboard/PhabricatorDashboardTransactionType.php', + 'PhabricatorDashboardTwoThirdsLayoutMode' => 'applications/dashboard/layoutconfig/PhabricatorDashboardTwoThirdsLayoutMode.php', + 'PhabricatorDashboardViewController' => 'applications/dashboard/controller/dashboard/PhabricatorDashboardViewController.php', 'PhabricatorDataCacheSpec' => 'applications/cache/spec/PhabricatorDataCacheSpec.php', 'PhabricatorDataNotAttachedException' => 'infrastructure/storage/lisk/PhabricatorDataNotAttachedException.php', 'PhabricatorDatabaseRef' => 'infrastructure/cluster/PhabricatorDatabaseRef.php', @@ -3052,6 +3095,7 @@ phutil_register_library_map(array( 'PhabricatorEdgeEditType' => 'applications/transactions/edittype/PhabricatorEdgeEditType.php', 'PhabricatorEdgeEditor' => 'infrastructure/edges/editor/PhabricatorEdgeEditor.php', 'PhabricatorEdgeGraph' => 'infrastructure/edges/util/PhabricatorEdgeGraph.php', + 'PhabricatorEdgeIndexEngineExtension' => 'applications/search/engineextension/PhabricatorEdgeIndexEngineExtension.php', 'PhabricatorEdgeObject' => 'infrastructure/edges/conduit/PhabricatorEdgeObject.php', 'PhabricatorEdgeObjectQuery' => 'infrastructure/edges/query/PhabricatorEdgeObjectQuery.php', 'PhabricatorEdgeQuery' => 'infrastructure/edges/query/PhabricatorEdgeQuery.php', @@ -3169,7 +3213,9 @@ phutil_register_library_map(array( 'PhabricatorFact' => 'applications/fact/fact/PhabricatorFact.php', 'PhabricatorFactAggregate' => 'applications/fact/storage/PhabricatorFactAggregate.php', 'PhabricatorFactApplication' => 'applications/fact/application/PhabricatorFactApplication.php', + 'PhabricatorFactChart' => 'applications/fact/storage/PhabricatorFactChart.php', 'PhabricatorFactChartController' => 'applications/fact/controller/PhabricatorFactChartController.php', + 'PhabricatorFactChartFunction' => 'applications/fact/chart/PhabricatorFactChartFunction.php', 'PhabricatorFactController' => 'applications/fact/controller/PhabricatorFactController.php', 'PhabricatorFactCursor' => 'applications/fact/storage/PhabricatorFactCursor.php', 'PhabricatorFactDAO' => 'applications/fact/storage/PhabricatorFactDAO.php', @@ -3711,6 +3757,7 @@ phutil_register_library_map(array( 'PhabricatorObjectRemarkupRule' => 'infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php', 'PhabricatorObjectSelectorDialog' => 'view/control/PhabricatorObjectSelectorDialog.php', 'PhabricatorObjectStatus' => 'infrastructure/status/PhabricatorObjectStatus.php', + 'PhabricatorObjectUsesDashboardPanelEdgeType' => 'applications/search/edge/PhabricatorObjectUsesDashboardPanelEdgeType.php', 'PhabricatorOffsetPagedQuery' => 'infrastructure/query/PhabricatorOffsetPagedQuery.php', 'PhabricatorOldWorldContentSource' => 'infrastructure/contentsource/PhabricatorOldWorldContentSource.php', 'PhabricatorOlderInlinesSetting' => 'applications/settings/setting/PhabricatorOlderInlinesSetting.php', @@ -4055,11 +4102,13 @@ phutil_register_library_map(array( 'PhabricatorProfileMenuEditor' => 'applications/search/editor/PhabricatorProfileMenuEditor.php', 'PhabricatorProfileMenuEngine' => 'applications/search/engine/PhabricatorProfileMenuEngine.php', 'PhabricatorProfileMenuItem' => 'applications/search/menuitem/PhabricatorProfileMenuItem.php', + 'PhabricatorProfileMenuItemAffectsObjectEdgeType' => 'applications/search/edge/PhabricatorProfileMenuItemAffectsObjectEdgeType.php', 'PhabricatorProfileMenuItemConfiguration' => 'applications/search/storage/PhabricatorProfileMenuItemConfiguration.php', 'PhabricatorProfileMenuItemConfigurationQuery' => 'applications/search/query/PhabricatorProfileMenuItemConfigurationQuery.php', 'PhabricatorProfileMenuItemConfigurationTransaction' => 'applications/search/storage/PhabricatorProfileMenuItemConfigurationTransaction.php', 'PhabricatorProfileMenuItemConfigurationTransactionQuery' => 'applications/search/query/PhabricatorProfileMenuItemConfigurationTransactionQuery.php', 'PhabricatorProfileMenuItemIconSet' => 'applications/search/menuitem/PhabricatorProfileMenuItemIconSet.php', + 'PhabricatorProfileMenuItemIndexEngineExtension' => 'applications/search/engineextension/PhabricatorProfileMenuItemIndexEngineExtension.php', 'PhabricatorProfileMenuItemPHIDType' => 'applications/search/phidtype/PhabricatorProfileMenuItemPHIDType.php', 'PhabricatorProfileMenuItemView' => 'applications/search/engine/PhabricatorProfileMenuItemView.php', 'PhabricatorProfileMenuItemViewList' => 'applications/search/engine/PhabricatorProfileMenuItemViewList.php', @@ -4200,18 +4249,21 @@ phutil_register_library_map(array( 'PhabricatorProjectTransactionQuery' => 'applications/project/query/PhabricatorProjectTransactionQuery.php', 'PhabricatorProjectTransactionType' => 'applications/project/xaction/PhabricatorProjectTransactionType.php', 'PhabricatorProjectTrigger' => 'applications/project/storage/PhabricatorProjectTrigger.php', + 'PhabricatorProjectTriggerAddProjectsRule' => 'applications/project/trigger/PhabricatorProjectTriggerAddProjectsRule.php', 'PhabricatorProjectTriggerController' => 'applications/project/controller/trigger/PhabricatorProjectTriggerController.php', 'PhabricatorProjectTriggerCorruptionException' => 'applications/project/exception/PhabricatorProjectTriggerCorruptionException.php', 'PhabricatorProjectTriggerEditController' => 'applications/project/controller/trigger/PhabricatorProjectTriggerEditController.php', 'PhabricatorProjectTriggerEditor' => 'applications/project/editor/PhabricatorProjectTriggerEditor.php', 'PhabricatorProjectTriggerInvalidRule' => 'applications/project/trigger/PhabricatorProjectTriggerInvalidRule.php', 'PhabricatorProjectTriggerListController' => 'applications/project/controller/trigger/PhabricatorProjectTriggerListController.php', + 'PhabricatorProjectTriggerManiphestOwnerRule' => 'applications/project/trigger/PhabricatorProjectTriggerManiphestOwnerRule.php', 'PhabricatorProjectTriggerManiphestPriorityRule' => 'applications/project/trigger/PhabricatorProjectTriggerManiphestPriorityRule.php', 'PhabricatorProjectTriggerManiphestStatusRule' => 'applications/project/trigger/PhabricatorProjectTriggerManiphestStatusRule.php', 'PhabricatorProjectTriggerNameTransaction' => 'applications/project/xaction/trigger/PhabricatorProjectTriggerNameTransaction.php', 'PhabricatorProjectTriggerPHIDType' => 'applications/project/phid/PhabricatorProjectTriggerPHIDType.php', 'PhabricatorProjectTriggerPlaySoundRule' => 'applications/project/trigger/PhabricatorProjectTriggerPlaySoundRule.php', 'PhabricatorProjectTriggerQuery' => 'applications/project/query/PhabricatorProjectTriggerQuery.php', + 'PhabricatorProjectTriggerRemoveProjectsRule' => 'applications/project/trigger/PhabricatorProjectTriggerRemoveProjectsRule.php', 'PhabricatorProjectTriggerRule' => 'applications/project/trigger/PhabricatorProjectTriggerRule.php', 'PhabricatorProjectTriggerRuleRecord' => 'applications/project/trigger/PhabricatorProjectTriggerRuleRecord.php', 'PhabricatorProjectTriggerRulesetTransaction' => 'applications/project/xaction/trigger/PhabricatorProjectTriggerRulesetTransaction.php', @@ -4251,6 +4303,7 @@ phutil_register_library_map(array( 'PhabricatorProjectsSearchEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsSearchEngineExtension.php', 'PhabricatorProjectsWatchersSearchEngineAttachment' => 'applications/project/engineextension/PhabricatorProjectsWatchersSearchEngineAttachment.php', 'PhabricatorPronounSetting' => 'applications/settings/setting/PhabricatorPronounSetting.php', + 'PhabricatorProtocolLog' => 'infrastructure/log/PhabricatorProtocolLog.php', 'PhabricatorPygmentSetupCheck' => 'applications/config/check/PhabricatorPygmentSetupCheck.php', 'PhabricatorQuery' => 'infrastructure/query/PhabricatorQuery.php', 'PhabricatorQueryConstraint' => 'infrastructure/query/constraint/PhabricatorQueryConstraint.php', @@ -4261,6 +4314,7 @@ phutil_register_library_map(array( 'PhabricatorQueryOrderVector' => 'infrastructure/query/order/PhabricatorQueryOrderVector.php', 'PhabricatorQuickSearchEngineExtension' => 'applications/search/engineextension/PhabricatorQuickSearchEngineExtension.php', 'PhabricatorRateLimitRequestExceptionHandler' => 'aphront/handler/PhabricatorRateLimitRequestExceptionHandler.php', + 'PhabricatorRebuildIndexesWorker' => 'applications/search/worker/PhabricatorRebuildIndexesWorker.php', 'PhabricatorRecaptchaConfigOptions' => 'applications/config/option/PhabricatorRecaptchaConfigOptions.php', 'PhabricatorRedirectController' => 'applications/base/controller/PhabricatorRedirectController.php', 'PhabricatorRefreshCSRFController' => 'applications/auth/controller/PhabricatorRefreshCSRFController.php', @@ -4281,8 +4335,6 @@ phutil_register_library_map(array( 'PhabricatorRepository' => 'applications/repository/storage/PhabricatorRepository.php', 'PhabricatorRepositoryActivateTransaction' => 'applications/repository/xaction/PhabricatorRepositoryActivateTransaction.php', 'PhabricatorRepositoryAuditRequest' => 'applications/repository/storage/PhabricatorRepositoryAuditRequest.php', - 'PhabricatorRepositoryAutocloseOnlyTransaction' => 'applications/repository/xaction/PhabricatorRepositoryAutocloseOnlyTransaction.php', - 'PhabricatorRepositoryAutocloseTransaction' => 'applications/repository/xaction/PhabricatorRepositoryAutocloseTransaction.php', 'PhabricatorRepositoryBlueprintsTransaction' => 'applications/repository/xaction/PhabricatorRepositoryBlueprintsTransaction.php', 'PhabricatorRepositoryBranch' => 'applications/repository/storage/PhabricatorRepositoryBranch.php', 'PhabricatorRepositoryCallsignTransaction' => 'applications/repository/xaction/PhabricatorRepositoryCallsignTransaction.php', @@ -4310,6 +4362,7 @@ phutil_register_library_map(array( 'PhabricatorRepositoryEngine' => 'applications/repository/engine/PhabricatorRepositoryEngine.php', 'PhabricatorRepositoryEnormousTransaction' => 'applications/repository/xaction/PhabricatorRepositoryEnormousTransaction.php', 'PhabricatorRepositoryFerretEngine' => 'applications/repository/search/PhabricatorRepositoryFerretEngine.php', + 'PhabricatorRepositoryFetchRefsTransaction' => 'applications/repository/xaction/PhabricatorRepositoryFetchRefsTransaction.php', 'PhabricatorRepositoryFilesizeLimitTransaction' => 'applications/repository/xaction/PhabricatorRepositoryFilesizeLimitTransaction.php', 'PhabricatorRepositoryFulltextEngine' => 'applications/repository/search/PhabricatorRepositoryFulltextEngine.php', 'PhabricatorRepositoryGitCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/PhabricatorRepositoryGitCommitChangeParserWorker.php', @@ -4357,6 +4410,8 @@ phutil_register_library_map(array( 'PhabricatorRepositoryNotifyTransaction' => 'applications/repository/xaction/PhabricatorRepositoryNotifyTransaction.php', 'PhabricatorRepositoryOldRef' => 'applications/repository/storage/PhabricatorRepositoryOldRef.php', 'PhabricatorRepositoryParsedChange' => 'applications/repository/data/PhabricatorRepositoryParsedChange.php', + 'PhabricatorRepositoryPermanentRefsTransaction' => 'applications/repository/xaction/PhabricatorRepositoryPermanentRefsTransaction.php', + 'PhabricatorRepositoryPublisher' => 'applications/repository/query/PhabricatorRepositoryPublisher.php', 'PhabricatorRepositoryPullEngine' => 'applications/repository/engine/PhabricatorRepositoryPullEngine.php', 'PhabricatorRepositoryPullEvent' => 'applications/repository/storage/PhabricatorRepositoryPullEvent.php', 'PhabricatorRepositoryPullEventPHIDType' => 'applications/repository/phid/PhabricatorRepositoryPullEventPHIDType.php', @@ -4427,6 +4482,7 @@ phutil_register_library_map(array( 'PhabricatorSSHWorkflow' => 'infrastructure/ssh/PhabricatorSSHWorkflow.php', 'PhabricatorSavedQuery' => 'applications/search/storage/PhabricatorSavedQuery.php', 'PhabricatorSavedQueryQuery' => 'applications/search/query/PhabricatorSavedQueryQuery.php', + 'PhabricatorScaleChartFunction' => 'applications/fact/chart/PhabricatorScaleChartFunction.php', 'PhabricatorScheduleTaskTriggerAction' => 'infrastructure/daemon/workers/action/PhabricatorScheduleTaskTriggerAction.php', 'PhabricatorScopedEnv' => 'infrastructure/env/PhabricatorScopedEnv.php', 'PhabricatorSearchAbstractDocument' => 'applications/search/index/PhabricatorSearchAbstractDocument.php', @@ -4517,9 +4573,11 @@ phutil_register_library_map(array( 'PhabricatorSetupIssue' => 'applications/config/issue/PhabricatorSetupIssue.php', 'PhabricatorSetupIssueUIExample' => 'applications/uiexample/examples/PhabricatorSetupIssueUIExample.php', 'PhabricatorSetupIssueView' => 'applications/config/view/PhabricatorSetupIssueView.php', + 'PhabricatorShiftChartFunction' => 'applications/fact/chart/PhabricatorShiftChartFunction.php', 'PhabricatorShortSite' => 'aphront/site/PhabricatorShortSite.php', 'PhabricatorShowFiletreeSetting' => 'applications/settings/setting/PhabricatorShowFiletreeSetting.php', 'PhabricatorSimpleEditType' => 'applications/transactions/edittype/PhabricatorSimpleEditType.php', + 'PhabricatorSinChartFunction' => 'applications/fact/chart/PhabricatorSinChartFunction.php', 'PhabricatorSite' => 'aphront/site/PhabricatorSite.php', 'PhabricatorSlackAuthProvider' => 'applications/auth/provider/PhabricatorSlackAuthProvider.php', 'PhabricatorSlowvoteApplication' => 'applications/slowvote/application/PhabricatorSlowvoteApplication.php', @@ -4892,6 +4950,7 @@ phutil_register_library_map(array( 'PhabricatorWorkingCopyDiscoveryTestCase' => 'applications/repository/engine/__tests__/PhabricatorWorkingCopyDiscoveryTestCase.php', 'PhabricatorWorkingCopyPullTestCase' => 'applications/repository/engine/__tests__/PhabricatorWorkingCopyPullTestCase.php', 'PhabricatorWorkingCopyTestCase' => 'applications/repository/engine/__tests__/PhabricatorWorkingCopyTestCase.php', + 'PhabricatorXChartFunction' => 'applications/fact/chart/PhabricatorXChartFunction.php', 'PhabricatorXHPASTDAO' => 'applications/phpast/storage/PhabricatorXHPASTDAO.php', 'PhabricatorXHPASTParseTree' => 'applications/phpast/storage/PhabricatorXHPASTParseTree.php', 'PhabricatorXHPASTViewController' => 'applications/phpast/controller/PhabricatorXHPASTViewController.php', @@ -6221,6 +6280,7 @@ phutil_register_library_map(array( 'DifferentialRevisionIDCommitMessageField' => 'DifferentialCommitMessageField', 'DifferentialRevisionInlineTransaction' => 'PhabricatorModularTransactionType', 'DifferentialRevisionInlinesController' => 'DifferentialController', + 'DifferentialRevisionJIRAIssueURIsHeraldField' => 'DifferentialRevisionHeraldField', 'DifferentialRevisionListController' => 'DifferentialController', 'DifferentialRevisionListView' => 'AphrontView', 'DifferentialRevisionMailReceiver' => 'PhabricatorObjectMailReceiver', @@ -6299,7 +6359,6 @@ phutil_register_library_map(array( 'DiffusionBranchListView' => 'DiffusionView', 'DiffusionBranchQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionBranchTableController' => 'DiffusionController', - 'DiffusionBranchTableView' => 'DiffusionView', 'DiffusionBrowseController' => 'DiffusionController', 'DiffusionBrowseQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionBrowseResultSet' => 'Phobject', @@ -6376,6 +6435,7 @@ phutil_register_library_map(array( 'DiffusionCommitRevisionAcceptedHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitRevisionAcceptingReviewersHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitRevisionHeraldField' => 'DiffusionCommitHeraldField', + 'DiffusionCommitRevisionQuery' => 'Phobject', 'DiffusionCommitRevisionReviewersHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitRevisionSubscribersHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', @@ -6427,6 +6487,11 @@ phutil_register_library_map(array( 'DiffusionRepositoryClusterEngineLogInterface', ), 'DiffusionGitUploadPackSSHWorkflow' => 'DiffusionGitSSHWorkflow', + 'DiffusionGitUploadPackWireProtocol' => 'DiffusionGitWireProtocol', + 'DiffusionGitWireProtocol' => 'Phobject', + 'DiffusionGitWireProtocolCapabilities' => 'Phobject', + 'DiffusionGitWireProtocolRef' => 'Phobject', + 'DiffusionGitWireProtocolRefList' => 'Phobject', 'DiffusionGraphController' => 'DiffusionController', 'DiffusionHistoryController' => 'DiffusionController', 'DiffusionHistoryListView' => 'DiffusionHistoryView', @@ -6544,7 +6609,6 @@ phutil_register_library_map(array( 'DiffusionRefTableController' => 'DiffusionController', 'DiffusionRefsQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionRenameHistoryQuery' => 'Phobject', - 'DiffusionRepositoryActionsManagementPanel' => 'DiffusionRepositoryManagementPanel', 'DiffusionRepositoryAutomationManagementPanel' => 'DiffusionRepositoryManagementPanel', 'DiffusionRepositoryBasicsManagementPanel' => 'DiffusionRepositoryManagementPanel', 'DiffusionRepositoryBranchesManagementPanel' => 'DiffusionRepositoryManagementPanel', @@ -6560,6 +6624,7 @@ phutil_register_library_map(array( 'DiffusionRepositoryEditDeleteController' => 'DiffusionRepositoryManageController', 'DiffusionRepositoryEditEngine' => 'PhabricatorEditEngine', 'DiffusionRepositoryEditEnormousController' => 'DiffusionRepositoryManageController', + 'DiffusionRepositoryEditPublishingController' => 'DiffusionRepositoryManageController', 'DiffusionRepositoryEditUpdateController' => 'DiffusionRepositoryManageController', 'DiffusionRepositoryFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'DiffusionRepositoryHistoryManagementPanel' => 'DiffusionRepositoryManagementPanel', @@ -6627,6 +6692,7 @@ phutil_register_library_map(array( 'DiffusionURIEditor' => 'PhabricatorApplicationTransactionEditor', 'DiffusionURITestCase' => 'PhutilTestCase', 'DiffusionUpdateCoverageConduitAPIMethod' => 'DiffusionConduitAPIMethod', + 'DiffusionUpdateObjectAfterCommitWorker' => 'PhabricatorWorker', 'DiffusionView' => 'AphrontView', 'DivinerArticleAtomizer' => 'DivinerAtomizer', 'DivinerAtom' => 'Phobject', @@ -7279,7 +7345,7 @@ phutil_register_library_map(array( 'HeraldRuleEditor' => 'PhabricatorApplicationTransactionEditor', 'HeraldRuleField' => 'HeraldField', 'HeraldRuleFieldGroup' => 'HeraldFieldGroup', - 'HeraldRuleIndexEngineExtension' => 'PhabricatorIndexEngineExtension', + 'HeraldRuleIndexEngineExtension' => 'PhabricatorEdgeIndexEngineExtension', 'HeraldRuleListController' => 'HeraldController', 'HeraldRuleListView' => 'AphrontView', 'HeraldRuleNameTransaction' => 'HeraldRuleTransactionType', @@ -8166,12 +8232,14 @@ phutil_register_library_map(array( 'PhabricatorAuthManagementLDAPWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementListFactorsWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementListMFAProvidersWorkflow' => 'PhabricatorAuthManagementWorkflow', + 'PhabricatorAuthManagementLockWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementRecoverWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementRefreshWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementRevokeWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementStripWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementTrustOAuthClientWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementUnlimitWorkflow' => 'PhabricatorAuthManagementWorkflow', + 'PhabricatorAuthManagementUnlockWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementUntrustOAuthClientWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementVerifyWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementWorkflow' => 'PhabricatorManagementWorkflow', @@ -8577,6 +8645,11 @@ phutil_register_library_map(array( 'PhabricatorChangeParserTestCase' => 'PhabricatorWorkingCopyTestCase', 'PhabricatorChangesetCachePurger' => 'PhabricatorCachePurger', 'PhabricatorChangesetResponse' => 'AphrontProxyResponse', + 'PhabricatorChartAxis' => 'Phobject', + 'PhabricatorChartDataQuery' => 'Phobject', + 'PhabricatorChartFunction' => 'Phobject', + 'PhabricatorChartFunctionArgument' => 'Phobject', + 'PhabricatorChartFunctionArgumentParser' => 'Phobject', 'PhabricatorChatLogApplication' => 'PhabricatorApplication', 'PhabricatorChatLogChannel' => array( 'PhabricatorChatLogDAO', @@ -8743,6 +8816,7 @@ phutil_register_library_map(array( 'PhabricatorConpherenceWidgetVisibleSetting' => 'PhabricatorInternalSetting', 'PhabricatorConsoleApplication' => 'PhabricatorApplication', 'PhabricatorConsoleContentSource' => 'PhabricatorContentSource', + 'PhabricatorConstantChartFunction' => 'PhabricatorChartFunction', 'PhabricatorContactNumbersSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorContentSource' => 'Phobject', 'PhabricatorContentSourceModule' => 'PhabricatorConfigModule', @@ -8754,6 +8828,7 @@ phutil_register_library_map(array( 'PhabricatorCoreCreateTransaction' => 'PhabricatorCoreTransactionType', 'PhabricatorCoreTransactionType' => 'PhabricatorModularTransactionType', 'PhabricatorCoreVoidTransaction' => 'PhabricatorModularTransactionType', + 'PhabricatorCosChartFunction' => 'PhabricatorChartFunction', 'PhabricatorCountFact' => 'PhabricatorFact', 'PhabricatorCountdown' => array( 'PhabricatorCountdownDAO', @@ -8873,74 +8948,94 @@ phutil_register_library_map(array( 'PhabricatorFlaggableInterface', 'PhabricatorDestructibleInterface', 'PhabricatorProjectInterface', - 'PhabricatorNgramsInterface', + 'PhabricatorFulltextInterface', + 'PhabricatorFerretInterface', + 'PhabricatorDashboardPanelContainerInterface', ), - 'PhabricatorDashboardAddPanelController' => 'PhabricatorDashboardController', + 'PhabricatorDashboardAdjustController' => 'PhabricatorDashboardController', 'PhabricatorDashboardApplication' => 'PhabricatorApplication', + 'PhabricatorDashboardApplicationInstallWorkflow' => 'PhabricatorDashboardInstallWorkflow', 'PhabricatorDashboardArchiveController' => 'PhabricatorDashboardController', - 'PhabricatorDashboardArrangeController' => 'PhabricatorDashboardProfileController', + 'PhabricatorDashboardColumn' => 'Phobject', 'PhabricatorDashboardConsoleController' => 'PhabricatorDashboardController', 'PhabricatorDashboardController' => 'PhabricatorController', 'PhabricatorDashboardDAO' => 'PhabricatorLiskDAO', - 'PhabricatorDashboardDashboardHasPanelEdgeType' => 'PhabricatorEdgeType', 'PhabricatorDashboardDashboardPHIDType' => 'PhabricatorPHIDType', 'PhabricatorDashboardDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorDashboardEditController' => 'PhabricatorDashboardController', + 'PhabricatorDashboardEditEngine' => 'PhabricatorEditEngine', + 'PhabricatorDashboardFavoritesInstallWorkflow' => 'PhabricatorDashboardApplicationInstallWorkflow', + 'PhabricatorDashboardFerretEngine' => 'PhabricatorFerretEngine', + 'PhabricatorDashboardFullLayoutMode' => 'PhabricatorDashboardLayoutMode', + 'PhabricatorDashboardFulltextEngine' => 'PhabricatorFulltextEngine', + 'PhabricatorDashboardHalfLayoutMode' => 'PhabricatorDashboardLayoutMode', + 'PhabricatorDashboardHomeInstallWorkflow' => 'PhabricatorDashboardApplicationInstallWorkflow', 'PhabricatorDashboardIconSet' => 'PhabricatorIconSet', - 'PhabricatorDashboardInstall' => 'PhabricatorDashboardDAO', + 'PhabricatorDashboardIconTransaction' => 'PhabricatorDashboardTransactionType', 'PhabricatorDashboardInstallController' => 'PhabricatorDashboardController', - 'PhabricatorDashboardLayoutConfig' => 'Phobject', + 'PhabricatorDashboardInstallWorkflow' => 'Phobject', + 'PhabricatorDashboardLayoutMode' => 'Phobject', + 'PhabricatorDashboardLayoutTransaction' => 'PhabricatorDashboardTransactionType', 'PhabricatorDashboardListController' => 'PhabricatorDashboardController', - 'PhabricatorDashboardManageController' => 'PhabricatorDashboardProfileController', - 'PhabricatorDashboardMovePanelController' => 'PhabricatorDashboardController', - 'PhabricatorDashboardNgrams' => 'PhabricatorSearchNgrams', + 'PhabricatorDashboardNameTransaction' => 'PhabricatorDashboardTransactionType', + 'PhabricatorDashboardObjectInstallWorkflow' => 'PhabricatorDashboardInstallWorkflow', + 'PhabricatorDashboardOneThirdLayoutMode' => 'PhabricatorDashboardLayoutMode', 'PhabricatorDashboardPanel' => array( 'PhabricatorDashboardDAO', 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', - 'PhabricatorCustomFieldInterface', 'PhabricatorFlaggableInterface', 'PhabricatorDestructibleInterface', - 'PhabricatorNgramsInterface', + 'PhabricatorFulltextInterface', + 'PhabricatorFerretInterface', + 'PhabricatorDashboardPanelContainerInterface', ), 'PhabricatorDashboardPanelArchiveController' => 'PhabricatorDashboardController', - 'PhabricatorDashboardPanelCoreCustomField' => array( - 'PhabricatorDashboardPanelCustomField', - 'PhabricatorStandardCustomFieldInterface', - ), - 'PhabricatorDashboardPanelCustomField' => 'PhabricatorCustomField', + 'PhabricatorDashboardPanelContainerIndexEngineExtension' => 'PhabricatorEdgeIndexEngineExtension', 'PhabricatorDashboardPanelDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorDashboardPanelEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'PhabricatorDashboardPanelEditController' => 'PhabricatorDashboardController', 'PhabricatorDashboardPanelEditEngine' => 'PhabricatorEditEngine', - 'PhabricatorDashboardPanelEditproController' => 'PhabricatorDashboardController', - 'PhabricatorDashboardPanelHasDashboardEdgeType' => 'PhabricatorEdgeType', + 'PhabricatorDashboardPanelFerretEngine' => 'PhabricatorFerretEngine', + 'PhabricatorDashboardPanelFulltextEngine' => 'PhabricatorFulltextEngine', 'PhabricatorDashboardPanelListController' => 'PhabricatorDashboardController', - 'PhabricatorDashboardPanelNgrams' => 'PhabricatorSearchNgrams', + 'PhabricatorDashboardPanelNameTransaction' => 'PhabricatorDashboardPanelTransactionType', 'PhabricatorDashboardPanelPHIDType' => 'PhabricatorPHIDType', + 'PhabricatorDashboardPanelPropertyTransaction' => 'PhabricatorDashboardPanelTransactionType', 'PhabricatorDashboardPanelQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorDashboardPanelRef' => 'Phobject', + 'PhabricatorDashboardPanelRefList' => 'Phobject', 'PhabricatorDashboardPanelRenderController' => 'PhabricatorDashboardController', 'PhabricatorDashboardPanelRenderingEngine' => 'Phobject', - 'PhabricatorDashboardPanelSearchApplicationCustomField' => 'PhabricatorStandardCustomField', 'PhabricatorDashboardPanelSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'PhabricatorDashboardPanelSearchQueryCustomField' => 'PhabricatorStandardCustomField', - 'PhabricatorDashboardPanelTabsCustomField' => 'PhabricatorStandardCustomField', - 'PhabricatorDashboardPanelTransaction' => 'PhabricatorApplicationTransaction', + 'PhabricatorDashboardPanelStatusTransaction' => 'PhabricatorDashboardPanelTransactionType', + 'PhabricatorDashboardPanelTabsController' => 'PhabricatorDashboardController', + 'PhabricatorDashboardPanelTransaction' => 'PhabricatorModularTransaction', 'PhabricatorDashboardPanelTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorDashboardPanelTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorDashboardPanelTransactionType' => 'PhabricatorModularTransactionType', 'PhabricatorDashboardPanelType' => 'Phobject', + 'PhabricatorDashboardPanelUsedByObjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorDashboardPanelViewController' => 'PhabricatorDashboardController', + 'PhabricatorDashboardPanelsTransaction' => 'PhabricatorDashboardTransactionType', 'PhabricatorDashboardPortal' => array( 'PhabricatorDashboardDAO', 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', 'PhabricatorDestructibleInterface', + 'PhabricatorProjectInterface', + 'PhabricatorFulltextInterface', + 'PhabricatorFerretInterface', ), 'PhabricatorDashboardPortalController' => 'PhabricatorDashboardController', + 'PhabricatorDashboardPortalDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorDashboardPortalEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'PhabricatorDashboardPortalEditController' => 'PhabricatorDashboardPortalController', 'PhabricatorDashboardPortalEditEngine' => 'PhabricatorEditEngine', 'PhabricatorDashboardPortalEditor' => 'PhabricatorApplicationTransactionEditor', + 'PhabricatorDashboardPortalFerretEngine' => 'PhabricatorFerretEngine', + 'PhabricatorDashboardPortalFulltextEngine' => 'PhabricatorFulltextEngine', + 'PhabricatorDashboardPortalInstallWorkflow' => 'PhabricatorDashboardObjectInstallWorkflow', 'PhabricatorDashboardPortalListController' => 'PhabricatorDashboardPortalController', 'PhabricatorDashboardPortalMenuItem' => 'PhabricatorProfileMenuItem', 'PhabricatorDashboardPortalNameTransaction' => 'PhabricatorDashboardPortalTransactionType', @@ -8956,19 +9051,28 @@ phutil_register_library_map(array( 'PhabricatorDashboardPortalViewController' => 'PhabricatorDashboardPortalController', 'PhabricatorDashboardProfileController' => 'PhabricatorController', 'PhabricatorDashboardProfileMenuItem' => 'PhabricatorProfileMenuItem', + 'PhabricatorDashboardProjectInstallWorkflow' => 'PhabricatorDashboardObjectInstallWorkflow', 'PhabricatorDashboardQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorDashboardQueryPanelApplicationEditField' => 'PhabricatorEditField', + 'PhabricatorDashboardQueryPanelApplicationTransaction' => 'PhabricatorDashboardPanelPropertyTransaction', 'PhabricatorDashboardQueryPanelInstallController' => 'PhabricatorDashboardController', + 'PhabricatorDashboardQueryPanelQueryEditField' => 'PhabricatorEditField', + 'PhabricatorDashboardQueryPanelQueryTransaction' => 'PhabricatorDashboardPanelPropertyTransaction', 'PhabricatorDashboardQueryPanelType' => 'PhabricatorDashboardPanelType', 'PhabricatorDashboardRemarkupRule' => 'PhabricatorObjectRemarkupRule', - 'PhabricatorDashboardRemovePanelController' => 'PhabricatorDashboardController', 'PhabricatorDashboardRenderingEngine' => 'Phobject', 'PhabricatorDashboardSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorDashboardSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'PhabricatorDashboardStatusTransaction' => 'PhabricatorDashboardTransactionType', + 'PhabricatorDashboardTabsPanelTabsTransaction' => 'PhabricatorDashboardPanelPropertyTransaction', 'PhabricatorDashboardTabsPanelType' => 'PhabricatorDashboardPanelType', + 'PhabricatorDashboardTextPanelTextTransaction' => 'PhabricatorDashboardPanelPropertyTransaction', 'PhabricatorDashboardTextPanelType' => 'PhabricatorDashboardPanelType', - 'PhabricatorDashboardTransaction' => 'PhabricatorApplicationTransaction', + 'PhabricatorDashboardTransaction' => 'PhabricatorModularTransaction', 'PhabricatorDashboardTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorDashboardTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorDashboardTransactionType' => 'PhabricatorModularTransactionType', + 'PhabricatorDashboardTwoThirdsLayoutMode' => 'PhabricatorDashboardLayoutMode', 'PhabricatorDashboardViewController' => 'PhabricatorDashboardProfileController', 'PhabricatorDataCacheSpec' => 'PhabricatorCacheSpec', 'PhabricatorDataNotAttachedException' => 'Exception', @@ -9033,6 +9137,7 @@ phutil_register_library_map(array( 'PhabricatorEdgeEditType' => 'PhabricatorPHIDListEditType', 'PhabricatorEdgeEditor' => 'Phobject', 'PhabricatorEdgeGraph' => 'AbstractDirectedGraph', + 'PhabricatorEdgeIndexEngineExtension' => 'PhabricatorIndexEngineExtension', 'PhabricatorEdgeObject' => array( 'Phobject', 'PhabricatorPolicyInterface', @@ -9158,7 +9263,12 @@ phutil_register_library_map(array( 'PhabricatorFact' => 'Phobject', 'PhabricatorFactAggregate' => 'PhabricatorFactDAO', 'PhabricatorFactApplication' => 'PhabricatorApplication', + 'PhabricatorFactChart' => array( + 'PhabricatorFactDAO', + 'PhabricatorPolicyInterface', + ), 'PhabricatorFactChartController' => 'PhabricatorFactController', + 'PhabricatorFactChartFunction' => 'PhabricatorChartFunction', 'PhabricatorFactController' => 'PhabricatorController', 'PhabricatorFactCursor' => 'PhabricatorFactDAO', 'PhabricatorFactDAO' => 'PhabricatorLiskDAO', @@ -9772,6 +9882,7 @@ phutil_register_library_map(array( 'PhabricatorObjectRemarkupRule' => 'PhutilRemarkupRule', 'PhabricatorObjectSelectorDialog' => 'Phobject', 'PhabricatorObjectStatus' => 'Phobject', + 'PhabricatorObjectUsesDashboardPanelEdgeType' => 'PhabricatorEdgeType', 'PhabricatorOffsetPagedQuery' => 'PhabricatorQuery', 'PhabricatorOldWorldContentSource' => 'PhabricatorContentSource', 'PhabricatorOlderInlinesSetting' => 'PhabricatorSelectSetting', @@ -10188,16 +10299,19 @@ phutil_register_library_map(array( 'PhabricatorProfileMenuEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorProfileMenuEngine' => 'Phobject', 'PhabricatorProfileMenuItem' => 'Phobject', + 'PhabricatorProfileMenuItemAffectsObjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorProfileMenuItemConfiguration' => array( 'PhabricatorSearchDAO', 'PhabricatorPolicyInterface', 'PhabricatorExtendedPolicyInterface', 'PhabricatorApplicationTransactionInterface', + 'PhabricatorIndexableInterface', ), 'PhabricatorProfileMenuItemConfigurationQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorProfileMenuItemConfigurationTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorProfileMenuItemConfigurationTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorProfileMenuItemIconSet' => 'PhabricatorIconSet', + 'PhabricatorProfileMenuItemIndexEngineExtension' => 'PhabricatorEdgeIndexEngineExtension', 'PhabricatorProfileMenuItemPHIDType' => 'PhabricatorPHIDType', 'PhabricatorProfileMenuItemView' => 'Phobject', 'PhabricatorProfileMenuItemViewList' => 'Phobject', @@ -10373,18 +10487,21 @@ phutil_register_library_map(array( 'PhabricatorIndexableInterface', 'PhabricatorDestructibleInterface', ), + 'PhabricatorProjectTriggerAddProjectsRule' => 'PhabricatorProjectTriggerRule', 'PhabricatorProjectTriggerController' => 'PhabricatorProjectController', 'PhabricatorProjectTriggerCorruptionException' => 'Exception', 'PhabricatorProjectTriggerEditController' => 'PhabricatorProjectTriggerController', 'PhabricatorProjectTriggerEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorProjectTriggerInvalidRule' => 'PhabricatorProjectTriggerRule', 'PhabricatorProjectTriggerListController' => 'PhabricatorProjectTriggerController', + 'PhabricatorProjectTriggerManiphestOwnerRule' => 'PhabricatorProjectTriggerRule', 'PhabricatorProjectTriggerManiphestPriorityRule' => 'PhabricatorProjectTriggerRule', 'PhabricatorProjectTriggerManiphestStatusRule' => 'PhabricatorProjectTriggerRule', 'PhabricatorProjectTriggerNameTransaction' => 'PhabricatorProjectTriggerTransactionType', 'PhabricatorProjectTriggerPHIDType' => 'PhabricatorPHIDType', 'PhabricatorProjectTriggerPlaySoundRule' => 'PhabricatorProjectTriggerRule', 'PhabricatorProjectTriggerQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorProjectTriggerRemoveProjectsRule' => 'PhabricatorProjectTriggerRule', 'PhabricatorProjectTriggerRule' => 'Phobject', 'PhabricatorProjectTriggerRuleRecord' => 'Phobject', 'PhabricatorProjectTriggerRulesetTransaction' => 'PhabricatorProjectTriggerTransactionType', @@ -10424,6 +10541,7 @@ phutil_register_library_map(array( 'PhabricatorProjectsSearchEngineExtension' => 'PhabricatorSearchEngineExtension', 'PhabricatorProjectsWatchersSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment', 'PhabricatorPronounSetting' => 'PhabricatorSelectSetting', + 'PhabricatorProtocolLog' => 'Phobject', 'PhabricatorPygmentSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorQuery' => 'Phobject', 'PhabricatorQueryConstraint' => 'Phobject', @@ -10437,6 +10555,7 @@ phutil_register_library_map(array( ), 'PhabricatorQuickSearchEngineExtension' => 'PhabricatorDatasourceEngineExtension', 'PhabricatorRateLimitRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler', + 'PhabricatorRebuildIndexesWorker' => 'PhabricatorWorker', 'PhabricatorRecaptchaConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorRedirectController' => 'PhabricatorController', 'PhabricatorRefreshCSRFController' => 'PhabricatorAuthController', @@ -10473,8 +10592,6 @@ phutil_register_library_map(array( 'PhabricatorRepositoryDAO', 'PhabricatorPolicyInterface', ), - 'PhabricatorRepositoryAutocloseOnlyTransaction' => 'PhabricatorRepositoryTransactionType', - 'PhabricatorRepositoryAutocloseTransaction' => 'PhabricatorRepositoryTransactionType', 'PhabricatorRepositoryBlueprintsTransaction' => 'PhabricatorRepositoryTransactionType', 'PhabricatorRepositoryBranch' => 'PhabricatorRepositoryDAO', 'PhabricatorRepositoryCallsignTransaction' => 'PhabricatorRepositoryTransactionType', @@ -10523,6 +10640,7 @@ phutil_register_library_map(array( 'PhabricatorRepositoryEngine' => 'Phobject', 'PhabricatorRepositoryEnormousTransaction' => 'PhabricatorRepositoryTransactionType', 'PhabricatorRepositoryFerretEngine' => 'PhabricatorFerretEngine', + 'PhabricatorRepositoryFetchRefsTransaction' => 'PhabricatorRepositoryTransactionType', 'PhabricatorRepositoryFilesizeLimitTransaction' => 'PhabricatorRepositoryTransactionType', 'PhabricatorRepositoryFulltextEngine' => 'PhabricatorFulltextEngine', 'PhabricatorRepositoryGitCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker', @@ -10581,6 +10699,8 @@ phutil_register_library_map(array( 'PhabricatorPolicyInterface', ), 'PhabricatorRepositoryParsedChange' => 'Phobject', + 'PhabricatorRepositoryPermanentRefsTransaction' => 'PhabricatorRepositoryTransactionType', + 'PhabricatorRepositoryPublisher' => 'Phobject', 'PhabricatorRepositoryPullEngine' => 'PhabricatorRepositoryEngine', 'PhabricatorRepositoryPullEvent' => array( 'PhabricatorRepositoryDAO', @@ -10674,6 +10794,7 @@ phutil_register_library_map(array( 'PhabricatorPolicyInterface', ), 'PhabricatorSavedQueryQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorScaleChartFunction' => 'PhabricatorChartFunction', 'PhabricatorScheduleTaskTriggerAction' => 'PhabricatorTriggerAction', 'PhabricatorScopedEnv' => 'Phobject', 'PhabricatorSearchAbstractDocument' => 'Phobject', @@ -10764,9 +10885,11 @@ phutil_register_library_map(array( 'PhabricatorSetupIssue' => 'Phobject', 'PhabricatorSetupIssueUIExample' => 'PhabricatorUIExample', 'PhabricatorSetupIssueView' => 'AphrontView', + 'PhabricatorShiftChartFunction' => 'PhabricatorChartFunction', 'PhabricatorShortSite' => 'PhabricatorSite', 'PhabricatorShowFiletreeSetting' => 'PhabricatorSelectSetting', 'PhabricatorSimpleEditType' => 'PhabricatorEditType', + 'PhabricatorSinChartFunction' => 'PhabricatorChartFunction', 'PhabricatorSite' => 'AphrontSite', 'PhabricatorSlackAuthProvider' => 'PhabricatorOAuth2AuthProvider', 'PhabricatorSlowvoteApplication' => 'PhabricatorApplication', @@ -11198,6 +11321,7 @@ phutil_register_library_map(array( 'PhabricatorWorkingCopyDiscoveryTestCase' => 'PhabricatorWorkingCopyTestCase', 'PhabricatorWorkingCopyPullTestCase' => 'PhabricatorWorkingCopyTestCase', 'PhabricatorWorkingCopyTestCase' => 'PhabricatorTestCase', + 'PhabricatorXChartFunction' => 'PhabricatorChartFunction', 'PhabricatorXHPASTDAO' => 'PhabricatorLiskDAO', 'PhabricatorXHPASTParseTree' => 'PhabricatorXHPASTDAO', 'PhabricatorXHPASTViewController' => 'PhabricatorController', diff --git a/src/applications/audit/editor/PhabricatorAuditEditor.php b/src/applications/audit/editor/PhabricatorAuditEditor.php index d4fa1c32ff..12757e16ce 100644 --- a/src/applications/audit/editor/PhabricatorAuditEditor.php +++ b/src/applications/audit/editor/PhabricatorAuditEditor.php @@ -718,7 +718,8 @@ final class PhabricatorAuditEditor switch ($xaction->getTransactionType()) { case PhabricatorAuditTransaction::TYPE_COMMIT: $repository = $object->getRepository(); - if (!$repository->shouldPublish()) { + $publisher = $repository->newPublisher(); + if (!$publisher->shouldPublishCommit($object)) { return false; } return true; @@ -779,7 +780,8 @@ final class PhabricatorAuditEditor // TODO: They should, and then we should simplify this. $repository = $object->getRepository($assert_attached = false); if ($repository != PhabricatorLiskDAO::ATTACHABLE) { - if (!$repository->shouldPublish()) { + $publisher = $repository->newPublisher(); + if (!$publisher->shouldPublishCommit($object)) { return false; } } diff --git a/src/applications/auth/controller/config/PhabricatorAuthListController.php b/src/applications/auth/controller/config/PhabricatorAuthListController.php index f4b05e8adf..b6ba91e7cd 100644 --- a/src/applications/auth/controller/config/PhabricatorAuthListController.php +++ b/src/applications/auth/controller/config/PhabricatorAuthListController.php @@ -13,6 +13,7 @@ final class PhabricatorAuthListController $list = new PHUIObjectItemListView(); $can_manage = $this->hasApplicationCapability( AuthManageProvidersCapability::CAPABILITY); + $is_locked = PhabricatorEnv::getEnvConfig('auth.lock-config'); foreach ($configs as $config) { $item = new PHUIObjectItemView(); @@ -69,7 +70,8 @@ final class PhabricatorAuthListController $crumbs->addTextCrumb(pht('Login and Registration')); $crumbs->setBorder(true); - $guidance_context = new PhabricatorAuthProvidersGuidanceContext(); + $guidance_context = id(new PhabricatorAuthProvidersGuidanceContext()) + ->setCanManage($can_manage); $guidance = id(new PhabricatorGuidanceEngine()) ->setViewer($viewer) @@ -81,7 +83,7 @@ final class PhabricatorAuthListController ->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE) ->setHref($this->getApplicationURI('config/new/')) ->setIcon('fa-plus') - ->setDisabled(!$can_manage) + ->setDisabled(!$can_manage || $is_locked) ->setText(pht('Add Provider')); $list->setFlush(true); diff --git a/src/applications/auth/engine/PhabricatorAuthSessionEngine.php b/src/applications/auth/engine/PhabricatorAuthSessionEngine.php index 38ae2201b8..e4956335a0 100644 --- a/src/applications/auth/engine/PhabricatorAuthSessionEngine.php +++ b/src/applications/auth/engine/PhabricatorAuthSessionEngine.php @@ -389,7 +389,7 @@ final class PhabricatorAuthSessionEngine extends Phobject { * appropriate for one-time checks. * * @param PhabricatorUser User whose session needs to be in high security. - * @param AphrontReqeust Current request. + * @param AphrontRequest Current request. * @param string URI to return the user to if they cancel. * @return PhabricatorAuthHighSecurityToken Security token. * @task hisec @@ -421,7 +421,7 @@ final class PhabricatorAuthSessionEngine extends Phobject { * use @{method:requireHighSecurityToken}. * * @param PhabricatorUser User whose session needs to be in high security. - * @param AphrontReqeust Current request. + * @param AphrontRequest Current request. * @param string URI to return the user to if they cancel. * @param bool True to jump partial sessions directly into high * security instead of just upgrading them to full diff --git a/src/applications/auth/guidance/PhabricatorAuthProvidersGuidanceContext.php b/src/applications/auth/guidance/PhabricatorAuthProvidersGuidanceContext.php index 1302846ec3..12c8300811 100644 --- a/src/applications/auth/guidance/PhabricatorAuthProvidersGuidanceContext.php +++ b/src/applications/auth/guidance/PhabricatorAuthProvidersGuidanceContext.php @@ -1,4 +1,17 @@ canManage = $can_manage; + return $this; + } + + public function getCanManage() { + return $this->canManage; + } + +} diff --git a/src/applications/auth/guidance/PhabricatorAuthProvidersGuidanceEngineExtension.php b/src/applications/auth/guidance/PhabricatorAuthProvidersGuidanceEngineExtension.php index d1f67393ca..32482a582a 100644 --- a/src/applications/auth/guidance/PhabricatorAuthProvidersGuidanceEngineExtension.php +++ b/src/applications/auth/guidance/PhabricatorAuthProvidersGuidanceEngineExtension.php @@ -92,6 +92,25 @@ final class PhabricatorAuthProvidersGuidanceEngineExtension ->setMessage($message); } + $locked_config_key = 'auth.lock-config'; + $is_locked = PhabricatorEnv::getEnvConfig($locked_config_key); + if ($is_locked) { + $message = pht( + 'Authentication provider configuration is locked, and can not be '. + 'changed without being unlocked. See the configuration setting %s '. + 'for details.', + phutil_tag( + 'a', + array( + 'href' => '/config/edit/'.$locked_config_key, + ), + $locked_config_key)); + + $results[] = $this->newWarning('auth.locked-config') + ->setPriority(500) + ->setMessage($message); + } + return $results; } diff --git a/src/applications/auth/management/PhabricatorAuthManagementLockWorkflow.php b/src/applications/auth/management/PhabricatorAuthManagementLockWorkflow.php new file mode 100644 index 0000000000..e15069f775 --- /dev/null +++ b/src/applications/auth/management/PhabricatorAuthManagementLockWorkflow.php @@ -0,0 +1,32 @@ +setName('lock') + ->setExamples('**lock**') + ->setSynopsis( + pht( + 'Lock authentication provider config, to prevent changes to '. + 'the config without doing **bin/auth unlock**.')); + } + + public function execute(PhutilArgumentParser $args) { + $console = PhutilConsole::getConsole(); + + $key = 'auth.lock-config'; + $config_entry = PhabricatorConfigEntry::loadConfigEntry($key); + $config_entry->setValue(true); + + // If the entry has been deleted, resurrect it. + $config_entry->setIsDeleted(0); + + $config_entry->save(); + + echo tsprintf( + "%s\n", + pht('Locked the authentication provider configuration.')); + } +} diff --git a/src/applications/auth/management/PhabricatorAuthManagementUnlockWorkflow.php b/src/applications/auth/management/PhabricatorAuthManagementUnlockWorkflow.php new file mode 100644 index 0000000000..bcca83f65e --- /dev/null +++ b/src/applications/auth/management/PhabricatorAuthManagementUnlockWorkflow.php @@ -0,0 +1,33 @@ +setName('unlock') + ->setExamples('**unlock**') + ->setSynopsis( + pht( + 'Unlock the authentication provider config, to make it possible '. + 'to edit the config using the web UI. Make sure to do '. + '**bin/auth lock** when done editing the configuration.')); + } + + public function execute(PhutilArgumentParser $args) { + $console = PhutilConsole::getConsole(); + + $key = 'auth.lock-config'; + $config_entry = PhabricatorConfigEntry::loadConfigEntry($key); + $config_entry->setValue(false); + + // If the entry has been deleted, resurrect it. + $config_entry->setIsDeleted(0); + + $config_entry->save(); + + echo tsprintf( + "%s\n", + pht('Unlocked the authentication provider configuration.')); + } +} diff --git a/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php b/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php index 61f6176f15..8cbea36d07 100644 --- a/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php +++ b/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php @@ -238,6 +238,7 @@ final class CelerityDefaultPostprocessor 'grey.button.gradient' => 'linear-gradient(to bottom, #ffffff, #f1f0f1)', 'grey.button.hover' => 'linear-gradient(to bottom, #ffffff, #eeebec)', + 'document.border' => '#dedee1', ); } diff --git a/src/applications/config/check/PhabricatorAuthSetupCheck.php b/src/applications/config/check/PhabricatorAuthSetupCheck.php index 43fab77eb7..59bc17ecce 100644 --- a/src/applications/config/check/PhabricatorAuthSetupCheck.php +++ b/src/applications/config/check/PhabricatorAuthSetupCheck.php @@ -22,6 +22,7 @@ final class PhabricatorAuthSetupCheck extends PhabricatorSetupCheck { ->setViewer(PhabricatorUser::getOmnipotentUser()) ->execute(); + $did_warn = false; if (!$configs) { $message = pht( 'You have not configured any authentication providers yet. You '. @@ -35,6 +36,42 @@ final class PhabricatorAuthSetupCheck extends PhabricatorSetupCheck { ->setName(pht('No Authentication Providers Configured')) ->setMessage($message) ->addLink('/auth/', pht('Auth Application')); + + $did_warn = true; + } + + // This check is meant for new administrators, but we don't want to + // show both this warning and the "No Auth Providers" warning. Also, + // show this as a reminder to go back and do a `bin/auth lock` after + // they make their desired changes. + $is_locked = PhabricatorEnv::getEnvConfig('auth.lock-config'); + if (!$is_locked && !$did_warn) { + $message = pht( + 'Your authentication provider configuration is unlocked. Once you '. + 'finish setting up or modifying authentication, you should lock the '. + 'configuration to prevent unauthorized changes.'. + "\n\n". + 'Leaving your authentication provider configuration unlocked '. + 'increases the damage that a compromised administrator account can '. + 'do to your install, by, for example, changing the authentication '. + 'provider to a server they control and intercepting usernames and '. + 'passwords.'. + "\n\n". + 'To prevent this attack, you should configure your authentication '. + 'providers, and then lock the configuration by doing `%s` '. + 'from the command line. This will prevent changing the '. + 'authentication provider config without first doing `%s`.', + 'bin/auth lock', + 'bin/auth unlock'); + $this + ->newIssue('auth.config-unlocked') + ->setShortName(pht('Auth Config Unlocked')) + ->setName(pht('Authenticaton Provider Configuration Unlocked')) + ->setMessage($message) + ->addRelatedPhabricatorConfig('auth.lock-config') + ->addCommand( + hsprintf( + 'phabricator/ $ ./bin/auth lock')); } } } diff --git a/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php b/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php index b676063e8c..a8a883ca96 100644 --- a/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php +++ b/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php @@ -121,6 +121,7 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck { 'phabricator.uninstalled-applications', 'phabricator.application-settings', 'config.ignore-issues', + 'auth.lock-config', ); $soft_locks = array_fuse($soft_locks); if (isset($soft_locks[$key])) { @@ -146,7 +147,7 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck { 'that it was previously not locked, you set it using the web UI, '. 'and it later became locked.'. "\n\n". - 'You should copy this configuration value in a local configuration '. + 'You should copy this configuration value to a local configuration '. 'source (usually by using %s) and then remove it from the database '. 'with the command below.'. "\n\n". diff --git a/src/applications/config/option/PhabricatorAuthenticationConfigOptions.php b/src/applications/config/option/PhabricatorAuthenticationConfigOptions.php index 1440714bf7..8058ecafcb 100644 --- a/src/applications/config/option/PhabricatorAuthenticationConfigOptions.php +++ b/src/applications/config/option/PhabricatorAuthenticationConfigOptions.php @@ -73,6 +73,26 @@ final class PhabricatorAuthenticationConfigOptions ->addExample( "yourcompany.com\nmail.yourcompany.com", pht('Valid Setting')), + $this->newOption('auth.lock-config', 'bool', false) + ->setBoolOptions( + array( + pht('Auth provider config must be unlocked before editing'), + pht('Auth provider config can be edited without unlocking'), + )) + ->setSummary( + pht( + 'Require administrators to unlock the authentication provider '. + 'configuration from the CLI before it can be edited.')) + ->setDescription( + pht( + 'When set to `true`, the authentication provider configuration '. + 'for this instance can not be modified without first running '. + '`bin/auth unlock` from the command line. This is to reduce '. + 'the security impact of a compromised administrator account. '. + "\n\n". + 'After running `bin/auth unlock` and making your changes to the '. + 'authentication provider config, you should run `bin/auth lock`.')) + ->setLocked(true), $this->newOption('account.editable', 'bool', true) ->setBoolOptions( array( diff --git a/src/applications/config/option/PhabricatorSecurityConfigOptions.php b/src/applications/config/option/PhabricatorSecurityConfigOptions.php index 50fc24a85d..d5557e3688 100644 --- a/src/applications/config/option/PhabricatorSecurityConfigOptions.php +++ b/src/applications/config/option/PhabricatorSecurityConfigOptions.php @@ -163,14 +163,26 @@ EOTEXT 'mailto' => true, )) ->setSummary( - pht('Determines which URI protocols are auto-linked.')) + pht( + 'Determines which URI protocols are valid for links and '. + 'redirects.')) ->setDescription( pht( - "When users write comments which have URIs, they'll be ". - "automatically linked if the protocol appears in this set. This ". - "whitelist is primarily to prevent security issues like ". - "%s URIs.", - 'javascript://')) + 'When users write comments which have URIs, they will be '. + 'automatically turned into clickable links if the URI protocol '. + 'appears in this set.'. + "\n\n". + 'This set of allowed protocols is primarily intended to prevent '. + 'security issues with "javascript:" and other potentially '. + 'dangerous URI handlers.'. + "\n\n". + 'This set is also used to enforce valid redirect URIs. '. + 'Phabricator will refuse to issue a HTTP "Location" redirect to a '. + 'URI with a protocol not on this set.'. + "\n\n". + 'Usually, "http" and "https" should be present in this set. If '. + 'you remove one or both protocols, some Phabricator features '. + 'which rely on links or redirects may not work.')) ->addExample("http\nhttps", pht('Valid Setting')) ->setLocked(true), $this->newOption( diff --git a/src/applications/dashboard/application/PhabricatorDashboardApplication.php b/src/applications/dashboard/application/PhabricatorDashboardApplication.php index 081367effa..9beac5ae53 100644 --- a/src/applications/dashboard/application/PhabricatorDashboardApplication.php +++ b/src/applications/dashboard/application/PhabricatorDashboardApplication.php @@ -41,28 +41,27 @@ final class PhabricatorDashboardApplication extends PhabricatorApplication { => 'PhabricatorDashboardListController', 'view/(?P\d+)/' => 'PhabricatorDashboardViewController', 'archive/(?P\d+)/' => 'PhabricatorDashboardArchiveController', - 'manage/(?P\d+)/' => 'PhabricatorDashboardManageController', - 'arrange/(?P\d+)/' => 'PhabricatorDashboardArrangeController', - 'create/' => 'PhabricatorDashboardEditController', - 'edit/(?:(?P\d+)/)?' => 'PhabricatorDashboardEditController', - 'install/(?:(?P\d+)/)?' => 'PhabricatorDashboardInstallController', + $this->getEditRoutePattern('edit/') => + 'PhabricatorDashboardEditController', + 'install/(?P\d+)/'. + '(?:(?P[^/]+)/'. + '(?:(?P[^/]+)/)?)?' => + 'PhabricatorDashboardInstallController', 'console/' => 'PhabricatorDashboardConsoleController', - 'addpanel/(?P\d+)/' => 'PhabricatorDashboardAddPanelController', - 'movepanel/(?P\d+)/' => 'PhabricatorDashboardMovePanelController', - 'removepanel/(?P\d+)/' - => 'PhabricatorDashboardRemovePanelController', + 'adjust/(?Premove|add|move)/' + => 'PhabricatorDashboardAdjustController', 'panel/' => array( 'install/(?P[^/]+)/(?:(?P[^/]+)/)?' => 'PhabricatorDashboardQueryPanelInstallController', '(?:query/(?P[^/]+)/)?' => 'PhabricatorDashboardPanelListController', - 'create/' => 'PhabricatorDashboardPanelEditController', - $this->getEditRoutePattern('editpro/') - => 'PhabricatorDashboardPanelEditproController', - 'edit/(?:(?P\d+)/)?' => 'PhabricatorDashboardPanelEditController', + $this->getEditRoutePattern('edit/') + => 'PhabricatorDashboardPanelEditController', 'render/(?P\d+)/' => 'PhabricatorDashboardPanelRenderController', 'archive/(?P\d+)/' => 'PhabricatorDashboardPanelArchiveController', + 'tabs/(?P\d+)/(?Padd|move|remove|rename)/' + => 'PhabricatorDashboardPanelTabsController', ), ), '/portal/' => array( diff --git a/src/applications/dashboard/controller/PhabricatorDashboardAddPanelController.php b/src/applications/dashboard/controller/PhabricatorDashboardAddPanelController.php deleted file mode 100644 index 65560f624f..0000000000 --- a/src/applications/dashboard/controller/PhabricatorDashboardAddPanelController.php +++ /dev/null @@ -1,104 +0,0 @@ -getViewer(); - $id = $request->getURIData('id'); - - $dashboard = id(new PhabricatorDashboardQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->needPanels(true) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$dashboard) { - return new Aphront404Response(); - } - - $redirect_uri = $this->getApplicationURI( - 'arrange/'.$dashboard->getID().'/'); - - $v_panel = head($request->getArr('panel')); - $e_panel = true; - $errors = array(); - if ($request->isFormPost()) { - if (strlen($v_panel)) { - $panel = id(new PhabricatorDashboardPanelQuery()) - ->setViewer($viewer) - ->withIDs(array($v_panel)) - ->executeOne(); - if (!$panel) { - $errors[] = pht('Not a valid panel.'); - $e_panel = pht('Invalid'); - } - - $on_dashboard = $dashboard->getPanels(); - $on_ids = mpull($on_dashboard, null, 'getID'); - if (array_key_exists($v_panel, $on_ids)) { - $p_name = $panel->getName(); - $errors[] = pht('Panel "%s" already exists on dashboard.', $p_name); - $e_panel = pht('Invalid'); - } - - } else { - $errors[] = pht('Select a panel to add.'); - $e_panel = pht('Required'); - } - - if (!$errors) { - PhabricatorDashboardTransactionEditor::addPanelToDashboard( - $viewer, - PhabricatorContentSource::newFromRequest($request), - $panel, - $dashboard, - $request->getInt('column', 0)); - - return id(new AphrontRedirectResponse())->setURI($redirect_uri); - } - } - - $panels = id(new PhabricatorDashboardPanelQuery()) - ->setViewer($viewer) - ->withArchived(false) - ->execute(); - - if (!$panels) { - return $this->newDialog() - ->setTitle(pht('No Panels Exist Yet')) - ->appendParagraph( - pht( - 'You have not created any dashboard panels yet, so you can not '. - 'add an existing panel.')) - ->appendParagraph( - pht('Instead, add a new panel.')) - ->addCancelButton($redirect_uri); - } - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->addHiddenInput('column', $request->getInt('column')) - ->appendRemarkupInstructions( - pht('Choose a panel to add to this dashboard:')) - ->appendChild( - id(new AphrontFormTokenizerControl()) - ->setUser($this->getViewer()) - ->setDatasource(new PhabricatorDashboardPanelDatasource()) - ->setLimit(1) - ->setName('panel') - ->setLabel(pht('Panel'))); - - return $this->newDialog() - ->setTitle(pht('Add Panel')) - ->setErrors($errors) - ->appendChild($form->buildLayoutView()) - ->addCancelButton($redirect_uri) - ->addSubmitButton(pht('Add Panel')); - } - -} diff --git a/src/applications/dashboard/controller/PhabricatorDashboardArrangeController.php b/src/applications/dashboard/controller/PhabricatorDashboardArrangeController.php deleted file mode 100644 index 1ef482b7f4..0000000000 --- a/src/applications/dashboard/controller/PhabricatorDashboardArrangeController.php +++ /dev/null @@ -1,79 +0,0 @@ -getViewer(); - $id = $request->getURIData('id'); - - $dashboard = id(new PhabricatorDashboardQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->needPanels(true) - ->executeOne(); - if (!$dashboard) { - return new Aphront404Response(); - } - $this->setDashboard($dashboard); - - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $dashboard, - PhabricatorPolicyCapability::CAN_EDIT); - - $title = $dashboard->getName(); - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Arrange')); - $header = $this->buildHeaderView(); - - $info_view = null; - if (!$can_edit) { - $no_edit = pht( - 'You do not have permission to edit this dashboard.'); - - $info_view = id(new PHUIInfoView()) - ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) - ->setErrors(array($no_edit)); - } - - $rendered_dashboard = id(new PhabricatorDashboardRenderingEngine()) - ->setViewer($viewer) - ->setDashboard($dashboard) - ->setArrangeMode($can_edit) - ->renderDashboard(); - - $dashboard_box = id(new PHUIBoxView()) - ->addClass('dashboard-preview-box') - ->appendChild($rendered_dashboard); - - $install_button = id(new PHUIButtonView()) - ->setTag('a') - ->setText('Install Dashboard') - ->setIcon('fa-plus') - ->setWorkflow(true) - ->setHref($this->getApplicationURI("/install/{$id}/")); - $header->addActionLink($install_button); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setFooter(array( - $info_view, - $dashboard_box, - )); - - $navigation = $this->buildSideNavView('arrange'); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->setNavigation($navigation) - ->appendChild($view); - - } - -} diff --git a/src/applications/dashboard/controller/PhabricatorDashboardEditController.php b/src/applications/dashboard/controller/PhabricatorDashboardEditController.php deleted file mode 100644 index 6b3a96d80c..0000000000 --- a/src/applications/dashboard/controller/PhabricatorDashboardEditController.php +++ /dev/null @@ -1,382 +0,0 @@ -getViewer(); - $id = $request->getURIData('id'); - - if ($id) { - $dashboard = id(new PhabricatorDashboardQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->needPanels(true) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$dashboard) { - return new Aphront404Response(); - } - $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs( - $dashboard->getPHID(), - PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); - $v_projects = array_reverse($v_projects); - $is_new = false; - } else { - if (!$request->getStr('edit')) { - if ($request->isFormPost()) { - switch ($request->getStr('template')) { - case 'empty': - break; - default: - return $this->processBuildTemplateRequest($request); - } - } else { - return $this->processTemplateRequest($request); - } - } - - $dashboard = PhabricatorDashboard::initializeNewDashboard($viewer); - $v_projects = array(); - $is_new = true; - } - - $crumbs = $this->buildApplicationCrumbs(); - - if ($is_new) { - $title = pht('Create Dashboard'); - $header_icon = 'fa-plus-square'; - $button = pht('Create Dashboard'); - $cancel_uri = $this->getApplicationURI(); - - $crumbs->addTextCrumb(pht('Create Dashboard')); - } else { - $id = $dashboard->getID(); - $cancel_uri = $this->getApplicationURI('manage/'.$id.'/'); - - $title = pht('Edit Dashboard: %s', $dashboard->getName()); - $header_icon = 'fa-pencil'; - $button = pht('Save Changes'); - - $crumbs->addTextCrumb($dashboard->getName(), $cancel_uri); - $crumbs->addTextCrumb(pht('Edit')); - } - - $v_name = $dashboard->getName(); - $v_icon = $dashboard->getIcon(); - $v_layout_mode = $dashboard->getLayoutConfigObject()->getLayoutMode(); - $e_name = true; - - $validation_exception = null; - if ($request->isFormPost() && $request->getStr('edit')) { - $v_name = $request->getStr('name'); - $v_icon = $request->getStr('icon'); - $v_layout_mode = $request->getStr('layout_mode'); - $v_view_policy = $request->getStr('viewPolicy'); - $v_edit_policy = $request->getStr('editPolicy'); - $v_projects = $request->getArr('projects'); - - $xactions = array(); - - $type_name = PhabricatorDashboardTransaction::TYPE_NAME; - $type_icon = PhabricatorDashboardTransaction::TYPE_ICON; - $type_layout_mode = PhabricatorDashboardTransaction::TYPE_LAYOUT_MODE; - $type_view_policy = PhabricatorTransactions::TYPE_VIEW_POLICY; - $type_edit_policy = PhabricatorTransactions::TYPE_EDIT_POLICY; - - $xactions[] = id(new PhabricatorDashboardTransaction()) - ->setTransactionType($type_name) - ->setNewValue($v_name); - $xactions[] = id(new PhabricatorDashboardTransaction()) - ->setTransactionType($type_layout_mode) - ->setNewValue($v_layout_mode); - $xactions[] = id(new PhabricatorDashboardTransaction()) - ->setTransactionType($type_icon) - ->setNewValue($v_icon); - $xactions[] = id(new PhabricatorDashboardTransaction()) - ->setTransactionType($type_view_policy) - ->setNewValue($v_view_policy); - $xactions[] = id(new PhabricatorDashboardTransaction()) - ->setTransactionType($type_edit_policy) - ->setNewValue($v_edit_policy); - - $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; - $xactions[] = id(new PhabricatorDashboardTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) - ->setMetadataValue('edge:type', $proj_edge_type) - ->setNewValue(array('=' => array_fuse($v_projects))); - - try { - $editor = id(new PhabricatorDashboardTransactionEditor()) - ->setActor($viewer) - ->setContinueOnNoEffect(true) - ->setContentSourceFromRequest($request) - ->applyTransactions($dashboard, $xactions); - - $uri = $this->getApplicationURI('arrange/'.$dashboard->getID().'/'); - - return id(new AphrontRedirectResponse())->setURI($uri); - } catch (PhabricatorApplicationTransactionValidationException $ex) { - $validation_exception = $ex; - - $e_name = $validation_exception->getShortMessage($type_name); - - $dashboard->setViewPolicy($v_view_policy); - $dashboard->setEditPolicy($v_edit_policy); - } - } - - $policies = id(new PhabricatorPolicyQuery()) - ->setViewer($viewer) - ->setObject($dashboard) - ->execute(); - - $layout_mode_options = - PhabricatorDashboardLayoutConfig::getLayoutModeSelectOptions(); - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->addHiddenInput('edit', true) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Name')) - ->setName('name') - ->setValue($v_name) - ->setError($e_name)) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setLabel(pht('Layout Mode')) - ->setName('layout_mode') - ->setValue($v_layout_mode) - ->setOptions($layout_mode_options)) - ->appendChild( - id(new PHUIFormIconSetControl()) - ->setLabel(pht('Icon')) - ->setName('icon') - ->setIconSet(new PhabricatorDashboardIconSet()) - ->setValue($v_icon)) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setName('viewPolicy') - ->setPolicyObject($dashboard) - ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) - ->setPolicies($policies)) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setName('editPolicy') - ->setPolicyObject($dashboard) - ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) - ->setPolicies($policies)); - - $form->appendControl( - id(new AphrontFormTokenizerControl()) - ->setLabel(pht('Tags')) - ->setName('projects') - ->setValue($v_projects) - ->setDatasource(new PhabricatorProjectDatasource())); - - $form->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue($button) - ->addCancelButton($cancel_uri)); - - $box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) - ->setForm($form) - ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) - ->setValidationException($validation_exception); - - $crumbs->setBorder(true); - - $view = id(new PHUITwoColumnView()) - ->setFooter($box); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild($view); - } - - private function processTemplateRequest(AphrontRequest $request) { - $viewer = $request->getUser(); - - $template_control = id(new AphrontFormRadioButtonControl()) - ->setName(pht('template')) - ->setValue($request->getStr('template', 'empty')) - ->addButton( - 'empty', - pht('Empty'), - pht('Start with a blank canvas.')) - ->addButton( - 'simple', - pht('Simple Template'), - pht( - 'Start with a simple dashboard with a welcome message, a feed of '. - 'recent events, and a few starter panels.')); - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendRemarkupInstructions( - pht('Choose a dashboard template to start with.')) - ->appendChild($template_control); - - return $this->newDialog() - ->setTitle(pht('Create Dashboard')) - ->setWidth(AphrontDialogView::WIDTH_FORM) - ->appendChild($form->buildLayoutView()) - ->addCancelButton('/dashboard/') - ->addSubmitButton(pht('Continue')); - } - - private function processBuildTemplateRequest(AphrontRequest $request) { - $viewer = $request->getUser(); - $template = $request->getStr('template'); - - $bare_panel = PhabricatorDashboardPanel::initializeNewPanel($viewer); - $panel_phids = array(); - - switch ($template) { - case 'simple': - $v_name = pht("%s's Dashboard", $viewer->getUsername()); - - $welcome_panel = $this->newPanel( - $request, - $viewer, - 'text', - pht('Welcome'), - array( - 'text' => pht( - "This is a simple template dashboard. You can edit this panel ". - "to change this text and replace it with a welcome message, or ". - "leave this placeholder text as-is to give your dashboard a ". - "rustic, authentic feel.\n\n". - "You can drag, remove, add, and edit panels to customize the ". - "rest of this dashboard to show the information you want.\n\n". - "To install this dashboard on the home page, edit your personal ". - "or global menu on the homepage and click Dashboard under ". - "New Menu Item on the right."), - )); - $panel_phids[] = $welcome_panel->getPHID(); - - $feed_panel = $this->newPanel( - $request, - $viewer, - 'query', - pht('Recent Activity'), - array( - 'class' => 'PhabricatorFeedSearchEngine', - 'key' => 'all', - )); - $panel_phids[] = $feed_panel->getPHID(); - - $revision_panel = $this->newPanel( - $request, - $viewer, - 'query', - pht('Active Revisions'), - array( - 'class' => 'DifferentialRevisionSearchEngine', - 'key' => 'active', - )); - $panel_phids[] = $revision_panel->getPHID(); - - $task_panel = $this->newPanel( - $request, - $viewer, - 'query', - pht('Assigned Tasks'), - array( - 'class' => 'ManiphestTaskSearchEngine', - 'key' => 'assigned', - )); - $panel_phids[] = $task_panel->getPHID(); - - $commit_panel = $this->newPanel( - $request, - $viewer, - 'query', - pht('Recent Commits'), - array( - 'class' => 'PhabricatorCommitSearchEngine', - 'key' => 'all', - )); - $panel_phids[] = $commit_panel->getPHID(); - - $mode_2_and_1 = PhabricatorDashboardLayoutConfig::MODE_THIRDS_AND_THIRD; - $layout = id(new PhabricatorDashboardLayoutConfig()) - ->setLayoutMode($mode_2_and_1) - ->setPanelLocation(0, $welcome_panel->getPHID()) - ->setPanelLocation(0, $revision_panel->getPHID()) - ->setPanelLocation(0, $task_panel->getPHID()) - ->setPanelLocation(0, $commit_panel->getPHID()) - ->setPanelLocation(1, $feed_panel->getPHID()); - - break; - default: - throw new Exception(pht('Unknown dashboard template %s!', $template)); - } - - // Create the dashboard. - - $dashboard = PhabricatorDashboard::initializeNewDashboard($viewer) - ->setLayoutConfigFromObject($layout); - - $xactions = array(); - - $xactions[] = id(new PhabricatorDashboardTransaction()) - ->setTransactionType(PhabricatorDashboardTransaction::TYPE_NAME) - ->setNewValue($v_name); - - $xactions[] = id(new PhabricatorDashboardTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) - ->setMetadataValue( - 'edge:type', - PhabricatorDashboardDashboardHasPanelEdgeType::EDGECONST) - ->setNewValue( - array( - '+' => array_fuse($panel_phids), - )); - - $editor = id(new PhabricatorDashboardTransactionEditor()) - ->setActor($viewer) - ->setContinueOnNoEffect(true) - ->setContentSourceFromRequest($request) - ->applyTransactions($dashboard, $xactions); - - $manage_uri = $this->getApplicationURI('arrange/'.$dashboard->getID().'/'); - - return id(new AphrontRedirectResponse()) - ->setURI($manage_uri); - } - - private function newPanel( - AphrontRequest $request, - PhabricatorUser $viewer, - $type, - $name, - array $properties) { - - $panel = PhabricatorDashboardPanel::initializeNewPanel($viewer) - ->setPanelType($type) - ->setProperties($properties); - - $xactions = array(); - - $xactions[] = id(new PhabricatorDashboardPanelTransaction()) - ->setTransactionType(PhabricatorDashboardPanelTransaction::TYPE_NAME) - ->setNewValue($name); - - $editor = id(new PhabricatorDashboardPanelTransactionEditor()) - ->setActor($viewer) - ->setContinueOnNoEffect(true) - ->setContentSourceFromRequest($request) - ->applyTransactions($panel, $xactions); - - return $panel; - } - - -} diff --git a/src/applications/dashboard/controller/PhabricatorDashboardInstallController.php b/src/applications/dashboard/controller/PhabricatorDashboardInstallController.php deleted file mode 100644 index 145c6f530e..0000000000 --- a/src/applications/dashboard/controller/PhabricatorDashboardInstallController.php +++ /dev/null @@ -1,141 +0,0 @@ -getViewer(); - $id = $request->getURIData('id'); - - $dashboard = id(new PhabricatorDashboardQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->executeOne(); - if (!$dashboard) { - return new Aphront404Response(); - } - - $cancel_uri = $this->getApplicationURI( - 'view/'.$dashboard->getID().'/'); - - $home_app = new PhabricatorHomeApplication(); - - $options = array(); - $options['home'] = array( - 'personal' => - array( - 'capability' => PhabricatorPolicyCapability::CAN_VIEW, - 'application' => $home_app, - 'name' => pht('Personal Dashboard'), - 'value' => 'personal', - 'description' => pht('Places this dashboard as a menu item on home '. - 'as a personal menu item. It will only be on your personal '. - 'home.'), - ), - 'global' => - array( - 'capability' => PhabricatorPolicyCapability::CAN_EDIT, - 'application' => $home_app, - 'name' => pht('Global Dashboard'), - 'value' => 'global', - 'description' => pht('Places this dashboard as a menu item on home '. - 'as a global menu item. It will be available to all users.'), - ), - ); - - - $errors = array(); - $v_name = null; - if ($request->isFormPost()) { - $menuitem = new PhabricatorDashboardProfileMenuItem(); - $dashboard_phid = $dashboard->getPHID(); - $home = new PhabricatorHomeApplication(); - $v_name = $request->getStr('name'); - $v_home = $request->getStr('home'); - - if ($v_home) { - $application = $options['home'][$v_home]['application']; - $capability = $options['home'][$v_home]['capability']; - - $can_edit_home = PhabricatorPolicyFilter::hasCapability( - $viewer, - $application, - $capability); - - if (!$can_edit_home) { - $errors[] = pht( - 'You do not have permission to install a dashboard on home.'); - } - } else { - $errors[] = pht( - 'You must select a destination to install this dashboard.'); - } - - $v_phid = $viewer->getPHID(); - if ($v_home == 'global') { - $v_phid = null; - } - - if (!$errors) { - $install = PhabricatorProfileMenuItemConfiguration::initializeNewItem( - $home, - $menuitem, - $v_phid); - - $install->setMenuItemProperty('dashboardPHID', $dashboard_phid); - $install->setMenuItemProperty('name', $v_name); - $install->setMenuItemOrder(1); - - $xactions = array(); - - $editor = id(new PhabricatorProfileMenuEditor()) - ->setActor($viewer) - ->setContinueOnNoEffect(true) - ->setContinueOnMissingFields(true) - ->setContentSourceFromRequest($request); - - $editor->applyTransactions($install, $xactions); - - $view_uri = '/home/menu/view/'.$install->getID().'/'; - - return id(new AphrontRedirectResponse())->setURI($view_uri); - } - } - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Menu Label')) - ->setName('name') - ->setValue($v_name)); - - $radio = id(new AphrontFormRadioButtonControl()) - ->setLabel(pht('Home Menu')) - ->setName('home'); - - foreach ($options['home'] as $type => $option) { - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $option['application'], - $option['capability']); - if ($can_edit) { - $radio->addButton( - $option['value'], - $option['name'], - $option['description']); - } - } - - $form->appendChild($radio); - - return $this->newDialog() - ->setTitle(pht('Install Dashboard')) - ->setErrors($errors) - ->setWidth(AphrontDialogView::WIDTH_FORM) - ->appendChild($form->buildLayoutView()) - ->addCancelButton($cancel_uri) - ->addSubmitButton(pht('Install Dashboard')); - } - -} diff --git a/src/applications/dashboard/controller/PhabricatorDashboardListController.php b/src/applications/dashboard/controller/PhabricatorDashboardListController.php index 93a6af9fbf..de33056115 100644 --- a/src/applications/dashboard/controller/PhabricatorDashboardListController.php +++ b/src/applications/dashboard/controller/PhabricatorDashboardListController.php @@ -36,11 +36,9 @@ final class PhabricatorDashboardListController protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); - $crumbs->addAction( - id(new PHUIListItemView()) - ->setIcon('fa-plus-square') - ->setName(pht('Create Dashboard')) - ->setHref($this->getApplicationURI().'create/')); + id(new PhabricatorDashboardEditEngine()) + ->setViewer($this->getViewer()) + ->addActionToCrumbs($crumbs); return $crumbs; } diff --git a/src/applications/dashboard/controller/PhabricatorDashboardManageController.php b/src/applications/dashboard/controller/PhabricatorDashboardManageController.php deleted file mode 100644 index 39a7e46841..0000000000 --- a/src/applications/dashboard/controller/PhabricatorDashboardManageController.php +++ /dev/null @@ -1,139 +0,0 @@ -getViewer(); - $id = $request->getURIData('id'); - - // TODO: This UI should drop a lot of capabilities if the user can't - // edit the dashboard, but we should still let them in for "Install" and - // "View History". - - $dashboard = id(new PhabricatorDashboardQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->needPanels(true) - ->executeOne(); - if (!$dashboard) { - return new Aphront404Response(); - } - $this->setDashboard($dashboard); - - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $dashboard, - PhabricatorPolicyCapability::CAN_EDIT); - - $title = $dashboard->getName(); - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Manage')); - - $header = $this->buildHeaderView(); - $curtain = $this->buildCurtainView($dashboard); - $properties = $this->buildPropertyView($dashboard); - - $timeline = $this->buildTransactionTimeline( - $dashboard, - new PhabricatorDashboardTransactionQuery()); - $timeline->setShouldTerminate(true); - - $info_view = null; - if (!$can_edit) { - $no_edit = pht( - 'You do not have permission to edit this dashboard.'); - - $info_view = id(new PHUIInfoView()) - ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) - ->setErrors(array($no_edit)); - } - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setCurtain($curtain) - ->setMainColumn(array( - $info_view, - $properties, - $timeline, - )); - - $navigation = $this->buildSideNavView('manage'); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->setNavigation($navigation) - ->appendChild($view); - - } - - private function buildCurtainView(PhabricatorDashboard $dashboard) { - $viewer = $this->getViewer(); - $id = $dashboard->getID(); - - $curtain = $this->newCurtainView($dashboard); - - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $dashboard, - PhabricatorPolicyCapability::CAN_EDIT); - - $curtain->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Edit Dashboard')) - ->setIcon('fa-pencil') - ->setHref($this->getApplicationURI("edit/{$id}/")) - ->setDisabled(!$can_edit)); - - if ($dashboard->isArchived()) { - $curtain->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Activate Dashboard')) - ->setIcon('fa-check') - ->setHref($this->getApplicationURI("archive/{$id}/")) - ->setDisabled(!$can_edit) - ->setWorkflow($can_edit)); - } else { - $curtain->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Archive Dashboard')) - ->setIcon('fa-ban') - ->setHref($this->getApplicationURI("archive/{$id}/")) - ->setDisabled(!$can_edit) - ->setWorkflow($can_edit)); - } - - return $curtain; - } - - private function buildPropertyView(PhabricatorDashboard $dashboard) { - $viewer = $this->getViewer(); - - $properties = id(new PHUIPropertyListView()) - ->setUser($viewer); - - $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( - $viewer, - $dashboard); - - $properties->addProperty( - pht('Editable By'), - $descriptions[PhabricatorPolicyCapability::CAN_EDIT]); - - $properties->addProperty( - pht('Panels'), - $viewer->renderHandleList($dashboard->getPanelPHIDs())); - - return id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Details')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->addPropertyList($properties); - } - -} diff --git a/src/applications/dashboard/controller/PhabricatorDashboardMovePanelController.php b/src/applications/dashboard/controller/PhabricatorDashboardMovePanelController.php deleted file mode 100644 index d1962517f9..0000000000 --- a/src/applications/dashboard/controller/PhabricatorDashboardMovePanelController.php +++ /dev/null @@ -1,71 +0,0 @@ -getViewer(); - $id = $request->getURIData('id'); - - $column_id = $request->getStr('columnID'); - $panel_phid = $request->getStr('objectPHID'); - $after_phid = $request->getStr('afterPHID'); - $before_phid = $request->getStr('beforePHID'); - - $dashboard = id(new PhabricatorDashboardQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->needPanels(true) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$dashboard) { - return new Aphront404Response(); - } - $panels = mpull($dashboard->getPanels(), null, 'getPHID'); - $panel = idx($panels, $panel_phid); - if (!$panel) { - return new Aphront404Response(); - } - - $layout_config = $dashboard->getLayoutConfigObject(); - $layout_config->removePanel($panel_phid); - $panel_location_grid = $layout_config->getPanelLocations(); - - $column_phids = idx($panel_location_grid, $column_id, array()); - $column_phids = array_values($column_phids); - if ($column_phids) { - $insert_at = 0; - foreach ($column_phids as $index => $phid) { - if ($phid === $before_phid) { - $insert_at = $index; - break; - } - if ($phid === $after_phid) { - $insert_at = $index + 1; - break; - } - } - - $new_column_phids = $column_phids; - array_splice( - $new_column_phids, - $insert_at, - 0, - array($panel_phid)); - } else { - $new_column_phids = array(0 => $panel_phid); - } - - $panel_location_grid[$column_id] = $new_column_phids; - $layout_config->setPanelLocations($panel_location_grid); - $dashboard->setLayoutConfigFromObject($layout_config); - $dashboard->save(); - - return id(new AphrontAjaxResponse())->setContent(''); - } - -} diff --git a/src/applications/dashboard/controller/PhabricatorDashboardPanelEditController.php b/src/applications/dashboard/controller/PhabricatorDashboardPanelEditController.php deleted file mode 100644 index 7aece8c603..0000000000 --- a/src/applications/dashboard/controller/PhabricatorDashboardPanelEditController.php +++ /dev/null @@ -1,354 +0,0 @@ -getViewer(); - $id = $request->getURIData('id'); - - // If the user is trying to create a panel directly on a dashboard, make - // sure they have permission to see and edit the dashboard. - - $dashboard_id = $request->getInt('dashboardID'); - $dashboard = null; - if ($dashboard_id) { - $dashboard = id(new PhabricatorDashboardQuery()) - ->setViewer($viewer) - ->withIDs(array($dashboard_id)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$dashboard) { - return new Aphront404Response(); - } - - $manage_uri = $this->getApplicationURI('arrange/'.$dashboard_id.'/'); - } - - if ($id) { - $is_create = false; - - if ($dashboard) { - $capabilities = array( - PhabricatorPolicyCapability::CAN_VIEW, - ); - } else { - $capabilities = array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - ); - } - - $panel = id(new PhabricatorDashboardPanelQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->requireCapabilities($capabilities) - ->executeOne(); - if (!$panel) { - return new Aphront404Response(); - } - - } else { - $is_create = true; - - $panel = PhabricatorDashboardPanel::initializeNewPanel($viewer); - $types = PhabricatorDashboardPanelType::getAllPanelTypes(); - $type = $request->getStr('type'); - if (empty($types[$type])) { - return $this->processPanelTypeRequest($request); - } - - $panel->setPanelType($type); - } - - if ($is_create) { - $title = pht('Create New Panel'); - $button = pht('Create Panel'); - $header_icon = 'fa-plus-square'; - if ($dashboard) { - $cancel_uri = $manage_uri; - } else { - $cancel_uri = $this->getApplicationURI('panel/'); - } - } else { - $title = pht('Edit Panel: %s', $panel->getName()); - $button = pht('Save Panel'); - $header_icon = 'fa-pencil'; - if ($dashboard) { - $cancel_uri = $manage_uri; - } else { - $cancel_uri = '/'.$panel->getMonogram(); - } - } - - $v_name = $panel->getName(); - $e_name = true; - - $field_list = PhabricatorCustomField::getObjectFields( - $panel, - PhabricatorCustomField::ROLE_EDIT); - $field_list - ->setViewer($viewer) - ->readFieldsFromStorage($panel); - - if ($is_create && !$request->isFormPost()) { - $panel->requireImplementation()->initializeFieldsFromRequest( - $panel, - $field_list, - $request); - } - - $validation_exception = null; - - // NOTE: We require 'edit' to distinguish between the "Choose a Type" - // and "Create a Panel" dialogs. - - if ($request->isFormPost() && $request->getBool('edit')) { - $v_name = $request->getStr('name'); - $v_view_policy = $request->getStr('viewPolicy'); - $v_edit_policy = $request->getStr('editPolicy'); - - $type_name = PhabricatorDashboardPanelTransaction::TYPE_NAME; - $type_view_policy = PhabricatorTransactions::TYPE_VIEW_POLICY; - $type_edit_policy = PhabricatorTransactions::TYPE_EDIT_POLICY; - - $xactions = array(); - - $xactions[] = id(new PhabricatorDashboardPanelTransaction()) - ->setTransactionType($type_name) - ->setNewValue($v_name); - - $xactions[] = id(new PhabricatorDashboardPanelTransaction()) - ->setTransactionType($type_view_policy) - ->setNewValue($v_view_policy); - - $xactions[] = id(new PhabricatorDashboardPanelTransaction()) - ->setTransactionType($type_edit_policy) - ->setNewValue($v_edit_policy); - - $field_xactions = $field_list->buildFieldTransactionsFromRequest( - new PhabricatorDashboardPanelTransaction(), - $request); - $xactions = array_merge($xactions, $field_xactions); - - try { - $editor = id(new PhabricatorDashboardPanelTransactionEditor()) - ->setActor($viewer) - ->setContinueOnNoEffect(true) - ->setContentSourceFromRequest($request) - ->applyTransactions($panel, $xactions); - - // If we're creating a panel directly on a dashboard, add it now. - if ($dashboard && $is_create) { - PhabricatorDashboardTransactionEditor::addPanelToDashboard( - $viewer, - PhabricatorContentSource::newFromRequest($request), - $panel, - $dashboard, - $request->getInt('column', 0)); - } - - if ($dashboard) { - $done_uri = $manage_uri; - } else { - $done_uri = '/'.$panel->getMonogram(); - } - - return id(new AphrontRedirectResponse())->setURI($done_uri); - } catch (PhabricatorApplicationTransactionValidationException $ex) { - $validation_exception = $ex; - - $e_name = $validation_exception->getShortMessage($type_name); - - $panel->setViewPolicy($v_view_policy); - $panel->setEditPolicy($v_edit_policy); - } - } - - // NOTE: We're setting the submit URI explicitly because we need to edit - // a different panel if we just cloned the original panel. - if ($is_create) { - $submit_uri = $this->getApplicationURI('panel/edit/'); - } else { - $submit_uri = $this->getApplicationURI('panel/edit/'.$panel->getID().'/'); - } - - $policies = id(new PhabricatorPolicyQuery()) - ->setViewer($viewer) - ->setObject($panel) - ->execute(); - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->setAction($submit_uri) - ->addHiddenInput('edit', true) - ->addHiddenInput('dashboardID', $request->getInt('dashboardID')) - ->addHiddenInput('column', $request->getInt('column')) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Name')) - ->setName('name') - ->setValue($v_name) - ->setError($e_name)); - - if (!$request->isAjax() || !$is_create) { - $form - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setName('viewPolicy') - ->setPolicyObject($panel) - ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) - ->setPolicies($policies)) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setName('editPolicy') - ->setPolicyObject($panel) - ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) - ->setPolicies($policies)); - } - - $field_list->appendFieldsToForm($form); - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb( - pht('Panels'), - $this->getApplicationURI('panel/')); - if ($is_create) { - $crumbs->addTextCrumb(pht('New Panel')); - $form->addHiddenInput('type', $panel->getPanelType()); - } else { - $crumbs->addTextCrumb( - $panel->getMonogram(), - '/'.$panel->getMonogram()); - $crumbs->addTextCrumb(pht('Edit')); - } - $crumbs->setBorder(true); - - if ($request->isAjax()) { - return $this->newDialog() - ->setTitle($title) - ->setSubmitURI($submit_uri) - ->setWidth(AphrontDialogView::WIDTH_FORM) - ->setValidationException($validation_exception) - ->appendChild($form->buildLayoutView()) - ->addCancelButton($cancel_uri) - ->addSubmitButton($button); - } else { - $form - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue($button) - ->addCancelButton($cancel_uri)); - } - - $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Panel')) - ->setValidationException($validation_exception) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setForm($form); - - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon($header_icon); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setFooter($box); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild($view); - } - - private function processPanelTypeRequest(AphrontRequest $request) { - $viewer = $request->getUser(); - - $types = PhabricatorDashboardPanelType::getAllPanelTypes(); - - $v_type = null; - $errors = array(); - if ($request->isFormPost()) { - $v_type = $request->getStr('type'); - if (!isset($types[$v_type])) { - $errors[] = pht('You must select a type of panel to create.'); - } - } - - $cancel_uri = $this->getApplicationURI('panel/'); - - if (!$v_type) { - $v_type = key($types); - } - - $panel_types = id(new AphrontFormRadioButtonControl()) - ->setName('type') - ->setValue($v_type); - - foreach ($types as $key => $type) { - $panel_types->addButton( - $key, - $type->getPanelTypeName(), - $type->getPanelTypeDescription()); - } - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->addHiddenInput('dashboardID', $request->getInt('dashboardID')) - ->addHiddenInput('column', $request->getInt('column')) - ->appendRemarkupInstructions( - pht( - 'Choose the type of dashboard panel to create:')) - ->appendChild($panel_types); - - if ($request->isAjax()) { - return $this->newDialog() - ->setTitle(pht('Add New Panel')) - ->setWidth(AphrontDialogView::WIDTH_FORM) - ->setErrors($errors) - ->appendChild($form->buildLayoutView()) - ->addCancelbutton($cancel_uri) - ->addSubmitButton(pht('Continue')); - } else { - $form->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue(pht('Continue')) - ->addCancelButton($cancel_uri)); - } - - $title = pht('Create Dashboard Panel'); - $header_icon = 'fa-plus-square'; - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb( - pht('Panels'), - $this->getApplicationURI('panel/')); - $crumbs->addTextCrumb(pht('New Panel')); - $crumbs->setBorder(true); - - $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Panel')) - ->setFormErrors($errors) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setForm($form); - - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon($header_icon); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setFooter($box); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild($view); - } - - -} diff --git a/src/applications/dashboard/controller/PhabricatorDashboardPanelEditproController.php b/src/applications/dashboard/controller/PhabricatorDashboardPanelEditproController.php deleted file mode 100644 index b40dfd85b3..0000000000 --- a/src/applications/dashboard/controller/PhabricatorDashboardPanelEditproController.php +++ /dev/null @@ -1,105 +0,0 @@ -setController($this); - - $id = $request->getURIData('id'); - if (!$id) { - $list_uri = $this->getApplicationURI('panel/'); - - $panel_type = $request->getStr('panelType'); - $panel_types = PhabricatorDashboardPanelType::getAllPanelTypes(); - if (empty($panel_types[$panel_type])) { - return $this->buildPanelTypeResponse($list_uri); - } - - $engine - ->addContextParameter('panelType', $panel_type) - ->setPanelType($panel_type); - } - - return $engine->buildResponse(); - } - - private function buildPanelTypeResponse($cancel_uri) { - $panel_types = PhabricatorDashboardPanelType::getAllPanelTypes(); - - $viewer = $this->getViewer(); - $request = $this->getRequest(); - - $e_type = null; - $errors = array(); - if ($request->isFormPost()) { - $e_type = pht('Required'); - $errors[] = pht( - 'To create a new dashboard panel, you must select a panel type.'); - } - - $type_control = id(new AphrontFormRadioButtonControl()) - ->setLabel(pht('Panel Type')) - ->setName('panelType') - ->setError($e_type); - - foreach ($panel_types as $key => $type) { - $type_control->addButton( - $key, - $type->getPanelTypeName(), - $type->getPanelTypeDescription()); - } - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendRemarkupInstructions( - pht('Choose the type of dashboard panel to create:')) - ->appendChild($type_control); - - if ($request->isAjax()) { - return $this->newDialog() - ->setTitle(pht('Add New Panel')) - ->setWidth(AphrontDialogView::WIDTH_FORM) - ->setErrors($errors) - ->appendForm($form) - ->addCancelButton($cancel_uri) - ->addSubmitButton(pht('Continue')); - } - - $form->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue(pht('Continue')) - ->addCancelButton($cancel_uri)); - - $title = pht('Create Dashboard Panel'); - $header_icon = 'fa-plus-square'; - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb( - pht('Panels'), - $this->getApplicationURI('panel/')); - $crumbs->addTextCrumb(pht('New Panel')); - $crumbs->setBorder(true); - - $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Panel')) - ->setFormErrors($errors) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setForm($form); - - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon($header_icon); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setFooter($box); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild($view); - } - -} diff --git a/src/applications/dashboard/controller/PhabricatorDashboardProfileController.php b/src/applications/dashboard/controller/PhabricatorDashboardProfileController.php index adc85efdf3..ec441658c2 100644 --- a/src/applications/dashboard/controller/PhabricatorDashboardProfileController.php +++ b/src/applications/dashboard/controller/PhabricatorDashboardProfileController.php @@ -14,10 +14,6 @@ abstract class PhabricatorDashboardProfileController return $this->dashboard; } - public function buildApplicationMenu() { - return $this->buildSideNavView()->getMenu(); - } - protected function buildHeaderView() { $viewer = $this->getViewer(); $dashboard = $this->getDashboard(); @@ -49,50 +45,10 @@ abstract class PhabricatorDashboardProfileController $dashboard = $this->getDashboard(); if ($dashboard) { - $id = $dashboard->getID(); - $dashboard_uri = $this->getApplicationURI("/view/{$id}/"); - $crumbs->addTextCrumb($dashboard->getName(), $dashboard_uri); + $crumbs->addTextCrumb($dashboard->getName(), $dashboard->getURI()); } return $crumbs; } - protected function buildSideNavView($filter = null) { - $viewer = $this->getViewer(); - $dashboard = $this->getDashboard(); - $id = $dashboard->getID(); - - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $dashboard, - PhabricatorPolicyCapability::CAN_EDIT); - - $nav = id(new AphrontSideNavFilterView()) - ->setBaseURI(new PhutilURI($this->getApplicationURI())); - - $nav->addLabel(pht('Dashboard')); - - $nav->addFilter( - 'view', - pht('View Dashboard'), - $this->getApplicationURI("/view/{$id}/"), - 'fa-dashboard'); - - $nav->addFilter( - 'arrange', - pht('Arrange Panels'), - $this->getApplicationURI("/arrange/{$id}/"), - 'fa-columns'); - - $nav->addFilter( - 'manage', - pht('Manage Dashboard'), - $this->getApplicationURI("/manage/{$id}/"), - 'fa-gears'); - - $nav->selectFilter($filter); - - return $nav; - } - } diff --git a/src/applications/dashboard/controller/PhabricatorDashboardQueryPanelInstallController.php b/src/applications/dashboard/controller/PhabricatorDashboardQueryPanelInstallController.php index 77068531a9..a229fcb41c 100644 --- a/src/applications/dashboard/controller/PhabricatorDashboardQueryPanelInstallController.php +++ b/src/applications/dashboard/controller/PhabricatorDashboardQueryPanelInstallController.php @@ -7,185 +7,160 @@ final class PhabricatorDashboardQueryPanelInstallController $viewer = $request->getViewer(); $v_dashboard = null; - $v_name = null; - $v_column = 0; - $v_engine = $request->getURIData('engineKey'); - $v_query = $request->getURIData('queryKey'); + $e_dashboard = null; + $v_name = null; $e_name = true; - // Validate Engines + $v_engine = $request->getStr('engine'); + if (!strlen($v_engine)) { + $v_engine = $request->getURIData('engineKey'); + } + + $v_query = $request->getStr('query'); + if (!strlen($v_query)) { + $v_query = $request->getURIData('queryKey'); + } + $engines = PhabricatorApplicationSearchEngine::getAllEngines(); - foreach ($engines as $name => $engine) { - if (!$engine->canUseInPanelContext()) { - unset($engines[$name]); - } - } - if (!in_array($v_engine, array_keys($engines))) { - return new Aphront404Response(); - } + $engine = idx($engines, $v_engine); + if ($engine) { + $engine = id(clone $engine) + ->setViewer($viewer); - // Validate Queries - $engine = $engines[$v_engine]; - $engine->setViewer($viewer); - $good_query = false; - if ($engine->isBuiltinQuery($v_query)) { - $good_query = true; + $redirect_uri = $engine->getQueryResultsPageURI($v_query); + + $named_query = idx($engine->loadEnabledNamedQueries(), $v_query); + if ($named_query) { + $v_name = $named_query->getQueryName(); + } } else { - $saved_query = id(new PhabricatorSavedQueryQuery()) - ->setViewer($viewer) - ->withEngineClassNames(array($v_engine)) - ->withQueryKeys(array($v_query)) - ->executeOne(); - if ($saved_query) { - $good_query = true; - } - } - if (!$good_query) { - return new Aphront404Response(); - } - - $named_query = idx($engine->loadEnabledNamedQueries(), $v_query); - if ($named_query) { - $v_name = $named_query->getQueryName(); + $redirect_uri = '/'; } $errors = array(); + $xaction_name = PhabricatorDashboardPanelNameTransaction::TRANSACTIONTYPE; + $xaction_engine = + PhabricatorDashboardQueryPanelApplicationTransaction::TRANSACTIONTYPE; + $xaction_query = + PhabricatorDashboardQueryPanelQueryTransaction::TRANSACTIONTYPE; + if ($request->isFormPost()) { - $v_dashboard = $request->getInt('dashboardID'); $v_name = $request->getStr('name'); if (!$v_name) { $errors[] = pht('You must provide a name for this panel.'); $e_name = pht('Required'); } - $dashboard = id(new PhabricatorDashboardQuery()) - ->setViewer($viewer) - ->withIDs(array($v_dashboard)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); + $v_dashboard = head($request->getArr('dashboardPHIDs')); + if (!$v_dashboard) { + $errors[] = pht('You must select a dashboard.'); + $e_dashboard = pht('Required'); + } else { + $dashboard = id(new PhabricatorDashboardQuery()) + ->setViewer($viewer) + ->withPHIDs(array($v_dashboard)) + ->executeOne(); + if (!$dashboard) { + $errors[] = pht('You must select a valid dashboard.'); + $e_dashboard = pht('Invalid'); + } - if (!$dashboard) { - $errors[] = pht('Please select a valid dashboard.'); + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $dashboard, + PhabricatorPolicyCapability::CAN_EDIT); + if (!$can_edit) { + $errors[] = pht( + 'You must select a dashboard you have permission to edit.'); + } } if (!$errors) { - $redirect_uri = "/dashboard/arrange/{$v_dashboard}/"; + $done_uri = $dashboard->getURI(); + + // First, create a new panel. $panel_type = id(new PhabricatorDashboardQueryPanelType()) ->getPanelTypeKey(); - $panel = PhabricatorDashboardPanel::initializeNewPanel($viewer); - $panel->setPanelType($panel_type); - $field_list = PhabricatorCustomField::getObjectFields( - $panel, - PhabricatorCustomField::ROLE_EDIT); - - $field_list - ->setViewer($viewer) - ->readFieldsFromStorage($panel); - - $panel->requireImplementation()->initializeFieldsFromRequest( - $panel, - $field_list, - $request); + $panel = PhabricatorDashboardPanel::initializeNewPanel($viewer) + ->setPanelType($panel_type); $xactions = array(); - $xactions[] = id(new PhabricatorDashboardPanelTransaction()) - ->setTransactionType(PhabricatorDashboardPanelTransaction::TYPE_NAME) - ->setNewValue($v_name); - - $xactions[] = id(new PhabricatorDashboardPanelTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_CUSTOMFIELD) - ->setMetadataValue('customfield:key', 'std:dashboard:core:class') - ->setOldValue(null) + $xactions[] = $panel->getApplicationTransactionTemplate() + ->setTransactionType($xaction_engine) ->setNewValue($v_engine); - $xactions[] = id(new PhabricatorDashboardPanelTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_CUSTOMFIELD) - ->setMetadataValue('customfield:key', 'std:dashboard:core:key') - ->setOldValue(null) + $xactions[] = $panel->getApplicationTransactionTemplate() + ->setTransactionType($xaction_query) ->setNewValue($v_query); - $editor = id(new PhabricatorDashboardPanelTransactionEditor()) + $xactions[] = $panel->getApplicationTransactionTemplate() + ->setTransactionType($xaction_name) + ->setNewValue($v_name); + + $editor = $panel->getApplicationTransactionEditor() ->setActor($viewer) - ->setContinueOnNoEffect(true) ->setContentSourceFromRequest($request) ->applyTransactions($panel, $xactions); - PhabricatorDashboardTransactionEditor::addPanelToDashboard( - $viewer, - PhabricatorContentSource::newFromRequest($request), - $panel, - $dashboard, - $request->getInt('column', 0)); + // Now that we've created a panel, add it to the dashboard. - return id(new AphrontRedirectResponse())->setURI($redirect_uri); + $xactions = array(); + + $ref_list = clone $dashboard->getPanelRefList(); + $ref_list->newPanelRef($panel); + $new_panels = $ref_list->toDictionary(); + + $xactions[] = $dashboard->getApplicationTransactionTemplate() + ->setTransactionType( + PhabricatorDashboardPanelsTransaction::TRANSACTIONTYPE) + ->setNewValue($new_panels); + + $editor = $dashboard->getApplicationTransactionEditor() + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true) + ->applyTransactions($dashboard, $xactions); + + return id(new AphrontRedirectResponse())->setURI($done_uri); } } - // Make this a select for now, as we don't expect someone to have - // edit access to a vast number of dashboards. - // Can add optiongroup if needed down the road. - $dashboards = id(new PhabricatorDashboardQuery()) - ->setViewer($viewer) - ->withStatuses(array( - PhabricatorDashboard::STATUS_ACTIVE, - )) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->execute(); - $options = mpull($dashboards, 'getName', 'getID'); - asort($options); - - $redirect_uri = $engine->getQueryResultsPageURI($v_query); - - if (!$options) { - $notice = id(new PHUIInfoView()) - ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) - ->appendChild(pht('You do not have access to any dashboards. To '. - 'continue, please create a dashboard first.')); - - return $this->newDialog() - ->setTitle(pht('No Dashboards')) - ->setWidth(AphrontDialogView::WIDTH_FORM) - ->appendChild($notice) - ->addCancelButton($redirect_uri); + if ($v_dashboard) { + $dashboard_phids = array($v_dashboard); + } else { + $dashboard_phids = array(); } $form = id(new AphrontFormView()) - ->setUser($viewer) - ->addHiddenInput('engine', $v_engine) - ->addHiddenInput('query', $v_query) - ->addHiddenInput('column', $v_column) - ->appendChild( + ->setViewer($viewer) + ->appendControl( id(new AphrontFormTextControl()) ->setLabel(pht('Name')) ->setName('name') ->setValue($v_name) ->setError($e_name)) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setUser($this->getViewer()) - ->setValue($v_dashboard) - ->setName('dashboardID') - ->setOptions($options) + ->appendControl( + id(new AphrontFormTokenizerControl()) + ->setValue($dashboard_phids) + ->setError($e_dashboard) + ->setName('dashboardPHIDs') + ->setLimit(1) + ->setDatasource(new PhabricatorDashboardDatasource()) ->setLabel(pht('Dashboard'))); return $this->newDialog() ->setTitle(pht('Add Panel to Dashboard')) ->setErrors($errors) ->setWidth(AphrontDialogView::WIDTH_FORM) - ->appendChild($form->buildLayoutView()) + ->addHiddenInput('engine', $v_engine) + ->addHiddenInput('query', $v_query) + ->appendForm($form) ->addCancelButton($redirect_uri) ->addSubmitButton(pht('Add Panel')); diff --git a/src/applications/dashboard/controller/PhabricatorDashboardRemovePanelController.php b/src/applications/dashboard/controller/PhabricatorDashboardRemovePanelController.php deleted file mode 100644 index d62b163db2..0000000000 --- a/src/applications/dashboard/controller/PhabricatorDashboardRemovePanelController.php +++ /dev/null @@ -1,89 +0,0 @@ -getViewer(); - $id = $request->getURIData('id'); - - $dashboard = id(new PhabricatorDashboardQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$dashboard) { - return new Aphront404Response(); - } - - // NOTE: If you can edit a dashboard, you can remove panels from it even - // if you don't have permission to see them or they aren't valid. We only - // require that the panel be present on the dashboard. - - $v_panel = $request->getStr('panelPHID'); - - $panel_on_dashboard = false; - $layout = $dashboard->getLayoutConfigObject(); - $columns = $layout->getPanelLocations(); - foreach ($columns as $column) { - foreach ($column as $column_panel_phid) { - if ($column_panel_phid == $v_panel) { - $panel_on_dashboard = true; - break; - } - } - } - - if (!$panel_on_dashboard) { - return new Aphront404Response(); - } - - $redirect_uri = $this->getApplicationURI( - 'arrange/'.$dashboard->getID().'/'); - $layout_config = $dashboard->getLayoutConfigObject(); - - if ($request->isFormPost()) { - $xactions = array(); - $xactions[] = id(new PhabricatorDashboardTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) - ->setMetadataValue( - 'edge:type', - PhabricatorDashboardDashboardHasPanelEdgeType::EDGECONST) - ->setNewValue( - array( - '-' => array( - $v_panel => $v_panel, - ), - )); - - $layout_config->removePanel($v_panel); - $dashboard->setLayoutConfigFromObject($layout_config); - - $editor = id(new PhabricatorDashboardTransactionEditor()) - ->setActor($viewer) - ->setContentSourceFromRequest($request) - ->setContinueOnMissingFields(true) - ->setContinueOnNoEffect(true) - ->applyTransactions($dashboard, $xactions); - - return id(new AphrontRedirectResponse())->setURI($redirect_uri); - } - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->addHiddenInput('confirm', true) - ->addHiddenInput('panelPHID', $v_panel) - ->appendChild(pht('Are you sure you want to remove this panel?')); - - return $this->newDialog() - ->setTitle(pht('Remove Panel')) - ->appendChild($form->buildLayoutView()) - ->addCancelButton($redirect_uri) - ->addSubmitButton(pht('Remove Panel')); - } - -} diff --git a/src/applications/dashboard/controller/PhabricatorDashboardViewController.php b/src/applications/dashboard/controller/PhabricatorDashboardViewController.php deleted file mode 100644 index 41441f09f4..0000000000 --- a/src/applications/dashboard/controller/PhabricatorDashboardViewController.php +++ /dev/null @@ -1,68 +0,0 @@ -getViewer(); - $id = $request->getURIData('id'); - - $dashboard = id(new PhabricatorDashboardQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->needPanels(true) - ->executeOne(); - if (!$dashboard) { - return new Aphront404Response(); - } - $this->setDashboard($dashboard); - - $dashboard_uri = $this->getApplicationURI("view/{$id}/"); - $title = $dashboard->getName(); - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('View')); - - if ($dashboard->getPanelPHIDs()) { - $rendered_dashboard = id(new PhabricatorDashboardRenderingEngine()) - ->setViewer($viewer) - ->setDashboard($dashboard) - ->renderDashboard(); - $content = id(new PHUIBoxView()) - ->addClass('dashboard-preview-box') - ->appendChild($rendered_dashboard); - } else { - $content = id(new PHUIInfoView()) - ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) - ->appendChild(pht('This dashboard has no panels yet.')); - } - - $navigation = $this->buildSideNavView('view'); - $header = $this->buildHeaderView(); - - $install_button = id(new PHUIButtonView()) - ->setTag('a') - ->setText('Install Dashboard') - ->setIcon('fa-plus') - ->setColor(PHUIButtonView::GREEN) - ->setWorkflow(true) - ->setHref($this->getApplicationURI("/install/{$id}/")); - $header->addActionLink($install_button); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setFooter(array( - $content, - )); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->setNavigation($navigation) - ->appendChild($view); - } - -} diff --git a/src/applications/dashboard/controller/dashboard/PhabricatorDashboardAdjustController.php b/src/applications/dashboard/controller/dashboard/PhabricatorDashboardAdjustController.php new file mode 100644 index 0000000000..86a3ff5805 --- /dev/null +++ b/src/applications/dashboard/controller/dashboard/PhabricatorDashboardAdjustController.php @@ -0,0 +1,243 @@ +getViewer(); + + $context_phid = $request->getStr('contextPHID'); + + $dashboard = id(new PhabricatorDashboardQuery()) + ->setViewer($viewer) + ->withPHIDs(array($context_phid)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$dashboard) { + return new Aphront404Response(); + } + + $this->contextPHID = $context_phid; + + $done_uri = $dashboard->getURI(); + $ref_list = $dashboard->getPanelRefList(); + + $panel_ref = null; + $panel_key = $request->getStr('panelKey'); + if (strlen($panel_key)) { + $panel_ref = $ref_list->getPanelRef($panel_key); + if (!$panel_ref) { + return new Aphront404Response(); + } + + $this->panelKey = $panel_key; + } + + $column_key = $request->getStr('columnKey'); + if (strlen($column_key)) { + $columns = $ref_list->getColumns(); + if (!isset($columns[$column_key])) { + return new Aphront404Response(); + } + $this->columnKey = $column_key; + } + + $after_ref = null; + $after_key = $request->getStr('afterKey'); + if (strlen($after_key)) { + $after_ref = $ref_list->getPanelRef($after_key); + if (!$after_ref) { + return new Aphront404Response(); + } + } + + switch ($request->getURIData('op')) { + case 'add': + return $this->handleAddRequest($dashboard, $done_uri); + case 'remove': + if (!$panel_ref) { + return new Aphront404Response(); + } + return $this->handleRemoveRequest($dashboard, $panel_ref, $done_uri); + case 'move': + return $this->handleMoveRequest($dashboard, $panel_ref, $after_ref); + } + } + + private function handleAddRequest( + PhabricatorDashboard $dashboard, + $done_uri) { + $request = $this->getRequest(); + $viewer = $this->getViewer(); + + $errors = array(); + + $panel_phid = null; + $e_panel = true; + if ($request->isFormPost()) { + $panel_phid = head($request->getArr('panelPHIDs')); + + if (!$panel_phid) { + $errors[] = pht('You must choose a panel to add to the dashboard.'); + $e_panel = pht('Required'); + } else { + $panel = id(new PhabricatorDashboardPanelQuery()) + ->setViewer($viewer) + ->withPHIDs(array($panel_phid)) + ->executeOne(); + if (!$panel) { + $errors[] = pht('You must choose a valid panel.'); + $e_panel = pht('Invalid'); + } + } + + if (!$errors) { + $xactions = array(); + + $ref_list = clone $dashboard->getPanelRefList(); + $ref_list->newPanelRef($panel, $this->columnKey); + $new_panels = $ref_list->toDictionary(); + + $xactions[] = $dashboard->getApplicationTransactionTemplate() + ->setTransactionType( + PhabricatorDashboardPanelsTransaction::TRANSACTIONTYPE) + ->setNewValue($new_panels); + + $editor = $dashboard->getApplicationTransactionEditor() + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true); + + $editor->applyTransactions($dashboard, $xactions); + + return id(new AphrontRedirectResponse())->setURI($done_uri); + } + } + + if ($panel_phid) { + $panel_phids = array($panel_phid); + } else { + $panel_phids = array(); + } + + $form = id(new AphrontFormView()) + ->setViewer($viewer) + ->appendRemarkupInstructions( + pht('Choose a panel to add to this dashboard:')) + ->appendControl( + id(new AphrontFormTokenizerControl()) + ->setDatasource(new PhabricatorDashboardPanelDatasource()) + ->setLimit(1) + ->setName('panelPHIDs') + ->setLabel(pht('Panel')) + ->setError($e_panel) + ->setValue($panel_phids)); + + return $this->newEditDialog() + ->setTitle(pht('Add Panel')) + ->setWidth(AphrontDialogView::WIDTH_FORM) + ->setErrors($errors) + ->appendForm($form) + ->addCancelButton($done_uri) + ->addSubmitButton(pht('Add Panel')); + } + + private function handleRemoveRequest( + PhabricatorDashboard $dashboard, + PhabricatorDashboardPanelRef $panel_ref, + $done_uri) { + $request = $this->getRequest(); + $viewer = $this->getViewer(); + + // NOTE: If you can edit a dashboard, you can remove panels from it even + // if you don't have permission to see them or they aren't valid. We only + // require that the panel be present on the dashboard. + + if ($request->isFormPost()) { + $xactions = array(); + + $ref_list = clone $dashboard->getPanelRefList(); + $ref_list->removePanelRef($panel_ref); + $new_panels = $ref_list->toDictionary(); + + $xactions[] = $dashboard->getApplicationTransactionTemplate() + ->setTransactionType( + PhabricatorDashboardPanelsTransaction::TRANSACTIONTYPE) + ->setNewValue($new_panels); + + $editor = $dashboard->getApplicationTransactionEditor() + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true); + + $editor->applyTransactions($dashboard, $xactions); + + return id(new AphrontRedirectResponse())->setURI($done_uri); + } + + $panel_phid = $panel_ref->getPanelPHID(); + $handles = $viewer->loadHandles(array($panel_phid)); + $handle = $handles[$panel_phid]; + + $message = pht( + 'Remove panel %s from dashboard %s?', + phutil_tag('strong', array(), $handle->getFullName()), + phutil_tag('strong', array(), $dashboard->getName())); + + return $this->newEditDialog() + ->setTitle(pht('Remove Dashboard Panel')) + ->appendParagraph($message) + ->addCancelButton($done_uri) + ->addSubmitButton(pht('Remove Panel')); + } + + private function handleMoveRequest( + PhabricatorDashboard $dashboard, + PhabricatorDashboardPanelRef $panel_ref, + PhabricatorDashboardPanelRef $after_ref = null) { + + $request = $this->getRequest(); + $request->validateCSRF(); + $viewer = $this->getViewer(); + + $xactions = array(); + + $ref_list = clone $dashboard->getPanelRefList(); + $ref_list->movePanelRef($panel_ref, $this->columnKey, $after_ref); + $new_panels = $ref_list->toDictionary(); + + $xactions[] = $dashboard->getApplicationTransactionTemplate() + ->setTransactionType( + PhabricatorDashboardPanelsTransaction::TRANSACTIONTYPE) + ->setNewValue($new_panels); + + $editor = $dashboard->getApplicationTransactionEditor() + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true); + + $editor->applyTransactions($dashboard, $xactions); + + return id(new AphrontAjaxResponse())->setContent(array()); + } + + + private function newEditDialog() { + return $this->newDialog() + ->addHiddenInput('contextPHID', $this->contextPHID) + ->addHiddenInput('panelKey', $this->panelKey) + ->addHiddenInput('columnKey', $this->columnKey); + } + +} diff --git a/src/applications/dashboard/controller/PhabricatorDashboardArchiveController.php b/src/applications/dashboard/controller/dashboard/PhabricatorDashboardArchiveController.php similarity index 92% rename from src/applications/dashboard/controller/PhabricatorDashboardArchiveController.php rename to src/applications/dashboard/controller/dashboard/PhabricatorDashboardArchiveController.php index 709e03fdf0..fb69c477d7 100644 --- a/src/applications/dashboard/controller/PhabricatorDashboardArchiveController.php +++ b/src/applications/dashboard/controller/dashboard/PhabricatorDashboardArchiveController.php @@ -20,7 +20,7 @@ final class PhabricatorDashboardArchiveController return new Aphront404Response(); } - $view_uri = $this->getApplicationURI('manage/'.$dashboard->getID().'/'); + $view_uri = $dashboard->getURI(); if ($request->isFormPost()) { if ($dashboard->isArchived()) { @@ -32,7 +32,8 @@ final class PhabricatorDashboardArchiveController $xactions = array(); $xactions[] = id(new PhabricatorDashboardTransaction()) - ->setTransactionType(PhabricatorDashboardTransaction::TYPE_STATUS) + ->setTransactionType( + PhabricatorDashboardStatusTransaction::TRANSACTIONTYPE) ->setNewValue($new_status); id(new PhabricatorDashboardTransactionEditor()) diff --git a/src/applications/dashboard/controller/dashboard/PhabricatorDashboardEditController.php b/src/applications/dashboard/controller/dashboard/PhabricatorDashboardEditController.php new file mode 100644 index 0000000000..00e03dd286 --- /dev/null +++ b/src/applications/dashboard/controller/dashboard/PhabricatorDashboardEditController.php @@ -0,0 +1,12 @@ +setController($this) + ->buildResponse(); + } + +} diff --git a/src/applications/dashboard/controller/dashboard/PhabricatorDashboardInstallController.php b/src/applications/dashboard/controller/dashboard/PhabricatorDashboardInstallController.php new file mode 100644 index 0000000000..417086b405 --- /dev/null +++ b/src/applications/dashboard/controller/dashboard/PhabricatorDashboardInstallController.php @@ -0,0 +1,75 @@ +dashboard = $dashboard; + return $this; + } + + public function getDashboard() { + return $this->dashboard; + } + + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); + + $dashboard = id(new PhabricatorDashboardQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + if (!$dashboard) { + return new Aphront404Response(); + } + + $this->setDashboard($dashboard); + $cancel_uri = $dashboard->getURI(); + + $workflow_key = $request->getURIData('workflowKey'); + + $workflows = PhabricatorDashboardInstallWorkflow::getAllWorkflows(); + if (!isset($workflows[$workflow_key])) { + return $this->newWorkflowDialog($dashboard, $workflows); + } + + return id(clone $workflows[$workflow_key]) + ->setRequest($request) + ->setViewer($viewer) + ->setDashboard($dashboard) + ->setMode($request->getURIData('modeKey')) + ->handleRequest($request); + } + + private function newWorkflowDialog( + PhabricatorDashboard $dashboard, + array $workflows) { + $viewer = $this->getViewer(); + $cancel_uri = $dashboard->getURI(); + + $menu = id(new PHUIObjectItemListView()) + ->setViewer($viewer) + ->setFlush(true) + ->setBig(true); + + foreach ($workflows as $key => $workflow) { + $item = $workflow->getWorkflowMenuItem(); + + $item_href = urisprintf('install/%d/%s/', $dashboard->getID(), $key); + $item_href = $this->getApplicationURI($item_href); + $item->setHref($item_href); + + $menu->addItem($item); + } + + return $this->newDialog() + ->setTitle(pht('Add Dashboard to Menu')) + ->setWidth(AphrontDialogView::WIDTH_FORM) + ->appendChild($menu) + ->addCancelButton($cancel_uri); + } + +} diff --git a/src/applications/dashboard/controller/dashboard/PhabricatorDashboardViewController.php b/src/applications/dashboard/controller/dashboard/PhabricatorDashboardViewController.php new file mode 100644 index 0000000000..f43afd7e57 --- /dev/null +++ b/src/applications/dashboard/controller/dashboard/PhabricatorDashboardViewController.php @@ -0,0 +1,202 @@ +getViewer(); + $id = $request->getURIData('id'); + + $dashboard = id(new PhabricatorDashboardQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + if (!$dashboard) { + return new Aphront404Response(); + } + $this->setDashboard($dashboard); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $dashboard, + PhabricatorPolicyCapability::CAN_EDIT); + + $title = $dashboard->getName(); + $crumbs = $this->buildApplicationCrumbs(); + $header = $this->buildHeaderView(); + + $curtain = $this->buildCurtainView($dashboard); + + $usage_box = $this->newUsageView($dashboard); + + $timeline = $this->buildTransactionTimeline( + $dashboard, + new PhabricatorDashboardTransactionQuery()); + $timeline->setShouldTerminate(true); + + $rendered_dashboard = id(new PhabricatorDashboardRenderingEngine()) + ->setViewer($viewer) + ->setDashboard($dashboard) + ->setArrangeMode($can_edit) + ->renderDashboard(); + + $dashboard_box = id(new PHUIBoxView()) + ->addClass('dashboard-preview-box') + ->appendChild($rendered_dashboard); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn( + array( + $dashboard_box, + $usage_box, + $timeline, + )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + + } + + private function buildCurtainView(PhabricatorDashboard $dashboard) { + $viewer = $this->getViewer(); + $id = $dashboard->getID(); + + $curtain = $this->newCurtainView($dashboard); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $dashboard, + PhabricatorPolicyCapability::CAN_EDIT); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Dashboard')) + ->setIcon('fa-pencil') + ->setHref($this->getApplicationURI("edit/{$id}/")) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit)); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Add Dashboard to Menu')) + ->setIcon('fa-wrench') + ->setHref($this->getApplicationURI("/install/{$id}/")) + ->setWorkflow(true)); + + if ($dashboard->isArchived()) { + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Activate Dashboard')) + ->setIcon('fa-check') + ->setHref($this->getApplicationURI("archive/{$id}/")) + ->setDisabled(!$can_edit) + ->setWorkflow(true)); + } else { + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Archive Dashboard')) + ->setIcon('fa-ban') + ->setHref($this->getApplicationURI("archive/{$id}/")) + ->setDisabled(!$can_edit) + ->setWorkflow(true)); + } + + return $curtain; + } + + private function newUsageView(PhabricatorDashboard $dashboard) { + $viewer = $this->getViewer(); + + $custom_phids = array(); + if ($viewer->getPHID()) { + $custom_phids[] = $viewer->getPHID(); + } + + $items = id(new PhabricatorProfileMenuItemConfigurationQuery()) + ->setViewer($viewer) + ->withAffectedObjectPHIDs( + array( + $dashboard->getPHID(), + )) + ->withCustomPHIDs($custom_phids, $include_global = true) + ->execute(); + + $handle_phids = array(); + foreach ($items as $item) { + $handle_phids[] = $item->getProfilePHID(); + $custom_phid = $item->getCustomPHID(); + if ($custom_phid) { + $handle_phids[] = $custom_phid; + } + } + + if ($handle_phids) { + $handles = $viewer->loadHandles($handle_phids); + } else { + $handles = array(); + } + + $items = msortv($items, 'newUsageSortVector'); + + $rows = array(); + foreach ($items as $item) { + $profile_phid = $item->getProfilePHID(); + $custom_phid = $item->getCustomPHID(); + + $profile = $handles[$profile_phid]->renderLink(); + $profile_icon = $handles[$profile_phid]->getIcon(); + + if ($custom_phid) { + $custom = $handles[$custom_phid]->renderLink(); + } else { + $custom = pht('Global'); + } + + $type = $item->getProfileMenuTypeDescription(); + + $rows[] = array( + id(new PHUIIconView())->setIcon($profile_icon), + $type, + $profile, + $custom, + ); + } + + $usage_table = id(new AphrontTableView($rows)) + ->setNoDataString( + pht('This dashboard has not been added to any menus.')) + ->setHeaders( + array( + null, + pht('Type'), + pht('Menu'), + pht('Global/Personal'), + )) + ->setColumnClasses( + array( + 'center', + null, + 'pri', + 'wide', + )); + + $header_view = id(new PHUIHeaderView()) + ->setHeader(pht('Dashboard Used By')); + + $usage_box = id(new PHUIObjectBoxView()) + ->setTable($usage_table) + ->setHeader($header_view); + + return $usage_box; + } + + +} diff --git a/src/applications/dashboard/controller/PhabricatorDashboardPanelArchiveController.php b/src/applications/dashboard/controller/panel/PhabricatorDashboardPanelArchiveController.php similarity index 94% rename from src/applications/dashboard/controller/PhabricatorDashboardPanelArchiveController.php rename to src/applications/dashboard/controller/panel/PhabricatorDashboardPanelArchiveController.php index 8710539c49..05171e086d 100644 --- a/src/applications/dashboard/controller/PhabricatorDashboardPanelArchiveController.php +++ b/src/applications/dashboard/controller/panel/PhabricatorDashboardPanelArchiveController.php @@ -25,7 +25,8 @@ final class PhabricatorDashboardPanelArchiveController if ($request->isFormPost()) { $xactions = array(); $xactions[] = id(new PhabricatorDashboardPanelTransaction()) - ->setTransactionType(PhabricatorDashboardPanelTransaction::TYPE_ARCHIVE) + ->setTransactionType( + PhabricatorDashboardPanelStatusTransaction::TRANSACTIONTYPE) ->setNewValue((int)!$panel->getIsArchived()); id(new PhabricatorDashboardPanelTransactionEditor()) diff --git a/src/applications/dashboard/controller/panel/PhabricatorDashboardPanelEditController.php b/src/applications/dashboard/controller/panel/PhabricatorDashboardPanelEditController.php new file mode 100644 index 0000000000..4ab76d18b5 --- /dev/null +++ b/src/applications/dashboard/controller/panel/PhabricatorDashboardPanelEditController.php @@ -0,0 +1,104 @@ +getViewer(); + + $engine = id(new PhabricatorDashboardPanelEditEngine()) + ->setController($this); + + // We can create or edit a panel in the context of a dashboard or + // container panel, like a tab panel. If we started this flow on some + // container object, we want to return to that container when we're done + // editing. + + $context_phid = $request->getStr('contextPHID'); + if (strlen($context_phid)) { + $context = id(new PhabricatorObjectQuery()) + ->setViewer($viewer) + ->withPHIDs(array($context_phid)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$context) { + return new Aphront404Response(); + } + + if (!($context instanceof PhabricatorDashboardPanelContainerInterface)) { + return new Aphront404Response(); + } + + $engine + ->setContextObject($context) + ->addContextParameter('contextPHID', $context_phid); + } else { + $context = null; + } + + $id = $request->getURIData('id'); + if (!$id) { + $column_key = $request->getStr('columnKey'); + + if ($context) { + $cancel_uri = $context->getURI(); + } else { + $cancel_uri = $this->getApplicationURI('panel/'); + } + + $panel_type = $request->getStr('panelType'); + $panel_types = PhabricatorDashboardPanelType::getAllPanelTypes(); + if (empty($panel_types[$panel_type])) { + return $this->buildPanelTypeResponse($cancel_uri); + } + + $engine + ->addContextParameter('panelType', $panel_type) + ->addContextParameter('columnKey', $column_key) + ->setPanelType($panel_type) + ->setColumnKey($column_key); + } + + return $engine->buildResponse(); + } + + private function buildPanelTypeResponse($cancel_uri) { + $viewer = $this->getViewer(); + $request = $this->getRequest(); + + $base_uri = $request->getRequestURI(); + $base_uri = new PhutilURI($base_uri); + + $menu = id(new PHUIObjectItemListView()) + ->setViewer($viewer) + ->setFlush(true) + ->setBig(true); + + $panel_types = PhabricatorDashboardPanelType::getAllPanelTypes(); + foreach ($panel_types as $panel_type) { + $item = id(new PHUIObjectItemView()) + ->setClickable(true) + ->setImageIcon($panel_type->getIcon()) + ->setHeader($panel_type->getPanelTypeName()) + ->addAttribute($panel_type->getPanelTypeDescription()); + + $type_uri = id(clone $base_uri) + ->replaceQueryParam('panelType', $panel_type->getPanelTypeKey()); + + $item->setHref($type_uri); + + $menu->addItem($item); + } + + return $this->newDialog() + ->setTitle(pht('Choose Panel Type')) + ->setWidth(AphrontDialogView::WIDTH_FORM) + ->appendChild($menu) + ->addCancelButton($cancel_uri); + } + +} diff --git a/src/applications/dashboard/controller/PhabricatorDashboardPanelListController.php b/src/applications/dashboard/controller/panel/PhabricatorDashboardPanelListController.php similarity index 94% rename from src/applications/dashboard/controller/PhabricatorDashboardPanelListController.php rename to src/applications/dashboard/controller/panel/PhabricatorDashboardPanelListController.php index eaffdaa0d5..1eab148261 100644 --- a/src/applications/dashboard/controller/PhabricatorDashboardPanelListController.php +++ b/src/applications/dashboard/controller/panel/PhabricatorDashboardPanelListController.php @@ -43,7 +43,7 @@ final class PhabricatorDashboardPanelListController id(new PHUIListItemView()) ->setIcon('fa-plus-square') ->setName(pht('Create Panel')) - ->setHref($this->getApplicationURI().'panel/create/')); + ->setHref($this->getApplicationURI().'panel/edit/')); return $crumbs; } @@ -52,7 +52,7 @@ final class PhabricatorDashboardPanelListController $create_button = id(new PHUIButtonView()) ->setTag('a') ->setText(pht('Create a Panel')) - ->setHref('/dashboard/panel/create/') + ->setHref('/dashboard/panel/edit/') ->setColor(PHUIButtonView::GREEN); $icon = $this->getApplication()->getIcon(); diff --git a/src/applications/dashboard/controller/PhabricatorDashboardPanelRenderController.php b/src/applications/dashboard/controller/panel/PhabricatorDashboardPanelRenderController.php similarity index 75% rename from src/applications/dashboard/controller/PhabricatorDashboardPanelRenderController.php rename to src/applications/dashboard/controller/panel/PhabricatorDashboardPanelRenderController.php index 8f25a0f9bb..fee7ef6de4 100644 --- a/src/applications/dashboard/controller/PhabricatorDashboardPanelRenderController.php +++ b/src/applications/dashboard/controller/panel/PhabricatorDashboardPanelRenderController.php @@ -31,14 +31,28 @@ final class PhabricatorDashboardPanelRenderController $parent_phids = array(); } - $rendered_panel = id(new PhabricatorDashboardPanelRenderingEngine()) + $engine = id(new PhabricatorDashboardPanelRenderingEngine()) ->setViewer($viewer) ->setPanel($panel) ->setPanelPHID($panel->getPHID()) ->setParentPanelPHIDs($parent_phids) + ->setMovable($request->getBool('movable')) ->setHeaderMode($request->getStr('headerMode')) - ->setDashboardID($request->getInt('dashboardID')) - ->renderPanel(); + ->setPanelKey($request->getStr('panelKey')); + + $context_phid = $request->getStr('contextPHID'); + if ($context_phid) { + $context = id(new PhabricatorObjectQuery()) + ->setViewer($viewer) + ->withPHIDs(array($context_phid)) + ->executeOne(); + if (!$context) { + return new Aphront404Response(); + } + $engine->setContextObject($context); + } + + $rendered_panel = $engine->renderPanel(); if ($request->isAjax()) { return id(new AphrontAjaxResponse()) diff --git a/src/applications/dashboard/controller/panel/PhabricatorDashboardPanelTabsController.php b/src/applications/dashboard/controller/panel/PhabricatorDashboardPanelTabsController.php new file mode 100644 index 0000000000..0703332eac --- /dev/null +++ b/src/applications/dashboard/controller/panel/PhabricatorDashboardPanelTabsController.php @@ -0,0 +1,354 @@ +contextObject = $context_object; + return $this; + } + + private function getContextObject() { + return $this->contextObject; + } + + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + + $panel = id(new PhabricatorDashboardPanelQuery()) + ->setViewer($viewer) + ->withIDs(array($request->getURIData('id'))) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$panel) { + return new Aphront404Response(); + } + + $tabs_type = id(new PhabricatorDashboardTabsPanelType()) + ->getPanelTypeKey(); + + // This controller may only be used to edit tab panels. + $panel_type = $panel->getPanelType(); + if ($panel_type !== $tabs_type) { + return new Aphront404Response(); + } + + $op = $request->getURIData('op'); + $after = $request->getStr('after'); + if (!strlen($after)) { + $after = null; + } + + $target = $request->getStr('target'); + if (!strlen($target)) { + $target = null; + } + + $impl = $panel->getImplementation(); + $config = $impl->getPanelConfiguration($panel); + + $cancel_uri = $panel->getURI(); + + if ($after !== null) { + $found = false; + foreach ($config as $key => $spec) { + if ((string)$key === $after) { + $found = true; + break; + } + } + + if (!$found) { + return $this->newDialog() + ->setTitle(pht('Adjacent Tab Not Found')) + ->appendParagraph( + pht( + 'Adjacent tab ("%s") was not found on this panel. It may have '. + 'been removed.', + $after)) + ->addCancelButton($cancel_uri); + } + } + + if ($target !== null) { + $found = false; + foreach ($config as $key => $spec) { + if ((string)$key === $target) { + $found = true; + break; + } + } + + if (!$found) { + return $this->newDialog() + ->setTitle(pht('Target Tab Not Found')) + ->appendParagraph( + pht( + 'Target tab ("%s") was not found on this panel. It may have '. + 'been removed.', + $target)) + ->addCancelButton($cancel_uri); + } + } + + // Tab panels may be edited from the panel page, or from the context of + // a dashboard. If we're editing from a dashboard, we want to redirect + // back to the dashboard after making changes. + + $context_phid = $request->getStr('contextPHID'); + $context = null; + if (strlen($context_phid)) { + $context = id(new PhabricatorObjectQuery()) + ->setViewer($viewer) + ->withPHIDs(array($context_phid)) + ->executeOne(); + if (!$context) { + return new Aphront404Response(); + } + + switch (phid_get_type($context_phid)) { + case PhabricatorDashboardDashboardPHIDType::TYPECONST: + $cancel_uri = $context->getURI(); + break; + case PhabricatorDashboardPanelPHIDType::TYPECONST: + $cancel_uri = $context->getURI(); + break; + default: + return $this->newDialog() + ->setTitle(pht('Context Object Unsupported')) + ->appendParagraph( + pht( + 'Context object ("%s") has unsupported type. Panels should '. + 'be rendered from the context of a dashboard or another '. + 'panel.', + $context_phid)) + ->addCancelButton($cancel_uri); + } + + $this->setContextObject($context); + } + + switch ($op) { + case 'add': + return $this->handleAddOperation($panel, $after, $cancel_uri); + case 'remove': + return $this->handleRemoveOperation($panel, $target, $cancel_uri); + case 'move': + break; + case 'rename': + return $this->handleRenameOperation($panel, $target, $cancel_uri); + } + } + + private function handleAddOperation( + PhabricatorDashboardPanel $panel, + $after, + $cancel_uri) { + $request = $this->getRequest(); + $viewer = $this->getViewer(); + + $panel_phid = null; + $errors = array(); + if ($request->isFormPost()) { + $panel_phid = $request->getArr('panelPHID'); + $panel_phid = head($panel_phid); + + $add_panel = id(new PhabricatorDashboardPanelQuery()) + ->setViewer($viewer) + ->withPHIDs(array($panel_phid)) + ->executeOne(); + if (!$add_panel) { + $errors[] = pht('You must select a valid panel.'); + } + + if (!$errors) { + $add_panel_config = array( + 'name' => null, + 'panelID' => $add_panel->getID(), + ); + $add_panel_key = Filesystem::readRandomCharacters(12); + + $impl = $panel->getImplementation(); + $old_config = $impl->getPanelConfiguration($panel); + $new_config = array(); + if ($after === null) { + $new_config = $old_config; + $new_config[] = $add_panel_config; + } else { + foreach ($old_config as $key => $value) { + $new_config[$key] = $value; + if ((string)$key === $after) { + $new_config[$add_panel_key] = $add_panel_config; + } + } + } + + $xactions = array(); + + $xactions[] = $panel->getApplicationTransactionTemplate() + ->setTransactionType( + PhabricatorDashboardTabsPanelTabsTransaction::TRANSACTIONTYPE) + ->setNewValue($new_config); + + $editor = id(new PhabricatorDashboardPanelTransactionEditor()) + ->setContentSourceFromRequest($request) + ->setActor($viewer) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true); + + $editor->applyTransactions($panel, $xactions); + + return id(new AphrontRedirectResponse())->setURI($cancel_uri); + } + } + + if ($panel_phid) { + $v_panel = array($panel_phid); + } else { + $v_panel = array(); + } + + $form = id(new AphrontFormView()) + ->setViewer($viewer) + ->appendControl( + id(new AphrontFormTokenizerControl()) + ->setDatasource(new PhabricatorDashboardPanelDatasource()) + ->setLimit(1) + ->setName('panelPHID') + ->setLabel(pht('Panel')) + ->setValue($v_panel)); + + return $this->newEditDialog() + ->setTitle(pht('Choose Dashboard Panel')) + ->setErrors($errors) + ->addHiddenInput('after', $after) + ->appendForm($form) + ->addCancelButton($cancel_uri) + ->addSubmitButton(pht('Add Panel')); + } + + private function handleRemoveOperation( + PhabricatorDashboardPanel $panel, + $target, + $cancel_uri) { + $request = $this->getRequest(); + $viewer = $this->getViewer(); + + $panel_phid = null; + $errors = array(); + if ($request->isFormPost()) { + $impl = $panel->getImplementation(); + $old_config = $impl->getPanelConfiguration($panel); + + $new_config = $this->removePanel($old_config, $target); + $this->writePanelConfig($panel, $new_config); + + return id(new AphrontRedirectResponse())->setURI($cancel_uri); + } + + return $this->newEditDialog() + ->setTitle(pht('Remove tab?')) + ->addHiddenInput('target', $target) + ->appendParagraph(pht('Really remove this tab?')) + ->addCancelButton($cancel_uri) + ->addSubmitButton(pht('Remove Tab')); + } + + private function handleRenameOperation( + PhabricatorDashboardPanel $panel, + $target, + $cancel_uri) { + $request = $this->getRequest(); + $viewer = $this->getViewer(); + + $impl = $panel->getImplementation(); + $old_config = $impl->getPanelConfiguration($panel); + + $spec = $old_config[$target]; + $name = idx($spec, 'name'); + + if ($request->isFormPost()) { + $name = $request->getStr('name'); + + $new_config = $this->renamePanel($old_config, $target, $name); + $this->writePanelConfig($panel, $new_config); + + return id(new AphrontRedirectResponse())->setURI($cancel_uri); + } + + $form = id(new AphrontFormView()) + ->setViewer($viewer) + ->appendControl( + id(new AphrontFormTextControl()) + ->setValue($name) + ->setName('name') + ->setLabel(pht('Tab Name'))); + + return $this->newEditDialog() + ->setTitle(pht('Rename Panel')) + ->addHiddenInput('target', $target) + ->appendForm($form) + ->addCancelButton($cancel_uri) + ->addSubmitButton(pht('Rename Tab')); + } + + + private function writePanelConfig( + PhabricatorDashboardPanel $panel, + array $config) { + $request = $this->getRequest(); + $viewer = $this->getViewer(); + + $xactions = array(); + + $xactions[] = $panel->getApplicationTransactionTemplate() + ->setTransactionType( + PhabricatorDashboardTabsPanelTabsTransaction::TRANSACTIONTYPE) + ->setNewValue($config); + + $editor = id(new PhabricatorDashboardPanelTransactionEditor()) + ->setContentSourceFromRequest($request) + ->setActor($viewer) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true); + + return $editor->applyTransactions($panel, $xactions); + } + + private function removePanel(array $config, $target) { + $result = array(); + + foreach ($config as $key => $panel_spec) { + if ((string)$key === $target) { + continue; + } + $result[$key] = $panel_spec; + } + + return $result; + } + + private function renamePanel(array $config, $target, $name) { + $config[$target]['name'] = $name; + return $config; + } + + protected function newEditDialog() { + $dialog = $this->newDialog() + ->setWidth(AphrontDialogView::WIDTH_FORM); + + $context = $this->getContextObject(); + if ($context) { + $dialog->addHiddenInput('contextPHID', $context->getPHID()); + } + + return $dialog; + } + +} diff --git a/src/applications/dashboard/controller/PhabricatorDashboardPanelViewController.php b/src/applications/dashboard/controller/panel/PhabricatorDashboardPanelViewController.php similarity index 66% rename from src/applications/dashboard/controller/PhabricatorDashboardPanelViewController.php rename to src/applications/dashboard/controller/panel/PhabricatorDashboardPanelViewController.php index 4b5f1b45be..56f9821fcd 100644 --- a/src/applications/dashboard/controller/PhabricatorDashboardPanelViewController.php +++ b/src/applications/dashboard/controller/panel/PhabricatorDashboardPanelViewController.php @@ -19,6 +19,11 @@ final class PhabricatorDashboardPanelViewController return new Aphront404Response(); } + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $panel, + PhabricatorPolicyCapability::CAN_EDIT); + $title = $panel->getMonogram().' '.$panel->getName(); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb( @@ -29,17 +34,21 @@ final class PhabricatorDashboardPanelViewController $header = $this->buildHeaderView($panel); $curtain = $this->buildCurtainView($panel); - $properties = $this->buildPropertyView($panel); + + $usage_box = $this->newUsageView($panel); $timeline = $this->buildTransactionTimeline( $panel, new PhabricatorDashboardPanelTransactionQuery()); + $timeline->setShouldTerminate(true); $rendered_panel = id(new PhabricatorDashboardPanelRenderingEngine()) ->setViewer($viewer) ->setPanel($panel) + ->setContextObject($panel) ->setPanelPHID($panel->getPHID()) ->setParentPanelPHIDs(array()) + ->setEditMode(true) ->renderPanel(); $preview = id(new PHUIBoxView()) @@ -50,10 +59,10 @@ final class PhabricatorDashboardPanelViewController ->setHeader($header) ->setCurtain($curtain) ->setMainColumn(array( - $properties, + $rendered_panel, + $usage_box, $timeline, - )) - ->setFooter($rendered_panel); + )); return $this->newPage() ->setTitle($title) @@ -65,18 +74,11 @@ final class PhabricatorDashboardPanelViewController $viewer = $this->getViewer(); $id = $panel->getID(); - $button = id(new PHUIButtonView()) - ->setTag('a') - ->setText(pht('View Panel')) - ->setIcon('fa-columns') - ->setHref($this->getApplicationURI("panel/render/{$id}/")); - $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($panel->getName()) ->setPolicyObject($panel) - ->setHeaderIcon('fa-columns') - ->addActionLink($button); + ->setHeaderIcon('fa-window-maximize'); if (!$panel->getIsArchived()) { $header->setStatus('fa-check', 'bluegrey', pht('Active')); @@ -124,51 +126,51 @@ final class PhabricatorDashboardPanelViewController return $curtain; } - private function buildPropertyView(PhabricatorDashboardPanel $panel) { + private function newUsageView(PhabricatorDashboardPanel $panel) { $viewer = $this->getViewer(); - $properties = id(new PHUIPropertyListView()) - ->setUser($viewer); + $object_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( + $panel->getPHID(), + PhabricatorDashboardPanelUsedByObjectEdgeType::EDGECONST); - $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( - $viewer, - $panel); - - $panel_type = $panel->getImplementation(); - if ($panel_type) { - $type_name = $panel_type->getPanelTypeName(); + if ($object_phids) { + $handles = $viewer->loadHandles($object_phids); } else { - $type_name = phutil_tag( - 'em', - array(), - nonempty($panel->getPanelType(), pht('null'))); + $handles = array(); } - $properties->addProperty( - pht('Panel Type'), - $type_name); + $rows = array(); + foreach ($object_phids as $object_phid) { + $handle = $handles[$object_phid]; - $properties->addProperty( - pht('Editable By'), - $descriptions[PhabricatorPolicyCapability::CAN_EDIT]); + $icon = $handle->getIcon(); - $dashboard_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( - $panel->getPHID(), - PhabricatorDashboardPanelHasDashboardEdgeType::EDGECONST); + $rows[] = array( + id(new PHUIIconView())->setIcon($icon), + $handle->getTypeName(), + $handle->renderLink(), + ); + } - $does_not_appear = pht( - 'This panel does not appear on any dashboards.'); + $usage_table = id(new AphrontTableView($rows)) + ->setNoDataString( + pht( + 'This panel is not used on any dashboard or inside any other '. + 'panel container.')) + ->setColumnClasses( + array( + 'center', + '', + 'pri wide', + )); - $properties->addProperty( - pht('Appears On'), - $dashboard_phids - ? $viewer->renderHandleList($dashboard_phids) - : phutil_tag('em', array(), $does_not_appear)); + $header_view = id(new PHUIHeaderView()) + ->setHeader(pht('Panel Used By')); - return id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Details')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->addPropertyList($properties); + $usage_box = id(new PHUIObjectBoxView()) + ->setTable($usage_table) + ->setHeader($header_view); + + return $usage_box; } - } diff --git a/src/applications/dashboard/customfield/PhabricatorDashboardPanelCoreCustomField.php b/src/applications/dashboard/customfield/PhabricatorDashboardPanelCoreCustomField.php deleted file mode 100644 index 6657e9cc41..0000000000 --- a/src/applications/dashboard/customfield/PhabricatorDashboardPanelCoreCustomField.php +++ /dev/null @@ -1,47 +0,0 @@ -getPanelType()) { - return array(); - } - - $impl = $object->requireImplementation(); - $specs = $impl->getFieldSpecifications(); - return PhabricatorStandardCustomField::buildStandardFields($this, $specs); - } - - public function shouldUseStorage() { - return false; - } - - public function readValueFromObject(PhabricatorCustomFieldInterface $object) { - $key = $this->getProxy()->getRawStandardFieldKey(); - $this->setValueFromStorage($object->getProperty($key)); - $this->didSetValueFromStorage(); - } - - public function applyApplicationTransactionInternalEffects( - PhabricatorApplicationTransaction $xaction) { - $object = $this->getObject(); - $key = $this->getProxy()->getRawStandardFieldKey(); - - $this->setValueFromApplicationTransactions($xaction->getNewValue()); - $value = $this->getValueForStorage(); - - $object->setProperty($key, $value); - } - - public function applyApplicationTransactionExternalEffects( - PhabricatorApplicationTransaction $xaction) { - return; - } - -} diff --git a/src/applications/dashboard/customfield/PhabricatorDashboardPanelCustomField.php b/src/applications/dashboard/customfield/PhabricatorDashboardPanelCustomField.php deleted file mode 100644 index 43ece18e7b..0000000000 --- a/src/applications/dashboard/customfield/PhabricatorDashboardPanelCustomField.php +++ /dev/null @@ -1,4 +0,0 @@ -getArr($this->getFieldKey().'_name'); - $panel_ids = $request->getArr($this->getFieldKey().'_panelID'); - $panels = array(); - foreach ($panel_ids as $panel_id) { - $panels[] = $panel_id[0]; - } - foreach ($names as $idx => $name) { - $panel_id = idx($panels, $idx); - if (strlen($name) && $panel_id) { - $value[] = array( - 'name' => $name, - 'panelID' => $panel_id, - ); - } - } - - $this->setFieldValue($value); - } - - public function getApplicationTransactionTitle( - PhabricatorApplicationTransaction $xaction) { - $author_phid = $xaction->getAuthorPHID(); - $old = $xaction->getOldValue(); - $new = $xaction->getNewValue(); - - $new_tabs = array(); - if ($new) { - foreach ($new as $new_tab) { - $new_tabs[] = $new_tab['name']; - } - $new_tabs = implode(' | ', $new_tabs); - } - - $old_tabs = array(); - if ($old) { - foreach ($old as $old_tab) { - $old_tabs[] = $old_tab['name']; - } - $old_tabs = implode(' | ', $old_tabs); - } - - if (!$old) { - // In case someone makes a tab panel with no tabs. - if ($new) { - return pht( - '%s set the tabs to "%s".', - $xaction->renderHandleLink($author_phid), - $new_tabs); - } - } else if (!$new) { - return pht( - '%s removed tabs.', - $xaction->renderHandleLink($author_phid)); - } else { - return pht( - '%s changed the tabs from "%s" to "%s".', - $xaction->renderHandleLink($author_phid), - $old_tabs, - $new_tabs); - } - } - - public function renderEditControl(array $handles) { - // NOTE: This includes archived panels so we don't mutate the tabs - // when saving a tab panel that includes archived panels. This whole UI is - // hopefully temporary anyway. - - $value = $this->getFieldValue(); - if (!is_array($value)) { - $value = array(); - } - - $out = array(); - for ($ii = 1; $ii <= 6; $ii++) { - $tab = idx($value, ($ii - 1), array()); - $panel = idx($tab, 'panelID', null); - $panel_id = array(); - if ($panel) { - $panel_id[] = $panel; - } - $out[] = id(new AphrontFormTextControl()) - ->setName($this->getFieldKey().'_name[]') - ->setValue(idx($tab, 'name')) - ->setLabel(pht('Tab %d Name', $ii)); - - $out[] = id(new AphrontFormTokenizerControl()) - ->setUser($this->getViewer()) - ->setDatasource(new PhabricatorDashboardPanelDatasource()) - ->setName($this->getFieldKey().'_panelID[]') - ->setValue($panel_id) - ->setLimit(1) - ->setLabel(pht('Tab %d Panel', $ii)); - } - - return $out; - } - -} diff --git a/src/applications/dashboard/edge/PhabricatorDashboardDashboardHasPanelEdgeType.php b/src/applications/dashboard/edge/PhabricatorDashboardDashboardHasPanelEdgeType.php deleted file mode 100644 index 8b31ea7476..0000000000 --- a/src/applications/dashboard/edge/PhabricatorDashboardDashboardHasPanelEdgeType.php +++ /dev/null @@ -1,103 +0,0 @@ -setAncestorClass('PhabricatorApplicationSearchEngine') ->setFilterMethod('canUseInPanelContext') @@ -31,7 +25,7 @@ final class PhabricatorDashboardPanelSearchApplicationCustomField $options = array(); - $value = $this->getFieldValue(); + $value = $this->getValueForControl(); if (strlen($value) && empty($engines[$value])) { $options[$value] = $value; } @@ -42,12 +36,24 @@ final class PhabricatorDashboardPanelSearchApplicationCustomField } return id(new AphrontFormSelectControl()) - ->setID($this->getFieldControlID()) - ->setLabel($this->getFieldName()) - ->setCaption($this->getCaption()) - ->setName($this->getFieldKey()) - ->setValue($this->getFieldValue()) + ->setID($this->getControlID()) ->setOptions($options); } + protected function newHTTPParameterType() { + return new AphrontSelectHTTPParameterType(); + } + + public function getControlID() { + if (!$this->controlID) { + $this->controlID = celerity_generate_unique_node_id(); + } + + return $this->controlID; + } + + protected function newConduitParameterType() { + return new ConduitStringParameterType(); + } + } diff --git a/src/applications/dashboard/customfield/PhabricatorDashboardPanelSearchQueryCustomField.php b/src/applications/dashboard/editfield/PhabricatorDashboardQueryPanelQueryEditField.php similarity index 59% rename from src/applications/dashboard/customfield/PhabricatorDashboardPanelSearchQueryCustomField.php rename to src/applications/dashboard/editfield/PhabricatorDashboardQueryPanelQueryEditField.php index 87870bf6ff..58192c0eee 100644 --- a/src/applications/dashboard/customfield/PhabricatorDashboardPanelSearchQueryCustomField.php +++ b/src/applications/dashboard/editfield/PhabricatorDashboardQueryPanelQueryEditField.php @@ -1,23 +1,26 @@ applicationControlID = $id; + return $this; } - public function shouldAppearInApplicationSearch() { - return false; + public function getApplicationControlID() { + return $this->applicationControlID; } - public function renderEditControl(array $handles) { + protected function newControl() { $engines = id(new PhutilClassMapQuery()) ->setAncestorClass('PhabricatorApplicationSearchEngine') ->setFilterMethod('canUseInPanelContext') ->execute(); - $value = $this->getFieldValue(); + $value = $this->getValueForControl(); $queries = array(); $seen = false; @@ -43,12 +46,14 @@ final class PhabricatorDashboardPanelSearchQueryCustomField $options = array($value => $name); - $app_control_key = $this->getFieldConfigValue('control.application'); + $application_id = $this->getApplicationControlID(); + $control_id = celerity_generate_unique_node_id(); + Javelin::initBehavior( 'dashboard-query-panel-select', array( - 'applicationID' => $this->getFieldControlID($app_control_key), - 'queryID' => $this->getFieldControlID(), + 'applicationID' => $application_id, + 'queryID' => $control_id, 'options' => $queries, 'value' => array( 'key' => strlen($value) ? $value : null, @@ -57,12 +62,16 @@ final class PhabricatorDashboardPanelSearchQueryCustomField )); return id(new AphrontFormSelectControl()) - ->setID($this->getFieldControlID()) - ->setLabel($this->getFieldName()) - ->setCaption($this->getCaption()) - ->setName($this->getFieldKey()) - ->setValue($this->getFieldValue()) + ->setID($control_id) ->setOptions($options); } + protected function newHTTPParameterType() { + return new AphrontSelectHTTPParameterType(); + } + + protected function newConduitParameterType() { + return new ConduitStringParameterType(); + } + } diff --git a/src/applications/dashboard/editor/PhabricatorDashboardEditEngine.php b/src/applications/dashboard/editor/PhabricatorDashboardEditEngine.php new file mode 100644 index 0000000000..06b0a3a6fb --- /dev/null +++ b/src/applications/dashboard/editor/PhabricatorDashboardEditEngine.php @@ -0,0 +1,108 @@ +getViewer(); + return PhabricatorDashboard::initializeNewDashboard($viewer); + } + + protected function newObjectQuery() { + return new PhabricatorDashboardQuery(); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create Dashboard'); + } + + protected function getObjectCreateButtonText($object) { + return pht('Create Dashboard'); + } + + protected function getObjectCreateCancelURI($object) { + return '/dashboard/'; + } + + protected function getObjectEditTitleText($object) { + return pht('Edit Dashboard: %s', $object->getName()); + } + + protected function getObjectEditShortText($object) { + return pht('Edit Dashboard'); + } + + protected function getObjectCreateShortText() { + return pht('Create Dashboard'); + } + + protected function getObjectName() { + return pht('Dashboard'); + } + + protected function getObjectViewURI($object) { + return $object->getURI(); + } + + protected function buildCustomEditFields($object) { + $layout_options = PhabricatorDashboardLayoutMode::getLayoutModeMap(); + + $fields = array( + id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setDescription(pht('Name of the dashboard.')) + ->setConduitDescription(pht('Rename the dashboard.')) + ->setConduitTypeDescription(pht('New dashboard name.')) + ->setTransactionType( + PhabricatorDashboardNameTransaction::TRANSACTIONTYPE) + ->setIsRequired(true) + ->setValue($object->getName()), + id(new PhabricatorIconSetEditField()) + ->setKey('icon') + ->setLabel(pht('Icon')) + ->setTransactionType( + PhabricatorDashboardIconTransaction::TRANSACTIONTYPE) + ->setIconSet(new PhabricatorDashboardIconSet()) + ->setDescription(pht('Dashboard icon.')) + ->setConduitDescription(pht('Change the dashboard icon.')) + ->setConduitTypeDescription(pht('New dashboard icon.')) + ->setValue($object->getIcon()), + id(new PhabricatorSelectEditField()) + ->setKey('layout') + ->setLabel(pht('Layout')) + ->setDescription(pht('Dashboard layout mode.')) + ->setConduitDescription(pht('Change the dashboard layout mode.')) + ->setConduitTypeDescription(pht('New dashboard layout mode.')) + ->setTransactionType( + PhabricatorDashboardLayoutTransaction::TRANSACTIONTYPE) + ->setOptions($layout_options) + ->setValue($object->getRawLayoutMode()), + ); + + return $fields; + } + +} diff --git a/src/applications/dashboard/editor/PhabricatorDashboardPanelEditEngine.php b/src/applications/dashboard/editor/PhabricatorDashboardPanelEditEngine.php index c9be8bf013..da891ebd5d 100644 --- a/src/applications/dashboard/editor/PhabricatorDashboardPanelEditEngine.php +++ b/src/applications/dashboard/editor/PhabricatorDashboardPanelEditEngine.php @@ -6,6 +6,8 @@ final class PhabricatorDashboardPanelEditEngine const ENGINECONST = 'dashboard.panel'; private $panelType; + private $contextObject; + private $columnKey; public function setPanelType($panel_type) { $this->panelType = $panel_type; @@ -16,6 +18,24 @@ final class PhabricatorDashboardPanelEditEngine return $this->panelType; } + public function setContextObject($context) { + $this->contextObject = $context; + return $this; + } + + public function getContextObject() { + return $this->contextObject; + } + + public function setColumnKey($column_key) { + $this->columnKey = $column_key; + return $this; + } + + public function getColumnKey() { + return $this->columnKey; + } + public function isEngineConfigurable() { return false; } @@ -63,6 +83,33 @@ final class PhabricatorDashboardPanelEditEngine return pht('Create Panel'); } + protected function getObjectCreateCancelURI($object) { + $context = $this->getContextObject(); + if ($context) { + return $context->getURI(); + } + + return parent::getObjectCreateCancelURI($object); + } + + public function getEffectiveObjectEditDoneURI($object) { + $context = $this->getContextObject(); + if ($context) { + return $context->getURI(); + } + + return parent::getEffectiveObjectEditDoneURI($object); + } + + protected function getObjectEditCancelURI($object) { + $context = $this->getContextObject(); + if ($context) { + return $context->getURI(); + } + + return parent::getObjectEditCancelURI($object); + } + protected function getObjectEditTitleText($object) { return pht('Edit Panel: %s', $object->getName()); } @@ -83,18 +130,58 @@ final class PhabricatorDashboardPanelEditEngine return $object->getURI(); } + protected function didApplyTransactions($object, array $xactions) { + $context = $this->getContextObject(); + + if ($context instanceof PhabricatorDashboard) { + $viewer = $this->getViewer(); + $controller = $this->getController(); + $request = $controller->getRequest(); + + $dashboard = $context; + + $xactions = array(); + + $ref_list = clone $dashboard->getPanelRefList(); + + $ref_list->newPanelRef($object, $this->getColumnKey()); + $new_panels = $ref_list->toDictionary(); + + $xactions[] = $dashboard->getApplicationTransactionTemplate() + ->setTransactionType( + PhabricatorDashboardPanelsTransaction::TRANSACTIONTYPE) + ->setNewValue($new_panels); + + $editor = $dashboard->getApplicationTransactionEditor() + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true); + + $editor->applyTransactions($dashboard, $xactions); + } + } + protected function buildCustomEditFields($object) { - return array( + $fields = array( id(new PhabricatorTextEditField()) ->setKey('name') ->setLabel(pht('Name')) ->setDescription(pht('Name of the panel.')) ->setConduitDescription(pht('Rename the panel.')) ->setConduitTypeDescription(pht('New panel name.')) - ->setTransactionType(PhabricatorDashboardPanelTransaction::TYPE_NAME) + ->setTransactionType( + PhabricatorDashboardPanelNameTransaction::TRANSACTIONTYPE) ->setIsRequired(true) ->setValue($object->getName()), ); + + $panel_fields = $object->getEditEngineFields(); + foreach ($panel_fields as $panel_field) { + $fields[] = $panel_field; + } + + return $fields; } } diff --git a/src/applications/dashboard/editor/PhabricatorDashboardPanelTransactionEditor.php b/src/applications/dashboard/editor/PhabricatorDashboardPanelTransactionEditor.php index 2a56721442..ea03c1ac7d 100644 --- a/src/applications/dashboard/editor/PhabricatorDashboardPanelTransactionEditor.php +++ b/src/applications/dashboard/editor/PhabricatorDashboardPanelTransactionEditor.php @@ -18,96 +18,11 @@ final class PhabricatorDashboardPanelTransactionEditor $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; $types[] = PhabricatorTransactions::TYPE_EDGE; - $types[] = PhabricatorDashboardPanelTransaction::TYPE_NAME; - $types[] = PhabricatorDashboardPanelTransaction::TYPE_ARCHIVE; - return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case PhabricatorDashboardPanelTransaction::TYPE_NAME: - if ($this->getIsNewObject()) { - return null; - } - return $object->getName(); - case PhabricatorDashboardPanelTransaction::TYPE_ARCHIVE: - return (int)$object->getIsArchived(); - } - - return parent::getCustomTransactionOldValue($object, $xaction); + protected function supportsSearch() { + return true; } - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case PhabricatorDashboardPanelTransaction::TYPE_NAME: - return $xaction->getNewValue(); - case PhabricatorDashboardPanelTransaction::TYPE_ARCHIVE: - return (int)$xaction->getNewValue(); - } - return parent::getCustomTransactionNewValue($object, $xaction); - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case PhabricatorDashboardPanelTransaction::TYPE_NAME: - $object->setName($xaction->getNewValue()); - return; - case PhabricatorDashboardPanelTransaction::TYPE_ARCHIVE: - $object->setIsArchived((int)$xaction->getNewValue()); - return; - } - - return parent::applyCustomInternalTransaction($object, $xaction); - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorDashboardPanelTransaction::TYPE_NAME: - case PhabricatorDashboardPanelTransaction::TYPE_ARCHIVE: - return; - } - - return parent::applyCustomExternalTransaction($object, $xaction); - } - - protected function validateTransaction( - PhabricatorLiskDAO $object, - $type, - array $xactions) { - - $errors = parent::validateTransaction($object, $type, $xactions); - - switch ($type) { - case PhabricatorDashboardPanelTransaction::TYPE_NAME: - $missing = $this->validateIsEmptyTextField( - $object->getName(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Panel name is required.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - break; - } - - return $errors; - } - - } diff --git a/src/applications/dashboard/editor/PhabricatorDashboardPortalEditor.php b/src/applications/dashboard/editor/PhabricatorDashboardPortalEditor.php index 9989d1e7d5..2c8a3ccfac 100644 --- a/src/applications/dashboard/editor/PhabricatorDashboardPortalEditor.php +++ b/src/applications/dashboard/editor/PhabricatorDashboardPortalEditor.php @@ -28,4 +28,8 @@ final class PhabricatorDashboardPortalEditor return $types; } + protected function supportsSearch() { + return true; + } + } diff --git a/src/applications/dashboard/editor/PhabricatorDashboardTransactionEditor.php b/src/applications/dashboard/editor/PhabricatorDashboardTransactionEditor.php index 792f6f9aa5..4a84577467 100644 --- a/src/applications/dashboard/editor/PhabricatorDashboardTransactionEditor.php +++ b/src/applications/dashboard/editor/PhabricatorDashboardTransactionEditor.php @@ -11,38 +11,6 @@ final class PhabricatorDashboardTransactionEditor return pht('Dashboards'); } - public static function addPanelToDashboard( - PhabricatorUser $actor, - PhabricatorContentSource $content_source, - PhabricatorDashboardPanel $panel, - PhabricatorDashboard $dashboard, - $column) { - - $xactions = array(); - $xactions[] = id(new PhabricatorDashboardTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) - ->setMetadataValue( - 'edge:type', - PhabricatorDashboardDashboardHasPanelEdgeType::EDGECONST) - ->setNewValue( - array( - '+' => array( - $panel->getPHID() => $panel->getPHID(), - ), - )); - - $layout_config = $dashboard->getLayoutConfigObject(); - $layout_config->setPanelLocation($column, $panel->getPHID()); - $dashboard->setLayoutConfigFromObject($layout_config); - - $editor = id(new PhabricatorDashboardTransactionEditor()) - ->setActor($actor) - ->setContentSource($content_source) - ->setContinueOnMissingFields(true) - ->setContinueOnNoEffect(true) - ->applyTransactions($dashboard, $xactions); - } - public function getTransactionTypes() { $types = parent::getTransactionTypes(); @@ -50,133 +18,11 @@ final class PhabricatorDashboardTransactionEditor $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; $types[] = PhabricatorTransactions::TYPE_EDGE; - $types[] = PhabricatorDashboardTransaction::TYPE_NAME; - $types[] = PhabricatorDashboardTransaction::TYPE_ICON; - $types[] = PhabricatorDashboardTransaction::TYPE_STATUS; - $types[] = PhabricatorDashboardTransaction::TYPE_LAYOUT_MODE; - return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case PhabricatorDashboardTransaction::TYPE_NAME: - if ($this->getIsNewObject()) { - return null; - } - return $object->getName(); - case PhabricatorDashboardTransaction::TYPE_ICON: - if ($this->getIsNewObject()) { - return null; - } - return $object->getIcon(); - case PhabricatorDashboardTransaction::TYPE_STATUS: - if ($this->getIsNewObject()) { - return null; - } - return $object->getStatus(); - case PhabricatorDashboardTransaction::TYPE_LAYOUT_MODE: - if ($this->getIsNewObject()) { - return null; - } - $layout_config = $object->getLayoutConfigObject(); - return $layout_config->getLayoutMode(); - } - - return parent::getCustomTransactionOldValue($object, $xaction); + protected function supportsSearch() { + return true; } - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case PhabricatorDashboardTransaction::TYPE_NAME: - case PhabricatorDashboardTransaction::TYPE_ICON: - case PhabricatorDashboardTransaction::TYPE_STATUS: - case PhabricatorDashboardTransaction::TYPE_LAYOUT_MODE: - return $xaction->getNewValue(); - } - return parent::getCustomTransactionNewValue($object, $xaction); - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case PhabricatorDashboardTransaction::TYPE_NAME: - $object->setName($xaction->getNewValue()); - return; - case PhabricatorDashboardTransaction::TYPE_ICON: - $object->setIcon($xaction->getNewValue()); - return; - case PhabricatorDashboardTransaction::TYPE_STATUS: - $object->setStatus($xaction->getNewValue()); - return; - case PhabricatorDashboardTransaction::TYPE_LAYOUT_MODE: - $old_layout = $object->getLayoutConfigObject(); - $new_layout = clone $old_layout; - $new_layout->setLayoutMode($xaction->getNewValue()); - if ($old_layout->isMultiColumnLayout() != - $new_layout->isMultiColumnLayout()) { - $panel_phids = $object->getPanelPHIDs(); - $new_locations = $new_layout->getDefaultPanelLocations(); - foreach ($panel_phids as $panel_phid) { - $new_locations[0][] = $panel_phid; - } - $new_layout->setPanelLocations($new_locations); - } - $object->setLayoutConfigFromObject($new_layout); - return; - } - - return parent::applyCustomInternalTransaction($object, $xaction); - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorDashboardTransaction::TYPE_NAME: - case PhabricatorDashboardTransaction::TYPE_ICON: - case PhabricatorDashboardTransaction::TYPE_STATUS: - case PhabricatorDashboardTransaction::TYPE_LAYOUT_MODE: - return; - } - - return parent::applyCustomExternalTransaction($object, $xaction); - } - - protected function validateTransaction( - PhabricatorLiskDAO $object, - $type, - array $xactions) { - - $errors = parent::validateTransaction($object, $type, $xactions); - - switch ($type) { - case PhabricatorDashboardTransaction::TYPE_NAME: - $missing = $this->validateIsEmptyTextField( - $object->getName(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Dashboard name is required.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - break; - } - - return $errors; - } - - } diff --git a/src/applications/dashboard/engine/PhabricatorDashboardFerretEngine.php b/src/applications/dashboard/engine/PhabricatorDashboardFerretEngine.php new file mode 100644 index 0000000000..0150827c31 --- /dev/null +++ b/src/applications/dashboard/engine/PhabricatorDashboardFerretEngine.php @@ -0,0 +1,18 @@ +setDocumentTitle($dashboard->getName()); + + $document->addRelationship( + $dashboard->isArchived() + ? PhabricatorSearchRelationship::RELATIONSHIP_CLOSED + : PhabricatorSearchRelationship::RELATIONSHIP_OPEN, + $dashboard->getPHID(), + PhabricatorDashboardDashboardPHIDType::TYPECONST, + PhabricatorTime::getNow()); + } + +} diff --git a/src/applications/dashboard/engine/PhabricatorDashboardPanelFerretEngine.php b/src/applications/dashboard/engine/PhabricatorDashboardPanelFerretEngine.php new file mode 100644 index 0000000000..b8bd0c31ab --- /dev/null +++ b/src/applications/dashboard/engine/PhabricatorDashboardPanelFerretEngine.php @@ -0,0 +1,18 @@ +setDocumentTitle($panel->getName()); + + $document->addRelationship( + $panel->getIsArchived() + ? PhabricatorSearchRelationship::RELATIONSHIP_CLOSED + : PhabricatorSearchRelationship::RELATIONSHIP_OPEN, + $panel->getPHID(), + PhabricatorDashboardPanelPHIDType::TYPECONST, + PhabricatorTime::getNow()); + } + +} diff --git a/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php b/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php index fc62c4d5cb..1866815610 100644 --- a/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php +++ b/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php @@ -12,16 +12,28 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject { private $enableAsyncRendering; private $parentPanelPHIDs; private $headerMode = self::HEADER_MODE_NORMAL; - private $dashboardID; - private $movable = true; + private $movable; + private $panelHandle; + private $editMode; + private $contextObject; + private $panelKey; - public function setDashboardID($id) { - $this->dashboardID = $id; + public function setContextObject($object) { + $this->contextObject = $object; return $this; } - public function getDashboardID() { - return $this->dashboardID; + public function getContextObject() { + return $this->contextObject; + } + + public function setPanelKey($panel_key) { + $this->panelKey = $panel_key; + return $this; + } + + public function getPanelKey() { + return $this->panelKey; } public function setHeaderMode($header_mode) { @@ -33,6 +45,24 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject { return $this->headerMode; } + public function setPanelHandle(PhabricatorObjectHandle $panel_handle) { + $this->panelHandle = $panel_handle; + return $this; + } + + public function getPanelHandle() { + return $this->panelHandle; + } + + public function isEditMode() { + return $this->editMode; + } + + public function setEditMode($mode) { + $this->editMode = $mode; + return $this; + } + /** * Allow the engine to render the panel via Ajax. */ @@ -90,11 +120,19 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject { $panel = $this->getPanel(); if (!$panel) { - return $this->renderErrorPanel( - pht('Missing or Restricted Panel'), - pht( - 'This panel does not exist, or you do not have permission '. - 'to see it.')); + $handle = $this->getPanelHandle(); + if ($handle->getPolicyFiltered()) { + return $this->renderErrorPanel( + pht('Restricted Panel'), + pht( + 'You do not have permission to see this panel.')); + } else { + return $this->renderErrorPanel( + pht('Invalid Panel'), + pht( + 'This panel is invalid or does not exist. It may have been '. + 'deleted.')); + } } $panel_type = $panel->getImplementation(); @@ -144,10 +182,10 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject { private function renderAsyncPanel() { + $context_phid = $this->getContextPHID(); $panel = $this->getPanel(); $panel_id = celerity_generate_unique_node_id(); - $dashboard_id = $this->getDashboardID(); Javelin::initBehavior( 'dashboard-async-panel', @@ -155,7 +193,9 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject { 'panelID' => $panel_id, 'parentPanelPHIDs' => $this->getParentPanelPHIDs(), 'headerMode' => $this->getHeaderMode(), - 'dashboardID' => $dashboard_id, + 'contextPHID' => $context_phid, + 'panelKey' => $this->getPanelKey(), + 'movable' => $this->getMovable(), 'uri' => '/dashboard/panel/render/'.$panel->getID().'/', )); @@ -185,12 +225,13 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject { ->setHeader($title); break; } + $icon = id(new PHUIIconView()) ->setIcon('fa-warning red msr'); $content = id(new PHUIBoxView()) ->addClass('dashboard-box') - ->addMargin(PHUI::MARGIN_MEDIUM) + ->addMargin(PHUI::MARGIN_LARGE) ->appendChild($icon) ->appendChild($body); @@ -240,11 +281,11 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject { if ($panel) { $box->setMetadata( array( - 'objectPHID' => $panel->getPHID(), + 'panelKey' => $this->getPanelKey(), )); } - return phutil_tag_div('dashboard-pane', $box); + return $box; } @@ -256,8 +297,11 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject { $header = null; break; case self::HEADER_MODE_EDIT: + // In edit mode, include the panel monogram to make managing boards + // a little easier. + $header_text = pht('%s %s', $panel->getMonogram(), $panel->getName()); $header = id(new PHUIHeaderView()) - ->setHeader($panel->getName()); + ->setHeader($header_text); $header = $this->addPanelHeaderActions($header); break; case self::HEADER_MODE_NORMAL: @@ -277,42 +321,65 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject { private function addPanelHeaderActions( PHUIHeaderView $header) { - $panel = $this->getPanel(); - $dashboard_id = $this->getDashboardID(); + $viewer = $this->getViewer(); + $panel = $this->getPanel(); + $context_phid = $this->getContextPHID(); + + $actions = array(); if ($panel) { $panel_id = $panel->getID(); $edit_uri = "/dashboard/panel/edit/{$panel_id}/"; - $edit_uri = new PhutilURI($edit_uri); - if ($dashboard_id) { - $edit_uri->replaceQueryParam('dashboardID', $dashboard_id); - } + $params = array( + 'contextPHID' => $context_phid, + ); + $edit_uri = new PhutilURI($edit_uri, $params); - $action_edit = id(new PHUIIconView()) + $actions[] = id(new PhabricatorActionView()) ->setIcon('fa-pencil') - ->setWorkflow(true) - ->setHref((string)$edit_uri); + ->setName(pht('Edit Panel')) + ->setHref($edit_uri); - $header->addActionItem($action_edit); + $actions[] = id(new PhabricatorActionView()) + ->setIcon('fa-window-maximize') + ->setName(pht('View Panel Details')) + ->setHref($panel->getURI()); } - if ($dashboard_id) { + if ($context_phid) { $panel_phid = $this->getPanelPHID(); - $remove_uri = "/dashboard/removepanel/{$dashboard_id}/"; - $remove_uri = id(new PhutilURI($remove_uri)) - ->replaceQueryParam('panelPHID', $panel_phid); + $remove_uri = urisprintf('/dashboard/adjust/remove/'); + $params = array( + 'contextPHID' => $context_phid, + 'panelKey' => $this->getPanelKey(), + ); + $remove_uri = new PhutilURI($remove_uri, $params); - $action_remove = id(new PHUIIconView()) - ->setIcon('fa-trash-o') - ->setHref((string)$remove_uri) + $actions[] = id(new PhabricatorActionView()) + ->setIcon('fa-times') + ->setHref($remove_uri) + ->setName(pht('Remove Panel')) ->setWorkflow(true); - - $header->addActionItem($action_remove); } + $dropdown_menu = id(new PhabricatorActionListView()) + ->setViewer($viewer); + + foreach ($actions as $action) { + $dropdown_menu->addAction($action); + } + + $action_menu = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-cog') + ->setText(pht('Manage Panel')) + ->setDropdownMenu($dropdown_menu); + + $header->addActionLink($action_menu); + return $header; } @@ -353,5 +420,14 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject { } } + private function getContextPHID() { + $context = $this->getContextObject(); + + if ($context) { + return $context->getPHID(); + } + + return null; + } } diff --git a/src/applications/dashboard/engine/PhabricatorDashboardPortalFerretEngine.php b/src/applications/dashboard/engine/PhabricatorDashboardPortalFerretEngine.php new file mode 100644 index 0000000000..b6cbe857fc --- /dev/null +++ b/src/applications/dashboard/engine/PhabricatorDashboardPortalFerretEngine.php @@ -0,0 +1,18 @@ +setDocumentTitle($portal->getName()); + + $document->addRelationship( + $portal->isArchived() + ? PhabricatorSearchRelationship::RELATIONSHIP_CLOSED + : PhabricatorSearchRelationship::RELATIONSHIP_OPEN, + $portal->getPHID(), + PhabricatorDashboardPortalPHIDType::TYPECONST, + PhabricatorTime::getNow()); + } + +} diff --git a/src/applications/dashboard/engine/PhabricatorDashboardPortalProfileMenuEngine.php b/src/applications/dashboard/engine/PhabricatorDashboardPortalProfileMenuEngine.php index 5da4760cd3..9f8afc74dc 100644 --- a/src/applications/dashboard/engine/PhabricatorDashboardPortalProfileMenuEngine.php +++ b/src/applications/dashboard/engine/PhabricatorDashboardPortalProfileMenuEngine.php @@ -20,6 +20,8 @@ final class PhabricatorDashboardPortalProfileMenuEngine protected function getBuiltinProfileItems($object) { $items = array(); + $items[] = $this->newDividerItem('tail'); + $items[] = $this->newManageItem(); $items[] = $this->newItem() diff --git a/src/applications/dashboard/engine/PhabricatorDashboardRenderingEngine.php b/src/applications/dashboard/engine/PhabricatorDashboardRenderingEngine.php index 9f6481c05b..9b63fcac55 100644 --- a/src/applications/dashboard/engine/PhabricatorDashboardRenderingEngine.php +++ b/src/applications/dashboard/engine/PhabricatorDashboardRenderingEngine.php @@ -11,11 +11,19 @@ final class PhabricatorDashboardRenderingEngine extends Phobject { return $this; } + public function getViewer() { + return $this->viewer; + } + public function setDashboard(PhabricatorDashboard $dashboard) { $this->dashboard = $dashboard; return $this; } + public function getDashboard() { + return $this->dashboard; + } + public function setArrangeMode($mode) { $this->arrangeMode = $mode; return $this; @@ -23,83 +31,121 @@ final class PhabricatorDashboardRenderingEngine extends Phobject { public function renderDashboard() { require_celerity_resource('phabricator-dashboard-css'); - $dashboard = $this->dashboard; - $viewer = $this->viewer; + $dashboard = $this->getDashboard(); + $viewer = $this->getViewer(); - $layout_config = $dashboard->getLayoutConfigObject(); - $panel_grid_locations = $layout_config->getPanelLocations(); - $panels = mpull($dashboard->getPanels(), null, 'getPHID'); - $dashboard_id = celerity_generate_unique_node_id(); - $result = id(new AphrontMultiColumnView()) - ->setID($dashboard_id) - ->setFluidLayout(true) - ->setGutter(AphrontMultiColumnView::GUTTER_LARGE); + $is_editable = $this->arrangeMode; - if ($this->arrangeMode) { + if ($is_editable) { $h_mode = PhabricatorDashboardPanelRenderingEngine::HEADER_MODE_EDIT; } else { $h_mode = PhabricatorDashboardPanelRenderingEngine::HEADER_MODE_NORMAL; } - foreach ($panel_grid_locations as $column => $panel_column_locations) { - $panel_phids = $panel_column_locations; + $panel_phids = $dashboard->getPanelPHIDs(); + if ($panel_phids) { + $panels = id(new PhabricatorDashboardPanelQuery()) + ->setViewer($viewer) + ->withPHIDs($panel_phids) + ->execute(); + $panels = mpull($panels, null, 'getPHID'); - // TODO: This list may contain duplicates when the dashboard itself - // does not? Perhaps this is related to T10612. For now, just unique - // the list before moving on. - $panel_phids = array_unique($panel_phids); + $handles = $viewer->loadHandles($panel_phids); + } else { + $panels = array(); + $handles = array(); + } + + $ref_list = $dashboard->getPanelRefList(); + $columns = $ref_list->getColumns(); + + $dashboard_id = celerity_generate_unique_node_id(); + + $result = id(new AphrontMultiColumnView()) + ->setID($dashboard_id) + ->setFluidLayout(true) + ->setGutter(AphrontMultiColumnView::GUTTER_LARGE); + + foreach ($columns as $column) { + $column_views = array(); + foreach ($column->getPanelRefs() as $panel_ref) { + $panel_phid = $panel_ref->getPanelPHID(); - $column_result = array(); - foreach ($panel_phids as $panel_phid) { $panel_engine = id(new PhabricatorDashboardPanelRenderingEngine()) ->setViewer($viewer) - ->setDashboardID($dashboard->getID()) ->setEnableAsyncRendering(true) + ->setContextObject($dashboard) + ->setPanelKey($panel_ref->getPanelKey()) ->setPanelPHID($panel_phid) ->setParentPanelPHIDs(array()) - ->setHeaderMode($h_mode); + ->setHeaderMode($h_mode) + ->setEditMode($is_editable) + ->setMovable(true) + ->setPanelHandle($handles[$panel_phid]); $panel = idx($panels, $panel_phid); if ($panel) { $panel_engine->setPanel($panel); } - $column_result[] = $panel_engine->renderPanel(); + $column_views[] = $panel_engine->renderPanel(); } - $column_class = $layout_config->getColumnClass( - $column, - $this->arrangeMode); - if ($this->arrangeMode) { - $column_result[] = $this->renderAddPanelPlaceHolder($column); - $column_result[] = $this->renderAddPanelUI($column); + + $column_classes = $column->getClasses(); + + $column_tail = array(); + if ($is_editable) { + $column_tail[] = $this->renderAddPanelPlaceHolder(); + $column_tail[] = $this->renderAddPanelUI($column); } + + $sigil = 'dashboard-column'; + + $metadata = array( + 'columnKey' => $column->getColumnKey(), + ); + + $column_view = javelin_tag( + 'div', + array( + 'sigil' => $sigil, + 'meta' => $metadata, + ), + $column_views); + $result->addColumn( - $column_result, - $column_class, - $sigil = 'dashboard-column', - $metadata = array('columnID' => $column)); + array( + $column_view, + $column_tail, + ), + implode(' ', $column_classes)); } - if ($this->arrangeMode) { + if ($is_editable) { + $params = array( + 'contextPHID' => $dashboard->getPHID(), + ); + $move_uri = new PhutilURI('/dashboard/adjust/move/', $params); + Javelin::initBehavior( 'dashboard-move-panels', array( - 'dashboardID' => $dashboard_id, - 'moveURI' => '/dashboard/movepanel/'.$dashboard->getID().'/', + 'dashboardNodeID' => $dashboard_id, + 'moveURI' => (string)$move_uri, )); } $view = id(new PHUIBoxView()) ->addClass('dashboard-view') - ->appendChild($result); + ->appendChild( + array( + $result, + )); return $view; } - private function renderAddPanelPlaceHolder($column) { - $dashboard = $this->dashboard; - $panels = $dashboard->getPanels(); - + private function renderAddPanelPlaceHolder() { return javelin_tag( 'span', array( @@ -109,19 +155,23 @@ final class PhabricatorDashboardRenderingEngine extends Phobject { pht('This column does not have any panels yet.')); } - private function renderAddPanelUI($column) { - $dashboard_id = $this->dashboard->getID(); + private function renderAddPanelUI(PhabricatorDashboardColumn $column) { + $dashboard = $this->getDashboard(); + $column_key = $column->getColumnKey(); - $create_uri = id(new PhutilURI('/dashboard/panel/create/')) - ->replaceQueryParam('dashboardID', $dashboard_id) - ->replaceQueryParam('column', $column); + $create_uri = id(new PhutilURI('/dashboard/panel/edit/')) + ->replaceQueryParam('contextPHID', $dashboard->getPHID()) + ->replaceQueryParam('columnKey', $column_key); - $add_uri = id(new PhutilURI('/dashboard/addpanel/'.$dashboard_id.'/')) - ->replaceQueryParam('column', $column); + $add_uri = id(new PhutilURI('/dashboard/adjust/add/')) + ->replaceQueryParam('contextPHID', $dashboard->getPHID()) + ->replaceQueryParam('columnKey', $column_key); $create_button = id(new PHUIButtonView()) ->setTag('a') ->setHref($create_uri) + ->setIcon('fa-plus') + ->setColor(PHUIButtonView::GREY) ->setWorkflow(true) ->setText(pht('Create Panel')) ->addClass(PHUI::MARGIN_MEDIUM); @@ -129,6 +179,8 @@ final class PhabricatorDashboardRenderingEngine extends Phobject { $add_button = id(new PHUIButtonView()) ->setTag('a') ->setHref($add_uri) + ->setIcon('fa-window-maximize') + ->setColor(PHUIButtonView::GREY) ->setWorkflow(true) ->setText(pht('Add Existing Panel')) ->addClass(PHUI::MARGIN_MEDIUM); diff --git a/src/applications/dashboard/engineextension/PhabricatorDashboardPanelContainerIndexEngineExtension.php b/src/applications/dashboard/engineextension/PhabricatorDashboardPanelContainerIndexEngineExtension.php new file mode 100644 index 0000000000..8fa3cffb53 --- /dev/null +++ b/src/applications/dashboard/engineextension/PhabricatorDashboardPanelContainerIndexEngineExtension.php @@ -0,0 +1,28 @@ +getDashboardPanelContainerPanelPHIDs(); + } + +} diff --git a/src/applications/dashboard/install/PhabricatorDashboardApplicationInstallWorkflow.php b/src/applications/dashboard/install/PhabricatorDashboardApplicationInstallWorkflow.php new file mode 100644 index 0000000000..5c8ad5743f --- /dev/null +++ b/src/applications/dashboard/install/PhabricatorDashboardApplicationInstallWorkflow.php @@ -0,0 +1,58 @@ +getViewer(), + $this->newApplication(), + PhabricatorPolicyCapability::CAN_EDIT); + } + + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + $application = $this->newApplication(); + $can_global = $this->canInstallToGlobalMenu(); + + switch ($this->getMode()) { + case 'global': + if (!$can_global) { + return $this->newGlobalPermissionDialog(); + } else if ($request->isFormPost()) { + return $this->installDashboard($application, null); + } else { + return $this->newGlobalConfirmDialog(); + } + case 'personal': + if ($request->isFormPost()) { + return $this->installDashboard($application, $viewer->getPHID()); + } else { + return $this->newPersonalConfirmDialog(); + } + } + + $global_item = $this->newGlobalMenuItem() + ->setDisabled(!$can_global); + + $menu = $this->newMenuFromItemMap( + array( + 'personal' => $this->newPersonalMenuItem(), + 'global' => $global_item, + )); + + return $this->newApplicationModeDialog() + ->appendChild($menu); + } + + abstract protected function newGlobalPermissionDialog(); + abstract protected function newGlobalConfirmDialog(); + abstract protected function newPersonalConfirmDialog(); + + abstract protected function newPersonalMenuItem(); + abstract protected function newGlobalMenuItem(); + abstract protected function newApplicationModeDialog(); + +} diff --git a/src/applications/dashboard/install/PhabricatorDashboardFavoritesInstallWorkflow.php b/src/applications/dashboard/install/PhabricatorDashboardFavoritesInstallWorkflow.php new file mode 100644 index 0000000000..51cd45883b --- /dev/null +++ b/src/applications/dashboard/install/PhabricatorDashboardFavoritesInstallWorkflow.php @@ -0,0 +1,85 @@ +newMenuItem() + ->setHeader(pht('Add to Favorites Menu')) + ->setImageIcon('fa-bookmark') + ->addAttribute( + pht( + 'Add this dashboard to the favorites menu in the main '. + 'menu bar.')); + } + + protected function newProfileEngine() { + return new PhabricatorFavoritesProfileMenuEngine(); + } + + protected function newApplication() { + return new PhabricatorFavoritesApplication(); + } + + protected function newApplicationModeDialog() { + return $this->newDialog() + ->setTitle(pht('Add Dashboard to Favorites Menu')); + } + + protected function newPersonalMenuItem() { + return $this->newMenuItem() + ->setHeader(pht('Add to Personal Favorites')) + ->setImageIcon('fa-user') + ->addAttribute( + pht( + 'Add this dashboard to your list of personal favorite menu items, '. + 'visible to only you.')); + } + + protected function newGlobalMenuItem() { + return $this->newMenuItem() + ->setHeader(pht('Add to Global Favorites')) + ->setImageIcon('fa-globe') + ->addAttribute( + pht( + 'Add this dashboard to the global favorites menu, visible to all '. + 'users.')); + } + + protected function newGlobalPermissionDialog() { + return $this->newDialog() + ->setTitle(pht('No Permission')) + ->appendParagraph( + pht( + 'You do not have permission to install items on the global '. + 'favorites menu.')); + } + + protected function newGlobalConfirmDialog() { + return $this->newDialog() + ->setTitle(pht('Add Dashboard to Global Favorites')) + ->appendParagraph( + pht( + 'Add dashboard %s as a global menu item in the favorites menu?', + $this->getDashboardDisplayName())) + ->addSubmitButton(pht('Add to Favorites')); + } + + protected function newPersonalConfirmDialog() { + return $this->newDialog() + ->setTitle(pht('Add Dashboard to Personal Favorites')) + ->appendParagraph( + pht( + 'Add dashboard %s as a personal menu item in the favorites menu?', + $this->getDashboardDisplayName())) + ->addSubmitButton(pht('Add to Favorites')); + } + + +} diff --git a/src/applications/dashboard/install/PhabricatorDashboardHomeInstallWorkflow.php b/src/applications/dashboard/install/PhabricatorDashboardHomeInstallWorkflow.php new file mode 100644 index 0000000000..28731a601e --- /dev/null +++ b/src/applications/dashboard/install/PhabricatorDashboardHomeInstallWorkflow.php @@ -0,0 +1,83 @@ +newMenuItem() + ->setHeader(pht('Add to Home Page Menu')) + ->setImageIcon('fa-home') + ->addAttribute( + pht( + 'Add this dashboard to the menu on the home page.')); + } + + protected function newProfileEngine() { + return new PhabricatorHomeProfileMenuEngine(); + } + + protected function newApplication() { + return new PhabricatorHomeApplication(); + } + + protected function newApplicationModeDialog() { + return $this->newDialog() + ->setTitle(pht('Add Dashboard to Home Menu')); + } + + protected function newPersonalMenuItem() { + return $this->newMenuItem() + ->setHeader(pht('Add to Personal Home Menu')) + ->setImageIcon('fa-user') + ->addAttribute( + pht( + 'Add this dashboard to your list of personal home menu items, '. + 'visible to only you.')); + } + + protected function newGlobalMenuItem() { + return $this->newMenuItem() + ->setHeader(pht('Add to Global Home Menu')) + ->setImageIcon('fa-globe') + ->addAttribute( + pht( + 'Add this dashboard to the global home menu, visible to all '. + 'users.')); + } + + protected function newGlobalPermissionDialog() { + return $this->newDialog() + ->setTitle(pht('No Permission')) + ->appendParagraph( + pht( + 'You do not have permission to install items on the global home '. + 'menu.')); + } + + protected function newGlobalConfirmDialog() { + return $this->newDialog() + ->setTitle(pht('Add Dashboard to Global Home Page')) + ->appendParagraph( + pht( + 'Add dashboard %s as a global menu item on the home page?', + $this->getDashboardDisplayName())) + ->addSubmitButton(pht('Add to Home')); + } + + protected function newPersonalConfirmDialog() { + return $this->newDialog() + ->setTitle(pht('Add Dashboard to Personal Home Page')) + ->appendParagraph( + pht( + 'Add dashboard %s as a personal menu item on your home page?', + $this->getDashboardDisplayName())) + ->addSubmitButton(pht('Add to Home')); + } + +} diff --git a/src/applications/dashboard/install/PhabricatorDashboardInstallWorkflow.php b/src/applications/dashboard/install/PhabricatorDashboardInstallWorkflow.php new file mode 100644 index 0000000000..fb77cdebe3 --- /dev/null +++ b/src/applications/dashboard/install/PhabricatorDashboardInstallWorkflow.php @@ -0,0 +1,143 @@ +viewer = $viewer; + return $this; + } + + final public function getViewer() { + return $this->viewer; + } + + final public function setDashboard(PhabricatorDashboard $dashboard) { + $this->dashboard = $dashboard; + return $this; + } + + final public function getDashboard() { + return $this->dashboard; + } + + final public function setMode($mode) { + $this->mode = $mode; + return $this; + } + + final public function getMode() { + return $this->mode; + } + + final public function setRequest(AphrontRequest $request) { + $this->request = $request; + return $this; + } + + final public function getRequest() { + return $this->request; + } + + final public function getWorkflowKey() { + return $this->getPhobjectClassConstant('WORKFLOWKEY', 32); + } + + final public static function getAllWorkflows() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getWorkflowKey') + ->setSortMethod('getOrder') + ->execute(); + } + + final public function getWorkflowMenuItem() { + return $this->newWorkflowMenuItem(); + } + + abstract public function getOrder(); + abstract protected function newWorkflowMenuItem(); + + final protected function newMenuItem() { + return id(new PHUIObjectItemView()) + ->setClickable(true); + } + + abstract public function handleRequest(AphrontRequest $request); + + final protected function newDialog() { + $dashboard = $this->getDashboard(); + + return id(new AphrontDialogView()) + ->setViewer($this->getViewer()) + ->setWidth(AphrontDialogView::WIDTH_FORM) + ->addCancelButton($dashboard->getURI()); + } + + final protected function newMenuFromItemMap(array $map) { + $viewer = $this->getViewer(); + $dashboard = $this->getDashboard(); + + $menu = id(new PHUIObjectItemListView()) + ->setViewer($viewer) + ->setFlush(true) + ->setBig(true); + + foreach ($map as $key => $item) { + $item->setHref( + urisprintf( + '/dashboard/install/%d/%s/%s/', + $dashboard->getID(), + $this->getWorkflowKey(), + $key)); + + $menu->addItem($item); + } + + return $menu; + } + + abstract protected function newProfileEngine(); + + final protected function installDashboard($profile_object, $custom_phid) { + $dashboard = $this->getDashboard(); + $engine = $this->newProfileEngine() + ->setProfileObject($profile_object); + + $request = $this->getRequest(); + $viewer = $this->getViewer(); + + $config = PhabricatorProfileMenuItemConfiguration::initializeNewItem( + $profile_object, + new PhabricatorDashboardProfileMenuItem(), + $custom_phid); + + $config->setMenuItemProperty('dashboardPHID', $dashboard->getPHID()); + + $xactions = array(); + + $editor = id(new PhabricatorProfileMenuEditor()) + ->setActor($viewer) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true) + ->setContentSourceFromRequest($request); + + $editor->applyTransactions($config, $xactions); + + $done_uri = $engine->getItemURI(urisprintf('view/%d/', $config->getID())); + + return id(new AphrontRedirectResponse()) + ->setURI($done_uri); + } + + final protected function getDashboardDisplayName() { + $dashboard = $this->getDashboard(); + return phutil_tag('strong', array(), $dashboard->getName()); + } + +} diff --git a/src/applications/dashboard/install/PhabricatorDashboardObjectInstallWorkflow.php b/src/applications/dashboard/install/PhabricatorDashboardObjectInstallWorkflow.php new file mode 100644 index 0000000000..eb1be21954 --- /dev/null +++ b/src/applications/dashboard/install/PhabricatorDashboardObjectInstallWorkflow.php @@ -0,0 +1,99 @@ +getViewer(); + + $target_identifier = null; + + $target_tokens = $request->getArr('target'); + if ($target_tokens) { + $target_identifier = head($target_tokens); + } + + if (!strlen($target_identifier)) { + $target_identifier = $request->getStr('target'); + } + + if (!strlen($target_identifier)) { + $target_identifier = $this->getMode(); + } + + $target = null; + if (strlen($target_identifier)) { + $targets = array(); + + if (ctype_digit($target_identifier)) { + $targets = $this->newQuery() + ->setViewer($viewer) + ->withIDs(array((int)$target_identifier)) + ->execute(); + } + + if (!$targets) { + $targets = $this->newQuery() + ->setViewer($viewer) + ->withPHIDs(array($target_identifier)) + ->execute(); + } + + if ($targets) { + $target = head($targets); + } + } + + if ($target) { + $target_phid = $target->getPHID(); + } else { + $target_phid = null; + } + + if ($target) { + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $target, + PhabricatorPolicyCapability::CAN_EDIT); + } else { + $can_edit = null; + } + + if ($request->isFormPost() && $target && $can_edit) { + if ($request->getBool('confirm')) { + return $this->installDashboard($target, null); + } else { + return $this->newConfirmDialog($target) + ->addHiddenInput('confirm', 1) + ->addHiddenInput('target', $target_phid); + } + } + + $errors = array(); + if (strlen($target_identifier)) { + if (!$target) { + $errors[] = pht('Choose a valid object.'); + } else if (!$can_edit) { + $errors[] = pht( + 'You do not have permission to edit the selected object. '. + 'You can only install dashboards on objects you can edit.'); + } + } else if ($request->getBool('pick')) { + $errors[] = pht( + 'Choose an object to install this dashboard on.'); + } + + $form = $this->newObjectSelectionForm($target) + ->addHiddenInput('pick', 1); + + return $this->newDialog() + ->setTitle(pht('Add Dashboard to Project Menu')) + ->setErrors($errors) + ->appendForm($form) + ->addSubmitButton(pht('Continue')); + } +} diff --git a/src/applications/dashboard/install/PhabricatorDashboardPortalInstallWorkflow.php b/src/applications/dashboard/install/PhabricatorDashboardPortalInstallWorkflow.php new file mode 100644 index 0000000000..b5f06684ec --- /dev/null +++ b/src/applications/dashboard/install/PhabricatorDashboardPortalInstallWorkflow.php @@ -0,0 +1,62 @@ +newMenuItem() + ->setHeader(pht('Add to Portal Menu')) + ->setImageIcon('fa-compass') + ->addAttribute( + pht('Add this dashboard to the menu on a portal.')); + } + + protected function newProfileEngine() { + return new PhabricatorDashboardPortalProfileMenuEngine(); + } + + protected function newQuery() { + return new PhabricatorDashboardPortalQuery(); + } + + protected function newConfirmDialog($object) { + return $this->newDialog() + ->setTitle(pht('Add Dashboard to Portal Menu')) + ->appendParagraph( + pht( + 'Add the dashboard %s to portal %s?', + $this->getDashboardDisplayName(), + phutil_tag('strong', array(), $object->getName()))) + ->addSubmitButton(pht('Add to Portal')); + } + protected function newObjectSelectionForm($object) { + $viewer = $this->getViewer(); + + if ($object) { + $tokenizer_value = array($object->getPHID()); + } else { + $tokenizer_value = array(); + } + + return id(new AphrontFormView()) + ->setViewer($viewer) + ->appendInstructions( + pht( + 'Select which portal you want to add the dashboard %s to.', + $this->getDashboardDisplayName())) + ->appendControl( + id(new AphrontFormTokenizerControl()) + ->setName('target') + ->setLimit(1) + ->setLabel(pht('Add to Portal')) + ->setValue($tokenizer_value) + ->setDatasource(new PhabricatorDashboardPortalDatasource())); + } + +} diff --git a/src/applications/dashboard/install/PhabricatorDashboardProjectInstallWorkflow.php b/src/applications/dashboard/install/PhabricatorDashboardProjectInstallWorkflow.php new file mode 100644 index 0000000000..aab70af2cb --- /dev/null +++ b/src/applications/dashboard/install/PhabricatorDashboardProjectInstallWorkflow.php @@ -0,0 +1,63 @@ +newMenuItem() + ->setHeader(pht('Add to Project Menu')) + ->setImageIcon('fa-briefcase') + ->addAttribute( + pht('Add this dashboard to the menu for a project.')); + } + + protected function newProfileEngine() { + return new PhabricatorProjectProfileMenuEngine(); + } + + protected function newQuery() { + return new PhabricatorProjectQuery(); + } + + protected function newConfirmDialog($object) { + return $this->newDialog() + ->setTitle(pht('Add Dashboard to Project Menu')) + ->appendParagraph( + pht( + 'Add the dashboard %s to the menu for project %s?', + $this->getDashboardDisplayName(), + phutil_tag('strong', array(), $object->getName()))) + ->addSubmitButton(pht('Add to Project')); + } + + protected function newObjectSelectionForm($object) { + $viewer = $this->getViewer(); + + if ($object) { + $tokenizer_value = array($object->getPHID()); + } else { + $tokenizer_value = array(); + } + + return id(new AphrontFormView()) + ->setViewer($viewer) + ->appendInstructions( + pht( + 'Select which project menu you want to add the dashboard %s to.', + $this->getDashboardDisplayName())) + ->appendControl( + id(new AphrontFormTokenizerControl()) + ->setName('target') + ->setLimit(1) + ->setLabel(pht('Add to Project')) + ->setValue($tokenizer_value) + ->setDatasource(new PhabricatorProjectDatasource())); + } + +} diff --git a/src/applications/dashboard/interface/PhabricatorDashboardPanelContainerInterface.php b/src/applications/dashboard/interface/PhabricatorDashboardPanelContainerInterface.php new file mode 100644 index 0000000000..02ad2c2740 --- /dev/null +++ b/src/applications/dashboard/interface/PhabricatorDashboardPanelContainerInterface.php @@ -0,0 +1,12 @@ + + */ + public function getDashboardPanelContainerPanelPHIDs(); + +} diff --git a/src/applications/dashboard/layoutconfig/PhabricatorDashboardColumn.php b/src/applications/dashboard/layoutconfig/PhabricatorDashboardColumn.php new file mode 100644 index 0000000000..3bf17d1a7c --- /dev/null +++ b/src/applications/dashboard/layoutconfig/PhabricatorDashboardColumn.php @@ -0,0 +1,43 @@ +columnKey = $column_key; + return $this; + } + + public function getColumnKey() { + return $this->columnKey; + } + + public function addClass($class) { + $this->classes[] = $class; + return $this; + } + + public function getClasses() { + return $this->classes; + } + + public function setPanelRefs(array $refs) { + assert_instances_of($refs, 'PhabricatorDashboardPanelRef'); + $this->refs = $refs; + return $this; + } + + public function addPanelRef(PhabricatorDashboardPanelRef $ref) { + $this->refs[] = $ref; + return $this; + } + + public function getPanelRefs() { + return $this->refs; + } + +} diff --git a/src/applications/dashboard/layoutconfig/PhabricatorDashboardFullLayoutMode.php b/src/applications/dashboard/layoutconfig/PhabricatorDashboardFullLayoutMode.php new file mode 100644 index 0000000000..a2a05ea633 --- /dev/null +++ b/src/applications/dashboard/layoutconfig/PhabricatorDashboardFullLayoutMode.php @@ -0,0 +1,23 @@ +newColumn() + ->setColumnKey('main'), + ); + } + +} diff --git a/src/applications/dashboard/layoutconfig/PhabricatorDashboardHalfLayoutMode.php b/src/applications/dashboard/layoutconfig/PhabricatorDashboardHalfLayoutMode.php new file mode 100644 index 0000000000..b586d5b8b0 --- /dev/null +++ b/src/applications/dashboard/layoutconfig/PhabricatorDashboardHalfLayoutMode.php @@ -0,0 +1,27 @@ +newColumn() + ->setColumnKey('left') + ->addClass('half'), + $this->newColumn() + ->setColumnKey('right') + ->addClass('half'), + ); + } + +} diff --git a/src/applications/dashboard/layoutconfig/PhabricatorDashboardLayoutConfig.php b/src/applications/dashboard/layoutconfig/PhabricatorDashboardLayoutConfig.php deleted file mode 100644 index 0913be81d5..0000000000 --- a/src/applications/dashboard/layoutconfig/PhabricatorDashboardLayoutConfig.php +++ /dev/null @@ -1,164 +0,0 @@ -layoutMode = $mode; - return $this; - } - public function getLayoutMode() { - return $this->layoutMode; - } - - public function setPanelLocation($which_column, $panel_phid) { - $this->panelLocations[$which_column][] = $panel_phid; - return $this; - } - - public function setPanelLocations(array $locations) { - $this->panelLocations = $locations; - return $this; - } - - public function getPanelLocations() { - return $this->panelLocations; - } - - public function replacePanel($old_phid, $new_phid) { - $locations = $this->getPanelLocations(); - foreach ($locations as $column => $panel_phids) { - foreach ($panel_phids as $key => $panel_phid) { - if ($panel_phid == $old_phid) { - $locations[$column][$key] = $new_phid; - } - } - } - return $this->setPanelLocations($locations); - } - - public function removePanel($panel_phid) { - $panel_location_grid = $this->getPanelLocations(); - foreach ($panel_location_grid as $column => $panel_columns) { - $found_old_column = array_search($panel_phid, $panel_columns); - if ($found_old_column !== false) { - $new_panel_columns = $panel_columns; - array_splice( - $new_panel_columns, - $found_old_column, - 1, - array()); - $panel_location_grid[$column] = $new_panel_columns; - break; - } - } - $this->setPanelLocations($panel_location_grid); - } - - public function getDefaultPanelLocations() { - switch ($this->getLayoutMode()) { - case self::MODE_HALF_AND_HALF: - case self::MODE_THIRD_AND_THIRDS: - case self::MODE_THIRDS_AND_THIRD: - $locations = array(array(), array()); - break; - case self::MODE_FULL: - default: - $locations = array(array()); - break; - } - return $locations; - } - - public function getColumnClass($column_index, $grippable = false) { - switch ($this->getLayoutMode()) { - case self::MODE_HALF_AND_HALF: - $class = 'half'; - break; - case self::MODE_THIRD_AND_THIRDS: - if ($column_index) { - $class = 'thirds'; - } else { - $class = 'third'; - } - break; - case self::MODE_THIRDS_AND_THIRD: - if ($column_index) { - $class = 'third'; - } else { - $class = 'thirds'; - } - break; - case self::MODE_FULL: - default: - $class = null; - break; - } - if ($grippable) { - $class .= ' grippable'; - } - return $class; - } - - public function isMultiColumnLayout() { - return $this->getLayoutMode() != self::MODE_FULL; - } - - public function getColumnSelectOptions() { - $options = array(); - - switch ($this->getLayoutMode()) { - case self::MODE_HALF_AND_HALF: - case self::MODE_THIRD_AND_THIRDS: - case self::MODE_THIRDS_AND_THIRD: - return array( - 0 => pht('Left'), - 1 => pht('Right'), - ); - break; - case self::MODE_FULL: - throw new Exception(pht('There is only one column in mode full.')); - break; - default: - throw new Exception(pht('Unknown layout mode!')); - break; - } - - return $options; - } - - public static function getLayoutModeSelectOptions() { - return array( - self::MODE_FULL => pht('One full-width column'), - self::MODE_HALF_AND_HALF => pht('Two columns, 1/2 and 1/2'), - self::MODE_THIRD_AND_THIRDS => pht('Two columns, 1/3 and 2/3'), - self::MODE_THIRDS_AND_THIRD => pht('Two columns, 2/3 and 1/3'), - ); - } - - public static function newFromDictionary(array $dict) { - $layout_config = id(new PhabricatorDashboardLayoutConfig()) - ->setLayoutMode(idx($dict, 'layoutMode', self::MODE_FULL)); - $layout_config->setPanelLocations(idx( - $dict, - 'panelLocations', - $layout_config->getDefaultPanelLocations())); - - return $layout_config; - } - - public function toDictionary() { - return array( - 'layoutMode' => $this->getLayoutMode(), - 'panelLocations' => $this->getPanelLocations(), - ); - } - -} diff --git a/src/applications/dashboard/layoutconfig/PhabricatorDashboardLayoutMode.php b/src/applications/dashboard/layoutconfig/PhabricatorDashboardLayoutMode.php new file mode 100644 index 0000000000..965adbe1d7 --- /dev/null +++ b/src/applications/dashboard/layoutconfig/PhabricatorDashboardLayoutMode.php @@ -0,0 +1,34 @@ +getPhobjectClassConstant('LAYOUTMODE', 32); + } + + public function getLayoutModeOrder() { + return 1000; + } + + abstract public function getLayoutModeName(); + abstract public function getLayoutModeColumns(); + + final protected function newColumn() { + return new PhabricatorDashboardColumn(); + } + + final public static function getAllLayoutModes() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getLayoutModeKey') + ->setSortMethod('getLayoutModeOrder') + ->execute(); + } + + final public static function getLayoutModeMap() { + $modes = self::getAllLayoutModes(); + return mpull($modes, 'getLayoutModeName', 'getLayoutModeKey'); + } + +} diff --git a/src/applications/dashboard/layoutconfig/PhabricatorDashboardOneThirdLayoutMode.php b/src/applications/dashboard/layoutconfig/PhabricatorDashboardOneThirdLayoutMode.php new file mode 100644 index 0000000000..7e02bcd6aa --- /dev/null +++ b/src/applications/dashboard/layoutconfig/PhabricatorDashboardOneThirdLayoutMode.php @@ -0,0 +1,27 @@ +newColumn() + ->setColumnKey('left') + ->addClass('third'), + $this->newColumn() + ->setColumnKey('right') + ->addClass('thirds'), + ); + } + +} diff --git a/src/applications/dashboard/layoutconfig/PhabricatorDashboardPanelRef.php b/src/applications/dashboard/layoutconfig/PhabricatorDashboardPanelRef.php new file mode 100644 index 0000000000..96a912e30a --- /dev/null +++ b/src/applications/dashboard/layoutconfig/PhabricatorDashboardPanelRef.php @@ -0,0 +1,45 @@ +panelPHID = $panel_phid; + return $this; + } + + public function getPanelPHID() { + return $this->panelPHID; + } + + public function setColumnKey($column_key) { + $this->columnKey = $column_key; + return $this; + } + + public function getColumnKey() { + return $this->columnKey; + } + + public function setPanelKey($panel_key) { + $this->panelKey = $panel_key; + return $this; + } + + public function getPanelKey() { + return $this->panelKey; + } + + public function toDictionary() { + return array( + 'panelKey' => $this->getPanelKey(), + 'panelPHID' => $this->getPanelPHID(), + 'columnKey' => $this->getColumnKey(), + ); + } + +} diff --git a/src/applications/dashboard/layoutconfig/PhabricatorDashboardPanelRefList.php b/src/applications/dashboard/layoutconfig/PhabricatorDashboardPanelRefList.php new file mode 100644 index 0000000000..2f74d0a937 --- /dev/null +++ b/src/applications/dashboard/layoutconfig/PhabricatorDashboardPanelRefList.php @@ -0,0 +1,163 @@ +getLayoutModeColumns(); + $columns = mpull($columns, null, 'getColumnKey'); + $default_column = head($columns); + + $panels = idx($config, 'panels'); + if (!is_array($panels)) { + $panels = array(); + } + + $seen_panels = array(); + $refs = array(); + foreach ($panels as $panel) { + $panel_phid = idx($panel, 'panelPHID'); + if (!strlen($panel_phid)) { + continue; + } + + $panel_key = idx($panel, 'panelKey'); + if (!strlen($panel_key)) { + continue; + } + + if (isset($seen_panels[$panel_key])) { + continue; + } + $seen_panels[$panel_key] = true; + + $column_key = idx($panel, 'columnKey'); + $column = idx($columns, $column_key, $default_column); + + $ref = id(new PhabricatorDashboardPanelRef()) + ->setPanelPHID($panel_phid) + ->setPanelKey($panel_key) + ->setColumnKey($column->getColumnKey()); + + $column->addPanelRef($ref); + $refs[] = $ref; + } + + $list = new self(); + + $list->columns = $columns; + $list->refs = $refs; + + return $list; + } + + public function getColumns() { + return $this->columns; + } + + public function getPanelRefs() { + return $this->refs; + } + + public function getPanelRef($panel_key) { + foreach ($this->getPanelRefs() as $ref) { + if ($ref->getPanelKey() === $panel_key) { + return $ref; + } + } + + return null; + } + + public function toDictionary() { + return array_values(mpull($this->getPanelRefs(), 'toDictionary')); + } + + public function newPanelRef( + PhabricatorDashboardPanel $panel, + $column_key = null) { + + if ($column_key === null) { + $column_key = head_key($this->columns); + } + + $ref = id(new PhabricatorDashboardPanelRef()) + ->setPanelKey($this->newPanelKey()) + ->setPanelPHID($panel->getPHID()) + ->setColumnKey($column_key); + + $this->refs[] = $ref; + + return $ref; + } + + public function removePanelRef(PhabricatorDashboardPanelRef $target) { + foreach ($this->refs as $key => $ref) { + if ($ref->getPanelKey() !== $target->getPanelKey()) { + continue; + } + + unset($this->refs[$key]); + return $ref; + } + + return null; + } + + public function movePanelRef( + PhabricatorDashboardPanelRef $target, + $column_key, + PhabricatorDashboardPanelRef $after = null) { + + $target->setColumnKey($column_key); + + $results = array(); + + if (!$after) { + $results[] = $target; + } + + foreach ($this->refs as $ref) { + if ($ref->getPanelKey() === $target->getPanelKey()) { + continue; + } + + $results[] = $ref; + + if ($after) { + if ($ref->getPanelKey() === $after->getPanelKey()) { + $results[] = $target; + } + } + } + + $this->refs = $results; + + $column_map = mgroup($results, 'getColumnKey'); + foreach ($this->columns as $column_key => $column) { + $column->setPanelRefs(idx($column_map, $column_key, array())); + } + + return $ref; + } + + private function newPanelKey() { + return Filesystem::readRandomCharacters(8); + } + + +} diff --git a/src/applications/dashboard/layoutconfig/PhabricatorDashboardTwoThirdsLayoutMode.php b/src/applications/dashboard/layoutconfig/PhabricatorDashboardTwoThirdsLayoutMode.php new file mode 100644 index 0000000000..91b07276d4 --- /dev/null +++ b/src/applications/dashboard/layoutconfig/PhabricatorDashboardTwoThirdsLayoutMode.php @@ -0,0 +1,27 @@ +newColumn() + ->setColumnKey('left') + ->addClass('thirds'), + $this->newColumn() + ->setColumnKey('right') + ->addClass('third'), + ); + } + +} diff --git a/src/applications/dashboard/paneltype/PhabricatorDashboardPanelType.php b/src/applications/dashboard/paneltype/PhabricatorDashboardPanelType.php index 34c7b1c3ee..8f9134e642 100644 --- a/src/applications/dashboard/paneltype/PhabricatorDashboardPanelType.php +++ b/src/applications/dashboard/paneltype/PhabricatorDashboardPanelType.php @@ -5,7 +5,6 @@ abstract class PhabricatorDashboardPanelType extends Phobject { abstract public function getPanelTypeKey(); abstract public function getPanelTypeName(); abstract public function getPanelTypeDescription(); - abstract public function getFieldSpecifications(); abstract public function getIcon(); abstract public function renderPanelContent( @@ -53,4 +52,15 @@ abstract class PhabricatorDashboardPanelType extends Phobject { ->execute(); } + final public function getEditEngineFields(PhabricatorDashboardPanel $panel) { + return $this->newEditEngineFields($panel); + } + + abstract protected function newEditEngineFields( + PhabricatorDashboardPanel $panel); + + public function getSubpanelPHIDs(PhabricatorDashboardPanel $panel) { + return array(); + } + } diff --git a/src/applications/dashboard/paneltype/PhabricatorDashboardQueryPanelType.php b/src/applications/dashboard/paneltype/PhabricatorDashboardQueryPanelType.php index a71263b27e..2964d1ccac 100644 --- a/src/applications/dashboard/paneltype/PhabricatorDashboardQueryPanelType.php +++ b/src/applications/dashboard/paneltype/PhabricatorDashboardQueryPanelType.php @@ -21,22 +21,29 @@ final class PhabricatorDashboardQueryPanelType 'revisions you need to review.'); } - public function getFieldSpecifications() { + protected function newEditEngineFields(PhabricatorDashboardPanel $panel) { + $application_field = + id(new PhabricatorDashboardQueryPanelApplicationEditField()) + ->setKey('class') + ->setLabel(pht('Search For')) + ->setTransactionType( + PhabricatorDashboardQueryPanelApplicationTransaction::TRANSACTIONTYPE) + ->setValue($panel->getProperty('class', '')); + + $application_id = $application_field->getControlID(); + + $query_field = + id(new PhabricatorDashboardQueryPanelQueryEditField()) + ->setKey('key') + ->setLabel(pht('Query')) + ->setApplicationControlID($application_id) + ->setTransactionType( + PhabricatorDashboardQueryPanelQueryTransaction::TRANSACTIONTYPE) + ->setValue($panel->getProperty('key', '')); + return array( - 'class' => array( - 'name' => pht('Search For'), - 'type' => 'search.application', - ), - 'key' => array( - 'name' => pht('Query'), - 'type' => 'search.query', - 'control.application' => 'class', - ), - 'limit' => array( - 'name' => pht('Limit'), - 'caption' => pht('Leave this blank for the default number of items.'), - 'type' => 'text', - ), + $application_field, + $query_field, ); } @@ -136,7 +143,16 @@ final class PhabricatorDashboardQueryPanelType $results_view->setContent($content); } - if ($pager->getHasMoreResults()) { + // TODO: A small number of queries, including "Notifications" and "Search", + // use an offset pager which has a slightly different API. Some day, we + // should unify these. + if ($pager instanceof PHUIPagerView) { + $has_more = $pager->getHasMorePages(); + } else { + $has_more = $pager->getHasMoreResults(); + } + + if ($has_more) { $item_list = $results_view->getObjectList(); $more_href = $engine->getQueryResultsPageURI($key); diff --git a/src/applications/dashboard/paneltype/PhabricatorDashboardTabsPanelType.php b/src/applications/dashboard/paneltype/PhabricatorDashboardTabsPanelType.php index b4772ba83f..682cc11d78 100644 --- a/src/applications/dashboard/paneltype/PhabricatorDashboardTabsPanelType.php +++ b/src/applications/dashboard/paneltype/PhabricatorDashboardTabsPanelType.php @@ -12,20 +12,15 @@ final class PhabricatorDashboardTabsPanelType } public function getIcon() { - return 'fa-window-maximize'; + return 'fa-columns'; } public function getPanelTypeDescription() { return pht('Use tabs to switch between several other panels.'); } - public function getFieldSpecifications() { - return array( - 'config' => array( - 'name' => pht('Tabs'), - 'type' => 'dashboard.tabs', - ), - ); + protected function newEditEngineFields(PhabricatorDashboardPanel $panel) { + return array(); } public function shouldRenderAsync() { @@ -33,37 +28,39 @@ final class PhabricatorDashboardTabsPanelType return false; } + public function getPanelConfiguration(PhabricatorDashboardPanel $panel) { + $config = $panel->getProperty('config'); + + if (!is_array($config)) { + // NOTE: The older version of this panel stored raw JSON. + try { + $config = phutil_json_decode($config); + } catch (PhutilJSONParserException $ex) { + $config = array(); + } + } + + return $config; + } + public function renderPanelContent( PhabricatorUser $viewer, PhabricatorDashboardPanel $panel, PhabricatorDashboardPanelRenderingEngine $engine) { - $config = $panel->getProperty('config'); - if (!is_array($config)) { - // NOTE: The older version of this panel stored raw JSON. - $config = phutil_json_decode($config); + $is_edit = $engine->isEditMode(); + $config = $this->getPanelConfiguration($panel); + + $context_object = $engine->getContextObject(); + if (!$context_object) { + $context_object = $panel; } + $context_phid = $context_object->getPHID(); + $list = id(new PHUIListView()) ->setType(PHUIListView::NAVBAR_LIST); - $selected = 0; - - $node_ids = array(); - foreach ($config as $idx => $tab_spec) { - $node_ids[$idx] = celerity_generate_unique_node_id(); - } - - foreach ($config as $idx => $tab_spec) { - $list->addMenuItem( - id(new PHUIListItemView()) - ->setHref('#') - ->setSelected($idx == $selected) - ->addSigil('dashboard-tab-panel-tab') - ->setMetadata(array('idx' => $idx)) - ->setName(idx($tab_spec, 'name', pht('Nameless Tab')))); - } - $ids = ipull($config, 'panelID'); if ($ids) { $panels = id(new PhabricatorDashboardPanelQuery()) @@ -74,6 +71,140 @@ final class PhabricatorDashboardTabsPanelType $panels = array(); } + $id = $panel->getID(); + + $add_uri = urisprintf('/dashboard/panel/tabs/%d/add/', $id); + $add_uri = id(new PhutilURI($add_uri)) + ->replaceQueryParam('contextPHID', $context_phid); + + $remove_uri = urisprintf('/dashboard/panel/tabs/%d/remove/', $id); + $remove_uri = id(new PhutilURI($remove_uri)) + ->replaceQueryParam('contextPHID', $context_phid); + + $rename_uri = urisprintf('/dashboard/panel/tabs/%d/rename/', $id); + $rename_uri = id(new PhutilURI($rename_uri)) + ->replaceQueryParam('contextPHID', $context_phid); + + $selected = 0; + + $last_idx = null; + foreach ($config as $idx => $tab_spec) { + $panel_id = idx($tab_spec, 'panelID'); + $subpanel = idx($panels, $panel_id); + + $name = idx($tab_spec, 'name'); + if (!strlen($name)) { + if ($subpanel) { + $name = $subpanel->getName(); + } + } + + if (!strlen($name)) { + $name = pht('Unnamed Tab'); + } + + $tab_view = id(new PHUIListItemView()) + ->setHref('#') + ->setSelected((string)$idx === (string)$selected) + ->addSigil('dashboard-tab-panel-tab') + ->setMetadata(array('panelKey' => $idx)) + ->setName($name); + + if ($is_edit) { + $dropdown_menu = id(new PhabricatorActionListView()) + ->setViewer($viewer); + + $remove_tab_uri = id(clone $remove_uri) + ->replaceQueryParam('target', $idx); + + $rename_tab_uri = id(clone $rename_uri) + ->replaceQueryParam('target', $idx); + + if ($subpanel) { + $details_uri = $subpanel->getURI(); + } else { + $details_uri = null; + } + + $edit_uri = urisprintf( + '/dashboard/panel/edit/%d/', + $panel_id); + if ($subpanel) { + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $subpanel, + PhabricatorPolicyCapability::CAN_EDIT); + } else { + $can_edit = false; + } + + $dropdown_menu->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Rename Tab')) + ->setIcon('fa-pencil') + ->setHref($rename_tab_uri) + ->setWorkflow(true)); + + $dropdown_menu->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Remove Tab')) + ->setIcon('fa-times') + ->setHref($remove_tab_uri) + ->setWorkflow(true)); + + $dropdown_menu->addAction( + id(new PhabricatorActionView()) + ->setType(PhabricatorActionView::TYPE_DIVIDER)); + + $dropdown_menu->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Panel')) + ->setIcon('fa-pencil') + ->setHref($edit_uri) + ->setWorkflow(true) + ->setDisabled(!$can_edit)); + + $dropdown_menu->addAction( + id(new PhabricatorActionView()) + ->setName(pht('View Panel Details')) + ->setIcon('fa-window-maximize') + ->setHref($details_uri) + ->setDisabled(!$subpanel)); + + $tab_view + ->setActionIcon('fa-caret-down', '#') + ->setDropdownMenu($dropdown_menu); + } + + $list->addMenuItem($tab_view); + + $last_idx = $idx; + } + + if ($is_edit) { + $actions = id(new PhabricatorActionListView()) + ->setViewer($viewer); + + $add_last_uri = clone $add_uri; + if ($last_idx) { + $add_last_uri->replaceQueryParam('after', $last_idx); + } + + $actions->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Add Existing Panel')) + ->setIcon('fa-window-maximize') + ->setHref($add_last_uri) + ->setWorkflow(true)); + + $list->addMenuItem( + id(new PHUIListItemView()) + ->setHref('#') + ->setSelected(false) + ->setName(pht('Add Tab...')) + ->setDropdownMenu($actions)); + } + $parent_phids = $engine->getParentPanelPHIDs(); $parent_phids[] = $panel->getPHID(); @@ -84,32 +215,62 @@ final class PhabricatorDashboardTabsPanelType // remains selected across page loads. $content = array(); + $panel_list = array(); $no_headers = PhabricatorDashboardPanelRenderingEngine::HEADER_MODE_NONE; foreach ($config as $idx => $tab_spec) { $panel_id = idx($tab_spec, 'panelID'); - $panel = idx($panels, $panel_id); + $subpanel = idx($panels, $panel_id); - if ($panel) { + if ($subpanel) { $panel_content = id(new PhabricatorDashboardPanelRenderingEngine()) ->setViewer($viewer) ->setEnableAsyncRendering(true) + ->setContextObject($context_object) ->setParentPanelPHIDs($parent_phids) - ->setPanel($panel) - ->setPanelPHID($panel->getPHID()) + ->setPanel($subpanel) + ->setPanelPHID($subpanel->getPHID()) ->setHeaderMode($no_headers) - ->setMovable(false) ->renderPanel(); } else { $panel_content = pht('(Invalid Panel)'); } + $content_id = celerity_generate_unique_node_id(); + $content[] = phutil_tag( 'div', array( - 'id' => $node_ids[$idx], + 'id' => $content_id, 'style' => ($idx == $selected) ? null : 'display: none', ), $panel_content); + + $panel_list[] = array( + 'panelKey' => (string)$idx, + 'panelContentID' => $content_id, + ); + } + + if (!$content) { + if ($is_edit) { + $message = pht( + 'This tab panel does not have any tabs yet. Use "Add Tab..." to '. + 'create or place a tab.'); + } else { + $message = pht( + 'This tab panel does not have any tabs yet.'); + } + + $content = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_NODATA) + ->setErrors( + array( + $message, + )); + + $content = id(new PHUIBoxView()) + ->addClass('mlt mlb') + ->appendChild($content); } Javelin::initBehavior('dashboard-tab-panel'); @@ -119,7 +280,7 @@ final class PhabricatorDashboardTabsPanelType array( 'sigil' => 'dashboard-tab-panel-container', 'meta' => array( - 'panels' => $node_ids, + 'panels' => $panel_list, ), ), array( @@ -128,4 +289,24 @@ final class PhabricatorDashboardTabsPanelType )); } + public function getSubpanelPHIDs(PhabricatorDashboardPanel $panel) { + $config = $this->getPanelConfiguration($panel); + + $panel_ids = array(); + foreach ($config as $tab_key => $tab_spec) { + $panel_ids[] = $tab_spec['panelID']; + } + + if ($panel_ids) { + $panels = id(new PhabricatorDashboardPanelQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withIDs($panel_ids) + ->execute(); + } else { + $panels = array(); + } + + return mpull($panels, 'getPHID'); + } + } diff --git a/src/applications/dashboard/paneltype/PhabricatorDashboardTextPanelType.php b/src/applications/dashboard/paneltype/PhabricatorDashboardTextPanelType.php index 5ecde8501f..8f135d106d 100644 --- a/src/applications/dashboard/paneltype/PhabricatorDashboardTextPanelType.php +++ b/src/applications/dashboard/paneltype/PhabricatorDashboardTextPanelType.php @@ -12,21 +12,23 @@ final class PhabricatorDashboardTextPanelType } public function getIcon() { - return 'fa-paragraph'; + return 'fa-file-text-o'; } public function getPanelTypeDescription() { return pht( - 'Add some static text to the dashboard. This can be used to '. - 'provide instructions or context.'); + 'Add a text panel to the dashboard to provide instructions or '. + 'context.'); } - public function getFieldSpecifications() { + protected function newEditEngineFields(PhabricatorDashboardPanel $panel) { return array( - 'text' => array( - 'name' => pht('Text'), - 'type' => 'remarkup', - ), + id(new PhabricatorRemarkupEditField()) + ->setKey('text') + ->setLabel(pht('Text')) + ->setTransactionType( + PhabricatorDashboardTextPanelTextTransaction::TRANSACTIONTYPE) + ->setValue($panel->getProperty('text', '')), ); } diff --git a/src/applications/dashboard/phid/PhabricatorDashboardPanelPHIDType.php b/src/applications/dashboard/phid/PhabricatorDashboardPanelPHIDType.php index 49a5091412..d84db72a25 100644 --- a/src/applications/dashboard/phid/PhabricatorDashboardPanelPHIDType.php +++ b/src/applications/dashboard/phid/PhabricatorDashboardPanelPHIDType.php @@ -35,9 +35,11 @@ final class PhabricatorDashboardPanelPHIDType extends PhabricatorPHIDType { $name = $panel->getName(); $monogram = $panel->getMonogram(); - $handle->setName($panel->getMonogram()); - $handle->setFullName("{$monogram} {$name}"); - $handle->setURI("/{$monogram}"); + $handle + ->setIcon('fa-window-maximize') + ->setName($name) + ->setFullName("{$monogram} {$name}") + ->setURI($panel->getURI()); if ($panel->getIsArchived()) { $handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED); diff --git a/src/applications/dashboard/phid/PhabricatorDashboardPortalPHIDType.php b/src/applications/dashboard/phid/PhabricatorDashboardPortalPHIDType.php index 41c8e40f69..378748d96b 100644 --- a/src/applications/dashboard/phid/PhabricatorDashboardPortalPHIDType.php +++ b/src/applications/dashboard/phid/PhabricatorDashboardPortalPHIDType.php @@ -34,6 +34,7 @@ final class PhabricatorDashboardPortalPHIDType $portal = $objects[$phid]; $handle + ->setIcon('fa-compass') ->setName($portal->getName()) ->setURI($portal->getURI()); } diff --git a/src/applications/dashboard/query/PhabricatorDashboardPanelQuery.php b/src/applications/dashboard/query/PhabricatorDashboardPanelQuery.php index ac72c9ce6f..dd32d1c9dc 100644 --- a/src/applications/dashboard/query/PhabricatorDashboardPanelQuery.php +++ b/src/applications/dashboard/query/PhabricatorDashboardPanelQuery.php @@ -34,12 +34,6 @@ final class PhabricatorDashboardPanelQuery return $this; } - public function withNameNgrams($ngrams) { - return $this->withNgramsConstraint( - id(new PhabricatorDashboardPanelNgrams()), - $ngrams); - } - protected function loadPage() { return $this->loadStandardPage($this->newResultObject()); } @@ -62,35 +56,35 @@ final class PhabricatorDashboardPanelQuery if ($this->ids !== null) { $where[] = qsprintf( $conn, - 'id IN (%Ld)', + 'panel.id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( $conn, - 'phid IN (%Ls)', + 'panel.phid IN (%Ls)', $this->phids); } if ($this->archived !== null) { $where[] = qsprintf( $conn, - 'isArchived = %d', + 'panel.isArchived = %d', (int)$this->archived); } if ($this->panelTypes !== null) { $where[] = qsprintf( $conn, - 'panelType IN (%Ls)', + 'panel.panelType IN (%Ls)', $this->panelTypes); } if ($this->authorPHIDs !== null) { $where[] = qsprintf( $conn, - 'authorPHID IN (%Ls)', + 'panel.authorPHID IN (%Ls)', $this->authorPHIDs); } @@ -102,7 +96,7 @@ final class PhabricatorDashboardPanelQuery } protected function getPrimaryTableAlias() { - return 'dashboard_panel'; + return 'panel'; } } diff --git a/src/applications/dashboard/query/PhabricatorDashboardPanelSearchEngine.php b/src/applications/dashboard/query/PhabricatorDashboardPanelSearchEngine.php index 87e908b9c2..dc50fa5d66 100644 --- a/src/applications/dashboard/query/PhabricatorDashboardPanelSearchEngine.php +++ b/src/applications/dashboard/query/PhabricatorDashboardPanelSearchEngine.php @@ -42,20 +42,12 @@ final class PhabricatorDashboardPanelSearchEngine $query->withAuthorPHIDs($map['authorPHIDs']); } - if ($map['name'] !== null) { - $query->withNameNgrams($map['name']); - } - return $query; } protected function buildCustomSearchFields() { return array( - id(new PhabricatorSearchTextField()) - ->setLabel(pht('Name Contains')) - ->setKey('name') - ->setDescription(pht('Search for panels by name substring.')), id(new PhabricatorSearchDatasourceField()) ->setLabel(pht('Authored By')) ->setKey('authorPHIDs') diff --git a/src/applications/dashboard/query/PhabricatorDashboardPortalQuery.php b/src/applications/dashboard/query/PhabricatorDashboardPortalQuery.php index d352b99c8f..857c4dc215 100644 --- a/src/applications/dashboard/query/PhabricatorDashboardPortalQuery.php +++ b/src/applications/dashboard/query/PhabricatorDashboardPortalQuery.php @@ -36,21 +36,21 @@ final class PhabricatorDashboardPortalQuery if ($this->ids !== null) { $where[] = qsprintf( $conn, - 'id IN (%Ld)', + 'portal.id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( $conn, - 'phid IN (%Ls)', + 'portal.phid IN (%Ls)', $this->phids); } if ($this->statuses !== null) { $where[] = qsprintf( $conn, - 'status IN (%Ls)', + 'portal.status IN (%Ls)', $this->statuses); } @@ -61,4 +61,8 @@ final class PhabricatorDashboardPortalQuery return 'PhabricatorDashboardApplication'; } + protected function getPrimaryTableAlias() { + return 'portal'; + } + } diff --git a/src/applications/dashboard/query/PhabricatorDashboardQuery.php b/src/applications/dashboard/query/PhabricatorDashboardQuery.php index 9f5e256391..76854bffc1 100644 --- a/src/applications/dashboard/query/PhabricatorDashboardQuery.php +++ b/src/applications/dashboard/query/PhabricatorDashboardQuery.php @@ -9,9 +9,6 @@ final class PhabricatorDashboardQuery private $authorPHIDs; private $canEdit; - private $needPanels; - private $needProjects; - public function withIDs(array $ids) { $this->ids = $ids; return $this; @@ -32,27 +29,11 @@ final class PhabricatorDashboardQuery return $this; } - public function needPanels($need_panels) { - $this->needPanels = $need_panels; - return $this; - } - - public function needProjects($need_projects) { - $this->needProjects = $need_projects; - return $this; - } - public function withCanEdit($can_edit) { $this->canEdit = $can_edit; return $this; } - public function withNameNgrams($ngrams) { - return $this->withNgramsConstraint( - id(new PhabricatorDashboardNgrams()), - $ngrams); - } - protected function loadPage() { return $this->loadStandardPage($this->newResultObject()); } @@ -74,58 +55,6 @@ final class PhabricatorDashboardQuery ->apply($dashboards); } - if ($this->needPanels) { - $edge_query = id(new PhabricatorEdgeQuery()) - ->withSourcePHIDs($phids) - ->withEdgeTypes( - array( - PhabricatorDashboardDashboardHasPanelEdgeType::EDGECONST, - )); - $edge_query->execute(); - - $panel_phids = $edge_query->getDestinationPHIDs(); - if ($panel_phids) { - // NOTE: We explicitly disable policy exceptions when loading panels. - // If a particular panel is invalid or not visible to the viewer, - // we'll still render the dashboard, just not that panel. - - $panels = id(new PhabricatorDashboardPanelQuery()) - ->setParentQuery($this) - ->setRaisePolicyExceptions(false) - ->setViewer($this->getViewer()) - ->withPHIDs($panel_phids) - ->execute(); - $panels = mpull($panels, null, 'getPHID'); - } else { - $panels = array(); - } - - foreach ($dashboards as $dashboard) { - $dashboard_phids = $edge_query->getDestinationPHIDs( - array($dashboard->getPHID())); - $dashboard_panels = array_select_keys($panels, $dashboard_phids); - - $dashboard->attachPanelPHIDs($dashboard_phids); - $dashboard->attachPanels($dashboard_panels); - } - } - - if ($this->needProjects) { - $edge_query = id(new PhabricatorEdgeQuery()) - ->withSourcePHIDs($phids) - ->withEdgeTypes( - array( - PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, - )); - $edge_query->execute(); - - foreach ($dashboards as $dashboard) { - $project_phids = $edge_query->getDestinationPHIDs( - array($dashboard->getPHID())); - $dashboard->attachProjectPHIDs($project_phids); - } - } - return $dashboards; } @@ -135,28 +64,28 @@ final class PhabricatorDashboardQuery if ($this->ids !== null) { $where[] = qsprintf( $conn, - 'id IN (%Ld)', + 'dashboard.id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( $conn, - 'phid IN (%Ls)', + 'dashboard.phid IN (%Ls)', $this->phids); } if ($this->statuses !== null) { $where[] = qsprintf( $conn, - 'status IN (%Ls)', + 'dashboard.status IN (%Ls)', $this->statuses); } if ($this->authorPHIDs !== null) { $where[] = qsprintf( $conn, - 'authorPHID IN (%Ls)', + 'dashboard.authorPHID IN (%Ls)', $this->authorPHIDs); } diff --git a/src/applications/dashboard/query/PhabricatorDashboardSearchEngine.php b/src/applications/dashboard/query/PhabricatorDashboardSearchEngine.php index a05d1c4121..ea3f69faab 100644 --- a/src/applications/dashboard/query/PhabricatorDashboardSearchEngine.php +++ b/src/applications/dashboard/query/PhabricatorDashboardSearchEngine.php @@ -12,8 +12,7 @@ final class PhabricatorDashboardSearchEngine } public function newQuery() { - return id(new PhabricatorDashboardQuery()) - ->needPanels(true); + return id(new PhabricatorDashboardQuery()); } public function canUseInPanelContext() { @@ -22,10 +21,6 @@ final class PhabricatorDashboardSearchEngine protected function buildCustomSearchFields() { return array( - id(new PhabricatorSearchTextField()) - ->setLabel(pht('Name Contains')) - ->setKey('name') - ->setDescription(pht('Search for dashboards by name substring.')), id(new PhabricatorSearchDatasourceField()) ->setLabel(pht('Authored By')) ->setKey('authorPHIDs') @@ -94,10 +89,6 @@ final class PhabricatorDashboardSearchEngine $query->withAuthorPHIDs($map['authorPHIDs']); } - if ($map['name'] !== null) { - $query->withNameNgrams($map['name']); - } - if ($map['editable'] !== null) { $query->withCanEdit($map['editable']); } @@ -122,32 +113,33 @@ final class PhabricatorDashboardSearchEngine $handles = $viewer->loadHandles($phids); + if ($dashboards) { + $edge_query = id(new PhabricatorEdgeQuery()) + ->withSourcePHIDs(mpull($dashboards, 'getPHID')) + ->withEdgeTypes( + array( + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, + )); + + $edge_query->execute(); + } + $list = id(new PHUIObjectItemListView()) - ->setUser($viewer); + ->setViewer($viewer); foreach ($dashboards as $dashboard) { - $id = $dashboard->getID(); - $item = id(new PHUIObjectItemView()) - ->setUser($viewer) + ->setViewer($viewer) + ->setObjectName($dashboard->getObjectName()) ->setHeader($dashboard->getName()) - ->setHref($this->getApplicationURI("view/{$id}/")) + ->setHref($dashboard->getURI()) ->setObject($dashboard); - $bg_color = 'bg-dark'; if ($dashboard->isArchived()) { $item->setDisabled(true); $bg_color = 'bg-grey'; - } - - $panels = $dashboard->getPanels(); - foreach ($panels as $panel) { - $item->addAttribute($panel->getName()); - } - - if (empty($panels)) { - $empty = phutil_tag('em', array(), pht('No panels.')); - $item->addAttribute($empty); + } else { + $bg_color = 'bg-dark'; } $icon = id(new PHUIIconView()) @@ -160,6 +152,17 @@ final class PhabricatorDashboardSearchEngine $author_name = $handles[$author_phid]->renderLink(); $item->addByline(pht('Author: %s', $author_name)); + $phid = $dashboard->getPHID(); + $project_phids = $edge_query->getDestinationPHIDs(array($phid)); + $project_handles = $viewer->loadHandles($project_phids); + + $item->addAttribute( + id(new PHUIHandleTagListView()) + ->setLimit(4) + ->setNoDataString(pht('No Tags')) + ->setSlim(true) + ->setHandles($project_handles)); + $list->addItem($item); } @@ -170,24 +173,4 @@ final class PhabricatorDashboardSearchEngine return $result; } - protected function getNewUserBody() { - $create_button = id(new PHUIButtonView()) - ->setTag('a') - ->setText(pht('Create a Dashboard')) - ->setHref('/dashboard/create/') - ->setColor(PHUIButtonView::GREEN); - - $icon = $this->getApplication()->getIcon(); - $app_name = $this->getApplication()->getName(); - $view = id(new PHUIBigInfoView()) - ->setIcon($icon) - ->setTitle(pht('Welcome to %s', $app_name)) - ->setDescription( - pht('Customize your homepage with different panels and '. - 'search queries.')) - ->addAction($create_button); - - return $view; - } - } diff --git a/src/applications/dashboard/storage/PhabricatorDashboard.php b/src/applications/dashboard/storage/PhabricatorDashboard.php index 53bc2f857d..4081a40caf 100644 --- a/src/applications/dashboard/storage/PhabricatorDashboard.php +++ b/src/applications/dashboard/storage/PhabricatorDashboard.php @@ -10,7 +10,9 @@ final class PhabricatorDashboard extends PhabricatorDashboardDAO PhabricatorFlaggableInterface, PhabricatorDestructibleInterface, PhabricatorProjectInterface, - PhabricatorNgramsInterface { + PhabricatorFulltextInterface, + PhabricatorFerretInterface, + PhabricatorDashboardPanelContainerInterface { protected $name; protected $authorPHID; @@ -23,10 +25,7 @@ final class PhabricatorDashboard extends PhabricatorDashboardDAO const STATUS_ACTIVE = 'active'; const STATUS_ARCHIVED = 'archived'; - private $panelPHIDs = self::ATTACHABLE; - private $panels = self::ATTACHABLE; - private $edgeProjectPHIDs = self::ATTACHABLE; - + private $panelRefList; public static function initializeNewDashboard(PhabricatorUser $actor) { return id(new PhabricatorDashboard()) @@ -35,9 +34,7 @@ final class PhabricatorDashboard extends PhabricatorDashboardDAO ->setViewPolicy(PhabricatorPolicies::getMostOpenPolicy()) ->setEditPolicy($actor->getPHID()) ->setStatus(self::STATUS_ACTIVE) - ->setAuthorPHID($actor->getPHID()) - ->attachPanels(array()) - ->attachPanelPHIDs(array()); + ->setAuthorPHID($actor->getPHID()); } public static function getStatusNameMap() { @@ -62,66 +59,78 @@ final class PhabricatorDashboard extends PhabricatorDashboardDAO ) + parent::getConfiguration(); } - public function generatePHID() { - return PhabricatorPHID::generateNewPHID( - PhabricatorDashboardDashboardPHIDType::TYPECONST); + public function getPHIDType() { + return PhabricatorDashboardDashboardPHIDType::TYPECONST; } - public function getLayoutConfigObject() { - return PhabricatorDashboardLayoutConfig::newFromDictionary( - $this->getLayoutConfig()); + public function getRawLayoutMode() { + $config = $this->getRawLayoutConfig(); + return idx($config, 'layoutMode'); } - public function setLayoutConfigFromObject( - PhabricatorDashboardLayoutConfig $object) { - - $this->setLayoutConfig($object->toDictionary()); - - // See PHI385. Dashboard panel mutations rely on changes to the Dashboard - // object persisting when transactions are applied, but this assumption is - // no longer valid after T13054. For now, just save the dashboard - // explicitly. - $this->save(); - - return $this; + public function setRawLayoutMode($mode) { + $config = $this->getRawLayoutConfig(); + $config['layoutMode'] = $mode; + return $this->setRawLayoutConfig($config); } - public function getProjectPHIDs() { - return $this->assertAttached($this->edgeProjectPHIDs); + public function getRawPanels() { + $config = $this->getRawLayoutConfig(); + return idx($config, 'panels'); } - public function attachProjectPHIDs(array $phids) { - $this->edgeProjectPHIDs = $phids; - return $this; + public function setRawPanels(array $panels) { + $config = $this->getRawLayoutConfig(); + $config['panels'] = $panels; + return $this->setRawLayoutConfig($config); } - public function attachPanelPHIDs(array $phids) { - $this->panelPHIDs = $phids; - return $this; + private function getRawLayoutConfig() { + $config = $this->getLayoutConfig(); + + if (!is_array($config)) { + $config = array(); + } + + return $config; } - public function getPanelPHIDs() { - return $this->assertAttached($this->panelPHIDs); - } + private function setRawLayoutConfig(array $config) { + // If a cached panel ref list exists, clear it. + $this->panelRefList = null; - public function attachPanels(array $panels) { - assert_instances_of($panels, 'PhabricatorDashboardPanel'); - $this->panels = $panels; - return $this; - } - - public function getPanels() { - return $this->assertAttached($this->panels); + return $this->setLayoutConfig($config); } public function isArchived() { return ($this->getStatus() == self::STATUS_ARCHIVED); } - public function getViewURI() { - return '/dashboard/view/'.$this->getID().'/'; + public function getURI() { + return urisprintf('/dashboard/view/%d/', $this->getID()); } + public function getObjectName() { + return pht('Dashboard %d', $this->getID()); + } + + public function getPanelRefList() { + if (!$this->panelRefList) { + $this->panelRefList = $this->newPanelRefList(); + } + return $this->panelRefList; + } + + private function newPanelRefList() { + $raw_config = $this->getLayoutConfig(); + return PhabricatorDashboardPanelRefList::newFromDictionary($raw_config); + } + + public function getPanelPHIDs() { + $ref_list = $this->getPanelRefList(); + $phids = mpull($ref_list->getPanelRefs(), 'getPanelPHID'); + return array_unique($phids); + } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ @@ -164,28 +173,25 @@ final class PhabricatorDashboard extends PhabricatorDashboardDAO public function destroyObjectPermanently( PhabricatorDestructionEngine $engine) { - - $this->openTransaction(); - $installs = id(new PhabricatorDashboardInstall())->loadAllWhere( - 'dashboardPHID = %s', - $this->getPHID()); - foreach ($installs as $install) { - $install->delete(); - } - - $this->delete(); - $this->saveTransaction(); + $this->delete(); } +/* -( PhabricatorDashboardPanelContainerInterface )------------------------ */ -/* -( PhabricatorNgramInterface )------------------------------------------ */ + public function getDashboardPanelContainerPanelPHIDs() { + return $this->getPanelPHIDs(); + } +/* -( PhabricatorFulltextInterface )--------------------------------------- */ - public function newNgrams() { - return array( - id(new PhabricatorDashboardNgrams()) - ->setValue($this->getName()), - ); + public function newFulltextEngine() { + return new PhabricatorDashboardFulltextEngine(); + } + +/* -( PhabricatorFerretInterface )----------------------------------------- */ + + public function newFerretEngine() { + return new PhabricatorDashboardFerretEngine(); } } diff --git a/src/applications/dashboard/storage/PhabricatorDashboardInstall.php b/src/applications/dashboard/storage/PhabricatorDashboardInstall.php deleted file mode 100644 index 318c064808..0000000000 --- a/src/applications/dashboard/storage/PhabricatorDashboardInstall.php +++ /dev/null @@ -1,52 +0,0 @@ - array( - 'applicationClass' => 'text64', - ), - self::CONFIG_KEY_SCHEMA => array( - 'objectPHID' => array( - 'columns' => array('objectPHID', 'applicationClass'), - 'unique' => true, - ), - ), - ) + parent::getConfiguration(); - } - - public static function getDashboard( - PhabricatorUser $viewer, - $object_phid, - $application_class) { - - $dashboard = null; - $dashboard_install = id(new PhabricatorDashboardInstall()) - ->loadOneWhere( - 'objectPHID = %s AND applicationClass = %s', - $object_phid, - $application_class); - if ($dashboard_install) { - $dashboard = id(new PhabricatorDashboardQuery()) - ->setViewer($viewer) - ->withPHIDs(array($dashboard_install->getDashboardPHID())) - ->needPanels(true) - ->executeOne(); - } - - return $dashboard; - } -} diff --git a/src/applications/dashboard/storage/PhabricatorDashboardNgrams.php b/src/applications/dashboard/storage/PhabricatorDashboardNgrams.php deleted file mode 100644 index 7884ec43f9..0000000000 --- a/src/applications/dashboard/storage/PhabricatorDashboardNgrams.php +++ /dev/null @@ -1,18 +0,0 @@ -setName('') @@ -46,9 +45,8 @@ final class PhabricatorDashboardPanel ) + parent::getConfiguration(); } - public function generatePHID() { - return PhabricatorPHID::generateNewPHID( - PhabricatorDashboardPanelPHIDType::TYPECONST); + public function getPHIDType() { + return PhabricatorDashboardPanelPHIDType::TYPECONST; } public function getProperty($key, $default = null) { @@ -105,6 +103,10 @@ final class PhabricatorDashboardPanel return $impl; } + public function getEditEngineFields() { + return $this->requireImplementation()->getEditEngineFields($this); + } + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ @@ -142,27 +144,6 @@ final class PhabricatorDashboardPanel } -/* -( PhabricatorCustomFieldInterface )------------------------------------ */ - - - public function getCustomFieldSpecificationForRole($role) { - return array(); - } - - public function getCustomFieldBaseClass() { - return 'PhabricatorDashboardPanelCustomField'; - } - - public function getCustomFields() { - return $this->assertAttached($this->customFields); - } - - public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) { - $this->customFields = $fields; - return $this; - } - - /* -( PhabricatorDestructibleInterface )----------------------------------- */ @@ -174,15 +155,22 @@ final class PhabricatorDashboardPanel $this->saveTransaction(); } +/* -( PhabricatorDashboardPanelContainerInterface )------------------------ */ -/* -( PhabricatorNgramInterface )------------------------------------------ */ + public function getDashboardPanelContainerPanelPHIDs() { + return $this->requireImplementation()->getSubpanelPHIDs($this); + } +/* -( PhabricatorFulltextInterface )--------------------------------------- */ - public function newNgrams() { - return array( - id(new PhabricatorDashboardPanelNgrams()) - ->setValue($this->getName()), - ); + public function newFulltextEngine() { + return new PhabricatorDashboardPanelFulltextEngine(); + } + +/* -( PhabricatorFerretInterface )----------------------------------------- */ + + public function newFerretEngine() { + return new PhabricatorDashboardPanelFerretEngine(); } } diff --git a/src/applications/dashboard/storage/PhabricatorDashboardPanelNgrams.php b/src/applications/dashboard/storage/PhabricatorDashboardPanelNgrams.php deleted file mode 100644 index 536da8b751..0000000000 --- a/src/applications/dashboard/storage/PhabricatorDashboardPanelNgrams.php +++ /dev/null @@ -1,18 +0,0 @@ -getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $author_link = $this->renderHandleLink($author_phid); - - $type = $this->getTransactionType(); - switch ($type) { - case self::TYPE_NAME: - if (!strlen($old)) { - return pht( - '%s created this panel.', - $author_link); - } else { - return pht( - '%s renamed this panel from "%s" to "%s".', - $author_link, - $old, - $new); - } - case self::TYPE_ARCHIVE: - if ($new) { - return pht( - '%s archived this panel.', - $author_link); - } else { - return pht( - '%s activated this panel.', - $author_link); - } - } - - return parent::getTitle(); + public function getBaseTransactionClass() { + return 'PhabricatorDashboardPanelTransactionType'; } - public function getTitleForFeed() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $author_link = $this->renderHandleLink($author_phid); - $object_link = $this->renderHandleLink($object_phid); - - $type = $this->getTransactionType(); - switch ($type) { - case self::TYPE_NAME: - if (!strlen($old)) { - return pht( - '%s created dashboard panel %s.', - $author_link, - $object_link); - } else { - return pht( - '%s renamed dashboard panel %s from "%s" to "%s".', - $author_link, - $object_link, - $old, - $new); - } - case self::TYPE_ARCHIVE: - if ($new) { - return pht( - '%s archived dashboard panel %s.', - $author_link, - $object_link); - } else { - return pht( - '%s activated dashboard panel %s.', - $author_link, - $object_link); - } - } - - return parent::getTitleForFeed(); - } - - public function getColor() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_NAME: - if (!strlen($old)) { - return PhabricatorTransactions::COLOR_GREEN; - } - break; - } - - return parent::getColor(); - } } diff --git a/src/applications/dashboard/storage/PhabricatorDashboardPortal.php b/src/applications/dashboard/storage/PhabricatorDashboardPortal.php index 1930a23bd4..d04f8282fb 100644 --- a/src/applications/dashboard/storage/PhabricatorDashboardPortal.php +++ b/src/applications/dashboard/storage/PhabricatorDashboardPortal.php @@ -5,7 +5,10 @@ final class PhabricatorDashboardPortal implements PhabricatorApplicationTransactionInterface, PhabricatorPolicyInterface, - PhabricatorDestructibleInterface { + PhabricatorDestructibleInterface, + PhabricatorProjectInterface, + PhabricatorFulltextInterface, + PhabricatorFerretInterface { protected $name; protected $viewPolicy; @@ -55,6 +58,11 @@ final class PhabricatorDashboardPortal return '/portal/view/'.$this->getID().'/'; } + public function isArchived() { + $status_archived = PhabricatorDashboardPortalStatus::STATUS_ARCHIVED; + return ($this->getStatus() === $status_archived); + } + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ @@ -100,5 +108,16 @@ final class PhabricatorDashboardPortal $this->delete(); } +/* -( PhabricatorFulltextInterface )--------------------------------------- */ + + public function newFulltextEngine() { + return new PhabricatorDashboardPortalFulltextEngine(); + } + +/* -( PhabricatorFerretInterface )----------------------------------------- */ + + public function newFerretEngine() { + return new PhabricatorDashboardPortalFerretEngine(); + } } diff --git a/src/applications/dashboard/storage/PhabricatorDashboardTransaction.php b/src/applications/dashboard/storage/PhabricatorDashboardTransaction.php index a4a6c7f8b1..8b2c5c69f6 100644 --- a/src/applications/dashboard/storage/PhabricatorDashboardTransaction.php +++ b/src/applications/dashboard/storage/PhabricatorDashboardTransaction.php @@ -1,12 +1,7 @@ getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $author_link = $this->renderHandleLink($author_phid); - - $type = $this->getTransactionType(); - switch ($type) { - case self::TYPE_NAME: - if (!strlen($old)) { - return pht( - '%s created this dashboard.', - $author_link); - } else { - return pht( - '%s renamed this dashboard from "%s" to "%s".', - $author_link, - $old, - $new); - } - break; - case self::TYPE_ICON: - if (!strlen($old)) { - return pht( - '%s set the dashboard icon.', - $author_link); - } else { - return pht( - '%s changed this dashboard icon from "%s" to "%s".', - $author_link, - $old, - $new); - } - break; - case self::TYPE_STATUS: - if ($new == PhabricatorDashboard::STATUS_ACTIVE) { - return pht( - '%s activated this dashboard.', - $author_link); - } else { - return pht( - '%s archived this dashboard.', - $author_link); - } - break; - } - - return parent::getTitle(); + public function getBaseTransactionClass() { + return 'PhabricatorDashboardTransactionType'; } - public function getTitleForFeed() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $author_link = $this->renderHandleLink($author_phid); - $object_link = $this->renderHandleLink($object_phid); - - $type = $this->getTransactionType(); - switch ($type) { - case self::TYPE_NAME: - if (!strlen($old)) { - return pht( - '%s created dashboard %s.', - $author_link, - $object_link); - } else { - return pht( - '%s renamed dashboard %s from "%s" to "%s".', - $author_link, - $object_link, - $old, - $new); - } - break; - case self::TYPE_ICON: - if (!strlen($old)) { - return pht( - '%s set dashboard icon for %s.', - $author_link, - $object_link); - } else { - return pht( - '%s set the dashboard icon on %s from "%s" to "%s".', - $author_link, - $object_link, - $old, - $new); - } - break; - case self::TYPE_STATUS: - if ($new == PhabricatorDashboard::STATUS_ACTIVE) { - return pht( - '%s activated dashboard %s.', - $author_link, - $object_link); - } else { - return pht( - '%s archived dashboard %s.', - $author_link, - $object_link); - } - break; - } - - return parent::getTitleForFeed(); - } - - public function getColor() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_NAME: - if (!strlen($old)) { - return PhabricatorTransactions::COLOR_GREEN; - } - break; - case self::TYPE_STATUS: - if ($new == PhabricatorDashboard::STATUS_ACTIVE) { - return PhabricatorTransactions::COLOR_GREEN; - } else { - return PhabricatorTransactions::COLOR_INDIGO; - } - break; - } - - return parent::getColor(); - } - - public function getIcon() { - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_NAME: - return 'fa-pencil'; - break; - case self::TYPE_STATUS: - if ($new == PhabricatorDashboard::STATUS_ACTIVE) { - return 'fa-check'; - } else { - return 'fa-ban'; - } - break; - case self::TYPE_LAYOUT_MODE: - return 'fa-columns'; - break; - } - return parent::getIcon(); - } - - public function shouldHide() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_LAYOUT_MODE: - return true; - } - return parent::shouldHide(); - } } diff --git a/src/applications/dashboard/typeahead/PhabricatorDashboardDatasource.php b/src/applications/dashboard/typeahead/PhabricatorDashboardDatasource.php index a1c0399624..ff3376bdf6 100644 --- a/src/applications/dashboard/typeahead/PhabricatorDashboardDatasource.php +++ b/src/applications/dashboard/typeahead/PhabricatorDashboardDatasource.php @@ -18,6 +18,12 @@ final class PhabricatorDashboardDatasource public function loadResults() { $query = id(new PhabricatorDashboardQuery()); + $this->applyFerretConstraints( + $query, + id(new PhabricatorDashboard())->newFerretEngine(), + 'title', + $this->getRawQuery()); + $dashboards = $this->executeQuery($query); $results = array(); foreach ($dashboards as $dashboard) { diff --git a/src/applications/dashboard/typeahead/PhabricatorDashboardPanelDatasource.php b/src/applications/dashboard/typeahead/PhabricatorDashboardPanelDatasource.php index 883b6d32bb..9ad4901bbf 100644 --- a/src/applications/dashboard/typeahead/PhabricatorDashboardPanelDatasource.php +++ b/src/applications/dashboard/typeahead/PhabricatorDashboardPanelDatasource.php @@ -33,7 +33,11 @@ final class PhabricatorDashboardPanelDatasource $id = (int)$id; $query->withIDs(array($id)); } else { - $query->withNameNgrams($raw_query); + $this->applyFerretConstraints( + $query, + id(new PhabricatorDashboardPanel())->newFerretEngine(), + 'title', + $this->getRawQuery()); } $panels = $this->executeQuery($query); @@ -48,13 +52,13 @@ final class PhabricatorDashboardPanelDatasource $type_text = nonempty($panel->getPanelType(), pht('Unknown Type')); $icon = 'fa-question'; } - $id = $panel->getID(); + $phid = $panel->getPHID(); $monogram = $panel->getMonogram(); $properties = $panel->getProperties(); $result = id(new PhabricatorTypeaheadResult()) ->setName($monogram.' '.$panel->getName()) - ->setPHID($id) + ->setPHID($phid) ->setIcon($icon) ->addAttribute($type_text); @@ -66,7 +70,7 @@ final class PhabricatorDashboardPanelDatasource $result->setClosed(pht('Archived')); } - $results[$id] = $result; + $results[$phid] = $result; } return $results; diff --git a/src/applications/dashboard/typeahead/PhabricatorDashboardPortalDatasource.php b/src/applications/dashboard/typeahead/PhabricatorDashboardPortalDatasource.php new file mode 100644 index 0000000000..008bb542ab --- /dev/null +++ b/src/applications/dashboard/typeahead/PhabricatorDashboardPortalDatasource.php @@ -0,0 +1,51 @@ +buildResults(); + return $this->filterResultsAgainstTokens($results); + } + + protected function renderSpecialTokens(array $values) { + return $this->renderTokensFromResults($this->buildResults(), $values); + } + + public function buildResults() { + $query = new PhabricatorDashboardPortalQuery(); + + $this->applyFerretConstraints( + $query, + id(new PhabricatorDashboardPortal())->newFerretEngine(), + 'title', + $this->getRawQuery()); + + $portals = $this->executeQuery($query); + + $results = array(); + foreach ($portals as $portal) { + $result = id(new PhabricatorTypeaheadResult()) + ->setName($portal->getObjectName().' '.$portal->getName()) + ->setPHID($portal->getPHID()) + ->setIcon('fa-compass'); + + $results[] = $result; + } + + return $results; + } + +} diff --git a/src/applications/dashboard/xaction/dashboard/PhabricatorDashboardIconTransaction.php b/src/applications/dashboard/xaction/dashboard/PhabricatorDashboardIconTransaction.php new file mode 100644 index 0000000000..cd90d3e6b4 --- /dev/null +++ b/src/applications/dashboard/xaction/dashboard/PhabricatorDashboardIconTransaction.php @@ -0,0 +1,27 @@ +getIcon(); + } + + public function applyInternalEffects($object, $value) { + $object->setIcon($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + return pht( + '%s changed the icon for this dashboard from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + +} diff --git a/src/applications/dashboard/xaction/dashboard/PhabricatorDashboardLayoutTransaction.php b/src/applications/dashboard/xaction/dashboard/PhabricatorDashboardLayoutTransaction.php new file mode 100644 index 0000000000..441bb4fd36 --- /dev/null +++ b/src/applications/dashboard/xaction/dashboard/PhabricatorDashboardLayoutTransaction.php @@ -0,0 +1,55 @@ +getRawLayoutMode(); + } + + public function applyInternalEffects($object, $value) { + $object->setRawLayoutMode($value); + } + + public function getTitle() { + $new = $this->getNewValue(); + + return pht( + '%s changed the layout mode for this dashboard from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $mode_map = PhabricatorDashboardLayoutMode::getLayoutModeMap(); + + $old_value = $object->getRawLayoutMode(); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + + if ($new_value === $old_value) { + continue; + } + + if (!isset($mode_map[$new_value])) { + $errors[] = $this->newInvalidError( + pht( + 'Layout mode "%s" is not valid. Supported layout modes '. + 'are: %s.', + $new_value, + implode(', ', array_keys($mode_map))), + $xaction); + continue; + } + } + + return $errors; + } + + +} diff --git a/src/applications/dashboard/xaction/dashboard/PhabricatorDashboardNameTransaction.php b/src/applications/dashboard/xaction/dashboard/PhabricatorDashboardNameTransaction.php new file mode 100644 index 0000000000..af76d66f47 --- /dev/null +++ b/src/applications/dashboard/xaction/dashboard/PhabricatorDashboardNameTransaction.php @@ -0,0 +1,70 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + return pht( + '%s renamed this dashboard from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $max_length = $object->getColumnMaximumByteLength('name'); + foreach ($xactions as $xaction) { + $new = $xaction->getNewValue(); + if (!strlen($new)) { + $errors[] = $this->newInvalidError( + pht('Dashboards must have a name.'), + $xaction); + continue; + } + + if (strlen($new) > $max_length) { + $errors[] = $this->newInvalidError( + pht( + 'Dashboard names must not be longer than %s characters.', + $max_length)); + continue; + } + } + + if (!$errors) { + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Dashboards must have a name.')); + } + } + + return $errors; + } + + public function getTransactionTypeForConduit($xaction) { + return 'name'; + } + + public function getFieldValuesForConduit($xaction, $data) { + return array( + 'old' => $xaction->getOldValue(), + 'new' => $xaction->getNewValue(), + ); + } + +} diff --git a/src/applications/dashboard/xaction/dashboard/PhabricatorDashboardPanelsTransaction.php b/src/applications/dashboard/xaction/dashboard/PhabricatorDashboardPanelsTransaction.php new file mode 100644 index 0000000000..35248159bd --- /dev/null +++ b/src/applications/dashboard/xaction/dashboard/PhabricatorDashboardPanelsTransaction.php @@ -0,0 +1,153 @@ +getRawPanels(); + } + + public function applyInternalEffects($object, $value) { + $object->setRawPanels($value); + } + + public function getTitle() { + return pht( + '%s changed the panels on this dashboard.', + $this->renderAuthor()); + } + + public function validateTransactions($object, array $xactions) { + $actor = $this->getActor(); + $errors = array(); + + $ref_list = $object->getPanelRefList(); + $columns = $ref_list->getColumns(); + + $old_phids = $object->getPanelPHIDs(); + $old_phids = array_fuse($old_phids); + + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + if (!is_array($new_value)) { + $errors[] = $this->newInvalidError( + pht('Panels must be a list of panel specifications.'), + $xaction); + continue; + } + + if (!phutil_is_natural_list($new_value)) { + $errors[] = $this->newInvalidError( + pht('Panels must be a list, not a map.'), + $xaction); + continue; + } + + $new_phids = array(); + $seen_keys = array(); + foreach ($new_value as $idx => $spec) { + if (!is_array($spec)) { + $errors[] = $this->newInvalidError( + pht( + 'Each panel specification must be a map of panel attributes. '. + 'Panel specification at index "%s" is "%s".', + $idx, + phutil_describe_type($spec)), + $xaction); + continue; + } + + try { + PhutilTypeSpec::checkMap( + $spec, + array( + 'panelPHID' => 'string', + 'columnKey' => 'string', + 'panelKey' => 'string', + )); + } catch (PhutilTypeCheckException $ex) { + $errors[] = $this->newInvalidError( + pht( + 'Panel specification at index "%s" is invalid: %s', + $idx, + $ex->getMessage()), + $xaction); + continue; + } + + $panel_key = $spec['panelKey']; + + if (!strlen($panel_key)) { + $errors[] = $this->newInvalidError( + pht( + 'Panel specification at index "%s" has bad panel key "%s". '. + 'Panel keys must be nonempty.', + $idx, + $panel_key), + $xaction); + continue; + } + + if (isset($seen_keys[$panel_key])) { + $errors[] = $this->newInvalidError( + pht( + 'Panel specification at index "%s" has duplicate panel key '. + '"%s". Each panel must have a unique panel key.', + $idx, + $panel_key), + $xaction); + continue; + } + + $seen_keys[$panel_key] = true; + + $panel_phid = $spec['panelPHID']; + $new_phids[] = $panel_phid; + + $column_key = $spec['columnKey']; + + if (!isset($columns[$column_key])) { + $errors[] = $this->newInvalidError( + pht( + 'Panel specification at index "%s" has bad column key "%s", '. + 'valid column keys are: %s.', + $idx, + $column_key, + implode(', ', array_keys($columns))), + $xaction); + continue; + } + } + + $new_phids = array_fuse($new_phids); + $add_phids = array_diff_key($new_phids, $old_phids); + + if ($add_phids) { + $panels = id(new PhabricatorDashboardPanelQuery()) + ->setViewer($actor) + ->withPHIDs($add_phids) + ->execute(); + $panels = mpull($panels, null, 'getPHID'); + + foreach ($add_phids as $add_phid) { + $panel = idx($panels, $add_phid); + + if (!$panel) { + $errors[] = $this->newInvalidError( + pht( + 'Panel specification adds panel "%s", but this is not a '. + 'valid panel or not a visible panel. You can only add '. + 'valid panels which you have permission to see to a dashboard.', + $add_phid)); + continue; + } + } + } + } + + return $errors; + } + +} diff --git a/src/applications/dashboard/xaction/dashboard/PhabricatorDashboardStatusTransaction.php b/src/applications/dashboard/xaction/dashboard/PhabricatorDashboardStatusTransaction.php new file mode 100644 index 0000000000..4012fe0cc8 --- /dev/null +++ b/src/applications/dashboard/xaction/dashboard/PhabricatorDashboardStatusTransaction.php @@ -0,0 +1,59 @@ +getStatus(); + } + + public function applyInternalEffects($object, $value) { + $object->setStatus($value); + } + + public function getTitle() { + $new = $this->getNewValue(); + + switch ($new) { + case PhabricatorDashboard::STATUS_ACTIVE: + return pht( + '%s activated this dashboard.', + $this->renderAuthor()); + case PhabricatorDashboard::STATUS_ARCHIVED: + return pht( + '%s archived this dashboard.', + $this->renderAuthor()); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $valid_statuses = PhabricatorDashboard::getStatusNameMap(); + + $old_value = $object->getStatus(); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + + if ($new_value === $old_value) { + continue; + } + + if (!isset($valid_statuses[$new_value])) { + $errors[] = $this->newInvalidError( + pht( + 'Status "%s" is not valid. Supported status constants are: %s.', + $new_value, + implode(', ', array_keys($valid_statuses))), + $xaction); + continue; + } + } + + return $errors; + } + + +} diff --git a/src/applications/dashboard/xaction/dashboard/PhabricatorDashboardTransactionType.php b/src/applications/dashboard/xaction/dashboard/PhabricatorDashboardTransactionType.php new file mode 100644 index 0000000000..d0d87405a3 --- /dev/null +++ b/src/applications/dashboard/xaction/dashboard/PhabricatorDashboardTransactionType.php @@ -0,0 +1,4 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + return pht( + '%s renamed this panel from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $max_length = $object->getColumnMaximumByteLength('name'); + foreach ($xactions as $xaction) { + $new = $xaction->getNewValue(); + if (!strlen($new)) { + $errors[] = $this->newInvalidError( + pht('Panels must have a title.'), + $xaction); + continue; + } + + if (strlen($new) > $max_length) { + $errors[] = $this->newInvalidError( + pht( + 'Panel names must not be longer than %s characters.', + $max_length)); + continue; + } + } + + if (!$errors) { + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Panels must have a title.')); + } + } + + return $errors; + } + + public function getTransactionTypeForConduit($xaction) { + return 'name'; + } + + public function getFieldValuesForConduit($xaction, $data) { + return array( + 'old' => $xaction->getOldValue(), + 'new' => $xaction->getNewValue(), + ); + } + +} diff --git a/src/applications/dashboard/xaction/panel/PhabricatorDashboardPanelPropertyTransaction.php b/src/applications/dashboard/xaction/panel/PhabricatorDashboardPanelPropertyTransaction.php new file mode 100644 index 0000000000..f30add6836 --- /dev/null +++ b/src/applications/dashboard/xaction/panel/PhabricatorDashboardPanelPropertyTransaction.php @@ -0,0 +1,18 @@ +getPropertyKey(); + return $object->getProperty($property_key); + } + + public function applyInternalEffects($object, $value) { + $property_key = $this->getPropertyKey(); + $object->setProperty($property_key, $value); + } + +} diff --git a/src/applications/dashboard/xaction/panel/PhabricatorDashboardPanelStatusTransaction.php b/src/applications/dashboard/xaction/panel/PhabricatorDashboardPanelStatusTransaction.php new file mode 100644 index 0000000000..da2e736f98 --- /dev/null +++ b/src/applications/dashboard/xaction/panel/PhabricatorDashboardPanelStatusTransaction.php @@ -0,0 +1,33 @@ +getIsArchived(); + } + + public function generateNewValue($object, $value) { + return (bool)$value; + } + + public function applyInternalEffects($object, $value) { + $object->setIsArchived((int)$value); + } + + public function getTitle() { + $new = $this->getNewValue(); + if ($new) { + return pht( + '%s archived this panel.', + $this->renderAuthor()); + } else { + return pht( + '%s activated this panel.', + $this->renderAuthor()); + } + } + +} diff --git a/src/applications/dashboard/xaction/panel/PhabricatorDashboardPanelTransactionType.php b/src/applications/dashboard/xaction/panel/PhabricatorDashboardPanelTransactionType.php new file mode 100644 index 0000000000..cfe829f7f5 --- /dev/null +++ b/src/applications/dashboard/xaction/panel/PhabricatorDashboardPanelTransactionType.php @@ -0,0 +1,4 @@ +getProperty($this->getPropertyKey()); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + + if ($new_value === $old_value) { + continue; + } + + if (!isset($engines[$new_value])) { + $errors[] = $this->newInvalidError( + pht( + 'Application search engine class "%s" is unknown. Query panels '. + 'must use a known search engine class.', + $new_value), + $xaction); + continue; + } + } + + return $errors; + } + +} diff --git a/src/applications/dashboard/xaction/panel/PhabricatorDashboardQueryPanelQueryTransaction.php b/src/applications/dashboard/xaction/panel/PhabricatorDashboardQueryPanelQueryTransaction.php new file mode 100644 index 0000000000..bfbfd996f6 --- /dev/null +++ b/src/applications/dashboard/xaction/panel/PhabricatorDashboardQueryPanelQueryTransaction.php @@ -0,0 +1,12 @@ +withIDs(array($revision_id)) ->setViewer($request->getUser()) ->needReviewers(true) + ->needCommitPHIDs(true) ->executeOne(); if (!$revision) { @@ -59,7 +60,7 @@ final class DifferentialGetRevisionConduitAPIMethod $diff_dicts = mpull($diffs, 'getDiffDict'); $commit_dicts = array(); - $commit_phids = $revision->loadCommitPHIDs(); + $commit_phids = $revision->getCommitPHIDs(); $handles = id(new PhabricatorHandleQuery()) ->setViewer($request->getUser()) ->withPHIDs($commit_phids) diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index ba36b1da17..9544e70c16 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -51,6 +51,7 @@ final class DifferentialRevisionViewController ->setViewer($viewer) ->needReviewers(true) ->needReviewerAuthority(true) + ->needCommitPHIDs(true) ->executeOne(); if (!$revision) { return new Aphront404Response(); @@ -146,7 +147,7 @@ final class DifferentialRevisionViewController $object_phids = array_merge( $revision->getReviewerPHIDs(), $subscriber_phids, - $revision->loadCommitPHIDs(), + $revision->getCommitPHIDs(), array( $revision->getAuthorPHID(), $viewer->getPHID(), diff --git a/src/applications/differential/engine/DifferentialDiffExtractionEngine.php b/src/applications/differential/engine/DifferentialDiffExtractionEngine.php index 7b94b1958b..feac4cce1c 100644 --- a/src/applications/differential/engine/DifferentialDiffExtractionEngine.php +++ b/src/applications/differential/engine/DifferentialDiffExtractionEngine.php @@ -243,8 +243,6 @@ final class DifferentialDiffExtractionEngine extends Phobject { PhabricatorContentSource $content_source) { $viewer = $this->getViewer(); - $result_data = array(); - $new_diff = $this->newDiffFromCommit($commit); $old_diff = $revision->getActiveDiff(); @@ -261,8 +259,6 @@ final class DifferentialDiffExtractionEngine extends Phobject { $old_diff, $new_diff); if ($has_changed) { - $result_data['vsDiff'] = $old_diff->getID(); - $revision_monogram = $revision->getMonogram(); $old_id = $old_diff->getID(); $new_id = $new_diff->getID(); @@ -319,6 +315,7 @@ final class DifferentialDiffExtractionEngine extends Phobject { $editor = id(new DifferentialTransactionEditor()) ->setActor($viewer) ->setContinueOnMissingFields(true) + ->setContinueOnNoEffect(true) ->setContentSource($content_source) ->setChangedPriorToCommitURI($changed_uri) ->setIsCloseByCommit(true); @@ -328,16 +325,7 @@ final class DifferentialDiffExtractionEngine extends Phobject { $editor->setActingAsPHID($author_phid); } - try { - $editor->applyTransactions($revision, $xactions); - } catch (PhabricatorApplicationTransactionNoEffectException $ex) { - // NOTE: We've marked transactions other than the CLOSE transaction - // as ignored when they don't have an effect, so this means that we - // lost a race to close the revision. That's perfectly fine, we can - // just continue normally. - } - - return $result_data; + $editor->applyTransactions($revision, $xactions); } private function loadConcerningBuilds(DifferentialRevision $revision) { diff --git a/src/applications/differential/herald/DifferentialReviewersAddBlockingReviewersHeraldAction.php b/src/applications/differential/herald/DifferentialReviewersAddBlockingReviewersHeraldAction.php index 16d07e5240..c920253dae 100644 --- a/src/applications/differential/herald/DifferentialReviewersAddBlockingReviewersHeraldAction.php +++ b/src/applications/differential/herald/DifferentialReviewersAddBlockingReviewersHeraldAction.php @@ -29,4 +29,8 @@ final class DifferentialReviewersAddBlockingReviewersHeraldAction return pht('Add blocking reviewers: %s.', $this->renderHandleList($value)); } + public function getPHIDsAffectedByAction(HeraldActionRecord $record) { + return $record->getTarget(); + } + } diff --git a/src/applications/differential/herald/DifferentialReviewersAddReviewersHeraldAction.php b/src/applications/differential/herald/DifferentialReviewersAddReviewersHeraldAction.php index d39a4370c5..60e64c750d 100644 --- a/src/applications/differential/herald/DifferentialReviewersAddReviewersHeraldAction.php +++ b/src/applications/differential/herald/DifferentialReviewersAddReviewersHeraldAction.php @@ -29,4 +29,8 @@ final class DifferentialReviewersAddReviewersHeraldAction return pht('Add reviewers: %s.', $this->renderHandleList($value)); } + public function getPHIDsAffectedByAction(HeraldActionRecord $record) { + return $record->getTarget(); + } + } diff --git a/src/applications/differential/herald/DifferentialRevisionJIRAIssueURIsHeraldField.php b/src/applications/differential/herald/DifferentialRevisionJIRAIssueURIsHeraldField.php new file mode 100644 index 0000000000..83b3cec39e --- /dev/null +++ b/src/applications/differential/herald/DifferentialRevisionJIRAIssueURIsHeraldField.php @@ -0,0 +1,44 @@ +getAdapter(); + $viewer = $adapter->getViewer(); + + $jira_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( + $object->getPHID(), + PhabricatorJiraIssueHasObjectEdgeType::EDGECONST); + if (!$jira_phids) { + return array(); + } + + $xobjs = id(new DoorkeeperExternalObjectQuery()) + ->setViewer($viewer) + ->withPHIDs($jira_phids) + ->execute(); + + return mpull($xobjs, 'getObjectURI'); + } + + protected function getHeraldFieldStandardType() { + return self::STANDARD_TEXT_LIST; + } + +} diff --git a/src/applications/differential/query/DifferentialRevisionQuery.php b/src/applications/differential/query/DifferentialRevisionQuery.php index a385fc5252..738df3156a 100644 --- a/src/applications/differential/query/DifferentialRevisionQuery.php +++ b/src/applications/differential/query/DifferentialRevisionQuery.php @@ -16,7 +16,6 @@ final class DifferentialRevisionQuery private $reviewers = array(); private $revIDs = array(); private $commitHashes = array(); - private $commitPHIDs = array(); private $phids = array(); private $responsibles = array(); private $branches = array(); @@ -119,20 +118,6 @@ final class DifferentialRevisionQuery return $this; } - /** - * Filter results to revisions that have one of the provided PHIDs as - * commits. Calling this function will clear anything set by previous calls - * to @{method:withCommitPHIDs}. - * - * @param array List of PHIDs of commits - * @return this - * @task config - */ - public function withCommitPHIDs(array $commit_phids) { - $this->commitPHIDs = $commit_phids; - return $this; - } - public function withStatuses(array $statuses) { $this->statuses = $statuses; return $this; @@ -400,7 +385,7 @@ final class DifferentialRevisionQuery $conn_r = $table->establishConnection('r'); if ($this->needCommitPHIDs) { - $this->loadCommitPHIDs($conn_r, $revisions); + $this->loadCommitPHIDs($revisions); } $need_active = $this->needActiveDiffs; @@ -606,13 +591,6 @@ final class DifferentialRevisionQuery $this->draftAuthors); } - if ($this->commitPHIDs) { - $joins[] = qsprintf( - $conn, - 'JOIN %T commits ON commits.revisionID = r.id', - DifferentialRevision::TABLE_COMMIT); - } - $joins[] = $this->buildJoinClauseParts($conn); return $this->formatJoinClause($conn, $joins); @@ -674,13 +652,6 @@ final class DifferentialRevisionQuery $where[] = $hash_clauses; } - if ($this->commitPHIDs) { - $where[] = qsprintf( - $conn, - 'commits.commitPHID IN (%Ls)', - $this->commitPHIDs); - } - if ($this->phids) { $where[] = qsprintf( $conn, @@ -807,18 +778,26 @@ final class DifferentialRevisionQuery ); } - private function loadCommitPHIDs($conn_r, array $revisions) { + private function loadCommitPHIDs(array $revisions) { assert_instances_of($revisions, 'DifferentialRevision'); - $commit_phids = queryfx_all( - $conn_r, - 'SELECT * FROM %T WHERE revisionID IN (%Ld)', - DifferentialRevision::TABLE_COMMIT, - mpull($revisions, 'getID')); - $commit_phids = igroup($commit_phids, 'revisionID'); - foreach ($revisions as $revision) { - $phids = idx($commit_phids, $revision->getID(), array()); - $phids = ipull($phids, 'commitPHID'); - $revision->attachCommitPHIDs($phids); + + if (!$revisions) { + return; + } + + $revisions = mpull($revisions, null, 'getPHID'); + + $edge_query = id(new PhabricatorEdgeQuery()) + ->withSourcePHIDs(array_keys($revisions)) + ->withEdgeTypes( + array( + DifferentialRevisionHasCommitEdgeType::EDGECONST, + )); + $edge_query->execute(); + + foreach ($revisions as $phid => $revision) { + $commit_phids = $edge_query->getDestinationPHIDs(array($phid)); + $revision->attachCommitPHIDs($commit_phids); } } diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index a2a058568b..b1d1c75ab4 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -41,7 +41,7 @@ final class DifferentialRevision extends DifferentialDAO protected $editPolicy = PhabricatorPolicies::POLICY_USER; protected $properties = array(); - private $commits = self::ATTACHABLE; + private $commitPHIDs = self::ATTACHABLE; private $activeDiff = self::ATTACHABLE; private $diffIDs = self::ATTACHABLE; private $hashes = self::ATTACHABLE; @@ -53,8 +53,6 @@ final class DifferentialRevision extends DifferentialDAO private $flags = array(); private $forceMap = array(); - const TABLE_COMMIT = 'differential_commit'; - const RELATION_REVIEWER = 'revw'; const RELATION_SUBSCRIBED = 'subd'; @@ -158,35 +156,8 @@ final class DifferentialRevision extends DifferentialDAO return '/'.$this->getMonogram(); } - public function loadIDsByCommitPHIDs($phids) { - if (!$phids) { - return array(); - } - $revision_ids = queryfx_all( - $this->establishConnection('r'), - 'SELECT * FROM %T WHERE commitPHID IN (%Ls)', - self::TABLE_COMMIT, - $phids); - return ipull($revision_ids, 'revisionID', 'commitPHID'); - } - - public function loadCommitPHIDs() { - if (!$this->getID()) { - return ($this->commits = array()); - } - - $commits = queryfx_all( - $this->establishConnection('r'), - 'SELECT commitPHID FROM %T WHERE revisionID = %d', - self::TABLE_COMMIT, - $this->getID()); - $commits = ipull($commits, 'commitPHID'); - - return ($this->commits = $commits); - } - public function getCommitPHIDs() { - return $this->assertAttached($this->commits); + return $this->assertAttached($this->commitPHIDs); } public function getActiveDiff() { @@ -214,7 +185,7 @@ final class DifferentialRevision extends DifferentialDAO } public function attachCommitPHIDs(array $phids) { - $this->commits = array_values($phids); + $this->commitPHIDs = $phids; return $this; } @@ -1048,12 +1019,6 @@ final class DifferentialRevision extends DifferentialDAO $conn_w = $this->establishConnection('w'); - queryfx( - $conn_w, - 'DELETE FROM %T WHERE revisionID = %d', - self::TABLE_COMMIT, - $this->getID()); - // we have to do paths a little differently as they do not have // an id or phid column for delete() to act on $dummy_path = new DifferentialAffectedPath(); diff --git a/src/applications/differential/storage/DifferentialSchemaSpec.php b/src/applications/differential/storage/DifferentialSchemaSpec.php index 7aa76fa822..4f747af06c 100644 --- a/src/applications/differential/storage/DifferentialSchemaSpec.php +++ b/src/applications/differential/storage/DifferentialSchemaSpec.php @@ -26,9 +26,12 @@ final class DifferentialSchemaSpec extends PhabricatorConfigSchemaSpec { 'persistence' => PhabricatorConfigTableSchema::PERSISTENCE_CACHE, )); + // TODO: All readers and writers for this table were removed in April + // 2019. Destroy this table once we're sure we won't miss it. + $this->buildRawSchema( id(new DifferentialRevision())->getApplicationName(), - DifferentialRevision::TABLE_COMMIT, + 'differential_commit', array( 'revisionID' => 'id', 'commitPHID' => 'phid', diff --git a/src/applications/differential/xaction/DifferentialRevisionCloseTransaction.php b/src/applications/differential/xaction/DifferentialRevisionCloseTransaction.php index f20ba7a011..6832108dc3 100644 --- a/src/applications/differential/xaction/DifferentialRevisionCloseTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionCloseTransaction.php @@ -83,49 +83,50 @@ final class DifferentialRevisionCloseTransaction } public function getTitle() { - if (!$this->getMetadataValue('isCommitClose')) { + $commit_phid = $this->getMetadataValue('commitPHID'); + if ($commit_phid) { + $commit = id(new DiffusionCommitQuery()) + ->setViewer($this->getViewer()) + ->withPHIDs(array($commit_phid)) + ->needIdentities(true) + ->executeOne(); + } else { + $commit = null; + } + + if (!$commit) { return pht( '%s closed this revision.', $this->renderAuthor()); } - $commit_phid = $this->getMetadataValue('commitPHID'); - $committer_phid = $this->getMetadataValue('committerPHID'); - $author_phid = $this->getMetadataValue('authorPHID'); - - if ($committer_phid) { - $committer_name = $this->renderHandle($committer_phid); - } else { - $committer_name = $this->getMetadataValue('committerName'); + $author_phid = null; + if ($commit->hasAuthorIdentity()) { + $identity = $commit->getAuthorIdentity(); + $author_phid = $identity->getIdentityDisplayPHID(); } - if ($author_phid) { - $author_name = $this->renderHandle($author_phid); - } else { - $author_name = $this->getMetadatavalue('authorName'); + $committer_phid = null; + if ($commit->hasCommitterIdentity()) { + $identity = $commit->getCommitterIdentity(); + $committer_phid = $identity->getIdentityDisplayPHID(); } - $same_phid = - strlen($committer_phid) && - strlen($author_phid) && - ($committer_phid == $author_phid); - - $same_name = - !strlen($committer_phid) && - !strlen($author_phid) && - ($committer_name == $author_name); - - if ($same_name || $same_phid) { + if (!$author_phid) { + return pht( + 'Closed by commit %s.', + $this->renderHandle($commit_phid)); + } else if (!$committer_phid || ($committer_phid === $author_phid)) { return pht( 'Closed by commit %s (authored by %s).', $this->renderHandle($commit_phid), - $author_name); + $this->renderHandle($author_phid)); } else { return pht( 'Closed by commit %s (authored by %s, committed by %s).', $this->renderHandle($commit_phid), - $author_name, - $committer_name); + $this->renderHandle($author_phid), + $this->renderHandle($committer_phid)); } } diff --git a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php index d635f5d559..96dbac7c8e 100644 --- a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php +++ b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php @@ -86,6 +86,7 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication { 'enormous/' => 'DiffusionRepositoryEditEnormousController', 'delete/' => 'DiffusionRepositoryEditDeleteController', 'update/' => 'DiffusionRepositoryEditUpdateController', + 'publish/' => 'DiffusionRepositoryEditPublishingController', 'testautomation/' => 'DiffusionRepositoryTestAutomationController', ), 'pathtree/(?P.*)' => 'DiffusionPathTreeController', diff --git a/src/applications/diffusion/conduit/DiffusionBranchQueryConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionBranchQueryConduitAPIMethod.php index ac2c223c1e..16cd819d47 100644 --- a/src/applications/diffusion/conduit/DiffusionBranchQueryConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionBranchQueryConduitAPIMethod.php @@ -46,22 +46,12 @@ final class DiffusionBranchQueryConduitAPIMethod // NOTE: We can't use DiffusionLowLevelGitRefQuery here because // `git for-each-ref` does not support `--contains`. - if ($repository->isWorkingCopyBare()) { - list($stdout) = $repository->execxLocalCommand( - 'branch --verbose --no-abbrev --contains %s -- %Ls', - $contains, - $patterns_argv); - $ref_map = DiffusionGitBranch::parseLocalBranchOutput( - $stdout); - } else { - list($stdout) = $repository->execxLocalCommand( - 'branch -r --verbose --no-abbrev --contains %s -- %Ls', - $contains, - $patterns_argv); - $ref_map = DiffusionGitBranch::parseRemoteBranchOutput( - $stdout, - DiffusionGitBranch::DEFAULT_GIT_REMOTE); - } + list($stdout) = $repository->execxLocalCommand( + 'branch --verbose --no-abbrev --contains %s -- %Ls', + $contains, + $patterns_argv); + $ref_map = DiffusionGitBranch::parseLocalBranchOutput( + $stdout); $refs = array(); foreach ($ref_map as $ref => $commit) { diff --git a/src/applications/diffusion/controller/DiffusionBlameController.php b/src/applications/diffusion/controller/DiffusionBlameController.php index 591462590f..5de464b335 100644 --- a/src/applications/diffusion/controller/DiffusionBlameController.php +++ b/src/applications/diffusion/controller/DiffusionBlameController.php @@ -36,27 +36,9 @@ final class DiffusionBlameController extends DiffusionController { $commit_map = mpull($commits, 'getCommitIdentifier', 'getPHID'); - $revisions = array(); - $revision_map = array(); - if ($commits) { - $revision_ids = id(new DifferentialRevision()) - ->loadIDsByCommitPHIDs(array_keys($commit_map)); - if ($revision_ids) { - $revisions = id(new DifferentialRevisionQuery()) - ->setViewer($viewer) - ->withIDs($revision_ids) - ->execute(); - $revisions = mpull($revisions, null, 'getID'); - } - - foreach ($revision_ids as $commit_phid => $revision_id) { - // If the viewer can't actually see this revision, skip it. - if (!isset($revisions[$revision_id])) { - continue; - } - $revision_map[$commit_map[$commit_phid]] = $revision_id; - } - } + $revision_map = DiffusionCommitRevisionQuery::loadRevisionMapForCommits( + $viewer, + $commits); $base_href = (string)$drequest->generateURI( array( @@ -75,8 +57,10 @@ final class DiffusionBlameController extends DiffusionController { $handle_phids[] = $commit->getAuthorDisplayPHID(); } - foreach ($revisions as $revision) { - $handle_phids[] = $revision->getAuthorPHID(); + foreach ($revision_map as $revisions) { + foreach ($revisions as $revision) { + $handle_phids[] = $revision->getAuthorPHID(); + } } $handles = $viewer->loadHandles($handle_phids); @@ -84,13 +68,6 @@ final class DiffusionBlameController extends DiffusionController { $map = array(); $epochs = array(); foreach ($identifiers as $identifier) { - $revision_id = idx($revision_map, $identifier); - if ($revision_id) { - $revision = idx($revisions, $revision_id); - } else { - $revision = null; - } - $skip_href = $base_href.'?before='.$identifier; $skip_link = javelin_tag( @@ -115,6 +92,17 @@ final class DiffusionBlameController extends DiffusionController { $commit = idx($commits, $identifier); + $revision = null; + if ($commit) { + $revisions = idx($revision_map, $commit->getPHID()); + + // There may be multiple edges between this commit and revisions in the + // database. If there are, just pick one arbitrarily. + if ($revisions) { + $revision = head($revisions); + } + } + $author_phid = null; if ($commit) { diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index 1493d658e6..60df3f35ac 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -1106,11 +1106,7 @@ final class DiffusionBrowseController extends DiffusionController { $history_table = id(new DiffusionHistoryTableView()) ->setViewer($viewer) ->setDiffusionRequest($drequest) - ->setHistory($history); - - $history_table->loadRevisions(); - - $history_table + ->setHistory($history) ->setParents($results['parents']) ->setFilterParents(true) ->setIsHead(true) diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index fae215f381..013d87ee68 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -240,6 +240,19 @@ final class DiffusionCommitController extends DiffusionController { 'reachable from any branch, tag, or ref.'); } } + if (!$commit->isPermanentCommit()) { + $nonpermanent_tag = id(new PHUITagView()) + ->setType(PHUITagView::TYPE_SHADE) + ->setName(pht('Unpublished')) + ->setColor(PHUITagView::COLOR_ORANGE); + + $header->addTag($nonpermanent_tag); + + $this->commitErrors[] = pht( + 'This commit is not reachable from any permanent branch, tag, '. + 'or ref.'); + } + if ($this->getCommitErrors()) { $error_panel = id(new PHUIInfoView()) @@ -794,8 +807,6 @@ final class DiffusionCommitController extends DiffusionController { ->setDiffusionRequest($drequest) ->setHistory($merges); - $history_table->loadRevisions(); - $panel = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Merged Changes')) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) diff --git a/src/applications/diffusion/controller/DiffusionCompareController.php b/src/applications/diffusion/controller/DiffusionCompareController.php index 2361c066dd..7dc3fb14c5 100644 --- a/src/applications/diffusion/controller/DiffusionCompareController.php +++ b/src/applications/diffusion/controller/DiffusionCompareController.php @@ -299,11 +299,7 @@ final class DiffusionCompareController extends DiffusionController { $history_table = id(new DiffusionHistoryTableView()) ->setUser($viewer) ->setDiffusionRequest($drequest) - ->setHistory($history); - - $history_table->loadRevisions(); - - $history_table + ->setHistory($history) ->setParents($results['parents']) ->setFilterParents(true) ->setIsHead(!$pager->getOffset()) diff --git a/src/applications/diffusion/controller/DiffusionGraphController.php b/src/applications/diffusion/controller/DiffusionGraphController.php index 096204ac6d..4536e29eaf 100644 --- a/src/applications/diffusion/controller/DiffusionGraphController.php +++ b/src/applications/diffusion/controller/DiffusionGraphController.php @@ -40,7 +40,6 @@ final class DiffusionGraphController extends DiffusionController { ->setDiffusionRequest($drequest) ->setHistory($history); - $graph->loadRevisions(); $show_graph = !strlen($drequest->getPath()); if ($show_graph) { $graph->setParents($history_results['parents']); diff --git a/src/applications/diffusion/controller/DiffusionHistoryController.php b/src/applications/diffusion/controller/DiffusionHistoryController.php index f50e73295f..f0fec0dd2d 100644 --- a/src/applications/diffusion/controller/DiffusionHistoryController.php +++ b/src/applications/diffusion/controller/DiffusionHistoryController.php @@ -40,7 +40,6 @@ final class DiffusionHistoryController extends DiffusionController { ->setDiffusionRequest($drequest) ->setHistory($history); - $history_list->loadRevisions(); $header = $this->buildHeader($drequest); $crumbs = $this->buildCrumbs( diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php index 268eedbcc7..12aaefe12a 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php @@ -334,6 +334,8 @@ final class DiffusionRepositoryController extends DiffusionController { 'fa-clock-o', 'indigo', pht('Importing (%s)...', $percentage)); + } else if ($repository->isPublishingDisabled()) { + $header->setStatus('fa-minus', 'bluegrey', pht('Publishing Disabled')); } else { $header->setStatus('fa-check', 'bluegrey', pht('Active')); } @@ -438,17 +440,13 @@ final class DiffusionRepositoryController extends DiffusionController { $history_table = id(new DiffusionHistoryTableView()) ->setUser($viewer) ->setDiffusionRequest($drequest) - ->setHistory($history); - - // TODO: Super sketchy. - $history_table->loadRevisions(); + ->setHistory($history) + ->setIsHead(true); if ($history_results) { $history_table->setParents($history_results['parents']); } - $history_table->setIsHead(true); - $panel = id(new PHUIObjectBoxView()) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addClass('diffusion-mobile-view'); diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditPublishingController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditPublishingController.php new file mode 100644 index 0000000000..3d55a5c2b1 --- /dev/null +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditPublishingController.php @@ -0,0 +1,83 @@ +loadDiffusionContextForEdit(); + if ($response) { + return $response; + } + + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + + $panel_uri = id(new DiffusionRepositoryBasicsManagementPanel()) + ->setRepository($repository) + ->getPanelURI(); + + if ($request->isFormPost()) { + if ($repository->isPublishingDisabled()) { + $new_status = true; + } else { + $new_status = false; + } + + $xaction = id(new PhabricatorRepositoryTransaction()) + ->setTransactionType( + PhabricatorRepositoryNotifyTransaction::TRANSACTIONTYPE) + ->setNewValue($new_status); + + $editor = id(new PhabricatorRepositoryEditor()) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true) + ->setContentSourceFromRequest($request) + ->setActor($viewer) + ->applyTransactions($repository, array($xaction)); + + return id(new AphrontReloadResponse())->setURI($panel_uri); + } + + $body = array(); + if (!$repository->isPublishingDisabled()) { + $title = pht('Disable Publishing'); + $body[] = pht( + 'If you disable publishing for this repository, new commits '. + 'will not: send email, publish feed stories, trigger audits, or '. + 'trigger Herald.'); + + $body[] = pht( + 'This option is most commonly used to temporarily allow a major '. + 'repository maintenance operation (like a history rewrite) to '. + 'occur with minimal disruption to users.'); + + $submit = pht('Disable Publishing'); + } else { + $title = pht('Reactivate Publishing'); + $body[] = pht( + 'If you reactivate publishing for this repository, new commits '. + 'that become reachable from permanent refs will: send email, '. + 'publish feed stories, trigger audits, and trigger Herald.'); + + $body[] = pht( + 'Commits which became reachable from a permanent ref while '. + 'publishing was disabled will not trigger these actions '. + 'retroactively.'); + + $submit = pht('Reactivate Publishing'); + } + + $dialog = $this->newDialog() + ->setTitle($title) + ->addSubmitButton($submit) + ->addCancelButton($panel_uri); + + foreach ($body as $graph) { + $dialog->appendParagraph($graph); + } + + return $dialog; + } + +} diff --git a/src/applications/diffusion/editor/DiffusionCommitEditEngine.php b/src/applications/diffusion/editor/DiffusionCommitEditEngine.php index 667af46550..84a389a421 100644 --- a/src/applications/diffusion/editor/DiffusionCommitEditEngine.php +++ b/src/applications/diffusion/editor/DiffusionCommitEditEngine.php @@ -113,30 +113,17 @@ final class DiffusionCommitEditEngine ->setConduitTypeDescription(pht('New auditors.')) ->setValue($object->getAuditorPHIDsForEdit()); - $reason = $data->getCommitDetail('autocloseReason', false); - if ($reason !== false) { - switch ($reason) { - case PhabricatorRepository::BECAUSE_REPOSITORY_IMPORTING: - $desc = pht('No, Repository Importing'); - break; - case PhabricatorRepository::BECAUSE_AUTOCLOSE_DISABLED: - $desc = pht('No, Autoclose Disabled'); - break; - case PhabricatorRepository::BECAUSE_NOT_ON_AUTOCLOSE_BRANCH: - $desc = pht('No, Not On Autoclose Branch'); - break; - case PhabricatorRepository::BECAUSE_AUTOCLOSE_FORCED: - $desc = pht('Yes, Forced Via bin/repository CLI Tool.'); - break; - case null: - $desc = pht('Yes'); - break; - default: - $desc = pht('Unknown'); - break; + $holds = $data->getPublisherHoldReasons(); + if ($holds) { + $hold_names = array(); + foreach ($holds as $hold) { + $hold_names[] = id(new PhabricatorRepositoryPublisher()) + ->getHoldName($hold); } + $desc = implode('; ', $hold_names); - $doc_href = PhabricatorEnv::getDoclink('Diffusion User Guide: Autoclose'); + $doc_href = PhabricatorEnv::getDoclink( + 'Diffusion User Guide: Permanent Refs'); $doc_link = phutil_tag( 'a', array( @@ -146,7 +133,7 @@ final class DiffusionCommitEditEngine pht('Learn More')); $fields[] = id(new PhabricatorStaticEditField()) - ->setLabel(pht('Autoclose?')) + ->setLabel(pht('Unpublished')) ->setValue(array($desc, " \xC2\xB7 ", $doc_link)); } diff --git a/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php b/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php index 8907a22ce5..a026e176fc 100644 --- a/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php +++ b/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php @@ -212,8 +212,9 @@ final class DiffusionRepositoryEditEngine ->setObject($object) ->execute(); + $fetch_value = $object->getFetchRules(); $track_value = $object->getTrackOnlyRules(); - $autoclose_value = $object->getAutocloseOnlyRules(); + $permanent_value = $object->getPermanentRefRules(); $automation_instructions = pht( "Configure **Repository Automation** to allow Phabricator to ". @@ -236,6 +237,27 @@ final class DiffusionRepositoryEditEngine 'you can set a path in **Import Only**. Phabricator will ignore '. 'commits which do not affect this path.'); + $filesize_warning = null; + if ($object->isGit()) { + $git_binary = PhutilBinaryAnalyzer::getForBinary('git'); + $git_version = $git_binary->getBinaryVersion(); + $filesize_version = '1.8.4'; + if (version_compare($git_version, $filesize_version, '<')) { + $filesize_warning = pht( + '(WARNING) {icon exclamation-triangle} The version of "git" ("%s") '. + 'installed on this server does not support '. + '"--batch-check=", a feature required to enforce filesize '. + 'limits. Upgrade to "git" %s or newer to use this feature.', + $git_version, + $filesize_version); + } + } + + $track_instructions = pht( + 'WARNING: The "Track Only" feature is deprecated. Use "Fetch Refs" '. + 'and "Permanent Refs" instead. This feature will be removed in a '. + 'future version of Phabricator.'); + return array( id(new PhabricatorSelectEditField()) ->setKey('vcs') @@ -348,6 +370,28 @@ final class DiffusionRepositoryEditEngine ->setConduitDescription(pht('Set the default branch name.')) ->setConduitTypeDescription(pht('New default branch name.')) ->setValue($object->getDetail('default-branch')), + id(new PhabricatorTextAreaEditField()) + ->setIsStringList(true) + ->setKey('fetchRefs') + ->setLabel(pht('Fetch Refs')) + ->setTransactionType( + PhabricatorRepositoryFetchRefsTransaction::TRANSACTIONTYPE) + ->setIsCopyable(true) + ->setDescription(pht('Fetch only these refs.')) + ->setConduitDescription(pht('Set the fetched refs.')) + ->setConduitTypeDescription(pht('New fetched refs.')) + ->setValue($fetch_value), + id(new PhabricatorTextAreaEditField()) + ->setIsStringList(true) + ->setKey('permanentRefs') + ->setLabel(pht('Permanent Refs')) + ->setTransactionType( + PhabricatorRepositoryPermanentRefsTransaction::TRANSACTIONTYPE) + ->setIsCopyable(true) + ->setDescription(pht('Only these refs are considered permanent.')) + ->setConduitDescription(pht('Set the permanent refs.')) + ->setConduitTypeDescription(pht('New permanent ref rules.')) + ->setValue($permanent_value), id(new PhabricatorTextAreaEditField()) ->setIsStringList(true) ->setKey('trackOnly') @@ -355,21 +399,11 @@ final class DiffusionRepositoryEditEngine ->setTransactionType( PhabricatorRepositoryTrackOnlyTransaction::TRANSACTIONTYPE) ->setIsCopyable(true) + ->setControlInstructions($track_instructions) ->setDescription(pht('Track only these branches.')) ->setConduitDescription(pht('Set the tracked branches.')) ->setConduitTypeDescription(pht('New tracked branches.')) ->setValue($track_value), - id(new PhabricatorTextAreaEditField()) - ->setIsStringList(true) - ->setKey('autocloseOnly') - ->setLabel(pht('Autoclose Only')) - ->setTransactionType( - PhabricatorRepositoryAutocloseOnlyTransaction::TRANSACTIONTYPE) - ->setIsCopyable(true) - ->setDescription(pht('Autoclose commits on only these branches.')) - ->setConduitDescription(pht('Set the autoclose branches.')) - ->setConduitTypeDescription(pht('New default tracked branches.')) - ->setValue($autoclose_value), id(new PhabricatorTextEditField()) ->setKey('importOnly') ->setLabel(pht('Import Only')) @@ -440,20 +474,7 @@ final class DiffusionRepositoryEditEngine ->setDescription(pht('Configure how changes are published.')) ->setConduitDescription(pht('Change publishing options.')) ->setConduitTypeDescription(pht('New notification setting.')) - ->setValue(!$object->getDetail('herald-disabled')), - id(new PhabricatorBoolEditField()) - ->setKey('autoclose') - ->setLabel(pht('Autoclose')) - ->setTransactionType( - PhabricatorRepositoryAutocloseTransaction::TRANSACTIONTYPE) - ->setIsCopyable(true) - ->setOptions( - pht('Disable Autoclose'), - pht('Enable Autoclose')) - ->setDescription(pht('Stop or resume autoclosing in this repository.')) - ->setConduitDescription(pht('Change autoclose setting.')) - ->setConduitTypeDescription(pht('New autoclose setting.')) - ->setValue(!$object->getDetail('disable-autoclose')), + ->setValue(!$object->isPublishingDisabled()), id(new PhabricatorPolicyEditField()) ->setKey('policy.push') ->setLabel(pht('Push Policy')) @@ -477,6 +498,7 @@ final class DiffusionRepositoryEditEngine ->setDescription(pht('Maximum permitted file size.')) ->setConduitDescription(pht('Change the filesize limit.')) ->setConduitTypeDescription(pht('New repository filesize limit.')) + ->setControlInstructions($filesize_warning) ->setValue($object->getFilesizeLimit()), id(new PhabricatorTextEditField()) ->setKey('copyTimeLimit') diff --git a/src/applications/diffusion/engine/DiffusionCommitHookEngine.php b/src/applications/diffusion/engine/DiffusionCommitHookEngine.php index 6856b8676d..6ebaf6bdc7 100644 --- a/src/applications/diffusion/engine/DiffusionCommitHookEngine.php +++ b/src/applications/diffusion/engine/DiffusionCommitHookEngine.php @@ -459,10 +459,7 @@ final class DiffusionCommitHookEngine extends Phobject { $ref_type = PhabricatorRepositoryPushLog::REFTYPE_TAG; $ref_raw = substr($ref_raw, strlen('refs/tags/')); } else { - throw new Exception( - pht( - "Unable to identify the reftype of '%s'. Rejecting push.", - $ref_raw)); + $ref_type = PhabricatorRepositoryPushLog::REFTYPE_REF; } $ref_update = $this->newPushLog() diff --git a/src/applications/diffusion/herald/DiffusionAuditorsAddAuditorsHeraldAction.php b/src/applications/diffusion/herald/DiffusionAuditorsAddAuditorsHeraldAction.php index 36b01b96f8..f895847d4a 100644 --- a/src/applications/diffusion/herald/DiffusionAuditorsAddAuditorsHeraldAction.php +++ b/src/applications/diffusion/herald/DiffusionAuditorsAddAuditorsHeraldAction.php @@ -30,4 +30,8 @@ final class DiffusionAuditorsAddAuditorsHeraldAction return pht('Add auditors: %s.', $this->renderHandleList($value)); } + public function getPHIDsAffectedByAction(HeraldActionRecord $record) { + return $record->getTarget(); + } + } diff --git a/src/applications/diffusion/herald/DiffusionCommitAutocloseHeraldField.php b/src/applications/diffusion/herald/DiffusionCommitAutocloseHeraldField.php index 674b023582..787446ee6a 100644 --- a/src/applications/diffusion/herald/DiffusionCommitAutocloseHeraldField.php +++ b/src/applications/diffusion/herald/DiffusionCommitAutocloseHeraldField.php @@ -5,12 +5,18 @@ final class DiffusionCommitAutocloseHeraldField const FIELDCONST = 'diffusion.commit.autoclose'; + public function getFieldGroupKey() { + return HeraldDeprecatedFieldGroup::FIELDGROUPKEY; + } + public function getHeraldFieldName() { - return pht('Commit is on autoclose branch'); + // Herald no longer triggers until a commit is reachable from a permanent + // ref, so this condition is always true by definition. + return pht('Commit Autocloses (Deprecated)'); } public function getHeraldFieldValue($object) { - return $object->getRepository()->shouldAutocloseCommit($object); + return true; } public function getHeraldFieldConditions() { diff --git a/src/applications/diffusion/herald/DiffusionPreCommitRefTypeHeraldField.php b/src/applications/diffusion/herald/DiffusionPreCommitRefTypeHeraldField.php index ff71ae8713..0c18f58712 100644 --- a/src/applications/diffusion/herald/DiffusionPreCommitRefTypeHeraldField.php +++ b/src/applications/diffusion/herald/DiffusionPreCommitRefTypeHeraldField.php @@ -24,6 +24,7 @@ final class DiffusionPreCommitRefTypeHeraldField $types = array( PhabricatorRepositoryPushLog::REFTYPE_BRANCH => pht('branch (git/hg)'), PhabricatorRepositoryPushLog::REFTYPE_TAG => pht('tag (git)'), + PhabricatorRepositoryPushLog::REFTYPE_REF => pht('ref (git)'), PhabricatorRepositoryPushLog::REFTYPE_BOOKMARK => pht('bookmark (hg)'), ); diff --git a/src/applications/diffusion/management/DiffusionRepositoryActionsManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryActionsManagementPanel.php deleted file mode 100644 index eb1b98dba7..0000000000 --- a/src/applications/diffusion/management/DiffusionRepositoryActionsManagementPanel.php +++ /dev/null @@ -1,86 +0,0 @@ -getRepository(); - - $has_any = - $repository->getDetail('herald-disabled') || - $repository->getDetail('disable-autoclose'); - - // NOTE: Any value here really means something is disabled, so try to - // hint that a little bit with the icon. - - if ($has_any) { - return 'fa-flash'; - } else { - return 'fa-flash grey'; - } - } - - protected function getEditEngineFieldKeys() { - return array( - 'publish', - 'autoclose', - ); - } - - public function buildManagementPanelCurtain() { - $repository = $this->getRepository(); - $viewer = $this->getViewer(); - $action_list = $this->newActionList(); - - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $repository, - PhabricatorPolicyCapability::CAN_EDIT); - - $actions_uri = $this->getEditPageURI(); - - $action_list->addAction( - id(new PhabricatorActionView()) - ->setIcon('fa-pencil') - ->setName(pht('Edit Actions')) - ->setHref($actions_uri) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit)); - - return $this->newCurtainView() - ->setActionList($action_list); - } - - public function buildManagementPanelContent() { - $repository = $this->getRepository(); - $viewer = $this->getViewer(); - - $view = id(new PHUIPropertyListView()) - ->setViewer($viewer); - - $notify = $repository->getDetail('herald-disabled') - ? pht('Off') - : pht('On'); - $notify = phutil_tag('em', array(), $notify); - $view->addProperty(pht('Publish/Notify'), $notify); - - $autoclose = $repository->getDetail('disable-autoclose') - ? pht('Off') - : pht('On'); - $autoclose = phutil_tag('em', array(), $autoclose); - $view->addProperty(pht('Autoclose'), $autoclose); - - return $this->newBox(pht('Actions'), $view); - } - -} diff --git a/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php index d585c5774d..2d006136e3 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php @@ -46,6 +46,7 @@ final class DiffusionRepositoryBasicsManagementPanel $dangerous_uri = $repository->getPathURI('edit/dangerous/'); $enormous_uri = $repository->getPathURI('edit/enormous/'); $update_uri = $repository->getPathURI('edit/update/'); + $publish_uri = $repository->getPathURI('edit/publish/'); if ($repository->isTracked()) { $activate_icon = 'fa-ban'; @@ -55,6 +56,14 @@ final class DiffusionRepositoryBasicsManagementPanel $activate_label = pht('Activate Repository'); } + if (!$repository->isPublishingDisabled()) { + $publish_icon = 'fa-ban'; + $publish_label = pht('Disable Publishing'); + } else { + $publish_icon = 'fa-check'; + $publish_label = pht('Enable Publishing'); + } + $should_dangerous = $repository->shouldAllowDangerousChanges(); if ($should_dangerous) { $dangerous_icon = 'fa-shield'; @@ -109,6 +118,10 @@ final class DiffusionRepositoryBasicsManagementPanel ->setDisabled(!$can_enormous) ->setWorkflow(true)); + $action_list->addAction( + id(new PhabricatorActionView()) + ->setType(PhabricatorActionView::TYPE_DIVIDER)); + $action_list->addAction( id(new PhabricatorActionView()) ->setName($activate_label) @@ -117,6 +130,14 @@ final class DiffusionRepositoryBasicsManagementPanel ->setDisabled(!$can_edit) ->setWorkflow(true)); + $action_list->addAction( + id(new PhabricatorActionView()) + ->setName($publish_label) + ->setHref($publish_uri) + ->setIcon($publish_icon) + ->setDisabled(!$can_edit) + ->setWorkflow(true)); + $action_list->addAction( id(new PhabricatorActionView()) ->setName(pht('Update Now')) @@ -147,6 +168,9 @@ final class DiffusionRepositoryBasicsManagementPanel $basics = $this->newBox(pht('Properties'), $basics); $repository = $this->getRepository(); + + $state = $this->buildStateView($repository); + $is_new = $repository->isNewlyInitialized(); $info_view = null; if ($is_new) { @@ -180,7 +204,7 @@ final class DiffusionRepositoryBasicsManagementPanel } $status = $this->buildStatus(); - return array($info_view, $basics, $description, $status); + return array($info_view, $state, $basics, $description, $status); } private function buildBasics() { @@ -287,7 +311,7 @@ final class DiffusionRepositoryBasicsManagementPanel $view->addTextContent($raw_error); } - return $this->newBox(pht('Status'), $view); + return $this->newBox(pht('Working Copy Status'), $view); } private function buildRepositoryUpdateInterval( @@ -720,4 +744,81 @@ final class DiffusionRepositoryBasicsManagementPanel 'environment.append-paths'); } + private function buildStateView(PhabricatorRepository $repository) { + $viewer = $this->getViewer(); + $is_new = $repository->isNewlyInitialized(); + + $view = id(new PHUIPropertyListView()) + ->setViewer($viewer); + + if (!$repository->isTracked()) { + if ($is_new) { + $active_icon = 'fa-ban'; + $active_color = 'yellow'; + $active_label = pht('Not Activated Yet'); + $active_note = pht('Complete Setup and Activate Repository'); + } else { + $active_icon = 'fa-times'; + $active_color = 'red'; + $active_label = pht('Not Active'); + $active_note = pht('Repository Disabled'); + } + } else if ($repository->isImporting()) { + $active_icon = 'fa-hourglass'; + $active_color = 'yellow'; + $active_label = pht('Importing...'); + $active_note = null; + } else { + $active_icon = 'fa-check'; + $active_color = 'green'; + $active_label = pht('Repository Active'); + $active_note = null; + } + + $active_view = id(new PHUIStatusListView()) + ->addItem( + id(new PHUIStatusItemView()) + ->setIcon($active_icon, $active_color) + ->setTarget($active_label) + ->setNote($active_note)); + + if ($repository->isPublishingDisabled()) { + $publishing_icon = 'fa-times'; + $publishing_color = 'red'; + $publishing_label = pht('Not Publishing'); + $publishing_note = pht('Publishing Disabled'); + } else if (!$repository->isTracked()) { + $publishing_icon = 'fa-ban'; + $publishing_color = 'yellow'; + $publishing_label = pht('Not Publishing'); + if ($is_new) { + $publishing_note = pht('Repository Not Active Yet'); + } else { + $publishing_note = pht('Repository Inactive'); + } + } else if ($repository->isImporting()) { + $publishing_icon = 'fa-ban'; + $publishing_color = 'yellow'; + $publishing_label = pht('Not Publishing'); + $publishing_note = pht('Repository Importing'); + } else { + $publishing_icon = 'fa-check'; + $publishing_color = 'green'; + $publishing_label = pht('Publishing Active'); + $publishing_note = null; + } + + $publishing_view = id(new PHUIStatusListView()) + ->addItem( + id(new PHUIStatusItemView()) + ->setIcon($publishing_icon, $publishing_color) + ->setTarget($publishing_label) + ->setNote($publishing_note)); + + $view->addProperty(pht('Active'), $active_view); + $view->addProperty(pht('Publishing'), $publishing_view); + + return $this->newBox(pht('State'), $view); + } + } diff --git a/src/applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php index bdd0a3f71d..b2e382652f 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php @@ -24,7 +24,7 @@ final class DiffusionRepositoryBranchesManagementPanel $has_any = $repository->getDetail('default-branch') || $repository->getTrackOnlyRules() || - $repository->getAutocloseOnlyRules(); + $repository->getPermanentRefRules(); if ($has_any) { return 'fa-code-fork'; @@ -36,8 +36,9 @@ final class DiffusionRepositoryBranchesManagementPanel protected function getEditEngineFieldKeys() { return array( 'defaultBranch', + 'fetchRefs', + 'permanentRefs', 'trackOnly', - 'autocloseOnly', ); } @@ -78,31 +79,38 @@ final class DiffusionRepositoryBranchesManagementPanel phutil_tag('em', array(), $repository->getDefaultBranch())); $view->addProperty(pht('Default Branch'), $default_branch); - $track_only_rules = $repository->getTrackOnlyRules(); - $track_only_rules = implode(', ', $track_only_rules); - $track_only = nonempty( - $track_only_rules, - phutil_tag('em', array(), pht('Track All Branches'))); - $view->addProperty(pht('Track Only'), $track_only); - - $autoclose_rules = $repository->getAutocloseOnlyRules(); - $autoclose_rules = implode(', ', $autoclose_rules); - $autoclose_only = nonempty( - $autoclose_rules, - phutil_tag('em', array(), pht('Autoclose On All Branches'))); - - $autoclose_disabled = false; - if ($repository->getDetail('disable-autoclose')) { - $autoclose_disabled = true; - $autoclose_only = - phutil_tag('em', array(), pht('Autoclose has been disabled')); + if ($repository->supportsFetchRules()) { + $fetch_only = $repository->getFetchRules(); + if ($fetch_only) { + $fetch_display = implode(', ', $fetch_only); + } else { + $fetch_display = phutil_tag('em', array(), pht('Fetch All Refs')); + } + $view->addProperty(pht('Fetch Refs'), $fetch_display); } - $view->addProperty(pht('Autoclose Only'), $autoclose_only); + $track_only_rules = $repository->getTrackOnlyRules(); + if ($track_only_rules) { + $track_only_rules = implode(', ', $track_only_rules); + $view->addProperty(pht('Track Only'), $track_only_rules); + } + + $publishing_disabled = $repository->isPublishingDisabled(); + if ($publishing_disabled) { + $permanent_display = + phutil_tag('em', array(), pht('Publishing Disabled')); + } else { + $permanent_rules = $repository->getPermanentRefRules(); + if ($permanent_rules) { + $permanent_display = implode(', ', $permanent_rules); + } else { + $permanent_display = phutil_tag('em', array(), pht('All Branches')); + } + } + $view->addProperty(pht('Permanent Refs'), $permanent_display); $content[] = $this->newBox(pht('Branches'), $view); - // Branch Autoclose Table if (!$repository->isImporting()) { $request = $this->getRequest(); $pager = id(new PHUIPagerView()) @@ -121,11 +129,12 @@ final class DiffusionRepositoryBranchesManagementPanel $branches = $pager->sliceResults($branches); $can_close_branches = ($repository->isHg()); + $publisher = $repository->newPublisher(); + $rows = array(); foreach ($branches as $branch) { $branch_name = $branch->getShortName(); - $tracking = $repository->shouldTrackBranch($branch_name); - $autoclosing = $repository->shouldAutocloseBranch($branch_name); + $permanent = $publisher->shouldPublishRef($branch); $default = $repository->getDefaultBranch(); $icon = null; @@ -142,18 +151,21 @@ final class DiffusionRepositoryBranchesManagementPanel $status = pht('Open'); } - if ($autoclose_disabled) { - $autoclose_status = pht('Disabled (Repository)'); + if ($publishing_disabled) { + $permanent_status = pht('Publishing Disabled'); } else { - $autoclose_status = pht('Off'); + if ($permanent) { + $permanent_status = pht('Permanent'); + } else { + $permanent_status = pht('Not Permanent'); + } } $rows[] = array( $icon, $branch_name, $status, - $tracking ? pht('Tracking') : pht('Off'), - $autoclosing ? pht('Autoclose On') : $autoclose_status, + $permanent_status, ); } $branch_table = new AphrontTableView($rows); @@ -162,15 +174,13 @@ final class DiffusionRepositoryBranchesManagementPanel '', pht('Branch'), pht('Status'), - pht('Track'), - pht('Autoclose'), + pht('Permanent'), )); $branch_table->setColumnClasses( array( '', 'pri', 'narrow', - 'narrow', 'wide', )); $branch_table->setColumnVisibility( @@ -179,7 +189,6 @@ final class DiffusionRepositoryBranchesManagementPanel true, $can_close_branches, true, - true, )); $box = $this->newBox(pht('Branch Status'), $branch_table); @@ -188,8 +197,10 @@ final class DiffusionRepositoryBranchesManagementPanel } else { $content[] = id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) - ->appendChild(pht('Branch status in unavailable while the repository '. - 'is still importing.')); + ->appendChild( + pht( + 'Branch status is unavailable while the repository is still '. + 'importing.')); } return $content; diff --git a/src/applications/diffusion/protocol/DiffusionGitUploadPackWireProtocol.php b/src/applications/diffusion/protocol/DiffusionGitUploadPackWireProtocol.php new file mode 100644 index 0000000000..451ad74c9e --- /dev/null +++ b/src/applications/diffusion/protocol/DiffusionGitUploadPackWireProtocol.php @@ -0,0 +1,325 @@ +readBuffer === null) { + $this->readBuffer = new PhutilRope(); + } + $buffer = $this->readBuffer; + + $buffer->append($bytes); + + while (true) { + $len = $buffer->getByteLength(); + switch ($this->readMode) { + case 'length': + // We're expecting 4 bytes containing the length of the protocol + // frame as hexadecimal in ASCII text, like "01ab". Wait until we + // see at least 4 bytes on the wire. + if ($len < 4) { + if ($len > 0) { + $bytes = $this->peekBytes($len); + if (!preg_match('/^[0-9a-f]+\z/', $bytes)) { + throw new Exception( + pht( + 'Bad frame length character in Git protocol ("%s"), '. + 'expected a 4-digit hexadecimal value encoded as ASCII '. + 'text.', + $bytes)); + } + } + + // We can't make any more progress until we get enough bytes, so + // we're done with state processing. + break 2; + } + + $frame_length = $this->readBytes(4); + $frame_length = hexdec($frame_length); + + // Note that the frame length includes the 4 header bytes, so we + // usually expect a length of 5 or larger. Frames with length 0 + // are boundaries. + if ($frame_length === 0) { + $this->readFrames[] = $this->newProtocolFrame('null', ''); + } else if ($frame_length >= 1 && $frame_length <= 3) { + throw new Exception( + pht( + 'Encountered Git protocol frame with unexpected frame '. + 'length (%s)!', + $frame_length)); + } else { + $this->readFrameLength = $frame_length - 4; + $this->readMode = 'frame'; + } + + break; + case 'frame': + // We're expecting a protocol frame of a specified length. Note that + // it is possible for a frame to have length 0. + + // We don't have enough bytes yet, so wait for more. + if ($len < $this->readFrameLength) { + break 2; + } + + if ($this->readFrameLength > 0) { + $bytes = $this->readBytes($this->readFrameLength); + } else { + $bytes = ''; + } + + // Emit a protocol frame. + $this->readFrames[] = $this->newProtocolFrame('data', $bytes); + $this->readMode = 'length'; + break; + } + } + + while (true) { + switch ($this->readFrameMode) { + case 'refs': + if (!$this->readFrames) { + break 2; + } + + foreach ($this->readFrames as $key => $frame) { + unset($this->readFrames[$key]); + + if ($frame['type'] === 'null') { + $ref_frames = $this->refFrames; + $this->refFrames = array(); + + $ref_frames[] = $frame; + + $this->readMessages[] = $this->newProtocolRefMessage($ref_frames); + $this->readFrameMode = 'passthru'; + break; + } else { + $this->refFrames[] = $frame; + } + } + + break; + case 'passthru': + if (!$this->readFrames) { + break 2; + } + + $this->readMessages[] = $this->newProtocolDataMessage( + $this->readFrames); + $this->readFrames = array(); + + break; + } + } + + $wire = array(); + foreach ($this->readMessages as $key => $message) { + $wire[] = $message; + unset($this->readMessages[$key]); + } + $wire = implode('', $wire); + + return $wire; + } + + public function willWriteBytes($bytes) { + return $bytes; + } + + private function readBytes($count) { + $buffer = $this->readBuffer; + + $bytes = $buffer->getPrefixBytes($count); + $buffer->removeBytesFromHead($count); + + return $bytes; + } + + private function peekBytes($count) { + $buffer = $this->readBuffer; + return $buffer->getPrefixBytes($count); + } + + private function newProtocolFrame($type, $bytes) { + return array( + 'type' => $type, + 'length' => strlen($bytes), + 'bytes' => $bytes, + ); + } + + private function newProtocolRefMessage(array $frames) { + $head_key = head_key($frames); + $last_key = last_key($frames); + + $capabilities = null; + $last_frame = null; + + $refs = array(); + foreach ($frames as $key => $frame) { + $is_last = ($key === $last_key); + if ($is_last) { + // This is a "0000" frame at the end of the list of refs, so we pass + // it through unmodified after we figure out what the rest of the + // frames should look like, below. + $last_frame = $frame; + continue; + } + + $is_first = ($key === $head_key); + + // Otherwise, we expect a list of: + // + // \0 + // + // ... + + $bytes = $frame['bytes']; + $matches = array(); + if ($is_first) { + $ok = preg_match( + '('. + '^'. + '(?P[0-9a-f]{40})'. + ' '. + '(?P[^\0\n]+)'. + '\0'. + '(?P[^\n]+)'. + '\n'. + '\z'. + ')', + $bytes, + $matches); + if (!$ok) { + throw new Exception( + pht( + 'Unexpected "git upload-pack" initial protocol frame: expected '. + '" \0\n", got "%s".', + $bytes)); + } + } else { + $ok = preg_match( + '('. + '^'. + '(?P[0-9a-f]{40})'. + ' '. + '(?P[^\0\n]+)'. + '\n'. + '\z'. + ')', + $bytes, + $matches); + if (!$ok) { + throw new Exception( + pht( + 'Unexpected "git upload-pack" protocol frame: expected '. + '" \n", got "%s".', + $bytes)); + } + } + + $hash = $matches['hash']; + $name = $matches['name']; + + if ($is_first) { + $capabilities = $matches['capabilities']; + } + + $refs[] = array( + 'hash' => $hash, + 'name' => $name, + ); + } + + $capabilities = DiffusionGitWireProtocolCapabilities::newFromWireFormat( + $capabilities); + + $ref_list = id(new DiffusionGitWireProtocolRefList()) + ->setCapabilities($capabilities); + + foreach ($refs as $ref) { + $ref_list->addRef( + id(new DiffusionGitWireProtocolRef()) + ->setName($ref['name']) + ->setHash($ref['hash'])); + } + + // TODO: Here, we have a structured list of refs. In a future change, + // we are free to mutate the structure before flattening it back into + // wire format. + + $refs = $ref_list->getRefs(); + + // Before we write the ref list, sort it for consistency with native + // Git output. We may have added, removed, or renamed refs and ended up + // with an out-of-order list. + + $refs = msortv($refs, 'newSortVector'); + + // The first ref we send back includes the capabilities data. Note that if + // we send back no refs, we also don't send back capabilities! This is + // a little surprising, but is consistent with the native behavior of the + // protocol. + + $output = array(); + $is_first = true; + foreach ($refs as $ref) { + if ($is_first) { + $result = sprintf( + "%s %s\0%s\n", + $ref->getHash(), + $ref->getName(), + $ref_list->getCapabilities()->toWireFormat()); + } else { + $result = sprintf( + "%s %s\n", + $ref->getHash(), + $ref->getName()); + } + + $output[] = $this->newProtocolFrame('data', $result); + $is_first = false; + } + + $output[] = $last_frame; + + return $this->newProtocolDataMessage($output); + } + + private function newProtocolDataMessage(array $frames) { + $message = array(); + + foreach ($frames as $frame) { + switch ($frame['type']) { + case 'null': + $message[] = '0000'; + break; + case 'data': + $message[] = sprintf( + '%04x%s', + $frame['length'] + 4, + $frame['bytes']); + break; + } + } + + $message = implode('', $message); + + return $message; + } + +} diff --git a/src/applications/diffusion/protocol/DiffusionGitWireProtocol.php b/src/applications/diffusion/protocol/DiffusionGitWireProtocol.php new file mode 100644 index 0000000000..9ea8d96cf4 --- /dev/null +++ b/src/applications/diffusion/protocol/DiffusionGitWireProtocol.php @@ -0,0 +1,19 @@ +protocolLog = $protocol_log; + return $this; + } + + final public function getProtocolLog() { + return $this->protocolLog; + } + + abstract public function willReadBytes($bytes); + abstract public function willWriteBytes($bytes); + +} diff --git a/src/applications/diffusion/protocol/DiffusionGitWireProtocolCapabilities.php b/src/applications/diffusion/protocol/DiffusionGitWireProtocolCapabilities.php new file mode 100644 index 0000000000..865a061484 --- /dev/null +++ b/src/applications/diffusion/protocol/DiffusionGitWireProtocolCapabilities.php @@ -0,0 +1,18 @@ +raw = $raw; + return $capabilities; + } + + public function toWireFormat() { + return $this->raw; + } + +} diff --git a/src/applications/diffusion/protocol/DiffusionGitWireProtocolRef.php b/src/applications/diffusion/protocol/DiffusionGitWireProtocolRef.php new file mode 100644 index 0000000000..bf5238c219 --- /dev/null +++ b/src/applications/diffusion/protocol/DiffusionGitWireProtocolRef.php @@ -0,0 +1,32 @@ +name = $name; + return $this; + } + + public function getName() { + return $this->name; + } + + public function setHash($hash) { + $this->hash = $hash; + return $this; + } + + public function getHash() { + return $this->hash; + } + + public function newSortVector() { + return id(new PhutilSortVector()) + ->addString($this->getName()); + } + +} diff --git a/src/applications/diffusion/protocol/DiffusionGitWireProtocolRefList.php b/src/applications/diffusion/protocol/DiffusionGitWireProtocolRefList.php new file mode 100644 index 0000000000..f4b2b70000 --- /dev/null +++ b/src/applications/diffusion/protocol/DiffusionGitWireProtocolRefList.php @@ -0,0 +1,28 @@ +capabilities = $capabilities; + return $this; + } + + public function getCapabilities() { + return $this->capabilities; + } + + public function addRef(DiffusionGitWireProtocolRef $ref) { + $this->refs[] = $ref; + return $this; + } + + public function getRefs() { + return $this->refs; + } + +} diff --git a/src/applications/diffusion/query/DiffusionCommitRevisionQuery.php b/src/applications/diffusion/query/DiffusionCommitRevisionQuery.php new file mode 100644 index 0000000000..a73db18a59 --- /dev/null +++ b/src/applications/diffusion/query/DiffusionCommitRevisionQuery.php @@ -0,0 +1,49 @@ +withSourcePHIDs($commit_phids) + ->withEdgeTypes( + array( + DiffusionCommitHasRevisionEdgeType::EDGECONST, + )); + $edge_query->execute(); + + $revision_phids = $edge_query->getDestinationPHIDs(); + if (!$revision_phids) { + return array(); + } + + $revisions = id(new DifferentialRevisionQuery()) + ->setViewer($viewer) + ->withPHIDs($revision_phids) + ->execute(); + $revisions = mpull($revisions, null, 'getPHID'); + + $map = array(); + foreach ($commit_phids as $commit_phid) { + $revision_phids = $edge_query->getDestinationPHIDs( + array( + $commit_phid, + )); + + $map[$commit_phid] = array_select_keys($revisions, $revision_phids); + } + + return $map; + } + +} diff --git a/src/applications/diffusion/query/lowlevel/DiffusionLowLevelGitRefQuery.php b/src/applications/diffusion/query/lowlevel/DiffusionLowLevelGitRefQuery.php index 3b9f88d232..d11b658131 100644 --- a/src/applications/diffusion/query/lowlevel/DiffusionLowLevelGitRefQuery.php +++ b/src/applications/diffusion/query/lowlevel/DiffusionLowLevelGitRefQuery.php @@ -33,16 +33,9 @@ final class DiffusionLowLevelGitRefQuery extends DiffusionLowLevelQuery { $prefixes = array(); - if ($repository->isWorkingCopyBare()) { - $branch_prefix = 'refs/heads/'; - } else { - $remote = DiffusionGitBranch::DEFAULT_GIT_REMOTE; - $branch_prefix = 'refs/remotes/'.$remote.'/'; - } - + $branch_prefix = 'refs/heads/'; $tag_prefix = 'refs/tags/'; - if ($with_refs || count($ref_types) > 1) { // If we're loading refs or more than one type of ref, just query // everything. diff --git a/src/applications/diffusion/ssh/DiffusionGitReceivePackSSHWorkflow.php b/src/applications/diffusion/ssh/DiffusionGitReceivePackSSHWorkflow.php index 54220f0776..abf2a4323e 100644 --- a/src/applications/diffusion/ssh/DiffusionGitReceivePackSSHWorkflow.php +++ b/src/applications/diffusion/ssh/DiffusionGitReceivePackSSHWorkflow.php @@ -28,7 +28,8 @@ final class DiffusionGitReceivePackSSHWorkflow extends DiffusionGitSSHWorkflow { ->setRepository($repository) ->setLog($this); - if ($this->shouldProxy()) { + $is_proxy = $this->shouldProxy(); + if ($is_proxy) { $command = $this->getProxyCommand(true); $did_write = false; @@ -51,6 +52,12 @@ final class DiffusionGitReceivePackSSHWorkflow extends DiffusionGitSSHWorkflow { } } + $log = $this->newProtocolLog($is_proxy); + if ($log) { + $this->setProtocolLog($log); + $log->didStartSession($command); + } + $caught = null; try { $err = $this->executeRepositoryCommand($command); @@ -58,6 +65,10 @@ final class DiffusionGitReceivePackSSHWorkflow extends DiffusionGitSSHWorkflow { $caught = $ex; } + if ($log) { + $log->didEndSession(); + } + // We've committed the write (or rejected it), so we can release the lock // without waiting for the client to receive the acknowledgement. if ($did_write) { diff --git a/src/applications/diffusion/ssh/DiffusionGitSSHWorkflow.php b/src/applications/diffusion/ssh/DiffusionGitSSHWorkflow.php index 6bc56d767d..d9cc8063d5 100644 --- a/src/applications/diffusion/ssh/DiffusionGitSSHWorkflow.php +++ b/src/applications/diffusion/ssh/DiffusionGitSSHWorkflow.php @@ -5,6 +5,9 @@ abstract class DiffusionGitSSHWorkflow implements DiffusionRepositoryClusterEngineLogInterface { private $engineLogProperties = array(); + private $protocolLog; + + private $wireProtocol; protected function writeError($message) { // Git assumes we'll add our own newlines. @@ -55,4 +58,74 @@ abstract class DiffusionGitSSHWorkflow $repository->getVersionControlSystem())); } + protected function newPassthruCommand() { + return parent::newPassthruCommand() + ->setWillWriteCallback(array($this, 'willWriteMessageCallback')) + ->setWillReadCallback(array($this, 'willReadMessageCallback')); + } + + protected function newProtocolLog($is_proxy) { + if ($is_proxy) { + return null; + } + + // While developing, do this to write a full protocol log to disk: + // + // return new PhabricatorProtocolLog('/tmp/git-protocol.log'); + + return null; + } + + final protected function getProtocolLog() { + return $this->protocolLog; + } + + final protected function setProtocolLog(PhabricatorProtocolLog $log) { + $this->protocolLog = $log; + } + + final protected function getWireProtocol() { + return $this->wireProtocol; + } + + final protected function setWireProtocol( + DiffusionGitWireProtocol $protocol) { + $this->wireProtocol = $protocol; + return $this; + } + + public function willWriteMessageCallback( + PhabricatorSSHPassthruCommand $command, + $message) { + + $log = $this->getProtocolLog(); + if ($log) { + $log->didWriteBytes($message); + } + + $protocol = $this->getWireProtocol(); + if ($protocol) { + $message = $protocol->willWriteBytes($message); + } + + return $message; + } + + public function willReadMessageCallback( + PhabricatorSSHPassthruCommand $command, + $message) { + + $log = $this->getProtocolLog(); + if ($log) { + $log->didReadBytes($message); + } + + $protocol = $this->getWireProtocol(); + if ($protocol) { + $message = $protocol->willReadBytes($message); + } + + return $message; + } + } diff --git a/src/applications/diffusion/ssh/DiffusionGitUploadPackSSHWorkflow.php b/src/applications/diffusion/ssh/DiffusionGitUploadPackSSHWorkflow.php index e96030f577..7e1f4a4f33 100644 --- a/src/applications/diffusion/ssh/DiffusionGitUploadPackSSHWorkflow.php +++ b/src/applications/diffusion/ssh/DiffusionGitUploadPackSSHWorkflow.php @@ -54,11 +54,31 @@ final class DiffusionGitUploadPackSSHWorkflow extends DiffusionGitSSHWorkflow { $future = id(new ExecFuture('%C', $command)) ->setEnv($this->getEnvironment()); + $log = $this->newProtocolLog($is_proxy); + if ($log) { + $this->setProtocolLog($log); + $log->didStartSession($command); + } + + if (!$is_proxy) { + if (PhabricatorEnv::getEnvConfig('phabricator.show-prototypes')) { + $protocol = new DiffusionGitUploadPackWireProtocol(); + if ($log) { + $protocol->setProtocolLog($log); + } + $this->setWireProtocol($protocol); + } + } + $err = $this->newPassthruCommand() ->setIOChannel($this->getIOChannel()) ->setCommandChannelFromExecFuture($future) ->execute(); + if ($log) { + $log->didEndSession(); + } + if ($err) { $pull_event ->setResultType(PhabricatorRepositoryPullEvent::RESULT_ERROR) diff --git a/src/applications/diffusion/view/DiffusionBranchListView.php b/src/applications/diffusion/view/DiffusionBranchListView.php index a2ba34893e..73ad87041a 100644 --- a/src/applications/diffusion/view/DiffusionBranchListView.php +++ b/src/applications/diffusion/view/DiffusionBranchListView.php @@ -32,7 +32,6 @@ final class DiffusionBranchListView extends DiffusionView { Javelin::initBehavior('phabricator-tooltips'); - $doc_href = PhabricatorEnv::getDoclink('Diffusion User Guide: Autoclose'); $list = id(new PHUIObjectItemListView()) ->setFlush(true) ->addClass('diffusion-history-list') diff --git a/src/applications/diffusion/view/DiffusionBranchTableView.php b/src/applications/diffusion/view/DiffusionBranchTableView.php deleted file mode 100644 index b75228a0ce..0000000000 --- a/src/applications/diffusion/view/DiffusionBranchTableView.php +++ /dev/null @@ -1,165 +0,0 @@ -branches = $branches; - return $this; - } - - public function setCommits(array $commits) { - assert_instances_of($commits, 'PhabricatorRepositoryCommit'); - $this->commits = mpull($commits, null, 'getCommitIdentifier'); - return $this; - } - - public function render() { - $drequest = $this->getDiffusionRequest(); - $current_branch = $drequest->getBranch(); - $repository = $drequest->getRepository(); - $commits = $this->commits; - $viewer = $this->getUser(); - - $buildables = $this->loadBuildables($commits); - $have_builds = false; - - $can_close_branches = ($repository->isHg()); - - Javelin::initBehavior('phabricator-tooltips'); - - $doc_href = PhabricatorEnv::getDoclink('Diffusion User Guide: Autoclose'); - - $rows = array(); - $rowc = array(); - foreach ($this->branches as $branch) { - $commit = idx($commits, $branch->getCommitIdentifier()); - if ($commit) { - $details = $commit->getSummary(); - $datetime = $viewer->formatShortDateTime($commit->getEpoch()); - $buildable = idx($buildables, $commit->getPHID()); - if ($buildable) { - $build_status = $this->renderBuildable($buildable); - $have_builds = true; - } else { - $build_status = null; - } - } else { - $datetime = null; - $details = null; - $build_status = null; - } - - switch ($repository->shouldSkipAutocloseBranch($branch->getShortName())) { - case PhabricatorRepository::BECAUSE_REPOSITORY_IMPORTING: - $icon = 'fa-times bluegrey'; - $tip = pht('Repository Importing'); - break; - case PhabricatorRepository::BECAUSE_AUTOCLOSE_DISABLED: - $icon = 'fa-times bluegrey'; - $tip = pht('Repository Autoclose Disabled'); - break; - case PhabricatorRepository::BECAUSE_BRANCH_UNTRACKED: - $icon = 'fa-times bluegrey'; - $tip = pht('Branch Untracked'); - break; - case PhabricatorRepository::BECAUSE_BRANCH_NOT_AUTOCLOSE: - $icon = 'fa-times bluegrey'; - $tip = pht('Branch Autoclose Disabled'); - break; - case null: - $icon = 'fa-check bluegrey'; - $tip = pht('Autoclose Enabled'); - break; - default: - $icon = 'fa-question'; - $tip = pht('Status Unknown'); - break; - } - - $status_icon = id(new PHUIIconView()) - ->setIcon($icon) - ->addSigil('has-tooltip') - ->setHref($doc_href) - ->setMetadata( - array( - 'tip' => $tip, - 'size' => 200, - )); - - $fields = $branch->getRawFields(); - $closed = idx($fields, 'closed'); - if ($closed) { - $status = pht('Closed'); - } else { - $status = pht('Open'); - } - - $rows[] = array( - $this->linkBranchHistory($branch->getShortName()), - phutil_tag( - 'a', - array( - 'href' => $drequest->generateURI( - array( - 'action' => 'browse', - 'branch' => $branch->getShortName(), - )), - ), - $branch->getShortName()), - self::linkCommit( - $drequest->getRepository(), - $branch->getCommitIdentifier()), - $build_status, - $status, - AphrontTableView::renderSingleDisplayLine($details), - $status_icon, - $datetime, - ); - if ($branch->getShortName() == $current_branch) { - $rowc[] = 'highlighted'; - } else { - $rowc[] = null; - } - } - - $view = new AphrontTableView($rows); - $view->setHeaders( - array( - null, - pht('Branch'), - pht('Head'), - null, - pht('State'), - pht('Details'), - null, - pht('Committed'), - )); - $view->setColumnClasses( - array( - '', - 'pri', - '', - 'icon', - '', - 'wide', - '', - 'right', - )); - $view->setColumnVisibility( - array( - true, - true, - true, - $have_builds, - $can_close_branches, - )); - $view->setRowClasses($rowc); - return $view->render(); - } - - -} diff --git a/src/applications/diffusion/view/DiffusionHistoryListView.php b/src/applications/diffusion/view/DiffusionHistoryListView.php index 62ba7b70c2..cc7bade925 100644 --- a/src/applications/diffusion/view/DiffusionHistoryListView.php +++ b/src/applications/diffusion/view/DiffusionHistoryListView.php @@ -104,16 +104,17 @@ final class DiffusionHistoryListView extends DiffusionHistoryView { $diff_tag = null; if ($show_revisions && $commit) { - $d_id = idx($this->getRevisions(), $commit->getPHID()); - if ($d_id) { + $revisions = $this->getRevisionsForCommit($commit); + if ($revisions) { + $revision = head($revisions); $diff_tag = id(new PHUITagView()) - ->setName('D'.$d_id) + ->setName($revision->getMonogram()) ->setType(PHUITagView::TYPE_SHADE) ->setColor(PHUITagView::COLOR_BLUE) - ->setHref('/D'.$d_id) + ->setHref($revision->getURI()) ->setBorder(PHUITagView::BORDER_NONE) ->setSlimShady(true); - } + } } $build_view = null; diff --git a/src/applications/diffusion/view/DiffusionHistoryTableView.php b/src/applications/diffusion/view/DiffusionHistoryTableView.php index cfd7019679..bd4fd5490a 100644 --- a/src/applications/diffusion/view/DiffusionHistoryTableView.php +++ b/src/applications/diffusion/view/DiffusionHistoryTableView.php @@ -127,6 +127,20 @@ final class DiffusionHistoryTableView extends DiffusionHistoryView { 'tip' => $name, )); + $revision_link = null; + if ($commit) { + $revisions = $this->getRevisionsForCommit($commit); + if ($revisions) { + $revision = head($revisions); + $revision_link = phutil_tag( + 'a', + array( + 'href' => $revision->getURI(), + ), + $revision->getMonogram()); + } + } + $rows[] = array( $graph ? $graph[$ii++] : null, $browse, @@ -135,9 +149,7 @@ final class DiffusionHistoryTableView extends DiffusionHistoryView { $history->getCommitIdentifier()), $build, $audit_view, - ($commit ? - self::linkRevision(idx($this->getRevisions(), $commit->getPHID())) : - null), + $revision_link, $author, $summary, $committed, diff --git a/src/applications/diffusion/view/DiffusionHistoryView.php b/src/applications/diffusion/view/DiffusionHistoryView.php index 56a0f3b75f..d7ae662ab6 100644 --- a/src/applications/diffusion/view/DiffusionHistoryView.php +++ b/src/applications/diffusion/view/DiffusionHistoryView.php @@ -9,6 +9,7 @@ abstract class DiffusionHistoryView extends DiffusionView { private $isTail; private $parents; private $filterParents; + private $revisionMap; public function setHistory(array $history) { assert_instances_of($history, 'DiffusionPathChange'); @@ -20,24 +21,6 @@ abstract class DiffusionHistoryView extends DiffusionView { return $this->history; } - public function loadRevisions() { - $commit_phids = array(); - foreach ($this->history as $item) { - if ($item->getCommit()) { - $commit_phids[] = $item->getCommit()->getPHID(); - } - } - - // TODO: Get rid of this. - $this->revisions = id(new DifferentialRevision()) - ->loadIDsByCommitPHIDs($commit_phids); - return $this; - } - - public function getRevisions() { - return $this->revisions; - } - public function setHandles(array $handles) { assert_instances_of($handles, 'PhabricatorObjectHandle'); $this->handles = $handles; @@ -98,4 +81,30 @@ abstract class DiffusionHistoryView extends DiffusionView { public function render() {} + final protected function getRevisionsForCommit( + PhabricatorRepositoryCommit $commit) { + + if ($this->revisionMap === null) { + $this->revisionMap = $this->newRevisionMap(); + } + + return idx($this->revisionMap, $commit->getPHID(), array()); + } + + private function newRevisionMap() { + $history = $this->history; + + $commits = array(); + foreach ($history as $item) { + $commit = $item->getCommit(); + if ($commit) { + $commits[] = $commit; + } + } + + return DiffusionCommitRevisionQuery::loadRevisionMapForCommits( + $this->getViewer(), + $commits); + } + } diff --git a/src/applications/diffusion/view/DiffusionView.php b/src/applications/diffusion/view/DiffusionView.php index 123d22af5e..795b47c773 100644 --- a/src/applications/diffusion/view/DiffusionView.php +++ b/src/applications/diffusion/view/DiffusionView.php @@ -169,19 +169,6 @@ abstract class DiffusionView extends AphrontView { $detail); } - final public static function linkRevision($id) { - if (!$id) { - return null; - } - - return phutil_tag( - 'a', - array( - 'href' => "/D{$id}", - ), - "D{$id}"); - } - final public static function renderName($name) { $email = new PhutilEmailAddress($name); if ($email->getDisplayName() && $email->getDomainName()) { diff --git a/src/applications/diffusion/worker/DiffusionUpdateObjectAfterCommitWorker.php b/src/applications/diffusion/worker/DiffusionUpdateObjectAfterCommitWorker.php new file mode 100644 index 0000000000..8477553407 --- /dev/null +++ b/src/applications/diffusion/worker/DiffusionUpdateObjectAfterCommitWorker.php @@ -0,0 +1,214 @@ +getViewer(); + $data = $this->getTaskData(); + + $commit_phid = idx($data, 'commitPHID'); + if (!$commit_phid) { + throw new PhabricatorWorkerPermanentFailureException( + pht('No "commitPHID" in task data.')); + } + + $commit = id(new DiffusionCommitQuery()) + ->setViewer($viewer) + ->withPHIDs(array($commit_phid)) + ->needIdentities(true) + ->executeOne(); + if (!$commit) { + throw new PhabricatorWorkerPermanentFailureException( + pht( + 'Unable to load commit "%s".', + $commit_phid)); + } + + $object_phid = idx($data, 'objectPHID'); + if (!$object_phid) { + throw new PhabricatorWorkerPermanentFailureException( + pht('No "objectPHID" in task data.')); + } + + $object = id(new PhabricatorObjectQuery()) + ->setViewer($viewer) + ->withPHIDs(array($object_phid)) + ->executeOne(); + if (!$object) { + throw new PhabricatorWorkerPermanentFailureException( + pht( + 'Unable to load object "%s".', + $object_phid)); + } + + $properties = idx($data, 'properties', array()); + $this->properties = $properties; + + if ($object instanceof ManiphestTask) { + $this->updateTask($commit, $object); + } else if ($object instanceof DifferentialRevision) { + $this->updateRevision($commit, $object); + } + } + + protected function getUpdateProperty($key, $default = null) { + return idx($this->properties, $key, $default); + } + + protected function getActingPHID(PhabricatorRepositoryCommit $commit) { + if ($commit->hasCommitterIdentity()) { + return $commit->getCommitterIdentity()->getIdentityDisplayPHID(); + } + + if ($commit->hasAuthorIdentity()) { + return $commit->getAuthorIdentity()->getIdentityDisplayPHID(); + } + + return id(new PhabricatorDiffusionApplication())->getPHID(); + } + + protected function loadActingUser($acting_phid) { + // If we we were able to identify an author or committer for the commit, we + // try to act as that user when affecting other objects, like tasks marked + // with "Fixes Txxx". + + // This helps to prevent mistakes where a user accidentally writes the + // wrong task IDs and affects tasks they can't see (and thus can't undo the + // status changes for). + + // This is just a guard rail, not a security measure. An attacker can still + // forge another user's identity trivially by forging author or committer + // email addresses. + + // We also let commits with unrecognized authors act on any task to make + // behavior less confusing for new installs, and any user can craft a + // commit with an unrecognized author and committer. + + $viewer = $this->getViewer(); + + $user_type = PhabricatorPeopleUserPHIDType::TYPECONST; + if (phid_get_type($acting_phid) === $user_type) { + $acting_user = id(new PhabricatorPeopleQuery()) + ->setViewer($viewer) + ->withPHIDs(array($acting_phid)) + ->executeOne(); + if ($acting_user) { + return $acting_user; + } + } + + return $viewer; + } + + private function updateTask( + PhabricatorRepositoryCommit $commit, + ManiphestTask $task) { + + $acting_phid = $this->getActingPHID($commit); + $acting_user = $this->loadActingUser($acting_phid); + + $commit_phid = $commit->getPHID(); + + $xactions = array(); + + $xactions[] = $this->newEdgeTransaction( + $task, + $commit, + ManiphestTaskHasCommitEdgeType::EDGECONST); + + $status = $this->getUpdateProperty('status'); + if ($status) { + $xactions[] = $task->getApplicationTransactionTemplate() + ->setTransactionType(ManiphestTaskStatusTransaction::TRANSACTIONTYPE) + ->setMetadataValue('commitPHID', $commit_phid) + ->setNewValue($status); + } + + $content_source = $this->newContentSource(); + + $editor = $task->getApplicationTransactionEditor() + ->setActor($acting_user) + ->setActingAsPHID($acting_phid) + ->setContentSource($content_source) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true) + ->setUnmentionablePHIDMap( + array( + $commit_phid => $commit_phid, + )); + + $editor->applyTransactions($task, $xactions); + } + + private function updateRevision( + PhabricatorRepositoryCommit $commit, + DifferentialRevision $revision) { + + // Reload the revision to get the active diff, which is currently required + // by "updateRevisionWithCommit()". + $revision = id(new DifferentialRevisionQuery()) + ->setViewer($this->getViewer()) + ->withIDs(array($revision->getID())) + ->needActiveDiffs(true) + ->executeOne(); + + $acting_phid = $this->getActingPHID($commit); + $acting_user = $this->loadActingUser($acting_phid); + + $xactions = array(); + + $xactions[] = $this->newEdgeTransaction( + $revision, + $commit, + DiffusionCommitHasRevisionEdgeType::EDGECONST); + + $match_data = $this->getUpdateProperty('revisionMatchData'); + + $type_close = DifferentialRevisionCloseTransaction::TRANSACTIONTYPE; + $xactions[] = $revision->getApplicationTransactionTemplate() + ->setTransactionType($type_close) + ->setNewValue(true) + ->setMetadataValue('isCommitClose', true) + ->setMetadataValue('revisionMatchData', $match_data) + ->setMetadataValue('commitPHID', $commit->getPHID()); + + $extraction_engine = id(new DifferentialDiffExtractionEngine()) + ->setViewer($acting_user) + ->setAuthorPHID($acting_phid); + + $content_source = $this->newContentSource(); + + $extraction_engine->updateRevisionWithCommit( + $revision, + $commit, + $xactions, + $content_source); + } + + private function newEdgeTransaction( + $object, + PhabricatorRepositoryCommit $commit, + $edge_type) { + + $commit_phid = $commit->getPHID(); + + return $object->getApplicationTransactionTemplate() + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) + ->setMetadataValue('edge:type', $edge_type) + ->setNewValue( + array( + '+' => array( + $commit_phid => $commit_phid, + ), + )); + } + + +} diff --git a/src/applications/fact/application/PhabricatorFactApplication.php b/src/applications/fact/application/PhabricatorFactApplication.php index 6444fe700a..2b493b7bea 100644 --- a/src/applications/fact/application/PhabricatorFactApplication.php +++ b/src/applications/fact/application/PhabricatorFactApplication.php @@ -30,7 +30,7 @@ final class PhabricatorFactApplication extends PhabricatorApplication { return array( '/fact/' => array( '' => 'PhabricatorFactHomeController', - 'chart/' => 'PhabricatorFactChartController', + '(?chart|draw)/' => 'PhabricatorFactChartController', 'object/(?[^/]+)/' => 'PhabricatorFactObjectController', ), ); diff --git a/src/applications/fact/chart/PhabricatorChartAxis.php b/src/applications/fact/chart/PhabricatorChartAxis.php new file mode 100644 index 0000000000..8a82c028c5 --- /dev/null +++ b/src/applications/fact/chart/PhabricatorChartAxis.php @@ -0,0 +1,27 @@ +minimumValue = $minimum_value; + return $this; + } + + public function getMinimumValue() { + return $this->minimumValue; + } + + public function setMaximumValue($maximum_value) { + $this->maximumValue = $maximum_value; + return $this; + } + + public function getMaximumValue() { + return $this->maximumValue; + } + +} diff --git a/src/applications/fact/chart/PhabricatorChartDataQuery.php b/src/applications/fact/chart/PhabricatorChartDataQuery.php new file mode 100644 index 0000000000..15708341f7 --- /dev/null +++ b/src/applications/fact/chart/PhabricatorChartDataQuery.php @@ -0,0 +1,37 @@ +minimumValue = $minimum_value; + return $this; + } + + public function getMinimumValue() { + return $this->minimumValue; + } + + public function setMaximumValue($maximum_value) { + $this->maximumValue = $maximum_value; + return $this; + } + + public function getMaximumValue() { + return $this->maximumValue; + } + + public function setLimit($limit) { + $this->limit = $limit; + return $this; + } + + public function getLimit() { + return $this->limit; + } + +} diff --git a/src/applications/fact/chart/PhabricatorChartFunction.php b/src/applications/fact/chart/PhabricatorChartFunction.php new file mode 100644 index 0000000000..40d13b0c38 --- /dev/null +++ b/src/applications/fact/chart/PhabricatorChartFunction.php @@ -0,0 +1,217 @@ +getPhobjectClassConstant('FUNCTIONKEY', 32); + } + + final public static function getAllFunctions() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getFunctionKey') + ->execute(); + } + + final public function setArguments(array $arguments) { + $parser = $this->getArgumentParser(); + $parser->setRawArguments($arguments); + + $specs = $this->newArguments(); + + if (!is_array($specs)) { + throw new Exception( + pht( + 'Expected "newArguments()" in class "%s" to return a list of '. + 'argument specifications, got %s.', + get_class($this), + phutil_describe_type($specs))); + } + + assert_instances_of($specs, 'PhabricatorChartFunctionArgument'); + + foreach ($specs as $spec) { + $parser->addArgument($spec); + } + + $parser->setHaveAllArguments(true); + $parser->parseArguments(); + + $source_argument = $parser->getSourceFunctionArgument(); + if ($source_argument) { + $source_function = $this->getArgument($source_argument->getName()); + $this->setSourceFunction($source_function); + } + + return $this; + } + + abstract protected function newArguments(); + + final protected function newArgument() { + return new PhabricatorChartFunctionArgument(); + } + + final protected function getArgument($key) { + return $this->getArgumentParser()->getArgumentValue($key); + } + + final protected function getArgumentParser() { + if (!$this->argumentParser) { + $parser = id(new PhabricatorChartFunctionArgumentParser()) + ->setFunction($this); + + $this->argumentParser = $parser; + } + return $this->argumentParser; + } + + public function loadData() { + return; + } + + protected function setSourceFunction(PhabricatorChartFunction $source) { + $this->sourceFunction = $source; + return $this; + } + + protected function getSourceFunction() { + return $this->sourceFunction; + } + + final public function setXAxis(PhabricatorChartAxis $x_axis) { + $this->xAxis = $x_axis; + return $this; + } + + final public function getXAxis() { + return $this->xAxis; + } + + final public function setYAxis(PhabricatorChartAxis $y_axis) { + $this->yAxis = $y_axis; + return $this; + } + + final public function getYAxis() { + return $this->yAxis; + } + + protected function canEvaluateFunction() { + return false; + } + + protected function evaluateFunction($x) { + throw new PhutilMethodNotImplementedException(); + } + + public function hasDomain() { + if ($this->canEvaluateFunction()) { + return false; + } + + throw new PhutilMethodNotImplementedException(); + } + + public function getDatapoints(PhabricatorChartDataQuery $query) { + if ($this->canEvaluateFunction()) { + $points = $this->newSourceDatapoints($query); + foreach ($points as $key => $point) { + $y = $point['y']; + $y = $this->evaluateFunction($y); + $points[$key]['y'] = $y; + } + + return $points; + } + + return $this->newDatapoints($query); + } + + protected function newDatapoints(PhabricatorChartDataQuery $query) { + throw new PhutilMethodNotImplementedException(); + } + + protected function newSourceDatapoints(PhabricatorChartDataQuery $query) { + $source = $this->getSourceFunction(); + if ($source) { + return $source->getDatapoints($query); + } + + return $this->newDefaultDatapoints($query); + } + + protected function newDefaultDatapoints(PhabricatorChartDataQuery $query) { + $x_min = $query->getMinimumValue(); + $x_max = $query->getMaximumValue(); + $limit = $query->getLimit(); + + $points = array(); + $steps = $this->newLinearSteps($x_min, $x_max, $limit); + foreach ($steps as $step) { + $points[] = array( + 'x' => $step, + 'y' => $step, + ); + } + + return $points; + } + + protected function newLinearSteps($src, $dst, $count) { + $count = (int)$count; + $src = (int)$src; + $dst = (int)$dst; + + if ($count === 0) { + throw new Exception( + pht('Can not generate zero linear steps between two values!')); + } + + if ($src === $dst) { + return array($src); + } + + if ($count === 1) { + return array($src); + } + + $is_reversed = ($src > $dst); + if ($is_reversed) { + $min = (double)$dst; + $max = (double)$src; + } else { + $min = (double)$src; + $max = (double)$dst; + } + + $step = (double)($max - $min) / (double)($count - 1); + + $steps = array(); + for ($cursor = $min; $cursor <= $max; $cursor += $step) { + $x = (int)round($cursor); + + if (isset($steps[$x])) { + continue; + } + + $steps[$x] = $x; + } + + $steps = array_values($steps); + + if ($is_reversed) { + $steps = array_reverse($steps); + } + + return $steps; + } + +} diff --git a/src/applications/fact/chart/PhabricatorChartFunctionArgument.php b/src/applications/fact/chart/PhabricatorChartFunctionArgument.php new file mode 100644 index 0000000000..baa4ee14d0 --- /dev/null +++ b/src/applications/fact/chart/PhabricatorChartFunctionArgument.php @@ -0,0 +1,139 @@ +name = $name; + return $this; + } + + public function getName() { + return $this->name; + } + + public function setType($type) { + $types = array( + 'fact-key' => true, + 'function' => true, + 'number' => true, + ); + + if (!isset($types[$type])) { + throw new Exception( + pht( + 'Chart function argument type "%s" is unknown. Valid types '. + 'are: %s.', + $type, + implode(', ', array_keys($types)))); + } + + $this->type = $type; + return $this; + } + + public function getType() { + return $this->type; + } + + public function setIsSourceFunction($is_source_function) { + $this->isSourceFunction = $is_source_function; + return $this; + } + + public function getIsSourceFunction() { + return $this->isSourceFunction; + } + + public function newValue($value) { + switch ($this->getType()) { + case 'fact-key': + if (!is_string($value)) { + throw new Exception( + pht( + 'Value for "fact-key" argument must be a string, got %s.', + phutil_describe_type($value))); + } + + $facts = PhabricatorFact::getAllFacts(); + $fact = idx($facts, $value); + if (!$fact) { + throw new Exception( + pht( + 'Fact key "%s" is not a known fact key.', + $value)); + } + + return $fact; + case 'function': + // If this is already a function object, just return it. + if ($value instanceof PhabricatorChartFunction) { + return $value; + } + + if (!is_array($value)) { + throw new Exception( + pht( + 'Value for "function" argument must be a function definition, '. + 'formatted as a list, like: [fn, arg1, arg, ...]. Actual value '. + 'is %s.', + phutil_describe_type($value))); + } + + if (!phutil_is_natural_list($value)) { + throw new Exception( + pht( + 'Value for "function" argument must be a natural list, not '. + 'a dictionary. Actual value is "%s".', + phutil_describe_type($value))); + } + + if (!$value) { + throw new Exception( + pht( + 'Value for "function" argument must be a list with a function '. + 'name; got an empty list.')); + } + + $function_name = array_shift($value); + + if (!is_string($function_name)) { + throw new Exception( + pht( + 'Value for "function" argument must be a natural list '. + 'beginning with a function name as a string. The first list '. + 'item has the wrong type, %s.', + phutil_describe_type($function_name))); + } + + $functions = PhabricatorChartFunction::getAllFunctions(); + if (!isset($functions[$function_name])) { + throw new Exception( + pht( + 'Function "%s" is unknown. Valid functions are: %s', + $function_name, + implode(', ', array_keys($functions)))); + } + + return id(clone $functions[$function_name]) + ->setArguments($value); + case 'number': + if (!is_float($value) && !is_int($value)) { + throw new Exception( + pht( + 'Value for "number" argument must be an integer or double, '. + 'got %s.', + phutil_describe_type($value))); + } + + return $value; + } + + throw new PhutilMethodNotImplementedException(); + } + +} diff --git a/src/applications/fact/chart/PhabricatorChartFunctionArgumentParser.php b/src/applications/fact/chart/PhabricatorChartFunctionArgumentParser.php new file mode 100644 index 0000000000..281cb88f4d --- /dev/null +++ b/src/applications/fact/chart/PhabricatorChartFunctionArgumentParser.php @@ -0,0 +1,193 @@ +function = $function; + return $this; + } + + public function getFunction() { + return $this->function; + } + + public function setRawArguments(array $arguments) { + $this->rawArguments = $arguments; + $this->unconsumedArguments = $arguments; + } + + public function addArgument(PhabricatorChartFunctionArgument $spec) { + $name = $spec->getName(); + if (!strlen($name)) { + throw new Exception( + pht( + 'Chart function "%s" emitted an argument specification with no '. + 'argument name. Argument specifications must have unique names.', + $this->getFunctionArgumentSignature())); + } + + $type = $spec->getType(); + if (!strlen($type)) { + throw new Exception( + pht( + 'Chart function "%s" emitted an argument specification ("%s") with '. + 'no type. Each argument specification must have a valid type.', + $name)); + } + + if (isset($this->argumentMap[$name])) { + throw new Exception( + pht( + 'Chart function "%s" emitted multiple argument specifications '. + 'with the same name ("%s"). Each argument specification must have '. + 'a unique name.', + $this->getFunctionArgumentSignature(), + $name)); + } + + $this->argumentMap[$name] = $spec; + $this->unparsedArguments[] = $spec; + + return $this; + } + + public function parseArgument( + PhabricatorChartFunctionArgument $spec) { + $this->addArgument($spec); + return $this->parseArguments(); + } + + public function setHaveAllArguments($have_all) { + $this->haveAllArguments = $have_all; + return $this; + } + + public function parseArguments() { + $have_count = count($this->rawArguments); + $want_count = count($this->argumentMap); + + if ($this->haveAllArguments) { + if ($want_count !== $have_count) { + throw new Exception( + pht( + 'Function "%s" expects %s argument(s), but %s argument(s) were '. + 'provided.', + $this->getFunctionArgumentSignature(), + $want_count, + $have_count)); + } + } + + while ($this->unparsedArguments) { + $argument = array_shift($this->unparsedArguments); + $name = $argument->getName(); + + if (!$this->unconsumedArguments) { + throw new Exception( + pht( + 'Function "%s" expects at least %s argument(s), but only %s '. + 'argument(s) were provided.', + $this->getFunctionArgumentSignature(), + $want_count, + $have_count)); + } + + $raw_argument = array_shift($this->unconsumedArguments); + $this->argumentPosition++; + + try { + $value = $argument->newValue($raw_argument); + } catch (Exception $ex) { + throw new Exception( + pht( + 'Argument "%s" (in position "%s") to function "%s" is '. + 'invalid: %s', + $name, + $this->argumentPosition, + $this->getFunctionArgumentSignature(), + $ex->getMessage())); + } + + $this->argumentValues[$name] = $value; + } + } + + public function getArgumentValue($key) { + if (!array_key_exists($key, $this->argumentValues)) { + throw new Exception( + pht( + 'Function "%s" is requesting an argument ("%s") that it did '. + 'not define.', + $this->getFunctionArgumentSignature(), + $key)); + } + + return $this->argumentValues[$key]; + } + + private function getFunctionArgumentSignature() { + $argument_list = array(); + foreach ($this->argumentMap as $key => $spec) { + $argument_list[] = $key; + } + + if (!$this->haveAllArguments) { + $argument_list[] = '...'; + } + + return sprintf( + '%s(%s)', + $this->getFunction()->getFunctionKey(), + implode(', ', $argument_list)); + } + + public function getSourceFunctionArgument() { + $required_type = 'function'; + + $sources = array(); + foreach ($this->argumentMap as $key => $argument) { + if (!$argument->getIsSourceFunction()) { + continue; + } + + if ($argument->getType() !== $required_type) { + throw new Exception( + pht( + 'Function "%s" defines an argument "%s" which is marked as a '. + 'source function, but the type of this argument is not "%s".', + $this->getFunctionArgumentSignature(), + $argument->getName(), + $required_type)); + } + + $sources[$key] = $argument; + } + + if (!$sources) { + return null; + } + + if (count($sources) > 1) { + throw new Exception( + pht( + 'Function "%s" defines more than one argument as a source '. + 'function (arguments: %s). Functions must have zero or one '. + 'source function.', + $this->getFunctionArgumentSignature(), + implode(', ', array_keys($sources)))); + } + + return head($sources); + } + +} diff --git a/src/applications/fact/chart/PhabricatorConstantChartFunction.php b/src/applications/fact/chart/PhabricatorConstantChartFunction.php new file mode 100644 index 0000000000..6ce9f1942d --- /dev/null +++ b/src/applications/fact/chart/PhabricatorConstantChartFunction.php @@ -0,0 +1,24 @@ +newArgument() + ->setName('n') + ->setType('number'), + ); + } + + protected function canEvaluateFunction() { + return true; + } + + protected function evaluateFunction($x) { + return $this->getArgument('n'); + } + +} diff --git a/src/applications/fact/chart/PhabricatorCosChartFunction.php b/src/applications/fact/chart/PhabricatorCosChartFunction.php new file mode 100644 index 0000000000..213124c3d1 --- /dev/null +++ b/src/applications/fact/chart/PhabricatorCosChartFunction.php @@ -0,0 +1,25 @@ +newArgument() + ->setName('x') + ->setType('function') + ->setIsSourceFunction(true), + ); + } + + protected function canEvaluateFunction() { + return true; + } + + protected function evaluateFunction($x) { + return cos(deg2rad($x)); + } + +} diff --git a/src/applications/fact/chart/PhabricatorFactChartFunction.php b/src/applications/fact/chart/PhabricatorFactChartFunction.php new file mode 100644 index 0000000000..2f28b22335 --- /dev/null +++ b/src/applications/fact/chart/PhabricatorFactChartFunction.php @@ -0,0 +1,116 @@ +newArgument() + ->setName('fact-key') + ->setType('fact-key'); + + $parser = $this->getArgumentParser(); + $parser->parseArgument($key_argument); + + $fact = $this->getArgument('fact-key'); + $this->fact = $fact; + + return $fact->getFunctionArguments(); + } + + public function loadData() { + $fact = $this->fact; + + $key_id = id(new PhabricatorFactKeyDimension()) + ->newDimensionID($fact->getKey()); + if (!$key_id) { + return; + } + + $table = $fact->newDatapoint(); + $conn = $table->establishConnection('r'); + $table_name = $table->getTableName(); + + $data = queryfx_all( + $conn, + 'SELECT value, epoch FROM %T WHERE keyID = %d ORDER BY epoch ASC', + $table_name, + $key_id); + if (!$data) { + return; + } + + $points = array(); + + $sum = 0; + foreach ($data as $key => $row) { + $sum += (int)$row['value']; + $points[] = array( + 'x' => (int)$row['epoch'], + 'y' => $sum, + ); + } + + $this->datapoints = $points; + } + + public function getDatapoints(PhabricatorChartDataQuery $query) { + $points = $this->datapoints; + if (!$points) { + return array(); + } + + $x_min = $query->getMinimumValue(); + $x_max = $query->getMaximumValue(); + $limit = $query->getLimit(); + + if ($x_min !== null) { + foreach ($points as $key => $point) { + if ($point['x'] < $x_min) { + unset($points[$key]); + } + } + } + + if ($x_max !== null) { + foreach ($points as $key => $point) { + if ($point['x'] > $x_max) { + unset($points[$key]); + } + } + } + + // If we have too many data points, throw away some of the data. + if ($limit !== null) { + $count = count($points); + if ($count > $limit) { + $ii = 0; + $every = ceil($count / $limit); + foreach ($points as $key => $point) { + $ii++; + if (($ii % $every) && ($ii != $count)) { + unset($points[$key]); + } + } + } + } + + return $points; + } + + public function hasDomain() { + return true; + } + + public function getDomain() { + // TODO: We can examine the data to fit a better domain. + + $now = PhabricatorTime::getNow(); + return array($now - phutil_units('90 days in seconds'), $now); + } + +} diff --git a/src/applications/fact/chart/PhabricatorScaleChartFunction.php b/src/applications/fact/chart/PhabricatorScaleChartFunction.php new file mode 100644 index 0000000000..78540a6844 --- /dev/null +++ b/src/applications/fact/chart/PhabricatorScaleChartFunction.php @@ -0,0 +1,28 @@ +newArgument() + ->setName('x') + ->setType('function') + ->setIsSourceFunction(true), + $this->newArgument() + ->setName('scale') + ->setType('number'), + ); + } + + protected function canEvaluateFunction() { + return true; + } + + protected function evaluateFunction($x) { + return $x * $this->getArgument('scale'); + } + +} diff --git a/src/applications/fact/chart/PhabricatorShiftChartFunction.php b/src/applications/fact/chart/PhabricatorShiftChartFunction.php new file mode 100644 index 0000000000..52f33d26b5 --- /dev/null +++ b/src/applications/fact/chart/PhabricatorShiftChartFunction.php @@ -0,0 +1,28 @@ +newArgument() + ->setName('x') + ->setType('function') + ->setIsSourceFunction(true), + $this->newArgument() + ->setName('shift') + ->setType('number'), + ); + } + + protected function canEvaluateFunction() { + return true; + } + + protected function evaluateFunction($x) { + return $x * $this->getArgument('shift'); + } + +} diff --git a/src/applications/fact/chart/PhabricatorSinChartFunction.php b/src/applications/fact/chart/PhabricatorSinChartFunction.php new file mode 100644 index 0000000000..1ac557f868 --- /dev/null +++ b/src/applications/fact/chart/PhabricatorSinChartFunction.php @@ -0,0 +1,25 @@ +newArgument() + ->setName('x') + ->setType('function') + ->setIsSourceFunction(true), + ); + } + + protected function canEvaluateFunction() { + return true; + } + + protected function evaluateFunction($x) { + return sin(deg2rad($x)); + } + +} diff --git a/src/applications/fact/chart/PhabricatorXChartFunction.php b/src/applications/fact/chart/PhabricatorXChartFunction.php new file mode 100644 index 0000000000..b2b7ab36ed --- /dev/null +++ b/src/applications/fact/chart/PhabricatorXChartFunction.php @@ -0,0 +1,20 @@ +getViewer(); - $series = $request->getStr('y1'); + // When drawing a chart, we send down a placeholder piece of HTML first, + // then fetch the data via async request. Determine if we're drawing + // the structure or actually pulling the data. + $mode = $request->getURIData('mode'); + $is_chart_mode = ($mode === 'chart'); + $is_draw_mode = ($mode === 'draw'); - $facts = PhabricatorFact::getAllFacts(); - $fact = idx($facts, $series); + $functions = array(); - if (!$fact) { - return new Aphront404Response(); - } + $functions[] = id(new PhabricatorFactChartFunction()) + ->setArguments(array('tasks.count.create')); - $key_id = id(new PhabricatorFactKeyDimension()) - ->newDimensionID($fact->getKey()); - if (!$key_id) { - return new Aphront404Response(); - } + $functions[] = id(new PhabricatorFactChartFunction()) + ->setArguments(array('tasks.open-count.create')); - $table = $fact->newDatapoint(); - $conn_r = $table->establishConnection('r'); - $table_name = $table->getTableName(); + $x_function = id(new PhabricatorXChartFunction()) + ->setArguments(array()); - $data = queryfx_all( - $conn_r, - 'SELECT value, epoch FROM %T WHERE keyID = %d ORDER BY epoch ASC', - $table_name, - $key_id); + $functions[] = id(new PhabricatorConstantChartFunction()) + ->setArguments(array(360)); - $points = array(); - $sum = 0; - foreach ($data as $key => $row) { - $sum += (int)$row['value']; - $points[(int)$row['epoch']] = $sum; - } + $functions[] = id(new PhabricatorSinChartFunction()) + ->setArguments(array($x_function)); - if (!$points) { - throw new Exception('No data to show!'); - } + $cos_function = id(new PhabricatorCosChartFunction()) + ->setArguments(array($x_function)); - // Limit amount of data passed to browser. - $count = count($points); - $limit = 2000; - if ($count > $limit) { - $i = 0; - $every = ceil($count / $limit); - foreach ($points as $epoch => $sum) { - $i++; - if ($i % $every && $i != $count) { - unset($points[$epoch]); - } + $functions[] = id(new PhabricatorShiftChartFunction()) + ->setArguments( + array( + array( + 'scale', + array( + 'cos', + array( + 'scale', + array('x'), + 0.001, + ), + ), + 10, + ), + 200, + )); + + list($domain_min, $domain_max) = $this->getDomain($functions); + + $axis = id(new PhabricatorChartAxis()) + ->setMinimumValue($domain_min) + ->setMaximumValue($domain_max); + + $data_query = id(new PhabricatorChartDataQuery()) + ->setMinimumValue($domain_min) + ->setMaximumValue($domain_max) + ->setLimit(2000); + + $datasets = array(); + foreach ($functions as $function) { + $function->setXAxis($axis); + + $function->loadData(); + + $points = $function->getDatapoints($data_query); + + $x = array(); + $y = array(); + + foreach ($points as $point) { + $x[] = $point['x']; + $y[] = $point['y']; } + + $datasets[] = array( + 'x' => $x, + 'y' => $y, + 'color' => '#ff00ff', + ); } - $x = array_keys($points); - $y = array_values($points); - $id = celerity_generate_unique_node_id(); - $chart = phutil_tag( + $y_min = 0; + $y_max = 0; + foreach ($datasets as $dataset) { + if (!$dataset['y']) { + continue; + } + + $y_min = min($y_min, min($dataset['y'])); + $y_max = max($y_max, max($dataset['y'])); + } + + $chart_data = array( + 'datasets' => $datasets, + 'xMin' => $domain_min, + 'xMax' => $domain_max, + 'yMin' => $y_min, + 'yMax' => $y_max, + ); + + // TODO: Move this back up, it's just down here for now to make + // debugging easier so the main page throws a more visible exception when + // something goes wrong. + if ($is_chart_mode) { + return $this->newChartResponse(); + } + + return id(new AphrontAjaxResponse())->setContent($chart_data); + } + + private function newChartResponse() { + $request = $this->getRequest(); + $chart_node_id = celerity_generate_unique_node_id(); + + $chart_view = phutil_tag( 'div', array( - 'id' => $id, + 'id' => $chart_node_id, 'style' => 'background: #ffffff; '. 'height: 480px; ', ), ''); - require_celerity_resource('d3'); + $data_uri = $request->getRequestURI(); + $data_uri->setPath('/fact/draw/'); - Javelin::initBehavior('line-chart', array( - 'hardpoint' => $id, - 'x' => array($x), - 'y' => array($y), - 'xformat' => 'epoch', - 'colors' => array('#0000ff'), - )); + Javelin::initBehavior( + 'line-chart', + array( + 'chartNodeID' => $chart_node_id, + 'dataURI' => (string)$data_uri, + )); $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Count of %s', $fact->getName())) - ->appendChild($chart); + ->setHeaderText(pht('Chart')) + ->appendChild($chart_view); - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Chart')); + $crumbs = $this->buildApplicationCrumbs() + ->addTextCrumb(pht('Chart')) + ->setBorder(true); $title = pht('Chart'); @@ -94,4 +154,49 @@ final class PhabricatorFactChartController extends PhabricatorFactController { } + private function getDomain(array $functions) { + $domain_min_list = null; + $domain_max_list = null; + foreach ($functions as $function) { + if ($function->hasDomain()) { + $domain = $function->getDomain(); + + list($domain_min, $domain_max) = $domain; + + if ($domain_min !== null) { + $domain_min_list[] = $domain_min; + } + + if ($domain_max !== null) { + $domain_max_list[] = $domain_max; + } + } + } + + $domain_min = null; + $domain_max = null; + + if ($domain_min_list) { + $domain_min = min($domain_min_list); + } + + if ($domain_max_list) { + $domain_max = max($domain_max_list); + } + + // If we don't have any domain data from the actual functions, pick a + // plausible domain automatically. + + if ($domain_max === null) { + $domain_max = PhabricatorTime::getNow(); + } + + if ($domain_min === null) { + $domain_min = $domain_max - phutil_units('365 days in seconds'); + } + + return array($domain_min, $domain_max); + } + + } diff --git a/src/applications/fact/fact/PhabricatorFact.php b/src/applications/fact/fact/PhabricatorFact.php index 2e33a029f3..a52fe5435e 100644 --- a/src/applications/fact/fact/PhabricatorFact.php +++ b/src/applications/fact/fact/PhabricatorFact.php @@ -37,4 +37,8 @@ abstract class PhabricatorFact extends Phobject { abstract protected function newTemplateDatapoint(); + final public function getFunctionArguments() { + return array(); + } + } diff --git a/src/applications/fact/storage/PhabricatorFactChart.php b/src/applications/fact/storage/PhabricatorFactChart.php new file mode 100644 index 0000000000..7829010037 --- /dev/null +++ b/src/applications/fact/storage/PhabricatorFactChart.php @@ -0,0 +1,69 @@ + array( + 'chartParameters' => self::SERIALIZATION_JSON, + ), + self::CONFIG_COLUMN_SCHEMA => array( + 'chartKey' => 'bytes12', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_chart' => array( + 'columns' => array('chartKey'), + 'unique' => true, + ), + ), + ) + parent::getConfiguration(); + } + + public function setChartParameter($key, $value) { + $this->chartParameters[$key] = $value; + return $this; + } + + public function getChartParameter($key, $default = null) { + return idx($this->chartParameters, $key, $default); + } + + public function save() { + if ($this->getID()) { + throw new Exception( + pht( + 'Chart configurations are not mutable. You can not update or '. + 'overwrite an existing chart configuration.')); + } + + $digest = serialize($this->chartParameters); + $digest = PhabricatorHash::digestForIndex($digest); + + $this->chartKey = $digest; + + return parent::save(); + } + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + ); + } + + public function getPolicy($capability) { + return PhabricatorPolicies::getMostOpenPolicy(); + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return false; + } + + +} diff --git a/src/applications/favorites/application/PhabricatorFavoritesApplication.php b/src/applications/favorites/application/PhabricatorFavoritesApplication.php index 3a6acd4ebc..50f56c5224 100644 --- a/src/applications/favorites/application/PhabricatorFavoritesApplication.php +++ b/src/applications/favorites/application/PhabricatorFavoritesApplication.php @@ -15,12 +15,13 @@ final class PhabricatorFavoritesApplication extends PhabricatorApplication { } public function getIcon() { - return 'fa-star'; + return 'fa-bookmark'; } public function getRoutes() { return array( '/favorites/' => array( + '' => 'PhabricatorFavoritesMenuItemController', 'menu/' => $this->getProfileMenuRouting( 'PhabricatorFavoritesMenuItemController'), ), diff --git a/src/applications/favorites/controller/PhabricatorFavoritesMenuItemController.php b/src/applications/favorites/controller/PhabricatorFavoritesMenuItemController.php index dd4bf77cf0..54af8908a6 100644 --- a/src/applications/favorites/controller/PhabricatorFavoritesMenuItemController.php +++ b/src/applications/favorites/controller/PhabricatorFavoritesMenuItemController.php @@ -16,8 +16,7 @@ final class PhabricatorFavoritesMenuItemController $engine = id(new PhabricatorFavoritesProfileMenuEngine()) ->setProfileObject($favorites) ->setCustomPHID($viewer->getPHID()) - ->setController($this) - ->setShowNavigation(false); + ->setController($this); return $engine->buildResponse(); } diff --git a/src/applications/favorites/engine/PhabricatorFavoritesProfileMenuEngine.php b/src/applications/favorites/engine/PhabricatorFavoritesProfileMenuEngine.php index 0ac31e7de8..a17c60226b 100644 --- a/src/applications/favorites/engine/PhabricatorFavoritesProfileMenuEngine.php +++ b/src/applications/favorites/engine/PhabricatorFavoritesProfileMenuEngine.php @@ -35,6 +35,10 @@ final class PhabricatorFavoritesProfileMenuEngine } } + $items[] = $this->newDividerItem('tail'); + $items[] = $this->newManageItem() + ->setMenuItemProperty('name', pht('Edit Favorites')); + return $items; } diff --git a/src/applications/favorites/engineextension/PhabricatorFavoritesMainMenuBarExtension.php b/src/applications/favorites/engineextension/PhabricatorFavoritesMainMenuBarExtension.php index adc272a125..a6cd4fc069 100644 --- a/src/applications/favorites/engineextension/PhabricatorFavoritesMainMenuBarExtension.php +++ b/src/applications/favorites/engineextension/PhabricatorFavoritesMainMenuBarExtension.php @@ -26,7 +26,7 @@ final class PhabricatorFavoritesMainMenuBarExtension $favorites_menu = id(new PHUIButtonView()) ->setTag('a') ->setHref('#') - ->setIcon('fa-star') + ->setIcon('fa-bookmark') ->addClass('phabricator-core-user-menu') ->setNoCSS(true) ->setDropdown(true) @@ -71,20 +71,11 @@ final class PhabricatorFavoritesMainMenuBarExtension $action = id(new PhabricatorActionView()) ->setName($item->getName()) ->setHref($item->getHref()) + ->setIcon($item->getIcon()) ->setType($item->getType()); $view->addAction($action); } - if ($viewer->isLoggedIn()) { - $view->addAction( - id(new PhabricatorActionView()) - ->setType(PhabricatorActionView::TYPE_DIVIDER)); - $view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Edit Favorites')) - ->setHref('/favorites/menu/configure/')); - } - return $view; } diff --git a/src/applications/feed/conduit/FeedQueryConduitAPIMethod.php b/src/applications/feed/conduit/FeedQueryConduitAPIMethod.php index bddc4f5921..53f3fe8f39 100644 --- a/src/applications/feed/conduit/FeedQueryConduitAPIMethod.php +++ b/src/applications/feed/conduit/FeedQueryConduitAPIMethod.php @@ -63,13 +63,7 @@ final class FeedQueryConduitAPIMethod extends FeedConduitAPIMethod { $view_type = 'data'; } - $limit = $request->getValue('limit'); - if (!$limit) { - $limit = $this->getDefaultLimit(); - } - $query = id(new PhabricatorFeedQuery()) - ->setLimit($limit) ->setViewer($user); $filter_phids = $request->getValue('filterPHIDs'); @@ -77,17 +71,25 @@ final class FeedQueryConduitAPIMethod extends FeedConduitAPIMethod { $query->withFilterPHIDs($filter_phids); } + $limit = $request->getValue('limit'); + if (!$limit) { + $limit = $this->getDefaultLimit(); + } + + $pager = id(new AphrontCursorPagerView()) + ->setPageSize($limit); + $after = $request->getValue('after'); if (strlen($after)) { - $query->setAfterID($after); + $pager->setAfterID($after); } $before = $request->getValue('before'); if (strlen($before)) { - $query->setBeforeID($before); + $pager->setBeforeID($before); } - $stories = $query->execute(); + $stories = $query->executeWithCursorPager($pager); if ($stories) { foreach ($stories as $story) { diff --git a/src/applications/guides/module/PhabricatorGuideQuickStartModule.php b/src/applications/guides/module/PhabricatorGuideQuickStartModule.php index 8c4b4c2606..65b07ffe2c 100644 --- a/src/applications/guides/module/PhabricatorGuideQuickStartModule.php +++ b/src/applications/guides/module/PhabricatorGuideQuickStartModule.php @@ -103,33 +103,6 @@ final class PhabricatorGuideQuickStartModule extends PhabricatorGuideModule { ->setDescription($description); $guide_items->addItem($item); - $title = pht('Build a Dashboard'); - $have_dashboard = (bool)PhabricatorDashboardInstall::getDashboard( - $viewer, - PhabricatorHomeApplication::DASHBOARD_DEFAULT, - 'PhabricatorHomeApplication'); - $href = PhabricatorEnv::getURI('/dashboard/'); - if ($have_dashboard) { - $icon = 'fa-check'; - $icon_bg = 'bg-green'; - $description = pht( - "You've created at least one dashboard."); - } else { - $icon = 'fa-dashboard'; - $icon_bg = 'bg-sky'; - $description = - pht('Customize the default homepage layout and items.'); - } - - $item = id(new PhabricatorGuideItemView()) - ->setTitle($title) - ->setHref($href) - ->setIcon($icon) - ->setIconBackground($icon_bg) - ->setDescription($description); - $guide_items->addItem($item); - - $title = pht('Personalize your Install'); $wordmark = PhabricatorEnv::getEnvConfig('ui.logo'); $href = PhabricatorEnv::getURI('/config/edit/ui.logo/'); diff --git a/src/applications/herald/engineextension/HeraldRuleIndexEngineExtension.php b/src/applications/herald/engineextension/HeraldRuleIndexEngineExtension.php index 7b7b2fb529..5df229ccd6 100644 --- a/src/applications/herald/engineextension/HeraldRuleIndexEngineExtension.php +++ b/src/applications/herald/engineextension/HeraldRuleIndexEngineExtension.php @@ -1,7 +1,7 @@ getPHID(), - $edge_type); - $old_edges = array_fuse($old_edges); - - $new_edges = $this->getPHIDsAffectedByActions($object); - $new_edges = array_fuse($new_edges); - - $add_edges = array_diff_key($new_edges, $old_edges); - $rem_edges = array_diff_key($old_edges, $new_edges); - - if (!$add_edges && !$rem_edges) { - return; - } - - $editor = new PhabricatorEdgeEditor(); - - foreach ($add_edges as $phid) { - $editor->addEdge($object->getPHID(), $edge_type, $phid); - } - - foreach ($rem_edges as $phid) { - $editor->removeEdge($object->getPHID(), $edge_type, $phid); - } - - $editor->save(); + protected function getIndexEdgeType() { + return HeraldRuleActionAffectsObjectEdgeType::EDGECONST; } - public function getIndexVersion($object) { - $phids = $this->getPHIDsAffectedByActions($object); - sort($phids); - $phids = implode(':', $phids); - return PhabricatorHash::digestForIndex($phids); - } + protected function getIndexDestinationPHIDs($object) { + $rule = $object; - private function getPHIDsAffectedByActions(HeraldRule $rule) { $viewer = $this->getViewer(); $rule = id(new HeraldRuleQuery()) diff --git a/src/applications/home/engine/PhabricatorHomeProfileMenuEngine.php b/src/applications/home/engine/PhabricatorHomeProfileMenuEngine.php index 5988d39f46..09ed4ea5e2 100644 --- a/src/applications/home/engine/PhabricatorHomeProfileMenuEngine.php +++ b/src/applications/home/engine/PhabricatorHomeProfileMenuEngine.php @@ -85,11 +85,12 @@ final class PhabricatorHomeProfileMenuEngine ->setMenuItemProperties($properties); } - // Hotlink to More Applications Launcher... $items[] = $this->newItem() ->setBuiltinKey(PhabricatorHomeConstants::ITEM_LAUNCHER) ->setMenuItemKey(PhabricatorHomeLauncherProfileMenuItem::MENUITEMKEY); + $items[] = $this->newDividerItem('tail'); + $items[] = $this->newManageItem(); return $items; diff --git a/src/applications/macro/controller/PhabricatorMacroViewController.php b/src/applications/macro/controller/PhabricatorMacroViewController.php index 1bcf34240a..d5a420920d 100644 --- a/src/applications/macro/controller/PhabricatorMacroViewController.php +++ b/src/applications/macro/controller/PhabricatorMacroViewController.php @@ -136,7 +136,12 @@ final class PhabricatorMacroViewController $image_uri = $handles[$author_phid]->getImageURI(); $image_href = $handles[$author_phid]->getURI(); - $content = pht('Masterfully imagined by %s on %s.', $author, $date); + if (!$date) { + $content = pht( + 'Masterfully imagined by %s in ages long past.', $author); + } else { + $content = pht('Masterfully imagined by %s on %s.', $author, $date); + } return id(new PHUIHeadThingView()) ->setImage($image_uri) diff --git a/src/applications/maniphest/controller/ManiphestReportController.php b/src/applications/maniphest/controller/ManiphestReportController.php index 40498e6e40..90210a0ee4 100644 --- a/src/applications/maniphest/controller/ManiphestReportController.php +++ b/src/applications/maniphest/controller/ManiphestReportController.php @@ -382,7 +382,7 @@ final class ManiphestReportController extends ManiphestController { require_celerity_resource('d3'); require_celerity_resource('phui-chart-css'); - Javelin::initBehavior('line-chart', array( + Javelin::initBehavior('line-chart-legacy', array( 'hardpoint' => $id, 'x' => array( $burn_x, diff --git a/src/applications/meta/query/PhabricatorApplicationQuery.php b/src/applications/meta/query/PhabricatorApplicationQuery.php index 940cf6406d..63de1174cf 100644 --- a/src/applications/meta/query/PhabricatorApplicationQuery.php +++ b/src/applications/meta/query/PhabricatorApplicationQuery.php @@ -170,10 +170,4 @@ final class PhabricatorApplicationQuery return null; } - protected function getResultCursor($object) { - // TODO: This won't work, but doesn't matter until we write more than 100 - // applications. Since we only have about 70, just avoid fataling for now. - return null; - } - } diff --git a/src/applications/notification/query/PhabricatorNotificationQuery.php b/src/applications/notification/query/PhabricatorNotificationQuery.php index 0063c06e45..6eea54a0de 100644 --- a/src/applications/notification/query/PhabricatorNotificationQuery.php +++ b/src/applications/notification/query/PhabricatorNotificationQuery.php @@ -53,10 +53,10 @@ final class PhabricatorNotificationQuery $data = queryfx_all( $conn, - 'SELECT story.*, notif.hasViewed FROM %R notif - JOIN %R story ON notif.chronologicalKey = story.chronologicalKey + 'SELECT story.*, notification.hasViewed FROM %R notification + JOIN %R story ON notification.chronologicalKey = story.chronologicalKey %Q - ORDER BY notif.chronologicalKey DESC + ORDER BY notification.chronologicalKey DESC %Q', $notification_table, $story_table, @@ -82,21 +82,21 @@ final class PhabricatorNotificationQuery if ($this->userPHIDs !== null) { $where[] = qsprintf( $conn, - 'notif.userPHID IN (%Ls)', + 'notification.userPHID IN (%Ls)', $this->userPHIDs); } if ($this->unread !== null) { $where[] = qsprintf( $conn, - 'notif.hasViewed = %d', + 'notification.hasViewed = %d', (int)!$this->unread); } if ($this->keys !== null) { $where[] = qsprintf( $conn, - 'notif.chronologicalKey IN (%Ls)', + 'notification.chronologicalKey IN (%Ls)', $this->keys); } @@ -113,8 +113,53 @@ final class PhabricatorNotificationQuery return $stories; } - protected function getResultCursor($item) { - return $item->getChronologicalKey(); + protected function getDefaultOrderVector() { + return array('key'); + } + + public function getBuiltinOrders() { + return array( + 'newest' => array( + 'vector' => array('key'), + 'name' => pht('Creation (Newest First)'), + 'aliases' => array('created'), + ), + 'oldest' => array( + 'vector' => array('-key'), + 'name' => pht('Creation (Oldest First)'), + ), + ); + } + + public function getOrderableColumns() { + return array( + 'key' => array( + 'table' => 'notification', + 'column' => 'chronologicalKey', + 'type' => 'string', + 'unique' => true, + ), + ); + } + + protected function applyExternalCursorConstraintsToQuery( + PhabricatorCursorPagedPolicyAwareQuery $subquery, + $cursor) { + $subquery->withKeys(array($cursor)); + } + + protected function newExternalCursorStringForResult($object) { + return $object->getChronologicalKey(); + } + + protected function newPagingMapFromPartialObject($object) { + return array( + 'key' => $object->getChronologicalKey(), + ); + } + + protected function getPrimaryTableAlias() { + return 'notification'; } public function getQueryApplicationClass() { diff --git a/src/applications/notification/query/PhabricatorNotificationSearchEngine.php b/src/applications/notification/query/PhabricatorNotificationSearchEngine.php index c7e1998333..4b56c5f5a1 100644 --- a/src/applications/notification/query/PhabricatorNotificationSearchEngine.php +++ b/src/applications/notification/query/PhabricatorNotificationSearchEngine.php @@ -134,8 +134,4 @@ final class PhabricatorNotificationSearchEngine return $result; } - public function shouldUseOffsetPaging() { - return true; - } - } diff --git a/src/applications/owners/controller/PhabricatorOwnersDetailController.php b/src/applications/owners/controller/PhabricatorOwnersDetailController.php index c458e4dbd1..3aace69074 100644 --- a/src/applications/owners/controller/PhabricatorOwnersDetailController.php +++ b/src/applications/owners/controller/PhabricatorOwnersDetailController.php @@ -144,6 +144,8 @@ final class PhabricatorOwnersDetailController $crumbs->addTextCrumb($package->getMonogram()); $crumbs->setBorder(true); + $rules_view = $this->newRulesView($package); + $timeline = $this->buildTransactionTimeline( $package, new PhabricatorOwnersPackageTransactionQuery()); @@ -154,6 +156,7 @@ final class PhabricatorOwnersDetailController ->setCurtain($curtain) ->setMainColumn(array( $this->renderPathsTable($paths, $repositories), + $rules_view, $commit_panels, $timeline, )) @@ -345,4 +348,55 @@ final class PhabricatorOwnersDetailController return $box; } + private function newRulesView(PhabricatorOwnersPackage $package) { + $viewer = $this->getViewer(); + + $limit = 10; + $rules = id(new HeraldRuleQuery()) + ->setViewer($viewer) + ->withDisabled(false) + ->withAffectedObjectPHIDs(array($package->getPHID())) + ->needValidateAuthors(true) + ->setLimit($limit + 1) + ->execute(); + + $more_results = (count($rules) > $limit); + $rules = array_slice($rules, 0, $limit); + + $list = id(new HeraldRuleListView()) + ->setViewer($viewer) + ->setRules($rules) + ->newObjectList(); + + $list->setNoDataString( + pht( + 'No active Herald rules add this package as an auditor, reviewer, '. + 'or subscriber.')); + + $more_href = new PhutilURI( + '/herald/', + array('affectedPHID' => $package->getPHID())); + + if ($more_results) { + $list->newTailButton() + ->setHref($more_href); + } + + $more_link = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-list-ul') + ->setText(pht('View All Rules')) + ->setHref($more_href); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Affected By Herald Rules')) + ->setHeaderIcon(id(new PhabricatorHeraldApplication())->getIcon()) + ->addActionLink($more_link); + + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($list); + } + } diff --git a/src/applications/people/controller/PhabricatorPeopleProfileEditController.php b/src/applications/people/controller/PhabricatorPeopleProfileEditController.php index 4876f4495d..3af95462a9 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileEditController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileEditController.php @@ -83,8 +83,9 @@ final class PhabricatorPeopleProfileEditController $crumbs->addTextCrumb(pht('Edit Profile')); $crumbs->setBorder(true); - $nav = $this->getProfileMenu(); - $nav->selectFilter(PhabricatorPeopleProfileMenuEngine::ITEM_MANAGE); + $nav = $this->newNavigation( + $user, + PhabricatorPeopleProfileMenuEngine::ITEM_MANAGE); $header = id(new PHUIHeaderView()) ->setHeader(pht('Edit Profile: %s', $user->getFullName())) diff --git a/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php b/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php index 92bb2e0b86..d50eccff13 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php @@ -266,8 +266,9 @@ final class PhabricatorPeopleProfilePictureController $crumbs->addTextCrumb(pht('Edit Profile Picture')); $crumbs->setBorder(true); - $nav = $this->getProfileMenu(); - $nav->selectFilter(PhabricatorPeopleProfileMenuEngine::ITEM_MANAGE); + $nav = $this->newNavigation( + $user, + PhabricatorPeopleProfileMenuEngine::ITEM_MANAGE); $header = $this->buildProfileHeader(); diff --git a/src/applications/phame/controller/post/PhamePostViewController.php b/src/applications/phame/controller/post/PhamePostViewController.php index 4fb01c4def..7bef0e6751 100644 --- a/src/applications/phame/controller/post/PhamePostViewController.php +++ b/src/applications/phame/controller/post/PhamePostViewController.php @@ -24,7 +24,7 @@ final class PhamePostViewController $hero = $this->buildPhamePostHeader($post); - if (!$is_external) { + if (!$is_external && $viewer->isLoggedIn()) { $actions = $this->renderActions($post); $header->setPolicyObject($post); $header->setActionList($actions); @@ -136,7 +136,7 @@ final class PhamePostViewController ->withTransactionTypes(array(PhabricatorTransactions::TYPE_COMMENT))); $timeline->setQuoteRef($monogram); - if ($is_external) { + if ($is_external || !$viewer->isLoggedIn()) { $add_comment = null; } else { $add_comment = $this->buildCommentForm($post, $timeline); diff --git a/src/applications/phid/view/PHUIHandleTagListView.php b/src/applications/phid/view/PHUIHandleTagListView.php index abae359a16..18c53e9b91 100644 --- a/src/applications/phid/view/PHUIHandleTagListView.php +++ b/src/applications/phid/view/PHUIHandleTagListView.php @@ -122,7 +122,7 @@ final class PHUIHandleTagListView extends AphrontTagView { private function newPlaceholderTag() { return id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) - ->setColor(PHUITagView::COLOR_DISABLED) + ->setColor(PHUITagView::COLOR_PLACEHOLDER) ->setSlimShady($this->slim); } diff --git a/src/applications/phriction/typeahead/PhrictionDocumentDatasource.php b/src/applications/phriction/typeahead/PhrictionDocumentDatasource.php index bb9608edb8..09056fe761 100644 --- a/src/applications/phriction/typeahead/PhrictionDocumentDatasource.php +++ b/src/applications/phriction/typeahead/PhrictionDocumentDatasource.php @@ -18,41 +18,17 @@ final class PhrictionDocumentDatasource public function loadResults() { $viewer = $this->getViewer(); - $raw_query = $this->getRawQuery(); - - $engine = id(new PhrictionDocument()) - ->newFerretEngine(); - - $compiler = id(new PhutilSearchQueryCompiler()) - ->setEnableFunctions(true); - - $raw_tokens = $compiler->newTokens($raw_query); - - $fulltext_tokens = array(); - foreach ($raw_tokens as $raw_token) { - - // This is a little hacky and could maybe be cleaner. We're treating - // every search term as though the user had entered "title:dog" insead - // of "dog". - - $alternate_token = PhutilSearchQueryToken::newFromDictionary( - array( - 'quoted' => $raw_token->isQuoted(), - 'value' => $raw_token->getValue(), - 'operator' => PhutilSearchQueryCompiler::OPERATOR_SUBSTRING, - 'function' => 'title', - )); - - $fulltext_token = id(new PhabricatorFulltextToken()) - ->setToken($alternate_token); - $fulltext_tokens[] = $fulltext_token; - } - - $documents = id(new PhrictionDocumentQuery()) + $query = id(new PhrictionDocumentQuery()) ->setViewer($viewer) - ->withFerretConstraint($engine, $fulltext_tokens) - ->needContent(true) - ->execute(); + ->needContent(true); + + $this->applyFerretConstraints( + $query, + id(new PhrictionDocument())->newFerretEngine(), + 'title', + $this->getRawQuery()); + + $documents = $query->execute(); $results = array(); foreach ($documents as $document) { diff --git a/src/applications/ponder/view/PonderAddAnswerView.php b/src/applications/ponder/view/PonderAddAnswerView.php index 20c52dac8e..d297e6408a 100644 --- a/src/applications/ponder/view/PonderAddAnswerView.php +++ b/src/applications/ponder/view/PonderAddAnswerView.php @@ -78,7 +78,7 @@ final class PonderAddAnswerView extends AphrontView { $box = id(new PHUIObjectBoxView()) ->appendChild($form) - ->setHeaderText('Answer') + ->setHeaderText(pht('Answer')) ->addClass('ponder-add-answer-view'); if ($info_panel) { diff --git a/src/applications/project/controller/PhabricatorProjectBoardBackgroundController.php b/src/applications/project/controller/PhabricatorProjectBoardBackgroundController.php index c70c211398..7859091de6 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardBackgroundController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardBackgroundController.php @@ -52,7 +52,9 @@ final class PhabricatorProjectBoardBackgroundController ->setURI($view_uri); } - $nav = $this->getProfileMenu(); + $nav = $this->newNavigation( + $board, + PhabricatorProject::ITEM_WORKBOARD); $crumbs = id($this->buildApplicationCrumbs()) ->addTextCrumb(pht('Workboard'), $board->getWorkboardURI()) diff --git a/src/applications/project/controller/PhabricatorProjectBoardManageController.php b/src/applications/project/controller/PhabricatorProjectBoardManageController.php index 21daf2e654..b2b6dfdf6d 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardManageController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardManageController.php @@ -38,7 +38,9 @@ final class PhabricatorProjectBoardManageController $crumbs->addTextCrumb(pht('Manage')); $crumbs->setBorder(true); - $nav = $this->getProfileMenu(); + $nav = $this->newNavigation( + $board, + PhabricatorProject::ITEM_WORKBOARD); $columns_list = $this->buildColumnsList($board, $columns); require_celerity_resource('project-view-css'); diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php index 3ee0213daa..8ceb576b6f 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php @@ -577,6 +577,11 @@ final class PhabricatorProjectBoardViewController $panel->addHeaderAction($column_menu); if ($column->canHaveTrigger()) { + $trigger = $column->getTrigger(); + if ($trigger) { + $trigger->setViewer($viewer); + } + $trigger_menu = $this->buildTriggerMenu($column); $panel->addHeaderAction($trigger_menu); } diff --git a/src/applications/project/controller/PhabricatorProjectColumnDetailController.php b/src/applications/project/controller/PhabricatorProjectColumnDetailController.php index 781461a812..016999bbe6 100644 --- a/src/applications/project/controller/PhabricatorProjectColumnDetailController.php +++ b/src/applications/project/controller/PhabricatorProjectColumnDetailController.php @@ -51,7 +51,9 @@ final class PhabricatorProjectColumnDetailController $crumbs->addTextCrumb(pht('Column: %s', $title)); $crumbs->setBorder(true); - $nav = $this->getProfileMenu(); + $nav = $this->newNavigation( + $project, + PhabricatorProject::ITEM_WORKBOARD); require_celerity_resource('project-view-css'); $view = id(new PHUITwoColumnView()) diff --git a/src/applications/project/controller/PhabricatorProjectEditPictureController.php b/src/applications/project/controller/PhabricatorProjectEditPictureController.php index e601d744c0..d141dc07e2 100644 --- a/src/applications/project/controller/PhabricatorProjectEditPictureController.php +++ b/src/applications/project/controller/PhabricatorProjectEditPictureController.php @@ -273,8 +273,9 @@ final class PhabricatorProjectEditPictureController ->setHeaderText(pht('Upload New Picture')) ->setForm($upload_form); - $nav = $this->getProfileMenu(); - $nav->selectFilter(PhabricatorProject::ITEM_MANAGE); + $nav = $this->newNavigation( + $project, + PhabricatorProject::ITEM_MANAGE); return $this->newPage() ->setTitle($title) diff --git a/src/applications/project/controller/trigger/PhabricatorProjectTriggerEditController.php b/src/applications/project/controller/trigger/PhabricatorProjectTriggerEditController.php index df362efb61..f8fbbf7356 100644 --- a/src/applications/project/controller/trigger/PhabricatorProjectTriggerEditController.php +++ b/src/applications/project/controller/trigger/PhabricatorProjectTriggerEditController.php @@ -25,6 +25,8 @@ final class PhabricatorProjectTriggerEditController $trigger = PhabricatorProjectTrigger::initializeNewTrigger(); } + $trigger->setViewer($viewer); + $column_phid = $request->getStr('columnPHID'); if ($column_phid) { $column = id(new PhabricatorProjectColumnQuery()) @@ -272,6 +274,10 @@ final class PhabricatorProjectTriggerEditController $rule_list = array_values($rule_list); $type_list = PhabricatorProjectTriggerRule::getAllTriggerRules(); + + foreach ($type_list as $rule) { + $rule->setViewer($this->getViewer()); + } $type_list = mpull($type_list, 'newTemplate'); $type_list = array_values($type_list); diff --git a/src/applications/project/controller/trigger/PhabricatorProjectTriggerViewController.php b/src/applications/project/controller/trigger/PhabricatorProjectTriggerViewController.php index d148c0a421..b5d22f4d72 100644 --- a/src/applications/project/controller/trigger/PhabricatorProjectTriggerViewController.php +++ b/src/applications/project/controller/trigger/PhabricatorProjectTriggerViewController.php @@ -20,6 +20,7 @@ final class PhabricatorProjectTriggerViewController if (!$trigger) { return new Aphront404Response(); } + $trigger->setViewer($viewer); $rules_view = $this->newRulesView($trigger); $columns_view = $this->newColumnsView($trigger); diff --git a/src/applications/project/menuitem/PhabricatorProjectPointsProfileMenuItem.php b/src/applications/project/menuitem/PhabricatorProjectPointsProfileMenuItem.php index d8c7ee82b1..20c8d2985b 100644 --- a/src/applications/project/menuitem/PhabricatorProjectPointsProfileMenuItem.php +++ b/src/applications/project/menuitem/PhabricatorProjectPointsProfileMenuItem.php @@ -165,8 +165,9 @@ final class PhabricatorProjectPointsProfileMenuItem ), $bar); - $item = $this->newItemView() - ->newProgressBar($bar); + $item = $this->newItemView(); + + $item->newProgressBar($bar); return array( $item, diff --git a/src/applications/project/storage/PhabricatorProjectTrigger.php b/src/applications/project/storage/PhabricatorProjectTrigger.php index 625dc7ffd8..d7892dd7a1 100644 --- a/src/applications/project/storage/PhabricatorProjectTrigger.php +++ b/src/applications/project/storage/PhabricatorProjectTrigger.php @@ -13,6 +13,7 @@ final class PhabricatorProjectTrigger protected $editPolicy; private $triggerRules; + private $viewer; private $usage = self::ATTACHABLE; public static function initializeNewTrigger() { @@ -41,6 +42,15 @@ final class PhabricatorProjectTrigger return PhabricatorProjectTriggerPHIDType::TYPECONST; } + public function getViewer() { + return $this->viewer; + } + + public function setViewer(PhabricatorUser $user) { + $this->viewer = $user; + return $this; + } + public function getDisplayName() { $name = $this->getName(); if (strlen($name)) { @@ -72,11 +82,16 @@ final class PhabricatorProjectTrigger parent::setRuleset($ruleset); } - public function getTriggerRules() { + public function getTriggerRules($viewer = null) { if ($this->triggerRules === null) { + if (!$viewer) { + $viewer = $this->getViewer(); + } + $trigger_rules = self::newTriggerRulesFromRuleSpecifications( $this->getRuleset(), - $allow_invalid = true); + $allow_invalid = true, + $viewer); $this->triggerRules = $trigger_rules; } @@ -86,11 +101,12 @@ final class PhabricatorProjectTrigger public static function newTriggerRulesFromRuleSpecifications( array $list, - $allow_invalid) { + $allow_invalid, + PhabricatorUser $viewer) { - // NOTE: With "$allow_invalid" set, we're trying to preserve the database + // NOTE: With "$allow_invalid" set, we're trying to preserve the database // state in the rule structure, even if it includes rule types we don't - // ha ve implementations for, or rules with invalid rule values. + // have implementations for, or rules with invalid rule values. // If an administrator adds or removes extensions which add rules, or // an upgrade affects rule validity, existing rules may become invalid. @@ -124,7 +140,7 @@ final class PhabricatorProjectTrigger if (!is_array($rule)) { throw new PhabricatorProjectTriggerCorruptionException( pht( - 'Trigger ruleset is corrupt: rule (with key "%s") should be a '. + 'Trigger ruleset is corrupt: rule (at index "%s") should be a '. 'rule specification, but is actually "%s".', $key, phutil_describe_type($rule))); @@ -140,7 +156,7 @@ final class PhabricatorProjectTrigger } catch (PhutilTypeCheckException $ex) { throw new PhabricatorProjectTriggerCorruptionException( pht( - 'Trigger ruleset is corrupt: rule (with key "%s") is not a '. + 'Trigger ruleset is corrupt: rule (at index "%s") is not a '. 'valid rule specification: %s', $key, $ex->getMessage())); @@ -179,6 +195,7 @@ final class PhabricatorProjectTrigger ->setRecord($record) ->setException($ex); } + $rule->setViewer($viewer); $trigger_rules[] = $rule; } @@ -206,9 +223,8 @@ final class PhabricatorProjectTrigger $object) { $trigger_xactions = array(); - foreach ($this->getTriggerRules() as $rule) { + foreach ($this->getTriggerRules($viewer) as $rule) { $rule - ->setViewer($viewer) ->setTrigger($this) ->setColumn($column) ->setObject($object); diff --git a/src/applications/project/trigger/PhabricatorProjectTriggerAddProjectsRule.php b/src/applications/project/trigger/PhabricatorProjectTriggerAddProjectsRule.php new file mode 100644 index 0000000000..42e13d9a66 --- /dev/null +++ b/src/applications/project/trigger/PhabricatorProjectTriggerAddProjectsRule.php @@ -0,0 +1,111 @@ +getDatasource()->getWireTokens($this->getValue()); + } + + protected function assertValidRuleRecordFormat($value) { + if (!is_array($value)) { + throw new Exception( + pht( + 'Add project rule value should be a list, but is not '. + '(value is "%s").', + phutil_describe_type($value))); + } + } + + protected function assertValidRuleRecordValue($value) { + if (!$value) { + throw new Exception( + pht( + 'You must select at least one project tag to add.')); + } + } + + protected function newDropTransactions($object, $value) { + $project_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; + + $xaction = $object->getApplicationTransactionTemplate() + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) + ->setMetadataValue('edge:type', $project_edge_type) + ->setNewValue( + array( + '+' => array_fuse($value), + )); + + return array($xaction); + } + + protected function newDropEffects($value) { + return array( + $this->newEffect() + ->setIcon('fa-briefcase') + ->setContent($this->getRuleViewDescription($value)), + ); + } + + protected function getDefaultValue() { + return null; + } + + protected function getPHUIXControlType() { + return 'tokenizer'; + } + + private function getDatasource() { + return id(new PhabricatorProjectDatasource()) + ->setViewer($this->getViewer()); + } + + protected function getPHUIXControlSpecification() { + $template = id(new AphrontTokenizerTemplateView()) + ->setViewer($this->getViewer()); + + $template_markup = $template->render(); + $datasource = $this->getDatasource(); + + return array( + 'markup' => (string)hsprintf('%s', $template_markup), + 'config' => array( + 'src' => $datasource->getDatasourceURI(), + 'browseURI' => $datasource->getBrowseURI(), + 'placeholder' => $datasource->getPlaceholderText(), + 'limit' => $datasource->getLimit(), + ), + 'value' => null, + ); + } + + public function getRuleViewLabel() { + return pht('Add Project Tags'); + } + + public function getRuleViewDescription($value) { + return pht( + 'Add project tags: %s.', + phutil_tag( + 'strong', + array(), + $this->getViewer() + ->renderHandleList($value) + ->setAsInline(true) + ->render())); + } + + public function getRuleViewIcon($value) { + return id(new PHUIIconView()) + ->setIcon('fa-briefcase', 'green'); + } + + + +} diff --git a/src/applications/project/trigger/PhabricatorProjectTriggerInvalidRule.php b/src/applications/project/trigger/PhabricatorProjectTriggerInvalidRule.php index ba53b77e75..07aa6e0792 100644 --- a/src/applications/project/trigger/PhabricatorProjectTriggerInvalidRule.php +++ b/src/applications/project/trigger/PhabricatorProjectTriggerInvalidRule.php @@ -24,7 +24,7 @@ final class PhabricatorProjectTriggerInvalidRule return false; } - protected function assertValidRuleValue($value) { + protected function assertValidRuleRecordFormat($value) { return; } diff --git a/src/applications/project/trigger/PhabricatorProjectTriggerManiphestOwnerRule.php b/src/applications/project/trigger/PhabricatorProjectTriggerManiphestOwnerRule.php new file mode 100644 index 0000000000..1648f1bc8b --- /dev/null +++ b/src/applications/project/trigger/PhabricatorProjectTriggerManiphestOwnerRule.php @@ -0,0 +1,148 @@ +getDatasource()->getWireTokens($this->getValue()); + } + + private function convertTokenizerValueToOwner($value) { + $value = head($value); + if ($value === PhabricatorPeopleNoOwnerDatasource::FUNCTION_TOKEN) { + $value = null; + } + return $value; + } + + protected function assertValidRuleRecordFormat($value) { + if (!is_array($value)) { + throw new Exception( + pht( + 'Owner rule value should be a list, but is not (value is "%s").', + phutil_describe_type($value))); + } + } + + protected function assertValidRuleRecordValue($value) { + if (!$value) { + throw new Exception( + pht( + 'Owner rule value is required. Specify a user to assign tasks '. + 'to, or the token "none()" to unassign tasks.')); + } + + if (count($value) > 1) { + throw new Exception( + pht( + 'Owner rule value must have only one elmement (value is "%s").', + implode(', ', $value))); + } + + $owner_phid = $this->convertTokenizerValueToOwner($value); + if ($owner_phid !== null) { + $user = id(new PhabricatorPeopleQuery()) + ->setViewer($this->getViewer()) + ->withPHIDs(array($owner_phid)) + ->executeOne(); + if (!$user) { + throw new Exception( + pht( + 'User PHID ("%s") is not a valid user.', + $owner_phid)); + } + } + } + + protected function newDropTransactions($object, $value) { + $value = $this->convertTokenizerValueToOwner($value); + return array( + $this->newTransaction() + ->setTransactionType(ManiphestTaskOwnerTransaction::TRANSACTIONTYPE) + ->setNewValue($value), + ); + } + + protected function newDropEffects($value) { + $owner_value = $this->convertTokenizerValueToOwner($value); + + return array( + $this->newEffect() + ->setIcon('fa-user') + ->setContent($this->getRuleViewDescription($value)) + ->addCondition('owner', '!=', $owner_value), + ); + } + + protected function getDefaultValue() { + return null; + } + + protected function getPHUIXControlType() { + return 'tokenizer'; + } + + private function getDatasource() { + $datasource = id(new ManiphestAssigneeDatasource()) + ->setLimit(1); + + if ($this->getViewer()) { + $datasource->setViewer($this->getViewer()); + } + + return $datasource; + } + + protected function getPHUIXControlSpecification() { + $template = id(new AphrontTokenizerTemplateView()) + ->setViewer($this->getViewer()); + + $template_markup = $template->render(); + $datasource = $this->getDatasource(); + + return array( + 'markup' => (string)hsprintf('%s', $template_markup), + 'config' => array( + 'src' => $datasource->getDatasourceURI(), + 'browseURI' => $datasource->getBrowseURI(), + 'placeholder' => $datasource->getPlaceholderText(), + 'limit' => $datasource->getLimit(), + ), + 'value' => null, + ); + } + + public function getRuleViewLabel() { + return pht('Change Owner'); + } + + public function getRuleViewDescription($value) { + $value = $this->convertTokenizerValueToOwner($value); + + if (!$value) { + return pht('Unassign task.'); + } else { + return pht( + 'Assign task to %s.', + phutil_tag( + 'strong', + array(), + $this->getViewer() + ->renderHandle($value) + ->render())); + } + } + + public function getRuleViewIcon($value) { + return id(new PHUIIconView()) + ->setIcon('fa-user', 'green'); + } + + +} diff --git a/src/applications/project/trigger/PhabricatorProjectTriggerManiphestPriorityRule.php b/src/applications/project/trigger/PhabricatorProjectTriggerManiphestPriorityRule.php index 98a03a1393..56b0fc9b1f 100644 --- a/src/applications/project/trigger/PhabricatorProjectTriggerManiphestPriorityRule.php +++ b/src/applications/project/trigger/PhabricatorProjectTriggerManiphestPriorityRule.php @@ -9,20 +9,24 @@ final class PhabricatorProjectTriggerManiphestPriorityRule return pht('Change priority to'); } - protected function assertValidRuleValue($value) { + protected function assertValidRuleRecordFormat($value) { if (!is_string($value)) { throw new Exception( pht( 'Priority rule value should be a string, but is not (value is "%s").', phutil_describe_type($value))); } + } + protected function assertValidRuleRecordValue($value) { $map = ManiphestTaskPriority::getTaskPriorityMap(); if (!isset($map[$value])) { throw new Exception( pht( - 'Rule value ("%s") is not a valid task priority.', - $value)); + 'Task priority value ("%s") is not a valid task priority. '. + 'Valid priorities are: %s.', + $value, + implode(', ', array_keys($map)))); } } @@ -54,7 +58,7 @@ final class PhabricatorProjectTriggerManiphestPriorityRule } protected function getDefaultValue() { - return head_key(ManiphestTaskPriority::getTaskPriorityMap()); + return ManiphestTaskPriority::getDefaultPriority(); } protected function getPHUIXControlType() { diff --git a/src/applications/project/trigger/PhabricatorProjectTriggerManiphestStatusRule.php b/src/applications/project/trigger/PhabricatorProjectTriggerManiphestStatusRule.php index b11d7567de..85d32134e4 100644 --- a/src/applications/project/trigger/PhabricatorProjectTriggerManiphestStatusRule.php +++ b/src/applications/project/trigger/PhabricatorProjectTriggerManiphestStatusRule.php @@ -9,20 +9,24 @@ final class PhabricatorProjectTriggerManiphestStatusRule return pht('Change status to'); } - protected function assertValidRuleValue($value) { + protected function assertValidRuleRecordFormat($value) { if (!is_string($value)) { throw new Exception( pht( 'Status rule value should be a string, but is not (value is "%s").', phutil_describe_type($value))); } + } + protected function assertValidRuleRecordValue($value) { $map = ManiphestTaskStatus::getTaskStatusMap(); if (!isset($map[$value])) { throw new Exception( pht( - 'Rule value ("%s") is not a valid task status.', - $value)); + 'Task status value ("%s") is not a valid task status. '. + 'Valid statues are: %s.', + $value, + implode(', ', array_keys($map)))); } } @@ -53,7 +57,7 @@ final class PhabricatorProjectTriggerManiphestStatusRule } protected function getDefaultValue() { - return head_key(ManiphestTaskStatus::getTaskStatusMap()); + return ManiphestTaskStatus::getDefaultClosedStatus(); } protected function getPHUIXControlType() { diff --git a/src/applications/project/trigger/PhabricatorProjectTriggerPlaySoundRule.php b/src/applications/project/trigger/PhabricatorProjectTriggerPlaySoundRule.php index ef19b504ef..9e99eb070f 100644 --- a/src/applications/project/trigger/PhabricatorProjectTriggerPlaySoundRule.php +++ b/src/applications/project/trigger/PhabricatorProjectTriggerPlaySoundRule.php @@ -9,20 +9,21 @@ final class PhabricatorProjectTriggerPlaySoundRule return pht('Play sound'); } - protected function assertValidRuleValue($value) { + protected function assertValidRuleRecordFormat($value) { if (!is_string($value)) { throw new Exception( pht( 'Status rule value should be a string, but is not (value is "%s").', phutil_describe_type($value))); } + } + protected function assertValidRuleRecordValue($value) { $map = self::getSoundMap(); - if (!isset($map[$value])) { throw new Exception( pht( - 'Rule value ("%s") is not a valid sound.', + 'Sound ("%s") is not a valid sound.', $value)); } } diff --git a/src/applications/project/trigger/PhabricatorProjectTriggerRemoveProjectsRule.php b/src/applications/project/trigger/PhabricatorProjectTriggerRemoveProjectsRule.php new file mode 100644 index 0000000000..42b6263ef6 --- /dev/null +++ b/src/applications/project/trigger/PhabricatorProjectTriggerRemoveProjectsRule.php @@ -0,0 +1,111 @@ +getDatasource()->getWireTokens($this->getValue()); + } + + protected function assertValidRuleRecordFormat($value) { + if (!is_array($value)) { + throw new Exception( + pht( + 'Remove project rule value should be a list, but is not '. + '(value is "%s").', + phutil_describe_type($value))); + } + } + + protected function assertValidRuleRecordValue($value) { + if (!$value) { + throw new Exception( + pht( + 'You must select at least one project tag to remove.')); + } + } + + protected function newDropTransactions($object, $value) { + $project_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; + + $xaction = $object->getApplicationTransactionTemplate() + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) + ->setMetadataValue('edge:type', $project_edge_type) + ->setNewValue( + array( + '-' => array_fuse($value), + )); + + return array($xaction); + } + + protected function newDropEffects($value) { + return array( + $this->newEffect() + ->setIcon('fa-briefcase', 'red') + ->setContent($this->getRuleViewDescription($value)), + ); + } + + protected function getDefaultValue() { + return null; + } + + protected function getPHUIXControlType() { + return 'tokenizer'; + } + + private function getDatasource() { + return id(new PhabricatorProjectDatasource()) + ->setViewer($this->getViewer()); + } + + protected function getPHUIXControlSpecification() { + $template = id(new AphrontTokenizerTemplateView()) + ->setViewer($this->getViewer()); + + $template_markup = $template->render(); + $datasource = $this->getDatasource(); + + return array( + 'markup' => (string)hsprintf('%s', $template_markup), + 'config' => array( + 'src' => $datasource->getDatasourceURI(), + 'browseURI' => $datasource->getBrowseURI(), + 'placeholder' => $datasource->getPlaceholderText(), + 'limit' => $datasource->getLimit(), + ), + 'value' => null, + ); + } + + public function getRuleViewLabel() { + return pht('Remove Project Tags'); + } + + public function getRuleViewDescription($value) { + return pht( + 'Remove project tags: %s.', + phutil_tag( + 'strong', + array(), + $this->getViewer() + ->renderHandleList($value) + ->setAsInline(true) + ->render())); + } + + public function getRuleViewIcon($value) { + return id(new PHUIIconView()) + ->setIcon('fa-briefcase', 'red'); + } + + + +} diff --git a/src/applications/project/trigger/PhabricatorProjectTriggerRule.php b/src/applications/project/trigger/PhabricatorProjectTriggerRule.php index ae2b3ee092..58c7cd1f1a 100644 --- a/src/applications/project/trigger/PhabricatorProjectTriggerRule.php +++ b/src/applications/project/trigger/PhabricatorProjectTriggerRule.php @@ -23,7 +23,7 @@ abstract class PhabricatorProjectTriggerRule final public function setRecord(PhabricatorProjectTriggerRuleRecord $record) { $value = $record->getValue(); - $this->assertValidRuleValue($value); + $this->assertValidRuleRecordFormat($value); $this->record = $record; return $this; @@ -37,11 +37,30 @@ abstract class PhabricatorProjectTriggerRule return $this->getRecord()->getValue(); } + protected function getValueForEditorField() { + return $this->getValue(); + } + abstract public function getSelectControlName(); abstract public function getRuleViewLabel(); abstract public function getRuleViewDescription($value); abstract public function getRuleViewIcon($value); - abstract protected function assertValidRuleValue($value); + abstract protected function assertValidRuleRecordFormat($value); + + final public function getRuleRecordValueValidationException() { + try { + $this->assertValidRuleRecordValue($this->getRecord()->getValue()); + } catch (Exception $ex) { + return $ex; + } + + return null; + } + + protected function assertValidRuleRecordValue($value) { + return; + } + abstract protected function newDropTransactions($object, $value); abstract protected function newDropEffects($value); abstract protected function getDefaultValue(); @@ -130,7 +149,7 @@ abstract class PhabricatorProjectTriggerRule return array( 'type' => $record->getType(), - 'value' => $record->getValue(), + 'value' => $this->getValueForEditorField(), 'isValidRule' => $is_valid, 'invalidView' => $invalid_view, ); diff --git a/src/applications/project/trigger/PhabricatorProjectTriggerUnknownRule.php b/src/applications/project/trigger/PhabricatorProjectTriggerUnknownRule.php index 925a369bae..d354b01eaf 100644 --- a/src/applications/project/trigger/PhabricatorProjectTriggerUnknownRule.php +++ b/src/applications/project/trigger/PhabricatorProjectTriggerUnknownRule.php @@ -13,7 +13,7 @@ final class PhabricatorProjectTriggerUnknownRule return false; } - protected function assertValidRuleValue($value) { + protected function assertValidRuleRecordFormat($value) { return; } diff --git a/src/applications/project/xaction/column/PhabricatorProjectColumnNameTransaction.php b/src/applications/project/xaction/column/PhabricatorProjectColumnNameTransaction.php index bff54277de..4a7342dc5f 100644 --- a/src/applications/project/xaction/column/PhabricatorProjectColumnNameTransaction.php +++ b/src/applications/project/xaction/column/PhabricatorProjectColumnNameTransaction.php @@ -41,7 +41,10 @@ final class PhabricatorProjectColumnNameTransaction if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { // The default "Backlog" column is allowed to be unnamed, which // means we use the default name. - if (!$object->isDefaultColumn()) { + + // Proxy columns can't have a name, so don't raise an error here. + + if (!$object->isDefaultColumn() && !$object->getProxy()) { $errors[] = $this->newRequiredError( pht('Columns must have a name.')); } diff --git a/src/applications/project/xaction/trigger/PhabricatorProjectTriggerRulesetTransaction.php b/src/applications/project/xaction/trigger/PhabricatorProjectTriggerRulesetTransaction.php index 59c846becf..0f3c1233c6 100644 --- a/src/applications/project/xaction/trigger/PhabricatorProjectTriggerRulesetTransaction.php +++ b/src/applications/project/xaction/trigger/PhabricatorProjectTriggerRulesetTransaction.php @@ -20,15 +20,18 @@ final class PhabricatorProjectTriggerRulesetTransaction } public function validateTransactions($object, array $xactions) { + $actor = $this->getActor(); $errors = array(); foreach ($xactions as $xaction) { $ruleset = $xaction->getNewValue(); try { - PhabricatorProjectTrigger::newTriggerRulesFromRuleSpecifications( - $ruleset, - $allow_invalid = false); + $rules = + PhabricatorProjectTrigger::newTriggerRulesFromRuleSpecifications( + $ruleset, + $allow_invalid = false, + $actor); } catch (PhabricatorProjectTriggerCorruptionException $ex) { $errors[] = $this->newInvalidError( pht( @@ -37,6 +40,19 @@ final class PhabricatorProjectTriggerRulesetTransaction $xaction); continue; } + + foreach ($rules as $rule) { + $exception = $rule->getRuleRecordValueValidationException(); + if ($exception) { + $errors[] = $this->newInvalidError( + pht( + 'Value for "%s" rule is invalid: %s', + $rule->getSelectControlName(), + $exception->getMessage()), + $xaction); + continue; + } + } } return $errors; diff --git a/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php b/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php index 8e99aabafa..038fa416f8 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php @@ -127,10 +127,7 @@ final class PhabricatorRepositoryDiscoveryEngine */ private function discoverGitCommits() { $repository = $this->getRepository(); - - if (!$repository->isHosted()) { - $this->verifyGitOrigin($repository); - } + $publisher = $repository->newPublisher(); $heads = id(new DiffusionLowLevelGitRefQuery()) ->setRepository($repository) @@ -189,7 +186,7 @@ final class PhabricatorRepositoryDiscoveryEngine $head_refs = $this->discoverStreamAncestry( new PhabricatorGitGraphStream($repository, $commit), $commit, - $repository->shouldAutocloseRef($ref)); + $publisher->shouldPublishRef($ref)); $this->didDiscoverRefs($head_refs); @@ -518,11 +515,12 @@ final class PhabricatorRepositoryDiscoveryEngine */ private function sortRefs(array $refs) { $repository = $this->getRepository(); + $publisher = $repository->newPublisher(); $head_refs = array(); $tail_refs = array(); foreach ($refs as $ref) { - if ($repository->shouldAutocloseRef($ref)) { + if ($publisher->shouldPublishRef($ref)) { $head_refs[] = $ref; } else { $tail_refs[] = $ref; diff --git a/src/applications/repository/engine/PhabricatorRepositoryEngine.php b/src/applications/repository/engine/PhabricatorRepositoryEngine.php index fed5b09521..12cd8f1ed6 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryEngine.php @@ -70,123 +70,6 @@ abstract class PhabricatorRepositoryEngine extends Phobject { return PhabricatorGlobalLock::newLock($lock_key, $lock_parts); } - - /** - * Verify that the "origin" remote exists, and points at the correct URI. - * - * This catches or corrects some types of misconfiguration, and also repairs - * an issue where Git 1.7.1 does not create an "origin" for `--bare` clones. - * See T4041. - * - * @param PhabricatorRepository Repository to verify. - * @return void - */ - protected function verifyGitOrigin(PhabricatorRepository $repository) { - try { - list($remotes) = $repository->execxLocalCommand( - 'remote show -n origin'); - } catch (CommandException $ex) { - throw new PhutilProxyException( - pht( - 'Expected to find a Git working copy at path "%s", but the '. - 'path exists and is not a valid working copy. If you remove '. - 'this directory, the daemons will automatically recreate it '. - 'correctly. Phabricator will not destroy the directory for you '. - 'because it can not be sure that it does not contain important '. - 'data.', - $repository->getLocalPath()), - $ex); - } - - $matches = null; - if (!preg_match('/^\s*Fetch URL:\s*(.*?)\s*$/m', $remotes, $matches)) { - throw new Exception( - pht( - "Expected '%s' in '%s'.", - 'Fetch URL', - 'git remote show -n origin')); - } - - $remote_uri = $matches[1]; - $expect_remote = $repository->getRemoteURI(); - - if ($remote_uri == 'origin') { - // If a remote does not exist, git pretends it does and prints out a - // made up remote where the URI is the same as the remote name. This is - // definitely not correct. - - // Possibly, we should use `git remote --verbose` instead, which does not - // suffer from this problem (but is a little more complicated to parse). - $valid = false; - $exists = false; - } else { - $normal_type_git = PhabricatorRepositoryURINormalizer::TYPE_GIT; - - $remote_normal = id(new PhabricatorRepositoryURINormalizer( - $normal_type_git, - $remote_uri))->getNormalizedPath(); - - $expect_normal = id(new PhabricatorRepositoryURINormalizer( - $normal_type_git, - $expect_remote))->getNormalizedPath(); - - $valid = ($remote_normal == $expect_normal); - $exists = true; - } - - // These URIs may have plaintext HTTP credentials. If they do, censor - // them for display. See T12945. - $display_remote = phutil_censor_credentials($remote_uri); - $display_expect = phutil_censor_credentials($expect_remote); - - if (!$valid) { - if (!$exists) { - // If there's no "origin" remote, just create it regardless of how - // strongly we own the working copy. There is almost no conceivable - // scenario in which this could do damage. - $this->log( - pht( - 'Remote "origin" does not exist. Creating "origin", with '. - 'URI "%s".', - $expect_remote)); - $repository->execxLocalCommand( - 'remote add origin %P', - $repository->getRemoteURIEnvelope()); - - // NOTE: This doesn't fetch the origin (it just creates it), so we won't - // know about origin branches until the next "pull" happens. That's fine - // for our purposes, but might impact things in the future. - } else { - if ($repository->canDestroyWorkingCopy()) { - // Bad remote, but we can try to repair it. - $this->log( - pht( - 'Remote "origin" exists, but is pointed at the wrong URI, "%s". '. - 'Resetting origin URI to "%s.', - $remote_uri, - $expect_remote)); - $repository->execxLocalCommand( - 'remote set-url origin %P', - $repository->getRemoteURIEnvelope()); - } else { - // Bad remote and we aren't comfortable repairing it. - $message = pht( - 'Working copy at "%s" has a mismatched origin URI, "%s". '. - 'The expected origin URI is "%s". Fix your configuration, or '. - 'set the remote URI correctly. To avoid breaking anything, '. - 'Phabricator will not automatically fix this.', - $repository->getLocalPath(), - $display_remote, - $display_expect); - throw new Exception($message); - } - } - } - } - - - - /** * @task internal */ diff --git a/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php index 208a3792b3..2c6ac8e83f 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php @@ -122,7 +122,6 @@ final class PhabricatorRepositoryPullEngine $repository->getDisplayName())); if ($is_git) { - $this->verifyGitOrigin($repository); $this->executeGitUpdate(); } else if ($is_hg) { $this->executeMercurialUpdate(); @@ -143,6 +142,10 @@ final class PhabricatorRepositoryPullEngine } } + if ($is_git) { + $this->updateGitWorkingCopyConfiguration(); + } + } catch (Exception $ex) { $this->abortPull( pht( @@ -340,8 +343,17 @@ final class PhabricatorRepositoryPullEngine throw new Exception($message); } - $remote_refs = $this->loadGitRemoteRefs($repository); - $local_refs = $this->loadGitLocalRefs($repository); + // Load the refs we're planning to fetch from the remote repository. + $remote_refs = $this->loadGitRemoteRefs( + $repository, + $repository->getRemoteURIEnvelope()); + + // Load the refs we're planning to fetch from the local repository, by + // using the local working copy path as the "remote" repository URI. + $local_refs = $this->loadGitRemoteRefs( + $repository, + new PhutilOpaqueEnvelope($path)); + if ($remote_refs === $local_refs) { $this->log( pht( @@ -352,26 +364,53 @@ final class PhabricatorRepositoryPullEngine $this->logRefDifferences($remote_refs, $local_refs); - // Force the "origin" URI to the configured value. - $repository->execxLocalCommand( - 'remote set-url origin -- %P', - $repository->getRemoteURIEnvelope()); + $fetch_rules = $this->getGitFetchRules($repository); - if ($repository->isWorkingCopyBare()) { - // For bare working copies, we need this magic incantation. - $future = $repository->getRemoteCommandFuture( - 'fetch origin %s --prune', - '+refs/*:refs/*'); - } else { - $future = $repository->getRemoteCommandFuture( - 'fetch --all --prune'); - } + // For very old non-bare working copies, we need to use "--update-head-ok" + // to tell Git that it is allowed to overwrite whatever is currently + // checked out. See T13280. + + $future = $repository->getRemoteCommandFuture( + 'fetch --prune --update-head-ok -- %P %Ls', + $repository->getRemoteURIEnvelope(), + $fetch_rules); $future ->setCWD($path) ->resolvex(); } + private function getGitRefRules(PhabricatorRepository $repository) { + $ref_rules = $repository->getFetchRules($repository); + + if (!$ref_rules) { + $ref_rules = array( + 'refs/*', + ); + } + + return $ref_rules; + } + + private function getGitFetchRules(PhabricatorRepository $repository) { + $ref_rules = $this->getGitRefRules($repository); + + // Rewrite each ref rule "X" into "+X:X". + + // The "X" means "fetch ref X". + // The "...:X" means "...and copy it into local ref X". + // The "+..." means "...and overwrite the local ref if it already exists". + + $fetch_rules = array(); + foreach ($ref_rules as $key => $ref_rule) { + $fetch_rules[] = sprintf( + '+%s:%s', + $ref_rule, + $ref_rule); + } + + return $fetch_rules; + } /** * @task git @@ -389,15 +428,67 @@ final class PhabricatorRepositoryPullEngine $this->installHook($root.$path); } - private function loadGitRemoteRefs(PhabricatorRepository $repository) { - $remote_envelope = $repository->getRemoteURIEnvelope(); + private function updateGitWorkingCopyConfiguration() { + $repository = $this->getRepository(); + + // See T5963. When you "git clone" from a remote with no "master", the + // client warns you that it isn't sure what it should check out as an + // initial state: + + // warning: remote HEAD refers to nonexistent ref, unable to checkout + + // We can tell the client what it should check out by making "HEAD" + // point somewhere. However: + // + // (1) If we don't set "receive.denyDeleteCurrent" to "ignore" and a user + // tries to delete the default branch, Git raises an error and refuses. + // We want to allow this; we already have sufficient protections around + // dangerous changes and do not need to special case the default branch. + // + // (2) A repository may have a nonexistent default branch configured. + // For now, we just respect configuration. This will raise a warning when + // users clone the repository. + // + // In any case, these changes are both advisory, so ignore any errors we + // may encounter. + + // We do this for both hosted and observed repositories. Although it is + // not terribly common to clone from Phabricator's copy of an observed + // repository, it works fine and makes sense occasionally. + + if ($repository->isWorkingCopyBare()) { + $repository->execLocalCommand( + 'config -- receive.denyDeleteCurrent ignore'); + $repository->execLocalCommand( + 'symbolic-ref HEAD %s', + 'refs/heads/'.$repository->getDefaultBranch()); + } + } + + private function loadGitRemoteRefs( + PhabricatorRepository $repository, + PhutilOpaqueEnvelope $remote_envelope) { + + $ref_rules = $this->getGitRefRules($repository); // NOTE: "git ls-remote" does not support "--" until circa January 2016. - // See T12416. None of the flags to "ls-remote" appear dangerous, and - // other checks make it difficult to configure a suspicious remote URI. + // See T12416. None of the flags to "ls-remote" appear dangerous, but + // refuse to list any refs beginning with "-" just in case. + + foreach ($ref_rules as $ref_rule) { + if (preg_match('/^-/', $ref_rule)) { + throw new Exception( + pht( + 'Refusing to list potentially dangerous ref ("%s") beginning '. + 'with "-".', + $ref_rule)); + } + } + list($stdout) = $repository->execxRemoteCommand( - 'ls-remote %P', - $remote_envelope); + 'ls-remote %P %Ls', + $remote_envelope, + $ref_rules); // Empty repositories don't have any refs. if (!strlen(rtrim($stdout))) { diff --git a/src/applications/repository/engine/PhabricatorRepositoryRefEngine.php b/src/applications/repository/engine/PhabricatorRepositoryRefEngine.php index 4b1cd829d5..72fb683401 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryRefEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryRefEngine.php @@ -347,7 +347,7 @@ final class PhabricatorRepositoryRefEngine return false; } - return $this->getRepository()->shouldAutocloseBranch($ref_name); + return $this->getRepository()->isBranchPermanentRef($ref_name); } /** @@ -455,7 +455,7 @@ final class PhabricatorRepositoryRefEngine $all_commits = queryfx_all( $conn_w, - 'SELECT id, commitIdentifier, importStatus FROM %T + 'SELECT id, phid, commitIdentifier, importStatus FROM %T WHERE repositoryID = %d AND commitIdentifier IN (%Ls)', $commit_table->getTableName(), $repository->getID(), @@ -485,10 +485,15 @@ final class PhabricatorRepositoryRefEngine $data = array( 'commitID' => $row['id'], - 'only' => true, ); - PhabricatorWorker::scheduleTask($class, $data); + PhabricatorWorker::scheduleTask( + $class, + $data, + array( + 'priority' => PhabricatorWorker::PRIORITY_COMMIT, + 'objectPHID' => $row['phid'], + )); } } diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementReparseWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementReparseWorkflow.php index 8b89075219..6a2384206e 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementReparseWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementReparseWorkflow.php @@ -88,13 +88,6 @@ final class PhabricatorRepositoryManagementReparseWorkflow 'help' => pht( 'Reparse all steps which have not yet completed.'), ), - array( - 'name' => 'force-autoclose', - 'help' => pht( - 'Only used with __%s__, use this to make sure any '. - 'pertinent diffs are closed regardless of configuration.', - '--message'), - ), )); } @@ -307,23 +300,25 @@ final class PhabricatorRepositoryManagementReparseWorkflow $spec = array( 'commitID' => $commit->getID(), 'only' => !$importing, - 'forceAutoclose' => $args->getArg('force-autoclose'), ); if ($all_from_repo && !$force_local) { - foreach ($classes as $class) { - PhabricatorWorker::scheduleTask( - $class, - $spec, - array( - 'priority' => PhabricatorWorker::PRIORITY_IMPORT, - )); - } + $background = true; } else { - foreach ($classes as $class) { - $worker = newv($class, array($spec)); - $worker->executeTask(); - } + $background = false; + } + + if (!$background) { + PhabricatorWorker::setRunAllTasksInProcess(true); + } + + foreach ($classes as $class) { + PhabricatorWorker::scheduleTask( + $class, + $spec, + array( + 'priority' => PhabricatorWorker::PRIORITY_IMPORT, + )); } $progress->update(1); diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementUnpublishWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementUnpublishWorkflow.php index 5a6afb8c6e..418030864d 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementUnpublishWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementUnpublishWorkflow.php @@ -241,15 +241,13 @@ final class PhabricatorRepositoryManagementUnpublishWorkflow if ($xactions) { foreach ($xactions as $xaction) { $metadata = $xaction->getMetadata(); - if (idx($metadata, 'isCommitClose')) { - if (idx($metadata, 'commitPHID') === $src->getPHID()) { - echo tsprintf( - "%s\n", - pht( - 'MANUAL Revision "%s" was likely closed improperly by "%s".', - $dst->getMonogram(), - $src->getMonogram())); - } + if (idx($metadata, 'commitPHID') === $src->getPHID()) { + echo tsprintf( + "%s\n", + pht( + 'MANUAL Revision "%s" was likely closed improperly by "%s".', + $dst->getMonogram(), + $src->getMonogram())); } } } diff --git a/src/applications/repository/query/PhabricatorRepositoryPublisher.php b/src/applications/repository/query/PhabricatorRepositoryPublisher.php new file mode 100644 index 0000000000..43feaf6bf0 --- /dev/null +++ b/src/applications/repository/query/PhabricatorRepositoryPublisher.php @@ -0,0 +1,120 @@ +repository = $repository; + return $this; + } + + public function getRepository() { + if (!$this->repository) { + throw new PhutilInvalidStateException('setRepository'); + } + return $this->repository; + } + +/* -( Publishing )--------------------------------------------------------- */ + + public function shouldPublishRepository() { + return !$this->getRepositoryHoldReasons(); + } + + public function shouldPublishRef(DiffusionRepositoryRef $ref) { + return !$this->getRefHoldReasons($ref); + } + + public function shouldPublishCommit(PhabricatorRepositoryCommit $commit) { + return !$this->getCommitHoldReasons($commit); + } + +/* -( Hold Reasons )------------------------------------------------------- */ + + public function getRepositoryHoldReasons() { + $repository = $this->getRepository(); + + $reasons = array(); + if ($repository->isImporting()) { + $reasons[] = self::HOLD_IMPORTING; + } + + if ($repository->isPublishingDisabled()) { + $reasons[] = self::HOLD_PUBLISHING_DISABLED; + } + + return $reasons; + } + + public function getRefHoldReasons(DiffusionRepositoryRef $ref) { + $repository = $this->getRepository(); + $reasons = $this->getRepositoryHoldReasons(); + + if (!$ref->isBranch()) { + $reasons[] = self::HOLD_REF_NOT_BRANCH; + } else { + $branch_name = $ref->getShortName(); + + if (!$repository->shouldTrackBranch($branch_name)) { + $reasons[] = self::HOLD_UNTRACKED; + } + + if (!$repository->isBranchPermanentRef($branch_name)) { + $reasons[] = self::HOLD_NOT_PERMANENT_REF; + } + } + + return $reasons; + } + + public function getCommitHoldReasons(PhabricatorRepositoryCommit $commit) { + $repository = $this->getRepository(); + $reasons = $this->getRepositoryHoldReasons(); + + if ($repository->isGit()) { + if (!$commit->isPermanentCommit()) { + $reasons[] = self::HOLD_NOT_REACHABLE_FROM_PERMANENT_REF; + } + } + + return $reasons; + } + +/* -( Rendering )---------------------------------------------------------- */ + + public function getHoldName($hold) { + $map = array( + self::HOLD_IMPORTING => array( + 'name' => pht('Repository Importing'), + ), + self::HOLD_PUBLISHING_DISABLED => array( + 'name' => pht('Repository Publishing Disabled'), + ), + self::HOLD_REF_NOT_BRANCH => array( + 'name' => pht('Not a Branch'), + ), + self::HOLD_NOT_REACHABLE_FROM_PERMANENT_REF => array( + 'name' => pht('Not Reachable from Permanent Ref'), + ), + self::HOLD_UNTRACKED => array( + 'name' => pht('Untracked Ref'), + ), + self::HOLD_NOT_PERMANENT_REF => array( + 'name' => pht('Not a Permanent Ref'), + ), + ); + + $spec = idx($map, $hold, array()); + return idx($spec, 'name', pht('Unknown ("%s")', $hold)); + } + +} diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index d5ad83b6d6..129745e101 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -2,7 +2,7 @@ /** * @task uri Repository URI Management - * @task autoclose Autoclose + * @task publishing Publishing * @task sync Cluster Synchronization */ final class PhabricatorRepository extends PhabricatorRepositoryDAO @@ -42,13 +42,6 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO const TABLE_PARENTS = 'repository_parents'; const TABLE_COVERAGE = 'repository_coverage'; - const BECAUSE_REPOSITORY_IMPORTING = 'auto/importing'; - const BECAUSE_AUTOCLOSE_DISABLED = 'auto/disabled'; - const BECAUSE_NOT_ON_AUTOCLOSE_BRANCH = 'auto/nobranch'; - const BECAUSE_BRANCH_UNTRACKED = 'auto/notrack'; - const BECAUSE_BRANCH_NOT_AUTOCLOSE = 'auto/noclose'; - const BECAUSE_AUTOCLOSE_FORCED = 'auto/forced'; - const STATUS_ACTIVE = 'active'; const STATUS_INACTIVE = 'inactive'; @@ -953,6 +946,10 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO return $this->isBranchInFilter($branch, 'branch-filter'); } + public function isBranchPermanentRef($branch) { + return $this->isBranchInFilter($branch, 'close-commits-filter'); + } + public function formatCommitName($commit_identifier, $local = false) { $vcs = $this->getVersionControlSystem(); @@ -1037,156 +1034,22 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO return $ratio; } - /** - * Should this repository publish feed, notifications, audits, and email? - * - * We do not publish information about repositories during initial import, - * or if the repository has been set not to publish. - */ - public function shouldPublish() { - if ($this->isImporting()) { - return false; - } +/* -( Publishing )--------------------------------------------------------- */ - if ($this->getDetail('herald-disabled')) { - return false; - } - - return true; + public function newPublisher() { + return id(new PhabricatorRepositoryPublisher()) + ->setRepository($this); } - -/* -( Autoclose )---------------------------------------------------------- */ - - - public function shouldAutocloseRef(DiffusionRepositoryRef $ref) { - if (!$ref->isBranch()) { - return false; - } - - return $this->shouldAutocloseBranch($ref->getShortName()); + public function isPublishingDisabled() { + return $this->getDetail('herald-disabled'); } - /** - * Determine if autoclose is active for a branch. - * - * For more details about why, use @{method:shouldSkipAutocloseBranch}. - * - * @param string Branch name to check. - * @return bool True if autoclose is active for the branch. - * @task autoclose - */ - public function shouldAutocloseBranch($branch) { - return ($this->shouldSkipAutocloseBranch($branch) === null); - } - - /** - * Determine if autoclose is active for a commit. - * - * For more details about why, use @{method:shouldSkipAutocloseCommit}. - * - * @param PhabricatorRepositoryCommit Commit to check. - * @return bool True if autoclose is active for the commit. - * @task autoclose - */ - public function shouldAutocloseCommit(PhabricatorRepositoryCommit $commit) { - return ($this->shouldSkipAutocloseCommit($commit) === null); - } - - - /** - * Determine why autoclose should be skipped for a branch. - * - * This method gives a detailed reason why autoclose will be skipped. To - * perform a simple test, use @{method:shouldAutocloseBranch}. - * - * @param string Branch name to check. - * @return const|null Constant identifying reason to skip this branch, or null - * if autoclose is active. - * @task autoclose - */ - public function shouldSkipAutocloseBranch($branch) { - $all_reason = $this->shouldSkipAllAutoclose(); - if ($all_reason) { - return $all_reason; - } - - if (!$this->shouldTrackBranch($branch)) { - return self::BECAUSE_BRANCH_UNTRACKED; - } - - if (!$this->isBranchInFilter($branch, 'close-commits-filter')) { - return self::BECAUSE_BRANCH_NOT_AUTOCLOSE; - } - - return null; - } - - - /** - * Determine why autoclose should be skipped for a commit. - * - * This method gives a detailed reason why autoclose will be skipped. To - * perform a simple test, use @{method:shouldAutocloseCommit}. - * - * @param PhabricatorRepositoryCommit Commit to check. - * @return const|null Constant identifying reason to skip this commit, or null - * if autoclose is active. - * @task autoclose - */ - public function shouldSkipAutocloseCommit( - PhabricatorRepositoryCommit $commit) { - - $all_reason = $this->shouldSkipAllAutoclose(); - if ($all_reason) { - return $all_reason; - } - - switch ($this->getVersionControlSystem()) { - case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: - case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: - return null; - case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: - break; - default: - throw new Exception(pht('Unrecognized version control system.')); - } - - $closeable_flag = PhabricatorRepositoryCommit::IMPORTED_CLOSEABLE; - if (!$commit->isPartiallyImported($closeable_flag)) { - return self::BECAUSE_NOT_ON_AUTOCLOSE_BRANCH; - } - - return null; - } - - - /** - * Determine why all autoclose operations should be skipped for this - * repository. - * - * @return const|null Constant identifying reason to skip all autoclose - * operations, or null if autoclose operations are not blocked at the - * repository level. - * @task autoclose - */ - private function shouldSkipAllAutoclose() { - if ($this->isImporting()) { - return self::BECAUSE_REPOSITORY_IMPORTING; - } - - if ($this->getDetail('disable-autoclose', false)) { - return self::BECAUSE_AUTOCLOSE_DISABLED; - } - - return null; - } - - public function getAutocloseOnlyRules() { + public function getPermanentRefRules() { return array_keys($this->getDetail('close-commits-filter', array())); } - public function setAutocloseOnlyRules(array $rules) { + public function setPermanentRefRules(array $rules) { $rules = array_fill_keys($rules, true); $this->setDetail('close-commits-filter', $rules); return $this; @@ -1202,6 +1065,22 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO return $this; } + public function supportsFetchRules() { + if ($this->isGit()) { + return true; + } + + return false; + } + + public function getFetchRules() { + return $this->getDetail('fetch-rules', array()); + } + + public function setFetchRules(array $rules) { + return $this->setDetail('fetch-rules', $rules); + } + /* -( Repository URI Management )------------------------------------------ */ @@ -2803,10 +2682,24 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO pht( 'The Almanac Service that hosts this repository, if the '. 'repository is clustered.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('refRules') + ->setType('map>') + ->setDescription( + pht( + 'The "Fetch" and "Permanent Ref" rules for this repository.')), ); } public function getFieldValuesForConduit() { + $fetch_rules = $this->getFetchRules(); + $track_rules = $this->getTrackOnlyRules(); + $permanent_rules = $this->getPermanentRefRules(); + + $fetch_rules = $this->getStringListForConduit($fetch_rules); + $track_rules = $this->getStringListForConduit($track_rules); + $permanent_rules = $this->getStringListForConduit($permanent_rules); + return array( 'name' => $this->getName(), 'vcs' => $this->getVersionControlSystem(), @@ -2815,9 +2708,29 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO 'status' => $this->getStatus(), 'isImporting' => (bool)$this->isImporting(), 'almanacServicePHID' => $this->getAlmanacServicePHID(), + 'refRules' => array( + 'fetchRules' => $fetch_rules, + 'trackRules' => $track_rules, + 'permanentRefRules' => $permanent_rules, + ), ); } + private function getStringListForConduit($list) { + if (!is_array($list)) { + $list = array(); + } + + foreach ($list as $key => $value) { + $value = (string)$value; + if (!strlen($value)) { + unset($list[$key]); + } + } + + return array_values($list); + } + public function getConduitSearchAttachments() { return array( id(new DiffusionRepositoryURIsSearchEngineAttachment()) diff --git a/src/applications/repository/storage/PhabricatorRepositoryCommit.php b/src/applications/repository/storage/PhabricatorRepositoryCommit.php index fecb1762bd..cc4745a754 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryCommit.php +++ b/src/applications/repository/storage/PhabricatorRepositoryCommit.php @@ -496,6 +496,10 @@ final class PhabricatorRepositoryCommit return $this->getAuditStatusObject()->isAudited(); } + public function isPermanentCommit() { + return (bool)$this->isPartiallyImported(self::IMPORTED_CLOSEABLE); + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { diff --git a/src/applications/repository/storage/PhabricatorRepositoryCommitData.php b/src/applications/repository/storage/PhabricatorRepositoryCommitData.php index 3a4c3a75f5..e4dd62a5f8 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryCommitData.php +++ b/src/applications/repository/storage/PhabricatorRepositoryCommitData.php @@ -68,4 +68,23 @@ final class PhabricatorRepositoryCommitData extends PhabricatorRepositoryDAO { ->loadFromArray($dict); } + public function getPublisherHoldReasons() { + $holds = $this->getCommitDetail('holdReasons'); + + // Look for the legacy "autocloseReason" if we don't have a modern list + // of hold reasons. + if (!$holds) { + $old_hold = $this->getCommitDetail('autocloseReason'); + if ($old_hold) { + $holds = array($old_hold); + } + } + + if (!$holds) { + $holds = array(); + } + + return $holds; + } + } diff --git a/src/applications/repository/storage/PhabricatorRepositoryPushLog.php b/src/applications/repository/storage/PhabricatorRepositoryPushLog.php index cfb5402c2a..095c227994 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryPushLog.php +++ b/src/applications/repository/storage/PhabricatorRepositoryPushLog.php @@ -17,6 +17,7 @@ final class PhabricatorRepositoryPushLog const REFTYPE_TAG = 'tag'; const REFTYPE_BOOKMARK = 'bookmark'; const REFTYPE_COMMIT = 'commit'; + const REFTYPE_REF = 'ref'; const CHANGEFLAG_ADD = 1; const CHANGEFLAG_DELETE = 2; diff --git a/src/applications/repository/worker/PhabricatorRepositoryCommitOwnersWorker.php b/src/applications/repository/worker/PhabricatorRepositoryCommitOwnersWorker.php index d5054a7f18..08842515f3 100644 --- a/src/applications/repository/worker/PhabricatorRepositoryCommitOwnersWorker.php +++ b/src/applications/repository/worker/PhabricatorRepositoryCommitOwnersWorker.php @@ -30,7 +30,8 @@ final class PhabricatorRepositoryCommitOwnersWorker PhabricatorRepositoryCommit $commit) { $viewer = PhabricatorUser::getOmnipotentUser(); - if (!$repository->shouldPublish()) { + $publisher = $repository->newPublisher(); + if (!$publisher->shouldPublishCommit($commit)) { return; } diff --git a/src/applications/repository/worker/PhabricatorRepositoryPushMailWorker.php b/src/applications/repository/worker/PhabricatorRepositoryPushMailWorker.php index 7459073ca8..f6958b7863 100644 --- a/src/applications/repository/worker/PhabricatorRepositoryPushMailWorker.php +++ b/src/applications/repository/worker/PhabricatorRepositoryPushMailWorker.php @@ -22,7 +22,9 @@ final class PhabricatorRepositoryPushMailWorker ->executeOne(); $repository = $event->getRepository(); - if (!$repository->shouldPublish()) { + + $publisher = $repository->newPublisher(); + if (!$publisher->shouldPublishRepository()) { // If the repository is still importing, don't send email. return; } @@ -149,6 +151,10 @@ final class PhabricatorRepositoryPushMailWorker $type_name = pht('bookmark'); $type_prefix = pht('bookmark:'); break; + case PhabricatorRepositoryPushLog::REFTYPE_REF: + $type_name = pht('ref'); + $type_prefix = pht('ref:'); + break; case PhabricatorRepositoryPushLog::REFTYPE_COMMIT: default: break; diff --git a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php index 8a1b5e5dd1..c7f00df73e 100644 --- a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php +++ b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php @@ -148,25 +148,6 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker $author_phid); } - $differential_app = 'PhabricatorDifferentialApplication'; - $revision_id = null; - $low_level_query = null; - if (PhabricatorApplication::isClassInstalled($differential_app)) { - $low_level_query = id(new DiffusionLowLevelCommitFieldsQuery()) - ->setRepository($repository) - ->withCommitRef($ref); - $field_values = $low_level_query->execute(); - $revision_id = idx($field_values, 'revisionID'); - - if (!empty($field_values['reviewedByPHIDs'])) { - $data->setCommitDetail( - 'reviewerPHID', - reset($field_values['reviewedByPHIDs'])); - } - - $data->setCommitDetail('differential.revisionID', $revision_id); - } - if ($author_phid != $commit->getAuthorPHID()) { $commit->setAuthorPHID($author_phid); } @@ -176,131 +157,18 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker $commit->setSummary($data->getSummary()); $commit->save(); - // Figure out if we're going to try to "autoclose" related objects (e.g., - // close linked tasks and related revisions) and, if not, record why we - // aren't. Autoclose can be disabled for various reasons at the repository - // or commit levels. + // If we're publishing this commit, we're going to queue tasks to update + // referenced objects (like tasks and revisions). Otherwise, record some + // details about why we are not publishing it yet. - $force_autoclose = idx($this->getTaskData(), 'forceAutoclose', false); - if ($force_autoclose) { - $autoclose_reason = PhabricatorRepository::BECAUSE_AUTOCLOSE_FORCED; + $publisher = $repository->newPublisher(); + if ($publisher->shouldPublishCommit($commit)) { + $actor = PhabricatorUser::getOmnipotentUser(); + $this->closeRevisions($actor, $ref, $commit, $data); + $this->closeTasks($actor, $ref, $commit, $data); } else { - $autoclose_reason = $repository->shouldSkipAutocloseCommit($commit); - } - $data->setCommitDetail('autocloseReason', $autoclose_reason); - $should_autoclose = $force_autoclose || - $repository->shouldAutocloseCommit($commit); - - - // When updating related objects, we'll act under an omnipotent user to - // ensure we can see them, but take actions as either the committer or - // author (if we recognize their accounts) or the Diffusion application - // (if we do not). - - $actor = PhabricatorUser::getOmnipotentUser(); - $acting_as_phid = nonempty( - $committer_phid, - $author_phid, - id(new PhabricatorDiffusionApplication())->getPHID()); - - $acting_user = $this->loadActingUser($actor, $acting_as_phid); - - $conn_w = id(new DifferentialRevision())->establishConnection('w'); - - // NOTE: The `differential_commit` table has a unique ID on `commitPHID`, - // preventing more than one revision from being associated with a commit. - // Generally this is good and desirable, but with the advent of hash - // tracking we may end up in a situation where we match several different - // revisions. We just kind of ignore this and pick one, we might want to - // revisit this and do something differently. (If we match several revisions - // someone probably did something very silly, though.) - - $revision = null; - if ($revision_id) { - $revision_query = id(new DifferentialRevisionQuery()) - ->withIDs(array($revision_id)) - ->setViewer($actor) - ->needReviewers(true) - ->needActiveDiffs(true); - - $revision = $revision_query->executeOne(); - - if ($revision) { - $commit_drev = DiffusionCommitHasRevisionEdgeType::EDGECONST; - id(new PhabricatorEdgeEditor()) - ->addEdge($commit->getPHID(), $commit_drev, $revision->getPHID()) - ->save(); - - queryfx( - $conn_w, - 'INSERT IGNORE INTO %T (revisionID, commitPHID) VALUES (%d, %s)', - DifferentialRevision::TABLE_COMMIT, - $revision->getID(), - $commit->getPHID()); - - $should_close = !$revision->isPublished() && $should_autoclose; - if ($should_close) { - $type_close = DifferentialRevisionCloseTransaction::TRANSACTIONTYPE; - - $commit_close_xaction = id(new DifferentialTransaction()) - ->setTransactionType($type_close) - ->setNewValue(true) - ->setMetadataValue('isCommitClose', true); - - $commit_close_xaction->setMetadataValue( - 'commitPHID', - $commit->getPHID()); - $commit_close_xaction->setMetadataValue( - 'committerPHID', - $committer_phid); - $commit_close_xaction->setMetadataValue( - 'committerName', - $data->getCommitDetail('committer')); - $commit_close_xaction->setMetadataValue( - 'authorPHID', - $author_phid); - $commit_close_xaction->setMetadataValue( - 'authorName', - $data->getAuthorName()); - - if ($low_level_query) { - $commit_close_xaction->setMetadataValue( - 'revisionMatchData', - $low_level_query->getRevisionMatchData()); - $data->setCommitDetail( - 'revisionMatchData', - $low_level_query->getRevisionMatchData()); - } - - $extraction_engine = id(new DifferentialDiffExtractionEngine()) - ->setViewer($actor) - ->setAuthorPHID($acting_as_phid); - - $content_source = $this->newContentSource(); - - $update_data = $extraction_engine->updateRevisionWithCommit( - $revision, - $commit, - array( - $commit_close_xaction, - ), - $content_source); - - foreach ($update_data as $key => $value) { - $data->setCommitDetail($key, $value); - } - } - } - } - - if ($should_autoclose) { - $this->closeTasks( - $actor, - $acting_as_phid, - $repository, - $commit, - $message, - $acting_user); + $hold_reasons = $publisher->getCommitHoldReasons($commit); + $data->setCommitDetail('holdReasons', $hold_reasons); } $data->save(); @@ -319,27 +187,63 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker ->execute(); } + private function closeRevisions( + PhabricatorUser $actor, + DiffusionCommitRef $ref, + PhabricatorRepositoryCommit $commit, + PhabricatorRepositoryCommitData $data) { + + $differential = 'PhabricatorDifferentialApplication'; + if (!PhabricatorApplication::isClassInstalled($differential)) { + return; + } + + $repository = $commit->getRepository(); + + $field_query = id(new DiffusionLowLevelCommitFieldsQuery()) + ->setRepository($repository) + ->withCommitRef($ref); + + $field_values = $field_query->execute(); + + $revision_id = idx($field_values, 'revisionID'); + if (!$revision_id) { + return; + } + + $revision = id(new DifferentialRevisionQuery()) + ->setViewer($actor) + ->withIDs(array($revision_id)) + ->executeOne(); + if (!$revision) { + return; + } + + // NOTE: This is very old code from when revisions had a single reviewer. + // It still powers the "Reviewer (Deprecated)" field in Herald, but should + // be removed. + if (!empty($field_values['reviewedByPHIDs'])) { + $data->setCommitDetail( + 'reviewerPHID', + head($field_values['reviewedByPHIDs'])); + } + + $match_data = $field_query->getRevisionMatchData(); + + $data->setCommitDetail('differential.revisionID', $revision_id); + $data->setCommitDetail('revisionMatchData', $match_data); + + $properties = array( + 'revisionMatchData' => $match_data, + ); + $this->queueObjectUpdate($commit, $revision, $properties); + } + private function closeTasks( PhabricatorUser $actor, - $acting_as, - PhabricatorRepository $repository, + DiffusionCommitRef $ref, PhabricatorRepositoryCommit $commit, - $message, - PhabricatorUser $acting_user = null) { - - // If we we were able to identify an author for the commit, we try to act - // as that user when loading tasks marked with "Fixes Txxx". This prevents - // mistakes where a user accidentally writes the wrong task IDs and affects - // tasks they can't see (and thus can't undo the status changes for). - - // This is just a guard rail, not a security measure. An attacker can still - // forge another user's identity trivially by forging author or committer - // emails. We also let commits with unrecognized authors act on any task to - // make behavior less confusing for new installs. - - if (!$acting_user) { - $acting_user = $actor; - } + PhabricatorRepositoryCommitData $data) { $maniphest = 'PhabricatorManiphestApplication'; if (!PhabricatorApplication::isClassInstalled($maniphest)) { @@ -348,11 +252,12 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker $prefixes = ManiphestTaskStatus::getStatusPrefixMap(); $suffixes = ManiphestTaskStatus::getStatusSuffixMap(); + $message = $data->getCommitMessage(); $matches = id(new ManiphestCustomFieldStatusParser()) ->parseCorpus($message); - $task_statuses = array(); + $task_map = array(); foreach ($matches as $match) { $prefix = phutil_utf8_strtolower($match['prefix']); $suffix = phutil_utf8_strtolower($match['suffix']); @@ -364,89 +269,44 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker foreach ($match['monograms'] as $task_monogram) { $task_id = (int)trim($task_monogram, 'tT'); - $task_statuses[$task_id] = $status; + $task_map[$task_id] = $status; } } - if (!$task_statuses) { + if (!$task_map) { return; } $tasks = id(new ManiphestTaskQuery()) - ->setViewer($acting_user) - ->withIDs(array_keys($task_statuses)) - ->needProjectPHIDs(true) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) + ->setViewer($actor) + ->withIDs(array_keys($task_map)) ->execute(); - foreach ($tasks as $task_id => $task) { - $xactions = array(); + $status = $task_map[$task_id]; - $edge_type = ManiphestTaskHasCommitEdgeType::EDGECONST; - $edge_xaction = id(new ManiphestTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) - ->setMetadataValue('edge:type', $edge_type) - ->setNewValue( - array( - '+' => array( - $commit->getPHID() => $commit->getPHID(), - ), - )); + $properties = array( + 'status' => $status, + ); - $status = $task_statuses[$task_id]; - if ($status) { - if ($task->getStatus() != $status) { - $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType( - ManiphestTaskStatusTransaction::TRANSACTIONTYPE) - ->setMetadataValue('commitPHID', $commit->getPHID()) - ->setNewValue($status); - - $edge_xaction->setMetadataValue('commitPHID', $commit->getPHID()); - } - } - - $xactions[] = $edge_xaction; - - $content_source = $this->newContentSource(); - - $editor = id(new ManiphestTransactionEditor()) - ->setActor($actor) - ->setActingAsPHID($acting_as) - ->setContinueOnNoEffect(true) - ->setContinueOnMissingFields(true) - ->setUnmentionablePHIDMap( - array($commit->getPHID() => $commit->getPHID())) - ->setContentSource($content_source); - - $editor->applyTransactions($task, $xactions); + $this->queueObjectUpdate($commit, $task, $properties); } } - private function loadActingUser(PhabricatorUser $viewer, $user_phid) { - if (!$user_phid) { - return null; - } + private function queueObjectUpdate( + PhabricatorRepositoryCommit $commit, + $object, + array $properties) { - $user_type = PhabricatorPeopleUserPHIDType::TYPECONST; - if (phid_get_type($user_phid) != $user_type) { - return null; - } - - $user = id(new PhabricatorPeopleQuery()) - ->setViewer($viewer) - ->withPHIDs(array($user_phid)) - ->executeOne(); - if (!$user) { - return null; - } - - return $user; + $this->queueTask( + 'DiffusionUpdateObjectAfterCommitWorker', + array( + 'commitPHID' => $commit->getPHID(), + 'objectPHID' => $object->getPHID(), + 'properties' => $properties, + ), + array( + 'priority' => PhabricatorWorker::PRIORITY_DEFAULT, + )); } - } diff --git a/src/applications/repository/xaction/PhabricatorRepositoryAutocloseTransaction.php b/src/applications/repository/xaction/PhabricatorRepositoryAutocloseTransaction.php deleted file mode 100644 index 6ab677fb4a..0000000000 --- a/src/applications/repository/xaction/PhabricatorRepositoryAutocloseTransaction.php +++ /dev/null @@ -1,34 +0,0 @@ -getDetail('disable-autoclose'); - } - - public function generateNewValue($object, $value) { - return (int)$value; - } - - public function applyInternalEffects($object, $value) { - $object->setDetail('disable-autoclose', (int)!$value); - } - - public function getTitle() { - $new = $this->getNewValue(); - - if ($new) { - return pht( - '%s enabled autoclose for this repository.', - $this->renderAuthor()); - } else { - return pht( - '%s disabled autoclose for this repository.', - $this->renderAuthor()); - } - } - -} diff --git a/src/applications/repository/xaction/PhabricatorRepositoryFetchRefsTransaction.php b/src/applications/repository/xaction/PhabricatorRepositoryFetchRefsTransaction.php new file mode 100644 index 0000000000..2965b0b742 --- /dev/null +++ b/src/applications/repository/xaction/PhabricatorRepositoryFetchRefsTransaction.php @@ -0,0 +1,94 @@ +getFetchRules(); + } + + public function applyInternalEffects($object, $value) { + $object->setFetchRules($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if (!$new) { + return pht( + '%s set this repository to fetch all refs.', + $this->renderAuthor()); + } else if (!$old) { + return pht( + '%s set this repository to fetch refs: %s.', + $this->renderAuthor(), + $this->renderValue(implode(', ', $new))); + } else { + return pht( + '%s changed fetched refs from %s to %s.', + $this->renderAuthor(), + $this->renderValue(implode(', ', $old)), + $this->renderValue(implode(', ', $new))); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + + if (!is_array($new_value) || !phutil_is_natural_list($new_value)) { + $errors[] = $this->newInvalidError( + pht( + 'Fetch rules must be a list of strings, got "%s".', + phutil_describe_type($new_value)), + $xaction); + continue; + } + + foreach ($new_value as $idx => $rule) { + if (!is_string($rule)) { + $errors[] = $this->newInvalidError( + pht( + 'Fetch rule (at index "%s") must be a string, got "%s".', + $idx, + phutil_describe_type($rule)), + $xaction); + continue; + } + + if (!strlen($rule)) { + $errors[] = $this->newInvalidError( + pht( + 'Fetch rule (at index "%s") is empty. Fetch rules must '. + 'contain text.', + $idx), + $xaction); + continue; + } + + // Since we fetch ref "X" as "+X:X", don't allow rules to include + // colons. This is specific to Git and may not be relevant if + // Mercurial repositories eventually get fetch rules. + if (preg_match('/:/', $rule)) { + $errors[] = $this->newInvalidError( + pht( + 'Fetch rule ("%s", at index "%s") is invalid: fetch rules '. + 'must not contain colons.', + $rule, + $idx), + $xaction); + continue; + } + + } + } + + return $errors; + } + +} diff --git a/src/applications/repository/xaction/PhabricatorRepositoryNotifyTransaction.php b/src/applications/repository/xaction/PhabricatorRepositoryNotifyTransaction.php index 4ffe1918ff..96868cc407 100644 --- a/src/applications/repository/xaction/PhabricatorRepositoryNotifyTransaction.php +++ b/src/applications/repository/xaction/PhabricatorRepositoryNotifyTransaction.php @@ -22,11 +22,11 @@ final class PhabricatorRepositoryNotifyTransaction if ($new) { return pht( - '%s enabled notifications and publishing for this repository.', + '%s enabled publishing for this repository.', $this->renderAuthor()); } else { return pht( - '%s disabled notifications and publishing for this repository.', + '%s disabled publishing for this repository.', $this->renderAuthor()); } } diff --git a/src/applications/repository/xaction/PhabricatorRepositoryAutocloseOnlyTransaction.php b/src/applications/repository/xaction/PhabricatorRepositoryPermanentRefsTransaction.php similarity index 69% rename from src/applications/repository/xaction/PhabricatorRepositoryAutocloseOnlyTransaction.php rename to src/applications/repository/xaction/PhabricatorRepositoryPermanentRefsTransaction.php index 5ff17c4b05..345a9092f0 100644 --- a/src/applications/repository/xaction/PhabricatorRepositoryAutocloseOnlyTransaction.php +++ b/src/applications/repository/xaction/PhabricatorRepositoryPermanentRefsTransaction.php @@ -1,16 +1,16 @@ getAutocloseOnlyRules(); + return $object->getPermanentRefRules(); } public function applyInternalEffects($object, $value) { - $object->setAutocloseOnlyRules($value); + $object->setPermanentRefRules($value); } public function getTitle() { @@ -19,16 +19,16 @@ final class PhabricatorRepositoryAutocloseOnlyTransaction if (!$new) { return pht( - '%s set this repository to autoclose on all branches.', + '%s marked all branches in this repository as permanent.', $this->renderAuthor()); } else if (!$old) { return pht( - '%s set this repository to autoclose on branches: %s.', + '%s set the permanent refs for this repository to: %s.', $this->renderAuthor(), $this->renderValue(implode(', ', $new))); } else { return pht( - '%s changed autoclose branches from %s to %s.', + '%s changed permanent refs for this repository from %s to %s.', $this->renderAuthor(), $this->renderValue(implode(', ', $old)), $this->renderValue(implode(', ', $new))); diff --git a/src/applications/search/controller/PhabricatorApplicationSearchController.php b/src/applications/search/controller/PhabricatorApplicationSearchController.php index c08c2abd88..962e6a00c5 100644 --- a/src/applications/search/controller/PhabricatorApplicationSearchController.php +++ b/src/applications/search/controller/PhabricatorApplicationSearchController.php @@ -200,9 +200,6 @@ $submit->addButton($save_button); } - // TODO: A "Create Dashboard Panel" action goes here somewhere once - // we sort out T5307. - $form->appendChild($submit); $body = array(); diff --git a/src/applications/search/edge/PhabricatorDashboardPanelUsedByObjectEdgeType.php b/src/applications/search/edge/PhabricatorDashboardPanelUsedByObjectEdgeType.php new file mode 100644 index 0000000000..f811b8b15d --- /dev/null +++ b/src/applications/search/edge/PhabricatorDashboardPanelUsedByObjectEdgeType.php @@ -0,0 +1,16 @@ +getValue('constraints', array()); + if (!is_array($constraints)) { + throw new Exception( + pht( + 'Parameter "constraints" must be a map of constraints, got "%s".', + phutil_describe_type($constraints))); + } $fields = $this->getSearchFieldsForConduit(); diff --git a/src/applications/search/engine/PhabricatorProfileMenuEngine.php b/src/applications/search/engine/PhabricatorProfileMenuEngine.php index fadb929594..aedbcc787f 100644 --- a/src/applications/search/engine/PhabricatorProfileMenuEngine.php +++ b/src/applications/search/engine/PhabricatorProfileMenuEngine.php @@ -8,7 +8,6 @@ abstract class PhabricatorProfileMenuEngine extends Phobject { private $items; private $controller; private $navigation; - private $showNavigation = true; private $editMode; private $pageClasses = array(); private $showContentCrumbs = true; @@ -71,15 +70,6 @@ abstract class PhabricatorProfileMenuEngine extends Phobject { return $this->controller; } - public function setShowNavigation($show) { - $this->showNavigation = $show; - return $this; - } - - public function getShowNavigation() { - return $this->showNavigation; - } - public function addContentPageClass($class) { $this->pageClasses[] = $class; return $this; @@ -181,13 +171,19 @@ abstract class PhabricatorProfileMenuEngine extends Phobject { $crumbs = $controller->buildApplicationCrumbsForEditEngine(); if (!$is_view) { + $edit_mode = null; + if ($selected_item) { - if ($selected_item->getCustomPHID()) { - $edit_mode = 'custom'; - } else { - $edit_mode = 'global'; + if ($selected_item->getBuiltinKey() !== self::ITEM_MANAGE) { + if ($selected_item->getCustomPHID()) { + $edit_mode = 'custom'; + } else { + $edit_mode = 'global'; + } } - } else { + } + + if ($edit_mode === null) { $edit_mode = $request->getURIData('itemEditMode'); } @@ -309,9 +305,7 @@ abstract class PhabricatorProfileMenuEngine extends Phobject { $page->setCrumbs($crumbs); } - if ($this->getShowNavigation()) { - $page->setNavigation($navigation); - } + $page->setNavigation($navigation); if ($is_view) { foreach ($this->pageClasses as $class) { @@ -1133,6 +1127,13 @@ abstract class PhabricatorProfileMenuEngine extends Phobject { ->setIsTailItem(true); } + protected function newDividerItem($key) { + return $this->newItem() + ->setBuiltinKey($key) + ->setMenuItemKey(PhabricatorDividerProfileMenuItem::MENUITEMKEY) + ->setIsTailItem(true); + } + public function getDefaultMenuItemConfiguration() { $configs = $this->getItems(); foreach ($configs as $config) { diff --git a/src/applications/search/engineextension/PhabricatorEdgeIndexEngineExtension.php b/src/applications/search/engineextension/PhabricatorEdgeIndexEngineExtension.php new file mode 100644 index 0000000000..528285c17f --- /dev/null +++ b/src/applications/search/engineextension/PhabricatorEdgeIndexEngineExtension.php @@ -0,0 +1,50 @@ +getIndexEdgeType(); + + $old_edges = PhabricatorEdgeQuery::loadDestinationPHIDs( + $object->getPHID(), + $edge_type); + $old_edges = array_fuse($old_edges); + + $new_edges = $this->getIndexDestinationPHIDs($object); + $new_edges = array_fuse($new_edges); + + $add_edges = array_diff_key($new_edges, $old_edges); + $rem_edges = array_diff_key($old_edges, $new_edges); + + if (!$add_edges && !$rem_edges) { + return; + } + + $editor = new PhabricatorEdgeEditor(); + + foreach ($add_edges as $phid) { + $editor->addEdge($object->getPHID(), $edge_type, $phid); + } + + foreach ($rem_edges as $phid) { + $editor->removeEdge($object->getPHID(), $edge_type, $phid); + } + + $editor->save(); + } + + final public function getIndexVersion($object) { + $phids = $this->getIndexDestinationPHIDs($object); + sort($phids); + $phids = implode(':', $phids); + return PhabricatorHash::digestForIndex($phids); + } + +} diff --git a/src/applications/search/engineextension/PhabricatorProfileMenuItemIndexEngineExtension.php b/src/applications/search/engineextension/PhabricatorProfileMenuItemIndexEngineExtension.php new file mode 100644 index 0000000000..078e51058c --- /dev/null +++ b/src/applications/search/engineextension/PhabricatorProfileMenuItemIndexEngineExtension.php @@ -0,0 +1,28 @@ +getAffectedObjectPHIDs(); + } + +} diff --git a/src/applications/search/menuitem/PhabricatorDashboardProfileMenuItem.php b/src/applications/search/menuitem/PhabricatorDashboardProfileMenuItem.php index af913778ee..9d89c52ff8 100644 --- a/src/applications/search/menuitem/PhabricatorDashboardProfileMenuItem.php +++ b/src/applications/search/menuitem/PhabricatorDashboardProfileMenuItem.php @@ -36,17 +36,24 @@ final class PhabricatorDashboardProfileMenuItem return $this->dashboard; } + public function getAffectedObjectPHIDs( + PhabricatorProfileMenuItemConfiguration $config) { + return array( + $this->getDashboardPHID($config), + ); + } + + public function newPageContent( PhabricatorProfileMenuItemConfiguration $config) { $viewer = $this->getViewer(); - $dashboard_phid = $config->getMenuItemProperty('dashboardPHID'); + $dashboard_phid = $this->getDashboardPHID($config); // Reload the dashboard to attach panels, which we need for rendering. $dashboard = id(new PhabricatorDashboardQuery()) ->setViewer($viewer) ->withPHIDs(array($dashboard_phid)) - ->needPanels(true) ->executeOne(); if (!$dashboard) { return $this->newEmptyView( @@ -71,7 +78,7 @@ final class PhabricatorDashboardProfileMenuItem $viewer = $this->getViewer(); $dashboard_phids = array(); foreach ($items as $item) { - $dashboard_phids[] = $item->getMenuItemProperty('dashboardPHID'); + $dashboard_phids[] = $this->getDashboardPHID($item); } $dashboards = id(new PhabricatorDashboardQuery()) @@ -83,7 +90,7 @@ final class PhabricatorDashboardProfileMenuItem $dashboards = mpull($dashboards, null, 'getPHID'); foreach ($items as $item) { - $dashboard_phid = $item->getMenuItemProperty('dashboardPHID'); + $dashboard_phid = $this->getDashboardPHID($item); $dashboard = idx($dashboards, $dashboard_phid, null); $menu_item = $item->getMenuItem(); @@ -125,7 +132,7 @@ final class PhabricatorDashboardProfileMenuItem ->setLabel(pht('Dashboard')) ->setIsRequired(true) ->setDatasource(new PhabricatorDashboardDatasource()) - ->setSingleValue($config->getMenuItemProperty('dashboardPHID')), + ->setSingleValue($this->getDashboardPHID($config)), id(new PhabricatorTextEditField()) ->setKey('name') ->setLabel(pht('Name')) @@ -153,7 +160,7 @@ final class PhabricatorDashboardProfileMenuItem $icon = $dashboard->getIcon(); $name = $this->getDisplayName($config); $is_disabled = false; - $action_uri = '/dashboard/arrange/'.$dashboard->getID().'/'; + $action_uri = $dashboard->getURI(); } } else { $icon = 'fa-ban'; @@ -226,6 +233,11 @@ final class PhabricatorDashboardProfileMenuItem return $errors; } + private function getDashboardPHID( + PhabricatorProfileMenuItemConfiguration $config) { + return $config->getMenuItemProperty('dashboardPHID'); + } + private function getDashboardHandle() { return $this->dashboardHandle; } diff --git a/src/applications/search/menuitem/PhabricatorProfileMenuItem.php b/src/applications/search/menuitem/PhabricatorProfileMenuItem.php index 118815393d..773a0f09ac 100644 --- a/src/applications/search/menuitem/PhabricatorProfileMenuItem.php +++ b/src/applications/search/menuitem/PhabricatorProfileMenuItem.php @@ -5,7 +5,6 @@ abstract class PhabricatorProfileMenuItem extends Phobject { private $viewer; private $engine; - public function getMenuItemTypeIcon() { return null; } @@ -160,4 +159,9 @@ abstract class PhabricatorProfileMenuItem extends Phobject { )); } + public function getAffectedObjectPHIDs( + PhabricatorProfileMenuItemConfiguration $config) { + return array(); + } + } diff --git a/src/applications/search/query/PhabricatorProfileMenuItemConfigurationQuery.php b/src/applications/search/query/PhabricatorProfileMenuItemConfigurationQuery.php index 230989e88e..16b5d793a4 100644 --- a/src/applications/search/query/PhabricatorProfileMenuItemConfigurationQuery.php +++ b/src/applications/search/query/PhabricatorProfileMenuItemConfigurationQuery.php @@ -8,6 +8,7 @@ final class PhabricatorProfileMenuItemConfigurationQuery private $profilePHIDs; private $customPHIDs; private $includeGlobal; + private $affectedObjectPHIDs; public function withIDs(array $ids) { $this->ids = $ids; @@ -30,6 +31,11 @@ final class PhabricatorProfileMenuItemConfigurationQuery return $this; } + public function withAffectedObjectPHIDs(array $phids) { + $this->affectedObjectPHIDs = $phids; + return $this; + } + public function newResultObject() { return new PhabricatorProfileMenuItemConfiguration(); } @@ -44,21 +50,21 @@ final class PhabricatorProfileMenuItemConfigurationQuery if ($this->ids !== null) { $where[] = qsprintf( $conn, - 'id IN (%Ld)', + 'config.id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( $conn, - 'phid IN (%Ls)', + 'config.phid IN (%Ls)', $this->phids); } if ($this->profilePHIDs !== null) { $where[] = qsprintf( $conn, - 'profilePHID IN (%Ls)', + 'config.profilePHID IN (%Ls)', $this->profilePHIDs); } @@ -66,23 +72,45 @@ final class PhabricatorProfileMenuItemConfigurationQuery if ($this->customPHIDs && $this->includeGlobal) { $where[] = qsprintf( $conn, - 'customPHID IN (%Ls) OR customPHID IS NULL', + 'config.customPHID IN (%Ls) OR config.customPHID IS NULL', $this->customPHIDs); } else if ($this->customPHIDs) { $where[] = qsprintf( $conn, - 'customPHID IN (%Ls)', + 'config.customPHID IN (%Ls)', $this->customPHIDs); } else { $where[] = qsprintf( $conn, - 'customPHID IS NULL'); + 'config.customPHID IS NULL'); } } + if ($this->affectedObjectPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'affected.dst IN (%Ls)', + $this->affectedObjectPHIDs); + } + return $where; } + protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { + $joins = parent::buildJoinClauseParts($conn); + + if ($this->affectedObjectPHIDs !== null) { + $joins[] = qsprintf( + $conn, + 'JOIN %T affected ON affected.src = config.phid + AND affected.type = %d', + PhabricatorEdgeConfig::TABLE_NAME_EDGE, + PhabricatorProfileMenuItemAffectsObjectEdgeType::EDGECONST); + } + + return $joins; + } + protected function willFilterPage(array $page) { $items = PhabricatorProfileMenuItem::getAllMenuItems(); foreach ($page as $key => $item) { @@ -128,4 +156,8 @@ final class PhabricatorProfileMenuItemConfigurationQuery return 'PhabricatorSearchApplication'; } + protected function getPrimaryTableAlias() { + return 'config'; + } + } diff --git a/src/applications/search/storage/PhabricatorProfileMenuItemConfiguration.php b/src/applications/search/storage/PhabricatorProfileMenuItemConfiguration.php index 8ca948cb07..4307b19732 100644 --- a/src/applications/search/storage/PhabricatorProfileMenuItemConfiguration.php +++ b/src/applications/search/storage/PhabricatorProfileMenuItemConfiguration.php @@ -5,7 +5,8 @@ final class PhabricatorProfileMenuItemConfiguration implements PhabricatorPolicyInterface, PhabricatorExtendedPolicyInterface, - PhabricatorApplicationTransactionInterface { + PhabricatorApplicationTransactionInterface, + PhabricatorIndexableInterface { protected $profilePHID; protected $menuItemKey; @@ -255,6 +256,49 @@ final class PhabricatorProfileMenuItemConfiguration return false; } + public function getAffectedObjectPHIDs() { + return $this->getMenuItem()->getAffectedObjectPHIDs($this); + } + + public function getProfileMenuTypeDescription() { + $profile_phid = $this->getProfilePHID(); + + $home_phid = id(new PhabricatorHomeApplication())->getPHID(); + if ($profile_phid === $home_phid) { + return pht('Home Menu'); + } + + $favorites_phid = id(new PhabricatorFavoritesApplication())->getPHID(); + if ($profile_phid === $favorites_phid) { + return pht('Favorites Menu'); + } + + switch (phid_get_type($profile_phid)) { + case PhabricatorProjectProjectPHIDType::TYPECONST: + return pht('Project Menu'); + case PhabricatorDashboardPortalPHIDType::TYPECONST: + return pht('Portal Menu'); + } + + return pht('Profile Menu'); + } + + public function newUsageSortVector() { + // Used to sort items in contexts where we're showing the usage of an + // object in menus, like "Dashboard Used By" on Dashboard pages. + + // Sort usage as a custom item after usage as a global item. + if ($this->getCustomPHID()) { + $is_personal = 1; + } else { + $is_personal = 0; + } + + return id(new PhutilSortVector()) + ->addInt($is_personal) + ->addInt($this->getID()); + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/search/worker/PhabricatorRebuildIndexesWorker.php b/src/applications/search/worker/PhabricatorRebuildIndexesWorker.php new file mode 100644 index 0000000000..7d5cc64b0b --- /dev/null +++ b/src/applications/search/worker/PhabricatorRebuildIndexesWorker.php @@ -0,0 +1,44 @@ + $query_class, + ), + array( + 'priority' => parent::PRIORITY_INDEX, + )); + } + + protected function doWork() { + $viewer = PhabricatorUser::getOmnipotentUser(); + + $data = $this->getTaskData(); + $query_class = idx($data, 'queryClass'); + + try { + $query = newv($query_class, array()); + } catch (Exception $ex) { + throw new PhabricatorWorkerPermanentFailureException( + pht( + 'Unable to instantiate query class "%s": %s', + $query_class, + $ex->getMessage())); + } + + $query->setViewer($viewer); + + $iterator = new PhabricatorQueryIterator($query); + foreach ($iterator as $object) { + PhabricatorSearchWorker::queueDocumentForIndexing( + $object->getPHID(), + array( + 'force' => true, + )); + } + } + +} diff --git a/src/applications/subscriptions/herald/PhabricatorSubscriptionsAddSubscribersHeraldAction.php b/src/applications/subscriptions/herald/PhabricatorSubscriptionsAddSubscribersHeraldAction.php index 14f72d313a..3604d368fc 100644 --- a/src/applications/subscriptions/herald/PhabricatorSubscriptionsAddSubscribersHeraldAction.php +++ b/src/applications/subscriptions/herald/PhabricatorSubscriptionsAddSubscribersHeraldAction.php @@ -29,4 +29,8 @@ final class PhabricatorSubscriptionsAddSubscribersHeraldAction return pht('Add subscribers: %s.', $this->renderHandleList($value)); } + public function getPHIDsAffectedByAction(HeraldActionRecord $record) { + return $record->getTarget(); + } + } diff --git a/src/applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSubscribersHeraldAction.php b/src/applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSubscribersHeraldAction.php index 67ece2cb72..7b55e6b5aa 100644 --- a/src/applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSubscribersHeraldAction.php +++ b/src/applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSubscribersHeraldAction.php @@ -29,4 +29,8 @@ final class PhabricatorSubscriptionsRemoveSubscribersHeraldAction return pht('Remove subscribers: %s.', $this->renderHandleList($value)); } + public function getPHIDsAffectedByAction(HeraldActionRecord $record) { + return $record->getTarget(); + } + } diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index 3b54655f57..a8d619db0a 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -977,91 +977,20 @@ abstract class PhabricatorApplicationTransactionEditor PhabricatorLiskDAO $object, array $xactions) { - $this->object = $object; - $this->xactions = $xactions; - $this->isNewObject = ($object->getPHID() === null); - - $this->validateEditParameters($object, $xactions); - $xactions = $this->newMFATransactions($object, $xactions); - - $actor = $this->requireActor(); - - // NOTE: Some transaction expansion requires that the edited object be - // attached. - foreach ($xactions as $xaction) { - $xaction->attachObject($object); - $xaction->attachViewer($actor); - } - - $xactions = $this->expandTransactions($object, $xactions); - $xactions = $this->expandSupportTransactions($object, $xactions); - $xactions = $this->combineTransactions($xactions); - - foreach ($xactions as $xaction) { - $xaction = $this->populateTransaction($object, $xaction); - } + $is_new = ($object->getPHID() === null); + $this->isNewObject = $is_new; $is_preview = $this->getIsPreview(); $read_locking = false; $transaction_open = false; - if (!$is_preview) { - $errors = array(); - $type_map = mgroup($xactions, 'getTransactionType'); - foreach ($this->getTransactionTypes() as $type) { - $type_xactions = idx($type_map, $type, array()); - $errors[] = $this->validateTransaction($object, $type, $type_xactions); - } - - $errors[] = $this->validateAllTransactions($object, $xactions); - $errors[] = $this->validateTransactionsWithExtensions($object, $xactions); - $errors = array_mergev($errors); - - $continue_on_missing = $this->getContinueOnMissingFields(); - foreach ($errors as $key => $error) { - if ($continue_on_missing && $error->getIsMissingFieldError()) { - unset($errors[$key]); - } - } - - if ($errors) { - throw new PhabricatorApplicationTransactionValidationException($errors); - } - - if ($this->raiseWarnings) { - $warnings = array(); - foreach ($xactions as $xaction) { - if ($this->hasWarnings($object, $xaction)) { - $warnings[] = $xaction; - } - } - if ($warnings) { - throw new PhabricatorApplicationTransactionWarningException( - $warnings); - } - } - } - - foreach ($xactions as $xaction) { - $this->adjustTransactionValues($object, $xaction); - } - - // Now that we've merged and combined transactions, check for required - // capabilities. Note that we're doing this before filtering - // transactions: if you try to apply an edit which you do not have - // permission to apply, we want to give you a permissions error even - // if the edit would have no effect. - $this->applyCapabilityChecks($object, $xactions); - - $xactions = $this->filterTransactions($object, $xactions); + // If we're attempting to apply transactions, lock and reload the object + // before we go anywhere. If we don't do this at the very beginning, we + // may be looking at an older version of the object when we populate and + // filter the transactions. See PHI1165 for an example. if (!$is_preview) { - $this->hasRequiredMFA = true; - if ($this->getShouldRequireMFA()) { - $this->requireMFA($object, $xactions); - } - - if ($object->getID()) { + if (!$is_new) { $this->buildOldRecipientLists($object, $xactions); $object->openTransaction(); @@ -1072,16 +1001,102 @@ abstract class PhabricatorApplicationTransactionEditor $object->reload(); } - - if ($this->shouldApplyInitialEffects($object, $xactions)) { - if (!$transaction_open) { - $object->openTransaction(); - $transaction_open = true; - } - } } try { + $this->object = $object; + $this->xactions = $xactions; + + $this->validateEditParameters($object, $xactions); + $xactions = $this->newMFATransactions($object, $xactions); + + $actor = $this->requireActor(); + + // NOTE: Some transaction expansion requires that the edited object be + // attached. + foreach ($xactions as $xaction) { + $xaction->attachObject($object); + $xaction->attachViewer($actor); + } + + $xactions = $this->expandTransactions($object, $xactions); + $xactions = $this->expandSupportTransactions($object, $xactions); + $xactions = $this->combineTransactions($xactions); + + foreach ($xactions as $xaction) { + $xaction = $this->populateTransaction($object, $xaction); + } + + if (!$is_preview) { + $errors = array(); + $type_map = mgroup($xactions, 'getTransactionType'); + foreach ($this->getTransactionTypes() as $type) { + $type_xactions = idx($type_map, $type, array()); + $errors[] = $this->validateTransaction( + $object, + $type, + $type_xactions); + } + + $errors[] = $this->validateAllTransactions($object, $xactions); + $errors[] = $this->validateTransactionsWithExtensions( + $object, + $xactions); + $errors = array_mergev($errors); + + $continue_on_missing = $this->getContinueOnMissingFields(); + foreach ($errors as $key => $error) { + if ($continue_on_missing && $error->getIsMissingFieldError()) { + unset($errors[$key]); + } + } + + if ($errors) { + throw new PhabricatorApplicationTransactionValidationException( + $errors); + } + + if ($this->raiseWarnings) { + $warnings = array(); + foreach ($xactions as $xaction) { + if ($this->hasWarnings($object, $xaction)) { + $warnings[] = $xaction; + } + } + if ($warnings) { + throw new PhabricatorApplicationTransactionWarningException( + $warnings); + } + } + } + + foreach ($xactions as $xaction) { + $this->adjustTransactionValues($object, $xaction); + } + + // Now that we've merged and combined transactions, check for required + // capabilities. Note that we're doing this before filtering + // transactions: if you try to apply an edit which you do not have + // permission to apply, we want to give you a permissions error even + // if the edit would have no effect. + $this->applyCapabilityChecks($object, $xactions); + + $xactions = $this->filterTransactions($object, $xactions); + + if (!$is_preview) { + $this->hasRequiredMFA = true; + if ($this->getShouldRequireMFA()) { + $this->requireMFA($object, $xactions); + } + + if ($this->shouldApplyInitialEffects($object, $xactions)) { + if (!$transaction_open) { + $object->openTransaction(); + $transaction_open = true; + } + } + } + if ($this->shouldApplyInitialEffects($object, $xactions)) { $this->applyInitialEffects($object, $xactions); } diff --git a/src/applications/transactions/query/PhabricatorEditEngineQuery.php b/src/applications/transactions/query/PhabricatorEditEngineQuery.php index ffdbd88535..400b62a487 100644 --- a/src/applications/transactions/query/PhabricatorEditEngineQuery.php +++ b/src/applications/transactions/query/PhabricatorEditEngineQuery.php @@ -46,8 +46,4 @@ final class PhabricatorEditEngineQuery return 'PhabricatorTransactionsApplication'; } - protected function getResultCursor($object) { - return null; - } - } diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php index d71728a01f..4b65c1b815 100644 --- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php +++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php @@ -657,7 +657,6 @@ abstract class PhabricatorApplicationTransaction case PhabricatorMutedEdgeType::EDGECONST: case PhabricatorMutedByEdgeType::EDGECONST: return true; - break; case PhabricatorObjectMentionedByObjectEdgeType::EDGECONST: $record = PhabricatorEdgeChangeRecord::newFromTransaction($this); $add = $record->getAddedPHIDs(); @@ -700,6 +699,10 @@ abstract class PhabricatorApplicationTransaction switch ($edge_type) { case PhabricatorObjectMentionsObjectEdgeType::EDGECONST: case PhabricatorObjectMentionedByObjectEdgeType::EDGECONST: + case DifferentialRevisionDependsOnRevisionEdgeType::EDGECONST: + case DifferentialRevisionDependedOnByRevisionEdgeType::EDGECONST: + case ManiphestTaskHasCommitEdgeType::EDGECONST: + case DiffusionCommitHasTaskEdgeType::EDGECONST: return true; case PhabricatorProjectObjectHasProjectEdgeType::EDGECONST: // When an object is first created, we hide any corresponding @@ -755,8 +758,11 @@ abstract class PhabricatorApplicationTransaction switch ($edge_type) { case PhabricatorObjectMentionsObjectEdgeType::EDGECONST: case PhabricatorObjectMentionedByObjectEdgeType::EDGECONST: + case DifferentialRevisionDependsOnRevisionEdgeType::EDGECONST: + case DifferentialRevisionDependedOnByRevisionEdgeType::EDGECONST: + case ManiphestTaskHasCommitEdgeType::EDGECONST: + case DiffusionCommitHasTaskEdgeType::EDGECONST: return true; - break; default: break; } diff --git a/src/applications/transactions/storage/PhabricatorEditEngineConfigurationTransaction.php b/src/applications/transactions/storage/PhabricatorEditEngineConfigurationTransaction.php index 8cf7fe5b48..71048e96c5 100644 --- a/src/applications/transactions/storage/PhabricatorEditEngineConfigurationTransaction.php +++ b/src/applications/transactions/storage/PhabricatorEditEngineConfigurationTransaction.php @@ -149,5 +149,19 @@ final class PhabricatorEditEngineConfigurationTransaction return parent::getIcon(); } + protected function newRemarkupChanges() { + $changes = array(); + + $type = $this->getTransactionType(); + switch ($type) { + case self::TYPE_PREAMBLE: + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + break; + } + + return $changes; + } } diff --git a/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php b/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php index e077d7a7ec..29a8d4b7ba 100644 --- a/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php +++ b/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php @@ -604,4 +604,38 @@ abstract class PhabricatorTypeaheadDatasource extends Phobject { return mpull($tokens, 'getWireFormat', 'getPHID'); } + final protected function applyFerretConstraints( + PhabricatorCursorPagedPolicyAwareQuery $query, + PhabricatorFerretEngine $engine, + $ferret_function, + $raw_query) { + + $compiler = id(new PhutilSearchQueryCompiler()) + ->setEnableFunctions(true); + + $raw_tokens = $compiler->newTokens($raw_query); + + $fulltext_tokens = array(); + foreach ($raw_tokens as $raw_token) { + // This is a little hacky and could maybe be cleaner. We're treating + // every search term as though the user had entered "title:dog" instead + // of "dog". + + $alternate_token = PhutilSearchQueryToken::newFromDictionary( + array( + 'quoted' => $raw_token->isQuoted(), + 'value' => $raw_token->getValue(), + 'operator' => PhutilSearchQueryCompiler::OPERATOR_SUBSTRING, + 'function' => $ferret_function, + )); + + $fulltext_token = id(new PhabricatorFulltextToken()) + ->setToken($alternate_token); + $fulltext_tokens[] = $fulltext_token; + } + + $query->withFerretConstraint($engine, $fulltext_tokens); + } + + } diff --git a/src/docs/user/field/repository_imports.diviner b/src/docs/user/field/repository_imports.diviner index 1b1c92bbc0..262874186c 100644 --- a/src/docs/user/field/repository_imports.diviner +++ b/src/docs/user/field/repository_imports.diviner @@ -30,9 +30,9 @@ updates in @{article:Diffusion User Guide: Repository Updates}. After commits are discovered, background tasks are queued to actually import commits. These tasks do things like look at commit messages, trigger mentions -and autoclose rules, cache changes, trigger Herald, publish feed stories and -email, and apply Owners rules. You can learn more about some of these steps in -@{article:Diffusion User Guide: Autoclose}. +and update related objects, cache changes, trigger Herald, publish feed stories +and email, and apply Owners rules. You can learn more about some of these steps +in @{article:Diffusion User Guide: Permanent Refs}. Specifically, the import pipeline has four steps: diff --git a/src/docs/user/upgrading.diviner b/src/docs/user/upgrading.diviner index d9eca240ec..bf15aa5904 100644 --- a/src/docs/user/upgrading.diviner +++ b/src/docs/user/upgrading.diviner @@ -90,19 +90,7 @@ set -x ROOT=`pwd` # You can hard-code the path here instead. -### UPDATE WORKING COPIES ###################################################### - -cd $ROOT/libphutil -git pull - -cd $ROOT/arcanist -git pull - -cd $ROOT/phabricator -git pull - - -### CYCLE WEB SERVER AND DAEMONS ############################################### +### STOP WEB SERVER AND DAEMONS ############################################### # Stop daemons. $ROOT/phabricator/bin/phd stop @@ -117,6 +105,16 @@ $ROOT/phabricator/bin/phd stop sudo /etc/init.d/httpd stop +### UPDATE WORKING COPIES ###################################################### + +cd $ROOT/libphutil +git pull + +cd $ROOT/arcanist +git pull + +cd $ROOT/phabricator +git pull # Upgrade the database schema. You may want to add the "--force" flag to allow # this script to run noninteractively. diff --git a/src/docs/user/userguide/arcanist_diff.diviner b/src/docs/user/userguide/arcanist_diff.diviner index 3480070b5f..4140deb537 100644 --- a/src/docs/user/userguide/arcanist_diff.diviner +++ b/src/docs/user/userguide/arcanist_diff.diviner @@ -167,11 +167,10 @@ You can use `arc help ` for detailed help with any of these. Differential will make a guess about a next step on accepted revisions, but it may not be the best next step for your workflow. -Phabricator will also automatically close revisions, if the changes are pushed +Phabricator will also automatically close revisions if the changes are pushed to a repository that is tracked in Diffusion. Specifically, it will close revisions based on commit and tree hashes, and `Differential Revision` -identifiers in commit messages. (You can disable this feature by disabling -"Autoclose" in the Repository configuration.) +identifiers in commit messages. If you push to an untracked repository (or `arc` can't figure out that it's tracked), `arc land`, `arc amend` and `arc commit` will implicitly run diff --git a/src/docs/user/userguide/diffusion.diviner b/src/docs/user/userguide/diffusion.diviner index 7c48d7a636..8ee66d3f8f 100644 --- a/src/docs/user/userguide/diffusion.diviner +++ b/src/docs/user/userguide/diffusion.diviner @@ -86,8 +86,8 @@ Continue by: If you're having trouble getting things working, these topic guides may be helpful: - - get details about automatically closing tasks and revisions in response - to commits in @{article:Diffusion User Guide: Autoclose}; or + - get details about automatically taking actions in response to commits in + @{article:Diffusion User Guide: Permanent Refs}; or - understand how Phabricator updates repositories with @{article:Diffusion User Guide: Repository Updates}; or - fix issues with repository imports with diff --git a/src/docs/user/userguide/diffusion_autoclose.diviner b/src/docs/user/userguide/diffusion_autoclose.diviner deleted file mode 100644 index 796a3b0404..0000000000 --- a/src/docs/user/userguide/diffusion_autoclose.diviner +++ /dev/null @@ -1,84 +0,0 @@ -@title Diffusion User Guide: Autoclose -@group userguide - -Explains when Diffusion will close tasks and revisions upon discovery of related -commits. - -Overview -======== - -Diffusion can close tasks and revisions when related commits appear in a -repository. For example, if you make a commit with `Fixes T123` in the commit -message, Diffusion will close the task `T123`. - -This document explains how autoclose works, how to configure it, and how to -troubleshoot it. - -Troubleshooting Autoclose -========================= - -You can check if a branch is currently configured to autoclose on the -management page for the given repository under the branches menu item. -You should see one of these statuses next to the name of the branch: - - - **Autoclose On** Autoclose is active for this branch. - - **Repository Importing** This repository is still importing. - Autoclose does not activate until a repository finishes importing for the - first time. This prevents situations where you import a repository and - accidentally close hundreds of related objects during import. Autoclose - will activate for new commits after the initial import completes. - - **Tracking Off** This branch is not tracked. Because it - is not tracked, commits on it won't be seen and won't be discovered. - - **Autoclose Off** Autoclose is not enabled for - this branch. You can adjust which branches autoclose in **Edit Repository**. - This option is only available in Git. - -If a branch is in good shape, you can check a specific commit by viewing it -in the web UI and clicking **Edit Commit**. There should be an **Autoclose?** -field visible in the form, with possible values listed below. - -Note that this field records the state of the world at the time the commit was -processed, and does not necessarily reflect the current state of the world. -For example, if a commit did not trigger autoclose because it was processed -during initial import, the field will still show **No, Repository Importing** -even after import completes. This means that the commit did not trigger -autoclose because the repository was importing at the time it was processed, -not necessarily that the repository is still importing. - - - **Yes** At the time the commit was imported, autoclose triggered and - Phabricator attempted to close related objects. - - **No, Repository Importing** At the time the commit was processed, the - repository was still importing. Autoclose does not activate until a - repository fully imports for the first time. - - **No, Autoclose Disabled** At the time the commit was processed, the - repository had autoclose disabled. - - **No, Not On Autoclose Branch** At the time the commit was processed, - no containing branch was configured to autoclose. - - //Field Not Present// This commit was processed before we implemented - this diagnostic feature, and no information is available. - - -Manually Closing Revisions -========================== - -If autoclose didn't activate for some reason and you want to manually close -revisions, you can do so in several ways: - -**Close Revision**: The revision author can use the "Close Revision" action -from the web UI, located in the action dropdown on the revision page (where -reviewers would "Accept Revision" or "Request Changes"). - -`differential.always-allow-close`: If you set this option in {nav Config}, -any user can take the "Close Revision" action in the web UI. - -`arc close-revision`: You can close revisions from the command line by using -`arc close-revision`. - - -Next Steps -========== - -Continue by: - - - troubleshooting in greater depth with - @{article:Troubleshooting Repository Imports}. diff --git a/src/docs/user/userguide/diffusion_managing.diviner b/src/docs/user/userguide/diffusion_managing.diviner index a671b0f09e..aa52c1c475 100644 --- a/src/docs/user/userguide/diffusion_managing.diviner +++ b/src/docs/user/userguide/diffusion_managing.diviner @@ -137,6 +137,19 @@ can not prevent dangerous changes in a remote repository it is merely observing. +Basics: Disable Publishing +========================== + +You can disable publishing for a repository. For more details on what this +means, see @{article:Diffusion User Guide: Permanent Refs}. + +This is primarily useful if you need to perform major maintenance on a +repository (like rewriting a large part of the repository history) and you +don't want the maintenance to generate a large volume of email and +notifications. You can disable publishing, apply major changes, wait for the +new changes to import, and then reactivate publishing. + + Basics: Deactivate Repository ============================= @@ -287,13 +300,40 @@ branches. This panel is not available for Subversion repositories, because Subversion does not have formal branches. -You can configure **Default Branch**. This controls which branch is shown by +You can configure a **Default Branch**. This controls which branch is shown by default in the UI. If no branch is provided, Phabricator will use `master` in Git and `default` in Mercurial. -If you want Diffusion to ignore some branches in the repository, you can -configure **Track Only**. Other branches will be ignored. If you do not specify -any branches, all branches are tracked. +**Fetch Refs**: In Git, if you are observing a remote repository, you can +specify that you only want to fetch a subset of refs using "Fetch Refs". + +Normally, all refs (`refs/*`) are fetched. This means all branches, all tags, +and all other refs. + +If you want to fetch only a few specific branches, you can list only those +branches. For example, this will fetch only the branch "master": + +``` +refs/heads/master +``` + +You can fetch all branches and tags (but ignore other refs) like this: + +``` +refs/heads/* +refs/tags/* +``` + +This may be useful if the remote is on a service like GitHub, GitLab, or +Gerrit and uses custom refs (like `refs/pull/` or `refs/changes/`) to store +metadata that you don't want to bring into Phabricator. + +**Permanent Refs**: To learn more about permanent refs, see: + + - @{article:Diffusion User Guide: Permanent Refs} + +By default, Phabricator considers all branches to be permanent refs. If you +only want some branches to be treated as permanent refs, specify them here. When specifying branches, you should enter one branch name per line. You can use regular expressions to match branches by wrapping an expression in @@ -301,24 +341,9 @@ use regular expressions to match branches by wrapping an expression in | Example | Effect | |---------|--------| -| `master` | Track only `master`. -| `regexp(/^release-/)` | Track all branches which start with `release-`. -| `regexp(/^(?!temp-)/)` | Do not track branches which start with `temp-`. - - -Actions -====== - -The **Actions** panel can configure notifications and publishing behavior. - -Normally, Phabricator publishes notifications when it discovers new commits. -You can disable publishing for a repository by turning off **Publish/Notify**. -This will disable notifications, feed, and Herald (including audits and build -plans) for this repository. - -When Phabricator discovers a new commit, it can automatically close associated -revisions and tasks. If you don't want Phabricator to close objects when it -discovers new commits, disable **Autoclose** for the repository. +| `master` | Only the `master` branch is a permanent ref. +| `regexp(/^release-/)` | Branches are permanent if they start with `release-`. +| `regexp(/^(?!temp-)/)` | Branches named `temp-` are not permanent. Staging Area diff --git a/src/docs/user/userguide/diffusion_permanent.diviner b/src/docs/user/userguide/diffusion_permanent.diviner new file mode 100644 index 0000000000..fba4341c0c --- /dev/null +++ b/src/docs/user/userguide/diffusion_permanent.diviner @@ -0,0 +1,81 @@ +@title Diffusion User Guide: Permanent Refs +@group userguide + +Explains when Diffusion will take actions in response to discovering commits. + +Overview +======== + +Diffusion can close tasks and revisions and take other actions when commits +appear in a repository (either because they were pushed to Phabricator, or +because they were pushed to some remote which Phabricator is observing). + +This document explains when Diffusion acts on commits and how to configure this +behavior. + + +Publishing Commits +================== + +Diffusion distinguishes between "pushed" and "published" commits. + +Not all commits that are pushed to a repository are destined for greatness: +for example, many tools push temporary commits to secret places like +`refs/pull/123`, `refs/notes/*`, or `refs/changes/12/345678/1`. + +Sometimes, human users intentionally push changes to branches like +"tmp-hack-ignore-123". This is formally discouraged by Phabricator, but the +practice is so widespread that we've given up trying to stop anyone from doing +it. + +Phabricator will import these commits and create pages for them so you can view +them in the web UI and link to them, but does not take any other actions until +they are "published". + +A commit is "published" when it becomes reachable from a permanent ref. By +default, all branches are permanent refs, so pushing a commit to "master" will +publish it, but pushing a commit to `refs/pull/123` (either directly, or by +using a tool like GitHub) will not. + +Usually, commits are published by pushing them directly to a permanent branch +like "master", or by merging a temporary branch into a permanent branch. + +When a commit is published, Phabricator acts on it and: + + - sends email; + - delivers notifications; + - publishes a feed story; + - triggers Audits; + - runs Herald rules; + - updates mentioned objects; + - closes referenced tasks; and + - closes associated revisions. + + +Configuring Repositories +======================== + +You can control publishing behavior in two primary ways: by configuring +which refs are considered to be permanent refs, and by disabling publishing +entirely. + +By default, all branches are considered permanent refs and all other refs +(including tags and other arbitrary custom refs) are considered nonpermanent. +This means that, by default, pushing commits to a branch like +"tmp-hack-ignore-123" will publish those commits. + +If you want to be free to push commits to temporary branches like this and +only want commits on certain branches (like "master") to be published, +configure which refs are treated as permanent by editing +{nav Branches > Permanent Refs} from the "Manage" page of the repository. + +To disable publishing entirely, select {nav Basics > Disable Publishing}. + + +Next Steps +========== + +Continue by: + + - troubleshooting in greater depth with + @{article:Troubleshooting Repository Imports}. diff --git a/src/infrastructure/daemon/workers/__tests__/PhabricatorWorkerTestCase.php b/src/infrastructure/daemon/workers/__tests__/PhabricatorWorkerTestCase.php index 83d2585e18..976f1800a9 100644 --- a/src/infrastructure/daemon/workers/__tests__/PhabricatorWorkerTestCase.php +++ b/src/infrastructure/daemon/workers/__tests__/PhabricatorWorkerTestCase.php @@ -8,6 +8,20 @@ final class PhabricatorWorkerTestCase extends PhabricatorTestCase { ); } + protected function willRunOneTest($test) { + parent::willRunOneTest($test); + + // Before we run these test cases, clear the queue. After D20412, we may + // have queued tasks from migrations. + $task_table = new PhabricatorWorkerActiveTask(); + $conn = $task_table->establishConnection('w'); + + queryfx( + $conn, + 'TRUNCATE %R', + $task_table); + } + public function testLeaseTask() { $task = $this->scheduleTask(); $this->expectNextLease($task, pht('Leasing should work.')); diff --git a/src/infrastructure/log/PhabricatorProtocolLog.php b/src/infrastructure/log/PhabricatorProtocolLog.php new file mode 100644 index 0000000000..089d35a229 --- /dev/null +++ b/src/infrastructure/log/PhabricatorProtocolLog.php @@ -0,0 +1,213 @@ +logfile = $logfile; + } + + public function didStartSession($session_name) { + $this->setMode('!'); + $this->buffer[] = $session_name; + $this->flush(); + } + + public function didEndSession() { + $this->setMode('_'); + $this->buffer[] = pht(''); + $this->flush(); + } + + public function didWriteBytes($bytes) { + if (!strlen($bytes)) { + return; + } + + $this->setMode('>'); + $this->buffer[] = $bytes; + } + + public function didReadBytes($bytes) { + if (!strlen($bytes)) { + return; + } + + $this->setMode('<'); + $this->buffer[] = $bytes; + } + + public function didReadFrame($frame) { + $this->writeFrame('<*', $frame); + } + + public function didWriteFrame($frame) { + $this->writeFrame('>*', $frame); + } + + private function writeFrame($header, $frame) { + $this->flush(); + + $frame = explode("\n", $frame); + foreach ($frame as $key => $line) { + $frame[$key] = $header.' '.$this->escapeBytes($line); + } + $frame = implode("\n", $frame)."\n\n"; + + $this->writeMessage($frame); + } + + private function setMode($mode) { + if ($this->mode === $mode) { + return $this; + } + + if ($this->mode !== null) { + $this->flush(); + } + + $this->mode = $mode; + + return $this; + } + + private function flush() { + $mode = $this->mode; + $bytes = $this->buffer; + + $this->mode = null; + $this->buffer = array(); + + $bytes = implode('', $bytes); + + if (strlen($bytes)) { + $this->writeBytes($mode, $bytes); + } + } + + private function writeBytes($mode, $bytes) { + $header = $mode; + $len = strlen($bytes); + + $out = array(); + switch ($mode) { + case '<': + $out[] = pht('%s Write [%s bytes]', $header, new PhutilNumber($len)); + break; + case '>': + $out[] = pht('%s Read [%s bytes]', $header, new PhutilNumber($len)); + break; + default: + $out[] = pht( + '%s %s', + $header, + $this->escapeBytes($bytes)); + break; + } + + switch ($mode) { + case '<': + case '>': + $out[] = $this->renderBytes($header, $bytes); + break; + } + + $out = implode("\n", $out)."\n\n"; + + $this->writeMessage($out); + } + + private function renderBytes($header, $bytes) { + $bytes_per_line = 48; + $bytes_per_chunk = 4; + + // Compute the width of the "bytes" display section, which looks like + // this: + // + // > 00112233 44556677 abcdefgh + // ^^^^^^^^^^^^^^^^^ + // + // We need to figure this out so we can align the plain text in the far + // right column appropriately. + + // The character width of the "bytes" part of a full display line. If + // we're rendering 48 bytes per line, we'll need 96 characters, since + // each byte is printed as a 2-character hexadecimal code. + $display_bytes = ($bytes_per_line * 2); + + // The character width of the number of spaces in between the "bytes" + // chunks. If we're rendering 12 chunks per line, we'll put 11 spaces + // in between them to separate them. + $display_spaces = (($bytes_per_line / $bytes_per_chunk) - 1); + + $pad_bytes = $display_bytes + $display_spaces; + + // When the protocol is plaintext, try to break it on newlines so it's + // easier to read. + $pos = 0; + $lines = array(); + while (true) { + $next_break = strpos($bytes, "\n", $pos); + if ($next_break === false) { + $len = strlen($bytes) - $pos; + } else { + $len = ($next_break - $pos) + 1; + } + $len = min($bytes_per_line, $len); + + $next_bytes = substr($bytes, $pos, $len); + + $chunk_parts = array(); + foreach (str_split($next_bytes, $bytes_per_chunk) as $chunk) { + $chunk_display = ''; + for ($ii = 0; $ii < strlen($chunk); $ii++) { + $chunk_display .= sprintf('%02x', ord($chunk[$ii])); + } + $chunk_parts[] = $chunk_display; + } + $chunk_parts = implode(' ', $chunk_parts); + + $chunk_parts = str_pad($chunk_parts, $pad_bytes, ' '); + + + $lines[] = $header.' '.$chunk_parts.' '.$this->escapeBytes($next_bytes); + + $pos += $len; + + if ($pos >= strlen($bytes)) { + break; + } + } + + $lines = implode("\n", $lines); + + return $lines; + } + + private function escapeBytes($bytes) { + $result = ''; + for ($ii = 0; $ii < strlen($bytes); $ii++) { + $c = $bytes[$ii]; + $o = ord($c); + + if ($o >= 0x20 && $o <= 0x7F) { + $result .= $c; + } else { + $result .= '.'; + } + } + return $result; + } + + private function writeMessage($message) { + $f = fopen($this->logfile, 'a'); + fwrite($f, $message); + fflush($f); + fclose($f); + } + +} diff --git a/src/view/page/PhabricatorStandardPageView.php b/src/view/page/PhabricatorStandardPageView.php index 1933f223e5..bebb13ad1e 100644 --- a/src/view/page/PhabricatorStandardPageView.php +++ b/src/view/page/PhabricatorStandardPageView.php @@ -304,6 +304,12 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView )); } + // If we aren't showing the page chrome, skip rendering DarkConsole and the + // main menu, since they won't be visible on the page. + if (!$this->getShowChrome()) { + return; + } + if ($console) { require_celerity_resource('aphront-dark-console-css'); @@ -347,6 +353,7 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView $menu->setApplicationMenu($application_menu); } + $this->menuContent = $menu->render(); } diff --git a/src/view/phui/PHUICrumbsView.php b/src/view/phui/PHUICrumbsView.php index 00a1f1911e..fb1a0ae9b4 100644 --- a/src/view/phui/PHUICrumbsView.php +++ b/src/view/phui/PHUICrumbsView.php @@ -50,9 +50,15 @@ final class PHUICrumbsView extends AphrontView { $action_view = null; if ($this->actions) { + // TODO: This block of code takes "PHUIListItemView" objects and turns + // them into some weird abomination by reading most of their properties + // out. Some day, this workflow should render the items and CSS should + // resytle them in place without needing a wholly separate set of + // DOM nodes. + $actions = array(); foreach ($this->actions as $action) { - if ($action->getType() == PHUIListItemView::TYPE_DIVIDER) { + if ($action->getType() == PHUIListItemView::TYPE_DIVIDER) { $actions[] = phutil_tag( 'span', array( diff --git a/src/view/phui/PHUIListItemView.php b/src/view/phui/PHUIListItemView.php index 8e53024826..5de58b5492 100644 --- a/src/view/phui/PHUIListItemView.php +++ b/src/view/phui/PHUIListItemView.php @@ -35,6 +35,7 @@ final class PHUIListItemView extends AphrontTagView { private $actionIconHref; private $count; private $rel; + private $dropdownMenu; public function setOpenInNewWindow($open_in_new_window) { $this->openInNewWindow = $open_in_new_window; @@ -64,10 +65,19 @@ final class PHUIListItemView extends AphrontTagView { } public function setDropdownMenu(PhabricatorActionListView $actions) { - Javelin::initBehavior('phui-dropdown-menu'); - $this->addSigil('phui-dropdown-menu'); - $this->setMetadata($actions->getDropdownMenuMetadata()); + $this->dropdownMenu = $actions; + + // TODO: "PHUICrumbsView" currently creates a bad copy of list items + // by reading some of their properties. To survive this copy step, we + // need to mutate "$this" immediately or the "Create Object" dropdown + // when multiple create forms exist breaks. + + if (!$this->actionIcon) { + Javelin::initBehavior('phui-dropdown-menu'); + $this->addSigil('phui-dropdown-menu'); + $this->setMetadata($actions->getDropdownMenuMetadata()); + } return $this; } @@ -235,8 +245,22 @@ final class PHUIListItemView extends AphrontTagView { $classes[] = 'phui-list-item-has-action-icon'; } + $sigil = null; + $metadata = null; + if ($this->dropdownMenu) { + $classes[] = 'dropdown'; + if (!$this->actionIcon) { + $classes[] = 'dropdown-with-caret'; + Javelin::initBehavior('phui-dropdown-menu'); + $sigil = 'phui-dropdown-menu'; + $metadata = $this->dropdownMenu->getDropdownMenuMetadata(); + } + } + return array( - 'class' => implode(' ', $classes), + 'class' => $classes, + 'sigil' => $sigil, + 'meta' => $metadata, ); } @@ -339,19 +363,7 @@ final class PHUIListItemView extends AphrontTagView { $classes[] = 'phui-list-item-indented'; } - $action_link = null; - if ($this->actionIcon) { - $action_icon = id(new PHUIIconView()) - ->setIcon($this->actionIcon) - ->addClass('phui-list-item-action-icon'); - $action_link = phutil_tag( - 'a', - array( - 'href' => $this->actionIconHref, - 'class' => 'phui-list-item-action-href', - ), - $action_icon); - } + $action_link = $this->newActionIconView(); $count = null; if ($this->count) { @@ -363,6 +375,12 @@ final class PHUIListItemView extends AphrontTagView { $this->count); } + $caret = null; + if ($this->dropdownMenu && !$this->actionIcon) { + $caret = id(new PHUIIconView()) + ->setIcon('fa-caret-down'); + } + $icons = $this->getIcons(); $list_item = javelin_tag( @@ -382,9 +400,42 @@ final class PHUIListItemView extends AphrontTagView { $this->renderChildren(), $name, $count, + $caret, )); return array($list_item, $action_link); } + private function newActionIconView() { + $action_icon = $this->actionIcon; + $action_href = $this->actionIconHref; + + if ($action_icon === null) { + return null; + } + + $icon_view = id(new PHUIIconView()) + ->setIcon($action_icon) + ->addClass('phui-list-item-action-icon'); + + if ($this->dropdownMenu) { + Javelin::initBehavior('phui-dropdown-menu'); + $sigil = 'phui-dropdown-menu'; + $metadata = $this->dropdownMenu->getDropdownMenuMetadata(); + } else { + $sigil = null; + $metadata = null; + } + + return javelin_tag( + 'a', + array( + 'href' => $action_href, + 'class' => 'phui-list-item-action-href', + 'sigil' => $sigil, + 'meta' => $metadata, + ), + $icon_view); + } + } diff --git a/src/view/phui/PHUIObjectItemView.php b/src/view/phui/PHUIObjectItemView.php index 40dd8f0a4b..05747c7ce6 100644 --- a/src/view/phui/PHUIObjectItemView.php +++ b/src/view/phui/PHUIObjectItemView.php @@ -299,6 +299,8 @@ final class PHUIObjectItemView extends AphrontTagView { if ($this->disabled) { $item_classes[] = 'phui-oi-disabled'; + } else { + $item_classes[] = 'phui-oi-enabled'; } switch ($this->effect) { diff --git a/src/view/phui/PHUITagView.php b/src/view/phui/PHUITagView.php index aa309a4bb6..b77ec5d70e 100644 --- a/src/view/phui/PHUITagView.php +++ b/src/view/phui/PHUITagView.php @@ -24,6 +24,7 @@ final class PHUITagView extends AphrontTagView { const COLOR_BLUEGREY = 'bluegrey'; const COLOR_CHECKERED = 'checkered'; const COLOR_DISABLED = 'disabled'; + const COLOR_PLACEHOLDER = 'placeholder'; const COLOR_OBJECT = 'object'; const COLOR_PERSON = 'person'; diff --git a/webroot/rsrc/css/aphront/table-view.css b/webroot/rsrc/css/aphront/table-view.css index fd1a918148..3736ffe841 100644 --- a/webroot/rsrc/css/aphront/table-view.css +++ b/webroot/rsrc/css/aphront/table-view.css @@ -160,6 +160,9 @@ th.aphront-table-view-sortable-selected { vertical-align: top; } +/* Apply this rule to both "" and "" so that the header widths + are correct if the table has no rows. */ +.aphront-table-view th.wide, .aphront-table-view td.wide { white-space: normal; width: 100%; diff --git a/webroot/rsrc/css/application/base/main-menu-view.css b/webroot/rsrc/css/application/base/main-menu-view.css index 9a4fe28d7a..f88db3a4be 100644 --- a/webroot/rsrc/css/application/base/main-menu-view.css +++ b/webroot/rsrc/css/application/base/main-menu-view.css @@ -650,6 +650,7 @@ ul.phabricator-core-user-profile-object .phui-oi-content, ul.phabricator-core-user-profile-object .phui-oi-subhead { padding: 0; margin: 0; + background: transparent; } ul.phabricator-core-user-profile-object.phui-oi-list-simple .phui-oi-image { diff --git a/webroot/rsrc/css/application/dashboard/dashboard.css b/webroot/rsrc/css/application/dashboard/dashboard.css index aed87b7673..daa2cfb5c7 100644 --- a/webroot/rsrc/css/application/dashboard/dashboard.css +++ b/webroot/rsrc/css/application/dashboard/dashboard.css @@ -54,7 +54,7 @@ } .grippable .aphront-multi-column-column .dashboard-box.phui-object-box:hover { - box-shadow: {$dropshadow}; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.10); } .grippable .aphront-multi-column-column .dashboard-box.phui-object-box:hover diff --git a/webroot/rsrc/css/application/phame/phame.css b/webroot/rsrc/css/application/phame/phame.css index 4478e607e9..be9939b51f 100644 --- a/webroot/rsrc/css/application/phame/phame.css +++ b/webroot/rsrc/css/application/phame/phame.css @@ -284,6 +284,7 @@ text-align: center; background: {$page.content}; padding: 16px 0 24px; + border-top: 1px solid {$document.border}; } .device-phone .phame-mega-header { diff --git a/webroot/rsrc/css/application/project/project-triggers.css b/webroot/rsrc/css/application/project/project-triggers.css index 9b3ce8e462..67705df146 100644 --- a/webroot/rsrc/css/application/project/project-triggers.css +++ b/webroot/rsrc/css/application/project/project-triggers.css @@ -27,6 +27,7 @@ .trigger-rules-table td.invalid-cell { padding-left: 12px; + width: 100%; } .trigger-rules-table td.invalid-cell .phui-icon-view { diff --git a/webroot/rsrc/css/phui/object-item/phui-oi-big-ui.css b/webroot/rsrc/css/phui/object-item/phui-oi-big-ui.css index 2d2163f9e9..3ad8f3a198 100644 --- a/webroot/rsrc/css/phui/object-item/phui-oi-big-ui.css +++ b/webroot/rsrc/css/phui/object-item/phui-oi-big-ui.css @@ -70,21 +70,29 @@ } .phui-oi-list-big .phui-oi-linked-container { - border: 1px solid {$lightblueborder}; + border-width: 1px; + border-style: solid; border-radius: 4px; - box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.035); } -.phui-oi-list-big .phui-oi-disabled { - border-radius: 4px; - background: {$lightgreybackground}; +.phui-oi-list-big .phui-oi-enabled.phui-oi-linked-container { + border-color: {$lightblueborder}; + box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.05); +} + +.phui-oi-list-big .phui-oi-disabled.phui-oi-linked-container { + border-color: {$greybackground}; +} + +.phui-oi-list-big .phui-oi-disabled .phui-oi-image-icon .phui-icon-view { + color: {$darkgreybackground}; } .device-desktop .phui-oi-linked-container { cursor: pointer; } -.device-desktop .phui-oi-linked-container:hover { +.device-desktop .phui-oi-enabled.phui-oi-linked-container:hover { background-color: {$hoverblue}; border-color: {$blueborder}; } diff --git a/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css b/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css index d8ac3a8bbb..85f83802e7 100644 --- a/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css +++ b/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css @@ -611,11 +611,6 @@ ul.phui-oi-list-view .phui-oi-selected border-top: none; } -.dashboard-pane .phui-oi-empty .phui-info-view { - border: none; - margin: 0; -} - .device-desktop .aphront-multi-column-fluid .aphront-multi-column-2-up .aphront-multi-column-column-outer.third .phui-oi-col2 { display: none; diff --git a/webroot/rsrc/css/phui/phui-action-list.css b/webroot/rsrc/css/phui/phui-action-list.css index 3df4ff1b78..0163437b60 100644 --- a/webroot/rsrc/css/phui/phui-action-list.css +++ b/webroot/rsrc/css/phui/phui-action-list.css @@ -213,3 +213,32 @@ .phabricator-action-view-item .phui-icon-view { color: {$sky}; } + +.phui-list-navbar .phui-list-item-href { + display: inline-block; +} + +.phui-list-navbar .phui-list-item-disabled .phui-list-item-href { + color: {$lightgreytext}; +} + +.phui-list-navbar .phui-list-item-action-href { + display: inline-block; + padding: 8px 16px; + line-height: 16px; +} + +.phui-list-navbar .phui-list-item-action-href .phui-icon-view { + color: {$darkgreytext}; +} + +.device-desktop + .phui-list-navbar .phui-list-item-action-href:hover { + background-color: rgba({$alphablue}, 0.07); + color: {$sky}; +} + +.phui-list-navbar .dropdown-with-caret .phui-list-item-href + .phui-icon-view { + margin-left: 12px; +} diff --git a/webroot/rsrc/css/phui/phui-document-pro.css b/webroot/rsrc/css/phui/phui-document-pro.css index 84dc11601f..cdd790f574 100644 --- a/webroot/rsrc/css/phui/phui-document-pro.css +++ b/webroot/rsrc/css/phui/phui-document-pro.css @@ -56,7 +56,7 @@ .phui-document-container { background-color: {$page.content}; position: relative; - border-bottom: 1px solid #dedee1; + border-bottom: 1px solid {$document.border}; } .phui-document-view-pro-box, diff --git a/webroot/rsrc/css/phui/phui-info-view.css b/webroot/rsrc/css/phui/phui-info-view.css index b4fafc6e59..0d15d08e4b 100644 --- a/webroot/rsrc/css/phui/phui-info-view.css +++ b/webroot/rsrc/css/phui/phui-info-view.css @@ -74,8 +74,8 @@ h1.phui-info-view-head { } .phui-info-view-list { - margin: 0; - list-style: none; + margin-left: 30px; + list-style: disc; line-height: 1.6em; } diff --git a/webroot/rsrc/css/phui/phui-list.css b/webroot/rsrc/css/phui/phui-list.css index 5fab89bcd8..6932d9f29b 100644 --- a/webroot/rsrc/css/phui/phui-list.css +++ b/webroot/rsrc/css/phui/phui-list.css @@ -110,10 +110,6 @@ border-right: 1px solid {$thinblueborder}; } -.phui-list-view.phui-list-navbar > li > * { - display: block; -} - .phui-list-navbar .phui-list-item-href { color: {$bluetext}; padding: 8px 16px; @@ -265,7 +261,8 @@ /* - Action Icon ----------------------------------------------------------- */ -.phui-list-item-has-action-icon .phui-list-item-action-href { +.phabricator-nav-local .phui-list-item-has-action-icon + .phui-list-item-action-href { position: absolute; width: 28px; top: 0; @@ -277,26 +274,30 @@ display: none; } -.phui-list-item-has-action-icon.phui-list-item-selected .phui-list-item-href { +.phabricator-nav-local .phui-list-item-has-action-icon.phui-list-item-selected + .phui-list-item-href { padding-right: 32px; } -.phui-list-item-has-action-icon.phui-list-item-selected +.phabricator-nav-local .phui-list-item-has-action-icon.phui-list-item-selected .phui-list-item-action-href { display: block; } -.phui-list-item-has-action-icon .phui-list-item-action-href:hover { +.phabricator-nav-local .phui-list-item-has-action-icon + .phui-list-item-action-href:hover { background-color: rgba({$alphablack},.05); } -.phui-list-item-has-action-icon .phui-list-item-action-icon { +.phabricator-nav-local .phui-list-item-has-action-icon + .phui-list-item-action-icon { opacity: 0.5; } -.phui-list-item-has-action-icon .phui-list-item-action-href:hover +.phabricator-nav-local .phui-list-item-has-action-icon + .phui-list-item-action-href:hover .phui-list-item-action-icon { - opacity: 1; + opacity: 1; } /* - Item Counts ----------------------------------------------------------- */ diff --git a/webroot/rsrc/css/phui/phui-tag-view.css b/webroot/rsrc/css/phui/phui-tag-view.css index 57529645a7..d0b2afce1d 100644 --- a/webroot/rsrc/css/phui/phui-tag-view.css +++ b/webroot/rsrc/css/phui/phui-tag-view.css @@ -441,6 +441,12 @@ a.phui-tag-view:hover.phui-tag-disabled .phui-tag-core { border-color: {$sh-disabledborder}; } +.phui-tag-placeholder .phui-tag-core { + border-color: transparent; + background-color: {$sh-disabledbackground}; + opacity: 0.5; +} + /* - Outline Tags -------------------------------------------------------------- Basic Tag with a bold border and white background diff --git a/webroot/rsrc/externals/javelin/core/util.js b/webroot/rsrc/externals/javelin/core/util.js index c0353c5f39..d8495ba4e5 100644 --- a/webroot/rsrc/externals/javelin/core/util.js +++ b/webroot/rsrc/externals/javelin/core/util.js @@ -295,9 +295,69 @@ if (!window.console || !window.console.log) { * @return void */ JX.log = function(message) { + // "JX.log()" accepts "Error" in addition to "string". Only try to + // treat the argument as a "sprintf()" pattern if it's a string. + if (typeof message === 'string') { + message = JX.sprintf.apply(null, arguments); + } window.console.log(message); }; +JX.sprintf = function(pattern) { + var argv = Array.prototype.slice.call(arguments); + argv.reverse(); + + // Pop off the pattern argument. + argv.pop(); + + var len = pattern.length; + var output = ''; + for (var ii = 0; ii < len; ii++) { + var c = pattern.charAt(ii); + + if (c !== '%') { + output += c; + continue; + } + + ii++; + + var next = pattern.charAt(ii); + if (next === '%') { + // This is "%%" (that is, an escaped "%" symbol), so just add a literal + // "%" to the result. + output += '%'; + continue; + } + + if (next === 's') { + if (!argv.length) { + throw new Error( + 'Too few arguments to "JX.sprintf(...)" for pattern: ' + pattern); + } + + output += '' + argv.pop(); + + continue; + } + + if (next === '') { + throw new Error( + 'Pattern passed to "JX.sprintf(...)" ends with "%": ' + pattern); + } + + throw new Error( + 'Unknown conversion "%' + c + '" passed to "JX.sprintf(...)" in ' + + 'pattern: ' + pattern); + } + + if (argv.length) { + throw new Error( + 'Too many arguments to "JX.sprintf()" for pattern: ' + pattern); + } + + return output; +}; if (__DEV__) { window.alert = (function(native_alert) { diff --git a/webroot/rsrc/externals/javelin/lib/behavior.js b/webroot/rsrc/externals/javelin/lib/behavior.js index ebd63bb1ac..a15f763fe3 100644 --- a/webroot/rsrc/externals/javelin/lib/behavior.js +++ b/webroot/rsrc/externals/javelin/lib/behavior.js @@ -91,7 +91,14 @@ JX.initBehaviors = function(map) { configs = [null]; } for (var ii = 0; ii < configs.length; ii++) { - JX.behavior._behaviors[name](configs[ii], JX.behavior._statics[name]); + try { + JX.behavior._behaviors[name](configs[ii], JX.behavior._statics[name]); + } catch (behavior_exception) { + JX.log( + 'JX.initBehaviors(...): behavior "%s" raised an error during setup.', + name); + JX.log(behavior_exception); + } } JX.behavior._initialized[name] = true; } diff --git a/webroot/rsrc/js/application/dashboard/behavior-dashboard-async-panel.js b/webroot/rsrc/js/application/dashboard/behavior-dashboard-async-panel.js index 0ab1796e5b..126f2d868e 100644 --- a/webroot/rsrc/js/application/dashboard/behavior-dashboard-async-panel.js +++ b/webroot/rsrc/js/application/dashboard/behavior-dashboard-async-panel.js @@ -12,7 +12,9 @@ JX.behavior('dashboard-async-panel', function(config) { var data = { parentPanelPHIDs: config.parentPanelPHIDs.join(','), headerMode: config.headerMode, - dashboardID: config.dashboardID + contextPHID: config.contextPHID, + movable: config.movable, + panelKey: config.panelKey }; new JX.Workflow(config.uri) diff --git a/webroot/rsrc/js/application/dashboard/behavior-dashboard-move-panels.js b/webroot/rsrc/js/application/dashboard/behavior-dashboard-move-panels.js index d8d08eca5f..c01ab6cd04 100644 --- a/webroot/rsrc/js/application/dashboard/behavior-dashboard-move-panels.js +++ b/webroot/rsrc/js/application/dashboard/behavior-dashboard-move-panels.js @@ -17,7 +17,7 @@ JX.behavior('dashboard-move-panels', function(config) { } function markcolempty(col, toggle) { - JX.DOM.alterClass(col, 'dashboard-column-empty', toggle); + JX.DOM.alterClass(col.parentNode, 'dashboard-column-empty', toggle); } function onupdate(col) { @@ -33,40 +33,16 @@ JX.behavior('dashboard-move-panels', function(config) { list.lock(); JX.DOM.alterClass(item, 'drag-sending', true); - var item_phid = JX.Stratcom.getData(item).objectPHID; var data = { - objectPHID: item_phid, - columnID: JX.Stratcom.getData(list.getRootNode()).columnID + panelKey: JX.Stratcom.getData(item).panelKey, + columnKey: JX.Stratcom.getData(list.getRootNode()).columnKey }; - var after_phid = null; - var items = finditems(list.getRootNode()); if (after) { - after_phid = JX.Stratcom.getData(after).objectPHID; - data.afterPHID = after_phid; - } - var ii; - var ii_item; - var ii_item_phid; - var ii_prev_item_phid = null; - var before_phid = null; - for (ii = 0; ii < items.length; ii++) { - ii_item = items[ii]; - ii_item_phid = JX.Stratcom.getData(ii_item).objectPHID; - if (ii_item_phid == item_phid) { - // skip the item we just dropped - continue; + var after_data = JX.Stratcom.getData(after); + if (after_data.panelKey) { + data.afterKey = after_data.panelKey; } - // note this handles when there is no after phid - we are at the top of - // the list - quite nicely - if (ii_prev_item_phid == after_phid) { - before_phid = ii_item_phid; - break; - } - ii_prev_item_phid = ii_item_phid; - } - if (before_phid) { - data.beforePHID = before_phid; } var workflow = new JX.Workflow(config.moveURI, data) @@ -77,23 +53,24 @@ JX.behavior('dashboard-move-panels', function(config) { workflow.start(); } - var lists = []; - var ii; - var cols = JX.DOM.scry(JX.$(config.dashboardID), 'div', 'dashboard-column'); - var col = null; + var dashboard_node = JX.$(config.dashboardNodeID); + var lists = []; + var cols = JX.DOM.scry(dashboard_node, 'div', 'dashboard-column'); + + var ii; for (ii = 0; ii < cols.length; ii++) { - col = cols[ii]; + var col = cols[ii]; var list = new JX.DraggableList(itemSigil, col) .setFindItemsHandler(JX.bind(null, finditems, col)) .setCanDragX(true); list.listen('didSend', JX.bind(list, onupdate, col)); list.listen('didReceive', JX.bind(list, onupdate, col)); - list.listen('didDrop', JX.bind(null, ondrop, list)); lists.push(list); + markcolempty(col, finditems(col).length === 0); } diff --git a/webroot/rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js b/webroot/rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js index b9c70d7df9..5d79c35ce3 100644 --- a/webroot/rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js +++ b/webroot/rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js @@ -8,28 +8,42 @@ JX.behavior('dashboard-tab-panel', function() { JX.Stratcom.listen('click', 'dashboard-tab-panel-tab', function(e) { + // On dashboard panels in edit mode, the user may click the dropdown caret + // within a tab to open the context menu. If they do, this click should + // just open the menu, not select the tab. For now, pass the event here + // to let the menu handler act on it. + if (JX.Stratcom.pass(e)) { + return; + } + e.kill(); - var ii; - var idx = e.getNodeData('dashboard-tab-panel-tab').idx; + var selected_key = e.getNodeData('dashboard-tab-panel-tab').panelKey; var root = e.getNode('dashboard-tab-panel-container'); var data = JX.Stratcom.getData(root); + + var ii; // Give the tab the user clicked a selected style, and remove it from // the other tabs. var tabs = JX.DOM.scry(root, 'li', 'dashboard-tab-panel-tab'); for (ii = 0; ii < tabs.length; ii++) { - JX.DOM.alterClass(tabs[ii], 'phui-list-item-selected', (ii == idx)); + var tab = tabs[ii]; + var tab_data = JX.Stratcom.getData(tab); + var is_selected = (tab_data.panelKey === selected_key); + JX.DOM.alterClass(tabs[ii], 'phui-list-item-selected', is_selected); } // Switch the visible content to correspond to whatever the user clicked. for (ii = 0; ii < data.panels.length; ii++) { - var panel = JX.$(data.panels[ii]); - if (ii == idx) { - JX.DOM.show(panel); + var panel = data.panels[ii]; + var node = JX.$(panel.panelContentID); + + if (panel.panelKey == selected_key) { + JX.DOM.show(node); } else { - JX.DOM.hide(panel); + JX.DOM.hide(node); } } diff --git a/webroot/rsrc/js/application/fact/Chart.js b/webroot/rsrc/js/application/fact/Chart.js new file mode 100644 index 0000000000..ceb3b2ad00 --- /dev/null +++ b/webroot/rsrc/js/application/fact/Chart.js @@ -0,0 +1,156 @@ +/** + * @provides javelin-chart + * @requires phui-chart-css + * d3 + */ +JX.install('Chart', { + + construct: function(root_node) { + this._rootNode = root_node; + + JX.Stratcom.listen('resize', null, JX.bind(this, this._redraw)); + }, + + members: { + _rootNode: null, + _data: null, + + setData: function(blob) { + this._data = blob; + this._redraw(); + }, + + _redraw: function() { + if (!this._data) { + return; + } + + var hardpoint = this._rootNode; + var viewport = JX.Vector.getDim(hardpoint); + var config = this._data; + + function css_function(n) { + return n + '(' + JX.$A(arguments).slice(1).join(', ') + ')'; + } + + var padding = { + top: 24, + left: 48, + bottom: 48, + right: 32 + }; + + var size = { + frameWidth: viewport.x, + frameHeight: viewport.y, + }; + + size.width = size.frameWidth - padding.left - padding.right; + size.height = size.frameHeight - padding.top - padding.bottom; + + var x = d3.time.scale() + .range([0, size.width]); + + var y = d3.scale.linear() + .range([size.height, 0]); + + var xAxis = d3.svg.axis() + .scale(x) + .orient('bottom'); + + var yAxis = d3.svg.axis() + .scale(y) + .orient('left'); + + // Remove the old chart (if one exists) before drawing the new chart. + JX.DOM.setContent(hardpoint, []); + + var svg = d3.select('#' + hardpoint.id).append('svg') + .attr('width', size.frameWidth) + .attr('height', size.frameHeight) + .attr('class', 'chart'); + + var g = svg.append('g') + .attr( + 'transform', + css_function('translate', padding.left, padding.top)); + + g.append('rect') + .attr('class', 'inner') + .attr('width', size.width) + .attr('height', size.height); + + function as_date(value) { + return new Date(value * 1000); + } + + x.domain([as_date(config.xMin), as_date(config.xMax)]); + y.domain([config.yMin, config.yMax]); + + var div = d3.select('body') + .append('div') + .attr('class', 'chart-tooltip') + .style('opacity', 0); + + for (var idx = 0; idx < config.datasets.length; idx++) { + var dataset = config.datasets[idx]; + + var line = d3.svg.line() + .x(function(d) { return x(d.xvalue); }) + .y(function(d) { return y(d.yvalue); }); + + var data = []; + for (var ii = 0; ii < dataset.x.length; ii++) { + data.push( + { + xvalue: as_date(dataset.x[ii]), + yvalue: dataset.y[ii] + }); + } + + g.append('path') + .datum(data) + .attr('class', 'line') + .style('stroke', dataset.color) + .attr('d', line); + + g.selectAll('dot') + .data(data) + .enter() + .append('circle') + .attr('class', 'point') + .attr('r', 3) + .attr('cx', function(d) { return x(d.xvalue); }) + .attr('cy', function(d) { return y(d.yvalue); }) + .on('mouseover', function(d) { + var d_y = d.xvalue.getFullYear(); + + // NOTE: Javascript months are zero-based. See PHI1017. + var d_m = d.xvalue.getMonth() + 1; + + var d_d = d.xvalue.getDate(); + + div + .html(d_y + '-' + d_m + '-' + d_d + ': ' + d.yvalue) + .style('opacity', 0.9) + .style('left', (d3.event.pageX - 60) + 'px') + .style('top', (d3.event.pageY - 38) + 'px'); + }) + .on('mouseout', function() { + div.style('opacity', 0); + }); + } + + g.append('g') + .attr('class', 'x axis') + .attr('transform', css_function('translate', 0, size.height)) + .call(xAxis); + + g.append('g') + .attr('class', 'y axis') + .attr('transform', css_function('translate', 0, 0)) + .call(yAxis); + } + } + +}); diff --git a/webroot/rsrc/js/application/maniphest/behavior-line-chart-legacy.js b/webroot/rsrc/js/application/maniphest/behavior-line-chart-legacy.js new file mode 100644 index 0000000000..187f39a8d4 --- /dev/null +++ b/webroot/rsrc/js/application/maniphest/behavior-line-chart-legacy.js @@ -0,0 +1,126 @@ +/** + * @provides javelin-behavior-line-chart-legacy + * @requires javelin-behavior + * javelin-dom + * javelin-vector + * phui-chart-css + */ + +JX.behavior('line-chart-legacy', function(config) { + + function fn(n) { + return n + '(' + JX.$A(arguments).slice(1).join(', ') + ')'; + } + + var h = JX.$(config.hardpoint); + var d = JX.Vector.getDim(h); + + var padding = { + top: 24, + left: 48, + bottom: 48, + right: 32 + }; + + var size = { + frameWidth: d.x, + frameHeight: d.y, + }; + + size.width = size.frameWidth - padding.left - padding.right; + size.height = size.frameHeight - padding.top - padding.bottom; + + var x = d3.time.scale() + .range([0, size.width]); + + var y = d3.scale.linear() + .range([size.height, 0]); + + var xAxis = d3.svg.axis() + .scale(x) + .orient('bottom'); + + var yAxis = d3.svg.axis() + .scale(y) + .orient('left'); + + var svg = d3.select('#' + config.hardpoint).append('svg') + .attr('width', size.frameWidth) + .attr('height', size.frameHeight) + .attr('class', 'chart'); + + var g = svg.append('g') + .attr('transform', fn('translate', padding.left, padding.top)); + + g.append('rect') + .attr('class', 'inner') + .attr('width', size.width) + .attr('height', size.height); + + var line = d3.svg.line() + .x(function(d) { return x(d.date); }) + .y(function(d) { return y(d.count); }); + + var data = []; + for (var ii = 0; ii < config.x[0].length; ii++) { + data.push( + { + date: new Date(config.x[0][ii] * 1000), + count: +config.y[0][ii] + }); + } + + x.domain(d3.extent(data, function(d) { return d.date; })); + + var yex = d3.extent(data, function(d) { return d.count; }); + yex[0] = 0; + yex[1] = yex[1] * 1.05; + y.domain(yex); + + g.append('path') + .datum(data) + .attr('class', 'line') + .attr('d', line); + + g.append('g') + .attr('class', 'x axis') + .attr('transform', fn('translate', 0, size.height)) + .call(xAxis); + + g.append('g') + .attr('class', 'y axis') + .attr('transform', fn('translate', 0, 0)) + .call(yAxis); + + var div = d3.select('body') + .append('div') + .attr('class', 'chart-tooltip') + .style('opacity', 0); + + g.selectAll('dot') + .data(data) + .enter() + .append('circle') + .attr('class', 'point') + .attr('r', 3) + .attr('cx', function(d) { return x(d.date); }) + .attr('cy', function(d) { return y(d.count); }) + .on('mouseover', function(d) { + var d_y = d.date.getFullYear(); + + // NOTE: Javascript months are zero-based. See PHI1017. + var d_m = d.date.getMonth() + 1; + + var d_d = d.date.getDate(); + + div + .html(d_y + '-' + d_m + '-' + d_d + ': ' + d.count) + .style('opacity', 0.9) + .style('left', (d3.event.pageX - 60) + 'px') + .style('top', (d3.event.pageY - 38) + 'px'); + }) + .on('mouseout', function() { + div.style('opacity', 0); + }); + +}); diff --git a/webroot/rsrc/js/application/maniphest/behavior-line-chart.js b/webroot/rsrc/js/application/maniphest/behavior-line-chart.js index b2290620ff..441461531c 100644 --- a/webroot/rsrc/js/application/maniphest/behavior-line-chart.js +++ b/webroot/rsrc/js/application/maniphest/behavior-line-chart.js @@ -2,125 +2,18 @@ * @provides javelin-behavior-line-chart * @requires javelin-behavior * javelin-dom - * javelin-vector - * phui-chart-css + * javelin-chart */ JX.behavior('line-chart', function(config) { + var chart_node = JX.$(config.chartNodeID); - function fn(n) { - return n + '(' + JX.$A(arguments).slice(1).join(', ') + ')'; + var chart = new JX.Chart(chart_node); + + function onresponse(r) { + chart.setData(r); } - var h = JX.$(config.hardpoint); - var d = JX.Vector.getDim(h); - - var padding = { - top: 24, - left: 48, - bottom: 48, - right: 32 - }; - - var size = { - frameWidth: d.x, - frameHeight: d.y, - }; - - size.width = size.frameWidth - padding.left - padding.right; - size.height = size.frameHeight - padding.top - padding.bottom; - - var x = d3.time.scale() - .range([0, size.width]); - - var y = d3.scale.linear() - .range([size.height, 0]); - - var xAxis = d3.svg.axis() - .scale(x) - .orient('bottom'); - - var yAxis = d3.svg.axis() - .scale(y) - .orient('left'); - - var svg = d3.select('#' + config.hardpoint).append('svg') - .attr('width', size.frameWidth) - .attr('height', size.frameHeight) - .attr('class', 'chart'); - - var g = svg.append('g') - .attr('transform', fn('translate', padding.left, padding.top)); - - g.append('rect') - .attr('class', 'inner') - .attr('width', size.width) - .attr('height', size.height); - - var line = d3.svg.line() - .x(function(d) { return x(d.date); }) - .y(function(d) { return y(d.count); }); - - var data = []; - for (var ii = 0; ii < config.x[0].length; ii++) { - data.push( - { - date: new Date(config.x[0][ii] * 1000), - count: +config.y[0][ii] - }); - } - - x.domain(d3.extent(data, function(d) { return d.date; })); - - var yex = d3.extent(data, function(d) { return d.count; }); - yex[0] = 0; - yex[1] = yex[1] * 1.05; - y.domain(yex); - - g.append('path') - .datum(data) - .attr('class', 'line') - .attr('d', line); - - g.append('g') - .attr('class', 'x axis') - .attr('transform', fn('translate', 0, size.height)) - .call(xAxis); - - g.append('g') - .attr('class', 'y axis') - .attr('transform', fn('translate', 0, 0)) - .call(yAxis); - - var div = d3.select('body') - .append('div') - .attr('class', 'chart-tooltip') - .style('opacity', 0); - - g.selectAll('dot') - .data(data) - .enter() - .append('circle') - .attr('class', 'point') - .attr('r', 3) - .attr('cx', function(d) { return x(d.date); }) - .attr('cy', function(d) { return y(d.count); }) - .on('mouseover', function(d) { - var d_y = d.date.getFullYear(); - - // NOTE: Javascript months are zero-based. See PHI1017. - var d_m = d.date.getMonth() + 1; - - var d_d = d.date.getDate(); - - div - .html(d_y + '-' + d_m + '-' + d_d + ': ' + d.count) - .style('opacity', 0.9) - .style('left', (d3.event.pageX - 60) + 'px') - .style('top', (d3.event.pageY - 38) + 'px'); - }) - .on('mouseout', function() { - div.style('opacity', 0); - }); - + new JX.Request(config.dataURI, onresponse) + .send(); }); diff --git a/webroot/rsrc/js/application/trigger/TriggerRule.js b/webroot/rsrc/js/application/trigger/TriggerRule.js index cf117e24d9..86f9ecb966 100644 --- a/webroot/rsrc/js/application/trigger/TriggerRule.js +++ b/webroot/rsrc/js/application/trigger/TriggerRule.js @@ -98,7 +98,24 @@ JX.install('TriggerRule', { }, _onTypeChange: function(control) { - this.setType(control.value); + var new_type = control.value; + + this.setType(new_type); + + // Before we build a new control, change the rule value to be appropriate + // for the new rule type. + + // TODO: Currently, we set the rule value to the default value for the + // type. This works most of the time, but if the user selects type "A", + // makes a change to the value, selects type "B", then changes back to + // type "A", it would be better to retain their edit. This is currently + // difficult because of tokenizers: if you save their value, you get a + // list of PHIDs which do not restore cleanly into tokens later. + + var editor = this.getEditor(); + var type = editor.getType(new_type); + this.setValue(type.getDefaultValue()); + this._rebuildValueControl(); }, diff --git a/webroot/rsrc/js/phuix/PHUIXDropdownMenu.js b/webroot/rsrc/js/phuix/PHUIXDropdownMenu.js index d9d86bf595..b49cb5b9f0 100644 --- a/webroot/rsrc/js/phuix/PHUIXDropdownMenu.js +++ b/webroot/rsrc/js/phuix/PHUIXDropdownMenu.js @@ -198,7 +198,35 @@ JX.install('PHUIXDropdownMenu', { var v = JX.$V(this._node); var d = JX.Vector.getDim(this._node); - switch (this.getAlign()) { + var alignments = ['right', 'left']; + var disallow = {}; + var margin = 8; + + // If "right" alignment would leave us with the dropdown near or off the + // left side of the screen, disallow it. + var x_min = ((v.x + d.x) - m.x); + if (x_min < margin) { + disallow.right = true; + } + + var align = this.getAlign(); + + // If the position disallows the configured alignment, try the next + // best alignment instead. + + // If no alignment is allowed, we'll stick with the original alignment + // and accept that it isn't going to render very nicely. This can happen + // if the browser window is very, very small. + if (align in disallow) { + for (var ii = 0; ii < alignments.length; ii++) { + if (!(alignments[ii] in disallow)) { + align = alignments[ii]; + break; + } + } + } + + switch (align) { case 'right': v = v.add(d) .add(JX.$V(-m.x, 0));