diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 87b45c9dc6..2433beba80 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,8 +9,8 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'e68cf1fa', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => 'fe4effd6', - 'core.pkg.js' => '5d80e0db', + 'core.pkg.css' => '291cbd98', + 'core.pkg.js' => '6c085267', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '45951e9e', 'differential.pkg.js' => 'b71b8c5d', @@ -39,7 +39,7 @@ return array( 'rsrc/css/aphront/typeahead.css' => 'a4a21016', 'rsrc/css/application/almanac/almanac.css' => 'dbb9b3af', 'rsrc/css/application/auth/auth.css' => '0877ed6e', - 'rsrc/css/application/base/main-menu-view.css' => '16053029', + 'rsrc/css/application/base/main-menu-view.css' => '1802a242', 'rsrc/css/application/base/notification-menu.css' => '73fefdfa', 'rsrc/css/application/base/phui-theme.css' => '9f261c6b', 'rsrc/css/application/base/standard-page-view.css' => '34ee718b', @@ -74,7 +74,7 @@ return array( 'rsrc/css/application/diffusion/diffusion-icons.css' => '0c15255e', 'rsrc/css/application/diffusion/diffusion-readme.css' => '419dd5b6', 'rsrc/css/application/diffusion/diffusion-repository.css' => 'ee6f20ec', - 'rsrc/css/application/diffusion/diffusion-source.css' => '750add59', + 'rsrc/css/application/diffusion/diffusion-source.css' => '47db8a7c', 'rsrc/css/application/diffusion/diffusion.css' => 'ceacf994', 'rsrc/css/application/feed/feed.css' => 'ecd4ec57', 'rsrc/css/application/files/global-drag-and-drop.css' => 'b556a948', @@ -127,7 +127,7 @@ return array( 'rsrc/css/layout/phabricator-source-code-view.css' => 'aea41829', 'rsrc/css/phui/button/phui-button-bar.css' => 'f1ff5494', 'rsrc/css/phui/button/phui-button-simple.css' => '8e1baf68', - 'rsrc/css/phui/button/phui-button.css' => '340f55c1', + 'rsrc/css/phui/button/phui-button.css' => 'a37aa3a8', 'rsrc/css/phui/calendar/phui-calendar-day.css' => '572b1893', 'rsrc/css/phui/calendar/phui-calendar-list.css' => '576be600', 'rsrc/css/phui/calendar/phui-calendar-month.css' => '21154caf', @@ -138,7 +138,7 @@ return array( 'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '9d9685d6', 'rsrc/css/phui/object-item/phui-oi-list-view.css' => 'bf094950', 'rsrc/css/phui/object-item/phui-oi-simple-ui.css' => 'a8beebea', - 'rsrc/css/phui/phui-action-list.css' => '6ee16164', + 'rsrc/css/phui/phui-action-list.css' => 'e7eba156', 'rsrc/css/phui/phui-action-panel.css' => 'b4798122', 'rsrc/css/phui/phui-badge.css' => '22c0cf4f', 'rsrc/css/phui/phui-basic-nav-view.css' => 'a0705f53', @@ -158,7 +158,7 @@ return array( 'rsrc/css/phui/phui-form-view.css' => 'ae9f8d16', 'rsrc/css/phui/phui-form.css' => '7aaa04e3', 'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f', - 'rsrc/css/phui/phui-header-view.css' => 'e7de7ee2', + 'rsrc/css/phui/phui-header-view.css' => '808b82c7', 'rsrc/css/phui/phui-hovercard.css' => 'f0592bcf', 'rsrc/css/phui/phui-icon-set-selector.css' => '87db8fee', 'rsrc/css/phui/phui-icon.css' => '5c4a5de6', @@ -375,9 +375,9 @@ return array( 'rsrc/image/texture/table_header_tall.png' => 'd56b434f', 'rsrc/js/application/aphlict/Aphlict.js' => 'e1d4b11a', 'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => 'caade6f2', - 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => '3c547a81', + 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'a14cbdfc', 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => '5e2634b9', - 'rsrc/js/application/aphlict/behavior-desktop-notifications-control.js' => 'd5a2d665', + 'rsrc/js/application/aphlict/behavior-desktop-notifications-control.js' => '27ca6289', 'rsrc/js/application/calendar/behavior-day-view.js' => '4b3c4443', 'rsrc/js/application/calendar/behavior-event-all-day.js' => 'b41537c9', 'rsrc/js/application/calendar/behavior-month-view.js' => 'fe33e256', @@ -452,7 +452,7 @@ return array( 'rsrc/js/application/transactions/behavior-comment-actions.js' => '9a6dd75c', 'rsrc/js/application/transactions/behavior-reorder-configs.js' => 'd7a74243', 'rsrc/js/application/transactions/behavior-reorder-fields.js' => 'b59e1e96', - 'rsrc/js/application/transactions/behavior-show-older-transactions.js' => 'ae95d984', + 'rsrc/js/application/transactions/behavior-show-older-transactions.js' => '8f29b364', 'rsrc/js/application/transactions/behavior-transaction-comment-form.js' => 'b23b49e6', 'rsrc/js/application/transactions/behavior-transaction-list.js' => '1f6794f6', 'rsrc/js/application/typeahead/behavior-typeahead-browse.js' => '635de1ec', @@ -468,7 +468,7 @@ return array( 'rsrc/js/core/KeyboardShortcut.js' => '1ae869f2', 'rsrc/js/core/KeyboardShortcutManager.js' => 'c19dd9b9', 'rsrc/js/core/MultirowRowManager.js' => 'b5d57730', - 'rsrc/js/core/Notification.js' => 'ccf1cbf8', + 'rsrc/js/core/Notification.js' => '5c3349b2', 'rsrc/js/core/Prefab.js' => 'c5af80a2', 'rsrc/js/core/ShapedRequest.js' => '7cbe244b', 'rsrc/js/core/TextAreaUtils.js' => '320810c8', @@ -574,7 +574,7 @@ return array( 'diffusion-icons-css' => '0c15255e', 'diffusion-readme-css' => '419dd5b6', 'diffusion-repository-css' => 'ee6f20ec', - 'diffusion-source-css' => '750add59', + 'diffusion-source-css' => '47db8a7c', 'diviner-shared-css' => '896f1d43', 'font-fontawesome' => 'e838e088', 'font-lato' => 'c7ccd872', @@ -587,7 +587,7 @@ return array( 'javelin-aphlict' => 'e1d4b11a', 'javelin-behavior' => '61cbc29a', 'javelin-behavior-aphlict-dropdown' => 'caade6f2', - 'javelin-behavior-aphlict-listen' => '3c547a81', + 'javelin-behavior-aphlict-listen' => 'a14cbdfc', 'javelin-behavior-aphlict-status' => '5e2634b9', 'javelin-behavior-aphront-basic-tokenizer' => 'b3a4b884', 'javelin-behavior-aphront-drag-and-drop-textarea' => '484a6e22', @@ -612,7 +612,7 @@ return array( 'javelin-behavior-dashboard-query-panel-select' => '453c5375', 'javelin-behavior-dashboard-tab-panel' => 'd4eecc63', 'javelin-behavior-day-view' => '4b3c4443', - 'javelin-behavior-desktop-notifications-control' => 'd5a2d665', + 'javelin-behavior-desktop-notifications-control' => '27ca6289', 'javelin-behavior-detect-timezone' => '4c193c96', 'javelin-behavior-device' => 'bb1dd507', 'javelin-behavior-diff-preview-link' => '051c7832', @@ -665,7 +665,7 @@ return array( 'javelin-behavior-phabricator-remarkup-assist' => 'acd29eee', 'javelin-behavior-phabricator-reveal-content' => '60821bc7', 'javelin-behavior-phabricator-search-typeahead' => 'd0a99ab4', - 'javelin-behavior-phabricator-show-older-transactions' => 'ae95d984', + 'javelin-behavior-phabricator-show-older-transactions' => '8f29b364', 'javelin-behavior-phabricator-tooltips' => 'c420b0b9', 'javelin-behavior-phabricator-transaction-comment-form' => 'b23b49e6', 'javelin-behavior-phabricator-transaction-list' => '1f6794f6', @@ -767,7 +767,7 @@ return array( 'path-typeahead' => 'f7fc67ec', 'people-picture-menu-item-css' => 'a06f7f34', 'people-profile-css' => '4df76faf', - 'phabricator-action-list-view-css' => '6ee16164', + 'phabricator-action-list-view-css' => 'e7eba156', 'phabricator-busy' => '59a7976a', 'phabricator-chatlog-css' => 'd295b020', 'phabricator-content-source-view-css' => '4b8b05d4', @@ -789,9 +789,9 @@ return array( 'phabricator-flag-css' => 'bba8f811', 'phabricator-keyboard-shortcut' => '1ae869f2', 'phabricator-keyboard-shortcut-manager' => 'c19dd9b9', - 'phabricator-main-menu-view' => '16053029', + 'phabricator-main-menu-view' => '1802a242', 'phabricator-nav-view-css' => 'faf6a6fc', - 'phabricator-notification' => 'ccf1cbf8', + 'phabricator-notification' => '5c3349b2', 'phabricator-notification-css' => '3f6c89c9', 'phabricator-notification-menu-css' => '73fefdfa', 'phabricator-object-selector-css' => '85ee8ce6', @@ -824,7 +824,7 @@ return array( 'phui-big-info-view-css' => 'acc3492c', 'phui-box-css' => '745e881d', 'phui-button-bar-css' => 'f1ff5494', - 'phui-button-css' => '340f55c1', + 'phui-button-css' => 'a37aa3a8', 'phui-button-simple-css' => '8e1baf68', 'phui-calendar-css' => 'f1ddf11c', 'phui-calendar-day-css' => '572b1893', @@ -845,7 +845,7 @@ return array( 'phui-form-css' => '7aaa04e3', 'phui-form-view-css' => 'ae9f8d16', 'phui-head-thing-view-css' => 'fd311e5f', - 'phui-header-view-css' => 'e7de7ee2', + 'phui-header-view-css' => '808b82c7', 'phui-hovercard' => '1bd28176', 'phui-hovercard-view-css' => 'f0592bcf', 'phui-icon-set-selector-css' => '87db8fee', @@ -981,9 +981,6 @@ return array( 'aphront-typeahead-control-css', 'phui-tag-view-css', ), - 16053029 => array( - 'phui-theme-css', - ), '17bb8539' => array( 'javelin-behavior', 'javelin-stratcom', @@ -994,6 +991,9 @@ return array( 'phabricator-darklog', 'phabricator-darkmessage', ), + '1802a242' => array( + 'phui-theme-css', + ), '185bbd53' => array( 'javelin-install', ), @@ -1058,6 +1058,13 @@ return array( 'phabricator-drag-and-drop-file-upload', 'javelin-workboard-board', ), + '27ca6289' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-uri', + 'phabricator-notification', + ), '2926fff2' => array( 'javelin-behavior', 'javelin-dom', @@ -1115,20 +1122,6 @@ return array( 'javelin-dom', 'javelin-magical-init', ), - '3c547a81' => array( - 'javelin-behavior', - 'javelin-aphlict', - 'javelin-stratcom', - 'javelin-request', - 'javelin-uri', - 'javelin-dom', - 'javelin-json', - 'javelin-router', - 'javelin-util', - 'javelin-leader', - 'javelin-sound', - 'phabricator-notification', - ), '3cb0b2fc' => array( 'javelin-behavior', 'javelin-dom', @@ -1338,6 +1331,13 @@ return array( 'javelin-vector', 'javelin-dom', ), + '5c3349b2' => array( + 'javelin-install', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-util', + 'phabricator-notification-css', + ), '5c54cbf3' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1575,6 +1575,12 @@ return array( 'javelin-install', 'phuix-button-view', ), + '8f29b364' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'phabricator-busy', + ), '8ff5e24c' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1676,6 +1682,20 @@ return array( 'javelin-util', 'phabricator-keyboard-shortcut', ), + 'a14cbdfc' => array( + 'javelin-behavior', + 'javelin-aphlict', + 'javelin-stratcom', + 'javelin-request', + 'javelin-uri', + 'javelin-dom', + 'javelin-json', + 'javelin-router', + 'javelin-util', + 'javelin-leader', + 'javelin-sound', + 'phabricator-notification', + ), 'a37126bd' => array( 'javelin-install', 'javelin-dom', @@ -1751,12 +1771,6 @@ return array( 'phuix-autocomplete', 'javelin-mask', ), - 'ae95d984' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'phabricator-busy', - ), 'b003d4fb' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1951,13 +1965,6 @@ return array( 'cae95e89' => array( 'syntax-default-css', ), - 'ccf1cbf8' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-util', - 'phabricator-notification-css', - ), 'cd2b9b77' => array( 'phui-oi-list-view-css', ), @@ -1996,13 +2003,6 @@ return array( 'javelin-dom', 'javelin-stratcom', ), - 'd5a2d665' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-uri', - 'phabricator-notification', - ), 'd6a7e717' => array( 'multirow-row-manager', 'javelin-install', diff --git a/resources/sql/autopatches/20170814.search.01.qconfig.sql b/resources/sql/autopatches/20170814.search.01.qconfig.sql new file mode 100644 index 0000000000..7914336dc4 --- /dev/null +++ b/resources/sql/autopatches/20170814.search.01.qconfig.sql @@ -0,0 +1,9 @@ +CREATE TABLE {$NAMESPACE}_search.search_namedqueryconfig ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + engineClassName VARCHAR(128) NOT NULL COLLATE {$COLLATE_TEXT}, + scopePHID VARBINARY(64) NOT NULL, + properties LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_scope` (engineClassName, scopePHID) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170824.search.01.saved.php b/resources/sql/autopatches/20170824.search.01.saved.php new file mode 100644 index 0000000000..ab1485ebd5 --- /dev/null +++ b/resources/sql/autopatches/20170824.search.01.saved.php @@ -0,0 +1,46 @@ +establishConnection('w'); + +$config_table = new PhabricatorNamedQueryConfig(); + +foreach (new LiskMigrationIterator($table) as $named_query) { + + // If this isn't a builtin query, it isn't changing. Leave it alone. + if (!$named_query->getIsBuiltin()) { + continue; + } + + // If the user reordered things but left a builtin query at the top, pin + // the query before we remove the row. + if ($named_query->getSequence() == 1) { + queryfx( + $conn, + 'INSERT IGNORE INTO %T + (engineClassName, scopePHID, properties, dateCreated, dateModified) + VALUES + (%s, %s, %s, %d, %d)', + $config_table->getTableName(), + $named_query->getEngineClassName(), + $named_query->getUserPHID(), + phutil_json_encode( + array( + PhabricatorNamedQueryConfig::PROPERTY_PINNED => + $named_query->getQueryKey(), + )), + PhabricatorTime::getNow(), + PhabricatorTime::getNow()); + } + + $named_query->delete(); +} diff --git a/resources/sql/autopatches/20170825.phame.01.post.views.sql b/resources/sql/autopatches/20170825.phame.01.post.views.sql new file mode 100644 index 0000000000..5cb5c9c7b6 --- /dev/null +++ b/resources/sql/autopatches/20170825.phame.01.post.views.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_phame.phame_post + DROP COLUMN views; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 070f56d5c8..4575908ef8 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -544,6 +544,7 @@ phutil_register_library_map(array( 'DifferentialRevisionHeraldField' => 'applications/differential/herald/DifferentialRevisionHeraldField.php', 'DifferentialRevisionHeraldFieldGroup' => 'applications/differential/herald/DifferentialRevisionHeraldFieldGroup.php', 'DifferentialRevisionIDCommitMessageField' => 'applications/differential/field/DifferentialRevisionIDCommitMessageField.php', + 'DifferentialRevisionInlineTransaction' => 'applications/differential/xaction/DifferentialRevisionInlineTransaction.php', 'DifferentialRevisionInlinesController' => 'applications/differential/controller/DifferentialRevisionInlinesController.php', 'DifferentialRevisionListController' => 'applications/differential/controller/DifferentialRevisionListController.php', 'DifferentialRevisionListView' => 'applications/differential/view/DifferentialRevisionListView.php', @@ -2647,8 +2648,6 @@ phutil_register_library_map(array( 'PhabricatorDebugController' => 'applications/system/controller/PhabricatorDebugController.php', 'PhabricatorDefaultRequestExceptionHandler' => 'aphront/handler/PhabricatorDefaultRequestExceptionHandler.php', 'PhabricatorDefaultSyntaxStyle' => 'infrastructure/syntax/PhabricatorDefaultSyntaxStyle.php', - 'PhabricatorDesktopNotificationsSetting' => 'applications/settings/setting/PhabricatorDesktopNotificationsSetting.php', - 'PhabricatorDesktopNotificationsSettingsPanel' => 'applications/settings/panel/PhabricatorDesktopNotificationsSettingsPanel.php', 'PhabricatorDestructibleCodex' => 'applications/system/codex/PhabricatorDestructibleCodex.php', 'PhabricatorDestructibleCodexInterface' => 'applications/system/interface/PhabricatorDestructibleCodexInterface.php', 'PhabricatorDestructibleInterface' => 'applications/system/interface/PhabricatorDestructibleInterface.php', @@ -3191,6 +3190,8 @@ phutil_register_library_map(array( 'PhabricatorMySQLSearchHost' => 'infrastructure/cluster/search/PhabricatorMySQLSearchHost.php', 'PhabricatorMySQLSetupCheck' => 'applications/config/check/PhabricatorMySQLSetupCheck.php', 'PhabricatorNamedQuery' => 'applications/search/storage/PhabricatorNamedQuery.php', + 'PhabricatorNamedQueryConfig' => 'applications/search/storage/PhabricatorNamedQueryConfig.php', + 'PhabricatorNamedQueryConfigQuery' => 'applications/search/query/PhabricatorNamedQueryConfigQuery.php', 'PhabricatorNamedQueryQuery' => 'applications/search/query/PhabricatorNamedQueryQuery.php', 'PhabricatorNavigationRemarkupRule' => 'infrastructure/markup/rule/PhabricatorNavigationRemarkupRule.php', 'PhabricatorNeverTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorNeverTriggerClock.php', @@ -3214,6 +3215,8 @@ phutil_register_library_map(array( 'PhabricatorNotificationTestFeedStory' => 'applications/notification/feed/PhabricatorNotificationTestFeedStory.php', 'PhabricatorNotificationUIExample' => 'applications/uiexample/examples/PhabricatorNotificationUIExample.php', 'PhabricatorNotificationsApplication' => 'applications/notification/application/PhabricatorNotificationsApplication.php', + 'PhabricatorNotificationsSetting' => 'applications/settings/setting/PhabricatorNotificationsSetting.php', + 'PhabricatorNotificationsSettingsPanel' => 'applications/settings/panel/PhabricatorNotificationsSettingsPanel.php', 'PhabricatorNuanceApplication' => 'applications/nuance/application/PhabricatorNuanceApplication.php', 'PhabricatorOAuth1AuthProvider' => 'applications/auth/provider/PhabricatorOAuth1AuthProvider.php', 'PhabricatorOAuth1SecretTemporaryTokenType' => 'applications/auth/provider/PhabricatorOAuth1SecretTemporaryTokenType.php', @@ -3908,6 +3911,7 @@ phutil_register_library_map(array( 'PhabricatorSearchDatasourceField' => 'applications/search/field/PhabricatorSearchDatasourceField.php', 'PhabricatorSearchDateControlField' => 'applications/search/field/PhabricatorSearchDateControlField.php', 'PhabricatorSearchDateField' => 'applications/search/field/PhabricatorSearchDateField.php', + 'PhabricatorSearchDefaultController' => 'applications/search/controller/PhabricatorSearchDefaultController.php', 'PhabricatorSearchDeleteController' => 'applications/search/controller/PhabricatorSearchDeleteController.php', 'PhabricatorSearchDocument' => 'applications/search/storage/document/PhabricatorSearchDocument.php', 'PhabricatorSearchDocumentField' => 'applications/search/storage/document/PhabricatorSearchDocumentField.php', @@ -4417,7 +4421,6 @@ phutil_register_library_map(array( 'PhamePostTransactionQuery' => 'applications/phame/query/PhamePostTransactionQuery.php', 'PhamePostTransactionType' => 'applications/phame/xaction/PhamePostTransactionType.php', 'PhamePostViewController' => 'applications/phame/controller/post/PhamePostViewController.php', - 'PhamePostViewsTransaction' => 'applications/phame/xaction/PhamePostViewsTransaction.php', 'PhamePostVisibilityTransaction' => 'applications/phame/xaction/PhamePostVisibilityTransaction.php', 'PhameSchemaSpec' => 'applications/phame/storage/PhameSchemaSpec.php', 'PhameSite' => 'applications/phame/site/PhameSite.php', @@ -4881,6 +4884,7 @@ phutil_register_library_map(array( 'TokenGiveConduitAPIMethod' => 'applications/tokens/conduit/TokenGiveConduitAPIMethod.php', 'TokenGivenConduitAPIMethod' => 'applications/tokens/conduit/TokenGivenConduitAPIMethod.php', 'TokenQueryConduitAPIMethod' => 'applications/tokens/conduit/TokenQueryConduitAPIMethod.php', + 'TransactionSearchConduitAPIMethod' => 'applications/transactions/conduit/TransactionSearchConduitAPIMethod.php', 'UserConduitAPIMethod' => 'applications/people/conduit/UserConduitAPIMethod.php', 'UserDisableConduitAPIMethod' => 'applications/people/conduit/UserDisableConduitAPIMethod.php', 'UserEnableConduitAPIMethod' => 'applications/people/conduit/UserEnableConduitAPIMethod.php', @@ -5543,6 +5547,7 @@ phutil_register_library_map(array( 'DifferentialRevisionHeraldField' => 'HeraldField', 'DifferentialRevisionHeraldFieldGroup' => 'HeraldFieldGroup', 'DifferentialRevisionIDCommitMessageField' => 'DifferentialCommitMessageField', + 'DifferentialRevisionInlineTransaction' => 'PhabricatorModularTransactionType', 'DifferentialRevisionInlinesController' => 'DifferentialController', 'DifferentialRevisionListController' => 'DifferentialController', 'DifferentialRevisionListView' => 'AphrontView', @@ -7952,8 +7957,6 @@ phutil_register_library_map(array( 'PhabricatorDebugController' => 'PhabricatorController', 'PhabricatorDefaultRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler', 'PhabricatorDefaultSyntaxStyle' => 'PhabricatorSyntaxStyle', - 'PhabricatorDesktopNotificationsSetting' => 'PhabricatorInternalSetting', - 'PhabricatorDesktopNotificationsSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorDestructibleCodex' => 'Phobject', 'PhabricatorDestructionEngine' => 'Phobject', 'PhabricatorDestructionEngineExtension' => 'Phobject', @@ -8554,6 +8557,11 @@ phutil_register_library_map(array( 'PhabricatorSearchDAO', 'PhabricatorPolicyInterface', ), + 'PhabricatorNamedQueryConfig' => array( + 'PhabricatorSearchDAO', + 'PhabricatorPolicyInterface', + ), + 'PhabricatorNamedQueryConfigQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorNamedQueryQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorNavigationRemarkupRule' => 'PhutilRemarkupRule', 'PhabricatorNeverTriggerClock' => 'PhabricatorTriggerClock', @@ -8577,6 +8585,8 @@ phutil_register_library_map(array( 'PhabricatorNotificationTestFeedStory' => 'PhabricatorFeedStory', 'PhabricatorNotificationUIExample' => 'PhabricatorUIExample', 'PhabricatorNotificationsApplication' => 'PhabricatorApplication', + 'PhabricatorNotificationsSetting' => 'PhabricatorInternalSetting', + 'PhabricatorNotificationsSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorNuanceApplication' => 'PhabricatorApplication', 'PhabricatorOAuth1AuthProvider' => 'PhabricatorOAuthAuthProvider', 'PhabricatorOAuth1SecretTemporaryTokenType' => 'PhabricatorAuthTemporaryTokenType', @@ -9448,6 +9458,7 @@ phutil_register_library_map(array( 'PhabricatorSearchDatasourceField' => 'PhabricatorSearchTokenizerField', 'PhabricatorSearchDateControlField' => 'PhabricatorSearchField', 'PhabricatorSearchDateField' => 'PhabricatorSearchField', + 'PhabricatorSearchDefaultController' => 'PhabricatorSearchBaseController', 'PhabricatorSearchDeleteController' => 'PhabricatorSearchBaseController', 'PhabricatorSearchDocument' => 'PhabricatorSearchDAO', 'PhabricatorSearchDocumentField' => 'PhabricatorSearchDAO', @@ -10042,7 +10053,6 @@ phutil_register_library_map(array( 'PhamePostTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhamePostTransactionType' => 'PhabricatorModularTransactionType', 'PhamePostViewController' => 'PhameLiveController', - 'PhamePostViewsTransaction' => 'PhamePostTransactionType', 'PhamePostVisibilityTransaction' => 'PhamePostTransactionType', 'PhameSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhameSite' => 'PhabricatorSite', @@ -10622,6 +10632,7 @@ phutil_register_library_map(array( 'TokenGiveConduitAPIMethod' => 'TokenConduitAPIMethod', 'TokenGivenConduitAPIMethod' => 'TokenConduitAPIMethod', 'TokenQueryConduitAPIMethod' => 'TokenConduitAPIMethod', + 'TransactionSearchConduitAPIMethod' => 'ConduitAPIMethod', 'UserConduitAPIMethod' => 'ConduitAPIMethod', 'UserDisableConduitAPIMethod' => 'UserConduitAPIMethod', 'UserEnableConduitAPIMethod' => 'UserConduitAPIMethod', diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index af246feeb0..e916728c86 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -107,20 +107,6 @@ final class DifferentialTransactionEditor return parent::getCustomTransactionNewValue($object, $xaction); } - protected function transactionHasEffect( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - $actor_phid = $this->getActingAsPHID(); - - switch ($xaction->getTransactionType()) { - case DifferentialTransaction::TYPE_INLINE: - return $xaction->hasComment(); - } - - return parent::transactionHasEffect($object, $xaction); - } - protected function applyCustomInternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { diff --git a/src/applications/differential/storage/DifferentialChangeset.php b/src/applications/differential/storage/DifferentialChangeset.php index be83c5e73b..9c718a8765 100644 --- a/src/applications/differential/storage/DifferentialChangeset.php +++ b/src/applications/differential/storage/DifferentialChangeset.php @@ -170,7 +170,7 @@ final class DifferentialChangeset extends DifferentialDAO } public function getAnchorName() { - return substr(md5($this->getFilename()), 0, 8); + return 'change-'.PhabricatorHash::digestForIndex($this->getFilename()); } public function getAbsoluteRepositoryPath( diff --git a/src/applications/differential/xaction/DifferentialRevisionInlineTransaction.php b/src/applications/differential/xaction/DifferentialRevisionInlineTransaction.php new file mode 100644 index 0000000000..35d5034033 --- /dev/null +++ b/src/applications/differential/xaction/DifferentialRevisionInlineTransaction.php @@ -0,0 +1,53 @@ +getViewer(); + + $changeset_ids = array(); + foreach ($xactions as $xaction) { + $changeset_ids[] = $xaction->getComment()->getChangesetID(); + } + + $changesets = id(new DifferentialChangesetQuery()) + ->setViewer($viewer) + ->withIDs($changeset_ids) + ->execute(); + + $changesets = mpull($changesets, null, 'getID'); + + return $changesets; + } + + public function getFieldValuesForConduit($object, $data) { + $comment = $object->getComment(); + + $changeset = $data[$comment->getChangesetID()]; + $diff = $changeset->getDiff(); + + return array( + 'diff' => array( + 'id' => (int)$diff->getID(), + 'phid' => $diff->getPHID(), + ), + 'path' => $changeset->getDisplayFilename(), + 'line' => (int)$comment->getLineNumber(), + 'length' => (int)($comment->getLineLength() + 1), + 'replyToCommentPHID' => $comment->getReplyToCommentPHID(), + ); + } + +} diff --git a/src/applications/differential/xaction/DifferentialRevisionStatusTransaction.php b/src/applications/differential/xaction/DifferentialRevisionStatusTransaction.php index c08eb9d187..615ce38bcf 100644 --- a/src/applications/differential/xaction/DifferentialRevisionStatusTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionStatusTransaction.php @@ -70,4 +70,15 @@ final class DifferentialRevisionStatusTransaction return DifferentialRevisionStatus::newForStatus($new); } + public function getTransactionTypeForConduit($xaction) { + return 'status'; + } + + public function getFieldValuesForConduit($object, $data) { + return array( + 'old' => $object->getOldValue(), + 'new' => $object->getNewValue(), + ); + } + } diff --git a/src/applications/differential/xaction/DifferentialRevisionTitleTransaction.php b/src/applications/differential/xaction/DifferentialRevisionTitleTransaction.php index 9b763c53ca..812464b26d 100644 --- a/src/applications/differential/xaction/DifferentialRevisionTitleTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionTitleTransaction.php @@ -55,4 +55,15 @@ final class DifferentialRevisionTitleTransaction return $errors; } + public function getTransactionTypeForConduit($xaction) { + return 'title'; + } + + public function getFieldValuesForConduit($object, $data) { + return array( + 'old' => $object->getOldValue(), + 'new' => $object->getNewValue(), + ); + } + } diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index 9ab7f194bf..b9d63b2f85 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -706,6 +706,7 @@ final class DiffusionBrowseController extends DiffusionController { $buttons[] = id(new PHUIButtonView()) + ->setTag('a') ->setText(pht('Last Change')) ->setColor(PHUIButtonView::GREY) ->setHref( @@ -1116,7 +1117,6 @@ final class DiffusionBrowseController extends DiffusionController { )); } - $skip_text = pht('Skip Past This Commit'); foreach ($display as $line_index => $line) { $row = array(); @@ -1132,12 +1132,14 @@ final class DiffusionBrowseController extends DiffusionController { $revision_link = null; $commit_link = null; $before_link = null; + $commit_date = null; - $style = 'background: '.$line['color'].';'; + $style = 'border-right: 2px solid '.$line['color'].';'; if ($identifier && !$line['duplicate']) { if (isset($commit_links[$identifier])) { - $commit_link = $commit_links[$identifier]; + $commit_link = $commit_links[$identifier]['link']; + $commit_date = $commit_links[$identifier]['date']; } if (isset($revision_map[$identifier])) { @@ -1148,6 +1150,10 @@ final class DiffusionBrowseController extends DiffusionController { } $skip_href = $line_href.'?before='.$identifier.'&view=blame'; + $skip_text = pht('Skip Past This Commit'); + $icon = id(new PHUIIconView()) + ->setIcon('fa-caret-square-o-left'); + $before_link = javelin_tag( 'a', array( @@ -1159,7 +1165,7 @@ final class DiffusionBrowseController extends DiffusionController { 'size' => 300, ), ), - "\xC2\xAB"); + $icon); } if ($show_blame) { @@ -1170,33 +1176,41 @@ final class DiffusionBrowseController extends DiffusionController { ), $before_link); - $object_links = array(); - $object_links[] = $commit_link; - if ($revision_link) { - $object_links[] = phutil_tag('span', array(), '/'); - $object_links[] = $revision_link; - } - $row[] = phutil_tag( 'th', array( 'class' => 'diffusion-rev-link', ), - $object_links); + $commit_link); + + if ($revision_map) { + $row[] = phutil_tag( + 'th', + array( + 'class' => 'diffusion-blame-revision', + ), + $revision_link); + } + + $row[] = phutil_tag( + 'th', + array( + 'class' => 'diffusion-blame-date', + ), + $commit_date); } $line_link = phutil_tag( 'a', array( 'href' => $line_href, - 'style' => $style, ), $line_number); $row[] = javelin_tag( 'th', array( - 'class' => 'diffusion-line-link', + 'class' => 'diffusion-line-link ', 'sigil' => 'phabricator-source-line', 'style' => $style, ), @@ -1510,33 +1524,6 @@ final class DiffusionBrowseController extends DiffusionController { return head($parents); } - private function renderRevisionTooltip( - DifferentialRevision $revision, - $handles) { - $viewer = $this->getRequest()->getUser(); - - $date = phabricator_date($revision->getDateModified(), $viewer); - $id = $revision->getID(); - $title = $revision->getTitle(); - $header = "D{$id} {$title}"; - - $author = $handles[$revision->getAuthorPHID()]->getName(); - - return "{$header}\n{$date} \xC2\xB7 {$author}"; - } - - private function renderCommitTooltip( - PhabricatorRepositoryCommit $commit, - $author) { - - $viewer = $this->getRequest()->getUser(); - - $date = phabricator_date($commit->getEpoch(), $viewer); - $summary = trim($commit->getSummary()); - - return "{$summary}\n{$date} \xC2\xB7 {$author}"; - } - protected function markupText($text) { $engine = PhabricatorMarkupEngine::newDiffusionMarkupEngine(); $engine->setConfig('viewer', $this->getRequest()->getUser()); @@ -1617,6 +1604,7 @@ final class DiffusionBrowseController extends DiffusionController { $head = null; if ($behind_head) { $head = id(new PHUIButtonView()) + ->setTag('a') ->setText(pht('Back to HEAD')) ->setHref($head_uri) ->setIcon('fa-home') @@ -1737,9 +1725,6 @@ final class DiffusionBrowseController extends DiffusionController { ->setViewer($viewer) ->withRepository($repository) ->withIdentifiers($identifiers) - // TODO: We only fetch this to improve author display behavior, but - // shouldn't really need to? - ->needCommitData(true) ->execute(); $commits = mpull($commits, null, 'getCommitIdentifier'); } else { @@ -1751,25 +1736,27 @@ final class DiffusionBrowseController extends DiffusionController { private function renderCommitLinks(array $commits, $handles) { $links = array(); + $viewer = $this->getViewer(); foreach ($commits as $identifier => $commit) { - $tooltip = $this->renderCommitTooltip( - $commit, - $commit->renderAuthorShortName($handles)); + $date = phabricator_date($commit->getEpoch(), $viewer); + $summary = trim($commit->getSummary()); - $commit_link = javelin_tag( + $commit_link = phutil_tag( 'a', array( 'href' => $commit->getURI(), - 'sigil' => 'has-tooltip', - 'meta' => array( - 'tip' => $tooltip, - 'align' => 'E', - 'size' => 600, - ), ), - $commit->getLocalName()); + $summary); - $links[$identifier] = $commit_link; + $commit_date = phutil_tag( + 'a', + array( + 'href' => $commit->getURI(), + ), + $date); + + $links[$identifier]['link'] = $commit_link; + $links[$identifier]['date'] = $commit_date; } return $links; @@ -1780,19 +1767,10 @@ final class DiffusionBrowseController extends DiffusionController { foreach ($revisions as $revision) { $revision_id = $revision->getID(); - - $tooltip = $this->renderRevisionTooltip($revision, $handles); - - $revision_link = javelin_tag( + $revision_link = phutil_tag( 'a', array( 'href' => '/'.$revision->getMonogram(), - 'sigil' => 'has-tooltip', - 'meta' => array( - 'tip' => $tooltip, - 'align' => 'E', - 'size' => 600, - ), ), $revision->getMonogram()); diff --git a/src/applications/maniphest/editor/ManiphestEditEngine.php b/src/applications/maniphest/editor/ManiphestEditEngine.php index 9f5e647ca9..b4ca86a442 100644 --- a/src/applications/maniphest/editor/ManiphestEditEngine.php +++ b/src/applications/maniphest/editor/ManiphestEditEngine.php @@ -251,6 +251,56 @@ EODOCS id(new PHUIRemarkupPreviewPanel()) ->setHeader(pht('Description Preview'))); + $parent_type = ManiphestTaskDependedOnByTaskEdgeType::EDGECONST; + $subtask_type = ManiphestTaskDependsOnTaskEdgeType::EDGECONST; + + $src_phid = $object->getPHID(); + if ($src_phid) { + $edge_query = id(new PhabricatorEdgeQuery()) + ->withSourcePHIDs(array($src_phid)) + ->withEdgeTypes( + array( + $parent_type, + $subtask_type, + )); + $edge_query->execute(); + + $parent_phids = $edge_query->getDestinationPHIDs( + array($src_phid), + array($parent_type)); + + $subtask_phids = $edge_query->getDestinationPHIDs( + array($src_phid), + array($subtask_type)); + } else { + $parent_phids = array(); + $subtask_phids = array(); + } + + $fields[] = id(new PhabricatorHandlesEditField()) + ->setKey('parents') + ->setLabel(pht('Parents')) + ->setDescription(pht('Parent tasks.')) + ->setConduitDescription(pht('Change the parents of this task.')) + ->setConduitTypeDescription(pht('List of parent task PHIDs.')) + ->setUseEdgeTransactions(true) + ->setIsConduitOnly(true) + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) + ->setMetadataValue('edge:type', $parent_type) + ->setValue($parent_phids); + + $fields[] = id(new PhabricatorHandlesEditField()) + ->setKey('subtasks') + ->setLabel(pht('Subtasks')) + ->setDescription(pht('Subtasks.')) + ->setConduitDescription(pht('Change the subtasks of this task.')) + ->setConduitTypeDescription(pht('List of subtask PHIDs.')) + ->setUseEdgeTransactions(true) + ->setIsConduitOnly(true) + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) + ->setMetadataValue('edge:type', $subtask_type) + ->setValue($parent_phids); + return $fields; } diff --git a/src/applications/maniphest/query/ManiphestTaskSearchEngine.php b/src/applications/maniphest/query/ManiphestTaskSearchEngine.php index 53efaf0bbc..150ec81def 100644 --- a/src/applications/maniphest/query/ManiphestTaskSearchEngine.php +++ b/src/applications/maniphest/query/ManiphestTaskSearchEngine.php @@ -236,8 +236,6 @@ final class ManiphestTaskSearchEngine $group = idx($this->getGroupValues(), $group); if ($group) { $query->setGroupBy($group); - } else { - $query->setGroupBy(head($this->getGroupValues())); } if ($map['ids']) { diff --git a/src/applications/notification/builder/PhabricatorNotificationBuilder.php b/src/applications/notification/builder/PhabricatorNotificationBuilder.php index 12e4b57bfb..5691755cc7 100644 --- a/src/applications/notification/builder/PhabricatorNotificationBuilder.php +++ b/src/applications/notification/builder/PhabricatorNotificationBuilder.php @@ -145,13 +145,16 @@ final class PhabricatorNotificationBuilder extends Phobject { $dict = array(); $viewer = $this->user; - $desktop_key = PhabricatorDesktopNotificationsSetting::SETTINGKEY; - $desktop_enabled = $viewer->getUserSetting($desktop_key); + $key = PhabricatorNotificationsSetting::SETTINGKEY; + $setting = $viewer->getUserSetting($key); + $desktop_ready = PhabricatorNotificationsSetting::desktopReady($setting); + $web_ready = PhabricatorNotificationsSetting::webReady($setting); foreach ($stories as $story) { if ($story instanceof PhabricatorApplicationTransactionFeedStory) { $dict[] = array( - 'desktopReady' => $desktop_enabled, + 'desktopReady' => $desktop_ready, + 'webReady' => $web_ready, 'title' => $story->renderText(), 'body' => $story->renderTextBody(), 'href' => $story->getURI(), @@ -159,7 +162,8 @@ final class PhabricatorNotificationBuilder extends Phobject { ); } else if ($story instanceof PhabricatorNotificationTestFeedStory) { $dict[] = array( - 'desktopReady' => $desktop_enabled, + 'desktopReady' => $desktop_ready, + 'webReady' => $web_ready, 'title' => pht('Test Notification'), 'body' => $story->renderText(), 'href' => null, @@ -168,6 +172,7 @@ final class PhabricatorNotificationBuilder extends Phobject { } else { $dict[] = array( 'desktopReady' => false, + 'webReady' => false, 'title' => null, 'body' => null, 'href' => null, diff --git a/src/applications/notification/controller/PhabricatorNotificationIndividualController.php b/src/applications/notification/controller/PhabricatorNotificationIndividualController.php index af3e8bcff0..8ef6286225 100644 --- a/src/applications/notification/controller/PhabricatorNotificationIndividualController.php +++ b/src/applications/notification/controller/PhabricatorNotificationIndividualController.php @@ -42,6 +42,7 @@ final class PhabricatorNotificationIndividualController 'pertinent' => true, 'primaryObjectPHID' => $story->getPrimaryObjectPHID(), 'desktopReady' => $data['desktopReady'], + 'webReady' => $data['webReady'], 'href' => $data['href'], 'icon' => $data['icon'], 'title' => $data['title'], diff --git a/src/applications/phame/controller/post/PhamePostViewController.php b/src/applications/phame/controller/post/PhamePostViewController.php index 7dbaddc17a..63adedb7ae 100644 --- a/src/applications/phame/controller/post/PhamePostViewController.php +++ b/src/applications/phame/controller/post/PhamePostViewController.php @@ -18,25 +18,6 @@ final class PhamePostViewController $is_live = $this->getIsLive(); $is_external = $this->getIsExternal(); - // Register a blog "view" count - // - if (!$post->isDraft() && !$post->isArchived()) { - $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); - $xactions = array(); - $xactions[] = id(new PhamePostTransaction()) - ->setTransactionType(PhamePostViewsTransaction::TRANSACTIONTYPE) - ->setNewValue(null); - - $editor = id(new PhamePostEditor()) - ->setActor($viewer) - ->setContentSourceFromRequest($request) - ->setContinueOnMissingFields(true) - ->setContinueOnNoEffect(true); - - $editor->applyTransactions($post, $xactions); - unset($unguarded); - } - $header = id(new PHUIHeaderView()) ->addClass('phame-header-bar') ->setUser($viewer); @@ -170,11 +151,6 @@ final class PhamePostViewController ->setUser($viewer) ->setObject($post); - $views = id(new PhutilNumber($post->getViews())); - $properties->addProperty( - pht('Views'), - pht('%s', $views)); - $is_live = $this->getIsLive(); $is_external = $this->getIsExternal(); $next_view = new PhameNextPostView(); diff --git a/src/applications/phame/editor/PhamePostEditor.php b/src/applications/phame/editor/PhamePostEditor.php index 845538dc12..488d7a4938 100644 --- a/src/applications/phame/editor/PhamePostEditor.php +++ b/src/applications/phame/editor/PhamePostEditor.php @@ -41,12 +41,6 @@ final class PhamePostEditor if ($object->isDraft() || $object->isArchived()) { return false; } - foreach ($xactions as $xaction) { - switch ($xaction->getTransactionType()) { - case PhamePostViewsTransaction::TRANSACTIONTYPE: - return false; - } - } return true; } diff --git a/src/applications/phame/storage/PhamePost.php b/src/applications/phame/storage/PhamePost.php index c3ec4d4608..a9525e0be7 100644 --- a/src/applications/phame/storage/PhamePost.php +++ b/src/applications/phame/storage/PhamePost.php @@ -22,7 +22,6 @@ final class PhamePost extends PhameDAO protected $phameTitle; protected $body; protected $visibility; - protected $views; protected $configData; protected $datePublished; protected $blogPHID; @@ -41,8 +40,7 @@ final class PhamePost extends PhameDAO ->setBlogPHID($blog->getPHID()) ->attachBlog($blog) ->setDatePublished(PhabricatorTime::getNow()) - ->setVisibility(PhameConstants::VISIBILITY_PUBLISHED) - ->setViews(0); + ->setVisibility(PhameConstants::VISIBILITY_PUBLISHED); return $post; } @@ -130,7 +128,6 @@ final class PhamePost extends PhameDAO 'subtitle' => 'text64', 'phameTitle' => 'sort64?', 'visibility' => 'uint32', - 'views' => 'uint32', 'mailKey' => 'bytes20', 'headerImagePHID' => 'phid?', diff --git a/src/applications/phame/xaction/PhamePostViewsTransaction.php b/src/applications/phame/xaction/PhamePostViewsTransaction.php deleted file mode 100644 index 2260a1d752..0000000000 --- a/src/applications/phame/xaction/PhamePostViewsTransaction.php +++ /dev/null @@ -1,18 +0,0 @@ -getViews(); - } - - public function applyInternalEffects($object, $value) { - $views = $object->getViews(); - $views++; - $object->setViews($views); - } - -} diff --git a/src/applications/search/application/PhabricatorSearchApplication.php b/src/applications/search/application/PhabricatorSearchApplication.php index a2a28c7665..3cf5923b9c 100644 --- a/src/applications/search/application/PhabricatorSearchApplication.php +++ b/src/applications/search/application/PhabricatorSearchApplication.php @@ -33,9 +33,18 @@ final class PhabricatorSearchApplication extends PhabricatorApplication { 'index/(?P[^/]+)/' => 'PhabricatorSearchIndexController', 'hovercard/' => 'PhabricatorSearchHovercardController', - 'edit/(?P[^/]+)/' => 'PhabricatorSearchEditController', - 'delete/(?P[^/]+)/(?P[^/]+)/' - => 'PhabricatorSearchDeleteController', + 'edit/' => array( + 'key/(?P[^/]+)/' => 'PhabricatorSearchEditController', + 'id/(?P[^/]+)/' => 'PhabricatorSearchEditController', + ), + 'default/(?P[^/]+)/(?P[^/]+)/' + => 'PhabricatorSearchDefaultController', + 'delete/' => array( + 'key/(?P[^/]+)/(?P[^/]+)/' + => 'PhabricatorSearchDeleteController', + 'id/(?P[^/]+)/' + => 'PhabricatorSearchDeleteController', + ), 'order/(?P[^/]+)/' => 'PhabricatorSearchOrderController', 'rel/(?P[^/]+)/(?P[^/]+)/' => 'PhabricatorSearchRelationshipController', diff --git a/src/applications/search/controller/PhabricatorApplicationSearchController.php b/src/applications/search/controller/PhabricatorApplicationSearchController.php index dbf964f9e4..f781e20380 100644 --- a/src/applications/search/controller/PhabricatorApplicationSearchController.php +++ b/src/applications/search/controller/PhabricatorApplicationSearchController.php @@ -127,7 +127,7 @@ final class PhabricatorApplicationSearchController if (!$found_query_data) { // Otherwise, there's no query data so just run the user's default // query for this application. - $query_key = head_key($engine->loadEnabledNamedQueries()); + $query_key = $engine->getDefaultQueryKey(); } } @@ -174,7 +174,7 @@ final class PhabricatorApplicationSearchController if ($run_query && !$named_query && $user->isLoggedIn()) { $save_button = id(new PHUIButtonView()) ->setTag('a') - ->setHref('/search/edit/'.$saved_query->getQueryKey().'/') + ->setHref('/search/edit/key/'.$saved_query->getQueryKey().'/') ->setText(pht('Save Query')) ->setIcon('fa-floppy-o'); $submit->addButton($save_button); @@ -377,7 +377,7 @@ final class PhabricatorApplicationSearchController private function processEditRequest() { $parent = $this->getDelegatingController(); $request = $this->getRequest(); - $user = $request->getUser(); + $viewer = $request->getUser(); $engine = $this->getSearchEngine(); $nav = $this->getNavigation(); @@ -387,64 +387,41 @@ final class PhabricatorApplicationSearchController $named_queries = $engine->loadAllNamedQueries(); - $list_id = celerity_generate_unique_node_id(); + $can_global = $viewer->getIsAdmin(); - $list = new PHUIObjectItemListView(); - $list->setUser($user); - $list->setID($list_id); - - Javelin::initBehavior( - 'search-reorder-queries', - array( - 'listID' => $list_id, - 'orderURI' => '/search/order/'.get_class($engine).'/', - )); + $groups = array( + 'personal' => array( + 'name' => pht('Personal Saved Queries'), + 'items' => array(), + 'edit' => true, + ), + 'global' => array( + 'name' => pht('Global Saved Queries'), + 'items' => array(), + 'edit' => $can_global, + ), + ); foreach ($named_queries as $named_query) { - $class = get_class($engine); - $key = $named_query->getQueryKey(); - - $item = id(new PHUIObjectItemView()) - ->setHeader($named_query->getQueryName()) - ->setHref($engine->getQueryResultsPageURI($key)); - - if ($named_query->getIsBuiltin() && $named_query->getIsDisabled()) { - $icon = 'fa-plus'; + if ($named_query->isGlobal()) { + $group = 'global'; } else { - $icon = 'fa-times'; + $group = 'personal'; } - $item->addAction( - id(new PHUIListItemView()) - ->setIcon($icon) - ->setHref('/search/delete/'.$key.'/'.$class.'/') - ->setWorkflow(true)); - - if ($named_query->getIsBuiltin()) { - if ($named_query->getIsDisabled()) { - $item->addIcon('fa-times lightgreytext', pht('Disabled')); - $item->setDisabled(true); - } else { - $item->addIcon('fa-lock lightgreytext', pht('Builtin')); - } - } else { - $item->addAction( - id(new PHUIListItemView()) - ->setIcon('fa-pencil') - ->setHref('/search/edit/'.$key.'/')); - } - - $item->setGrippable(true); - $item->addSigil('named-query'); - $item->setMetadata( - array( - 'queryKey' => $named_query->getQueryKey(), - )); - - $list->addItem($item); + $groups[$group]['items'][] = $named_query; } - $list->setNoDataString(pht('No saved queries.')); + $default_key = $engine->getDefaultQueryKey(); + + $lists = array(); + foreach ($groups as $group) { + $lists[] = $this->newQueryListView( + $group['name'], + $group['items'], + $default_key, + $group['edit']); + } $crumbs = $parent ->buildApplicationCrumbs() @@ -457,20 +434,144 @@ final class PhabricatorApplicationSearchController ->setHeader(pht('Saved Queries')) ->setProfileHeader(true); - $box = id(new PHUIObjectBoxView()) + $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->setObjectList($list) - ->addClass('application-search-results'); - - $nav->addClass('application-search-view'); - require_celerity_resource('application-search-view-css'); + ->setFooter($lists); return $this->newPage() ->setApplicationMenu($this->buildApplicationMenu()) ->setTitle(pht('Saved Queries')) ->setCrumbs($crumbs) ->setNavigation($nav) - ->appendChild($box); + ->appendChild($view); + } + + private function newQueryListView( + $list_name, + array $named_queries, + $default_key, + $can_edit) { + + $engine = $this->getSearchEngine(); + $viewer = $this->getViewer(); + + $list = id(new PHUIObjectItemListView()) + ->setViewer($viewer); + + if ($can_edit) { + $list_id = celerity_generate_unique_node_id(); + $list->setID($list_id); + + Javelin::initBehavior( + 'search-reorder-queries', + array( + 'listID' => $list_id, + 'orderURI' => '/search/order/'.get_class($engine).'/', + )); + } + + foreach ($named_queries as $named_query) { + $class = get_class($engine); + $key = $named_query->getQueryKey(); + + $item = id(new PHUIObjectItemView()) + ->setHeader($named_query->getQueryName()) + ->setHref($engine->getQueryResultsPageURI($key)); + + if ($named_query->getIsDisabled()) { + if ($can_edit) { + $item->setDisabled(true); + } else { + // If an item is disabled and you don't have permission to edit it, + // just skip it. + continue; + } + } + + if ($can_edit) { + if ($named_query->getIsBuiltin() && $named_query->getIsDisabled()) { + $icon = 'fa-plus'; + $disable_name = pht('Enable'); + } else { + $icon = 'fa-times'; + if ($named_query->getIsBuiltin()) { + $disable_name = pht('Disable'); + } else { + $disable_name = pht('Delete'); + } + } + + if ($named_query->getID()) { + $disable_href = '/search/delete/id/'.$named_query->getID().'/'; + } else { + $disable_href = '/search/delete/key/'.$key.'/'.$class.'/'; + } + + $item->addAction( + id(new PHUIListItemView()) + ->setIcon($icon) + ->setHref($disable_href) + ->setRenderNameAsTooltip(true) + ->setName($disable_name) + ->setWorkflow(true)); + } + + $default_disabled = $named_query->getIsDisabled(); + $default_icon = 'fa-thumb-tack'; + + if ($default_key === $key) { + $default_color = 'green'; + } else { + $default_color = null; + } + + $item->addAction( + id(new PHUIListItemView()) + ->setIcon("{$default_icon} {$default_color}") + ->setHref('/search/default/'.$key.'/'.$class.'/') + ->setRenderNameAsTooltip(true) + ->setName(pht('Make Default')) + ->setWorkflow(true) + ->setDisabled($default_disabled)); + + if ($can_edit) { + if ($named_query->getIsBuiltin()) { + $edit_icon = 'fa-lock lightgreytext'; + $edit_disabled = true; + $edit_name = pht('Builtin'); + $edit_href = null; + } else { + $edit_icon = 'fa-pencil'; + $edit_disabled = false; + $edit_name = pht('Edit'); + $edit_href = '/search/edit/id/'.$named_query->getID().'/'; + } + + $item->addAction( + id(new PHUIListItemView()) + ->setIcon($edit_icon) + ->setHref($edit_href) + ->setRenderNameAsTooltip(true) + ->setName($edit_name) + ->setDisabled($edit_disabled)); + } + + $item->setGrippable($can_edit); + $item->addSigil('named-query'); + $item->setMetadata( + array( + 'queryKey' => $named_query->getQueryKey(), + )); + + $list->addItem($item); + } + + $list->setNoDataString(pht('No saved queries.')); + + return id(new PHUIObjectBoxView()) + ->setHeaderText($list_name) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setObjectList($list); } public function buildApplicationMenu() { @@ -610,7 +711,7 @@ final class PhabricatorApplicationSearchController $engine_class = get_class($engine); $query_key = $this->getQueryKey(); if (!$query_key) { - $query_key = head_key($engine->loadEnabledNamedQueries()); + $query_key = $engine->getDefaultQueryKey(); } $can_use = $engine->canUseInPanelContext(); diff --git a/src/applications/search/controller/PhabricatorSearchDefaultController.php b/src/applications/search/controller/PhabricatorSearchDefaultController.php new file mode 100644 index 0000000000..a4f68e503a --- /dev/null +++ b/src/applications/search/controller/PhabricatorSearchDefaultController.php @@ -0,0 +1,85 @@ +getViewer(); + $engine_class = $request->getURIData('engine'); + + $base_class = 'PhabricatorApplicationSearchEngine'; + if (!is_subclass_of($engine_class, $base_class)) { + return new Aphront400Response(); + } + + $engine = newv($engine_class, array()); + $engine->setViewer($viewer); + + $key = $request->getURIData('queryKey'); + + $named_query = id(new PhabricatorNamedQueryQuery()) + ->setViewer($viewer) + ->withEngineClassNames(array($engine_class)) + ->withQueryKeys(array($key)) + ->withUserPHIDs( + array( + $viewer->getPHID(), + PhabricatorNamedQuery::SCOPE_GLOBAL, + )) + ->executeOne(); + + if (!$named_query && $engine->isBuiltinQuery($key)) { + $named_query = $engine->getBuiltinQuery($key); + } + + if (!$named_query) { + return new Aphront404Response(); + } + + $return_uri = $engine->getQueryManagementURI(); + + $builtin = null; + if ($engine->isBuiltinQuery($key)) { + $builtin = $engine->getBuiltinQuery($key); + } + + if ($request->isFormPost()) { + $config = id(new PhabricatorNamedQueryConfigQuery()) + ->setViewer($viewer) + ->withEngineClassNames(array($engine_class)) + ->withScopePHIDs(array($viewer->getPHID())) + ->executeOne(); + if (!$config) { + $config = PhabricatorNamedQueryConfig::initializeNewQueryConfig() + ->setEngineClassName($engine_class) + ->setScopePHID($viewer->getPHID()); + } + + $config->setConfigProperty( + PhabricatorNamedQueryConfig::PROPERTY_PINNED, + $key); + + $config->save(); + + return id(new AphrontRedirectResponse())->setURI($return_uri); + } + + if ($named_query->getIsBuiltin()) { + $query_name = $builtin->getQueryName(); + } else { + $query_name = $named_query->getQueryName(); + } + + $title = pht('Set Default Query'); + $body = pht( + 'This query will become your default query in the current application.'); + $button = pht('Set Default Query'); + + return $this->newDialog() + ->setTitle($title) + ->appendChild($body) + ->addCancelButton($return_uri) + ->addSubmitButton($button); + } + +} diff --git a/src/applications/search/controller/PhabricatorSearchDeleteController.php b/src/applications/search/controller/PhabricatorSearchDeleteController.php index f72c283519..9cbabd3a2f 100644 --- a/src/applications/search/controller/PhabricatorSearchDeleteController.php +++ b/src/applications/search/controller/PhabricatorSearchDeleteController.php @@ -5,32 +5,45 @@ final class PhabricatorSearchDeleteController public function handleRequest(AphrontRequest $request) { $viewer = $this->getViewer(); - $key = $request->getURIData('queryKey'); - $engine_class = $request->getURIData('engine'); - $base_class = 'PhabricatorApplicationSearchEngine'; - if (!is_subclass_of($engine_class, $base_class)) { - return new Aphront400Response(); - } + $id = $request->getURIData('id'); + if ($id) { + $named_query = id(new PhabricatorNamedQueryQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$named_query) { + return new Aphront404Response(); + } - $engine = newv($engine_class, array()); - $engine->setViewer($viewer); + $engine = newv($named_query->getEngineClassName(), array()); + $engine->setViewer($viewer); - $named_query = id(new PhabricatorNamedQueryQuery()) - ->setViewer($viewer) - ->withEngineClassNames(array($engine_class)) - ->withQueryKeys(array($key)) - ->withUserPHIDs(array($viewer->getPHID())) - ->executeOne(); + $key = $named_query->getQueryKey(); + } else { + $key = $request->getURIData('queryKey'); + $engine_class = $request->getURIData('engine'); + + $base_class = 'PhabricatorApplicationSearchEngine'; + if (!is_subclass_of($engine_class, $base_class)) { + return new Aphront400Response(); + } + + $engine = newv($engine_class, array()); + $engine->setViewer($viewer); + + if (!$engine->isBuiltinQuery($key)) { + return new Aphront404Response(); + } - if (!$named_query && $engine->isBuiltinQuery($key)) { $named_query = $engine->getBuiltinQuery($key); } - if (!$named_query) { - return new Aphront404Response(); - } - $builtin = null; if ($engine->isBuiltinQuery($key)) { $builtin = $engine->getBuiltinQuery($key); diff --git a/src/applications/search/controller/PhabricatorSearchEditController.php b/src/applications/search/controller/PhabricatorSearchEditController.php index c7f6c860e9..a526091503 100644 --- a/src/applications/search/controller/PhabricatorSearchEditController.php +++ b/src/applications/search/controller/PhabricatorSearchEditController.php @@ -6,11 +6,31 @@ final class PhabricatorSearchEditController public function handleRequest(AphrontRequest $request) { $viewer = $this->getViewer(); + $id = $request->getURIData('id'); + if ($id) { + $named_query = id(new PhabricatorNamedQueryQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$named_query) { + return new Aphront404Response(); + } + + $query_key = $named_query->getQueryKey(); + } else { + $query_key = $request->getURIData('queryKey'); + $named_query = null; + } + $saved_query = id(new PhabricatorSavedQueryQuery()) ->setViewer($viewer) - ->withQueryKeys(array($request->getURIData('queryKey'))) + ->withQueryKeys(array($query_key)) ->executeOne(); - if (!$saved_query) { return new Aphront404Response(); } @@ -20,11 +40,6 @@ final class PhabricatorSearchEditController $complete_uri = $engine->getQueryManagementURI(); $cancel_uri = $complete_uri; - $named_query = id(new PhabricatorNamedQueryQuery()) - ->setViewer($viewer) - ->withQueryKeys(array($saved_query->getQueryKey())) - ->withUserPHIDs(array($viewer->getPHID())) - ->executeOne(); if (!$named_query) { $named_query = id(new PhabricatorNamedQuery()) ->setUserPHID($viewer->getPHID()) @@ -36,12 +51,27 @@ final class PhabricatorSearchEditController // management interface. $cancel_uri = $engine->getQueryResultsPageURI( $saved_query->getQueryKey()); + + $is_new = true; + } else { + $is_new = false; } + $can_global = ($viewer->getIsAdmin() && $is_new); + + $v_global = false; + $e_name = true; $errors = array(); if ($request->isFormPost()) { + if ($can_global) { + $v_global = $request->getBool('global'); + if ($v_global) { + $named_query->setUserPHID(PhabricatorNamedQuery::SCOPE_GLOBAL); + } + } + $named_query->setQueryName($request->getStr('name')); if (!strlen($named_query->getQueryName())) { $e_name = pht('Required'); @@ -51,6 +81,7 @@ final class PhabricatorSearchEditController } if (!$errors) { + $named_query->save(); return id(new AphrontRedirectResponse())->setURI($complete_uri); } @@ -66,6 +97,18 @@ final class PhabricatorSearchEditController ->setValue($named_query->getQueryName()) ->setError($e_name)); + if ($can_global) { + $form->appendChild( + id(new AphrontFormCheckboxControl()) + ->addCheckbox( + 'global', + '1', + pht( + 'Save this query as a global query, making it visible to '. + 'all users.'), + $v_global)); + } + $form->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Save Query')) diff --git a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php index 5b2f7827f9..6122e66e23 100644 --- a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php +++ b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php @@ -474,8 +474,12 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { if ($this->namedQueries === null) { $named_queries = id(new PhabricatorNamedQueryQuery()) ->setViewer($viewer) - ->withUserPHIDs(array($viewer->getPHID())) ->withEngineClassNames(array(get_class($this))) + ->withUserPHIDs( + array( + $viewer->getPHID(), + PhabricatorNamedQuery::SCOPE_GLOBAL, + )) ->execute(); $named_queries = mpull($named_queries, null, 'getQueryKey'); @@ -494,7 +498,7 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { unset($builtin[$key]); } - $named_queries = msort($named_queries, 'getSortKey'); + $named_queries = msortv($named_queries, 'getNamedQuerySortVector'); $this->namedQueries = $named_queries; } @@ -511,6 +515,34 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { return $named_queries; } + public function getDefaultQueryKey() { + $viewer = $this->requireViewer(); + + $configs = id(new PhabricatorNamedQueryConfigQuery()) + ->setViewer($viewer) + ->withEngineClassNames(array(get_class($this))) + ->withScopePHIDs( + array( + $viewer->getPHID(), + PhabricatorNamedQueryConfig::SCOPE_GLOBAL, + )) + ->execute(); + $configs = msortv($configs, 'getStrengthSortVector'); + + $key_pinned = PhabricatorNamedQueryConfig::PROPERTY_PINNED; + $map = $this->loadEnabledNamedQueries(); + foreach ($configs as $config) { + $pinned = $config->getConfigProperty($key_pinned); + if (!isset($map[$pinned])) { + continue; + } + + return $pinned; + } + + return head_key($map); + } + protected function setQueryProjects( PhabricatorCursorPagedPolicyAwareQuery $query, PhabricatorSavedQuery $saved) { @@ -603,7 +635,7 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { $sequence = 0; foreach ($names as $key => $name) { $queries[$key] = id(new PhabricatorNamedQuery()) - ->setUserPHID($this->requireViewer()->getPHID()) + ->setUserPHID(PhabricatorNamedQuery::SCOPE_GLOBAL) ->setEngineClassName(get_class($this)) ->setQueryName($name) ->setQueryKey($key) diff --git a/src/applications/search/engine/PhabricatorProfileMenuEngine.php b/src/applications/search/engine/PhabricatorProfileMenuEngine.php index 7b1e213d20..90f056ac4f 100644 --- a/src/applications/search/engine/PhabricatorProfileMenuEngine.php +++ b/src/applications/search/engine/PhabricatorProfileMenuEngine.php @@ -465,7 +465,7 @@ abstract class PhabricatorProfileMenuEngine extends Phobject { $default = null; $first = null; foreach ($items as $item) { - if (!$item->canMakeDefault()) { + if (!$item->canMakeDefault() || $item->isDisabled()) { continue; } diff --git a/src/applications/search/query/PhabricatorNamedQueryConfigQuery.php b/src/applications/search/query/PhabricatorNamedQueryConfigQuery.php new file mode 100644 index 0000000000..6c4fad31a3 --- /dev/null +++ b/src/applications/search/query/PhabricatorNamedQueryConfigQuery.php @@ -0,0 +1,64 @@ +ids = $ids; + return $this; + } + + public function withScopePHIDs(array $scope_phids) { + $this->scopePHIDs = $scope_phids; + return $this; + } + + public function withEngineClassNames(array $engine_class_names) { + $this->engineClassNames = $engine_class_names; + return $this; + } + + public function newResultObject() { + return new PhabricatorNamedQueryConfig(); + } + + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); + } + + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); + + if ($this->ids !== null) { + $where[] = qsprintf( + $conn, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->engineClassNames !== null) { + $where[] = qsprintf( + $conn, + 'engineClassName IN (%Ls)', + $this->engineClassNames); + } + + if ($this->scopePHIDs !== null) { + $where[] = qsprintf( + $conn, + 'scopePHID IN (%Ls)', + $this->scopePHIDs); + } + + return $where; + } + + public function getQueryApplicationClass() { + return 'PhabricatorSearchApplication'; + } + +} diff --git a/src/applications/search/storage/PhabricatorNamedQuery.php b/src/applications/search/storage/PhabricatorNamedQuery.php index ac34a4fa32..44d7a403b1 100644 --- a/src/applications/search/storage/PhabricatorNamedQuery.php +++ b/src/applications/search/storage/PhabricatorNamedQuery.php @@ -12,6 +12,8 @@ final class PhabricatorNamedQuery extends PhabricatorSearchDAO protected $isDisabled = 0; protected $sequence = 0; + const SCOPE_GLOBAL = 'scope.global'; + protected function getConfiguration() { return array( self::CONFIG_COLUMN_SCHEMA => array( @@ -31,8 +33,29 @@ final class PhabricatorNamedQuery extends PhabricatorSearchDAO ) + parent::getConfiguration(); } - public function getSortKey() { - return sprintf('~%010d%010d', $this->sequence, $this->getID()); + public function isGlobal() { + if ($this->getIsBuiltin()) { + return true; + } + + if ($this->getUserPHID() === self::SCOPE_GLOBAL) { + return true; + } + + return false; + } + + public function getNamedQuerySortVector() { + if (!$this->isGlobal()) { + $phase = 0; + } else { + $phase = 1; + } + + return id(new PhutilSortVector()) + ->addInt($phase) + ->addInt($this->sequence) + ->addInt($this->getID()); } /* -( PhabricatorPolicyInterface )----------------------------------------- */ @@ -41,6 +64,7 @@ final class PhabricatorNamedQuery extends PhabricatorSearchDAO public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, ); } @@ -49,9 +73,19 @@ final class PhabricatorNamedQuery extends PhabricatorSearchDAO } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { - if ($viewer->getPHID() == $this->userPHID) { + if ($viewer->getPHID() == $this->getUserPHID()) { return true; } + + if ($this->isGlobal()) { + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + return true; + case PhabricatorPolicyCapability::CAN_EDIT: + return $viewer->getIsAdmin(); + } + } + return false; } diff --git a/src/applications/search/storage/PhabricatorNamedQueryConfig.php b/src/applications/search/storage/PhabricatorNamedQueryConfig.php new file mode 100644 index 0000000000..d5cdfe88d0 --- /dev/null +++ b/src/applications/search/storage/PhabricatorNamedQueryConfig.php @@ -0,0 +1,92 @@ + array( + 'properties' => self::SERIALIZATION_JSON, + ), + self::CONFIG_COLUMN_SCHEMA => array( + 'engineClassName' => 'text128', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_scope' => array( + 'columns' => array('engineClassName', 'scopePHID'), + 'unique' => true, + ), + ), + ) + parent::getConfiguration(); + } + + public static function initializeNewQueryConfig() { + return new self(); + } + + public function isGlobal() { + return ($this->getScopePHID() == self::SCOPE_GLOBAL); + } + + public function getConfigProperty($key, $default = null) { + return idx($this->properties, $key, $default); + } + + public function setConfigProperty($key, $value) { + $this->properties[$key] = $value; + return $this; + } + + public function getStrengthSortVector() { + // Apply personal preferences before global preferences. + if (!$this->isGlobal()) { + $phase = 0; + } else { + $phase = 1; + } + + return id(new PhutilSortVector()) + ->addInt($phase) + ->addInt($this->getID()); + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + ); + } + + public function getPolicy($capability) { + return PhabricatorPolicies::POLICY_NOONE; + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + if ($this->isGlobal()) { + return true; + } + + if ($viewer->getPHID() == $this->getScopePHID()) { + return true; + } + + return false; + } + + public function describeAutomaticCapability($capability) { + return null; + } + +} diff --git a/src/applications/settings/panel/PhabricatorDesktopNotificationsSettingsPanel.php b/src/applications/settings/panel/PhabricatorNotificationsSettingsPanel.php similarity index 83% rename from src/applications/settings/panel/PhabricatorDesktopNotificationsSettingsPanel.php rename to src/applications/settings/panel/PhabricatorNotificationsSettingsPanel.php index f94de959cd..d61ece6da5 100644 --- a/src/applications/settings/panel/PhabricatorDesktopNotificationsSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorNotificationsSettingsPanel.php @@ -1,6 +1,6 @@ getViewer(); $preferences = $this->getPreferences(); - $notifications_key = PhabricatorDesktopNotificationsSetting::SETTINGKEY; + $notifications_key = PhabricatorNotificationsSetting::SETTINGKEY; $notifications_value = $preferences->getSettingValue($notifications_key); if ($request->isFormPost()) { @@ -43,7 +43,7 @@ final class PhabricatorDesktopNotificationsSettingsPanel ->setURI($this->getPanelURI('?saved=true')); } - $title = pht('Desktop Notifications'); + $title = pht('Notifications'); $control_id = celerity_generate_unique_node_id(); $status_id = celerity_generate_unique_node_id(); $browser_status_id = celerity_generate_unique_node_id(); @@ -97,19 +97,31 @@ final class PhabricatorDesktopNotificationsSettingsPanel 'id' => $message_id, )); + $saved_box = null; + if ($request->getBool('saved')) { + $saved_box = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) + ->appendChild(pht('Changes saved.')); + } + $status_box = id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) ->setID($status_id) ->setIsHidden(true) ->appendChild($message_container); + $status_box = id(new PHUIBoxView()) + ->addClass('mll mlr') + ->appendChild($status_box); + $control_config = array( 'controlID' => $control_id, 'statusID' => $status_id, 'messageID' => $message_id, 'browserStatusID' => $browser_status_id, 'defaultMode' => 0, - 'desktopMode' => 1, + 'desktop' => 1, + 'desktopOnly' => 2, 'cancelAsk' => $cancel_ask, 'grantedAsk' => $accept_ask, 'deniedAsk' => $reject_ask, @@ -127,16 +139,12 @@ final class PhabricatorDesktopNotificationsSettingsPanel ->setControlID($control_id) ->setName($notifications_key) ->setValue($notifications_value) - ->setOptions( - array( - 1 => pht('Send Desktop Notifications Too'), - 0 => pht('Send Application Notifications Only'), - )) + ->setOptions(PhabricatorNotificationsSetting::getOptionsMap()) ->setCaption( pht( - 'Should Phabricator send desktop notifications? These are sent '. - 'in addition to the notifications within the Phabricator '. - 'application.')) + 'Phabricator can send real-time notifications to your web browser '. + 'or to your desktop. Select where you\'d want to receive these '. + 'real-time updates.')) ->initBehavior( 'desktop-notifications-control', $control_config)) @@ -154,12 +162,14 @@ final class PhabricatorDesktopNotificationsSettingsPanel $form_box = id(new PHUIObjectBoxView()) ->setHeader( id(new PHUIHeaderView()) - ->setHeader(pht('Desktop Notifications')) + ->setHeader(pht('Notifications')) ->addActionLink($test_button)) - ->setForm($form) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setInfoView($status_box) - ->setFormSaved($request->getBool('saved')); + ->appendChild(array( + $saved_box, + $status_box, + $form, + )); $browser_status_box = id(new PHUIInfoView()) ->setID($browser_status_id) diff --git a/src/applications/settings/setting/PhabricatorDesktopNotificationsSetting.php b/src/applications/settings/setting/PhabricatorDesktopNotificationsSetting.php deleted file mode 100644 index f590d37325..0000000000 --- a/src/applications/settings/setting/PhabricatorDesktopNotificationsSetting.php +++ /dev/null @@ -1,12 +0,0 @@ - pht('Web Only'), + self::WEB_AND_DESKTOP => pht('Web and Desktop'), + self::DESKTOP_ONLY => pht('Desktop Only'), + self::NONE => pht('No Notifications'), + ); + } + + public static function desktopReady($option) { + switch ($option) { + case self::WEB_AND_DESKTOP: + case self::DESKTOP_ONLY: + return true; + } + return false; + } + + public static function webReady($option) { + switch ($option) { + case self::WEB_AND_DESKTOP: + case self::WEB_ONLY: + return true; + } + return false; + } + +} diff --git a/src/applications/transactions/conduit/TransactionSearchConduitAPIMethod.php b/src/applications/transactions/conduit/TransactionSearchConduitAPIMethod.php new file mode 100644 index 0000000000..43b94874bf --- /dev/null +++ b/src/applications/transactions/conduit/TransactionSearchConduitAPIMethod.php @@ -0,0 +1,216 @@ + 'phid|string', + ) + $this->getPagerParamTypes(); + } + + protected function defineReturnType() { + return 'list'; + } + + protected function defineErrorTypes() { + return array(); + } + + protected function execute(ConduitAPIRequest $request) { + $viewer = $request->getUser(); + $pager = $this->newPager($request); + + $object_name = $request->getValue('objectIdentifier', null); + if (!strlen($object_name)) { + throw new Exception( + pht( + 'When calling "transaction.search", you must provide an object to '. + 'retrieve transactions for.')); + } + + $object = id(new PhabricatorObjectQuery()) + ->setViewer($viewer) + ->withNames(array($object_name)) + ->executeOne(); + if (!$object) { + throw new Exception( + pht( + 'No object "%s" exists.', + $object_name)); + } + + if (!($object instanceof PhabricatorApplicationTransactionInterface)) { + throw new Exception( + pht( + 'Object "%s" does not implement "%s", so transactions can not '. + 'be loaded for it.')); + } + + $xaction_query = PhabricatorApplicationTransactionQuery::newQueryForObject( + $object); + + $xactions = $xaction_query + ->withObjectPHIDs(array($object->getPHID())) + ->setViewer($viewer) + ->executeWithCursorPager($pager); + + if ($xactions) { + $template = head($xactions)->getApplicationTransactionCommentObject(); + + $query = new PhabricatorApplicationTransactionTemplatedCommentQuery(); + + $comment_map = $query + ->setViewer($viewer) + ->setTemplate($template) + ->withTransactionPHIDs(mpull($xactions, 'getPHID')) + ->execute(); + + $comment_map = msort($comment_map, 'getCommentVersion'); + $comment_map = array_reverse($comment_map); + $comment_map = mgroup($comment_map, 'getTransactionPHID'); + } else { + $comment_map = array(); + } + + $modular_classes = array(); + $modular_objects = array(); + $modular_xactions = array(); + foreach ($xactions as $xaction) { + if (!$xaction instanceof PhabricatorModularTransaction) { + continue; + } + + // TODO: Hack things so certain transactions which don't have a modular + // type yet can use a pseudotype until they modularize. Some day, we'll + // modularize everything and remove this. + switch ($xaction->getTransactionType()) { + case DifferentialTransaction::TYPE_INLINE: + $modular_template = new DifferentialRevisionInlineTransaction(); + break; + default: + $modular_template = $xaction->getModularType(); + break; + } + + $modular_class = get_class($modular_template); + if (!isset($modular_objects[$modular_class])) { + try { + $modular_object = newv($modular_class, array()); + $modular_objects[$modular_class] = $modular_object; + } catch (Exception $ex) { + continue; + } + } + + $modular_classes[$xaction->getPHID()] = $modular_class; + $modular_xactions[$modular_class][] = $xaction; + } + + $modular_data_map = array(); + foreach ($modular_objects as $class => $modular_type) { + $modular_data_map[$class] = $modular_type + ->setViewer($viewer) + ->loadTransactionTypeConduitData($modular_xactions[$class]); + } + + $data = array(); + foreach ($xactions as $xaction) { + $comments = idx($comment_map, $xaction->getPHID()); + + $comment_data = array(); + if ($comments) { + $removed = head($comments)->getIsDeleted(); + + foreach ($comments as $comment) { + if ($removed) { + // If the most recent version of the comment has been removed, + // don't show the history. This is for consistency with the web + // UI, which also prevents users from retrieving the content of + // removed comments. + $content = array( + 'raw' => '', + ); + } else { + $content = array( + 'raw' => (string)$comment->getContent(), + ); + } + + $comment_data[] = array( + 'id' => (int)$comment->getID(), + 'phid' => (string)$comment->getPHID(), + 'version' => (int)$comment->getCommentVersion(), + 'authorPHID' => (string)$comment->getAuthorPHID(), + 'dateCreated' => (int)$comment->getDateCreated(), + 'dateModified' => (int)$comment->getDateModified(), + 'removed' => (bool)$comment->getIsDeleted(), + 'content' => $content, + ); + } + } + + $fields = array(); + $type = null; + + if (isset($modular_classes[$xaction->getPHID()])) { + $modular_class = $modular_classes[$xaction->getPHID()]; + $modular_object = $modular_objects[$modular_class]; + $modular_data = $modular_data_map[$modular_class]; + + $type = $modular_object->getTransactionTypeForConduit($xaction); + $fields = $modular_object->getFieldValuesForConduit( + $xaction, + $modular_data); + } + + if (!$fields) { + $fields = (object)$fields; + } + + // If we haven't found a modular type, fallback for some simple core + // types. Ideally, we'll modularize everything some day. + if ($type === null) { + switch ($xaction->getTransactionType()) { + case PhabricatorTransactions::TYPE_COMMENT: + $type = 'comment'; + break; + } + } + + $data[] = array( + 'id' => (int)$xaction->getID(), + 'phid' => (string)$xaction->getPHID(), + 'type' => $type, + 'authorPHID' => (string)$xaction->getAuthorPHID(), + 'objectPHID' => (string)$xaction->getObjectPHID(), + 'dateCreated' => (int)$xaction->getDateCreated(), + 'dateModified' => (int)$xaction->getDateModified(), + 'comments' => $comment_data, + 'fields' => $fields, + ); + } + + $results = array( + 'data' => $data, + ); + + return $this->addPagerResults($results, $pager); + } +} diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index 07f85407fe..21e082b4ed 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -486,8 +486,6 @@ abstract class PhabricatorApplicationTransactionEditor switch ($xaction->getTransactionType()) { case PhabricatorTransactions::TYPE_CREATE: return true; - case PhabricatorTransactions::TYPE_COMMENT: - return $xaction->hasComment(); case PhabricatorTransactions::TYPE_CUSTOMFIELD: $field = $this->getCustomFieldForTransaction($object, $xaction); return $field->getApplicationTransactionHasEffect($xaction); @@ -534,6 +532,10 @@ abstract class PhabricatorApplicationTransactionEditor $xaction->getNewValue()); } + if ($xaction->hasComment()) { + return true; + } + return ($xaction->getOldValue() !== $xaction->getNewValue()); } diff --git a/src/applications/transactions/storage/PhabricatorModularTransactionType.php b/src/applications/transactions/storage/PhabricatorModularTransactionType.php index 128b5c7c19..119bfedd32 100644 --- a/src/applications/transactions/storage/PhabricatorModularTransactionType.php +++ b/src/applications/transactions/storage/PhabricatorModularTransactionType.php @@ -332,4 +332,16 @@ abstract class PhabricatorModularTransactionType return $this->getStorage()->getMetadataValue($key, $default); } + public function loadTransactionTypeConduitData(array $xactions) { + return null; + } + + public function getTransactionTypeForConduit($xaction) { + return null; + } + + public function getFieldValuesForConduit($xaction, $data) { + return array(); + } + } diff --git a/src/applications/uiexample/examples/PHUIButtonExample.php b/src/applications/uiexample/examples/PHUIButtonExample.php index c7107250ff..f6e8fb526c 100644 --- a/src/applications/uiexample/examples/PHUIButtonExample.php +++ b/src/applications/uiexample/examples/PHUIButtonExample.php @@ -58,10 +58,12 @@ final class PHUIButtonExample extends PhabricatorUIExample { array( 'text' => pht('Comment'), 'icon' => 'fa-comment', + 'dropdown' => true, ), array( 'text' => pht('Give Token'), 'icon' => 'fa-trophy', + 'dropdown' => true, ), array( 'text' => pht('Reverse Time'), @@ -73,9 +75,11 @@ final class PHUIButtonExample extends PhabricatorUIExample { ), array( 'icon' => 'fa-rocket', + 'dropdown' => true, ), array( 'icon' => 'fa-clipboard', + 'dropdown' => true, ), array( 'icon' => 'fa-upload', @@ -95,7 +99,8 @@ final class PHUIButtonExample extends PhabricatorUIExample { ->setColor(PHUIButtonView::GREY) ->setIcon(idx($spec, 'icon')) ->setText(idx($spec, 'text')) - ->addClass(PHUI::MARGIN_SMALL_RIGHT); + ->addClass(PHUI::MARGIN_SMALL_RIGHT) + ->setDropdown(idx($spec, 'dropdown')); $copy = idx($spec, 'copy'); if ($copy !== null) { diff --git a/src/docs/user/userguide/profile_menu.diviner b/src/docs/user/userguide/profile_menu.diviner index 2725e71851..88eb32d3ff 100644 --- a/src/docs/user/userguide/profile_menu.diviner +++ b/src/docs/user/userguide/profile_menu.diviner @@ -153,6 +153,12 @@ to a Project or to a Home menu, that Dashboard will be presented in the context of that menu. This allows customization of different pages of content without having the user leave Home or the Project. +To use a Dashboard to replace the default Home menu, install it as a Global +Menu Item and move it to the topmost item. By default, the first Dashboard +the menu renders will be selected as the default. Users that modify their +personal Home menu, will have their topmost Dashboard be their default, +overriding the Global settings. + Writing New Item Types ====================== diff --git a/webroot/rsrc/css/application/base/main-menu-view.css b/webroot/rsrc/css/application/base/main-menu-view.css index 39ec89c9fc..80adda0502 100644 --- a/webroot/rsrc/css/application/base/main-menu-view.css +++ b/webroot/rsrc/css/application/base/main-menu-view.css @@ -166,7 +166,7 @@ border: none; background-color: {$page.content}; height: 28px; - padding: 3px 28px 3px 52px; + padding: 3px 28px 3px 48px; float: left; width: 280px; } @@ -212,7 +212,7 @@ position: absolute; right: auto; left: 12px; - width: 46px; + width: 40px; background: {$greybackground}; z-index: 1; } @@ -252,7 +252,7 @@ a.phabricator-core-user-menu .caret:before { .phabricator-main-menu-search-dropdown .caret { position: absolute; - right: 18px; + right: 20px; top: 2px; border: none; margin-top: 1px; diff --git a/webroot/rsrc/css/application/diffusion/diffusion-source.css b/webroot/rsrc/css/application/diffusion/diffusion-source.css index 744f84359a..ffbed2589e 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion-source.css +++ b/webroot/rsrc/css/application/diffusion/diffusion-source.css @@ -7,24 +7,22 @@ background: {$page.content}; } -.diffusion-source tr.phabricator-source-highlight { - background: {$sh-yellowbackground}; +.diffusion-source tr.phabricator-source-highlight th, +.diffusion-source tr.phabricator-source-highlight td { + background: {$gentle.highlight}; } .diffusion-source th { text-align: right; vertical-align: top; - background: {$lightgreybackground}; - color: {$bluetext}; + color: {$darkbluetext}; border-right: 1px solid {$thinblueborder}; } .diffusion-source td { vertical-align: top; white-space: pre-wrap; - padding-top: 1px; - padding-bottom: 1px; - padding-left: 8px; + padding: 3px 12px; width: 100%; word-break: break-all; } @@ -34,12 +32,18 @@ } .diffusion-blame-link, -.diffusion-rev-link { +.diffusion-rev-link, +.diffusion-blame-date { white-space: nowrap; } -.diffusion-blame-link { - min-width: 28px; +.diffusion-blame-date, +.diffusion-blame-link, +.diffusion-blame-revision, +.diffusion-rev-link { + background: {$lightgreybackground}; + font: {$basefont}; + font-size: {$smallerfontsize}; } .diffusion-source th.diffusion-rev-link { @@ -47,16 +51,23 @@ min-width: 130px; } -.diffusion-blame-link a, -.diffusion-rev-link a, -.diffusion-line-link a { +.diffusion-source a { color: {$darkbluetext}; } -.diffusion-rev-link a, -.diffusion-rev-link span { - margin: 2px 8px 0; - display: inline-block; +.diffusion-rev-link a { + max-width: 300px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + margin: 3px 8px; + display: block; +} + +.diffusion-blame-date a, +.diffusion-blame-revision a { + float: right; + margin: 3px 8px; } .diffusion-rev-link span { @@ -69,7 +80,19 @@ .diffusion-line-link a { /* Give the user a larger click target. */ display: block; - padding: 2px 8px; + padding: 4px 8px 3px; +} + +.diffusion-line-link a { + color: {$lightgreytext}; +} + +.diffusion-blame-link a .phui-icon-view { + color: {$bluetext}; +} + +.diffusion-blame-link a:hover .phui-icon-view { + color: {$sky}; } .diffusion-line-link { diff --git a/webroot/rsrc/css/phui/button/phui-button.css b/webroot/rsrc/css/phui/button/phui-button.css index a36d69d4bc..99fef3707c 100644 --- a/webroot/rsrc/css/phui/button/phui-button.css +++ b/webroot/rsrc/css/phui/button/phui-button.css @@ -271,11 +271,17 @@ a.policy-control .phui-button-text { position: relative; } +.button.has-icon.dropdown .phui-icon-view { + margin-right: 8px; + margin-left: -2px; +} + .button.has-text .phui-icon-view { display: inline-block; position: absolute; top: 7px; left: 12px; + margin: 0; } .button.icon-last .phui-icon-view { diff --git a/webroot/rsrc/css/phui/phui-action-list.css b/webroot/rsrc/css/phui/phui-action-list.css index 44b93e61be..a7ad662d33 100644 --- a/webroot/rsrc/css/phui/phui-action-list.css +++ b/webroot/rsrc/css/phui/phui-action-list.css @@ -112,12 +112,6 @@ -webkit-font-smoothing: antialiased; } -.device-desktop li.phabricator-action-view-label:hover - .phabricator-action-view-item { - background-color: {$page.content}; - color: {$bluetext}; -} - .phabricator-action-view + .phabricator-action-view-label { padding-top: 8px; } diff --git a/webroot/rsrc/css/phui/phui-header-view.css b/webroot/rsrc/css/phui/phui-header-view.css index 7926e431c1..8aefcee31c 100644 --- a/webroot/rsrc/css/phui/phui-header-view.css +++ b/webroot/rsrc/css/phui/phui-header-view.css @@ -187,8 +187,8 @@ body .phui-header-shell.phui-bleed-header margin-right: 4px; } -.phui-header-subheader .phui-tag-view .phui-icon-view, -.phui-header-subheader .policy-header-callout .phui-icon-view { +.phui-header-subheader .phui-tag-view span.phui-icon-view, +.phui-header-subheader .policy-header-callout span.phui-icon-view { display: inline-block; margin: -2px 4px -2px 0; font-size: 15px; diff --git a/webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js b/webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js index 333e8daac1..2886aa0372 100644 --- a/webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js +++ b/webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js @@ -82,6 +82,7 @@ JX.behavior('aphlict-listen', function(config) { new JX.Notification() .setContent(JX.$H(response.content)) .setDesktopReady(response.desktopReady) + .setWebReady(response.webReady) .setKey(response.primaryObjectPHID) .setTitle(response.title) .setBody(response.body) diff --git a/webroot/rsrc/js/application/aphlict/behavior-desktop-notifications-control.js b/webroot/rsrc/js/application/aphlict/behavior-desktop-notifications-control.js index 3a9b028ca3..9be9c510b0 100644 --- a/webroot/rsrc/js/application/aphlict/behavior-desktop-notifications-control.js +++ b/webroot/rsrc/js/application/aphlict/behavior-desktop-notifications-control.js @@ -84,12 +84,12 @@ JX.behavior('desktop-notifications-control', function(config, statics) { return; } var value = e.getTarget().value; - if (value == config.desktopMode) { - window.Notification.requestPermission( - function (permission) { - updateFormStatus(permission); - updateBrowserStatus(permission); - }); + if ((value == config.desktop) || (value == config.desktopOnly)) { + window.Notification.requestPermission( + function (permission) { + updateFormStatus(permission); + updateBrowserStatus(permission); + }); } else { var statusEl = JX.$(config.statusID); JX.DOM.hide(statusEl); diff --git a/webroot/rsrc/js/application/transactions/behavior-show-older-transactions.js b/webroot/rsrc/js/application/transactions/behavior-show-older-transactions.js index ab4bf768d8..28754ee3a2 100644 --- a/webroot/rsrc/js/application/transactions/behavior-show-older-transactions.js +++ b/webroot/rsrc/js/application/transactions/behavior-show-older-transactions.js @@ -17,6 +17,13 @@ JX.behavior('phabricator-show-older-transactions', function(config) { if (!hash) { return false; } + + // If the hash isn't purely numeric, ignore it. Comments always have + // numeric hashes. See PHI43 and T12970. + if (!hash.match(/^\d+$/)) { + return false; + } + var id = 'anchor-'+hash; try { JX.$(id); diff --git a/webroot/rsrc/js/core/Notification.js b/webroot/rsrc/js/core/Notification.js index 50585419c5..9e59ec1501 100644 --- a/webroot/rsrc/js/core/Notification.js +++ b/webroot/rsrc/js/core/Notification.js @@ -27,6 +27,7 @@ JX.install('Notification', { _hideTimer : null, _duration : 12000, _desktopReady : false, + _webReady : false, _key : null, _title : null, _body : null, @@ -35,6 +36,12 @@ JX.install('Notification', { show : function() { var self = JX.Notification; + + // This person doesn't like any real-time notification + if (!this._desktopReady && !this._webReady) { + return; + } + if (!this._visible) { this._visible = true; @@ -92,6 +99,11 @@ JX.install('Notification', { return this; }, + setWebReady : function(ready) { + this._webReady = ready; + return this; + }, + setTitle : function(title) { this._title = title; return this;