From fc103f71e96b9e206c0d9016dadee91553334433 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 8 Apr 2018 06:05:05 -0700 Subject: [PATCH 01/53] Fix very odd wrapping / linebreaking for Remarkup code block headers in Safari Summary: Fixes T13118. Ref T13120. This construction is a little odd; I'm not entirely sure why Safari is doing what it's doing, but this appears to fix it. Test Plan: Viewed blocks like those in T13118 in Safari. Before the patch, weird last-letter wrapping. After the patch, sensible behavior. Maniphest Tasks: T13118, T13120 Differential Revision: https://secure.phabricator.com/D19303 --- resources/celerity/map.php | 6 +++--- webroot/rsrc/css/core/remarkup.css | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index e0bb41b8aa..79e645a927 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'e68cf1fa', 'conpherence.pkg.js' => '15191c65', - 'core.pkg.css' => '49b87886', + 'core.pkg.css' => '29452b31', 'core.pkg.js' => '1ea38af8', 'differential.pkg.css' => '113e692c', 'differential.pkg.js' => 'f6d809c0', @@ -112,7 +112,7 @@ return array( 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', 'rsrc/css/core/core.css' => '62fa3ace', - 'rsrc/css/core/remarkup.css' => '1828e2ad', + 'rsrc/css/core/remarkup.css' => '924fc97d', 'rsrc/css/core/syntax.css' => 'cae95e89', 'rsrc/css/core/z-index.css' => '9d8f7c4b', 'rsrc/css/diviner/diviner-shared.css' => '896f1d43', @@ -780,7 +780,7 @@ return array( 'phabricator-object-selector-css' => '85ee8ce6', 'phabricator-phtize' => 'd254d646', 'phabricator-prefab' => '77b0ae28', - 'phabricator-remarkup-css' => '1828e2ad', + 'phabricator-remarkup-css' => '924fc97d', 'phabricator-search-results-css' => '505dd8cf', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-slowvote-css' => 'a94b7230', diff --git a/webroot/rsrc/css/core/remarkup.css b/webroot/rsrc/css/core/remarkup.css index 990c6f0bab..dad202a9c8 100644 --- a/webroot/rsrc/css/core/remarkup.css +++ b/webroot/rsrc/css/core/remarkup.css @@ -34,6 +34,7 @@ display: inline-block; border-top-left-radius: 3px; border-top-right-radius: 3px; + overflow: hidden; } .phabricator-remarkup .code-block-counterexample .remarkup-code-header { From af87f414e8339f1a6cc9a163ce01507aa6ca16dc Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 8 Apr 2018 06:10:05 -0700 Subject: [PATCH 02/53] Stop the debugging view for typeahead datasources from fataling Summary: Fixes T13119. Ref T13120. This isn't the world's most elegant patch, but restores the debugging version of this view to service. Test Plan: Viewed debugging phage (at `/typeahead/class/`). Used the actual proxy (by changing a datasource custom field from the comment area). Maniphest Tasks: T13120, T13119 Differential Revision: https://secure.phabricator.com/D19304 --- ...PhabricatorTypeaheadModularDatasourceController.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php b/src/applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php index e0ba9a763b..9e905f8cce 100644 --- a/src/applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php +++ b/src/applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php @@ -276,10 +276,20 @@ final class PhabricatorTypeaheadModularDatasourceController // format to make it easier to debug typeahead output. foreach ($sources as $key => $source) { + // See T13119. Exclude proxy datasources from the dropdown since they + // fatal if built like this without actually being configured with an + // underlying datasource. This is a bit hacky but this is just a + // debugging/development UI anyway. + if ($source instanceof PhabricatorTypeaheadProxyDatasource) { + unset($sources[$key]); + continue; + } + // This can happen with composite or generic sources. if (!$source->getDatasourceApplicationClass()) { continue; } + if (!PhabricatorApplication::isClassInstalledForViewer( $source->getDatasourceApplicationClass(), $viewer)) { From 9bb338c0384ec4876a0c171b98888fdb074e195e Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 8 Apr 2018 10:10:34 -0700 Subject: [PATCH 03/53] Revert the alternate menu names for applications Summary: This reverts D18524. See that revision for discussion. Test Plan: Viewed home menu, saw application names as menu items. Differential Revision: https://secure.phabricator.com/D19308 --- src/applications/base/PhabricatorApplication.php | 4 ---- .../application/PhabricatorDifferentialApplication.php | 4 ---- .../diffusion/application/PhabricatorDiffusionApplication.php | 4 ---- .../maniphest/application/PhabricatorManiphestApplication.php | 4 ---- .../phame/application/PhabricatorPhameApplication.php | 4 ---- .../pholio/application/PhabricatorPholioApplication.php | 4 ---- .../phriction/application/PhabricatorPhrictionApplication.php | 4 ---- .../search/menuitem/PhabricatorApplicationProfileMenuItem.php | 2 +- 8 files changed, 1 insertion(+), 29 deletions(-) diff --git a/src/applications/base/PhabricatorApplication.php b/src/applications/base/PhabricatorApplication.php index 420d126507..e276e035e4 100644 --- a/src/applications/base/PhabricatorApplication.php +++ b/src/applications/base/PhabricatorApplication.php @@ -57,10 +57,6 @@ abstract class PhabricatorApplication abstract public function getName(); - public function getMenuName() { - return $this->getName(); - } - public function getShortDescription() { return pht('%s Application', $this->getName()); } diff --git a/src/applications/differential/application/PhabricatorDifferentialApplication.php b/src/applications/differential/application/PhabricatorDifferentialApplication.php index da40739579..eaa4948eee 100644 --- a/src/applications/differential/application/PhabricatorDifferentialApplication.php +++ b/src/applications/differential/application/PhabricatorDifferentialApplication.php @@ -10,10 +10,6 @@ final class PhabricatorDifferentialApplication extends PhabricatorApplication { return pht('Differential'); } - public function getMenuName() { - return pht('Code Review'); - } - public function getShortDescription() { return pht('Pre-Commit Review'); } diff --git a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php index d42e58b747..90956077fd 100644 --- a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php +++ b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php @@ -6,10 +6,6 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication { return pht('Diffusion'); } - public function getMenuName() { - return pht('Repositories'); - } - public function getShortDescription() { return pht('Host and Browse Repositories'); } diff --git a/src/applications/maniphest/application/PhabricatorManiphestApplication.php b/src/applications/maniphest/application/PhabricatorManiphestApplication.php index 0075770863..e57b9b545e 100644 --- a/src/applications/maniphest/application/PhabricatorManiphestApplication.php +++ b/src/applications/maniphest/application/PhabricatorManiphestApplication.php @@ -6,10 +6,6 @@ final class PhabricatorManiphestApplication extends PhabricatorApplication { return pht('Maniphest'); } - public function getMenuName() { - return pht('Tasks'); - } - public function getShortDescription() { return pht('Tasks and Bugs'); } diff --git a/src/applications/phame/application/PhabricatorPhameApplication.php b/src/applications/phame/application/PhabricatorPhameApplication.php index f9ee2831b2..9342076ddd 100644 --- a/src/applications/phame/application/PhabricatorPhameApplication.php +++ b/src/applications/phame/application/PhabricatorPhameApplication.php @@ -6,10 +6,6 @@ final class PhabricatorPhameApplication extends PhabricatorApplication { return pht('Phame'); } - public function getMenuName() { - return pht('Blogs'); - } - public function getBaseURI() { return '/phame/'; } diff --git a/src/applications/pholio/application/PhabricatorPholioApplication.php b/src/applications/pholio/application/PhabricatorPholioApplication.php index 4afaeba263..4d80dd12ae 100644 --- a/src/applications/pholio/application/PhabricatorPholioApplication.php +++ b/src/applications/pholio/application/PhabricatorPholioApplication.php @@ -6,10 +6,6 @@ final class PhabricatorPholioApplication extends PhabricatorApplication { return pht('Pholio'); } - public function getMenuName() { - return pht('Design Review'); - } - public function getBaseURI() { return '/pholio/'; } diff --git a/src/applications/phriction/application/PhabricatorPhrictionApplication.php b/src/applications/phriction/application/PhabricatorPhrictionApplication.php index 1b06fd197c..ea91727c76 100644 --- a/src/applications/phriction/application/PhabricatorPhrictionApplication.php +++ b/src/applications/phriction/application/PhabricatorPhrictionApplication.php @@ -6,10 +6,6 @@ final class PhabricatorPhrictionApplication extends PhabricatorApplication { return pht('Phriction'); } - public function getMenuName() { - return pht('Wiki'); - } - public function getShortDescription() { return pht('Wiki Documents'); } diff --git a/src/applications/search/menuitem/PhabricatorApplicationProfileMenuItem.php b/src/applications/search/menuitem/PhabricatorApplicationProfileMenuItem.php index 40275a8d7a..aa42d56cfb 100644 --- a/src/applications/search/menuitem/PhabricatorApplicationProfileMenuItem.php +++ b/src/applications/search/menuitem/PhabricatorApplicationProfileMenuItem.php @@ -31,7 +31,7 @@ final class PhabricatorApplicationProfileMenuItem return $name; } - return $application->getMenuName(); + return $application->getName(); } public function buildEditEngineFields( From 7d4e25614d001e26557203da12194366f1ef750a Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 5 Apr 2018 07:31:47 -0700 Subject: [PATCH 04/53] Remove the ability to disable blame in Diffusion Summary: Ref T13105. Given that we now load blame with AJAX, it's not clear that there's any benefit to disabling it. This would also interact oddly with the document engine. Test Plan: Viewed files in Diffusion, no longer saw blame-related options. Reviewers: mydeveloperday Reviewed By: mydeveloperday Maniphest Tasks: T13105 Differential Revision: https://secure.phabricator.com/D19300 --- .../controller/DiffusionBrowseController.php | 124 ++++-------------- 1 file changed, 29 insertions(+), 95 deletions(-) diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index a2380aab4a..8c0c2c19c4 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -111,41 +111,18 @@ final class DiffusionBrowseController extends DiffusionController { $path = $drequest->getPath(); - $blame_key = PhabricatorDiffusionBlameSetting::SETTINGKEY; - $show_blame = $request->getBool( - 'blame', - $viewer->getUserSetting($blame_key)); - - $view = $request->getStr('view'); - if ($request->isFormPost() && $view != 'raw' && $viewer->isLoggedIn()) { - $preferences = PhabricatorUserPreferences::loadUserPreferences($viewer); - - $editor = id(new PhabricatorUserPreferencesEditor()) - ->setActor($viewer) - ->setContentSourceFromRequest($request) - ->setContinueOnNoEffect(true) - ->setContinueOnMissingFields(true); - - $xactions = array(); - $xactions[] = $preferences->newTransaction($blame_key, $show_blame); - $editor->applyTransactions($preferences, $xactions); - - $uri = $request->getRequestURI() - ->alter('blame', null); - - return id(new AphrontRedirectResponse())->setURI($uri); - } - // We need the blame information if blame is on and this is an Ajax request. // If blame is on and this is a colorized request, we don't show blame at // first (we ajax it in afterward) so we don't need to query for it. - $needs_blame = ($show_blame && $request->isAjax()); + $needs_blame = $request->isAjax(); $params = array( 'commit' => $drequest->getCommit(), 'path' => $drequest->getPath(), ); + $view = $request->getStr('view'); + $byte_limit = null; if ($view !== 'raw') { $byte_limit = PhabricatorFileStorageEngine::getChunkThreshold(); @@ -225,7 +202,6 @@ final class DiffusionBrowseController extends DiffusionController { // Build the content of the file. $corpus = $this->buildCorpus( - $show_blame, $data, $needs_blame, $drequest, @@ -240,8 +216,7 @@ final class DiffusionBrowseController extends DiffusionController { require_celerity_resource('diffusion-source-css'); - // Render the page. - $bar = $this->buildButtonBar($drequest, $show_blame, $show_editor); + $bar = $this->buildButtonBar($drequest, $show_editor); $header = $this->buildHeaderView($drequest); $header->setHeaderIcon('fa-file-code-o'); @@ -540,7 +515,6 @@ final class DiffusionBrowseController extends DiffusionController { } private function buildCorpus( - $show_blame, $file_corpus, $needs_blame, DiffusionRequest $drequest, @@ -585,8 +559,7 @@ final class DiffusionBrowseController extends DiffusionController { $rows = $this->buildDisplayRows( $lines, $blame_list, - $blame_commits, - $show_blame); + $blame_commits); $corpus_table = javelin_tag( 'table', @@ -677,7 +650,7 @@ final class DiffusionBrowseController extends DiffusionController { phutil_format_bytes($highlight_limit)); } - if ($show_blame && !$can_blame) { + if (!$can_blame) { $messages[] = pht( 'This file is larger than %s, so blame is disabled.', phutil_format_bytes($blame_limit)); @@ -701,7 +674,6 @@ final class DiffusionBrowseController extends DiffusionController { private function buildButtonBar( DiffusionRequest $drequest, - $show_blame, $show_editor) { $viewer = $this->getViewer(); @@ -728,38 +700,6 @@ final class DiffusionBrowseController extends DiffusionController { ))) ->setIcon('fa-backward'); - if ($show_blame) { - $blame_text = pht('Disable Blame'); - $blame_icon = 'fa-exclamation-circle lightgreytext'; - $blame_value = 0; - } else { - $blame_text = pht('Enable Blame'); - $blame_icon = 'fa-exclamation-circle'; - $blame_value = 1; - } - - $blame = id(new PHUIButtonView()) - ->setText($blame_text) - ->setIcon($blame_icon) - ->setUser($viewer) - ->setSelected(!$blame_value) - ->setColor(PHUIButtonView::GREY); - - if ($viewer->isLoggedIn()) { - $blame = phabricator_form( - $viewer, - array( - 'action' => $base_uri->alter('blame', $blame_value), - 'method' => 'POST', - 'style' => 'display: inline-block;', - ), - $blame); - } else { - $blame->setTag('a'); - $blame->setHref($base_uri->alter('blame', $blame_value)); - } - $buttons[] = $blame; - if ($editor_link) { $buttons[] = id(new PHUIButtonView()) @@ -931,8 +871,7 @@ final class DiffusionBrowseController extends DiffusionController { private function buildDisplayRows( array $lines, array $blame_list, - array $blame_commits, - $show_blame) { + array $blame_commits) { $request = $this->getRequest(); $viewer = $this->getViewer(); @@ -1123,7 +1062,6 @@ final class DiffusionBrowseController extends DiffusionController { $rows = $this->renderInlines( idx($inlines, 0, array()), - $show_blame, (bool)$this->coverage, $engine); @@ -1157,7 +1095,7 @@ final class DiffusionBrowseController extends DiffusionController { $skip_text = pht('Skip Past This Commit'); $skip_icon = id(new PHUIIconView()) - ->setIcon('fa-caret-square-o-left'); + ->setIcon('fa-backward'); foreach ($display as $line_index => $line) { $row = array(); @@ -1191,7 +1129,7 @@ final class DiffusionBrowseController extends DiffusionController { } } - $skip_href = $line_href.'?before='.$identifier.'&view=blame'; + $skip_href = $line_href.'?before='.$identifier; $before_link = javelin_tag( 'a', array( @@ -1206,30 +1144,28 @@ final class DiffusionBrowseController extends DiffusionController { $skip_icon); } - if ($show_blame) { - $row[] = phutil_tag( - 'th', - array( - 'class' => 'diffusion-blame-link', - ), - $before_link); + $row[] = phutil_tag( + 'th', + array( + 'class' => 'diffusion-blame-link', + ), + $before_link); - $object_links = array(); - $object_links[] = $author_link; - $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); + $object_links = array(); + $object_links[] = $author_link; + $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); + $line_link = phutil_tag( 'a', array( @@ -1305,7 +1241,6 @@ final class DiffusionBrowseController extends DiffusionController { $cur_inlines = $this->renderInlines( idx($inlines, $line_number, array()), - $show_blame, $this->coverage, $engine); foreach ($cur_inlines as $cur_inline) { @@ -1318,7 +1253,6 @@ final class DiffusionBrowseController extends DiffusionController { private function renderInlines( array $inlines, - $show_blame, $has_coverage, $engine) { @@ -1333,7 +1267,7 @@ final class DiffusionBrowseController extends DiffusionController { ->setInlineComment($inline) ->render(); - $row = array_fill(0, ($show_blame ? 3 : 1), phutil_tag('th')); + $row = array_fill(0, 3, phutil_tag('th')); $row[] = phutil_tag('td', array(), $inline_view); From 245132a0b22e9f572acda9b7b90b96475845d4b9 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 5 Apr 2018 08:21:12 -0700 Subject: [PATCH 05/53] Pull file Document Engine rendering out of "Files" application controllers Summary: Ref T13105. This separates document rendering from the Controllers which trigger it so it can be reused elsewhere (notably, in Diffusion). This shouldn't cause any application behavior to change, it just pulls the rendering logic out so it can be reused elsewhere. Test Plan: Viewed various types of files in Files; toggled rendering, highlighting, and encoding. Reviewers: mydeveloperday Reviewed By: mydeveloperday Maniphest Tasks: T13105 Differential Revision: https://secure.phabricator.com/D19301 --- src/__phutil_library_map__.php | 4 + .../PhabricatorFilesApplication.php | 2 +- .../PhabricatorFileDocumentController.php | 100 +----- .../PhabricatorFileViewController.php | 113 +------ .../document/PhabricatorDocumentEngine.php | 12 - .../PhabricatorDocumentRenderingEngine.php | 285 ++++++++++++++++++ ...PhabricatorFileDocumentRenderingEngine.php | 47 +++ 7 files changed, 344 insertions(+), 219 deletions(-) create mode 100644 src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php create mode 100644 src/applications/files/document/render/PhabricatorFileDocumentRenderingEngine.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 391b8fb1f2..95f8ed45b2 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2819,6 +2819,7 @@ phutil_register_library_map(array( 'PhabricatorDivinerApplication' => 'applications/diviner/application/PhabricatorDivinerApplication.php', 'PhabricatorDocumentEngine' => 'applications/files/document/PhabricatorDocumentEngine.php', 'PhabricatorDocumentRef' => 'applications/files/document/PhabricatorDocumentRef.php', + 'PhabricatorDocumentRenderingEngine' => 'applications/files/document/render/PhabricatorDocumentRenderingEngine.php', 'PhabricatorDoorkeeperApplication' => 'applications/doorkeeper/application/PhabricatorDoorkeeperApplication.php', 'PhabricatorDraft' => 'applications/draft/storage/PhabricatorDraft.php', 'PhabricatorDraftDAO' => 'applications/draft/storage/PhabricatorDraftDAO.php', @@ -3010,6 +3011,7 @@ phutil_register_library_map(array( 'PhabricatorFileDeleteController' => 'applications/files/controller/PhabricatorFileDeleteController.php', 'PhabricatorFileDeleteTransaction' => 'applications/files/xaction/PhabricatorFileDeleteTransaction.php', 'PhabricatorFileDocumentController' => 'applications/files/controller/PhabricatorFileDocumentController.php', + 'PhabricatorFileDocumentRenderingEngine' => 'applications/files/document/render/PhabricatorFileDocumentRenderingEngine.php', 'PhabricatorFileDropUploadController' => 'applications/files/controller/PhabricatorFileDropUploadController.php', 'PhabricatorFileEditController' => 'applications/files/controller/PhabricatorFileEditController.php', 'PhabricatorFileEditEngine' => 'applications/files/editor/PhabricatorFileEditEngine.php', @@ -8401,6 +8403,7 @@ phutil_register_library_map(array( 'PhabricatorDivinerApplication' => 'PhabricatorApplication', 'PhabricatorDocumentEngine' => 'Phobject', 'PhabricatorDocumentRef' => 'Phobject', + 'PhabricatorDocumentRenderingEngine' => 'Phobject', 'PhabricatorDoorkeeperApplication' => 'PhabricatorApplication', 'PhabricatorDraft' => 'PhabricatorDraftDAO', 'PhabricatorDraftDAO' => 'PhabricatorLiskDAO', @@ -8624,6 +8627,7 @@ phutil_register_library_map(array( 'PhabricatorFileDeleteController' => 'PhabricatorFileController', 'PhabricatorFileDeleteTransaction' => 'PhabricatorFileTransactionType', 'PhabricatorFileDocumentController' => 'PhabricatorFileController', + 'PhabricatorFileDocumentRenderingEngine' => 'PhabricatorDocumentRenderingEngine', 'PhabricatorFileDropUploadController' => 'PhabricatorFileController', 'PhabricatorFileEditController' => 'PhabricatorFileController', 'PhabricatorFileEditEngine' => 'PhabricatorEditEngine', diff --git a/src/applications/files/application/PhabricatorFilesApplication.php b/src/applications/files/application/PhabricatorFilesApplication.php index 5bf1ebccc4..14bbd83e78 100644 --- a/src/applications/files/application/PhabricatorFilesApplication.php +++ b/src/applications/files/application/PhabricatorFilesApplication.php @@ -73,7 +73,7 @@ final class PhabricatorFilesApplication extends PhabricatorApplication { => 'PhabricatorFileViewController', '/file/' => array( '(query/(?P[^/]+)/)?' => 'PhabricatorFileListController', - 'view/(?P[^/]+)/'. + 'view/(?P[1-9]\d*)/'. '(?:(?P[^/]+)/)?'. '(?:\$(?P\d+(?:-\d+)?))?' => 'PhabricatorFileViewController', diff --git a/src/applications/files/controller/PhabricatorFileDocumentController.php b/src/applications/files/controller/PhabricatorFileDocumentController.php index 15fadaa289..e74b0bc6eb 100644 --- a/src/applications/files/controller/PhabricatorFileDocumentController.php +++ b/src/applications/files/controller/PhabricatorFileDocumentController.php @@ -3,10 +3,6 @@ final class PhabricatorFileDocumentController extends PhabricatorFileController { - private $file; - private $engine; - private $ref; - public function shouldAllowPublic() { return true; } @@ -26,102 +22,14 @@ final class PhabricatorFileDocumentController 'This file ("%s") does not exist or could not be loaded.', $file_phid)); } - $this->file = $file; $ref = id(new PhabricatorDocumentRef()) ->setFile($file); - $this->ref = $ref; - $engines = PhabricatorDocumentEngine::getEnginesForRef($viewer, $ref); - $engine_key = $request->getURIData('engineKey'); - if (!isset($engines[$engine_key])) { - return $this->newErrorResponse( - pht( - 'The engine ("%s") is unknown, or unable to render this document.', - $engine_key)); - } - $engine = $engines[$engine_key]; - $this->engine = $engine; - - $encode_setting = $request->getStr('encode'); - if (strlen($encode_setting)) { - $engine->setEncodingConfiguration($encode_setting); - } - - $highlight_setting = $request->getStr('highlight'); - if (strlen($highlight_setting)) { - $engine->setHighlightingConfiguration($highlight_setting); - } - - try { - $content = $engine->newDocument($ref); - } catch (Exception $ex) { - return $this->newErrorResponse($ex->getMessage()); - } - - return $this->newContentResponse($content); - } - - private function newErrorResponse($message) { - $container = phutil_tag( - 'div', - array( - 'class' => 'document-engine-error', - ), - array( - id(new PHUIIconView()) - ->setIcon('fa-exclamation-triangle red'), - ' ', - $message, - )); - - return $this->newContentResponse($container); - } - - - private function newContentResponse($content) { - $viewer = $this->getViewer(); - $request = $this->getRequest(); - - $file = $this->file; - $engine = $this->engine; - $ref = $this->ref; - - if ($request->isAjax()) { - return id(new AphrontAjaxResponse()) - ->setContent( - array( - 'markup' => hsprintf('%s', $content), - )); - } - - $crumbs = $this->buildApplicationCrumbs(); - if ($file) { - $crumbs->addTextCrumb($file->getMonogram(), $file->getInfoURI()); - } - - $label = $engine->getViewAsLabel($ref); - if ($label) { - $crumbs->addTextCrumb($label); - } - - $crumbs->setBorder(true); - - $content_frame = id(new PHUIObjectBoxView()) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->appendChild($content); - - $page_frame = id(new PHUITwoColumnView()) - ->setFooter($content_frame); - - return $this->newPage() - ->setCrumbs($crumbs) - ->setTitle( - array( - $ref->getName(), - pht('Standalone'), - )) - ->appendChild($page_frame); + return id(new PhabricatorFileDocumentRenderingEngine()) + ->setRequest($request) + ->setController($this) + ->newRenderResponse($ref); } } diff --git a/src/applications/files/controller/PhabricatorFileViewController.php b/src/applications/files/controller/PhabricatorFileViewController.php index 9d32ff6fb1..42a632424d 100644 --- a/src/applications/files/controller/PhabricatorFileViewController.php +++ b/src/applications/files/controller/PhabricatorFileViewController.php @@ -403,122 +403,15 @@ final class PhabricatorFileViewController extends PhabricatorFileController { } private function newFileContent(PhabricatorFile $file) { - $viewer = $this->getViewer(); $request = $this->getRequest(); $ref = id(new PhabricatorDocumentRef()) ->setFile($file); - $engines = PhabricatorDocumentEngine::getEnginesForRef($viewer, $ref); + $engine = id(new PhabricatorFileDocumentRenderingEngine()) + ->setRequest($request); - $engine_key = $request->getURIData('engineKey'); - if (!isset($engines[$engine_key])) { - $engine_key = head_key($engines); - } - $engine = $engines[$engine_key]; - - $lines = $request->getURILineRange('lines', 1000); - if ($lines) { - $engine->setHighlightedLines(range($lines[0], $lines[1])); - } - - $encode_setting = $request->getStr('encode'); - if (strlen($encode_setting)) { - $engine->setEncodingConfiguration($encode_setting); - } - - $highlight_setting = $request->getStr('highlight'); - if (strlen($highlight_setting)) { - $engine->setHighlightingConfiguration($highlight_setting); - } - - $views = array(); - foreach ($engines as $candidate_key => $candidate_engine) { - $label = $candidate_engine->getViewAsLabel($ref); - if ($label === null) { - continue; - } - - $view_uri = '/file/view/'.$file->getID().'/'.$candidate_key.'/'; - - $view_icon = $candidate_engine->getViewAsIconIcon($ref); - $view_color = $candidate_engine->getViewAsIconColor($ref); - $loading = $candidate_engine->newLoadingContent($ref); - - $views[] = array( - 'viewKey' => $candidate_engine->getDocumentEngineKey(), - 'icon' => $view_icon, - 'color' => $view_color, - 'name' => $label, - 'engineURI' => $candidate_engine->getRenderURI($ref), - 'viewURI' => $view_uri, - 'loadingMarkup' => hsprintf('%s', $loading), - 'canEncode' => $candidate_engine->canConfigureEncoding($ref), - 'canHighlight' => $candidate_engine->CanConfigureHighlighting($ref), - ); - } - - $viewport_id = celerity_generate_unique_node_id(); - $control_id = celerity_generate_unique_node_id(); - $icon = $engine->newDocumentIcon($ref); - - if ($engine->shouldRenderAsync($ref)) { - $content = $engine->newLoadingContent($ref); - $config = array( - 'renderControlID' => $control_id, - ); - } else { - $content = $engine->newDocument($ref); - $config = array(); - } - - Javelin::initBehavior('document-engine', $config); - - $viewport = phutil_tag( - 'div', - array( - 'id' => $viewport_id, - ), - $content); - - $meta = array( - 'viewportID' => $viewport_id, - 'viewKey' => $engine->getDocumentEngineKey(), - 'views' => $views, - 'standaloneURI' => $engine->getRenderURI($ref), - 'encode' => array( - 'icon' => 'fa-font', - 'name' => pht('Change Text Encoding...'), - 'uri' => '/services/encoding/', - 'value' => $encode_setting, - ), - 'highlight' => array( - 'icon' => 'fa-lightbulb-o', - 'name' => pht('Highlight As...'), - 'uri' => '/services/highlight/', - 'value' => $highlight_setting, - ), - ); - - $view_button = id(new PHUIButtonView()) - ->setTag('a') - ->setText(pht('View Options')) - ->setIcon('fa-file-image-o') - ->setColor(PHUIButtonView::GREY) - ->setID($control_id) - ->setMetadata($meta) - ->setDropdown(true) - ->addSigil('document-engine-view-dropdown'); - - $header = id(new PHUIHeaderView()) - ->setHeaderIcon($icon) - ->setHeader($ref->getName()) - ->addActionLink($view_button); - - return id(new PHUIObjectBoxView()) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setHeader($header) - ->appendChild($viewport); + return $engine->newDocumentView($ref); } } diff --git a/src/applications/files/document/PhabricatorDocumentEngine.php b/src/applications/files/document/PhabricatorDocumentEngine.php index c3a1a7317a..17feed1c3e 100644 --- a/src/applications/files/document/PhabricatorDocumentEngine.php +++ b/src/applications/files/document/PhabricatorDocumentEngine.php @@ -152,18 +152,6 @@ abstract class PhabricatorDocumentEngine return null; } - public function getRenderURI(PhabricatorDocumentRef $ref) { - $file = $ref->getFile(); - if (!$file) { - throw new PhutilMethodNotImplementedException(); - } - - $engine_key = $this->getDocumentEngineKey(); - $file_phid = $file->getPHID(); - - return "/file/document/{$engine_key}/{$file_phid}/"; - } - final public static function getEnginesForRef( PhabricatorUser $viewer, PhabricatorDocumentRef $ref) { diff --git a/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php b/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php new file mode 100644 index 0000000000..37b0917a65 --- /dev/null +++ b/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php @@ -0,0 +1,285 @@ +request = $request; + return $this; + } + + final public function getRequest() { + if (!$this->request) { + throw new PhutilInvalidStateException('setRequest'); + } + + return $this->request; + } + + final public function setController(PhabricatorController $controller) { + $this->controller = $controller; + return $this; + } + + final public function getController() { + if (!$this->controller) { + throw new PhutilInvalidStateException('setController'); + } + + return $this->controller; + } + + final protected function getActiveEngine() { + if (!$this->activeEngine) { + throw new PhutilInvalidStateException('setActiveEngine'); + } + + return $this->activeEngine; + } + + final public function newDocumentView(PhabricatorDocumentRef $ref) { + $request = $this->getRequest(); + $viewer = $request->getViewer(); + + $engines = PhabricatorDocumentEngine::getEnginesForRef($viewer, $ref); + + $engine_key = $this->getSelectedDocumentEngineKey(); + if (!isset($engines[$engine_key])) { + $engine_key = head_key($engines); + } + $engine = $engines[$engine_key]; + + $lines = $request->getURILineRange('lines', 1000); + if ($lines) { + $engine->setHighlightedLines(range($lines[0], $lines[1])); + } + + $encode_setting = $request->getStr('encode'); + if (strlen($encode_setting)) { + $engine->setEncodingConfiguration($encode_setting); + } + + $highlight_setting = $request->getStr('highlight'); + if (strlen($highlight_setting)) { + $engine->setHighlightingConfiguration($highlight_setting); + } + + $views = array(); + foreach ($engines as $candidate_key => $candidate_engine) { + $label = $candidate_engine->getViewAsLabel($ref); + if ($label === null) { + continue; + } + + $view_uri = $this->newRefViewURI($ref, $candidate_engine); + + $view_icon = $candidate_engine->getViewAsIconIcon($ref); + $view_color = $candidate_engine->getViewAsIconColor($ref); + $loading = $candidate_engine->newLoadingContent($ref); + + $views[] = array( + 'viewKey' => $candidate_engine->getDocumentEngineKey(), + 'icon' => $view_icon, + 'color' => $view_color, + 'name' => $label, + 'engineURI' => $this->newRefRenderURI($ref, $candidate_engine), + 'viewURI' => $view_uri, + 'loadingMarkup' => hsprintf('%s', $loading), + 'canEncode' => $candidate_engine->canConfigureEncoding($ref), + 'canHighlight' => $candidate_engine->CanConfigureHighlighting($ref), + ); + } + + $viewport_id = celerity_generate_unique_node_id(); + $control_id = celerity_generate_unique_node_id(); + $icon = $engine->newDocumentIcon($ref); + + if ($engine->shouldRenderAsync($ref)) { + $content = $engine->newLoadingContent($ref); + $config = array( + 'renderControlID' => $control_id, + ); + } else { + $content = $engine->newDocument($ref); + $config = array(); + } + + Javelin::initBehavior('document-engine', $config); + + $viewport = phutil_tag( + 'div', + array( + 'id' => $viewport_id, + ), + $content); + + $meta = array( + 'viewportID' => $viewport_id, + 'viewKey' => $engine->getDocumentEngineKey(), + 'views' => $views, + 'encode' => array( + 'icon' => 'fa-font', + 'name' => pht('Change Text Encoding...'), + 'uri' => '/services/encoding/', + 'value' => $encode_setting, + ), + 'highlight' => array( + 'icon' => 'fa-lightbulb-o', + 'name' => pht('Highlight As...'), + 'uri' => '/services/highlight/', + 'value' => $highlight_setting, + ), + ); + + $view_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('View Options')) + ->setIcon('fa-file-image-o') + ->setColor(PHUIButtonView::GREY) + ->setID($control_id) + ->setMetadata($meta) + ->setDropdown(true) + ->addSigil('document-engine-view-dropdown'); + + $header = id(new PHUIHeaderView()) + ->setHeaderIcon($icon) + ->setHeader($ref->getName()) + ->addActionLink($view_button); + + return id(new PHUIObjectBoxView()) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setHeader($header) + ->appendChild($viewport); + } + + abstract protected function newRefViewURI( + PhabricatorDocumentRef $ref, + PhabricatorDocumentEngine $engine); + + abstract protected function newRefRenderURI( + PhabricatorDocumentRef $ref, + PhabricatorDocumentEngine $engine); + + protected function getSelectedDocumentEngineKey() { + return $this->getRequest()->getURIData('engineKey'); + } + + final public function newRenderResponse(PhabricatorDocumentRef $ref) { + $request = $this->getRequest(); + $viewer = $request->getViewer(); + + $engines = PhabricatorDocumentEngine::getEnginesForRef($viewer, $ref); + $engine_key = $request->getURIData('engineKey'); + if (!isset($engines[$engine_key])) { + return $this->newErrorResponse( + $ref, + pht( + 'The engine ("%s") is unknown, or unable to render this document.', + $engine_key)); + } + $engine = $engines[$engine_key]; + + $this->activeEngine = $engine; + + $encode_setting = $request->getStr('encode'); + if (strlen($encode_setting)) { + $engine->setEncodingConfiguration($encode_setting); + } + + $highlight_setting = $request->getStr('highlight'); + if (strlen($highlight_setting)) { + $engine->setHighlightingConfiguration($highlight_setting); + } + + try { + $content = $engine->newDocument($ref); + } catch (Exception $ex) { + return $this->newErrorResponse($ref, $ex->getMessage()); + } + + return $this->newContentResponse($ref, $content); + } + + private function newErrorResponse( + PhabricatorDocumentRef $ref, + $message) { + + $container = phutil_tag( + 'div', + array( + 'class' => 'document-engine-error', + ), + array( + id(new PHUIIconView()) + ->setIcon('fa-exclamation-triangle red'), + ' ', + $message, + )); + + return $this->newContentResponse($ref, $container); + } + + private function newContentResponse( + PhabricatorDocumentRef $ref, + $content) { + + $request = $this->getRequest(); + $viewer = $request->getViewer(); + $controller = $this->getController(); + + if ($request->isAjax()) { + return id(new AphrontAjaxResponse()) + ->setContent( + array( + 'markup' => hsprintf('%s', $content), + )); + } + + $crumbs = $this->newCrumbs($ref); + if ($crumbs) { + $crumbs->setBorder(true); + } + + $content_frame = id(new PHUIObjectBoxView()) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($content); + + $page_frame = id(new PHUITwoColumnView()) + ->setFooter($content_frame); + + return $controller->newPage() + ->setCrumbs($crumbs) + ->setTitle( + array( + $ref->getName(), + pht('Standalone'), + )) + ->appendChild($page_frame); + } + + protected function newCrumbs(PhabricatorDocumentRef $ref) { + $controller = $this->getController(); + $crumbs = $controller->buildApplicationCrumbsForEditEngine(); + + $this->addApplicationCrumbs($ref, $crumbs); + + $engine = $this->getActiveEngine(); + $label = $engine->getViewAsLabel($ref); + if ($label) { + $crumbs->addTextCrumb($label); + } + + return $crumbs; + } + + protected function addApplicationCrumbs( + PhabricatorDocumentRef $ref, + PHUICrumbsView $crumbs) { + return; + } + +} diff --git a/src/applications/files/document/render/PhabricatorFileDocumentRenderingEngine.php b/src/applications/files/document/render/PhabricatorFileDocumentRenderingEngine.php new file mode 100644 index 0000000000..f48336bea3 --- /dev/null +++ b/src/applications/files/document/render/PhabricatorFileDocumentRenderingEngine.php @@ -0,0 +1,47 @@ +getFile(); + $engine_key = $engine->getDocumentEngineKey(); + + return urisprintf( + '/file/view/%d/%s/', + $file->getID(), + $engine_key); + } + + protected function newRefRenderURI( + PhabricatorDocumentRef $ref, + PhabricatorDocumentEngine $engine) { + $file = $ref->getFile(); + if (!$file) { + throw new PhutilMethodNotImplementedException(); + } + + $engine_key = $engine->getDocumentEngineKey(); + $file_phid = $file->getPHID(); + + return urisprintf( + '/file/document/%s/%s/', + $engine_key, + $file_phid); + } + + protected function addApplicationCrumbs( + PhabricatorDocumentRef $ref, + PHUICrumbsView $crumbs) { + + $file = $ref->getFile(); + if ($file) { + $crumbs->addTextCrumb($file->getMonogram(), $file->getInfoURI()); + } + + } + +} From 1fde4a9450610586e76c9e536c88f556cc52683c Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 5 Apr 2018 10:21:48 -0700 Subject: [PATCH 06/53] Move Diffusion browse rendering to DocumentEngine, breaking almost all features Summary: Ref T13105. This breaks about 9,000 features but moves Diffusion to DocumentEngine for rendering. See T13105 for a more complete list of all the broken stuff. But you can't bake a software without breaking all the features every time you make a change, right? Test Plan: Viewed various files in Diffusion, used DocumentEngine features like highlighting and rendering engine selection. Reviewers: mydeveloperday Reviewed By: mydeveloperday Subscribers: mydeveloperday Maniphest Tasks: T13105 Differential Revision: https://secure.phabricator.com/D19302 --- src/__phutil_library_map__.php | 4 + .../PhabricatorDiffusionApplication.php | 2 + .../controller/DiffusionBrowseController.php | 625 +----------------- .../DiffusionDocumentController.php | 45 ++ .../DiffusionDocumentRenderingEngine.php | 67 ++ .../PhabricatorFileDocumentController.php | 11 +- .../PhabricatorDocumentRenderingEngine.php | 72 +- ...PhabricatorFileDocumentRenderingEngine.php | 12 +- .../storage/PhabricatorRepository.php | 2 + 9 files changed, 187 insertions(+), 653 deletions(-) create mode 100644 src/applications/diffusion/controller/DiffusionDocumentController.php create mode 100644 src/applications/diffusion/document/DiffusionDocumentRenderingEngine.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 95f8ed45b2..8e2eb24b15 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -732,6 +732,8 @@ phutil_register_library_map(array( 'DiffusionDiffController' => 'applications/diffusion/controller/DiffusionDiffController.php', 'DiffusionDiffInlineCommentQuery' => 'applications/diffusion/query/DiffusionDiffInlineCommentQuery.php', 'DiffusionDiffQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionDiffQueryConduitAPIMethod.php', + 'DiffusionDocumentController' => 'applications/diffusion/controller/DiffusionDocumentController.php', + 'DiffusionDocumentRenderingEngine' => 'applications/diffusion/document/DiffusionDocumentRenderingEngine.php', 'DiffusionDoorkeeperCommitFeedStoryPublisher' => 'applications/diffusion/doorkeeper/DiffusionDoorkeeperCommitFeedStoryPublisher.php', 'DiffusionEmptyResultView' => 'applications/diffusion/view/DiffusionEmptyResultView.php', 'DiffusionExistsQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionExistsQueryConduitAPIMethod.php', @@ -5987,6 +5989,8 @@ phutil_register_library_map(array( 'DiffusionDiffController' => 'DiffusionController', 'DiffusionDiffInlineCommentQuery' => 'PhabricatorDiffInlineCommentQuery', 'DiffusionDiffQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', + 'DiffusionDocumentController' => 'DiffusionController', + 'DiffusionDocumentRenderingEngine' => 'PhabricatorDocumentRenderingEngine', 'DiffusionDoorkeeperCommitFeedStoryPublisher' => 'DoorkeeperFeedStoryPublisher', 'DiffusionEmptyResultView' => 'DiffusionView', 'DiffusionExistsQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', diff --git a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php index 90956077fd..4a1c2fdd95 100644 --- a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php +++ b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php @@ -53,6 +53,8 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication { 'history/(?P.*)' => 'DiffusionHistoryController', 'graph/(?P.*)' => 'DiffusionGraphController', 'browse/(?P.*)' => 'DiffusionBrowseController', + 'document/(?P.*)' + => 'DiffusionDocumentController', 'lastmodified/(?P.*)' => 'DiffusionLastModifiedController', 'diff/' => 'DiffusionDiffController', 'tags/(?P.*)' => 'DiffusionTagListController', diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index 8c0c2c19c4..2c2a2f46b9 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -45,13 +45,13 @@ final class DiffusionBrowseController extends DiffusionController { if ($is_file) { return $this->browseFile(); - } else { - $paths = $results->getPaths(); - $paths = $pager->sliceResults($paths); - $results->setPaths($paths); - - return $this->browseDirectory($results, $pager); } + + $paths = $results->getPaths(); + $paths = $pager->sliceResults($paths); + $results->setPaths($paths); + + return $this->browseDirectory($results, $pager); } private function browseSearch() { @@ -187,26 +187,21 @@ final class DiffusionBrowseController extends DiffusionController { } else { $corpus = $this->buildGitLFSCorpus($lfs_ref); } - } else if (ArcanistDiffUtils::isHeuristicBinaryFile($data)) { - $file_uri = $file->getBestURI(); - - if ($file->isViewableImage()) { - $corpus = $this->buildImageCorpus($file_uri); - } else { - $corpus = $this->buildBinaryCorpus($file_uri, $data); - } } else { $this->loadLintMessages(); $this->coverage = $drequest->loadCoverage(); $show_editor = true; - // Build the content of the file. - $corpus = $this->buildCorpus( - $data, - $needs_blame, - $drequest, - $path, - $data); + $ref = id(new PhabricatorDocumentRef()) + ->setFile($file); + + $engine = id(new DiffusionDocumentRenderingEngine()) + ->setRequest($request) + ->setDiffusionRequest($drequest); + + $corpus = $engine->newDocumentView($ref); + + $this->corpusButtons[] = $this->renderFileButton(); } } @@ -514,164 +509,6 @@ final class DiffusionBrowseController extends DiffusionController { '/'.$drequest->getPath()); } - private function buildCorpus( - $file_corpus, - $needs_blame, - DiffusionRequest $drequest, - $path, - $data) { - - $viewer = $this->getViewer(); - $blame_timeout = 15; - $blame_failed = false; - - $highlight_limit = DifferentialChangesetParser::HIGHLIGHT_BYTE_LIMIT; - $blame_limit = DifferentialChangesetParser::HIGHLIGHT_BYTE_LIMIT; - $can_highlight = (strlen($file_corpus) <= $highlight_limit); - $can_blame = (strlen($file_corpus) <= $blame_limit); - - if ($needs_blame && $can_blame) { - $blame = $this->loadBlame($path, $drequest->getCommit(), $blame_timeout); - list($blame_list, $blame_commits) = $blame; - if ($blame_list === null) { - $blame_failed = true; - $blame_list = array(); - } - } else { - $blame_list = array(); - $blame_commits = array(); - } - - require_celerity_resource('syntax-highlighting-css'); - if ($can_highlight) { - $highlighted = PhabricatorSyntaxHighlighter::highlightWithFilename( - $path, - $file_corpus); - } else { - // Highlight as plain text to escape the content properly. - $highlighted = PhabricatorSyntaxHighlighter::highlightWithLanguage( - 'txt', - $file_corpus); - } - - $lines = phutil_split_lines($highlighted); - - $rows = $this->buildDisplayRows( - $lines, - $blame_list, - $blame_commits); - - $corpus_table = javelin_tag( - 'table', - array( - 'class' => 'diffusion-source remarkup-code PhabricatorMonospaced', - 'sigil' => 'phabricator-source', - 'meta' => array( - 'uri' => $this->getLineNumberBaseURI(), - ), - ), - $rows); - - $corpus_table = phutil_tag_div('diffusion-source-wrap', $corpus_table); - - if ($this->getRequest()->isAjax()) { - return $corpus_table; - } - - $id = celerity_generate_unique_node_id(); - - $repo = $drequest->getRepository(); - $symbol_repos = nonempty($repo->getSymbolSources(), array()); - $symbol_repos[] = $repo->getPHID(); - - $lang = last(explode('.', $drequest->getPath())); - $repo_languages = $repo->getSymbolLanguages(); - $repo_languages = nonempty($repo_languages, array()); - $repo_languages = array_fill_keys($repo_languages, true); - - $needs_symbols = true; - if ($repo_languages && $symbol_repos) { - $have_symbols = id(new DiffusionSymbolQuery()) - ->existsSymbolsInRepository($repo->getPHID()); - if (!$have_symbols) { - $needs_symbols = false; - } - } - - if ($needs_symbols && $repo_languages) { - $needs_symbols = isset($repo_languages[$lang]); - } - - if ($needs_symbols) { - Javelin::initBehavior( - 'repository-crossreference', - array( - 'container' => $id, - 'lang' => $lang, - 'repositories' => $symbol_repos, - )); - } - - $corpus = phutil_tag( - 'div', - array( - 'id' => $id, - ), - $corpus_table); - - Javelin::initBehavior('load-blame', array('id' => $id)); - - $this->corpusButtons[] = $this->renderFileButton(); - $title = basename($this->getDiffusionRequest()->getPath()); - $icon = 'fa-file-code-o'; - $drequest = $this->getDiffusionRequest(); - $this->buildActionButtons($drequest); - - $header = $this->buildPanelHeaderView($title, $icon); - - $corpus = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->appendChild($corpus) - ->addClass('diffusion-mobile-view') - ->addSigil('diffusion-file-content-view') - ->setMetadata( - array( - 'path' => $this->getDiffusionRequest()->getPath(), - )) - ->setCollapsed(true); - - $messages = array(); - - if (!$can_highlight) { - $messages[] = pht( - 'This file is larger than %s, so syntax highlighting is disabled '. - 'by default.', - phutil_format_bytes($highlight_limit)); - } - - if (!$can_blame) { - $messages[] = pht( - 'This file is larger than %s, so blame is disabled.', - phutil_format_bytes($blame_limit)); - } - - if ($blame_failed) { - $messages[] = pht( - 'Failed to load blame information for this file in %s second(s).', - new PhutilNumber($blame_timeout)); - } - - if ($messages) { - $corpus->setInfoView( - id(new PHUIInfoView()) - ->setSeverity(PHUIInfoView::SEVERITY_WARNING) - ->setErrors($messages)); - } - - return $corpus; - } - private function buildButtonBar( DiffusionRequest $drequest, $show_editor) { @@ -868,389 +705,6 @@ final class DiffusionBrowseController extends DiffusionController { ->setColor(PHUIButtonView::GREY); } - private function buildDisplayRows( - array $lines, - array $blame_list, - array $blame_commits) { - - $request = $this->getRequest(); - $viewer = $this->getViewer(); - $drequest = $this->getDiffusionRequest(); - $repository = $drequest->getRepository(); - - $revision_map = array(); - $revisions = array(); - if ($blame_commits) { - $commit_map = mpull($blame_commits, 'getCommitIdentifier', 'getPHID'); - - $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; - } - } - - $phids = array(); - foreach ($blame_commits as $commit) { - $author_phid = $commit->getAuthorPHID(); - if ($author_phid === null) { - continue; - } - $phids[$author_phid] = $author_phid; - } - - foreach ($revisions as $revision) { - $author_phid = $revision->getAuthorPHID(); - if ($author_phid === null) { - continue; - } - $phids[$author_phid] = $author_phid; - } - - $handles = $viewer->loadHandles($phids); - - $author_phids = array(); - $author_map = array(); - foreach ($blame_commits as $commit) { - $commit_identifier = $commit->getCommitIdentifier(); - - $author_phid = ''; - if (isset($revision_map[$commit_identifier])) { - $revision_id = $revision_map[$commit_identifier]; - $revision = $revisions[$revision_id]; - $author_phid = $revision->getAuthorPHID(); - } else { - $author_phid = $commit->getAuthorPHID(); - } - - $author_map[$commit_identifier] = $author_phid; - $author_phids[$author_phid] = $author_phid; - } - - $colors = array(); - if ($blame_commits) { - $epochs = array(); - - foreach ($blame_commits as $identifier => $commit) { - $epochs[$identifier] = $commit->getEpoch(); - } - - $epoch_list = array_filter($epochs); - $epoch_list = array_unique($epoch_list); - $epoch_list = array_values($epoch_list); - - $epoch_min = min($epoch_list); - $epoch_max = max($epoch_list); - $epoch_range = ($epoch_max - $epoch_min) + 1; - - foreach ($blame_commits as $identifier => $commit) { - $epoch = $epochs[$identifier]; - if (!$epoch) { - $color = '#ffffdd'; // Warning color, missing data. - } else { - $color_ratio = ($epoch - $epoch_min) / $epoch_range; - $color_value = 0xE6 * (1.0 - $color_ratio); - $color = sprintf( - '#%02x%02x%02x', - $color_value, - 0xF6, - $color_value); - } - - $colors[$identifier] = $color; - } - } - - $display = array(); - $last_identifier = null; - $last_color = null; - foreach ($lines as $line_index => $line) { - $color = '#f6f6f6'; - $duplicate = false; - if (isset($blame_list[$line_index])) { - $identifier = $blame_list[$line_index]; - if (isset($colors[$identifier])) { - $color = $colors[$identifier]; - } - - if ($identifier === $last_identifier) { - $duplicate = true; - } else { - $last_identifier = $identifier; - } - } - - $display[$line_index] = array( - 'data' => $line, - 'target' => false, - 'highlighted' => false, - 'color' => $color, - 'duplicate' => $duplicate, - ); - } - - $line_arr = array(); - $line_str = $drequest->getLine(); - $ranges = explode(',', $line_str); - foreach ($ranges as $range) { - if (strpos($range, '-') !== false) { - list($min, $max) = explode('-', $range, 2); - $line_arr[] = array( - 'min' => min($min, $max), - 'max' => max($min, $max), - ); - } else if (strlen($range)) { - $line_arr[] = array( - 'min' => $range, - 'max' => $range, - ); - } - } - - // Mark the first highlighted line as the target line. - if ($line_arr) { - $target_line = $line_arr[0]['min']; - if (isset($display[$target_line - 1])) { - $display[$target_line - 1]['target'] = true; - } - } - - // Mark all other highlighted lines as highlighted. - foreach ($line_arr as $range) { - for ($ii = $range['min']; $ii <= $range['max']; $ii++) { - if (isset($display[$ii - 1])) { - $display[$ii - 1]['highlighted'] = true; - } - } - } - - $engine = null; - $inlines = array(); - if ($this->getRequest()->getStr('lint') !== null && $this->lintMessages) { - $engine = new PhabricatorMarkupEngine(); - $engine->setViewer($viewer); - - foreach ($this->lintMessages as $message) { - $inline = id(new PhabricatorAuditInlineComment()) - ->setSyntheticAuthor( - ArcanistLintSeverity::getStringForSeverity($message['severity']). - ' '.$message['code'].' ('.$message['name'].')') - ->setLineNumber($message['line']) - ->setContent($message['description']); - $inlines[$message['line']][] = $inline; - - $engine->addObject( - $inline, - PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY); - } - - $engine->process(); - require_celerity_resource('differential-changeset-view-css'); - } - - $rows = $this->renderInlines( - idx($inlines, 0, array()), - (bool)$this->coverage, - $engine); - - // NOTE: We're doing this manually because rendering is otherwise - // dominated by URI generation for very large files. - $line_base = $this->getLineNumberBaseURI(); - - require_celerity_resource('aphront-tooltip-css'); - Javelin::initBehavior('phabricator-oncopy'); - Javelin::initBehavior('phabricator-tooltips'); - Javelin::initBehavior('phabricator-line-linker'); - - // Render these once, since they tend to get repeated many times in large - // blame outputs. - $commit_links = $this->renderCommitLinks($blame_commits, $handles); - $revision_links = $this->renderRevisionLinks($revisions, $handles); - $author_links = $this->renderAuthorLinks($author_map, $handles); - - if ($this->coverage) { - require_celerity_resource('differential-changeset-view-css'); - Javelin::initBehavior( - 'diffusion-browse-file', - array( - 'labels' => array( - 'cov-C' => pht('Covered'), - 'cov-N' => pht('Not Covered'), - 'cov-U' => pht('Not Executable'), - ), - )); - } - - $skip_text = pht('Skip Past This Commit'); - $skip_icon = id(new PHUIIconView()) - ->setIcon('fa-backward'); - - foreach ($display as $line_index => $line) { - $row = array(); - - $line_number = $line_index + 1; - $line_href = $line_base.'$'.$line_number; - - if (isset($blame_list[$line_index])) { - $identifier = $blame_list[$line_index]; - } else { - $identifier = null; - } - - $revision_link = null; - $commit_link = null; - $author_link = null; - $before_link = null; - - $style = 'background: '.$line['color'].';'; - - if ($identifier && !$line['duplicate']) { - if (isset($commit_links[$identifier])) { - $commit_link = $commit_links[$identifier]; - $author_link = $author_links[$author_map[$identifier]]; - } - - if (isset($revision_map[$identifier])) { - $revision_id = $revision_map[$identifier]; - if (isset($revision_links[$revision_id])) { - $revision_link = $revision_links[$revision_id]; - } - } - - $skip_href = $line_href.'?before='.$identifier; - $before_link = javelin_tag( - 'a', - array( - 'href' => $skip_href, - 'sigil' => 'has-tooltip', - 'meta' => array( - 'tip' => $skip_text, - 'align' => 'E', - 'size' => 300, - ), - ), - $skip_icon); - } - - $row[] = phutil_tag( - 'th', - array( - 'class' => 'diffusion-blame-link', - ), - $before_link); - - $object_links = array(); - $object_links[] = $author_link; - $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); - - $line_link = phutil_tag( - 'a', - array( - 'href' => $line_href, - 'style' => $style, - ), - $line_number); - - $row[] = javelin_tag( - 'th', - array( - 'class' => 'diffusion-line-link', - 'sigil' => 'phabricator-source-line', - 'style' => $style, - ), - $line_link); - - if ($line['target']) { - Javelin::initBehavior( - 'diffusion-jump-to', - array( - 'target' => 'scroll_target', - )); - $anchor_text = phutil_tag( - 'a', - array( - 'id' => 'scroll_target', - ), - ''); - } else { - $anchor_text = null; - } - - $row[] = phutil_tag( - 'td', - array( - ), - array( - $anchor_text, - - // NOTE: See phabricator-oncopy behavior. - "\xE2\x80\x8B", - - // TODO: [HTML] Not ideal. - phutil_safe_html(str_replace("\t", ' ', $line['data'])), - )); - - if ($this->coverage) { - $cov_index = $line_index; - - if (isset($this->coverage[$cov_index])) { - $cov_class = $this->coverage[$cov_index]; - } else { - $cov_class = 'N'; - } - - $row[] = phutil_tag( - 'td', - array( - 'class' => 'cov cov-'.$cov_class, - ), - ''); - } - - $rows[] = phutil_tag( - 'tr', - array( - 'class' => ($line['highlighted'] ? - 'phabricator-source-highlight' : - null), - ), - $row); - - $cur_inlines = $this->renderInlines( - idx($inlines, $line_number, array()), - $this->coverage, - $engine); - foreach ($cur_inlines as $cur_inline) { - $rows[] = $cur_inline; - } - } - - return $rows; - } - private function renderInlines( array $inlines, $has_coverage, @@ -1285,53 +739,6 @@ final class DiffusionBrowseController extends DiffusionController { return $rows; } - private function buildImageCorpus($file_uri) { - $properties = new PHUIPropertyListView(); - - $properties->addImageContent( - phutil_tag( - 'img', - array( - 'src' => $file_uri, - ))); - - $this->corpusButtons[] = $this->renderFileButton($file_uri); - $title = basename($this->getDiffusionRequest()->getPath()); - $icon = 'fa-file-image-o'; - $drequest = $this->getDiffusionRequest(); - $this->buildActionButtons($drequest); - $header = $this->buildPanelHeaderView($title, $icon); - - return id(new PHUIObjectBoxView()) - ->setHeader($header) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->addClass('diffusion-mobile-view') - ->addPropertyList($properties); - } - - private function buildBinaryCorpus($file_uri, $data) { - $size = new PhutilNumber(strlen($data)); - $text = pht('This is a binary file. It is %s byte(s) in length.', $size); - $text = id(new PHUIBoxView()) - ->addPadding(PHUI::PADDING_LARGE) - ->appendChild($text); - - $this->corpusButtons[] = $this->renderFileButton($file_uri); - $title = basename($this->getDiffusionRequest()->getPath()); - $icon = 'fa-file'; - $drequest = $this->getDiffusionRequest(); - $this->buildActionButtons($drequest); - $header = $this->buildPanelHeaderView($title, $icon); - - $box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->addClass('diffusion-mobile-view') - ->appendChild($text); - - return $box; - } - private function buildErrorCorpus($message) { $text = id(new PHUIBoxView()) ->addPadding(PHUI::PADDING_LARGE) diff --git a/src/applications/diffusion/controller/DiffusionDocumentController.php b/src/applications/diffusion/controller/DiffusionDocumentController.php new file mode 100644 index 0000000000..c8956fc84c --- /dev/null +++ b/src/applications/diffusion/controller/DiffusionDocumentController.php @@ -0,0 +1,45 @@ +loadDiffusionContext(); + if ($response) { + return $response; + } + + $drequest = $this->getDiffusionRequest(); + + $engine = id(new DiffusionDocumentRenderingEngine()) + ->setRequest($request) + ->setDiffusionRequest($drequest) + ->setController($this); + + $viewer = $this->getViewer(); + $request = $this->getRequest(); + $repository = $drequest->getRepository(); + + $file_phid = $request->getStr('filePHID'); + + $file = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($file_phid)) + ->executeOne(); + if (!$file) { + return $engine->newErrorResponse( + pht( + 'This file ("%s") does not exist or could not be loaded.', + $file_phid)); + } + + $ref = id(new PhabricatorDocumentRef()) + ->setFile($file); + + return $engine->newRenderResponse($ref); + } + +} diff --git a/src/applications/diffusion/document/DiffusionDocumentRenderingEngine.php b/src/applications/diffusion/document/DiffusionDocumentRenderingEngine.php new file mode 100644 index 0000000000..d0b5a4b33a --- /dev/null +++ b/src/applications/diffusion/document/DiffusionDocumentRenderingEngine.php @@ -0,0 +1,67 @@ +diffusionRequest = $drequest; + return $this; + } + + public function getDiffusionRequest() { + return $this->diffusionRequest; + } + + protected function getSelectedDocumentEngineKey() { + return $this->getRequest()->getStr('as'); + } + + protected function newRefViewURI( + PhabricatorDocumentRef $ref, + PhabricatorDocumentEngine $engine) { + + $file = $ref->getFile(); + $engine_key = $engine->getDocumentEngineKey(); + $drequest = $this->getDiffusionRequest(); + + return (string)$drequest->generateURI( + array( + 'action' => 'browse', + 'stable' => true, + 'params' => array( + 'as' => $engine_key, + ), + )); + } + + protected function newRefRenderURI( + PhabricatorDocumentRef $ref, + PhabricatorDocumentEngine $engine) { + + $engine_key = $engine->getDocumentEngineKey(); + + $file = $ref->getFile(); + $file_phid = $file->getPHID(); + + $drequest = $this->getDiffusionRequest(); + + return (string)$drequest->generateURI( + array( + 'action' => 'document', + 'stable' => true, + 'params' => array( + 'as' => $engine_key, + 'filePHID' => $file_phid, + ), + )); + } + + protected function addApplicationCrumbs( + PHUICrumbsView $crumbs, + PhabricatorDocumentRef $ref = null) { + return; + } + +} diff --git a/src/applications/files/controller/PhabricatorFileDocumentController.php b/src/applications/files/controller/PhabricatorFileDocumentController.php index e74b0bc6eb..645b0e8e92 100644 --- a/src/applications/files/controller/PhabricatorFileDocumentController.php +++ b/src/applications/files/controller/PhabricatorFileDocumentController.php @@ -8,6 +8,10 @@ final class PhabricatorFileDocumentController } public function handleRequest(AphrontRequest $request) { + $engine = id(new PhabricatorFileDocumentRenderingEngine()) + ->setRequest($request) + ->setController($this); + $viewer = $request->getViewer(); $file_phid = $request->getURIData('phid'); @@ -17,7 +21,7 @@ final class PhabricatorFileDocumentController ->withPHIDs(array($file_phid)) ->executeOne(); if (!$file) { - return $this->newErrorResponse( + return $engine->newErrorResponse( pht( 'This file ("%s") does not exist or could not be loaded.', $file_phid)); @@ -26,10 +30,7 @@ final class PhabricatorFileDocumentController $ref = id(new PhabricatorDocumentRef()) ->setFile($file); - return id(new PhabricatorFileDocumentRenderingEngine()) - ->setRequest($request) - ->setController($this) - ->newRenderResponse($ref); + return $engine->newRenderResponse($ref); } } diff --git a/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php b/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php index 37b0917a65..fb6fe67e62 100644 --- a/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php +++ b/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php @@ -6,6 +6,7 @@ abstract class PhabricatorDocumentRenderingEngine private $request; private $controller; private $activeEngine; + private $ref; final public function setRequest(AphrontRequest $request) { $this->request = $request; @@ -34,13 +35,13 @@ abstract class PhabricatorDocumentRenderingEngine } final protected function getActiveEngine() { - if (!$this->activeEngine) { - throw new PhutilInvalidStateException('setActiveEngine'); - } - return $this->activeEngine; } + final protected function getRef() { + return $this->ref; + } + final public function newDocumentView(PhabricatorDocumentRef $ref) { $request = $this->getRequest(); $viewer = $request->getViewer(); @@ -173,10 +174,9 @@ abstract class PhabricatorDocumentRenderingEngine $viewer = $request->getViewer(); $engines = PhabricatorDocumentEngine::getEnginesForRef($viewer, $ref); - $engine_key = $request->getURIData('engineKey'); + $engine_key = $this->getSelectedDocumentEngineKey(); if (!isset($engines[$engine_key])) { return $this->newErrorResponse( - $ref, pht( 'The engine ("%s") is unknown, or unable to render this document.', $engine_key)); @@ -198,16 +198,13 @@ abstract class PhabricatorDocumentRenderingEngine try { $content = $engine->newDocument($ref); } catch (Exception $ex) { - return $this->newErrorResponse($ref, $ex->getMessage()); + return $this->newErrorResponse($ex->getMessage()); } - return $this->newContentResponse($ref, $content); + return $this->newContentResponse($content); } - private function newErrorResponse( - PhabricatorDocumentRef $ref, - $message) { - + public function newErrorResponse($message) { $container = phutil_tag( 'div', array( @@ -220,13 +217,10 @@ abstract class PhabricatorDocumentRenderingEngine $message, )); - return $this->newContentResponse($ref, $container); + return $this->newContentResponse($container); } - private function newContentResponse( - PhabricatorDocumentRef $ref, - $content) { - + private function newContentResponse($content) { $request = $this->getRequest(); $viewer = $request->getViewer(); $controller = $this->getController(); @@ -239,10 +233,8 @@ abstract class PhabricatorDocumentRenderingEngine )); } - $crumbs = $this->newCrumbs($ref); - if ($crumbs) { - $crumbs->setBorder(true); - } + $crumbs = $this->newCrumbs(); + $crumbs->setBorder(true); $content_frame = id(new PHUIObjectBoxView()) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) @@ -251,34 +243,46 @@ abstract class PhabricatorDocumentRenderingEngine $page_frame = id(new PHUITwoColumnView()) ->setFooter($content_frame); + $title = array(); + $ref = $this->getRef(); + if ($ref) { + $title = array( + $ref->getName(), + pht('Standalone'), + ); + } else { + $title = pht('Document'); + } + return $controller->newPage() ->setCrumbs($crumbs) - ->setTitle( - array( - $ref->getName(), - pht('Standalone'), - )) + ->setTitle($title) ->appendChild($page_frame); } - protected function newCrumbs(PhabricatorDocumentRef $ref) { + protected function newCrumbs() { + $engine = $this->getActiveEngine(); $controller = $this->getController(); + $crumbs = $controller->buildApplicationCrumbsForEditEngine(); - $this->addApplicationCrumbs($ref, $crumbs); + $ref = $this->getRef(); - $engine = $this->getActiveEngine(); - $label = $engine->getViewAsLabel($ref); - if ($label) { - $crumbs->addTextCrumb($label); + $this->addApplicationCrumbs($crumbs, $ref); + + if ($ref) { + $label = $engine->getViewAsLabel($ref); + if ($label) { + $crumbs->addTextCrumb($label); + } } return $crumbs; } protected function addApplicationCrumbs( - PhabricatorDocumentRef $ref, - PHUICrumbsView $crumbs) { + PHUICrumbsView $crumbs, + PhabricatorDocumentRef $ref = null) { return; } diff --git a/src/applications/files/document/render/PhabricatorFileDocumentRenderingEngine.php b/src/applications/files/document/render/PhabricatorFileDocumentRenderingEngine.php index f48336bea3..65e9a49681 100644 --- a/src/applications/files/document/render/PhabricatorFileDocumentRenderingEngine.php +++ b/src/applications/files/document/render/PhabricatorFileDocumentRenderingEngine.php @@ -34,12 +34,14 @@ final class PhabricatorFileDocumentRenderingEngine } protected function addApplicationCrumbs( - PhabricatorDocumentRef $ref, - PHUICrumbsView $crumbs) { + PHUICrumbsView $crumbs, + PhabricatorDocumentRef $ref = null) { - $file = $ref->getFile(); - if ($file) { - $crumbs->addTextCrumb($file->getMonogram(), $file->getInfoURI()); + if ($ref) { + $file = $ref->getFile(); + if ($file) { + $crumbs->addTextCrumb($file->getMonogram(), $file->getInfoURI()); + } } } diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index 435e5749e6..1dfbceead0 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -704,6 +704,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO case 'graph': case 'clone': case 'browse': + case 'document': case 'change': case 'lastmodified': case 'tags': @@ -782,6 +783,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO case 'history': case 'graph': case 'browse': + case 'document': case 'lastmodified': case 'tags': case 'branches': From 6dea2ba3b37f0590efb607ea5e27c623ba19bca7 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 8 Apr 2018 06:29:29 -0700 Subject: [PATCH 07/53] Fix DocumentEngine line behaviors in Diffusion Summary: Ref T13105. Fixes some issues with line linking and highlighting under DocumentEngine: - Adding `$1-3` to the URI didn't work correctly with query parameters. - Reading `$1-3` from the URI didn't work correctly because Diffusion parses them slightly abnormally. Test Plan: Clicked/dragged lines to select them. Observed URI. Reloaded page, got the right selection. Reviewers: mydeveloperday Reviewed By: mydeveloperday Maniphest Tasks: T13105 Differential Revision: https://secure.phabricator.com/D19305 --- resources/celerity/map.php | 16 +++++----- src/aphront/AphrontRequest.php | 4 +++ .../DiffusionDocumentRenderingEngine.php | 13 +++++--- .../PhabricatorDocumentRenderingEngine.php | 30 +++++++++++-------- webroot/rsrc/js/core/behavior-line-linker.js | 13 ++++++-- 5 files changed, 49 insertions(+), 27 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 79e645a927..ca75f4ee09 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -473,7 +473,7 @@ return array( 'rsrc/js/core/behavior-keyboard-pager.js' => 'a8da01f0', 'rsrc/js/core/behavior-keyboard-shortcuts.js' => '01fca1f0', 'rsrc/js/core/behavior-lightbox-attachments.js' => '6b31879a', - 'rsrc/js/core/behavior-line-linker.js' => '13e39479', + 'rsrc/js/core/behavior-line-linker.js' => 'febf4ae7', 'rsrc/js/core/behavior-more.js' => 'a80d0378', 'rsrc/js/core/behavior-object-selector.js' => '77c1f0b0', 'rsrc/js/core/behavior-oncopy.js' => '2926fff2', @@ -638,7 +638,7 @@ return array( 'javelin-behavior-phabricator-gesture-example' => '558829c2', 'javelin-behavior-phabricator-keyboard-pager' => 'a8da01f0', 'javelin-behavior-phabricator-keyboard-shortcuts' => '01fca1f0', - 'javelin-behavior-phabricator-line-linker' => '13e39479', + 'javelin-behavior-phabricator-line-linker' => 'febf4ae7', 'javelin-behavior-phabricator-nav' => '836f966d', 'javelin-behavior-phabricator-notification-example' => '8ce821c5', 'javelin-behavior-phabricator-object-selector' => '77c1f0b0', @@ -964,12 +964,6 @@ return array( 'javelin-install', 'javelin-util', ), - '13e39479' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-history', - ), '15d5ff71' => array( 'aphront-typeahead-control-css', 'phui-tag-view-css', @@ -2174,6 +2168,12 @@ return array( 'javelin-view-visitor', 'javelin-util', ), + 'febf4ae7' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-history', + ), ), 'packages' => array( 'conpherence.pkg.css' => array( diff --git a/src/aphront/AphrontRequest.php b/src/aphront/AphrontRequest.php index c1dc0d1305..78e6dac9d3 100644 --- a/src/aphront/AphrontRequest.php +++ b/src/aphront/AphrontRequest.php @@ -61,6 +61,10 @@ final class AphrontRequest extends Phobject { */ public function getURILineRange($key, $limit) { $range = $this->getURIData($key); + return self::parseURILineRange($range, $limit); + } + + public static function parseURILineRange($range, $limit) { if (!strlen($range)) { return null; } diff --git a/src/applications/diffusion/document/DiffusionDocumentRenderingEngine.php b/src/applications/diffusion/document/DiffusionDocumentRenderingEngine.php index d0b5a4b33a..b3b77c9301 100644 --- a/src/applications/diffusion/document/DiffusionDocumentRenderingEngine.php +++ b/src/applications/diffusion/document/DiffusionDocumentRenderingEngine.php @@ -14,10 +14,6 @@ final class DiffusionDocumentRenderingEngine return $this->diffusionRequest; } - protected function getSelectedDocumentEngineKey() { - return $this->getRequest()->getStr('as'); - } - protected function newRefViewURI( PhabricatorDocumentRef $ref, PhabricatorDocumentEngine $engine) { @@ -58,6 +54,15 @@ final class DiffusionDocumentRenderingEngine )); } + protected function getSelectedDocumentEngineKey() { + return $this->getRequest()->getStr('as'); + } + + protected function getSelectedLineRange() { + $range = $this->getDiffusionRequest()->getLine(); + return AphrontRequest::parseURILineRange($range, 1000); + } + protected function addApplicationCrumbs( PHUICrumbsView $crumbs, PhabricatorDocumentRef $ref = null) { diff --git a/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php b/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php index fb6fe67e62..d95d93f74a 100644 --- a/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php +++ b/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php @@ -54,7 +54,7 @@ abstract class PhabricatorDocumentRenderingEngine } $engine = $engines[$engine_key]; - $lines = $request->getURILineRange('lines', 1000); + $lines = $this->getSelectedLineRange(); if ($lines) { $engine->setHighlightedLines(range($lines[0], $lines[1])); } @@ -157,18 +157,6 @@ abstract class PhabricatorDocumentRenderingEngine ->appendChild($viewport); } - abstract protected function newRefViewURI( - PhabricatorDocumentRef $ref, - PhabricatorDocumentEngine $engine); - - abstract protected function newRefRenderURI( - PhabricatorDocumentRef $ref, - PhabricatorDocumentEngine $engine); - - protected function getSelectedDocumentEngineKey() { - return $this->getRequest()->getURIData('engineKey'); - } - final public function newRenderResponse(PhabricatorDocumentRef $ref) { $request = $this->getRequest(); $viewer = $request->getViewer(); @@ -280,6 +268,22 @@ abstract class PhabricatorDocumentRenderingEngine return $crumbs; } + abstract protected function newRefViewURI( + PhabricatorDocumentRef $ref, + PhabricatorDocumentEngine $engine); + + abstract protected function newRefRenderURI( + PhabricatorDocumentRef $ref, + PhabricatorDocumentEngine $engine); + + protected function getSelectedDocumentEngineKey() { + return $this->getRequest()->getURIData('engineKey'); + } + + protected function getSelectedLineRange() { + return $this->getRequest()->getURILineRange('lines', 1000); + } + protected function addApplicationCrumbs( PHUICrumbsView $crumbs, PhabricatorDocumentRef $ref = null) { diff --git a/webroot/rsrc/js/core/behavior-line-linker.js b/webroot/rsrc/js/core/behavior-line-linker.js index 8a68cec842..6130bdd859 100644 --- a/webroot/rsrc/js/core/behavior-line-linker.js +++ b/webroot/rsrc/js/core/behavior-line-linker.js @@ -144,9 +144,14 @@ JX.behavior('phabricator-line-linker', function() { var o = getRowNumber(origin); var t = getRowNumber(target); var uri = JX.Stratcom.getData(root).uri; + var path; if (!uri) { - uri = ('' + window.location).split('$')[0]; + uri = JX.$U(window.location); + path = uri.getPath(); + path = path.replace(/\$[\d-]+$/, ''); + uri.setPath(path); + uri = uri.toString(); } origin = null; @@ -154,7 +159,11 @@ JX.behavior('phabricator-line-linker', function() { root = null; var lines = (o == t ? o : Math.min(o, t) + '-' + Math.max(o, t)); - uri = uri + '$' + lines; + + uri = JX.$U(uri); + path = uri.getPath(); + path = path + '$' + lines; + uri = uri.setPath(path).toString(); JX.History.replace(uri); From 0363febeb2c6990e0d06c2f0772ca4f8370228aa Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 8 Apr 2018 08:12:07 -0700 Subject: [PATCH 08/53] Disable default syntax highlighting for large files in DocumentEngine Summary: Ref T13105. See also T7895. When users render very large files as source via DocumentEngine, skip highlighting. Test Plan: Fiddled with the limit, viewed files, saw highlighting degrade. Reviewers: mydeveloperday Reviewed By: mydeveloperday Maniphest Tasks: T13105 Differential Revision: https://secure.phabricator.com/D19306 --- .../PhabricatorSourceDocumentEngine.php | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/applications/files/document/PhabricatorSourceDocumentEngine.php b/src/applications/files/document/PhabricatorSourceDocumentEngine.php index cd7c2af92b..5a4b7f0be7 100644 --- a/src/applications/files/document/PhabricatorSourceDocumentEngine.php +++ b/src/applications/files/document/PhabricatorSourceDocumentEngine.php @@ -24,18 +24,31 @@ final class PhabricatorSourceDocumentEngine protected function newDocumentContent(PhabricatorDocumentRef $ref) { $content = $this->loadTextData($ref); + $messages = array(); + $highlighting = $this->getHighlightingConfiguration(); if ($highlighting !== null) { $content = PhabricatorSyntaxHighlighter::highlightWithLanguage( $highlighting, $content); } else { - $content = PhabricatorSyntaxHighlighter::highlightWithFilename( - $ref->getName(), - $content); + $highlight_limit = DifferentialChangesetParser::HIGHLIGHT_BYTE_LIMIT; + if (strlen($content) > $highlight_limit) { + $messages[] = $this->newMessage( + pht( + 'This file is larger than %s, so syntax highlighting was skipped.', + phutil_format_bytes($highlight_limit))); + } else { + $content = PhabricatorSyntaxHighlighter::highlightWithFilename( + $ref->getName(), + $content); + } } - return $this->newTextDocumentContent($content); + return array( + $messages, + $this->newTextDocumentContent($content), + ); } } From 90a614778c0768e18084744e007eeccd7d7f4c8c Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 8 Apr 2018 09:10:03 -0700 Subject: [PATCH 09/53] Make repository symbol references work with DocumentEngine Summary: Ref T13105. Ref T13047. This makes symbol indexes work with DocumentEngine in Files, and restores support in Diffusion. Test Plan: Command-clicked stuff, got taken to the symbol index with reasonable metadata in Diffusion, Differential and Files. Reviewers: mydeveloperday Reviewed By: mydeveloperday Maniphest Tasks: T13105, T13047 Differential Revision: https://secure.phabricator.com/D19307 --- resources/celerity/map.php | 34 ++-- .../DiffusionDocumentRenderingEngine.php | 20 +++ .../files/document/PhabricatorDocumentRef.php | 12 ++ .../PhabricatorJSONDocumentEngine.php | 2 +- .../PhabricatorSourceDocumentEngine.php | 2 +- .../PhabricatorTextDocumentEngine.php | 7 +- .../PhabricatorDocumentRenderingEngine.php | 7 + src/view/layout/PhabricatorSourceCodeView.php | 21 ++- webroot/rsrc/css/core/syntax.css | 13 +- .../layout/phabricator-source-code-view.css | 1 - .../repository/repository-crossreference.js | 150 ++++++++++++++---- 11 files changed, 201 insertions(+), 68 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index ca75f4ee09..b4381dea5c 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,10 +9,10 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'e68cf1fa', 'conpherence.pkg.js' => '15191c65', - 'core.pkg.css' => '29452b31', + 'core.pkg.css' => '4a83e174', 'core.pkg.js' => '1ea38af8', 'differential.pkg.css' => '113e692c', - 'differential.pkg.js' => 'f6d809c0', + 'differential.pkg.js' => '3da2650a', 'diffusion.pkg.css' => 'a2d17c7d', 'diffusion.pkg.js' => '6134c5a1', 'maniphest.pkg.css' => '4845691a', @@ -113,14 +113,14 @@ return array( 'rsrc/css/application/uiexample/example.css' => '528b19de', 'rsrc/css/core/core.css' => '62fa3ace', 'rsrc/css/core/remarkup.css' => '924fc97d', - 'rsrc/css/core/syntax.css' => 'cae95e89', + 'rsrc/css/core/syntax.css' => 'e9c95dd4', 'rsrc/css/core/z-index.css' => '9d8f7c4b', 'rsrc/css/diviner/diviner-shared.css' => '896f1d43', 'rsrc/css/font/font-awesome.css' => 'e838e088', 'rsrc/css/font/font-lato.css' => 'c7ccd872', 'rsrc/css/font/phui-font-icon-base.css' => '870a7360', 'rsrc/css/layout/phabricator-filetree-view.css' => 'b912ad97', - 'rsrc/css/layout/phabricator-source-code-view.css' => '31ee3c83', + 'rsrc/css/layout/phabricator-source-code-view.css' => 'c6fc6834', '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' => '1863cc6e', @@ -423,7 +423,7 @@ return array( 'rsrc/js/application/releeph/releeph-preview-branch.js' => 'b2b4fbaf', 'rsrc/js/application/releeph/releeph-request-state-change.js' => 'a0b57eb8', 'rsrc/js/application/releeph/releeph-request-typeahead.js' => 'de2e896f', - 'rsrc/js/application/repository/repository-crossreference.js' => '2ab10a76', + 'rsrc/js/application/repository/repository-crossreference.js' => '9a860428', 'rsrc/js/application/search/behavior-reorder-profile-menu-items.js' => 'e2e0a072', 'rsrc/js/application/search/behavior-reorder-queries.js' => 'e9581f08', 'rsrc/js/application/slowvote/behavior-slowvote-embed.js' => '887ad43f', @@ -676,7 +676,7 @@ return array( 'javelin-behavior-reorder-applications' => '76b9fc3e', 'javelin-behavior-reorder-columns' => 'e1d25dfb', 'javelin-behavior-reorder-profile-menu-items' => 'e2e0a072', - 'javelin-behavior-repository-crossreference' => '2ab10a76', + 'javelin-behavior-repository-crossreference' => '9a860428', 'javelin-behavior-scrollbar' => '834a1173', 'javelin-behavior-search-reorder-queries' => 'e9581f08', 'javelin-behavior-select-content' => 'bf5374ef', @@ -784,7 +784,7 @@ return array( 'phabricator-search-results-css' => '505dd8cf', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-slowvote-css' => 'a94b7230', - 'phabricator-source-code-view-css' => '31ee3c83', + 'phabricator-source-code-view-css' => 'c6fc6834', 'phabricator-standard-page-view' => '34ee718b', 'phabricator-textareautils' => '320810c8', 'phabricator-title' => '485aaa6c', @@ -884,7 +884,7 @@ return array( 'sprite-login-css' => '396f3c3a', 'sprite-tokens-css' => '9cdfd599', 'syntax-default-css' => '9923583c', - 'syntax-highlighting-css' => 'cae95e89', + 'syntax-highlighting-css' => 'e9c95dd4', 'tokens-css' => '3d0f239e', 'typeahead-browse-css' => 'f2818435', 'unhandled-exception-css' => '4c96257a', @@ -1050,12 +1050,6 @@ return array( 'javelin-install', 'javelin-util', ), - '2ab10a76' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-uri', - ), '2ae077e1' => array( 'javelin-behavior', 'javelin-dom', @@ -1683,6 +1677,12 @@ return array( 'phuix-icon-view', 'javelin-behavior-phabricator-gesture', ), + '9a860428' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-uri', + ), '9bbf3762' => array( 'javelin-behavior', 'javelin-dom', @@ -1972,9 +1972,6 @@ return array( 'phabricator-title', 'phabricator-favicon', ), - 'cae95e89' => array( - 'syntax-default-css', - ), 'cd2b9b77' => array( 'phui-oi-list-view-css', ), @@ -2103,6 +2100,9 @@ return array( 'javelin-dom', 'phabricator-draggable-list', ), + 'e9c95dd4' => array( + 'syntax-default-css', + ), 'ec1f3669' => array( 'javelin-behavior', 'javelin-util', diff --git a/src/applications/diffusion/document/DiffusionDocumentRenderingEngine.php b/src/applications/diffusion/document/DiffusionDocumentRenderingEngine.php index b3b77c9301..2ca23473ca 100644 --- a/src/applications/diffusion/document/DiffusionDocumentRenderingEngine.php +++ b/src/applications/diffusion/document/DiffusionDocumentRenderingEngine.php @@ -69,4 +69,24 @@ final class DiffusionDocumentRenderingEngine return; } + protected function willRenderRef(PhabricatorDocumentRef $ref) { + $ref->setSymbolMetadata($this->getSymbolMetadata()); + } + + private function getSymbolMetadata() { + $drequest = $this->getDiffusionRequest(); + + $repo = $drequest->getRepository(); + $symbol_repos = nonempty($repo->getSymbolSources(), array()); + $symbol_repos[] = $repo->getPHID(); + + $lang = last(explode('.', $drequest->getPath())); + + return array( + 'repositories' => $symbol_repos, + 'lang' => $lang, + 'path' => $drequest->getPath(), + ); + } + } diff --git a/src/applications/files/document/PhabricatorDocumentRef.php b/src/applications/files/document/PhabricatorDocumentRef.php index cca0c102e2..50230cc91d 100644 --- a/src/applications/files/document/PhabricatorDocumentRef.php +++ b/src/applications/files/document/PhabricatorDocumentRef.php @@ -8,6 +8,7 @@ final class PhabricatorDocumentRef private $file; private $byteLength; private $snippet; + private $symbolMetadata = array(); public function setFile(PhabricatorFile $file) { $this->file = $file; @@ -131,4 +132,15 @@ final class PhabricatorDocumentRef return $this->snippet; } + public function setSymbolMetadata(array $metadata) { + $this->symbolMetadata = $metadata; + return $this; + } + + public function getSymbolMetadata() { + return $this->symbolMetadata; + } + + + } diff --git a/src/applications/files/document/PhabricatorJSONDocumentEngine.php b/src/applications/files/document/PhabricatorJSONDocumentEngine.php index 331a7e6820..683f1746e6 100644 --- a/src/applications/files/document/PhabricatorJSONDocumentEngine.php +++ b/src/applications/files/document/PhabricatorJSONDocumentEngine.php @@ -52,7 +52,7 @@ final class PhabricatorJSONDocumentEngine return array( $message, - $this->newTextDocumentContent($content), + $this->newTextDocumentContent($ref, $content), ); } diff --git a/src/applications/files/document/PhabricatorSourceDocumentEngine.php b/src/applications/files/document/PhabricatorSourceDocumentEngine.php index 5a4b7f0be7..21898ef636 100644 --- a/src/applications/files/document/PhabricatorSourceDocumentEngine.php +++ b/src/applications/files/document/PhabricatorSourceDocumentEngine.php @@ -47,7 +47,7 @@ final class PhabricatorSourceDocumentEngine return array( $messages, - $this->newTextDocumentContent($content), + $this->newTextDocumentContent($ref, $content), ); } diff --git a/src/applications/files/document/PhabricatorTextDocumentEngine.php b/src/applications/files/document/PhabricatorTextDocumentEngine.php index 08a5cabe54..c53b9edd58 100644 --- a/src/applications/files/document/PhabricatorTextDocumentEngine.php +++ b/src/applications/files/document/PhabricatorTextDocumentEngine.php @@ -13,12 +13,15 @@ abstract class PhabricatorTextDocumentEngine return true; } - protected function newTextDocumentContent($content) { + protected function newTextDocumentContent( + PhabricatorDocumentRef $ref, + $content) { $lines = phutil_split_lines($content); $view = id(new PhabricatorSourceCodeView()) ->setHighlights($this->getHighlightedLines()) - ->setLines($lines); + ->setLines($lines) + ->setSymbolMetadata($ref->getSymbolMetadata()); $message = null; if ($this->encodingMessage !== null) { diff --git a/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php b/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php index d95d93f74a..f412cb2869 100644 --- a/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php +++ b/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php @@ -105,6 +105,7 @@ abstract class PhabricatorDocumentRenderingEngine 'renderControlID' => $control_id, ); } else { + $this->willRenderRef($ref); $content = $engine->newDocument($ref); $config = array(); } @@ -158,6 +159,8 @@ abstract class PhabricatorDocumentRenderingEngine } final public function newRenderResponse(PhabricatorDocumentRef $ref) { + $this->willRenderRef($ref); + $request = $this->getRequest(); $viewer = $request->getViewer(); @@ -290,4 +293,8 @@ abstract class PhabricatorDocumentRenderingEngine return; } + protected function willRenderRef(PhabricatorDocumentRef $ref) { + return; + } + } diff --git a/src/view/layout/PhabricatorSourceCodeView.php b/src/view/layout/PhabricatorSourceCodeView.php index ce26fa3df7..6a51e1843d 100644 --- a/src/view/layout/PhabricatorSourceCodeView.php +++ b/src/view/layout/PhabricatorSourceCodeView.php @@ -8,6 +8,7 @@ final class PhabricatorSourceCodeView extends AphrontView { private $canClickHighlight = true; private $truncatedFirstBytes = false; private $truncatedFirstLines = false; + private $symbolMetadata; public function setLines(array $lines) { $this->lines = $lines; @@ -39,6 +40,15 @@ final class PhabricatorSourceCodeView extends AphrontView { return $this; } + public function setSymbolMetadata(array $symbol_metadata) { + $this->symbolMetadata = $symbol_metadata; + return $this; + } + + public function getSymbolMetadata() { + return $this->symbolMetadata; + } + public function render() { require_celerity_resource('phabricator-source-code-view-css'); require_celerity_resource('syntax-highlighting-css'); @@ -130,15 +140,24 @@ final class PhabricatorSourceCodeView extends AphrontView { $classes[] = 'remarkup-code'; $classes[] = 'PhabricatorMonospaced'; + $symbol_metadata = $this->getSymbolMetadata(); + + $sigils = array(); + $sigils[] = 'phabricator-source'; + $sigils[] = 'has-symbols'; + + Javelin::initBehavior('repository-crossreference'); + return phutil_tag_div( 'phabricator-source-code-container', javelin_tag( 'table', array( 'class' => implode(' ', $classes), - 'sigil' => 'phabricator-source', + 'sigil' => implode(' ', $sigils), 'meta' => array( 'uri' => (string)$this->uri, + 'symbols' => $symbol_metadata, ), ), phutil_implode_html('', $rows))); diff --git a/webroot/rsrc/css/core/syntax.css b/webroot/rsrc/css/core/syntax.css index a0b84ea2b3..cfc82da09b 100644 --- a/webroot/rsrc/css/core/syntax.css +++ b/webroot/rsrc/css/core/syntax.css @@ -6,11 +6,6 @@ color: #aa0066; } -.remarkup-code .over-the-line { - color: #aa0066; - margin-right: 1px; -} - .remarkup-code td > span { display: inline; word-break: break-all; @@ -24,11 +19,9 @@ .remarkup-code .rbw_i { color: indigo; } .remarkup-code .rbw_v { color: violet; } -.repository-crossreference .remarkup-code .crossreference-item { - background: lightyellow; - border-bottom: 1px dotted #bbddbb; -} -.crossreference-cursor { +span.crossreference-item { + background: {$lightyellow}; + border-bottom: 1px solid {$yellow}; cursor: help; } diff --git a/webroot/rsrc/css/layout/phabricator-source-code-view.css b/webroot/rsrc/css/layout/phabricator-source-code-view.css index 1836d321a5..3c6e85f71e 100644 --- a/webroot/rsrc/css/layout/phabricator-source-code-view.css +++ b/webroot/rsrc/css/layout/phabricator-source-code-view.css @@ -7,7 +7,6 @@ overflow-y: hidden; border: 1px solid {$paste.border}; border-radius: 3px; - background-color: {$paste.content}; } .phui-oi .phabricator-source-code-container { diff --git a/webroot/rsrc/js/application/repository/repository-crossreference.js b/webroot/rsrc/js/application/repository/repository-crossreference.js index 5f2f8306bf..548ef6173b 100644 --- a/webroot/rsrc/js/application/repository/repository-crossreference.js +++ b/webroot/rsrc/js/application/repository/repository-crossreference.js @@ -11,12 +11,29 @@ JX.behavior('repository-crossreference', function(config, statics) { var highlighted; var linked = []; - var isMac = navigator.platform.indexOf('Mac') > -1; - var signalKey = isMac ? 91 /*COMMAND*/ : 17 /*CTRL*/; - function isSignalkey(event) { - return isMac ? - event.getRawEvent().metaKey : - event.getRawEvent().ctrlKey; + function isMacOS() { + return (navigator.platform.indexOf('Mac') > -1); + } + + function isHighlightModifierKey(e) { + var signal_key; + if (isMacOS()) { + // On macOS, use the "Command" key. + signal_key = 91; + } else { + // On other platforms, use the "Control" key. + signal_key = 17; + } + + return (e.getRawEvent().keyCode === signal_key); + } + + function hasHighlightModifierKey(e) { + if (isMacOS()) { + return e.getRawEvent().metaKey; + } else { + return e.getRawEvent().ctrlKey; + } } var classHighlight = 'crossreference-item'; @@ -43,7 +60,7 @@ JX.behavior('repository-crossreference', function(config, statics) { unhighlight(); return; } - if (!isSignalkey(e)) { + if (!hasHighlightModifierKey(e)) { return; } @@ -76,7 +93,7 @@ JX.behavior('repository-crossreference', function(config, statics) { target = target.parentNode; } } else if (e.getType() === 'click') { - openSearch(target, lang); + openSearch(target, {lang: lang}); } }); } @@ -85,13 +102,24 @@ JX.behavior('repository-crossreference', function(config, statics) { highlighted = null; } - function openSearch(target, lang) { + function openSearch(target, context) { var symbol = target.textContent || target.innerText; - var query = { - lang : lang, - repositories : config.repositories.join(','), - jump : true - }; + + context = context || {}; + context.lang = context.lang || null; + context.repositories = + context.repositories || + (config && config.repositories) || + []; + + var query = JX.copy({}, context); + if (query.repositories.length) { + query.repositories = query.repositories.join(','); + } else { + delete query.repositories; + } + query.jump = true; + var c = target.className; c = c.replace(classHighlight, '').trim(); @@ -112,9 +140,11 @@ JX.behavior('repository-crossreference', function(config, statics) { query.line = line; } - var path = getPath(target); - if (path !== null) { - query.path = path; + if (!query.hasOwnProperty('path')) { + var path = getPath(target); + if (path !== null) { + query.path = path; + } } var char = getChar(target); @@ -124,7 +154,8 @@ JX.behavior('repository-crossreference', function(config, statics) { var uri = JX.$U('/diffusion/symbol/' + symbol + '/'); uri.addQueryParams(query); - window.open(uri); + + window.open(uri.toString()); } function linkAll() { @@ -188,16 +219,6 @@ JX.behavior('repository-crossreference', function(config, statics) { // Ignore. } - // This method works in Diffusion, when viewing the content of a file at - // a particular commit. - var file; - try { - file = JX.DOM.findAbove(target, 'div', 'diffusion-file-content-view'); - return JX.Stratcom.getData(file).path; - } catch (ex) { - // Ignore. - } - return null; } @@ -227,12 +248,6 @@ JX.behavior('repository-crossreference', function(config, statics) { return null; } - if (config.container) { - link(JX.$(config.container), config.lang); - } else if (config.section) { - linkAll(JX.$(config.section)); - } - JX.Stratcom.listen( 'differential-preview-update', null, @@ -245,9 +260,10 @@ JX.behavior('repository-crossreference', function(config, statics) { ['keydown', 'keyup'], null, function(e) { - if (e.getRawEvent().keyCode !== signalKey) { + if (!isHighlightModifierKey(e)) { return; } + setCursorMode(e.getType() === 'keydown'); if (!statics.active) { @@ -272,4 +288,68 @@ JX.behavior('repository-crossreference', function(config, statics) { JX.DOM.alterClass(element, classMouseCursor, statics.active); }); } + + + if (config && config.container) { + link(JX.$(config.container), config.lang); + } + + JX.Stratcom.listen( + ['mouseover', 'mouseout', 'click'], + ['has-symbols', 'tag:span'], + function(e) { + var type = e.getType(); + + if (type === 'mouseout') { + unhighlight(); + return; + } + + if (!hasHighlightModifierKey(e)) { + return; + } + + var target = e.getTarget(); + + try { + // If we're in an inline comment, don't link symbols. + if (JX.DOM.findAbove(target, 'div', 'differential-inline-comment')) { + return; + } + } catch (ex) { + // Continue if we're not inside an inline comment. + } + + // If only part of the symbol was edited, the symbol name itself will + // have another "" inside of it which highlights only the + // edited part. Skip over it. + if (JX.DOM.isNode(target, 'span') && (target.className === 'bright')) { + target = target.parentNode; + } + + if (type === 'click') { + openSearch(target, e.getNodeData('has-symbols').symbols); + e.kill(); + return; + } + + if (e.getType() === 'mouseover') { + while (target && target !== document.body) { + if (!JX.DOM.isNode(target, 'span')) { + target = target.parentNode; + continue; + } + + if (!class_map.hasOwnProperty(target.className)) { + target = target.parentNode; + continue; + } + + highlighted = target; + JX.DOM.alterClass(highlighted, classHighlight, true); + break; + } + } + }); + }); From 09c6d42b95999ec7f2cb74eba7a2721aaaba0365 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 8 Apr 2018 11:38:19 -0700 Subject: [PATCH 10/53] Mostly make blame work with DocumentEngine Summary: Ref T13105. This needs refinement but blame sort of works again, now. Test Plan: Viewed files in Diffusion and Files; saw blame in Diffusion when viewing in source mode. Reviewers: mydeveloperday Reviewed By: mydeveloperday Maniphest Tasks: T13105 Differential Revision: https://secure.phabricator.com/D19309 --- resources/celerity/map.php | 42 ++- resources/celerity/packages.php | 1 - src/__phutil_library_map__.php | 2 + .../PhabricatorDiffusionApplication.php | 2 + .../controller/DiffusionBlameController.php | 254 +++++++++++++++++ .../controller/DiffusionBrowseController.php | 264 +----------------- .../DiffusionDocumentRenderingEngine.php | 12 +- .../document/PhabricatorDocumentEngine.php | 4 + .../files/document/PhabricatorDocumentRef.php | 8 + .../PhabricatorSourceDocumentEngine.php | 14 +- .../PhabricatorTextDocumentEngine.php | 21 +- .../PhabricatorDocumentRenderingEngine.php | 20 +- .../storage/PhabricatorRepository.php | 2 + .../storage/PhabricatorRepositoryCommit.php | 22 -- src/view/layout/PhabricatorSourceCodeView.php | 44 ++- .../differential/changeset-view.css | 4 - .../diffusion/diffusion-source.css | 102 ------- .../layout/phabricator-source-code-view.css | 43 +++ .../diffusion/behavior-load-blame.js | 14 - .../files/behavior-document-engine.js | 84 +++++- 20 files changed, 513 insertions(+), 446 deletions(-) create mode 100644 src/applications/diffusion/controller/DiffusionBlameController.php delete mode 100644 webroot/rsrc/css/application/diffusion/diffusion-source.css delete mode 100644 webroot/rsrc/js/application/diffusion/behavior-load-blame.js diff --git a/resources/celerity/map.php b/resources/celerity/map.php index b4381dea5c..4f4356cc63 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -11,8 +11,8 @@ return array( 'conpherence.pkg.js' => '15191c65', 'core.pkg.css' => '4a83e174', 'core.pkg.js' => '1ea38af8', - 'differential.pkg.css' => '113e692c', - 'differential.pkg.js' => '3da2650a', + 'differential.pkg.css' => '06dc617c', + 'differential.pkg.js' => 'c2ca903a', 'diffusion.pkg.css' => 'a2d17c7d', 'diffusion.pkg.js' => '6134c5a1', 'maniphest.pkg.css' => '4845691a', @@ -61,7 +61,7 @@ return array( 'rsrc/css/application/dashboard/dashboard.css' => 'fe5b1869', 'rsrc/css/application/diff/inline-comment-summary.css' => 'f23d4e8f', 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', - 'rsrc/css/application/differential/changeset-view.css' => 'bf84345b', + 'rsrc/css/application/differential/changeset-view.css' => 'db34a142', 'rsrc/css/application/differential/core.css' => '5b7b8ff4', 'rsrc/css/application/differential/phui-inline-comment.css' => '65ae3bc2', 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', @@ -71,7 +71,6 @@ 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' => '5f35a3bd', 'rsrc/css/application/diffusion/diffusion.css' => '45727264', 'rsrc/css/application/feed/feed.css' => 'ecd4ec57', 'rsrc/css/application/files/global-drag-and-drop.css' => 'b556a948', @@ -120,7 +119,7 @@ return array( 'rsrc/css/font/font-lato.css' => 'c7ccd872', 'rsrc/css/font/phui-font-icon-base.css' => '870a7360', 'rsrc/css/layout/phabricator-filetree-view.css' => 'b912ad97', - 'rsrc/css/layout/phabricator-source-code-view.css' => 'c6fc6834', + 'rsrc/css/layout/phabricator-source-code-view.css' => 'af54e277', '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' => '1863cc6e', @@ -387,12 +386,11 @@ return array( 'rsrc/js/application/diffusion/behavior-commit-graph.js' => '75b83cbb', 'rsrc/js/application/diffusion/behavior-diffusion-browse-file.js' => '054a0f0b', 'rsrc/js/application/diffusion/behavior-jump-to.js' => '73d09eef', - 'rsrc/js/application/diffusion/behavior-load-blame.js' => '42126667', 'rsrc/js/application/diffusion/behavior-locate-file.js' => '6d3e1947', 'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'f01586dc', 'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => '1db13e70', 'rsrc/js/application/drydock/drydock-live-operation-status.js' => '901935ef', - 'rsrc/js/application/files/behavior-document-engine.js' => '9108ee1a', + 'rsrc/js/application/files/behavior-document-engine.js' => 'ac52a3be', 'rsrc/js/application/files/behavior-icon-composer.js' => '8499b6ab', 'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888', 'rsrc/js/application/harbormaster/behavior-harbormaster-log.js' => '191b4909', @@ -543,7 +541,7 @@ return array( 'conpherence-thread-manager' => '4d863052', 'conpherence-transaction-css' => '85129c68', 'd3' => 'a11a5ff2', - 'differential-changeset-view-css' => 'bf84345b', + 'differential-changeset-view-css' => 'db34a142', 'differential-core-view-css' => '5b7b8ff4', 'differential-revision-add-comment-css' => 'c47f8c40', 'differential-revision-comment-css' => '14b8565a', @@ -554,7 +552,6 @@ return array( 'diffusion-icons-css' => '0c15255e', 'diffusion-readme-css' => '419dd5b6', 'diffusion-repository-css' => 'ee6f20ec', - 'diffusion-source-css' => '5f35a3bd', 'diviner-shared-css' => '896f1d43', 'font-fontawesome' => 'e838e088', 'font-lato' => 'c7ccd872', @@ -607,7 +604,7 @@ return array( 'javelin-behavior-diffusion-jump-to' => '73d09eef', 'javelin-behavior-diffusion-locate-file' => '6d3e1947', 'javelin-behavior-diffusion-pull-lastmodified' => 'f01586dc', - 'javelin-behavior-document-engine' => '9108ee1a', + 'javelin-behavior-document-engine' => 'ac52a3be', 'javelin-behavior-doorkeeper-tag' => '1db13e70', 'javelin-behavior-drydock-live-operation-status' => '901935ef', 'javelin-behavior-durable-column' => '2ae077e1', @@ -624,7 +621,6 @@ return array( 'javelin-behavior-launch-icon-composer' => '48086888', 'javelin-behavior-lightbox-attachments' => '6b31879a', 'javelin-behavior-line-chart' => 'e4232876', - 'javelin-behavior-load-blame' => '42126667', 'javelin-behavior-maniphest-batch-selector' => 'ad54037e', 'javelin-behavior-maniphest-list-editor' => 'a9f88de2', 'javelin-behavior-maniphest-subpriority-editor' => '71237763', @@ -784,7 +780,7 @@ return array( 'phabricator-search-results-css' => '505dd8cf', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-slowvote-css' => 'a94b7230', - 'phabricator-source-code-view-css' => 'c6fc6834', + 'phabricator-source-code-view-css' => 'af54e277', 'phabricator-standard-page-view' => '34ee718b', 'phabricator-textareautils' => '320810c8', 'phabricator-title' => '485aaa6c', @@ -1153,11 +1149,6 @@ return array( 'phabricator-diff-changeset-list', 'phabricator-diff-changeset', ), - 42126667 => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-request', - ), '4250a34e' => array( 'javelin-behavior', 'javelin-dom', @@ -1632,11 +1623,6 @@ return array( 'javelin-stratcom', 'javelin-vector', ), - '9108ee1a' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - ), '92b9ec77' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1772,6 +1758,11 @@ return array( 'javelin-dom', 'javelin-typeahead-normalizer', ), + 'ac52a3be' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + ), 'acd29eee' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1902,9 +1893,6 @@ return array( 'javelin-stratcom', 'javelin-dom', ), - 'bf84345b' => array( - 'phui-inline-comment-view-css', - ), 'bff6884b' => array( 'javelin-install', 'javelin-dom', @@ -2021,6 +2009,9 @@ return array( 'javelin-util', 'phabricator-shaped-request', ), + 'db34a142' => array( + 'phui-inline-comment-view-css', + ), 'dca75c0e' => array( 'multirow-row-manager', 'javelin-install', @@ -2367,7 +2358,6 @@ return array( 'javelin-behavior-aphront-drag-and-drop-textarea', 'javelin-behavior-phabricator-object-selector', 'javelin-behavior-repository-crossreference', - 'javelin-behavior-load-blame', 'javelin-behavior-differential-user-select', 'javelin-behavior-aphront-more', 'phabricator-diff-inline', diff --git a/resources/celerity/packages.php b/resources/celerity/packages.php index 3cec508678..958b1d6afa 100644 --- a/resources/celerity/packages.php +++ b/resources/celerity/packages.php @@ -199,7 +199,6 @@ return array( 'javelin-behavior-aphront-drag-and-drop-textarea', 'javelin-behavior-phabricator-object-selector', 'javelin-behavior-repository-crossreference', - 'javelin-behavior-load-blame', 'javelin-behavior-differential-user-select', 'javelin-behavior-aphront-more', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 8e2eb24b15..4663aed50d 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -632,6 +632,7 @@ phutil_register_library_map(array( 'DiffusionAuditorsAddSelfHeraldAction' => 'applications/diffusion/herald/DiffusionAuditorsAddSelfHeraldAction.php', 'DiffusionAuditorsHeraldAction' => 'applications/diffusion/herald/DiffusionAuditorsHeraldAction.php', 'DiffusionBlameConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionBlameConduitAPIMethod.php', + 'DiffusionBlameController' => 'applications/diffusion/controller/DiffusionBlameController.php', 'DiffusionBlameQuery' => 'applications/diffusion/query/blame/DiffusionBlameQuery.php', 'DiffusionBlockHeraldAction' => 'applications/diffusion/herald/DiffusionBlockHeraldAction.php', 'DiffusionBranchListView' => 'applications/diffusion/view/DiffusionBranchListView.php', @@ -5889,6 +5890,7 @@ phutil_register_library_map(array( 'DiffusionAuditorsAddSelfHeraldAction' => 'DiffusionAuditorsHeraldAction', 'DiffusionAuditorsHeraldAction' => 'HeraldAction', 'DiffusionBlameConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', + 'DiffusionBlameController' => 'DiffusionController', 'DiffusionBlameQuery' => 'DiffusionQuery', 'DiffusionBlockHeraldAction' => 'HeraldAction', 'DiffusionBranchListView' => 'DiffusionView', diff --git a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php index 4a1c2fdd95..70990bfe3c 100644 --- a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php +++ b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php @@ -55,6 +55,8 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication { 'browse/(?P.*)' => 'DiffusionBrowseController', 'document/(?P.*)' => 'DiffusionDocumentController', + 'blame/(?P.*)' + => 'DiffusionBlameController', 'lastmodified/(?P.*)' => 'DiffusionLastModifiedController', 'diff/' => 'DiffusionDiffController', 'tags/(?P.*)' => 'DiffusionTagListController', diff --git a/src/applications/diffusion/controller/DiffusionBlameController.php b/src/applications/diffusion/controller/DiffusionBlameController.php new file mode 100644 index 0000000000..758a865e0a --- /dev/null +++ b/src/applications/diffusion/controller/DiffusionBlameController.php @@ -0,0 +1,254 @@ +loadDiffusionContext(); + if ($response) { + return $response; + } + + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + + $blame = $this->loadBlame(); + + $identifiers = array_fuse($blame); + if ($identifiers) { + $commits = id(new DiffusionCommitQuery()) + ->setViewer($viewer) + ->withRepository($repository) + ->withIdentifiers($identifiers) + ->execute(); + $commits = mpull($commits, null, 'getCommitIdentifier'); + } else { + $commits = array(); + } + + $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; + } + } + + $base_href = (string)$drequest->generateURI( + array( + 'action' => 'browse', + 'stable' => true, + )); + + $skip_text = pht('Skip Past This Commit'); + $skip_icon = id(new PHUIIconView()) + ->setIcon('fa-backward'); + + Javelin::initBehavior('phabricator-tooltips'); + + $handle_phids = array(); + foreach ($commits as $commit) { + $author_phid = $commit->getAuthorPHID(); + if ($author_phid) { + $handle_phids[] = $author_phid; + } + } + + foreach ($revisions as $revision) { + $handle_phids[] = $revision->getAuthorPHID(); + } + + $handles = $viewer->loadHandles($handle_phids); + + $map = 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( + 'a', + array( + 'href' => $skip_href, + 'sigil' => 'has-tooltip', + 'meta' => array( + 'tip' => $skip_text, + 'align' => 'E', + 'size' => 300, + ), + ), + $skip_icon); + + $commit = $commits[$identifier]; + + $author_phid = $commit->getAuthorPHID(); + if (!$author_phid && $revision) { + $author_phid = $revision->getAuthorPHID(); + } + + if (!$author_phid) { + // This means we couldn't identify an author for the commit or the + // revision. We just render a blank for alignment. + $author_style = null; + $author_href = null; + $author_sigil = null; + $author_meta = null; + } else { + $author_src = $handles[$author_phid]->getImageURI(); + $author_style = 'background-image: url('.$author_src.');'; + $author_href = $handles[$author_phid]->getURI(); + $author_sigil = 'has-tooltip'; + $author_meta = array( + 'tip' => $handles[$author_phid]->getName(), + 'align' => 'E', + ); + } + + $author_link = javelin_tag( + $author_href ? 'a' : 'span', + array( + 'class' => 'phabricator-source-blame-author', + 'style' => $author_style, + 'href' => $author_href, + 'sigil' => $author_sigil, + 'meta' => $author_meta, + )); + + $commit_link = javelin_tag( + 'a', + array( + 'href' => $commit->getURI(), + 'sigil' => 'has-tooltip', + 'meta' => array( + 'tip' => $this->renderCommitTooltip($commit, $handles), + 'align' => 'E', + 'size' => 600, + ), + ), + $commit->getLocalName()); + + $info = array( + $author_link, + $commit_link, + ); + + if ($revision) { + $revision_link = phutil_tag( + 'a', + array( + 'href' => $revision->getURI(), + 'sigil' => 'has-tooltip', + 'meta' => array( + 'tip' => $this->renderRevisionTooltip($revision, $handles), + 'align' => 'E', + 'size' => 600, + ), + ), + $revision->getMonogram()); + + + $info = array( + $info, + ' / ', + $revision_link, + ); + } + + $data = array( + 'skip' => $skip_link, + 'info' => hsprintf('%s', $info), + ); + + $map[$identifier] = $data; + } + + return id(new AphrontAjaxResponse())->setContent( + array( + 'blame' => $blame, + 'map' => $map, + )); + } + + private function loadBlame() { + $drequest = $this->getDiffusionRequest(); + + $commit = $drequest->getCommit(); + $path = $drequest->getPath(); + + $blame_timeout = 15; + + $blame = $this->callConduitWithDiffusionRequest( + 'diffusion.blame', + array( + 'commit' => $commit, + 'paths' => array($path), + 'timeout' => $blame_timeout, + )); + + return idx($blame, $path, array()); + } + + private function renderRevisionTooltip( + DifferentialRevision $revision, + $handles) { + $viewer = $this->getViewer(); + + $date = phabricator_date($revision->getDateModified(), $viewer); + $monogram = $revision->getMonogram(); + $title = $revision->getTitle(); + $header = "{$monogram} {$title}"; + + $author = $handles[$revision->getAuthorPHID()]->getName(); + + return "{$header}\n{$date} \xC2\xB7 {$author}"; + } + + private function renderCommitTooltip( + PhabricatorRepositoryCommit $commit, + $handles) { + + $viewer = $this->getViewer(); + + $date = phabricator_date($commit->getEpoch(), $viewer); + $summary = trim($commit->getSummary()); + + $author_phid = $commit->getAuthorPHID(); + if ($author_phid && isset($handles[$author_phid])) { + $author_name = $handles[$author_phid]->getName(); + } else { + $author_name = null; + } + + if ($author_name) { + return "{$summary}\n{$date} \xC2\xB7 {$author_name}"; + } else { + return "{$summary}\n{$date}"; + } + } + +} diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index 2c2a2f46b9..1053ef477c 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -110,12 +110,6 @@ final class DiffusionBrowseController extends DiffusionController { } $path = $drequest->getPath(); - - // We need the blame information if blame is on and this is an Ajax request. - // If blame is on and this is a colorized request, we don't show blame at - // first (we ajax it in afterward) so we don't need to query for it. - $needs_blame = $request->isAjax(); - $params = array( 'commit' => $drequest->getCommit(), 'path' => $drequest->getPath(), @@ -184,11 +178,10 @@ final class DiffusionBrowseController extends DiffusionController { $file->setName($basename); return $file->getRedirectResponse(); - } else { - $corpus = $this->buildGitLFSCorpus($lfs_ref); } + + $corpus = $this->buildGitLFSCorpus($lfs_ref); } else { - $this->loadLintMessages(); $this->coverage = $drequest->loadCoverage(); $show_editor = true; @@ -205,12 +198,6 @@ final class DiffusionBrowseController extends DiffusionController { } } - if ($request->isAjax()) { - return id(new AphrontAjaxResponse())->setContent($corpus); - } - - require_celerity_resource('diffusion-source-css'); - $bar = $this->buildButtonBar($drequest, $show_editor); $header = $this->buildHeaderView($drequest); $header->setHeaderIcon('fa-file-code-o'); @@ -480,35 +467,6 @@ final class DiffusionBrowseController extends DiffusionController { return $view; } - private function loadLintMessages() { - $drequest = $this->getDiffusionRequest(); - $branch = $drequest->loadBranch(); - - if (!$branch || !$branch->getLintCommit()) { - return; - } - - $this->lintCommit = $branch->getLintCommit(); - - $conn = id(new PhabricatorRepository())->establishConnection('r'); - - $where = ''; - if ($drequest->getLint()) { - $where = qsprintf( - $conn, - 'AND code = %s', - $drequest->getLint()); - } - - $this->lintMessages = queryfx_all( - $conn, - 'SELECT * FROM %T WHERE branchID = %d %Q AND path = %s', - PhabricatorRepository::TABLE_LINTMESSAGE, - $branch->getID(), - $where, - '/'.$drequest->getPath()); - } - private function buildButtonBar( DiffusionRequest $drequest, $show_editor) { @@ -550,33 +508,6 @@ final class DiffusionBrowseController extends DiffusionController { ->setColor(PHUIButtonView::GREY); } - $href = null; - $show_lint = true; - if ($this->getRequest()->getStr('lint') !== null) { - $lint_text = pht('Hide Lint'); - $href = $base_uri->alter('lint', null); - - } else if ($this->lintCommit === null) { - $show_lint = false; - } else { - $lint_text = pht('Show Lint'); - $href = $this->getDiffusionRequest()->generateURI(array( - 'action' => 'browse', - 'commit' => $this->lintCommit, - ))->alter('lint', ''); - } - - if ($show_lint) { - $buttons[] = - id(new PHUIButtonView()) - ->setTag('a') - ->setText($lint_text) - ->setHref($href) - ->setIcon('fa-exclamation-triangle') - ->setDisabled(!$href) - ->setColor(PHUIButtonView::GREY); - } - $bar = id(new PHUILeftRightView()) ->setLeft($buttons) ->addClass('diffusion-action-bar full-mobile-buttons'); @@ -705,40 +636,6 @@ final class DiffusionBrowseController extends DiffusionController { ->setColor(PHUIButtonView::GREY); } - private function renderInlines( - array $inlines, - $has_coverage, - $engine) { - - $rows = array(); - foreach ($inlines as $inline) { - - // TODO: This should use modern scaffolding code. - - $inline_view = id(new PHUIDiffInlineCommentDetailView()) - ->setUser($this->getViewer()) - ->setMarkupEngine($engine) - ->setInlineComment($inline) - ->render(); - - $row = array_fill(0, 3, phutil_tag('th')); - - $row[] = phutil_tag('td', array(), $inline_view); - - if ($has_coverage) { - $row[] = phutil_tag( - 'td', - array( - 'class' => 'cov cov-I', - )); - } - - $rows[] = phutil_tag('tr', array('class' => 'inline'), $row); - } - - return $rows; - } - private function buildErrorCorpus($message) { $text = id(new PHUIBoxView()) ->addPadding(PHUI::PADDING_LARGE) @@ -898,33 +795,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()); @@ -1108,127 +978,6 @@ final class DiffusionBrowseController extends DiffusionController { return $view; } - private function loadBlame($path, $commit, $timeout) { - $blame = $this->callConduitWithDiffusionRequest( - 'diffusion.blame', - array( - 'commit' => $commit, - 'paths' => array($path), - 'timeout' => $timeout, - )); - - $identifiers = idx($blame, $path, null); - - if ($identifiers) { - $viewer = $this->getViewer(); - $drequest = $this->getDiffusionRequest(); - $repository = $drequest->getRepository(); - - $commits = id(new DiffusionCommitQuery()) - ->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 { - $commits = array(); - } - - return array($identifiers, $commits); - } - - private function renderAuthorLinks(array $authors, $handles) { - $links = array(); - - foreach ($authors as $phid) { - if (!strlen($phid)) { - // This means we couldn't identify an author for the commit or the - // revision. We just render a blank for alignment. - $style = null; - $href = null; - $sigil = null; - $meta = null; - } else { - $src = $handles[$phid]->getImageURI(); - $style = 'background-image: url('.$src.');'; - $href = $handles[$phid]->getURI(); - $sigil = 'has-tooltip'; - $meta = array( - 'tip' => $handles[$phid]->getName(), - 'align' => 'E', - ); - } - - $links[$phid] = javelin_tag( - $href ? 'a' : 'span', - array( - 'class' => 'diffusion-author-link', - 'style' => $style, - 'href' => $href, - 'sigil' => $sigil, - 'meta' => $meta, - )); - } - - return $links; - } - - private function renderCommitLinks(array $commits, $handles) { - $links = array(); - foreach ($commits as $identifier => $commit) { - $tooltip = $this->renderCommitTooltip( - $commit, - $commit->renderAuthorShortName($handles)); - - $commit_link = javelin_tag( - 'a', - array( - 'href' => $commit->getURI(), - 'sigil' => 'has-tooltip', - 'meta' => array( - 'tip' => $tooltip, - 'align' => 'E', - 'size' => 600, - ), - ), - $commit->getLocalName()); - - $links[$identifier] = $commit_link; - } - - return $links; - } - - private function renderRevisionLinks(array $revisions, $handles) { - $links = array(); - - foreach ($revisions as $revision) { - $revision_id = $revision->getID(); - - $tooltip = $this->renderRevisionTooltip($revision, $handles); - - $revision_link = javelin_tag( - 'a', - array( - 'href' => '/'.$revision->getMonogram(), - 'sigil' => 'has-tooltip', - 'meta' => array( - 'tip' => $tooltip, - 'align' => 'E', - 'size' => 600, - ), - ), - $revision->getMonogram()); - - $links[$revision_id] = $revision_link; - } - - return $links; - } - private function getGitLFSRef(PhabricatorRepository $repository, $data) { if (!$repository->canUseGitLFS()) { return null; @@ -1375,13 +1124,4 @@ final class DiffusionBrowseController extends DiffusionController { ->setTable($history_table); } - private function getLineNumberBaseURI() { - $drequest = $this->getDiffusionRequest(); - - return (string)$drequest->generateURI( - array( - 'action' => 'browse', - 'stable' => true, - )); - } } diff --git a/src/applications/diffusion/document/DiffusionDocumentRenderingEngine.php b/src/applications/diffusion/document/DiffusionDocumentRenderingEngine.php index 2ca23473ca..17abba5c4f 100644 --- a/src/applications/diffusion/document/DiffusionDocumentRenderingEngine.php +++ b/src/applications/diffusion/document/DiffusionDocumentRenderingEngine.php @@ -70,7 +70,17 @@ final class DiffusionDocumentRenderingEngine } protected function willRenderRef(PhabricatorDocumentRef $ref) { - $ref->setSymbolMetadata($this->getSymbolMetadata()); + $drequest = $this->getDiffusionRequest(); + + $blame_uri = (string)$drequest->generateURI( + array( + 'action' => 'blame', + 'stable' => true, + )); + + $ref + ->setSymbolMetadata($this->getSymbolMetadata()) + ->setBlameURI($blame_uri); } private function getSymbolMetadata() { diff --git a/src/applications/files/document/PhabricatorDocumentEngine.php b/src/applications/files/document/PhabricatorDocumentEngine.php index 17feed1c3e..85219b904c 100644 --- a/src/applications/files/document/PhabricatorDocumentEngine.php +++ b/src/applications/files/document/PhabricatorDocumentEngine.php @@ -38,6 +38,10 @@ abstract class PhabricatorDocumentEngine return false; } + public function canBlame(PhabricatorDocumentRef $ref) { + return false; + } + final public function setEncodingConfiguration($config) { $this->encodingConfiguration = $config; return $this; diff --git a/src/applications/files/document/PhabricatorDocumentRef.php b/src/applications/files/document/PhabricatorDocumentRef.php index 50230cc91d..953ac7df5d 100644 --- a/src/applications/files/document/PhabricatorDocumentRef.php +++ b/src/applications/files/document/PhabricatorDocumentRef.php @@ -9,6 +9,7 @@ final class PhabricatorDocumentRef private $byteLength; private $snippet; private $symbolMetadata = array(); + private $blameURI; public function setFile(PhabricatorFile $file) { $this->file = $file; @@ -141,6 +142,13 @@ final class PhabricatorDocumentRef return $this->symbolMetadata; } + public function setBlameURI($blame_uri) { + $this->blameURI = $blame_uri; + return $this; + } + public function getBlameURI() { + return $this->blameURI; + } } diff --git a/src/applications/files/document/PhabricatorSourceDocumentEngine.php b/src/applications/files/document/PhabricatorSourceDocumentEngine.php index 21898ef636..20a1c94a95 100644 --- a/src/applications/files/document/PhabricatorSourceDocumentEngine.php +++ b/src/applications/files/document/PhabricatorSourceDocumentEngine.php @@ -13,6 +13,10 @@ final class PhabricatorSourceDocumentEngine return true; } + public function canBlame(PhabricatorDocumentRef $ref) { + return true; + } + protected function getDocumentIconIcon(PhabricatorDocumentRef $ref) { return 'fa-code'; } @@ -45,9 +49,17 @@ final class PhabricatorSourceDocumentEngine } } + $options = array(); + if ($ref->getBlameURI()) { + $content = phutil_split_lines($content); + $blame = range(1, count($content)); + $blame = array_fuse($blame); + $options['blame'] = $blame; + } + return array( $messages, - $this->newTextDocumentContent($ref, $content), + $this->newTextDocumentContent($ref, $content, $options), ); } diff --git a/src/applications/files/document/PhabricatorTextDocumentEngine.php b/src/applications/files/document/PhabricatorTextDocumentEngine.php index c53b9edd58..0377a24353 100644 --- a/src/applications/files/document/PhabricatorTextDocumentEngine.php +++ b/src/applications/files/document/PhabricatorTextDocumentEngine.php @@ -15,14 +15,31 @@ abstract class PhabricatorTextDocumentEngine protected function newTextDocumentContent( PhabricatorDocumentRef $ref, - $content) { - $lines = phutil_split_lines($content); + $content, + array $options = array()) { + + PhutilTypeSpec::checkMap( + $options, + array( + 'blame' => 'optional wild', + )); + + if (is_array($content)) { + $lines = $content; + } else { + $lines = phutil_split_lines($content); + } $view = id(new PhabricatorSourceCodeView()) ->setHighlights($this->getHighlightedLines()) ->setLines($lines) ->setSymbolMetadata($ref->getSymbolMetadata()); + $blame = idx($options, 'blame'); + if ($blame !== null) { + $view->setBlameMap($blame); + } + $message = null; if ($this->encodingMessage !== null) { $message = $this->newMessage($this->encodingMessage); diff --git a/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php b/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php index f412cb2869..4a16f53fdf 100644 --- a/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php +++ b/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php @@ -91,7 +91,8 @@ abstract class PhabricatorDocumentRenderingEngine 'viewURI' => $view_uri, 'loadingMarkup' => hsprintf('%s', $loading), 'canEncode' => $candidate_engine->canConfigureEncoding($ref), - 'canHighlight' => $candidate_engine->CanConfigureHighlighting($ref), + 'canHighlight' => $candidate_engine->canConfigureHighlighting($ref), + 'canBlame' => $candidate_engine->canBlame($ref), ); } @@ -99,15 +100,20 @@ abstract class PhabricatorDocumentRenderingEngine $control_id = celerity_generate_unique_node_id(); $icon = $engine->newDocumentIcon($ref); + $config = array( + 'controlID' => $control_id, + ); + if ($engine->shouldRenderAsync($ref)) { $content = $engine->newLoadingContent($ref); - $config = array( - 'renderControlID' => $control_id, - ); + $config['next'] = 'render'; } else { $this->willRenderRef($ref); $content = $engine->newDocument($ref); - $config = array(); + + if ($engine->canBlame($ref)) { + $config['next'] = 'blame'; + } } Javelin::initBehavior('document-engine', $config); @@ -135,6 +141,10 @@ abstract class PhabricatorDocumentRenderingEngine 'uri' => '/services/highlight/', 'value' => $highlight_setting, ), + 'blame' => array( + 'uri' => $ref->getBlameURI(), + 'value' => null, + ), ); $view_button = id(new PHUIButtonView()) diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index 1dfbceead0..334867cc85 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -703,6 +703,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO case 'history': case 'graph': case 'clone': + case 'blame': case 'browse': case 'document': case 'change': @@ -782,6 +783,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO case 'change': case 'history': case 'graph': + case 'blame': case 'browse': case 'document': case 'lastmodified': diff --git a/src/applications/repository/storage/PhabricatorRepositoryCommit.php b/src/applications/repository/storage/PhabricatorRepositoryCommit.php index f6e6e22c3a..01088522e7 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryCommit.php +++ b/src/applications/repository/storage/PhabricatorRepositoryCommit.php @@ -416,28 +416,6 @@ final class PhabricatorRepositoryCommit return $repository->formatCommitName($identifier, $local = true); } - public function renderAuthorLink($handles) { - $author_phid = $this->getAuthorPHID(); - if ($author_phid && isset($handles[$author_phid])) { - return $handles[$author_phid]->renderLink(); - } - - return $this->renderAuthorShortName($handles); - } - - public function renderAuthorShortName($handles) { - $author_phid = $this->getAuthorPHID(); - if ($author_phid && isset($handles[$author_phid])) { - return $handles[$author_phid]->getName(); - } - - $data = $this->getCommitData(); - $name = $data->getAuthorName(); - - $parsed = new PhutilEmailAddress($name); - return nonempty($parsed->getDisplayName(), $parsed->getAddress()); - } - /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/view/layout/PhabricatorSourceCodeView.php b/src/view/layout/PhabricatorSourceCodeView.php index 6a51e1843d..19fa17c582 100644 --- a/src/view/layout/PhabricatorSourceCodeView.php +++ b/src/view/layout/PhabricatorSourceCodeView.php @@ -9,6 +9,7 @@ final class PhabricatorSourceCodeView extends AphrontView { private $truncatedFirstBytes = false; private $truncatedFirstLines = false; private $symbolMetadata; + private $blameMap; public function setLines(array $lines) { $this->lines = $lines; @@ -49,7 +50,19 @@ final class PhabricatorSourceCodeView extends AphrontView { return $this->symbolMetadata; } + public function setBlameMap(array $map) { + $this->blameMap = $map; + return $this; + } + + public function getBlameMap() { + return $this->blameMap; + } + public function render() { + $blame_map = $this->getBlameMap(); + $has_blame = ($blame_map !== null); + require_celerity_resource('phabricator-source-code-view-css'); require_celerity_resource('syntax-highlighting-css'); @@ -85,7 +98,6 @@ final class PhabricatorSourceCodeView extends AphrontView { $base_uri = (string)$this->uri; foreach ($lines as $line) { - // NOTE: See phabricator-oncopy behavior. $content_line = hsprintf("\xE2\x80\x8B%s", $line); @@ -114,10 +126,40 @@ final class PhabricatorSourceCodeView extends AphrontView { $line_number); } + if ($has_blame) { + $lines = idx($blame_map, $line_number); + + if ($lines) { + $skip_blame = 'skip;'.$lines; + $info_blame = 'info;'.$lines; + } else { + $skip_blame = null; + $info_blame = null; + } + + $blame_cells = array( + phutil_tag( + 'th', + array( + 'class' => 'phabricator-source-blame-skip', + 'data-blame' => $skip_blame, + )), + phutil_tag( + 'th', + array( + 'class' => 'phabricator-source-blame-info', + 'data-blame' => $info_blame, + )), + ); + } else { + $blame_cells = null; + } + $rows[] = phutil_tag( 'tr', $row_attributes, array( + $blame_cells, phutil_tag( 'th', array( diff --git a/webroot/rsrc/css/application/differential/changeset-view.css b/webroot/rsrc/css/application/differential/changeset-view.css index 194561aa7a..b9683dc7c6 100644 --- a/webroot/rsrc/css/application/differential/changeset-view.css +++ b/webroot/rsrc/css/application/differential/changeset-view.css @@ -165,10 +165,6 @@ padding: 0; } -.diffusion-source td.cov { - padding: 0 8px; -} - td.cov-U { background: #dd8866; } diff --git a/webroot/rsrc/css/application/diffusion/diffusion-source.css b/webroot/rsrc/css/application/diffusion/diffusion-source.css deleted file mode 100644 index a2c67cf16d..0000000000 --- a/webroot/rsrc/css/application/diffusion/diffusion-source.css +++ /dev/null @@ -1,102 +0,0 @@ -/** - * @provides diffusion-source-css - */ - -.diffusion-source { - width: 100%; - background: {$page.content}; - overflow: hidden; -} - -.device-phone .diffusion-source-wrap { - overflow: scroll; - -webkit-overflow-scrolling: touch; -} - -.diffusion-source tr.phabricator-source-highlight { - background: {$sh-yellowbackground}; -} - -.diffusion-source th { - text-align: right; - vertical-align: top; - background: {$lightgreybackground}; - color: {$bluetext}; - border-right: 1px solid {$thinblueborder}; -} - -.diffusion-source td { - vertical-align: top; - white-space: pre-wrap; - padding-top: 1px; - padding-bottom: 1px; - padding-left: 8px; - width: 100%; - word-break: break-all; -} - -.device .diffusion-source td { - word-break: normal; - white-space: nowrap; -} - -.diffusion-browse-type-form { - float: right; -} - -.diffusion-blame-link, -.diffusion-rev-link { - white-space: nowrap; -} - -.diffusion-blame-link { - min-width: 28px; -} - -.diffusion-source th.diffusion-rev-link { - text-align: left; - min-width: 130px; -} - -.diffusion-blame-link a, -.diffusion-rev-link a, -.diffusion-line-link a { - color: {$darkbluetext}; -} - -.diffusion-rev-link a { - margin: 0 8px 0 0; - display: inline-block; -} - -.diffusion-rev-link span { - display: inline-block; - margin-right: 4px; - margin-left: -4px; - color: {$lightgreytext}; -} - -.diffusion-blame-link a, -.diffusion-line-link a { - /* Give the user a larger click target. */ - display: block; - padding: 2px 8px; -} - -.diffusion-line-link { - -moz-user-select: -moz-none; - -khtml-user-select: none; - -webkit-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.diffusion-rev-link .diffusion-author-link { - display: inline-block; - padding: 0; - margin: 2px 6px -4px 8px; - width: 16px; - height: 16px; - background-size: 100% 100%; - background-repeat: no-repeat; -} diff --git a/webroot/rsrc/css/layout/phabricator-source-code-view.css b/webroot/rsrc/css/layout/phabricator-source-code-view.css index 3c6e85f71e..ae9793c56f 100644 --- a/webroot/rsrc/css/layout/phabricator-source-code-view.css +++ b/webroot/rsrc/css/layout/phabricator-source-code-view.css @@ -68,3 +68,46 @@ th.phabricator-source-line a:hover { .phabricator-source-code-summary .phabricator-source-code { white-space: nowrap; } + + +.phabricator-source-blame-skip, +.phabricator-source-blame-info { + -moz-user-select: -moz-none; + -khtml-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.phabricator-source-blame-skip { + min-width: 28px; + border-right: 1px solid {$thinblueborder}; +} + +.phabricator-source-blame-info { + color: {$lightgreytext}; + white-space: nowrap; + min-width: 130px; + border-right: 1px solid {$paste.border}; + padding-right: 8px; +} + +.phabricator-source-blame-skip a { + /* Give the user a larger click target. */ + display: block; + padding: 2px 8px; +} + +.device-desktop .phabricator-source-blame-skip a:hover { + background: {$bluebackground}; +} + +.phabricator-source-blame-author { + display: inline-block; + padding: 0; + margin: 2px 6px -4px 8px; + width: 16px; + height: 16px; + background-size: 100% 100%; + background-repeat: no-repeat; +} diff --git a/webroot/rsrc/js/application/diffusion/behavior-load-blame.js b/webroot/rsrc/js/application/diffusion/behavior-load-blame.js deleted file mode 100644 index bebafc8886..0000000000 --- a/webroot/rsrc/js/application/diffusion/behavior-load-blame.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * @provides javelin-behavior-load-blame - * @requires javelin-behavior - * javelin-dom - * javelin-request - */ - -JX.behavior('load-blame', function(config) { - - new JX.Request(location.href, function (response) { - JX.DOM.setContent(JX.$(config.id), JX.$H(response)); - }).send(); - -}); diff --git a/webroot/rsrc/js/application/files/behavior-document-engine.js b/webroot/rsrc/js/application/files/behavior-document-engine.js index ccbd41ca92..fa5ed8f860 100644 --- a/webroot/rsrc/js/application/files/behavior-document-engine.js +++ b/webroot/rsrc/js/application/files/behavior-document-engine.js @@ -7,8 +7,6 @@ JX.behavior('document-engine', function(config, statics) { - - function onmenu(e) { var node = e.getNode('document-engine-view-dropdown'); var data = JX.Stratcom.getData(node); @@ -213,15 +211,91 @@ JX.behavior('document-engine', function(config, statics) { JX.DOM.setContent(viewport, JX.$H(r.markup)); } + function blame(data) { + if (!data.blame.uri) { + return; + } + + if (!data.blame.value) { + new JX.Request(data.blame.uri, JX.bind(null, onblame, data)) + .send(); + return; + } + + var viewport = JX.$(data.viewportID); + + var cells = JX.DOM.scry(viewport, 'th'); + + for (var ii = 0; ii < cells.length; ii++) { + var cell = cells[ii]; + + var spec = cell.getAttribute('data-blame'); + if (!spec) { + continue; + } + + spec = spec.split(';'); + var type = spec[0]; + var lines = spec[1]; + + var content = null; + switch (type) { + case 'skip': + content = renderSkip(data.blame.value, lines); + break; + case 'info': + content = renderInfo(data.blame.value, lines); + break; + } + + JX.DOM.setContent(cell, content); + } + } + + function onblame(data, r) { + data.blame.value = r; + blame(data); + } + + function renderSkip(blame, lines) { + var commit = blame.blame[lines - 1]; + if (!commit) { + return null; + } + + var spec = blame.map[commit]; + + return JX.$H(spec.skip); + } + + function renderInfo(blame, lines) { + var commit = blame.blame[lines - 1]; + if (!commit) { + return null; + } + + var spec = blame.map[commit]; + + return JX.$H(spec.info); + } + if (!statics.initialized) { JX.Stratcom.listen('click', 'document-engine-view-dropdown', onmenu); statics.initialized = true; } - if (config && config.renderControlID) { - var control = JX.$(config.renderControlID); + if (config && config.controlID) { + var control = JX.$(config.controlID); var data = JX.Stratcom.getData(control); - onview(data, null, true); + + switch (config.next) { + case 'render': + onview(data, null, true); + break; + case 'blame': + blame(data); + break; + } } }); From 11664277b33dfc6cda281e683eb89efae515b6e7 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 9 Apr 2018 05:08:49 -0700 Subject: [PATCH 11/53] Make DocumentEngine source line linking behavior better when blame is shown Summary: Ref T13105. The line linker behavior currently has trouble identifying the line number when blame is active. Improve this, albeit not the most cleanly. Test Plan: Selected lines with blame on. Maniphest Tasks: T13105 Differential Revision: https://secure.phabricator.com/D19310 --- resources/celerity/map.php | 16 ++++++++-------- webroot/rsrc/js/core/behavior-line-linker.js | 17 ++++++++++++++++- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 4f4356cc63..d99e55fa06 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -471,7 +471,7 @@ return array( 'rsrc/js/core/behavior-keyboard-pager.js' => 'a8da01f0', 'rsrc/js/core/behavior-keyboard-shortcuts.js' => '01fca1f0', 'rsrc/js/core/behavior-lightbox-attachments.js' => '6b31879a', - 'rsrc/js/core/behavior-line-linker.js' => 'febf4ae7', + 'rsrc/js/core/behavior-line-linker.js' => '1e017314', 'rsrc/js/core/behavior-more.js' => 'a80d0378', 'rsrc/js/core/behavior-object-selector.js' => '77c1f0b0', 'rsrc/js/core/behavior-oncopy.js' => '2926fff2', @@ -634,7 +634,7 @@ return array( 'javelin-behavior-phabricator-gesture-example' => '558829c2', 'javelin-behavior-phabricator-keyboard-pager' => 'a8da01f0', 'javelin-behavior-phabricator-keyboard-shortcuts' => '01fca1f0', - 'javelin-behavior-phabricator-line-linker' => 'febf4ae7', + 'javelin-behavior-phabricator-line-linker' => '1e017314', 'javelin-behavior-phabricator-nav' => '836f966d', 'javelin-behavior-phabricator-notification-example' => '8ce821c5', 'javelin-behavior-phabricator-object-selector' => '77c1f0b0', @@ -998,6 +998,12 @@ return array( 'javelin-workflow', 'javelin-magical-init', ), + '1e017314' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-history', + ), '1f6794f6' => array( 'javelin-behavior', 'javelin-stratcom', @@ -2159,12 +2165,6 @@ return array( 'javelin-view-visitor', 'javelin-util', ), - 'febf4ae7' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-history', - ), ), 'packages' => array( 'conpherence.pkg.css' => array( diff --git a/webroot/rsrc/js/core/behavior-line-linker.js b/webroot/rsrc/js/core/behavior-line-linker.js index 6130bdd859..2506e205b8 100644 --- a/webroot/rsrc/js/core/behavior-line-linker.js +++ b/webroot/rsrc/js/core/behavior-line-linker.js @@ -20,7 +20,22 @@ JX.behavior('phabricator-line-linker', function() { } function getRowNumber(tr) { - var th = tr.firstChild; + // Starting from the left, find the rightmost "" tag among all + // "" tags at the start of the row. Our goal here is to skip over + // blame information in Diffusion. This could probably be significantly + // more graceful. + var th = null; + for (var ii = 0; ii < tr.childNodes.length; ii++) { + if (JX.DOM.isType(tr.childNodes[ii], 'th')) { + th = tr.childNodes[ii]; + continue; + } + break; + } + + if (!th) { + return null; + } // If the "" tag contains an "" with "data-n" that we're using // to prevent copy/paste of line numbers, use that. From eca7dc25f20be9ff20df46f13dda721849369082 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 9 Apr 2018 05:18:58 -0700 Subject: [PATCH 12/53] Use javelin_tag(), not phutil_tag(), to render revision blame tooltips properly Summary: Depends on D19310. Ref T13105. The "meta" value was not populating correctly because this used `phutil_tag()`. Test Plan: Will verify on `secure`. Maniphest Tasks: T13105 Differential Revision: https://secure.phabricator.com/D19311 --- .../diffusion/controller/DiffusionBlameController.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/applications/diffusion/controller/DiffusionBlameController.php b/src/applications/diffusion/controller/DiffusionBlameController.php index 758a865e0a..6d922c6322 100644 --- a/src/applications/diffusion/controller/DiffusionBlameController.php +++ b/src/applications/diffusion/controller/DiffusionBlameController.php @@ -145,9 +145,9 @@ final class DiffusionBlameController extends DiffusionController { 'href' => $commit->getURI(), 'sigil' => 'has-tooltip', 'meta' => array( - 'tip' => $this->renderCommitTooltip($commit, $handles), + 'tip' => $this->renderCommitTooltip($commit, $handles), 'align' => 'E', - 'size' => 600, + 'size' => 600, ), ), $commit->getLocalName()); @@ -158,20 +158,19 @@ final class DiffusionBlameController extends DiffusionController { ); if ($revision) { - $revision_link = phutil_tag( + $revision_link = javelin_tag( 'a', array( 'href' => $revision->getURI(), 'sigil' => 'has-tooltip', 'meta' => array( - 'tip' => $this->renderRevisionTooltip($revision, $handles), + 'tip' => $this->renderRevisionTooltip($revision, $handles), 'align' => 'E', - 'size' => 600, + 'size' => 600, ), ), $revision->getMonogram()); - $info = array( $info, ' / ', From eb80f0a2d95d99057e4e5ab72e427c1aeb362d85 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 9 Apr 2018 05:25:35 -0700 Subject: [PATCH 13/53] When you swap between document rendering engines, populate or redraw blame if appropriate Summary: Depends on D19311. Ref T13105. Currently, blame only renders on the initial request. Instead, redraw blame after swapping views. Test Plan: Swapped from "Source -> Hexdump -> Source" and "Hexdump -> Source". Saw blame on source in all cases. Maniphest Tasks: T13105 Differential Revision: https://secure.phabricator.com/D19312 --- resources/celerity/map.php | 14 +++++----- .../files/behavior-document-engine.js | 26 +++++++++++++++---- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index d99e55fa06..e58c5a8158 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -390,7 +390,7 @@ return array( 'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'f01586dc', 'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => '1db13e70', 'rsrc/js/application/drydock/drydock-live-operation-status.js' => '901935ef', - 'rsrc/js/application/files/behavior-document-engine.js' => 'ac52a3be', + 'rsrc/js/application/files/behavior-document-engine.js' => 'ed539253', 'rsrc/js/application/files/behavior-icon-composer.js' => '8499b6ab', 'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888', 'rsrc/js/application/harbormaster/behavior-harbormaster-log.js' => '191b4909', @@ -604,7 +604,7 @@ return array( 'javelin-behavior-diffusion-jump-to' => '73d09eef', 'javelin-behavior-diffusion-locate-file' => '6d3e1947', 'javelin-behavior-diffusion-pull-lastmodified' => 'f01586dc', - 'javelin-behavior-document-engine' => 'ac52a3be', + 'javelin-behavior-document-engine' => 'ed539253', 'javelin-behavior-doorkeeper-tag' => '1db13e70', 'javelin-behavior-drydock-live-operation-status' => '901935ef', 'javelin-behavior-durable-column' => '2ae077e1', @@ -1764,11 +1764,6 @@ return array( 'javelin-dom', 'javelin-typeahead-normalizer', ), - 'ac52a3be' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - ), 'acd29eee' => array( 'javelin-behavior', 'javelin-stratcom', @@ -2121,6 +2116,11 @@ return array( 'javelin-stratcom', 'javelin-vector', ), + 'ed539253' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + ), 'edf8a145' => array( 'javelin-behavior', 'javelin-uri', diff --git a/webroot/rsrc/js/application/files/behavior-document-engine.js b/webroot/rsrc/js/application/files/behavior-document-engine.js index fa5ed8f860..ea01ba3aca 100644 --- a/webroot/rsrc/js/application/files/behavior-document-engine.js +++ b/webroot/rsrc/js/application/files/behavior-document-engine.js @@ -151,7 +151,7 @@ JX.behavior('document-engine', function(config, statics) { } data.sequence = (data.sequence || 0) + 1; - var handler = JX.bind(null, onrender, data, data.sequence); + var handler = JX.bind(null, onrender, data, data.sequence, spec); data.viewKey = spec.viewKey; @@ -190,7 +190,7 @@ JX.behavior('document-engine', function(config, statics) { JX.DOM.setContent(viewport, JX.$H(spec.loadingMarkup)); } - function onrender(data, sequence, r) { + function onrender(data, sequence, spec, r) { // If this isn't the most recent request we sent, throw it away. This can // happen if the user makes multiple selections from the menu while we are // still rendering the first view. @@ -209,19 +209,34 @@ JX.behavior('document-engine', function(config, statics) { data.loadingView = false; JX.DOM.setContent(viewport, JX.$H(r.markup)); + + // If this engine supports rendering blame, populate or draw it. + if (spec.canBlame) { + blame(data); + } } function blame(data) { + // If the rendering engine can't handle blame, bail. if (!data.blame.uri) { return; } - if (!data.blame.value) { - new JX.Request(data.blame.uri, JX.bind(null, onblame, data)) - .send(); + // If we already have an outstanding request for blame data, bail. + if (data.blame.request) { return; } + // If we don't have blame data yet, request it and then try rendering + // again later. + if (!data.blame.value) { + var req = new JX.Request(data.blame.uri, JX.bind(null, onblame, data)); + data.blame.request = req; + req.send(); + return; + } + + // We're ready to render. var viewport = JX.$(data.viewportID); var cells = JX.DOM.scry(viewport, 'th'); @@ -253,6 +268,7 @@ JX.behavior('document-engine', function(config, statics) { } function onblame(data, r) { + data.blame.request = null; data.blame.value = r; blame(data); } From cf75d63b49a7181d6c6eb8dd5de78c77784db340 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 9 Apr 2018 05:38:06 -0700 Subject: [PATCH 14/53] When lines 12, 13, 14, etc all blame to the same change, only show it once Summary: Depends on D19312. Ref T13105. For readability, render only one link for each contiguous block of changes. Also make the actual rendering logic a little more defensible. Test Plan: Viewed some files with blame, saw one render per chunk instead of one per line. Maniphest Tasks: T13105 Differential Revision: https://secure.phabricator.com/D19313 --- resources/celerity/map.php | 14 +-- src/view/layout/PhabricatorSourceCodeView.php | 5 +- .../files/behavior-document-engine.js | 86 +++++++++++-------- 3 files changed, 61 insertions(+), 44 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index e58c5a8158..00e394851c 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -390,7 +390,7 @@ return array( 'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'f01586dc', 'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => '1db13e70', 'rsrc/js/application/drydock/drydock-live-operation-status.js' => '901935ef', - 'rsrc/js/application/files/behavior-document-engine.js' => 'ed539253', + 'rsrc/js/application/files/behavior-document-engine.js' => '6760beb4', 'rsrc/js/application/files/behavior-icon-composer.js' => '8499b6ab', 'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888', 'rsrc/js/application/harbormaster/behavior-harbormaster-log.js' => '191b4909', @@ -604,7 +604,7 @@ return array( 'javelin-behavior-diffusion-jump-to' => '73d09eef', 'javelin-behavior-diffusion-locate-file' => '6d3e1947', 'javelin-behavior-diffusion-pull-lastmodified' => 'f01586dc', - 'javelin-behavior-document-engine' => 'ed539253', + 'javelin-behavior-document-engine' => '6760beb4', 'javelin-behavior-doorkeeper-tag' => '1db13e70', 'javelin-behavior-drydock-live-operation-status' => '901935ef', 'javelin-behavior-durable-column' => '2ae077e1', @@ -1398,6 +1398,11 @@ return array( 'javelin-json', 'phuix-form-control-view', ), + '6760beb4' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + ), '680ea2c8' => array( 'javelin-install', 'javelin-dom', @@ -2116,11 +2121,6 @@ return array( 'javelin-stratcom', 'javelin-vector', ), - 'ed539253' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - ), 'edf8a145' => array( 'javelin-behavior', 'javelin-uri', diff --git a/src/view/layout/PhabricatorSourceCodeView.php b/src/view/layout/PhabricatorSourceCodeView.php index 19fa17c582..e6fb1803bc 100644 --- a/src/view/layout/PhabricatorSourceCodeView.php +++ b/src/view/layout/PhabricatorSourceCodeView.php @@ -130,8 +130,8 @@ final class PhabricatorSourceCodeView extends AphrontView { $lines = idx($blame_map, $line_number); if ($lines) { - $skip_blame = 'skip;'.$lines; - $info_blame = 'info;'.$lines; + $skip_blame = 'skip'; + $info_blame = 'info'; } else { $skip_blame = null; $info_blame = null; @@ -149,6 +149,7 @@ final class PhabricatorSourceCodeView extends AphrontView { array( 'class' => 'phabricator-source-blame-info', 'data-blame' => $info_blame, + 'data-blame-lines' => $lines, )), ); } else { diff --git a/webroot/rsrc/js/application/files/behavior-document-engine.js b/webroot/rsrc/js/application/files/behavior-document-engine.js index ea01ba3aca..eb4216ee15 100644 --- a/webroot/rsrc/js/application/files/behavior-document-engine.js +++ b/webroot/rsrc/js/application/files/behavior-document-engine.js @@ -239,31 +239,48 @@ JX.behavior('document-engine', function(config, statics) { // We're ready to render. var viewport = JX.$(data.viewportID); - var cells = JX.DOM.scry(viewport, 'th'); + var row_nodes = JX.DOM.scry(viewport, 'tr'); + var row_list = []; + var ii; - for (var ii = 0; ii < cells.length; ii++) { - var cell = cells[ii]; + for (ii = 0; ii < row_nodes.length; ii++) { + var row = {}; + var keep = false; + var node = row_nodes[ii]; - var spec = cell.getAttribute('data-blame'); - if (!spec) { - continue; + for (var jj = 0; jj < node.childNodes.length; jj++) { + var child = node.childNodes[jj]; + + if (!JX.DOM.isType(child, 'th')) { + continue; + } + + var spec = child.getAttribute('data-blame'); + if (spec) { + row[spec] = child; + keep = true; + } + + if (spec === 'info') { + row.lines = child.getAttribute('data-blame-lines'); + } } - spec = spec.split(';'); - var type = spec[0]; - var lines = spec[1]; - - var content = null; - switch (type) { - case 'skip': - content = renderSkip(data.blame.value, lines); - break; - case 'info': - content = renderInfo(data.blame.value, lines); - break; + if (keep) { + row_list.push(row); } + } - JX.DOM.setContent(cell, content); + var last = null; + for (ii = 0; ii < row_list.length; ii++) { + var commit = data.blame.value.blame[row_list[ii].lines - 1]; + row_list[ii].commit = commit; + row_list[ii].last = last; + last = commit; + } + + for (ii = 0; ii < row_list.length; ii++) { + renderBlame(row_list[ii], data.blame.value); } } @@ -273,26 +290,25 @@ JX.behavior('document-engine', function(config, statics) { blame(data); } - function renderSkip(blame, lines) { - var commit = blame.blame[lines - 1]; - if (!commit) { - return null; + function renderBlame(row, blame) { + var spec = blame.map[row.commit]; + + + var info = null; + var skip = null; + + if (spec && (row.commit != row.last)) { + skip = JX.$H(spec.skip); + info = JX.$H(spec.info); } - var spec = blame.map[commit]; - - return JX.$H(spec.skip); - } - - function renderInfo(blame, lines) { - var commit = blame.blame[lines - 1]; - if (!commit) { - return null; + if (row.skip) { + JX.DOM.setContent(row.skip, skip); } - var spec = blame.map[commit]; - - return JX.$H(spec.info); + if (row.info) { + JX.DOM.setContent(row.info, info); + } } if (!statics.initialized) { From 472bc3d90a595f38dceb5d77d7e70595bfa174dd Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 9 Apr 2018 06:05:40 -0700 Subject: [PATCH 15/53] Colorize lines in blame under DocumentEngine, to show relative age of changes Summary: Depends on D19313. Ref T13105. Fixes T13015. We lost the coloration for ages in the switch to Document Engine. Restore it, and use a wider range of colors to make the information more clear. Test Plan: Viewed some blame, saw a nice explosion of bright colors. This is a cornerstone of good design. Maniphest Tasks: T13105, T13015 Differential Revision: https://secure.phabricator.com/D19314 --- resources/celerity/map.php | 18 +++---- .../controller/DiffusionBlameController.php | 15 +++++- .../layout/phabricator-source-code-view.css | 16 ++++++- .../files/behavior-document-engine.js | 48 ++++++++++++++++++- 4 files changed, 84 insertions(+), 13 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 00e394851c..6c1f2c0236 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -119,7 +119,7 @@ return array( 'rsrc/css/font/font-lato.css' => 'c7ccd872', 'rsrc/css/font/phui-font-icon-base.css' => '870a7360', 'rsrc/css/layout/phabricator-filetree-view.css' => 'b912ad97', - 'rsrc/css/layout/phabricator-source-code-view.css' => 'af54e277', + 'rsrc/css/layout/phabricator-source-code-view.css' => 'a526a787', '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' => '1863cc6e', @@ -390,7 +390,7 @@ return array( 'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'f01586dc', 'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => '1db13e70', 'rsrc/js/application/drydock/drydock-live-operation-status.js' => '901935ef', - 'rsrc/js/application/files/behavior-document-engine.js' => '6760beb4', + 'rsrc/js/application/files/behavior-document-engine.js' => '0333c0b6', 'rsrc/js/application/files/behavior-icon-composer.js' => '8499b6ab', 'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888', 'rsrc/js/application/harbormaster/behavior-harbormaster-log.js' => '191b4909', @@ -604,7 +604,7 @@ return array( 'javelin-behavior-diffusion-jump-to' => '73d09eef', 'javelin-behavior-diffusion-locate-file' => '6d3e1947', 'javelin-behavior-diffusion-pull-lastmodified' => 'f01586dc', - 'javelin-behavior-document-engine' => '6760beb4', + 'javelin-behavior-document-engine' => '0333c0b6', 'javelin-behavior-doorkeeper-tag' => '1db13e70', 'javelin-behavior-drydock-live-operation-status' => '901935ef', 'javelin-behavior-durable-column' => '2ae077e1', @@ -780,7 +780,7 @@ return array( 'phabricator-search-results-css' => '505dd8cf', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-slowvote-css' => 'a94b7230', - 'phabricator-source-code-view-css' => 'af54e277', + 'phabricator-source-code-view-css' => 'a526a787', 'phabricator-standard-page-view' => '34ee718b', 'phabricator-textareautils' => '320810c8', 'phabricator-title' => '485aaa6c', @@ -909,6 +909,11 @@ return array( 'javelin-behavior', 'javelin-uri', ), + '0333c0b6' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + ), '040fce04' => array( 'javelin-behavior', 'javelin-request', @@ -1398,11 +1403,6 @@ return array( 'javelin-json', 'phuix-form-control-view', ), - '6760beb4' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - ), '680ea2c8' => array( 'javelin-install', 'javelin-dom', diff --git a/src/applications/diffusion/controller/DiffusionBlameController.php b/src/applications/diffusion/controller/DiffusionBlameController.php index 6d922c6322..66403a8af9 100644 --- a/src/applications/diffusion/controller/DiffusionBlameController.php +++ b/src/applications/diffusion/controller/DiffusionBlameController.php @@ -80,7 +80,9 @@ final class DiffusionBlameController extends DiffusionController { $handles = $viewer->loadHandles($handle_phids); + $map = array(); + $epochs = array(); foreach ($identifiers as $identifier) { $revision_id = idx($revision_map, $identifier); if ($revision_id) { @@ -173,23 +175,34 @@ final class DiffusionBlameController extends DiffusionController { $info = array( $info, - ' / ', + " \xC2\xB7 ", $revision_link, ); } + $epoch = $commit->getEpoch(); + $epochs[] = $epoch; + $data = array( 'skip' => $skip_link, 'info' => hsprintf('%s', $info), + 'epoch' => $epoch, ); $map[$identifier] = $data; } + $epoch_min = min($epochs); + $epoch_max = max($epochs); + return id(new AphrontAjaxResponse())->setContent( array( 'blame' => $blame, 'map' => $map, + 'epoch' => array( + 'min' => $epoch_min, + 'max' => $epoch_max, + ), )); } diff --git a/webroot/rsrc/css/layout/phabricator-source-code-view.css b/webroot/rsrc/css/layout/phabricator-source-code-view.css index ae9793c56f..5eb2b36c8e 100644 --- a/webroot/rsrc/css/layout/phabricator-source-code-view.css +++ b/webroot/rsrc/css/layout/phabricator-source-code-view.css @@ -85,11 +85,18 @@ th.phabricator-source-line a:hover { } .phabricator-source-blame-info { - color: {$lightgreytext}; white-space: nowrap; min-width: 130px; border-right: 1px solid {$paste.border}; padding-right: 8px; + + vertical-align: middle; + color: #ffffff; +} + +.phabricator-source-blame-info a { + color: {$darkbluetext}; + text-shadow: 1px 1px rgba(0, 0, 0, 0.111); } .phabricator-source-blame-skip a { @@ -98,14 +105,19 @@ th.phabricator-source-line a:hover { padding: 2px 8px; } +.phabricator-source-blame-skip a .phui-icon-view { + color: {$darkbluetext}; +} + .device-desktop .phabricator-source-blame-skip a:hover { background: {$bluebackground}; } .phabricator-source-blame-author { display: inline-block; + vertical-align: middle; padding: 0; - margin: 2px 6px -4px 8px; + margin: 0 6px 0 8px; width: 16px; height: 16px; background-size: 100% 100%; diff --git a/webroot/rsrc/js/application/files/behavior-document-engine.js b/webroot/rsrc/js/application/files/behavior-document-engine.js index eb4216ee15..2fc4887408 100644 --- a/webroot/rsrc/js/application/files/behavior-document-engine.js +++ b/webroot/rsrc/js/application/files/behavior-document-engine.js @@ -293,7 +293,6 @@ JX.behavior('document-engine', function(config, statics) { function renderBlame(row, blame) { var spec = blame.map[row.commit]; - var info = null; var skip = null; @@ -309,6 +308,53 @@ JX.behavior('document-engine', function(config, statics) { if (row.info) { JX.DOM.setContent(row.info, info); } + + var epoch_range = (blame.epoch.max - blame.epoch.min); + + var epoch_value; + if (!epoch_range) { + epoch_value = 1; + } else { + epoch_value = (spec.epoch - blame.epoch.min) / epoch_range; + } + + var h_min = 0.04; + var h_max = 0.44; + var h = h_min + ((h_max - h_min) * epoch_value); + + var s = 0.44; + + var v_min = 0.92; + var v_max = 1.00; + var v = v_min + ((v_max - v_min) * epoch_value); + + row.info.style.background = getHSV(h, s, v); + } + + function getHSV(h, s, v) { + var r, g, b, i, f, p, q, t; + + i = Math.floor(h * 6); + f = h * 6 - i; + p = v * (1 - s); + q = v * (1 - f * s); + t = v * (1 - (1 - f) * s); + + switch (i % 6) { + case 0: r = v, g = t, b = p; break; + case 1: r = q, g = v, b = p; break; + case 2: r = p, g = v, b = t; break; + case 3: r = p, g = q, b = v; break; + case 4: r = t, g = p, b = v; break; + case 5: r = v, g = p, b = q; break; + } + + r = Math.round(r * 255); + g = Math.round(g * 255); + b = Math.round(b * 255); + + + return 'rgb(' + r + ', ' + g + ', ' + b + ')'; } if (!statics.initialized) { From 72ab8640c5d19f4e9eb8745f7501ff3e26aa6ac3 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 9 Apr 2018 09:54:57 -0700 Subject: [PATCH 16/53] Narrowly fix web UI fatal for "almanac.service.edit" Conduit API method Summary: See T13120. See T12414. See PHI145. See PHI473. Almanac services require a type before they can do anything, and EditEngine currently builds one with no type. We then fatal when trying to do mundane things like generate documentation. Instead, build a generic but complete Service for documentation generation in the web UI. This is similar to the previous Drydock Blueprint change from D18849 (or some earlier diff in that series). (You still probably can't use this method to //create// a service; I'll fix that in the next change.) Test Plan: - Viewed "almanac.service.edit" in the web UI. - Before: immediate fatal ("No Almanac service type "" exists!"). - After: Page works. No claims about the method doing anything useful. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D19315 --- .../almanac/editor/AlmanacServiceEditEngine.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/applications/almanac/editor/AlmanacServiceEditEngine.php b/src/applications/almanac/editor/AlmanacServiceEditEngine.php index e64cfce3a2..7f5ad698fd 100644 --- a/src/applications/almanac/editor/AlmanacServiceEditEngine.php +++ b/src/applications/almanac/editor/AlmanacServiceEditEngine.php @@ -41,6 +41,12 @@ final class AlmanacServiceEditEngine return AlmanacService::initializeNewService($service_type); } + protected function newEditableObjectForDocumentation() { + $service_type = new AlmanacCustomServiceType(); + $this->setServiceType($service_type->getServiceTypeConstant()); + return $this->newEditableObject(); + } + protected function newObjectQuery() { return new AlmanacServiceQuery(); } From 16802117021816a7b40df8a5e0ef9ad483fc72ad Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 9 Apr 2018 10:06:42 -0700 Subject: [PATCH 17/53] Remove dead "Service Lock" code from Almanac Summary: Depends on D19315. Ref T13120. Ref T12414. See PHI145. See PHI473. I want to move Almanac services to ModularTransactions but ran into this old piece of dead/unused code along the way. Long ago, Almanac services could be individually "locked", but this didn't really work out very well. It was replaced by "Can Manage Cluster Services" in D15339 and prior changes, but not all of the old "Lock" code got cleaned up. I don't expect to restore this feature, so clean it up now. Test Plan: - Grepped for `AlmanacServiceTransaction::TYPE_LOCK`, `TYPE_LOCK`, etc. - Grepped for `updateServiceLock()`, no callsites. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13120, T12414 Differential Revision: https://secure.phabricator.com/D19316 --- .../management/AlmanacManagementWorkflow.php | 16 ---------------- .../storage/AlmanacServiceTransaction.php | 12 ------------ 2 files changed, 28 deletions(-) diff --git a/src/applications/almanac/management/AlmanacManagementWorkflow.php b/src/applications/almanac/management/AlmanacManagementWorkflow.php index 0f1dd6d773..5d466293c4 100644 --- a/src/applications/almanac/management/AlmanacManagementWorkflow.php +++ b/src/applications/almanac/management/AlmanacManagementWorkflow.php @@ -26,20 +26,4 @@ abstract class AlmanacManagementWorkflow return $services; } - protected function updateServiceLock(AlmanacService $service, $lock) { - $almanac_phid = id(new PhabricatorAlmanacApplication())->getPHID(); - - $xaction = id(new AlmanacServiceTransaction()) - ->setTransactionType(AlmanacServiceTransaction::TYPE_LOCK) - ->setNewValue((int)$lock); - - $editor = id(new AlmanacServiceEditor()) - ->setActor($this->getViewer()) - ->setActingAsPHID($almanac_phid) - ->setContentSource($this->newContentSource()) - ->setContinueOnMissingFields(true); - - $editor->applyTransactions($service, array($xaction)); - } - } diff --git a/src/applications/almanac/storage/AlmanacServiceTransaction.php b/src/applications/almanac/storage/AlmanacServiceTransaction.php index a668f860fa..d357641cb6 100644 --- a/src/applications/almanac/storage/AlmanacServiceTransaction.php +++ b/src/applications/almanac/storage/AlmanacServiceTransaction.php @@ -4,7 +4,6 @@ final class AlmanacServiceTransaction extends AlmanacTransaction { const TYPE_NAME = 'almanac:service:name'; - const TYPE_LOCK = 'almanac:service:lock'; public function getApplicationTransactionType() { return AlmanacServicePHIDType::TYPECONST; @@ -30,17 +29,6 @@ final class AlmanacServiceTransaction $new); } break; - case self::TYPE_LOCK: - if ($new) { - return pht( - '%s locked this service.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s unlocked this service.', - $this->renderHandleLink($author_phid)); - } - break; } return parent::getTitle(); From 4c4a5a7656e16aa34845567b83e1251355c505b4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 9 Apr 2018 16:00:46 -0700 Subject: [PATCH 18/53] Fix the wrapping/padding behavior of Remarkup code block headers more thoroughly? Summary: Ref T13118. The first fix there fixed Safari, but made Chrome weird. Try this? Test Plan: Viewed a code block with `name=...` in Safari, Firefox and Chrome and saw consistent display without weird wrappping. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13118 Differential Revision: https://secure.phabricator.com/D19319 --- resources/celerity/map.php | 6 +++--- webroot/rsrc/css/core/remarkup.css | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 6c1f2c0236..bb1dcfc5cd 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'e68cf1fa', 'conpherence.pkg.js' => '15191c65', - 'core.pkg.css' => '4a83e174', + 'core.pkg.css' => '3ced0b1d', 'core.pkg.js' => '1ea38af8', 'differential.pkg.css' => '06dc617c', 'differential.pkg.js' => 'c2ca903a', @@ -111,7 +111,7 @@ return array( 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', 'rsrc/css/core/core.css' => '62fa3ace', - 'rsrc/css/core/remarkup.css' => '924fc97d', + 'rsrc/css/core/remarkup.css' => 'bff43c81', 'rsrc/css/core/syntax.css' => 'e9c95dd4', 'rsrc/css/core/z-index.css' => '9d8f7c4b', 'rsrc/css/diviner/diviner-shared.css' => '896f1d43', @@ -776,7 +776,7 @@ return array( 'phabricator-object-selector-css' => '85ee8ce6', 'phabricator-phtize' => 'd254d646', 'phabricator-prefab' => '77b0ae28', - 'phabricator-remarkup-css' => '924fc97d', + 'phabricator-remarkup-css' => 'bff43c81', 'phabricator-search-results-css' => '505dd8cf', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-slowvote-css' => 'a94b7230', diff --git a/webroot/rsrc/css/core/remarkup.css b/webroot/rsrc/css/core/remarkup.css index dad202a9c8..686ec632dc 100644 --- a/webroot/rsrc/css/core/remarkup.css +++ b/webroot/rsrc/css/core/remarkup.css @@ -31,7 +31,7 @@ font-size: 13px; font-weight: bold; background: rgba({$alphablue},0.08); - display: inline-block; + display: table-cell; border-top-left-radius: 3px; border-top-right-radius: 3px; overflow: hidden; From d398bcd67c76e7786f2d3cc238a8555275e77458 Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Tue, 10 Apr 2018 09:57:13 -0700 Subject: [PATCH 19/53] Fix argument ordering in error message Summary: Before: ``` $ ./config set phabricator.base-uri local.phacility.com:8080 Usage Exception: Config option 'http://' is invalid. The URI must start with https://' or 'phabricator.base-uri'. ``` After: ``` $ ./config set phabricator.base-uri local.phacility.com:8080 Usage Exception: Config option 'phabricator.base-uri' is invalid. The URI must start with http://' or 'https://'. ``` Test Plan: See above Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D19330 --- .../option/PhabricatorCoreConfigOptions.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/applications/config/option/PhabricatorCoreConfigOptions.php b/src/applications/config/option/PhabricatorCoreConfigOptions.php index 0c9e69ebf2..08266217ea 100644 --- a/src/applications/config/option/PhabricatorCoreConfigOptions.php +++ b/src/applications/config/option/PhabricatorCoreConfigOptions.php @@ -268,24 +268,24 @@ EOREMARKUP if ($protocol !== 'http' && $protocol !== 'https') { throw new PhabricatorConfigValidationException( pht( - "Config option '%s' is invalid. The URI must start with ". - "%s' or '%s'.", + 'Config option "%s" is invalid. The URI must start with '. + '"%s" or "%s".', + $key, 'http://', - 'https://', - $key)); + 'https://')); } $domain = $uri->getDomain(); if (strpos($domain, '.') === false) { throw new PhabricatorConfigValidationException( pht( - "Config option '%s' is invalid. The URI must contain a dot ". - "('%s'), like '%s', not just a bare name like '%s'. Some web ". - "browsers will not set cookies on domains with no TLD.", + 'Config option "%s" is invalid. The URI must contain a dot '. + '("%s"), like "%s", not just a bare name like "%s". Some web '. + 'browsers will not set cookies on domains with no TLD.', + $key, '.', 'http://example.com/', - 'http://example/', - $key)); + 'http://example/')); } $path = $uri->getPath(); From 0755482bf07be95f4e91b1841c82da0aa978eedf Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Wed, 11 Apr 2018 08:46:38 -0700 Subject: [PATCH 20/53] Add transactions for installing/uninstalling applications Summary: Fixes T11476. Test Plan: - Installed/uninstalled the Conpherence application - Observed correct timeline stories - Observed correct config in database - Observed 404 for application page Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T11476 Differential Revision: https://secure.phabricator.com/D19339 --- src/__phutil_library_map__.php | 2 + ...bricatorApplicationUninstallController.php | 75 +++++++++--------- ...atorApplicationPolicyChangeTransaction.php | 2 +- ...ricatorApplicationUninstallTransaction.php | 79 +++++++++++++++++++ 4 files changed, 120 insertions(+), 38 deletions(-) create mode 100644 src/applications/meta/xactions/PhabricatorApplicationUninstallTransaction.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 4663aed50d..e634bb9a88 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2069,6 +2069,7 @@ phutil_register_library_map(array( 'PhabricatorApplicationTransactionWarningException' => 'applications/transactions/exception/PhabricatorApplicationTransactionWarningException.php', 'PhabricatorApplicationTransactionWarningResponse' => 'applications/transactions/response/PhabricatorApplicationTransactionWarningResponse.php', 'PhabricatorApplicationUninstallController' => 'applications/meta/controller/PhabricatorApplicationUninstallController.php', + 'PhabricatorApplicationUninstallTransaction' => 'applications/meta/xactions/PhabricatorApplicationUninstallTransaction.php', 'PhabricatorApplicationsApplication' => 'applications/meta/application/PhabricatorApplicationsApplication.php', 'PhabricatorApplicationsController' => 'applications/meta/controller/PhabricatorApplicationsController.php', 'PhabricatorApplicationsListController' => 'applications/meta/controller/PhabricatorApplicationsListController.php', @@ -7533,6 +7534,7 @@ phutil_register_library_map(array( 'PhabricatorApplicationTransactionWarningException' => 'Exception', 'PhabricatorApplicationTransactionWarningResponse' => 'AphrontProxyResponse', 'PhabricatorApplicationUninstallController' => 'PhabricatorApplicationsController', + 'PhabricatorApplicationUninstallTransaction' => 'PhabricatorApplicationTransactionType', 'PhabricatorApplicationsApplication' => 'PhabricatorApplication', 'PhabricatorApplicationsController' => 'PhabricatorController', 'PhabricatorApplicationsListController' => 'PhabricatorApplicationsController', diff --git a/src/applications/meta/controller/PhabricatorApplicationUninstallController.php b/src/applications/meta/controller/PhabricatorApplicationUninstallController.php index 68518413bf..09f3e92ee5 100644 --- a/src/applications/meta/controller/PhabricatorApplicationUninstallController.php +++ b/src/applications/meta/controller/PhabricatorApplicationUninstallController.php @@ -3,17 +3,15 @@ final class PhabricatorApplicationUninstallController extends PhabricatorApplicationsController { - private $application; - private $action; - public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); - $this->action = $request->getURIData('action'); - $this->application = $request->getURIData('application'); + $user = $request->getUser(); + $action = $request->getURIData('action'); + $application_name = $request->getURIData('application'); - $selected = id(new PhabricatorApplicationQuery()) + $application = id(new PhabricatorApplicationQuery()) ->setViewer($viewer) - ->withClasses(array($this->application)) + ->withClasses(array($application_name)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, @@ -21,11 +19,11 @@ final class PhabricatorApplicationUninstallController )) ->executeOne(); - if (!$selected) { + if (!$application) { return new Aphront404Response(); } - $view_uri = $this->getApplicationURI('view/'.$this->application); + $view_uri = $this->getApplicationURI('view/'.$application_name); $prototypes_enabled = PhabricatorEnv::getEnvConfig( 'phabricator.show-prototypes'); @@ -34,7 +32,7 @@ final class PhabricatorApplicationUninstallController ->setUser($viewer) ->addCancelButton($view_uri); - if ($selected->isPrototype() && !$prototypes_enabled) { + if ($application->isPrototype() && !$prototypes_enabled) { $dialog ->setTitle(pht('Prototypes Not Enabled')) ->appendChild( @@ -46,18 +44,40 @@ final class PhabricatorApplicationUninstallController } if ($request->isDialogFormPost()) { - $this->manageApplication(); - return id(new AphrontRedirectResponse())->setURI($view_uri); + $xactions = array(); + $template = $application->getApplicationTransactionTemplate(); + $xactions[] = id(clone $template) + ->setTransactionType( + PhabricatorApplicationUninstallTransaction::TRANSACTIONTYPE) + ->setNewValue($action); + + $editor = id(new PhabricatorApplicationEditor()) + ->setActor($user) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true); + + try { + $editor->applyTransactions($application, $xactions); + return id(new AphrontRedirectResponse())->setURI($view_uri); + } catch (PhabricatorApplicationTransactionValidationException $ex) { + $validation_exception = $ex; + } + + return $this->newDialog() + ->setTitle(pht('Validation Failed')) + ->setValidationException($validation_exception) + ->addCancelButton($view_uri); } - if ($this->action == 'install') { - if ($selected->canUninstall()) { + if ($action == 'install') { + if ($application->canUninstall()) { $dialog ->setTitle(pht('Confirmation')) ->appendChild( pht( 'Install %s application?', - $selected->getName())) + $application->getName())) ->addSubmitButton(pht('Install')); } else { @@ -66,10 +86,10 @@ final class PhabricatorApplicationUninstallController ->appendChild(pht('You cannot install an installed application.')); } } else { - if ($selected->canUninstall()) { + if ($application->canUninstall()) { $dialog->setTitle(pht('Really Uninstall Application?')); - if ($selected instanceof PhabricatorHomeApplication) { + if ($application instanceof PhabricatorHomeApplication) { $dialog ->appendParagraph( pht( @@ -86,7 +106,7 @@ final class PhabricatorApplicationUninstallController ->appendParagraph( pht( 'Really uninstall the %s application?', - $selected->getName())) + $application->getName())) ->addSubmitButton(pht('Uninstall')); } } else { @@ -101,23 +121,4 @@ final class PhabricatorApplicationUninstallController return id(new AphrontDialogResponse())->setDialog($dialog); } - public function manageApplication() { - $key = 'phabricator.uninstalled-applications'; - $config_entry = PhabricatorConfigEntry::loadConfigEntry($key); - $list = $config_entry->getValue(); - $uninstalled = PhabricatorEnv::getEnvConfig($key); - - if (isset($uninstalled[$this->application])) { - unset($list[$this->application]); - } else { - $list[$this->application] = true; - } - - PhabricatorConfigEditor::storeNewValue( - $this->getViewer(), - $config_entry, - $list, - PhabricatorContentSource::newFromRequest($this->getRequest())); - } - } diff --git a/src/applications/meta/xactions/PhabricatorApplicationPolicyChangeTransaction.php b/src/applications/meta/xactions/PhabricatorApplicationPolicyChangeTransaction.php index dca2f5a795..91b5c73249 100644 --- a/src/applications/meta/xactions/PhabricatorApplicationPolicyChangeTransaction.php +++ b/src/applications/meta/xactions/PhabricatorApplicationPolicyChangeTransaction.php @@ -14,7 +14,7 @@ final class PhabricatorApplicationPolicyChangeTransaction return $application->getPolicy($capability); } - public function applyInternalEffects($object, $value) { + public function applyExternalEffects($object, $value) { $application = $object; $user = $this->getActor(); diff --git a/src/applications/meta/xactions/PhabricatorApplicationUninstallTransaction.php b/src/applications/meta/xactions/PhabricatorApplicationUninstallTransaction.php new file mode 100644 index 0000000000..12fbc8ebd4 --- /dev/null +++ b/src/applications/meta/xactions/PhabricatorApplicationUninstallTransaction.php @@ -0,0 +1,79 @@ +getValue(); + $uninstalled = PhabricatorEnv::getEnvConfig($key); + + if (isset($uninstalled[get_class($object)])) { + return 'uninstalled'; + } else { + return 'installed'; + } + } + + public function generateNewValue($object, $value) { + if ($value === 'uninstall') { + return 'uninstalled'; + } else { + return 'installed'; + } + } + + public function applyExternalEffects($object, $value) { + $application = $object; + $user = $this->getActor(); + + $key = 'phabricator.uninstalled-applications'; + $config_entry = PhabricatorConfigEntry::loadConfigEntry($key); + $list = $config_entry->getValue(); + $uninstalled = PhabricatorEnv::getEnvConfig($key); + + if (isset($uninstalled[get_class($application)])) { + unset($list[get_class($application)]); + } else { + $list[get_class($application)] = true; + } + + $editor = $this->getEditor(); + $content_source = $editor->getContentSource(); + PhabricatorConfigEditor::storeNewValue( + $user, + $config_entry, + $list, + $content_source); + } + + public function getTitle() { + if ($this->getNewValue() === 'uninstalled') { + return pht( + '%s uninstalled this application.', + $this->renderAuthor()); + } else { + return pht( + '%s installed this application.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + if ($this->getNewValue() === 'uninstalled') { + return pht( + '%s uninstalled %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s installed %s.', + $this->renderAuthor(), + $this->renderObject()); + } + } + +} From c428f60a97f2fb8558fc1fcdf1ffcc9f2262d33f Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 9 Apr 2018 10:05:39 -0700 Subject: [PATCH 21/53] Partially modularize AlmanacService transactions Summary: Ref T13120. Ref T12414. See PHI145. See PHI473. This partially modernizes AlmanacService transactions by moving them to ModularTransactions. This isn't complete because the "update property" and "remove property" transactions aren't modularized. They still //work//, since the parent Editor implements them, but they no longer render properly on the timeline since the `Transaction` object no longer has rendering logic for them. Tentatively, I'm going to try to convert the rest of the Almanac objects and then modularize those transactions. (Currently, all of Binding, Device, Namespace and Service support properties, although they can only actually be edited on Service, Device and Binding.) If that turns out to be really tricky for some reason I can just copy/paste the timeline rendering for now, but I think it won't be too hard. Test Plan: - Created and edited Services. - Tried to create a service with: a bad name, no name, a name which put it in a namespace I can't edit (got errors in all cases). - Edited and removed properties. The edits worked, the timeline just renders a generic story now ('X edited this object (transaction type "almanac:property:update").'). Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13120, T12414 Differential Revision: https://secure.phabricator.com/D19317 --- src/__phutil_library_map__.php | 8 +- .../editor/AlmanacServiceEditEngine.php | 2 +- .../almanac/editor/AlmanacServiceEditor.php | 145 +----------------- .../storage/AlmanacServiceTransaction.php | 35 ++--- .../xaction/AlmanacServiceNameTransaction.php | 87 +++++++++++ .../xaction/AlmanacServiceTransactionType.php | 4 + .../xaction/AlmanacTransactionType.php | 4 + 7 files changed, 121 insertions(+), 164 deletions(-) create mode 100644 src/applications/almanac/xaction/AlmanacServiceNameTransaction.php create mode 100644 src/applications/almanac/xaction/AlmanacServiceTransactionType.php create mode 100644 src/applications/almanac/xaction/AlmanacTransactionType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index e634bb9a88..fe8e000041 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -114,6 +114,7 @@ phutil_register_library_map(array( 'AlmanacServiceEditor' => 'applications/almanac/editor/AlmanacServiceEditor.php', 'AlmanacServiceListController' => 'applications/almanac/controller/AlmanacServiceListController.php', 'AlmanacServiceNameNgrams' => 'applications/almanac/storage/AlmanacServiceNameNgrams.php', + 'AlmanacServiceNameTransaction' => 'applications/almanac/xaction/AlmanacServiceNameTransaction.php', 'AlmanacServicePHIDType' => 'applications/almanac/phid/AlmanacServicePHIDType.php', 'AlmanacServicePropertyEditEngine' => 'applications/almanac/editor/AlmanacServicePropertyEditEngine.php', 'AlmanacServiceQuery' => 'applications/almanac/query/AlmanacServiceQuery.php', @@ -121,11 +122,13 @@ phutil_register_library_map(array( 'AlmanacServiceSearchEngine' => 'applications/almanac/query/AlmanacServiceSearchEngine.php', 'AlmanacServiceTransaction' => 'applications/almanac/storage/AlmanacServiceTransaction.php', 'AlmanacServiceTransactionQuery' => 'applications/almanac/query/AlmanacServiceTransactionQuery.php', + 'AlmanacServiceTransactionType' => 'applications/almanac/xaction/AlmanacServiceTransactionType.php', 'AlmanacServiceType' => 'applications/almanac/servicetype/AlmanacServiceType.php', 'AlmanacServiceTypeDatasource' => 'applications/almanac/typeahead/AlmanacServiceTypeDatasource.php', 'AlmanacServiceTypeTestCase' => 'applications/almanac/servicetype/__tests__/AlmanacServiceTypeTestCase.php', 'AlmanacServiceViewController' => 'applications/almanac/controller/AlmanacServiceViewController.php', 'AlmanacTransaction' => 'applications/almanac/storage/AlmanacTransaction.php', + 'AlmanacTransactionType' => 'applications/almanac/xaction/AlmanacTransactionType.php', 'AphlictDropdownDataQuery' => 'applications/aphlict/query/AphlictDropdownDataQuery.php', 'Aphront304Response' => 'aphront/response/Aphront304Response.php', 'Aphront400Response' => 'aphront/response/Aphront400Response.php', @@ -5316,18 +5319,21 @@ phutil_register_library_map(array( 'AlmanacServiceEditor' => 'AlmanacEditor', 'AlmanacServiceListController' => 'AlmanacServiceController', 'AlmanacServiceNameNgrams' => 'PhabricatorSearchNgrams', + 'AlmanacServiceNameTransaction' => 'AlmanacServiceTransactionType', 'AlmanacServicePHIDType' => 'PhabricatorPHIDType', 'AlmanacServicePropertyEditEngine' => 'AlmanacPropertyEditEngine', 'AlmanacServiceQuery' => 'AlmanacQuery', 'AlmanacServiceSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'AlmanacServiceSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'AlmanacServiceTransaction' => 'AlmanacTransaction', + 'AlmanacServiceTransaction' => 'PhabricatorModularTransaction', 'AlmanacServiceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'AlmanacServiceTransactionType' => 'AlmanacTransactionType', 'AlmanacServiceType' => 'Phobject', 'AlmanacServiceTypeDatasource' => 'PhabricatorTypeaheadDatasource', 'AlmanacServiceTypeTestCase' => 'PhabricatorTestCase', 'AlmanacServiceViewController' => 'AlmanacServiceController', 'AlmanacTransaction' => 'PhabricatorApplicationTransaction', + 'AlmanacTransactionType' => 'PhabricatorModularTransactionType', 'AphlictDropdownDataQuery' => 'Phobject', 'Aphront304Response' => 'AphrontResponse', 'Aphront400Response' => 'AphrontResponse', diff --git a/src/applications/almanac/editor/AlmanacServiceEditEngine.php b/src/applications/almanac/editor/AlmanacServiceEditEngine.php index 7f5ad698fd..8cf7b97991 100644 --- a/src/applications/almanac/editor/AlmanacServiceEditEngine.php +++ b/src/applications/almanac/editor/AlmanacServiceEditEngine.php @@ -98,7 +98,7 @@ final class AlmanacServiceEditEngine ->setKey('name') ->setLabel(pht('Name')) ->setDescription(pht('Name of the service.')) - ->setTransactionType(AlmanacServiceTransaction::TYPE_NAME) + ->setTransactionType(AlmanacServiceNameTransaction::TRANSACTIONTYPE) ->setIsRequired(true) ->setValue($object->getName()), ); diff --git a/src/applications/almanac/editor/AlmanacServiceEditor.php b/src/applications/almanac/editor/AlmanacServiceEditor.php index 8b0326f749..ba509dae34 100644 --- a/src/applications/almanac/editor/AlmanacServiceEditor.php +++ b/src/applications/almanac/editor/AlmanacServiceEditor.php @@ -7,152 +7,23 @@ final class AlmanacServiceEditor return pht('Almanac Service'); } + public function getCreateObjectTitle($author, $object) { + return pht('%s created this service.', $author); + } + + public function getCreateObjectTitleForFeed($author, $object) { + return pht('%s created %s.', $author, $object); + } + public function getTransactionTypes() { $types = parent::getTransactionTypes(); - $types[] = AlmanacServiceTransaction::TYPE_NAME; - $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case AlmanacServiceTransaction::TYPE_NAME: - return $object->getName(); - } - - return parent::getCustomTransactionOldValue($object, $xaction); - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case AlmanacServiceTransaction::TYPE_NAME: - return $xaction->getNewValue(); - } - - return parent::getCustomTransactionNewValue($object, $xaction); - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case AlmanacServiceTransaction::TYPE_NAME: - $object->setName($xaction->getNewValue()); - return; - } - - return parent::applyCustomInternalTransaction($object, $xaction); - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case AlmanacServiceTransaction::TYPE_NAME: - return; - } - - return parent::applyCustomExternalTransaction($object, $xaction); - } - - protected function validateTransaction( - PhabricatorLiskDAO $object, - $type, - array $xactions) { - - $errors = parent::validateTransaction($object, $type, $xactions); - - switch ($type) { - case AlmanacServiceTransaction::TYPE_NAME: - $missing = $this->validateIsEmptyTextField( - $object->getName(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Service name is required.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } else { - foreach ($xactions as $xaction) { - $message = null; - - $name = $xaction->getNewValue(); - - try { - AlmanacNames::validateName($name); - } catch (Exception $ex) { - $message = $ex->getMessage(); - } - - if ($message !== null) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - $message, - $xaction); - $errors[] = $error; - continue; - } - - $other = id(new AlmanacServiceQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withNames(array($name)) - ->executeOne(); - if ($other && ($other->getID() != $object->getID())) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Not Unique'), - pht('Almanac services must have unique names.'), - last($xactions)); - $errors[] = $error; - continue; - } - - if ($name === $object->getName()) { - continue; - } - - $namespace = AlmanacNamespace::loadRestrictedNamespace( - $this->getActor(), - $name); - if ($namespace) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Restricted'), - pht( - 'You do not have permission to create Almanac services '. - 'within the "%s" namespace.', - $namespace->getName()), - $xaction); - $errors[] = $error; - continue; - } - } - } - - break; - } - - return $errors; - } - - protected function validateAllTransactions( PhabricatorLiskDAO $object, array $xactions) { diff --git a/src/applications/almanac/storage/AlmanacServiceTransaction.php b/src/applications/almanac/storage/AlmanacServiceTransaction.php index d357641cb6..692cc2997e 100644 --- a/src/applications/almanac/storage/AlmanacServiceTransaction.php +++ b/src/applications/almanac/storage/AlmanacServiceTransaction.php @@ -1,37 +1,22 @@ getAuthorPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_NAME: - if ($old === null) { - return pht( - '%s created this service.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s renamed this service from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - } - break; - } - - return parent::getTitle(); + public function getBaseTransactionClass() { + return 'AlmanacServiceTransactionType'; } } diff --git a/src/applications/almanac/xaction/AlmanacServiceNameTransaction.php b/src/applications/almanac/xaction/AlmanacServiceNameTransaction.php new file mode 100644 index 0000000000..24d9857bfa --- /dev/null +++ b/src/applications/almanac/xaction/AlmanacServiceNameTransaction.php @@ -0,0 +1,87 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + } + + public function getTitle() { + return pht( + '%s renamed this service from %s to %s.', + $this->renderAuthorLink(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function getTitleForFeed() { + return pht( + '%s renamed %s from %s to %s.', + $this->renderAuthorLink(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Almanac services must have a name.')); + } + + foreach ($xactions as $xaction) { + $name = $xaction->getNewValue(); + + $message = null; + try { + AlmanacNames::validateName($name); + } catch (Exception $ex) { + $message = $ex->getMessage(); + } + + if ($message !== null) { + $errors[] = $this->newInvalidError($message, $xaction); + continue; + } + + if ($name === $object->getName()) { + continue; + } + + $other = id(new AlmanacServiceQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withNames(array($name)) + ->executeOne(); + if ($other && ($other->getID() != $object->getID())) { + $errors[] = $this->newInvalidError( + pht('Almanac services must have unique names.'), + $xaction); + continue; + } + + $namespace = AlmanacNamespace::loadRestrictedNamespace( + $this->getActor(), + $name); + if ($namespace) { + $errors[] = $this->newInvalidError( + pht( + 'You do not have permission to create Almanac services '. + 'within the "%s" namespace.', + $namespace->getName()), + $xaction); + continue; + } + } + + return $errors; + } +} diff --git a/src/applications/almanac/xaction/AlmanacServiceTransactionType.php b/src/applications/almanac/xaction/AlmanacServiceTransactionType.php new file mode 100644 index 0000000000..0e062806fd --- /dev/null +++ b/src/applications/almanac/xaction/AlmanacServiceTransactionType.php @@ -0,0 +1,4 @@ + Date: Mon, 9 Apr 2018 10:00:13 -0700 Subject: [PATCH 22/53] Allow "almanac.service.edit" to create services Summary: Depends on D19317. Ref T13120. Ref T12414. See PHI145. See PHI473. This adds a Conduit-only "type" transaction for Almanac services. This is very similar to the approach in D18849 for Drydock blueprints. Test Plan: - Tried to create an empty service via "almanac.service.edit", was told to pick a type. - Tried to pick a bad type, was told to pick a good type. - Created a new Almanac service via "almanac.service.edit". - Tried to edit the service to change the type, wasn't allowed to. - Created and edited via the web UI, nothing changed from before. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13120, T12414 Differential Revision: https://secure.phabricator.com/D19318 --- src/__phutil_library_map__.php | 2 + .../editor/AlmanacServiceEditEngine.php | 41 ++++++++++++++ .../xaction/AlmanacServiceNameTransaction.php | 4 +- .../xaction/AlmanacServiceTypeTransaction.php | 55 +++++++++++++++++++ 4 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 src/applications/almanac/xaction/AlmanacServiceTypeTransaction.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index fe8e000041..5d68c3b0c6 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -126,6 +126,7 @@ phutil_register_library_map(array( 'AlmanacServiceType' => 'applications/almanac/servicetype/AlmanacServiceType.php', 'AlmanacServiceTypeDatasource' => 'applications/almanac/typeahead/AlmanacServiceTypeDatasource.php', 'AlmanacServiceTypeTestCase' => 'applications/almanac/servicetype/__tests__/AlmanacServiceTypeTestCase.php', + 'AlmanacServiceTypeTransaction' => 'applications/almanac/xaction/AlmanacServiceTypeTransaction.php', 'AlmanacServiceViewController' => 'applications/almanac/controller/AlmanacServiceViewController.php', 'AlmanacTransaction' => 'applications/almanac/storage/AlmanacTransaction.php', 'AlmanacTransactionType' => 'applications/almanac/xaction/AlmanacTransactionType.php', @@ -5331,6 +5332,7 @@ phutil_register_library_map(array( 'AlmanacServiceType' => 'Phobject', 'AlmanacServiceTypeDatasource' => 'PhabricatorTypeaheadDatasource', 'AlmanacServiceTypeTestCase' => 'PhabricatorTestCase', + 'AlmanacServiceTypeTransaction' => 'AlmanacServiceTransactionType', 'AlmanacServiceViewController' => 'AlmanacServiceController', 'AlmanacTransaction' => 'PhabricatorApplicationTransaction', 'AlmanacTransactionType' => 'PhabricatorModularTransactionType', diff --git a/src/applications/almanac/editor/AlmanacServiceEditEngine.php b/src/applications/almanac/editor/AlmanacServiceEditEngine.php index 8cf7b97991..93912fad8b 100644 --- a/src/applications/almanac/editor/AlmanacServiceEditEngine.php +++ b/src/applications/almanac/editor/AlmanacServiceEditEngine.php @@ -41,6 +41,37 @@ final class AlmanacServiceEditEngine return AlmanacService::initializeNewService($service_type); } + protected function newEditableObjectFromConduit(array $raw_xactions) { + $type = null; + foreach ($raw_xactions as $raw_xaction) { + if ($raw_xaction['type'] !== 'type') { + continue; + } + + $type = $raw_xaction['value']; + } + + if ($type === null) { + throw new Exception( + pht( + 'When creating a new Almanac service via the Conduit API, you '. + 'must provide a "type" transaction to select a type.')); + } + + $map = AlmanacServiceType::getAllServiceTypes(); + if (!isset($map[$type])) { + throw new Exception( + pht( + 'Service type "%s" is unrecognized. Valid types are: %s.', + $type, + implode(', ', array_keys($map)))); + } + + $this->setServiceType($type); + + return $this->newEditableObject(); + } + protected function newEditableObjectForDocumentation() { $service_type = new AlmanacCustomServiceType(); $this->setServiceType($service_type->getServiceTypeConstant()); @@ -101,6 +132,16 @@ final class AlmanacServiceEditEngine ->setTransactionType(AlmanacServiceNameTransaction::TRANSACTIONTYPE) ->setIsRequired(true) ->setValue($object->getName()), + id(new PhabricatorTextEditField()) + ->setKey('type') + ->setLabel(pht('Type')) + ->setIsConduitOnly(true) + ->setTransactionType( + AlmanacServiceTypeTransaction::TRANSACTIONTYPE) + ->setDescription(pht('When creating a service, set the type.')) + ->setConduitDescription(pht('Set the service type.')) + ->setConduitTypeDescription(pht('Service type.')) + ->setValue($object->getServiceType()), ); } diff --git a/src/applications/almanac/xaction/AlmanacServiceNameTransaction.php b/src/applications/almanac/xaction/AlmanacServiceNameTransaction.php index 24d9857bfa..ea1ccb8faa 100644 --- a/src/applications/almanac/xaction/AlmanacServiceNameTransaction.php +++ b/src/applications/almanac/xaction/AlmanacServiceNameTransaction.php @@ -16,7 +16,7 @@ final class AlmanacServiceNameTransaction public function getTitle() { return pht( '%s renamed this service from %s to %s.', - $this->renderAuthorLink(), + $this->renderAuthor(), $this->renderOldValue(), $this->renderNewValue()); } @@ -24,7 +24,7 @@ final class AlmanacServiceNameTransaction public function getTitleForFeed() { return pht( '%s renamed %s from %s to %s.', - $this->renderAuthorLink(), + $this->renderAuthor(), $this->renderObject(), $this->renderOldValue(), $this->renderNewValue()); diff --git a/src/applications/almanac/xaction/AlmanacServiceTypeTransaction.php b/src/applications/almanac/xaction/AlmanacServiceTypeTransaction.php new file mode 100644 index 0000000000..1b1c63d040 --- /dev/null +++ b/src/applications/almanac/xaction/AlmanacServiceTypeTransaction.php @@ -0,0 +1,55 @@ +getServiceType(); + } + + public function applyInternalEffects($object, $value) { + $object->setServiceType($value); + } + + public function getTitle() { + // This transaction can only be applied during object creation via + // Conduit and never generates a timeline event. + return null; + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getServiceType(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('You must select a service type when creating a service.')); + } + + $map = AlmanacServiceType::getAllServiceTypes(); + + foreach ($xactions as $xaction) { + if (!$this->isNewObject()) { + $errors[] = $this->newInvalidError( + pht( + 'The type of a service can not be changed once it has '. + 'been created.'), + $xaction); + continue; + } + + $new = $xaction->getNewValue(); + if (!isset($map[$new])) { + $errors[] = $this->newInvalidError( + pht( + 'Service type "%s" is not valid. Valid types are: %s.', + $new, + implode(', ', array_keys($map)))); + continue; + } + } + + return $errors; + } +} From 5ada1211cdeb978a9c2e3689241382fc5fefc730 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 10 Apr 2018 04:43:51 -0700 Subject: [PATCH 23/53] Modularize Almanac Namespace transactions Summary: Depends on D19318. Ref T13120. Ref T12414. Move transactions for Almanac Namespaces ("name" is the only meaningful one) to ModularTransactions. Test Plan: - Created a new namespace. - Edited a namespace. - Tried to choose no name, an invalid name, a duplicate name, and a name in a namespace I can't edit; got appropriate errors. - Grepped for `AlmanacNamespaceTransaction::TYPE_NAME`. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13120, T12414 Differential Revision: https://secure.phabricator.com/D19320 --- src/__phutil_library_map__.php | 6 +- .../editor/AlmanacNamespaceEditEngine.php | 2 +- .../almanac/editor/AlmanacNamespaceEditor.php | 145 +----------------- .../storage/AlmanacNamespaceTransaction.php | 27 +--- .../AlmanacNamespaceNameTransaction.php | 91 +++++++++++ .../AlmanacNamespaceTransactionType.php | 4 + 6 files changed, 112 insertions(+), 163 deletions(-) create mode 100644 src/applications/almanac/xaction/AlmanacNamespaceNameTransaction.php create mode 100644 src/applications/almanac/xaction/AlmanacNamespaceTransactionType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 5d68c3b0c6..57e58ac74e 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -75,11 +75,13 @@ phutil_register_library_map(array( 'AlmanacNamespaceEditor' => 'applications/almanac/editor/AlmanacNamespaceEditor.php', 'AlmanacNamespaceListController' => 'applications/almanac/controller/AlmanacNamespaceListController.php', 'AlmanacNamespaceNameNgrams' => 'applications/almanac/storage/AlmanacNamespaceNameNgrams.php', + 'AlmanacNamespaceNameTransaction' => 'applications/almanac/xaction/AlmanacNamespaceNameTransaction.php', 'AlmanacNamespacePHIDType' => 'applications/almanac/phid/AlmanacNamespacePHIDType.php', 'AlmanacNamespaceQuery' => 'applications/almanac/query/AlmanacNamespaceQuery.php', 'AlmanacNamespaceSearchEngine' => 'applications/almanac/query/AlmanacNamespaceSearchEngine.php', 'AlmanacNamespaceTransaction' => 'applications/almanac/storage/AlmanacNamespaceTransaction.php', 'AlmanacNamespaceTransactionQuery' => 'applications/almanac/query/AlmanacNamespaceTransactionQuery.php', + 'AlmanacNamespaceTransactionType' => 'applications/almanac/xaction/AlmanacNamespaceTransactionType.php', 'AlmanacNamespaceViewController' => 'applications/almanac/controller/AlmanacNamespaceViewController.php', 'AlmanacNetwork' => 'applications/almanac/storage/AlmanacNetwork.php', 'AlmanacNetworkController' => 'applications/almanac/controller/AlmanacNetworkController.php', @@ -5263,11 +5265,13 @@ phutil_register_library_map(array( 'AlmanacNamespaceEditor' => 'PhabricatorApplicationTransactionEditor', 'AlmanacNamespaceListController' => 'AlmanacNamespaceController', 'AlmanacNamespaceNameNgrams' => 'PhabricatorSearchNgrams', + 'AlmanacNamespaceNameTransaction' => 'AlmanacNamespaceTransactionType', 'AlmanacNamespacePHIDType' => 'PhabricatorPHIDType', 'AlmanacNamespaceQuery' => 'AlmanacQuery', 'AlmanacNamespaceSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'AlmanacNamespaceTransaction' => 'PhabricatorApplicationTransaction', + 'AlmanacNamespaceTransaction' => 'PhabricatorModularTransaction', 'AlmanacNamespaceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'AlmanacNamespaceTransactionType' => 'AlmanacTransactionType', 'AlmanacNamespaceViewController' => 'AlmanacNamespaceController', 'AlmanacNetwork' => array( 'AlmanacDAO', diff --git a/src/applications/almanac/editor/AlmanacNamespaceEditEngine.php b/src/applications/almanac/editor/AlmanacNamespaceEditEngine.php index 3b8c4ba2a6..cf6ad36c00 100644 --- a/src/applications/almanac/editor/AlmanacNamespaceEditEngine.php +++ b/src/applications/almanac/editor/AlmanacNamespaceEditEngine.php @@ -81,7 +81,7 @@ final class AlmanacNamespaceEditEngine ->setKey('name') ->setLabel(pht('Name')) ->setDescription(pht('Name of the namespace.')) - ->setTransactionType(AlmanacNamespaceTransaction::TYPE_NAME) + ->setTransactionType(AlmanacNamespaceNameTransaction::TRANSACTIONTYPE) ->setIsRequired(true) ->setValue($object->getName()), ); diff --git a/src/applications/almanac/editor/AlmanacNamespaceEditor.php b/src/applications/almanac/editor/AlmanacNamespaceEditor.php index 5243d3c935..93d65e62b1 100644 --- a/src/applications/almanac/editor/AlmanacNamespaceEditor.php +++ b/src/applications/almanac/editor/AlmanacNamespaceEditor.php @@ -11,6 +11,14 @@ final class AlmanacNamespaceEditor return pht('Almanac Namespace'); } + public function getCreateObjectTitle($author, $object) { + return pht('%s created this namespace.', $author); + } + + public function getCreateObjectTitleForFeed($author, $object) { + return pht('%s created %s.', $author, $object); + } + protected function supportsSearch() { return true; } @@ -18,149 +26,12 @@ final class AlmanacNamespaceEditor public function getTransactionTypes() { $types = parent::getTransactionTypes(); - $types[] = AlmanacNamespaceTransaction::TYPE_NAME; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case AlmanacNamespaceTransaction::TYPE_NAME: - return $object->getName(); - } - - return parent::getCustomTransactionOldValue($object, $xaction); - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case AlmanacNamespaceTransaction::TYPE_NAME: - return $xaction->getNewValue(); - } - - return parent::getCustomTransactionNewValue($object, $xaction); - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case AlmanacNamespaceTransaction::TYPE_NAME: - $object->setName($xaction->getNewValue()); - return; - } - - return parent::applyCustomInternalTransaction($object, $xaction); - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case AlmanacNamespaceTransaction::TYPE_NAME: - return; - } - - return parent::applyCustomExternalTransaction($object, $xaction); - } - - protected function validateTransaction( - PhabricatorLiskDAO $object, - $type, - array $xactions) { - - $errors = parent::validateTransaction($object, $type, $xactions); - - switch ($type) { - case AlmanacNamespaceTransaction::TYPE_NAME: - $missing = $this->validateIsEmptyTextField( - $object->getName(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Namespace name is required.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } else { - foreach ($xactions as $xaction) { - $name = $xaction->getNewValue(); - - $message = null; - try { - AlmanacNames::validateName($name); - } catch (Exception $ex) { - $message = $ex->getMessage(); - } - - if ($message !== null) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - $message, - $xaction); - $errors[] = $error; - continue; - } - - $other = id(new AlmanacNamespaceQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withNames(array($name)) - ->executeOne(); - if ($other && ($other->getID() != $object->getID())) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Not Unique'), - pht( - 'The namespace name "%s" is already in use by another '. - 'namespace. Each namespace must have a unique name.', - $name), - $xaction); - $errors[] = $error; - continue; - } - - if ($name === $object->getName()) { - continue; - } - - $namespace = AlmanacNamespace::loadRestrictedNamespace( - $this->getActor(), - $name); - if ($namespace) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Restricted'), - pht( - 'You do not have permission to create Almanac namespaces '. - 'within the "%s" namespace.', - $namespace->getName()), - $xaction); - $errors[] = $error; - continue; - } - } - } - - break; - } - - return $errors; - } - protected function didCatchDuplicateKeyException( PhabricatorLiskDAO $object, array $xactions, diff --git a/src/applications/almanac/storage/AlmanacNamespaceTransaction.php b/src/applications/almanac/storage/AlmanacNamespaceTransaction.php index 9461fa5f13..9a6f10e1de 100644 --- a/src/applications/almanac/storage/AlmanacNamespaceTransaction.php +++ b/src/applications/almanac/storage/AlmanacNamespaceTransaction.php @@ -1,9 +1,7 @@ getAuthorPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case PhabricatorTransactions::TYPE_CREATE: - return pht( - '%s created this namespace.', - $this->renderHandleLink($author_phid)); - break; - case self::TYPE_NAME: - return pht( - '%s renamed this namespace from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - } - - return parent::getTitle(); + public function getBaseTransactionClass() { + return 'AlmanacNamespaceTransactionType'; } } diff --git a/src/applications/almanac/xaction/AlmanacNamespaceNameTransaction.php b/src/applications/almanac/xaction/AlmanacNamespaceNameTransaction.php new file mode 100644 index 0000000000..dfb187db89 --- /dev/null +++ b/src/applications/almanac/xaction/AlmanacNamespaceNameTransaction.php @@ -0,0 +1,91 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + } + + public function getTitle() { + return pht( + '%s renamed this namespace from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function getTitleForFeed() { + return pht( + '%s renamed %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Namespace name is required.')); + } + + foreach ($xactions as $xaction) { + $name = $xaction->getNewValue(); + + $message = null; + try { + AlmanacNames::validateName($name); + } catch (Exception $ex) { + $message = $ex->getMessage(); + } + + if ($message !== null) { + $errors[] = $this->newInvalidError($message, $xaction); + continue; + } + + if ($name === $object->getName()) { + continue; + } + + $other = id(new AlmanacNamespaceQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withNames(array($name)) + ->executeOne(); + if ($other && ($other->getID() != $object->getID())) { + $errors[] = $this->newInvalidError( + pht( + 'The namespace name "%s" is already in use by another '. + 'namespace. Each namespace must have a unique name.', + $name), + $xaction); + continue; + } + + $namespace = AlmanacNamespace::loadRestrictedNamespace( + $this->getActor(), + $name); + if ($namespace) { + $errors[] = $this->newInvalidError( + pht( + 'You do not have permission to create Almanac namespaces '. + 'within the "%s" namespace.', + $namespace->getName()), + $xaction); + continue; + } + } + + return $errors; + } + +} diff --git a/src/applications/almanac/xaction/AlmanacNamespaceTransactionType.php b/src/applications/almanac/xaction/AlmanacNamespaceTransactionType.php new file mode 100644 index 0000000000..fee3d6d1eb --- /dev/null +++ b/src/applications/almanac/xaction/AlmanacNamespaceTransactionType.php @@ -0,0 +1,4 @@ + Date: Tue, 10 Apr 2018 05:08:02 -0700 Subject: [PATCH 24/53] Modularize Almanac Binding transactions Summary: Depends on D19320. Ref T13120. Ref T12414. Move transactions for Almanac Bindings to ModularTransactions. Test Plan: - Created a new binding. - Tried to create a duplicate binding, got an error. - Edited a binding to rebind it to a different device. - Disabled and enabled bindings. - Grepped for `AlmanacBindingTransaction::` constants. When a binding is created, it currently renders a bad "changed the interface from ??? to X" transaction. This is because creation isn't currently using EditEngine. I plan to swap it shortly, which will turn this into a real "Create" transaction and fix the issue. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13120, T12414 Differential Revision: https://secure.phabricator.com/D19321 --- src/__phutil_library_map__.php | 8 +- .../AlmanacBindingDisableController.php | 2 +- .../AlmanacBindingEditController.php | 2 +- .../almanac/editor/AlmanacBindingEditor.php | 165 +----------------- .../storage/AlmanacBindingTransaction.php | 61 +------ .../AlmanacBindingDisableTransaction.php | 28 +++ .../AlmanacBindingInterfaceTransaction.php | 111 ++++++++++++ .../xaction/AlmanacBindingTransactionType.php | 4 + 8 files changed, 159 insertions(+), 222 deletions(-) create mode 100644 src/applications/almanac/xaction/AlmanacBindingDisableTransaction.php create mode 100644 src/applications/almanac/xaction/AlmanacBindingInterfaceTransaction.php create mode 100644 src/applications/almanac/xaction/AlmanacBindingTransactionType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 57e58ac74e..cfa091f048 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -13,14 +13,17 @@ phutil_register_library_map(array( 'AlmanacAddress' => 'applications/almanac/util/AlmanacAddress.php', 'AlmanacBinding' => 'applications/almanac/storage/AlmanacBinding.php', 'AlmanacBindingDisableController' => 'applications/almanac/controller/AlmanacBindingDisableController.php', + 'AlmanacBindingDisableTransaction' => 'applications/almanac/xaction/AlmanacBindingDisableTransaction.php', 'AlmanacBindingEditController' => 'applications/almanac/controller/AlmanacBindingEditController.php', 'AlmanacBindingEditor' => 'applications/almanac/editor/AlmanacBindingEditor.php', + 'AlmanacBindingInterfaceTransaction' => 'applications/almanac/xaction/AlmanacBindingInterfaceTransaction.php', 'AlmanacBindingPHIDType' => 'applications/almanac/phid/AlmanacBindingPHIDType.php', 'AlmanacBindingPropertyEditEngine' => 'applications/almanac/editor/AlmanacBindingPropertyEditEngine.php', 'AlmanacBindingQuery' => 'applications/almanac/query/AlmanacBindingQuery.php', 'AlmanacBindingTableView' => 'applications/almanac/view/AlmanacBindingTableView.php', 'AlmanacBindingTransaction' => 'applications/almanac/storage/AlmanacBindingTransaction.php', 'AlmanacBindingTransactionQuery' => 'applications/almanac/query/AlmanacBindingTransactionQuery.php', + 'AlmanacBindingTransactionType' => 'applications/almanac/xaction/AlmanacBindingTransactionType.php', 'AlmanacBindingViewController' => 'applications/almanac/controller/AlmanacBindingViewController.php', 'AlmanacBindingsSearchEngineAttachment' => 'applications/almanac/engineextension/AlmanacBindingsSearchEngineAttachment.php', 'AlmanacCacheEngineExtension' => 'applications/almanac/engineextension/AlmanacCacheEngineExtension.php', @@ -5179,14 +5182,17 @@ phutil_register_library_map(array( 'PhabricatorExtendedPolicyInterface', ), 'AlmanacBindingDisableController' => 'AlmanacServiceController', + 'AlmanacBindingDisableTransaction' => 'AlmanacBindingTransactionType', 'AlmanacBindingEditController' => 'AlmanacServiceController', 'AlmanacBindingEditor' => 'AlmanacEditor', + 'AlmanacBindingInterfaceTransaction' => 'AlmanacBindingTransactionType', 'AlmanacBindingPHIDType' => 'PhabricatorPHIDType', 'AlmanacBindingPropertyEditEngine' => 'AlmanacPropertyEditEngine', 'AlmanacBindingQuery' => 'AlmanacQuery', 'AlmanacBindingTableView' => 'AphrontView', - 'AlmanacBindingTransaction' => 'AlmanacTransaction', + 'AlmanacBindingTransaction' => 'PhabricatorModularTransaction', 'AlmanacBindingTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'AlmanacBindingTransactionType' => 'AlmanacTransactionType', 'AlmanacBindingViewController' => 'AlmanacServiceController', 'AlmanacBindingsSearchEngineAttachment' => 'AlmanacSearchEngineAttachment', 'AlmanacCacheEngineExtension' => 'PhabricatorCacheEngineExtension', diff --git a/src/applications/almanac/controller/AlmanacBindingDisableController.php b/src/applications/almanac/controller/AlmanacBindingDisableController.php index eb1f093125..d709db3e82 100644 --- a/src/applications/almanac/controller/AlmanacBindingDisableController.php +++ b/src/applications/almanac/controller/AlmanacBindingDisableController.php @@ -40,7 +40,7 @@ final class AlmanacBindingDisableController if ($request->isFormPost()) { - $type_disable = AlmanacBindingTransaction::TYPE_DISABLE; + $type_disable = AlmanacBindingDisableTransaction::TRANSACTIONTYPE; $xactions = array(); diff --git a/src/applications/almanac/controller/AlmanacBindingEditController.php b/src/applications/almanac/controller/AlmanacBindingEditController.php index 5f1fe30043..f33bf73ccb 100644 --- a/src/applications/almanac/controller/AlmanacBindingEditController.php +++ b/src/applications/almanac/controller/AlmanacBindingEditController.php @@ -58,7 +58,7 @@ final class AlmanacBindingEditController if ($request->isFormPost()) { $v_interface = $request->getArr('interfacePHIDs'); - $type_interface = AlmanacBindingTransaction::TYPE_INTERFACE; + $type_interface = AlmanacBindingInterfaceTransaction::TRANSACTIONTYPE; $xactions = array(); diff --git a/src/applications/almanac/editor/AlmanacBindingEditor.php b/src/applications/almanac/editor/AlmanacBindingEditor.php index 6532bf671f..d84845722c 100644 --- a/src/applications/almanac/editor/AlmanacBindingEditor.php +++ b/src/applications/almanac/editor/AlmanacBindingEditor.php @@ -3,173 +3,16 @@ final class AlmanacBindingEditor extends AlmanacEditor { - private $devicePHID; - public function getEditorObjectsDescription() { return pht('Almanac Binding'); } - public function getTransactionTypes() { - $types = parent::getTransactionTypes(); - - $types[] = AlmanacBindingTransaction::TYPE_INTERFACE; - $types[] = AlmanacBindingTransaction::TYPE_DISABLE; - - return $types; + public function getCreateObjectTitle($author, $object) { + return pht('%s created this binding.', $author); } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case AlmanacBindingTransaction::TYPE_INTERFACE: - return $object->getInterfacePHID(); - case AlmanacBindingTransaction::TYPE_DISABLE: - return $object->getIsDisabled(); - } - - return parent::getCustomTransactionOldValue($object, $xaction); + public function getCreateObjectTitleForFeed($author, $object) { + return pht('%s created %s.', $author, $object); } - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case AlmanacBindingTransaction::TYPE_INTERFACE: - return $xaction->getNewValue(); - case AlmanacBindingTransaction::TYPE_DISABLE: - return (int)$xaction->getNewValue(); - } - - return parent::getCustomTransactionNewValue($object, $xaction); - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case AlmanacBindingTransaction::TYPE_INTERFACE: - $interface = id(new AlmanacInterfaceQuery()) - ->setViewer($this->requireActor()) - ->withPHIDs(array($xaction->getNewValue())) - ->executeOne(); - $object->setDevicePHID($interface->getDevicePHID()); - $object->setInterfacePHID($interface->getPHID()); - return; - case AlmanacBindingTransaction::TYPE_DISABLE: - $object->setIsDisabled($xaction->getNewValue()); - return; - } - - return parent::applyCustomInternalTransaction($object, $xaction); - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case AlmanacBindingTransaction::TYPE_DISABLE: - return; - case AlmanacBindingTransaction::TYPE_INTERFACE: - $interface_phids = array(); - - $interface_phids[] = $xaction->getOldValue(); - $interface_phids[] = $xaction->getNewValue(); - - $interface_phids = array_filter($interface_phids); - $interface_phids = array_unique($interface_phids); - - $interfaces = id(new AlmanacInterfaceQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withPHIDs($interface_phids) - ->execute(); - - $device_phids = array(); - foreach ($interfaces as $interface) { - $device_phids[] = $interface->getDevicePHID(); - } - - $device_phids = array_unique($device_phids); - - $devices = id(new AlmanacDeviceQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withPHIDs($device_phids) - ->execute(); - - foreach ($devices as $device) { - $device->rebuildClusterBindingStatus(); - } - return; - } - - return parent::applyCustomExternalTransaction($object, $xaction); - } - - protected function validateTransaction( - PhabricatorLiskDAO $object, - $type, - array $xactions) { - - $errors = parent::validateTransaction($object, $type, $xactions); - - switch ($type) { - case AlmanacBindingTransaction::TYPE_INTERFACE: - $missing = $this->validateIsEmptyTextField( - $object->getInterfacePHID(), - $xactions); - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Bindings must specify an interface.'), - nonempty(last($xactions), null)); - $error->setIsMissingFieldError(true); - $errors[] = $error; - } else if ($xactions) { - foreach ($xactions as $xaction) { - $interfaces = id(new AlmanacInterfaceQuery()) - ->setViewer($this->requireActor()) - ->withPHIDs(array($xaction->getNewValue())) - ->execute(); - if (!$interfaces) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'You can not bind a service to an invalid or restricted '. - 'interface.'), - $xaction); - $errors[] = $error; - } - } - - $final_value = last($xactions)->getNewValue(); - - $binding = id(new AlmanacBindingQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withServicePHIDs(array($object->getServicePHID())) - ->withInterfacePHIDs(array($final_value)) - ->executeOne(); - if ($binding && ($binding->getID() != $object->getID())) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Already Bound'), - pht( - 'You can not bind a service to the same interface multiple '. - 'times.'), - last($xactions)); - $errors[] = $error; - } - } - break; - } - - return $errors; - } - - - } diff --git a/src/applications/almanac/storage/AlmanacBindingTransaction.php b/src/applications/almanac/storage/AlmanacBindingTransaction.php index 11120f9827..24b606639f 100644 --- a/src/applications/almanac/storage/AlmanacBindingTransaction.php +++ b/src/applications/almanac/storage/AlmanacBindingTransaction.php @@ -1,10 +1,7 @@ getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_INTERFACE: - if ($old) { - $phids[] = $old; - } - if ($new) { - $phids[] = $new; - } - break; - } - - return $phids; - } - - public function getTitle() { - $author_phid = $this->getAuthorPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_INTERFACE: - if ($old === null) { - return pht( - '%s created this binding.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s changed this binding from %s to %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($old), - $this->renderHandleLink($new)); - } - break; - case self::TYPE_DISABLE: - if ($new) { - return pht( - '%s disabled this binding.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s enabled this binding.', - $this->renderHandleLink($author_phid)); - } - break; - } - - return parent::getTitle(); + public function getBaseTransactionClass() { + return 'AlmanacBindingTransactionType'; } } diff --git a/src/applications/almanac/xaction/AlmanacBindingDisableTransaction.php b/src/applications/almanac/xaction/AlmanacBindingDisableTransaction.php new file mode 100644 index 0000000000..1592686835 --- /dev/null +++ b/src/applications/almanac/xaction/AlmanacBindingDisableTransaction.php @@ -0,0 +1,28 @@ +getIsDisabled(); + } + + public function applyInternalEffects($object, $value) { + $object->setIsDisabled((int)$value); + } + + public function getTitle() { + if ($this->getNewValue()) { + return pht( + '%s disabled this binding.', + $this->renderAuthor()); + } else { + return pht( + '%s enabled this binding.', + $this->renderAuthor()); + } + } + +} diff --git a/src/applications/almanac/xaction/AlmanacBindingInterfaceTransaction.php b/src/applications/almanac/xaction/AlmanacBindingInterfaceTransaction.php new file mode 100644 index 0000000000..f43fcdfa86 --- /dev/null +++ b/src/applications/almanac/xaction/AlmanacBindingInterfaceTransaction.php @@ -0,0 +1,111 @@ +getInterfacePHID(); + } + + public function applyInternalEffects($object, $value) { + $interface = $this->loadInterface($value); + + $object + ->setDevicePHID($interface->getDevicePHID()) + ->setInterfacePHID($interface->getPHID()); + } + + public function applyExternalEffects($object, $value) { + + // When we change which services a device is bound to, we need to + // recalculate whether it is a cluster device or not so we can tell if + // the "Can Manage Cluster Services" permission applies to it. + + $viewer = PhabricatorUser::getOmnipotentUser(); + $interface_phids = array(); + + $interface_phids[] = $this->getOldValue(); + $interface_phids[] = $this->getNewValue(); + + $interface_phids = array_filter($interface_phids); + $interface_phids = array_unique($interface_phids); + + $interfaces = id(new AlmanacInterfaceQuery()) + ->setViewer($viewer) + ->withPHIDs($interface_phids) + ->execute(); + + $device_phids = array(); + foreach ($interfaces as $interface) { + $device_phids[] = $interface->getDevicePHID(); + } + + $device_phids = array_unique($device_phids); + + $devices = id(new AlmanacDeviceQuery()) + ->setViewer($viewer) + ->withPHIDs($device_phids) + ->execute(); + + foreach ($devices as $device) { + $device->rebuildClusterBindingStatus(); + } + } + + public function getTitle() { + return pht( + '%s changed the interface for this binding from %s to %s.', + $this->renderAuthor(), + $this->renderOldHandle(), + $this->renderNewHandle()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $interface_phid = $object->getInterfacePHID(); + if ($this->isEmptyTextTransaction($interface_phid, $xactions)) { + $errors[] = $this->newRequiredError( + pht('Bindings must specify an interface.')); + } + + foreach ($xactions as $xaction) { + $interface_phid = $xaction->getNewValue(); + + $interface = $this->loadInterface($interface_phid); + if (!$interface) { + $errors[] = $this->newInvalidError( + pht( + 'You can not bind a service to an invalid or restricted '. + 'interface.'), + $xaction); + continue; + } + + $binding = id(new AlmanacBindingQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withServicePHIDs(array($object->getServicePHID())) + ->withInterfacePHIDs(array($interface_phid)) + ->executeOne(); + if ($binding && ($binding->getID() != $object->getID())) { + $errors[] = $this->newInvalidError( + pht( + 'You can not bind a service to the same interface multiple '. + 'times.'), + $xaction); + continue; + } + } + + return $errors; + } + + private function loadInterface($phid) { + return id(new AlmanacInterfaceQuery()) + ->setViewer($this->getActor()) + ->withPHIDs(array($phid)) + ->executeOne(); + } +} diff --git a/src/applications/almanac/xaction/AlmanacBindingTransactionType.php b/src/applications/almanac/xaction/AlmanacBindingTransactionType.php new file mode 100644 index 0000000000..3a3d9408b5 --- /dev/null +++ b/src/applications/almanac/xaction/AlmanacBindingTransactionType.php @@ -0,0 +1,4 @@ + Date: Tue, 10 Apr 2018 05:19:47 -0700 Subject: [PATCH 25/53] Modularize Almanac Network transactions Summary: Depends on D19321. Ref T13120. Ref T12414. Move transactions for Almanac Networks (just "name") to ModularTransactions. Test Plan: - Created a new network. - Renamed a network. - Tried to create a network with no name (got an error). - Grepped for `AlmanacNetworkTransaction::`. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13120, T12414 Differential Revision: https://secure.phabricator.com/D19322 --- src/__phutil_library_map__.php | 6 +- .../editor/AlmanacNetworkEditEngine.php | 2 +- .../almanac/editor/AlmanacNetworkEditor.php | 88 ++----------------- .../storage/AlmanacNetworkTransaction.php | 26 +----- .../xaction/AlmanacNetworkNameTransaction.php | 44 ++++++++++ .../xaction/AlmanacNetworkTransactionType.php | 4 + 6 files changed, 65 insertions(+), 105 deletions(-) create mode 100644 src/applications/almanac/xaction/AlmanacNetworkNameTransaction.php create mode 100644 src/applications/almanac/xaction/AlmanacNetworkTransactionType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index cfa091f048..7cf2cbbf12 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -93,11 +93,13 @@ phutil_register_library_map(array( 'AlmanacNetworkEditor' => 'applications/almanac/editor/AlmanacNetworkEditor.php', 'AlmanacNetworkListController' => 'applications/almanac/controller/AlmanacNetworkListController.php', 'AlmanacNetworkNameNgrams' => 'applications/almanac/storage/AlmanacNetworkNameNgrams.php', + 'AlmanacNetworkNameTransaction' => 'applications/almanac/xaction/AlmanacNetworkNameTransaction.php', 'AlmanacNetworkPHIDType' => 'applications/almanac/phid/AlmanacNetworkPHIDType.php', 'AlmanacNetworkQuery' => 'applications/almanac/query/AlmanacNetworkQuery.php', 'AlmanacNetworkSearchEngine' => 'applications/almanac/query/AlmanacNetworkSearchEngine.php', 'AlmanacNetworkTransaction' => 'applications/almanac/storage/AlmanacNetworkTransaction.php', 'AlmanacNetworkTransactionQuery' => 'applications/almanac/query/AlmanacNetworkTransactionQuery.php', + 'AlmanacNetworkTransactionType' => 'applications/almanac/xaction/AlmanacNetworkTransactionType.php', 'AlmanacNetworkViewController' => 'applications/almanac/controller/AlmanacNetworkViewController.php', 'AlmanacPropertiesDestructionEngineExtension' => 'applications/almanac/engineextension/AlmanacPropertiesDestructionEngineExtension.php', 'AlmanacPropertiesSearchEngineAttachment' => 'applications/almanac/engineextension/AlmanacPropertiesSearchEngineAttachment.php', @@ -5292,11 +5294,13 @@ phutil_register_library_map(array( 'AlmanacNetworkEditor' => 'PhabricatorApplicationTransactionEditor', 'AlmanacNetworkListController' => 'AlmanacNetworkController', 'AlmanacNetworkNameNgrams' => 'PhabricatorSearchNgrams', + 'AlmanacNetworkNameTransaction' => 'AlmanacNetworkTransactionType', 'AlmanacNetworkPHIDType' => 'PhabricatorPHIDType', 'AlmanacNetworkQuery' => 'AlmanacQuery', 'AlmanacNetworkSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'AlmanacNetworkTransaction' => 'PhabricatorApplicationTransaction', + 'AlmanacNetworkTransaction' => 'PhabricatorModularTransaction', 'AlmanacNetworkTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'AlmanacNetworkTransactionType' => 'AlmanacTransactionType', 'AlmanacNetworkViewController' => 'AlmanacNetworkController', 'AlmanacPropertiesDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension', 'AlmanacPropertiesSearchEngineAttachment' => 'AlmanacSearchEngineAttachment', diff --git a/src/applications/almanac/editor/AlmanacNetworkEditEngine.php b/src/applications/almanac/editor/AlmanacNetworkEditEngine.php index e474e81291..027b2a9aa0 100644 --- a/src/applications/almanac/editor/AlmanacNetworkEditEngine.php +++ b/src/applications/almanac/editor/AlmanacNetworkEditEngine.php @@ -81,7 +81,7 @@ final class AlmanacNetworkEditEngine ->setKey('name') ->setLabel(pht('Name')) ->setDescription(pht('Name of the network.')) - ->setTransactionType(AlmanacNetworkTransaction::TYPE_NAME) + ->setTransactionType(AlmanacNetworkNameTransaction::TRANSACTIONTYPE) ->setIsRequired(true) ->setValue($object->getName()), ); diff --git a/src/applications/almanac/editor/AlmanacNetworkEditor.php b/src/applications/almanac/editor/AlmanacNetworkEditor.php index f32b58219e..f5a74e2095 100644 --- a/src/applications/almanac/editor/AlmanacNetworkEditor.php +++ b/src/applications/almanac/editor/AlmanacNetworkEditor.php @@ -15,93 +15,21 @@ final class AlmanacNetworkEditor return true; } + public function getCreateObjectTitle($author, $object) { + return pht('%s created this network.', $author); + } + + public function getCreateObjectTitleForFeed($author, $object) { + return pht('%s created %s.', $author, $object); + } + public function getTransactionTypes() { $types = parent::getTransactionTypes(); - $types[] = AlmanacNetworkTransaction::TYPE_NAME; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case AlmanacNetworkTransaction::TYPE_NAME: - return $object->getName(); - } - - return parent::getCustomTransactionOldValue($object, $xaction); - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case AlmanacNetworkTransaction::TYPE_NAME: - return $xaction->getNewValue(); - } - - return parent::getCustomTransactionNewValue($object, $xaction); - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case AlmanacNetworkTransaction::TYPE_NAME: - $object->setName($xaction->getNewValue()); - return; - } - - return parent::applyCustomInternalTransaction($object, $xaction); - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case AlmanacNetworkTransaction::TYPE_NAME: - return; - } - - return parent::applyCustomExternalTransaction($object, $xaction); - } - - protected function validateTransaction( - PhabricatorLiskDAO $object, - $type, - array $xactions) { - - $errors = parent::validateTransaction($object, $type, $xactions); - - switch ($type) { - case AlmanacNetworkTransaction::TYPE_NAME: - $missing = $this->validateIsEmptyTextField( - $object->getName(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Network name is required.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - break; - } - - return $errors; - } - - - } diff --git a/src/applications/almanac/storage/AlmanacNetworkTransaction.php b/src/applications/almanac/storage/AlmanacNetworkTransaction.php index f8c2b43b0b..0717631403 100644 --- a/src/applications/almanac/storage/AlmanacNetworkTransaction.php +++ b/src/applications/almanac/storage/AlmanacNetworkTransaction.php @@ -1,9 +1,7 @@ getAuthorPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case PhabricatorTransactions::TYPE_CREATE: - return pht( - '%s created this network.', - $this->renderHandleLink($author_phid)); - case self::TYPE_NAME: - return pht( - '%s renamed this network from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - } - - return parent::getTitle(); + public function getBaseTransactionClass() { + return 'AlmanacNetworkTransactionType'; } } diff --git a/src/applications/almanac/xaction/AlmanacNetworkNameTransaction.php b/src/applications/almanac/xaction/AlmanacNetworkNameTransaction.php new file mode 100644 index 0000000000..140c1a9661 --- /dev/null +++ b/src/applications/almanac/xaction/AlmanacNetworkNameTransaction.php @@ -0,0 +1,44 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + } + + public function getTitle() { + return pht( + '%s renamed this network from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function getTitleForFeed() { + return pht( + '%s renamed %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Network name is required.')); + } + + return $errors; + } + +} diff --git a/src/applications/almanac/xaction/AlmanacNetworkTransactionType.php b/src/applications/almanac/xaction/AlmanacNetworkTransactionType.php new file mode 100644 index 0000000000..9f822ab09f --- /dev/null +++ b/src/applications/almanac/xaction/AlmanacNetworkTransactionType.php @@ -0,0 +1,4 @@ + Date: Tue, 10 Apr 2018 05:50:49 -0700 Subject: [PATCH 26/53] Add skeleton code for Almanac Interfaces to have real transactions Summary: Depends on D19322. Ref T13120. Ref T12414. Currently, `AlmanacDevice` has a bit of a beast of a `TYPE_INTERFACE` transaction that fully creates a complex Interface object. This isn't very flexible or consistent, and Interfaces are complex enough to reasonably have their own object behaviors (for example, they have their own PHIDs). The complexity of this transaction makes modularizing `AlmanacDevice` transactions tricky. To simplify this, move Interface toward having its own set of normal transactions. This change just adds some reasonable-looking transactions; it doesn't actually hook them up in the UI or make them reachable. I'll test that they actually work as I swap the UI over. We may also have some code using the `TYPE_INTERFACE` transaction in Phacility support stuff, so that may need to wait a week to actually phase out. Test Plan: Ran `bin/storage upgrade` and `arc liberate`. This code isn't reachable yet. Reviewers: amckinley Reviewed By: amckinley Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T13120, T12414 Differential Revision: https://secure.phabricator.com/D19323 --- .../20180410.almanac.01.iface.xaction.sql | 19 ++++++ src/__phutil_library_map__.php | 15 +++++ .../almanac/editor/AlmanacInterfaceEditor.php | 22 +++++++ .../almanac/storage/AlmanacInterface.php | 25 +++++++- .../storage/AlmanacInterfaceTransaction.php | 22 +++++++ .../AlmanacInterfaceAddressTransaction.php | 44 ++++++++++++++ .../AlmanacInterfaceDeviceTransaction.php | 60 +++++++++++++++++++ .../AlmanacInterfaceNetworkTransaction.php | 53 ++++++++++++++++ .../AlmanacInterfacePortTransaction.php | 53 ++++++++++++++++ .../AlmanacInterfaceTransactionType.php | 4 ++ 10 files changed, 316 insertions(+), 1 deletion(-) create mode 100644 resources/sql/autopatches/20180410.almanac.01.iface.xaction.sql create mode 100644 src/applications/almanac/editor/AlmanacInterfaceEditor.php create mode 100644 src/applications/almanac/storage/AlmanacInterfaceTransaction.php create mode 100644 src/applications/almanac/xaction/AlmanacInterfaceAddressTransaction.php create mode 100644 src/applications/almanac/xaction/AlmanacInterfaceDeviceTransaction.php create mode 100644 src/applications/almanac/xaction/AlmanacInterfaceNetworkTransaction.php create mode 100644 src/applications/almanac/xaction/AlmanacInterfacePortTransaction.php create mode 100644 src/applications/almanac/xaction/AlmanacInterfaceTransactionType.php diff --git a/resources/sql/autopatches/20180410.almanac.01.iface.xaction.sql b/resources/sql/autopatches/20180410.almanac.01.iface.xaction.sql new file mode 100644 index 0000000000..5f0dec18aa --- /dev/null +++ b/resources/sql/autopatches/20180410.almanac.01.iface.xaction.sql @@ -0,0 +1,19 @@ +CREATE TABLE {$NAMESPACE}_almanac.almanac_interfacetransaction ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + authorPHID VARBINARY(64) NOT NULL, + objectPHID VARBINARY(64) NOT NULL, + viewPolicy VARBINARY(64) NOT NULL, + editPolicy VARBINARY(64) NOT NULL, + commentPHID VARBINARY(64) DEFAULT NULL, + commentVersion INT UNSIGNED NOT NULL, + transactionType VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL, + oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (`phid`), + KEY `key_object` (`objectPHID`) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 7cf2cbbf12..08806ab1b9 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -57,12 +57,19 @@ phutil_register_library_map(array( 'AlmanacDrydockPoolServiceType' => 'applications/almanac/servicetype/AlmanacDrydockPoolServiceType.php', 'AlmanacEditor' => 'applications/almanac/editor/AlmanacEditor.php', 'AlmanacInterface' => 'applications/almanac/storage/AlmanacInterface.php', + 'AlmanacInterfaceAddressTransaction' => 'applications/almanac/xaction/AlmanacInterfaceAddressTransaction.php', 'AlmanacInterfaceDatasource' => 'applications/almanac/typeahead/AlmanacInterfaceDatasource.php', 'AlmanacInterfaceDeleteController' => 'applications/almanac/controller/AlmanacInterfaceDeleteController.php', + 'AlmanacInterfaceDeviceTransaction' => 'applications/almanac/xaction/AlmanacInterfaceDeviceTransaction.php', 'AlmanacInterfaceEditController' => 'applications/almanac/controller/AlmanacInterfaceEditController.php', + 'AlmanacInterfaceEditor' => 'applications/almanac/editor/AlmanacInterfaceEditor.php', + 'AlmanacInterfaceNetworkTransaction' => 'applications/almanac/xaction/AlmanacInterfaceNetworkTransaction.php', 'AlmanacInterfacePHIDType' => 'applications/almanac/phid/AlmanacInterfacePHIDType.php', + 'AlmanacInterfacePortTransaction' => 'applications/almanac/xaction/AlmanacInterfacePortTransaction.php', 'AlmanacInterfaceQuery' => 'applications/almanac/query/AlmanacInterfaceQuery.php', 'AlmanacInterfaceTableView' => 'applications/almanac/view/AlmanacInterfaceTableView.php', + 'AlmanacInterfaceTransaction' => 'applications/almanac/storage/AlmanacInterfaceTransaction.php', + 'AlmanacInterfaceTransactionType' => 'applications/almanac/xaction/AlmanacInterfaceTransactionType.php', 'AlmanacKeys' => 'applications/almanac/util/AlmanacKeys.php', 'AlmanacManageClusterServicesCapability' => 'applications/almanac/capability/AlmanacManageClusterServicesCapability.php', 'AlmanacManagementRegisterWorkflow' => 'applications/almanac/management/AlmanacManagementRegisterWorkflow.php', @@ -5243,13 +5250,21 @@ phutil_register_library_map(array( 'PhabricatorPolicyInterface', 'PhabricatorDestructibleInterface', 'PhabricatorExtendedPolicyInterface', + 'PhabricatorApplicationTransactionInterface', ), + 'AlmanacInterfaceAddressTransaction' => 'AlmanacNetworkTransactionType', 'AlmanacInterfaceDatasource' => 'PhabricatorTypeaheadDatasource', 'AlmanacInterfaceDeleteController' => 'AlmanacDeviceController', + 'AlmanacInterfaceDeviceTransaction' => 'AlmanacNetworkTransactionType', 'AlmanacInterfaceEditController' => 'AlmanacDeviceController', + 'AlmanacInterfaceEditor' => 'PhabricatorApplicationTransactionEditor', + 'AlmanacInterfaceNetworkTransaction' => 'AlmanacNetworkTransactionType', 'AlmanacInterfacePHIDType' => 'PhabricatorPHIDType', + 'AlmanacInterfacePortTransaction' => 'AlmanacNetworkTransactionType', 'AlmanacInterfaceQuery' => 'AlmanacQuery', 'AlmanacInterfaceTableView' => 'AphrontView', + 'AlmanacInterfaceTransaction' => 'PhabricatorModularTransaction', + 'AlmanacInterfaceTransactionType' => 'AlmanacTransactionType', 'AlmanacKeys' => 'Phobject', 'AlmanacManageClusterServicesCapability' => 'PhabricatorPolicyCapability', 'AlmanacManagementRegisterWorkflow' => 'AlmanacManagementWorkflow', diff --git a/src/applications/almanac/editor/AlmanacInterfaceEditor.php b/src/applications/almanac/editor/AlmanacInterfaceEditor.php new file mode 100644 index 0000000000..066b026dbd --- /dev/null +++ b/src/applications/almanac/editor/AlmanacInterfaceEditor.php @@ -0,0 +1,22 @@ +delete(); } + +/* -( PhabricatorApplicationTransactionInterface )------------------------- */ + + + public function getApplicationTransactionEditor() { + return new AlmanacInterfaceEditor(); + } + + public function getApplicationTransactionObject() { + return $this; + } + + public function getApplicationTransactionTemplate() { + return new AlmanacInterfaceTransaction(); + } + + public function willRenderTimeline( + PhabricatorApplicationTransactionView $timeline, + AphrontRequest $request) { + return $timeline; + } + } diff --git a/src/applications/almanac/storage/AlmanacInterfaceTransaction.php b/src/applications/almanac/storage/AlmanacInterfaceTransaction.php new file mode 100644 index 0000000000..f469f926f7 --- /dev/null +++ b/src/applications/almanac/storage/AlmanacInterfaceTransaction.php @@ -0,0 +1,22 @@ +getAddress(); + } + + public function applyInternalEffects($object, $value) { + $object->setAddress($value); + } + + public function getTitle() { + return pht( + '%s changed the address for this interface from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getAddress(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Interfaces must have an address.')); + } + + foreach ($xactions as $xaction) { + + // NOTE: For now, we don't validate addresses. We generally expect users + // to provide IPv4 addresses, but it's reasonable for them to provide + // IPv6 addresses, and some installs currently use DNS names. This is + // off-label but works today. + + } + + return $errors; + } + +} diff --git a/src/applications/almanac/xaction/AlmanacInterfaceDeviceTransaction.php b/src/applications/almanac/xaction/AlmanacInterfaceDeviceTransaction.php new file mode 100644 index 0000000000..3c4bf6045b --- /dev/null +++ b/src/applications/almanac/xaction/AlmanacInterfaceDeviceTransaction.php @@ -0,0 +1,60 @@ +getDevicePHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setDevicePHID($value); + } + + public function getTitle() { + return pht( + '%s changed the device for this interface from %s to %s.', + $this->renderAuthor(), + $this->renderOldHandle(), + $this->renderNewHandle()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getAddress(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Interfaces must have a device.')); + } + + foreach ($xactions as $xaction) { + if (!$this->isNewObject()) { + $errors[] = $this->newInvalidError( + pht( + 'The device for an interface can not be changed once it has '. + 'been created.'), + $xaction); + continue; + } + + $device_phid = $xaction->getNewValue(); + $devices = id(new AlmanacDeviceQuery()) + ->setViewer($this->getActor()) + ->withPHIDs(array($device_phid)) + ->execute(); + if (!$devices) { + $errors[] = $this->newInvalidError( + pht( + 'You can not attach an interface to a nonexistent or restricted '. + 'device.'), + $xaction); + continue; + } + } + + return $errors; + } + +} diff --git a/src/applications/almanac/xaction/AlmanacInterfaceNetworkTransaction.php b/src/applications/almanac/xaction/AlmanacInterfaceNetworkTransaction.php new file mode 100644 index 0000000000..50353d904d --- /dev/null +++ b/src/applications/almanac/xaction/AlmanacInterfaceNetworkTransaction.php @@ -0,0 +1,53 @@ +getNetworkPHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setNetworkPHID($value); + } + + public function getTitle() { + return pht( + '%s changed the network for this interface from %s to %s.', + $this->renderAuthor(), + $this->renderOldHandle(), + $this->renderNewHandle()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $network_phid = $object->getNetworkPHID(); + if ($this->isEmptyTextTransaction($network_phid, $xactions)) { + $errors[] = $this->newRequiredError( + pht('Interfaces must have a network.')); + } + + foreach ($xactions as $xaction) { + $network_phid = $xaction->getNewValue(); + + $networks = id(new AlmanacNetworkQuery()) + ->setViewer($this->getActor()) + ->withPHIDs(array($network_phid)) + ->execute(); + if (!$networks) { + $errors[] = $this->newInvalidError( + pht( + 'You can not put an interface on a nonexistent or restricted '. + 'network.'), + $xaction); + continue; + } + } + + return $errors; + } + +} diff --git a/src/applications/almanac/xaction/AlmanacInterfacePortTransaction.php b/src/applications/almanac/xaction/AlmanacInterfacePortTransaction.php new file mode 100644 index 0000000000..c6a60dee61 --- /dev/null +++ b/src/applications/almanac/xaction/AlmanacInterfacePortTransaction.php @@ -0,0 +1,53 @@ +getPort(); + + if ($port !== null) { + $port = (int)$port; + } + + return $port; + } + + public function applyInternalEffects($object, $value) { + $object->setPort((int)$value); + } + + public function getTitle() { + return pht( + '%s changed the port for this interface from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Interfaces must have a port number.')); + } + + foreach ($xactions as $xaction) { + $port = $xaction->getNewValue(); + + $port = (int)$port; + if ($port < 1 || $port > 65535) { + $errors[] = $this->newInvalidError( + pht('Port numbers must be between 1 and 65535, inclusive.'), + $xaction); + continue; + } + } + + return $errors; + } + +} diff --git a/src/applications/almanac/xaction/AlmanacInterfaceTransactionType.php b/src/applications/almanac/xaction/AlmanacInterfaceTransactionType.php new file mode 100644 index 0000000000..cf13663a18 --- /dev/null +++ b/src/applications/almanac/xaction/AlmanacInterfaceTransactionType.php @@ -0,0 +1,4 @@ + Date: Tue, 10 Apr 2018 06:09:51 -0700 Subject: [PATCH 27/53] Edit Interfaces in Almanac with EditEngine Summary: Depends on D19323. Ref T13120. Ref T12414. Move editing to modern stuff and fix some implementation errors from D19323 (mostly copy/paste stuff). Test Plan: - Created and edited interfaces. - Tried to create/edit an interface with a bogus/empty address/port, got errors. - Tried to create an interface on a bogus device, got an error. - Tried to create an interface on a device I could not edit, got an error. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13120, T12414 Differential Revision: https://secure.phabricator.com/D19324 --- src/__phutil_library_map__.php | 10 +- .../AlmanacInterfaceEditController.php | 151 ++---------------- .../editor/AlmanacInterfaceEditEngine.php | 139 ++++++++++++++++ .../AlmanacInterfaceAddressTransaction.php | 2 +- .../AlmanacInterfaceDeviceTransaction.php | 18 ++- .../AlmanacInterfaceNetworkTransaction.php | 2 +- .../AlmanacInterfacePortTransaction.php | 4 +- 7 files changed, 175 insertions(+), 151 deletions(-) create mode 100644 src/applications/almanac/editor/AlmanacInterfaceEditEngine.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 08806ab1b9..b3def5acde 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -62,6 +62,7 @@ phutil_register_library_map(array( 'AlmanacInterfaceDeleteController' => 'applications/almanac/controller/AlmanacInterfaceDeleteController.php', 'AlmanacInterfaceDeviceTransaction' => 'applications/almanac/xaction/AlmanacInterfaceDeviceTransaction.php', 'AlmanacInterfaceEditController' => 'applications/almanac/controller/AlmanacInterfaceEditController.php', + 'AlmanacInterfaceEditEngine' => 'applications/almanac/editor/AlmanacInterfaceEditEngine.php', 'AlmanacInterfaceEditor' => 'applications/almanac/editor/AlmanacInterfaceEditor.php', 'AlmanacInterfaceNetworkTransaction' => 'applications/almanac/xaction/AlmanacInterfaceNetworkTransaction.php', 'AlmanacInterfacePHIDType' => 'applications/almanac/phid/AlmanacInterfacePHIDType.php', @@ -5252,15 +5253,16 @@ phutil_register_library_map(array( 'PhabricatorExtendedPolicyInterface', 'PhabricatorApplicationTransactionInterface', ), - 'AlmanacInterfaceAddressTransaction' => 'AlmanacNetworkTransactionType', + 'AlmanacInterfaceAddressTransaction' => 'AlmanacInterfaceTransactionType', 'AlmanacInterfaceDatasource' => 'PhabricatorTypeaheadDatasource', 'AlmanacInterfaceDeleteController' => 'AlmanacDeviceController', - 'AlmanacInterfaceDeviceTransaction' => 'AlmanacNetworkTransactionType', + 'AlmanacInterfaceDeviceTransaction' => 'AlmanacInterfaceTransactionType', 'AlmanacInterfaceEditController' => 'AlmanacDeviceController', + 'AlmanacInterfaceEditEngine' => 'PhabricatorEditEngine', 'AlmanacInterfaceEditor' => 'PhabricatorApplicationTransactionEditor', - 'AlmanacInterfaceNetworkTransaction' => 'AlmanacNetworkTransactionType', + 'AlmanacInterfaceNetworkTransaction' => 'AlmanacInterfaceTransactionType', 'AlmanacInterfacePHIDType' => 'PhabricatorPHIDType', - 'AlmanacInterfacePortTransaction' => 'AlmanacNetworkTransactionType', + 'AlmanacInterfacePortTransaction' => 'AlmanacInterfaceTransactionType', 'AlmanacInterfaceQuery' => 'AlmanacQuery', 'AlmanacInterfaceTableView' => 'AphrontView', 'AlmanacInterfaceTransaction' => 'PhabricatorModularTransaction', diff --git a/src/applications/almanac/controller/AlmanacInterfaceEditController.php b/src/applications/almanac/controller/AlmanacInterfaceEditController.php index d223360003..017dad00ce 100644 --- a/src/applications/almanac/controller/AlmanacInterfaceEditController.php +++ b/src/applications/almanac/controller/AlmanacInterfaceEditController.php @@ -4,32 +4,16 @@ final class AlmanacInterfaceEditController extends AlmanacDeviceController { public function handleRequest(AphrontRequest $request) { - $viewer = $request->getViewer(); + $viewer = $this->getViewer(); + + $engine = id(new AlmanacInterfaceEditEngine()) + ->setController($this); $id = $request->getURIData('id'); - if ($id) { - $interface = id(new AlmanacInterfaceQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$interface) { - return new Aphront404Response(); - } - - $device = $interface->getDevice(); - - $is_new = false; - $title = pht('Edit Interface'); - $save_button = pht('Save Changes'); - } else { + if (!$id) { $device = id(new AlmanacDeviceQuery()) ->setViewer($viewer) - ->withIDs(array($request->getStr('deviceID'))) + ->withIDs(array($request->getInt('deviceID'))) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, @@ -40,127 +24,12 @@ final class AlmanacInterfaceEditController return new Aphront404Response(); } - $interface = AlmanacInterface::initializeNewInterface(); - $is_new = true; - - $title = pht('Create Interface'); - $save_button = pht('Create Interface'); + $engine + ->addContextParameter('deviceID', $device->getID()) + ->setDevice($device); } - $device_uri = $device->getURI(); - $cancel_uri = $device_uri; - - $v_network = $interface->getNetworkPHID(); - - $v_address = $interface->getAddress(); - $e_address = true; - - $v_port = $interface->getPort(); - - $validation_exception = null; - - if ($request->isFormPost()) { - $v_network = $request->getStr('networkPHID'); - $v_address = $request->getStr('address'); - $v_port = $request->getStr('port'); - - $type_interface = AlmanacDeviceTransaction::TYPE_INTERFACE; - - $address = AlmanacAddress::newFromParts($v_network, $v_address, $v_port); - - $xaction = id(new AlmanacDeviceTransaction()) - ->setTransactionType($type_interface) - ->setNewValue($address->toDictionary()); - - if ($interface->getID()) { - $xaction->setOldValue(array( - 'id' => $interface->getID(), - ) + $interface->toAddress()->toDictionary()); - } else { - $xaction->setOldValue(array()); - } - - $xactions = array(); - $xactions[] = $xaction; - - $editor = id(new AlmanacDeviceEditor()) - ->setActor($viewer) - ->setContentSourceFromRequest($request) - ->setContinueOnNoEffect(true) - ->setContinueOnMissingFields(true); - - try { - $editor->applyTransactions($device, $xactions); - - $device_uri = $device->getURI(); - return id(new AphrontRedirectResponse())->setURI($device_uri); - } catch (PhabricatorApplicationTransactionValidationException $ex) { - $validation_exception = $ex; - $e_address = $ex->getShortMessage($type_interface); - } - } - - $networks = id(new AlmanacNetworkQuery()) - ->setViewer($viewer) - ->execute(); - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setLabel(pht('Network')) - ->setName('networkPHID') - ->setValue($v_network) - ->setOptions(mpull($networks, 'getName', 'getPHID'))) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Address')) - ->setName('address') - ->setValue($v_address) - ->setError($e_address)) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Port')) - ->setName('port') - ->setValue($v_port) - ->setError($e_address)) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->addCancelButton($cancel_uri) - ->setValue($save_button)); - - $box = id(new PHUIObjectBoxView()) - ->setValidationException($validation_exception) - ->setHeaderText(pht('Interface')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setForm($form); - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb($device->getName(), $device_uri); - if ($is_new) { - $crumbs->addTextCrumb(pht('Create Interface')); - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Create Interface')) - ->setHeaderIcon('fa-plus-square'); - } else { - $crumbs->addTextCrumb(pht('Edit Interface')); - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Edit Interface')) - ->setHeaderIcon('fa-pencil'); - } - $crumbs->setBorder(true); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setFooter(array( - $box, - )); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild($view); - + return $engine->buildResponse(); } } diff --git a/src/applications/almanac/editor/AlmanacInterfaceEditEngine.php b/src/applications/almanac/editor/AlmanacInterfaceEditEngine.php new file mode 100644 index 0000000000..c7e7992743 --- /dev/null +++ b/src/applications/almanac/editor/AlmanacInterfaceEditEngine.php @@ -0,0 +1,139 @@ +device = $device; + return $this; + } + + public function getDevice() { + if (!$this->device) { + throw new PhutilInvalidStateException('setDevice'); + } + return $this->device; + } + + public function isEngineConfigurable() { + return false; + } + + public function getEngineName() { + return pht('Almanac Interfaces'); + } + + public function getSummaryHeader() { + return pht('Edit Almanac Interface Configurations'); + } + + public function getSummaryText() { + return pht('This engine is used to edit Almanac interfaces.'); + } + + public function getEngineApplicationClass() { + return 'PhabricatorAlmanacApplication'; + } + + protected function newEditableObject() { + $interface = AlmanacInterface::initializeNewInterface(); + + $device = $this->getDevice(); + $interface + ->setDevicePHID($device->getPHID()) + ->attachDevice($device); + + return $interface; + } + + protected function newObjectQuery() { + return new AlmanacInterfaceQuery(); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create Interface'); + } + + protected function getObjectCreateButtonText($object) { + return pht('Create Interface'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit Interface'); + } + + protected function getObjectEditShortText($object) { + return pht('Edit Interface'); + } + + protected function getObjectCreateShortText() { + return pht('Create Interface'); + } + + protected function getObjectName() { + return pht('Interface'); + } + + protected function getEditorURI() { + return '/almanac/interface/edit/'; + } + + protected function getObjectCreateCancelURI($object) { + return '/almanac/interface/'; + } + + protected function getObjectViewURI($object) { + return $object->getDevice()->getURI(); + } + + protected function buildCustomEditFields($object) { + $viewer = $this->getViewer(); + + // TODO: Some day, this should be a datasource. + $networks = id(new AlmanacNetworkQuery()) + ->setViewer($viewer) + ->execute(); + $network_map = mpull($networks, 'getName', 'getPHID'); + + return array( + id(new PhabricatorTextEditField()) + ->setKey('device') + ->setLabel(pht('Device')) + ->setIsConduitOnly(true) + ->setTransactionType( + AlmanacInterfaceDeviceTransaction::TRANSACTIONTYPE) + ->setDescription(pht('When creating an interface, set the device.')) + ->setConduitDescription(pht('Set the device.')) + ->setConduitTypeDescription(pht('Device PHID.')) + ->setValue($object->getDevicePHID()), + id(new PhabricatorSelectEditField()) + ->setKey('network') + ->setLabel(pht('Network')) + ->setDescription(pht('Network for the interface.')) + ->setTransactionType( + AlmanacInterfaceNetworkTransaction::TRANSACTIONTYPE) + ->setValue($object->getNetworkPHID()) + ->setOptions($network_map), + id(new PhabricatorTextEditField()) + ->setKey('address') + ->setLabel(pht('Address')) + ->setDescription(pht('Address of the service.')) + ->setTransactionType( + AlmanacInterfaceAddressTransaction::TRANSACTIONTYPE) + ->setIsRequired(true) + ->setValue($object->getAddress()), + id(new PhabricatorTextEditField()) + ->setKey('port') + ->setLabel(pht('Port')) + ->setDescription(pht('Port of the service.')) + ->setTransactionType(AlmanacInterfacePortTransaction::TRANSACTIONTYPE) + ->setIsRequired(true) + ->setValue($object->getPort()), + ); + } + +} diff --git a/src/applications/almanac/xaction/AlmanacInterfaceAddressTransaction.php b/src/applications/almanac/xaction/AlmanacInterfaceAddressTransaction.php index 47eab92c16..7eb8e0a567 100644 --- a/src/applications/almanac/xaction/AlmanacInterfaceAddressTransaction.php +++ b/src/applications/almanac/xaction/AlmanacInterfaceAddressTransaction.php @@ -1,7 +1,7 @@ isEmptyTextTransaction($object->getAddress(), $xactions)) { + $device_phid = $object->getDevicePHID(); + if ($this->isEmptyTextTransaction($device_phid, $xactions)) { $errors[] = $this->newRequiredError( pht('Interfaces must have a device.')); } @@ -52,6 +53,19 @@ final class AlmanacInterfaceDeviceTransaction $xaction); continue; } + + $device = head($devices); + $can_edit = PhabricatorPolicyFilter::hasCapability( + $this->getActor(), + $device, + PhabricatorPolicyCapability::CAN_EDIT); + if (!$can_edit) { + $errors[] = $this->newInvalidError( + pht( + 'You can not attach an interface to a device which you do not '. + 'have permission to edit.')); + continue; + } } return $errors; diff --git a/src/applications/almanac/xaction/AlmanacInterfaceNetworkTransaction.php b/src/applications/almanac/xaction/AlmanacInterfaceNetworkTransaction.php index 50353d904d..def85c08e2 100644 --- a/src/applications/almanac/xaction/AlmanacInterfaceNetworkTransaction.php +++ b/src/applications/almanac/xaction/AlmanacInterfaceNetworkTransaction.php @@ -1,7 +1,7 @@ isEmptyTextTransaction($object->getName(), $xactions)) { + if ($this->isEmptyTextTransaction($object->getPort(), $xactions)) { $errors[] = $this->newRequiredError( pht('Interfaces must have a port number.')); } From d240969e4705533cd1d2f20608e495ad6fa672d3 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 10 Apr 2018 06:22:39 -0700 Subject: [PATCH 28/53] Use Interface transactions, not Device transactions, to destroy Interfaces Summary: Depends on D19324. Ref T13120. Ref T12414. This moves "Destroy Interface" to use Interface transactions instead of Device transactions, so we can ultimately get rid of the complex and difficult-to-modernize `AlmanacDeviceTransaction::TYPE_INTERFACE`. This transaction is a bit weird since it makes the interface delete itself, but this should work OK for now. At some point in the future I'd probably want to change this into more of a "disable" action, but I don't think we face any immediate peril by retaining this behavior for now. Test Plan: - Destroyed interfaces on devices using the web UI, saw them vanish. - Ran daemons, nothing fataled/exploded even though the transaction is weird and destroys the object it affects. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13120, T12414 Differential Revision: https://secure.phabricator.com/D19325 --- src/__phutil_library_map__.php | 2 ++ .../AlmanacInterfaceDeleteController.php | 17 ++++------ .../AlmanacInterfaceDestroyTransaction.php | 32 +++++++++++++++++++ 3 files changed, 40 insertions(+), 11 deletions(-) create mode 100644 src/applications/almanac/xaction/AlmanacInterfaceDestroyTransaction.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index b3def5acde..955405f30c 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -60,6 +60,7 @@ phutil_register_library_map(array( 'AlmanacInterfaceAddressTransaction' => 'applications/almanac/xaction/AlmanacInterfaceAddressTransaction.php', 'AlmanacInterfaceDatasource' => 'applications/almanac/typeahead/AlmanacInterfaceDatasource.php', 'AlmanacInterfaceDeleteController' => 'applications/almanac/controller/AlmanacInterfaceDeleteController.php', + 'AlmanacInterfaceDestroyTransaction' => 'applications/almanac/xaction/AlmanacInterfaceDestroyTransaction.php', 'AlmanacInterfaceDeviceTransaction' => 'applications/almanac/xaction/AlmanacInterfaceDeviceTransaction.php', 'AlmanacInterfaceEditController' => 'applications/almanac/controller/AlmanacInterfaceEditController.php', 'AlmanacInterfaceEditEngine' => 'applications/almanac/editor/AlmanacInterfaceEditEngine.php', @@ -5256,6 +5257,7 @@ phutil_register_library_map(array( 'AlmanacInterfaceAddressTransaction' => 'AlmanacInterfaceTransactionType', 'AlmanacInterfaceDatasource' => 'PhabricatorTypeaheadDatasource', 'AlmanacInterfaceDeleteController' => 'AlmanacDeviceController', + 'AlmanacInterfaceDestroyTransaction' => 'AlmanacInterfaceTransactionType', 'AlmanacInterfaceDeviceTransaction' => 'AlmanacInterfaceTransactionType', 'AlmanacInterfaceEditController' => 'AlmanacDeviceController', 'AlmanacInterfaceEditEngine' => 'PhabricatorEditEngine', diff --git a/src/applications/almanac/controller/AlmanacInterfaceDeleteController.php b/src/applications/almanac/controller/AlmanacInterfaceDeleteController.php index ddbe979af6..4d40031b93 100644 --- a/src/applications/almanac/controller/AlmanacInterfaceDeleteController.php +++ b/src/applications/almanac/controller/AlmanacInterfaceDeleteController.php @@ -34,26 +34,21 @@ final class AlmanacInterfaceDeleteController } if ($request->isFormPost()) { - $type_interface = AlmanacDeviceTransaction::TYPE_INTERFACE; + $type_destroy = AlmanacInterfaceDestroyTransaction::TRANSACTIONTYPE; $xactions = array(); - $v_old = array( - 'id' => $interface->getID(), - ) + $interface->toAddress()->toDictionary(); + $xactions[] = $interface->getApplicationTransactionTemplate() + ->setTransactionType($type_destroy) + ->setNewValue(true); - $xactions[] = id(new AlmanacDeviceTransaction()) - ->setTransactionType($type_interface) - ->setOldValue($v_old) - ->setNewValue(null); - - $editor = id(new AlmanacDeviceEditor()) + $editor = id(new AlmanacInterfaceEditor()) ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true) ->setContinueOnMissingFields(true); - $editor->applyTransactions($device, $xactions); + $editor->applyTransactions($interface, $xactions); return id(new AphrontRedirectResponse())->setURI($device_uri); } diff --git a/src/applications/almanac/xaction/AlmanacInterfaceDestroyTransaction.php b/src/applications/almanac/xaction/AlmanacInterfaceDestroyTransaction.php new file mode 100644 index 0000000000..5e829ff311 --- /dev/null +++ b/src/applications/almanac/xaction/AlmanacInterfaceDestroyTransaction.php @@ -0,0 +1,32 @@ +destroyObject($object); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($xactions) { + if ($object->loadIsInUse()) { + $errors[] = $this->newInvalidError( + pht( + 'You can not delete this interface because it is currently in '. + 'use. One or more services are bound to it.')); + } + } + + return $errors; + } + +} From 4e156a0385093662aa69457b0befac50d7ea7262 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 10 Apr 2018 08:23:52 -0700 Subject: [PATCH 29/53] Remove TYPE_INTERFACE transaction from Almanac Device Summary: Depends on D19325. Ref T13120. Ref T12414. This no longer has any callers in the upstream or in Phacility support libraries, so get rid of it. This will make modularizing Device transactions significantly easier, since the other transactions are reasonable, normal sorts of transactions. For existing devices, this leaves some "author edited this object." transactions in the log. I might just leave those since they aren't really hurting anything, or maybe I'll clean them up or hide them later once I have more confidence that these changes are stable. Test Plan: Grepped for `TYPE_INTERFACE` and `AlmanacDeviceTransaction`, found no callsites. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13120, T12414 Differential Revision: https://secure.phabricator.com/D19328 --- .../almanac/editor/AlmanacDeviceEditor.php | 183 +----------------- .../storage/AlmanacDeviceTransaction.php | 55 ------ 2 files changed, 1 insertion(+), 237 deletions(-) diff --git a/src/applications/almanac/editor/AlmanacDeviceEditor.php b/src/applications/almanac/editor/AlmanacDeviceEditor.php index 8e110954a3..5a4246231e 100644 --- a/src/applications/almanac/editor/AlmanacDeviceEditor.php +++ b/src/applications/almanac/editor/AlmanacDeviceEditor.php @@ -11,7 +11,7 @@ final class AlmanacDeviceEditor $types = parent::getTransactionTypes(); $types[] = AlmanacDeviceTransaction::TYPE_NAME; - $types[] = AlmanacDeviceTransaction::TYPE_INTERFACE; + $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; @@ -35,7 +35,6 @@ final class AlmanacDeviceEditor switch ($xaction->getTransactionType()) { case AlmanacDeviceTransaction::TYPE_NAME: - case AlmanacDeviceTransaction::TYPE_INTERFACE: return $xaction->getNewValue(); } @@ -50,8 +49,6 @@ final class AlmanacDeviceEditor case AlmanacDeviceTransaction::TYPE_NAME: $object->setName($xaction->getNewValue()); return; - case AlmanacDeviceTransaction::TYPE_INTERFACE: - return; } return parent::applyCustomInternalTransaction($object, $xaction); @@ -64,37 +61,6 @@ final class AlmanacDeviceEditor switch ($xaction->getTransactionType()) { case AlmanacDeviceTransaction::TYPE_NAME: return; - case AlmanacDeviceTransaction::TYPE_INTERFACE: - $old = $xaction->getOldValue(); - if ($old) { - $interface = id(new AlmanacInterfaceQuery()) - ->setViewer($this->requireActor()) - ->withIDs(array($old['id'])) - ->executeOne(); - if (!$interface) { - throw new Exception(pht('Unable to load interface!')); - } - } else { - $interface = AlmanacInterface::initializeNewInterface() - ->setDevicePHID($object->getPHID()); - } - - $new = $xaction->getNewValue(); - if ($new) { - $interface - ->setNetworkPHID($new['networkPHID']) - ->setAddress($new['address']) - ->setPort((int)$new['port']); - - if (idx($new, 'phid')) { - $interface->setPHID($new['phid']); - } - - $interface->save(); - } else { - $interface->delete(); - } - return; } return parent::applyCustomExternalTransaction($object, $xaction); @@ -180,153 +146,6 @@ final class AlmanacDeviceEditor } break; - case AlmanacDeviceTransaction::TYPE_INTERFACE: - // We want to make sure that all the affected networks are visible to - // the actor, any edited interfaces exist, and that the actual address - // components are valid. - - $network_phids = array(); - foreach ($xactions as $xaction) { - $old = $xaction->getOldValue(); - $new = $xaction->getNewValue(); - if ($old) { - $network_phids[] = $old['networkPHID']; - } - if ($new) { - $network_phids[] = $new['networkPHID']; - - $address = $new['address']; - if (!strlen($address)) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht('Interfaces must have an address.'), - $xaction); - $errors[] = $error; - } else { - // TODO: Validate addresses, but IPv6 addresses are not trivial - // to validate. - } - - $port = $new['port']; - if (!strlen($port)) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht('Interfaces must have a port.'), - $xaction); - $errors[] = $error; - } else if ((int)$port < 1 || (int)$port > 65535) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'Port numbers must be between 1 and 65535, inclusive.'), - $xaction); - $errors[] = $error; - } - - $phid = idx($new, 'phid'); - if ($phid) { - $interface_phid_type = AlmanacInterfacePHIDType::TYPECONST; - if (phid_get_type($phid) !== $interface_phid_type) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'Precomputed interface PHIDs must be of type '. - 'AlmanacInterfacePHIDType.'), - $xaction); - $errors[] = $error; - } - } - } - } - - if ($network_phids) { - $networks = id(new AlmanacNetworkQuery()) - ->setViewer($this->requireActor()) - ->withPHIDs($network_phids) - ->execute(); - $networks = mpull($networks, null, 'getPHID'); - } else { - $networks = array(); - } - - $addresses = array(); - foreach ($xactions as $xaction) { - $old = $xaction->getOldValue(); - if ($old) { - $network = idx($networks, $old['networkPHID']); - if (!$network) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'You can not edit an interface which belongs to a '. - 'nonexistent or restricted network.'), - $xaction); - $errors[] = $error; - } - - $addresses[] = $old['id']; - } - - $new = $xaction->getNewValue(); - if ($new) { - $network = idx($networks, $new['networkPHID']); - if (!$network) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'You can not add an interface on a nonexistent or '. - 'restricted network.'), - $xaction); - $errors[] = $error; - } - } - } - - if ($addresses) { - $interfaces = id(new AlmanacInterfaceQuery()) - ->setViewer($this->requireActor()) - ->withDevicePHIDs(array($object->getPHID())) - ->withIDs($addresses) - ->execute(); - $interfaces = mpull($interfaces, null, 'getID'); - } else { - $interfaces = array(); - } - - foreach ($xactions as $xaction) { - $old = $xaction->getOldValue(); - if ($old) { - $interface = idx($interfaces, $old['id']); - if (!$interface) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht('You can not edit an invalid or restricted interface.'), - $xaction); - $errors[] = $error; - continue; - } - - $new = $xaction->getNewValue(); - if (!$new) { - if ($interface->loadIsInUse()) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('In Use'), - pht('You can not delete an interface which is still in use.'), - $xaction); - $errors[] = $error; - } - } - } - } - break; } return $errors; diff --git a/src/applications/almanac/storage/AlmanacDeviceTransaction.php b/src/applications/almanac/storage/AlmanacDeviceTransaction.php index fd1a21f782..b70ffbe008 100644 --- a/src/applications/almanac/storage/AlmanacDeviceTransaction.php +++ b/src/applications/almanac/storage/AlmanacDeviceTransaction.php @@ -4,7 +4,6 @@ final class AlmanacDeviceTransaction extends AlmanacTransaction { const TYPE_NAME = 'almanac:device:name'; - const TYPE_INTERFACE = 'almanac:device:interface'; public function getApplicationName() { return 'almanac'; @@ -18,26 +17,6 @@ final class AlmanacDeviceTransaction return null; } - public function getRequiredHandlePHIDs() { - $phids = parent::getRequiredHandlePHIDs(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_INTERFACE: - if ($old) { - $phids[] = $old['networkPHID']; - } - if ($new) { - $phids[] = $new['networkPHID']; - } - break; - } - - return $phids; - } - public function getTitle() { $author_phid = $this->getAuthorPHID(); @@ -58,43 +37,9 @@ final class AlmanacDeviceTransaction $new); } break; - case self::TYPE_INTERFACE: - if ($old && $new) { - return pht( - '%s changed interface %s on this device to %s.', - $this->renderHandleLink($author_phid), - $this->describeInterface($old), - $this->describeInterface($new)); - } else if ($old) { - return pht( - '%s removed the interface %s from this device.', - $this->renderHandleLink($author_phid), - $this->describeInterface($old)); - } else if ($new) { - return pht( - '%s added the interface %s to this device.', - $this->renderHandleLink($author_phid), - $this->describeInterface($new)); - } } return parent::getTitle(); } - public function shouldGenerateOldValue() { - switch ($this->getTransactionType()) { - case self::TYPE_INTERFACE: - return false; - } - return parent::shouldGenerateOldValue(); - } - - private function describeInterface(array $info) { - return pht( - '%s:%s (%s)', - $info['address'], - $info['port'], - $this->renderHandleLink($info['networkPHID'])); - } - } From 71c77fcc3a153801fa6ec464e4038e2a54d6bfeb Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 10 Apr 2018 08:39:10 -0700 Subject: [PATCH 30/53] Modularize transactions for Almanac Device Summary: Depends on D19328. Ref T13120. Ref T12414. Prior work has left us with just a NAME transaction here, which is straightforward to modularize. Test Plan: - Created and renamed devices. - Tried to set no name, a bad name, a duplicate name (got errors). - Tried to create/rename into a namespace I could not edit (got an error). - Grepped for `AlmanacDeviceTransaction::`. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13120, T12414 Differential Revision: https://secure.phabricator.com/D19329 --- src/__phutil_library_map__.php | 6 +- .../editor/AlmanacDeviceEditEngine.php | 2 +- .../almanac/editor/AlmanacDeviceEditor.php | 135 ------------------ .../storage/AlmanacDeviceTransaction.php | 29 +--- .../xaction/AlmanacDeviceNameTransaction.php | 79 ++++++++++ .../xaction/AlmanacDeviceTransactionType.php | 4 + 6 files changed, 92 insertions(+), 163 deletions(-) create mode 100644 src/applications/almanac/xaction/AlmanacDeviceNameTransaction.php create mode 100644 src/applications/almanac/xaction/AlmanacDeviceTransactionType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 955405f30c..6900238d6f 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -46,6 +46,7 @@ phutil_register_library_map(array( 'AlmanacDeviceEditor' => 'applications/almanac/editor/AlmanacDeviceEditor.php', 'AlmanacDeviceListController' => 'applications/almanac/controller/AlmanacDeviceListController.php', 'AlmanacDeviceNameNgrams' => 'applications/almanac/storage/AlmanacDeviceNameNgrams.php', + 'AlmanacDeviceNameTransaction' => 'applications/almanac/xaction/AlmanacDeviceNameTransaction.php', 'AlmanacDevicePHIDType' => 'applications/almanac/phid/AlmanacDevicePHIDType.php', 'AlmanacDevicePropertyEditEngine' => 'applications/almanac/editor/AlmanacDevicePropertyEditEngine.php', 'AlmanacDeviceQuery' => 'applications/almanac/query/AlmanacDeviceQuery.php', @@ -53,6 +54,7 @@ phutil_register_library_map(array( 'AlmanacDeviceSearchEngine' => 'applications/almanac/query/AlmanacDeviceSearchEngine.php', 'AlmanacDeviceTransaction' => 'applications/almanac/storage/AlmanacDeviceTransaction.php', 'AlmanacDeviceTransactionQuery' => 'applications/almanac/query/AlmanacDeviceTransactionQuery.php', + 'AlmanacDeviceTransactionType' => 'applications/almanac/xaction/AlmanacDeviceTransactionType.php', 'AlmanacDeviceViewController' => 'applications/almanac/controller/AlmanacDeviceViewController.php', 'AlmanacDrydockPoolServiceType' => 'applications/almanac/servicetype/AlmanacDrydockPoolServiceType.php', 'AlmanacEditor' => 'applications/almanac/editor/AlmanacEditor.php', @@ -5237,13 +5239,15 @@ phutil_register_library_map(array( 'AlmanacDeviceEditor' => 'AlmanacEditor', 'AlmanacDeviceListController' => 'AlmanacDeviceController', 'AlmanacDeviceNameNgrams' => 'PhabricatorSearchNgrams', + 'AlmanacDeviceNameTransaction' => 'AlmanacDeviceTransactionType', 'AlmanacDevicePHIDType' => 'PhabricatorPHIDType', 'AlmanacDevicePropertyEditEngine' => 'AlmanacPropertyEditEngine', 'AlmanacDeviceQuery' => 'AlmanacQuery', 'AlmanacDeviceSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'AlmanacDeviceSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'AlmanacDeviceTransaction' => 'AlmanacTransaction', + 'AlmanacDeviceTransaction' => 'PhabricatorModularTransaction', 'AlmanacDeviceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'AlmanacDeviceTransactionType' => 'AlmanacTransactionType', 'AlmanacDeviceViewController' => 'AlmanacDeviceController', 'AlmanacDrydockPoolServiceType' => 'AlmanacServiceType', 'AlmanacEditor' => 'PhabricatorApplicationTransactionEditor', diff --git a/src/applications/almanac/editor/AlmanacDeviceEditEngine.php b/src/applications/almanac/editor/AlmanacDeviceEditEngine.php index 1d325e403b..84ca59d310 100644 --- a/src/applications/almanac/editor/AlmanacDeviceEditEngine.php +++ b/src/applications/almanac/editor/AlmanacDeviceEditEngine.php @@ -80,7 +80,7 @@ final class AlmanacDeviceEditEngine ->setKey('name') ->setLabel(pht('Name')) ->setDescription(pht('Name of the device.')) - ->setTransactionType(AlmanacDeviceTransaction::TYPE_NAME) + ->setTransactionType(AlmanacDeviceNameTransaction::TRANSACTIONTYPE) ->setIsRequired(true) ->setValue($object->getName()), ); diff --git a/src/applications/almanac/editor/AlmanacDeviceEditor.php b/src/applications/almanac/editor/AlmanacDeviceEditor.php index 5a4246231e..ea04bffc07 100644 --- a/src/applications/almanac/editor/AlmanacDeviceEditor.php +++ b/src/applications/almanac/editor/AlmanacDeviceEditor.php @@ -10,145 +10,10 @@ final class AlmanacDeviceEditor public function getTransactionTypes() { $types = parent::getTransactionTypes(); - $types[] = AlmanacDeviceTransaction::TYPE_NAME; - $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case AlmanacDeviceTransaction::TYPE_NAME: - return $object->getName(); - } - - return parent::getCustomTransactionOldValue($object, $xaction); - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case AlmanacDeviceTransaction::TYPE_NAME: - return $xaction->getNewValue(); - } - - return parent::getCustomTransactionNewValue($object, $xaction); - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case AlmanacDeviceTransaction::TYPE_NAME: - $object->setName($xaction->getNewValue()); - return; - } - - return parent::applyCustomInternalTransaction($object, $xaction); - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case AlmanacDeviceTransaction::TYPE_NAME: - return; - } - - return parent::applyCustomExternalTransaction($object, $xaction); - } - - protected function validateTransaction( - PhabricatorLiskDAO $object, - $type, - array $xactions) { - - $errors = parent::validateTransaction($object, $type, $xactions); - - switch ($type) { - case AlmanacDeviceTransaction::TYPE_NAME: - $missing = $this->validateIsEmptyTextField( - $object->getName(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Device name is required.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } else { - foreach ($xactions as $xaction) { - $message = null; - $name = $xaction->getNewValue(); - - try { - AlmanacNames::validateName($name); - } catch (Exception $ex) { - $message = $ex->getMessage(); - } - - if ($message !== null) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - $message, - $xaction); - $errors[] = $error; - continue; - } - - $other = id(new AlmanacDeviceQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withNames(array($name)) - ->executeOne(); - if ($other && ($other->getID() != $object->getID())) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Not Unique'), - pht('Almanac devices must have unique names.'), - $xaction); - $errors[] = $error; - continue; - } - - if ($name === $object->getName()) { - continue; - } - - $namespace = AlmanacNamespace::loadRestrictedNamespace( - $this->getActor(), - $name); - if ($namespace) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Restricted'), - pht( - 'You do not have permission to create Almanac devices '. - 'within the "%s" namespace.', - $namespace->getName()), - $xaction); - $errors[] = $error; - continue; - } - } - } - - break; - } - - return $errors; - } - } diff --git a/src/applications/almanac/storage/AlmanacDeviceTransaction.php b/src/applications/almanac/storage/AlmanacDeviceTransaction.php index b70ffbe008..e74def1a9b 100644 --- a/src/applications/almanac/storage/AlmanacDeviceTransaction.php +++ b/src/applications/almanac/storage/AlmanacDeviceTransaction.php @@ -1,9 +1,7 @@ getAuthorPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_NAME: - if ($old === null) { - return pht( - '%s created this device.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s renamed this device from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - } - break; - } - - return parent::getTitle(); + public function getBaseTransactionClass() { + return 'AlmanacDeviceTransactionType'; } } diff --git a/src/applications/almanac/xaction/AlmanacDeviceNameTransaction.php b/src/applications/almanac/xaction/AlmanacDeviceNameTransaction.php new file mode 100644 index 0000000000..d72b380472 --- /dev/null +++ b/src/applications/almanac/xaction/AlmanacDeviceNameTransaction.php @@ -0,0 +1,79 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + } + + public function getTitle() { + return pht( + '%s renamed this device from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Device name is required.')); + } + + foreach ($xactions as $xaction) { + $name = $xaction->getNewValue(); + + $message = null; + try { + AlmanacNames::validateName($name); + } catch (Exception $ex) { + $message = $ex->getMessage(); + } + + if ($message !== null) { + $errors[] = $this->newInvalidError($message, $xaction); + continue; + } + + if ($name === $object->getName()) { + continue; + } + + $other = id(new AlmanacDeviceQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withNames(array($name)) + ->executeOne(); + if ($other && ($other->getID() != $object->getID())) { + $errors[] = $this->newInvalidError( + pht('Almanac devices must have unique names.'), + $xaction); + continue; + } + + $namespace = AlmanacNamespace::loadRestrictedNamespace( + $this->getActor(), + $name); + if ($namespace) { + $errors[] = $this->newInvalidError( + pht( + 'You do not have permission to create Almanac devices '. + 'within the "%s" namespace.', + $namespace->getName()), + $xaction); + continue; + } + } + + return $errors; + } + +} diff --git a/src/applications/almanac/xaction/AlmanacDeviceTransactionType.php b/src/applications/almanac/xaction/AlmanacDeviceTransactionType.php new file mode 100644 index 0000000000..cf3b239ab2 --- /dev/null +++ b/src/applications/almanac/xaction/AlmanacDeviceTransactionType.php @@ -0,0 +1,4 @@ + Date: Tue, 10 Apr 2018 10:37:47 -0700 Subject: [PATCH 31/53] Modularize Almanac property transactions Summary: Depends on D19329. Ref T13120. Ref T12414. Recent changes have mostly modularized Almanac transactions, but the "property" transactions remained written in an older style with the logic on the Editor/Transaction classes. This moves them to modern modular transactions. These end up being a little bit copy-pastey, but it doesn't feel too terribly bad. Test Plan: Created, edited, and deleted properties on services, devices and bindings. Grepped for removed constants. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13120, T12414 Differential Revision: https://secure.phabricator.com/D19334 --- src/__phutil_library_map__.php | 34 ++-- .../AlmanacPropertyDeleteController.php | 4 +- .../almanac/editor/AlmanacBindingEditor.php | 4 + .../almanac/editor/AlmanacDeviceEditor.php | 12 ++ .../almanac/editor/AlmanacEditor.php | 146 ------------------ .../almanac/editor/AlmanacInterfaceEditor.php | 6 +- .../almanac/editor/AlmanacNamespaceEditor.php | 6 +- .../almanac/editor/AlmanacNetworkEditor.php | 14 +- .../editor/AlmanacPropertyEditEngine.php | 2 +- .../property/AlmanacPropertyInterface.php | 2 + .../almanac/storage/AlmanacBinding.php | 8 + .../storage/AlmanacBindingTransaction.php | 10 +- .../almanac/storage/AlmanacDevice.php | 8 + .../storage/AlmanacDeviceTransaction.php | 10 +- .../storage/AlmanacInterfaceTransaction.php | 10 +- .../storage/AlmanacModularTransaction.php | 14 ++ .../almanac/storage/AlmanacNamespace.php | 8 + .../storage/AlmanacNamespaceTransaction.php | 10 +- .../storage/AlmanacNetworkTransaction.php | 10 +- .../almanac/storage/AlmanacProperty.php | 4 +- .../almanac/storage/AlmanacService.php | 8 + .../storage/AlmanacServiceTransaction.php | 10 +- .../almanac/storage/AlmanacTransaction.php | 38 ----- ...lmanacBindingDeletePropertyTransaction.php | 20 +++ .../AlmanacBindingSetPropertyTransaction.php | 24 +++ ...AlmanacDeviceDeletePropertyTransaction.php | 20 +++ .../AlmanacDeviceSetPropertyTransaction.php | 24 +++ ...lmanacServiceDeletePropertyTransaction.php | 20 +++ .../AlmanacServiceSetPropertyTransaction.php | 24 +++ .../xaction/AlmanacTransactionType.php | 95 +++++++++++- 30 files changed, 332 insertions(+), 273 deletions(-) create mode 100644 src/applications/almanac/storage/AlmanacModularTransaction.php delete mode 100644 src/applications/almanac/storage/AlmanacTransaction.php create mode 100644 src/applications/almanac/xaction/AlmanacBindingDeletePropertyTransaction.php create mode 100644 src/applications/almanac/xaction/AlmanacBindingSetPropertyTransaction.php create mode 100644 src/applications/almanac/xaction/AlmanacDeviceDeletePropertyTransaction.php create mode 100644 src/applications/almanac/xaction/AlmanacDeviceSetPropertyTransaction.php create mode 100644 src/applications/almanac/xaction/AlmanacServiceDeletePropertyTransaction.php create mode 100644 src/applications/almanac/xaction/AlmanacServiceSetPropertyTransaction.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 6900238d6f..118196a5d7 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -12,6 +12,7 @@ phutil_register_library_map(array( 'AlamancServiceEditConduitAPIMethod' => 'applications/almanac/conduit/AlamancServiceEditConduitAPIMethod.php', 'AlmanacAddress' => 'applications/almanac/util/AlmanacAddress.php', 'AlmanacBinding' => 'applications/almanac/storage/AlmanacBinding.php', + 'AlmanacBindingDeletePropertyTransaction' => 'applications/almanac/xaction/AlmanacBindingDeletePropertyTransaction.php', 'AlmanacBindingDisableController' => 'applications/almanac/controller/AlmanacBindingDisableController.php', 'AlmanacBindingDisableTransaction' => 'applications/almanac/xaction/AlmanacBindingDisableTransaction.php', 'AlmanacBindingEditController' => 'applications/almanac/controller/AlmanacBindingEditController.php', @@ -20,6 +21,7 @@ phutil_register_library_map(array( 'AlmanacBindingPHIDType' => 'applications/almanac/phid/AlmanacBindingPHIDType.php', 'AlmanacBindingPropertyEditEngine' => 'applications/almanac/editor/AlmanacBindingPropertyEditEngine.php', 'AlmanacBindingQuery' => 'applications/almanac/query/AlmanacBindingQuery.php', + 'AlmanacBindingSetPropertyTransaction' => 'applications/almanac/xaction/AlmanacBindingSetPropertyTransaction.php', 'AlmanacBindingTableView' => 'applications/almanac/view/AlmanacBindingTableView.php', 'AlmanacBindingTransaction' => 'applications/almanac/storage/AlmanacBindingTransaction.php', 'AlmanacBindingTransactionQuery' => 'applications/almanac/query/AlmanacBindingTransactionQuery.php', @@ -40,6 +42,7 @@ phutil_register_library_map(array( 'AlmanacDAO' => 'applications/almanac/storage/AlmanacDAO.php', 'AlmanacDevice' => 'applications/almanac/storage/AlmanacDevice.php', 'AlmanacDeviceController' => 'applications/almanac/controller/AlmanacDeviceController.php', + 'AlmanacDeviceDeletePropertyTransaction' => 'applications/almanac/xaction/AlmanacDeviceDeletePropertyTransaction.php', 'AlmanacDeviceEditConduitAPIMethod' => 'applications/almanac/conduit/AlmanacDeviceEditConduitAPIMethod.php', 'AlmanacDeviceEditController' => 'applications/almanac/controller/AlmanacDeviceEditController.php', 'AlmanacDeviceEditEngine' => 'applications/almanac/editor/AlmanacDeviceEditEngine.php', @@ -52,6 +55,7 @@ phutil_register_library_map(array( 'AlmanacDeviceQuery' => 'applications/almanac/query/AlmanacDeviceQuery.php', 'AlmanacDeviceSearchConduitAPIMethod' => 'applications/almanac/conduit/AlmanacDeviceSearchConduitAPIMethod.php', 'AlmanacDeviceSearchEngine' => 'applications/almanac/query/AlmanacDeviceSearchEngine.php', + 'AlmanacDeviceSetPropertyTransaction' => 'applications/almanac/xaction/AlmanacDeviceSetPropertyTransaction.php', 'AlmanacDeviceTransaction' => 'applications/almanac/storage/AlmanacDeviceTransaction.php', 'AlmanacDeviceTransactionQuery' => 'applications/almanac/query/AlmanacDeviceTransactionQuery.php', 'AlmanacDeviceTransactionType' => 'applications/almanac/xaction/AlmanacDeviceTransactionType.php', @@ -80,6 +84,7 @@ phutil_register_library_map(array( 'AlmanacManagementTrustKeyWorkflow' => 'applications/almanac/management/AlmanacManagementTrustKeyWorkflow.php', 'AlmanacManagementUntrustKeyWorkflow' => 'applications/almanac/management/AlmanacManagementUntrustKeyWorkflow.php', 'AlmanacManagementWorkflow' => 'applications/almanac/management/AlmanacManagementWorkflow.php', + 'AlmanacModularTransaction' => 'applications/almanac/storage/AlmanacModularTransaction.php', 'AlmanacNames' => 'applications/almanac/util/AlmanacNames.php', 'AlmanacNamesTestCase' => 'applications/almanac/util/__tests__/AlmanacNamesTestCase.php', 'AlmanacNamespace' => 'applications/almanac/storage/AlmanacNamespace.php', @@ -127,6 +132,7 @@ phutil_register_library_map(array( 'AlmanacService' => 'applications/almanac/storage/AlmanacService.php', 'AlmanacServiceController' => 'applications/almanac/controller/AlmanacServiceController.php', 'AlmanacServiceDatasource' => 'applications/almanac/typeahead/AlmanacServiceDatasource.php', + 'AlmanacServiceDeletePropertyTransaction' => 'applications/almanac/xaction/AlmanacServiceDeletePropertyTransaction.php', 'AlmanacServiceEditController' => 'applications/almanac/controller/AlmanacServiceEditController.php', 'AlmanacServiceEditEngine' => 'applications/almanac/editor/AlmanacServiceEditEngine.php', 'AlmanacServiceEditor' => 'applications/almanac/editor/AlmanacServiceEditor.php', @@ -138,6 +144,7 @@ phutil_register_library_map(array( 'AlmanacServiceQuery' => 'applications/almanac/query/AlmanacServiceQuery.php', 'AlmanacServiceSearchConduitAPIMethod' => 'applications/almanac/conduit/AlmanacServiceSearchConduitAPIMethod.php', 'AlmanacServiceSearchEngine' => 'applications/almanac/query/AlmanacServiceSearchEngine.php', + 'AlmanacServiceSetPropertyTransaction' => 'applications/almanac/xaction/AlmanacServiceSetPropertyTransaction.php', 'AlmanacServiceTransaction' => 'applications/almanac/storage/AlmanacServiceTransaction.php', 'AlmanacServiceTransactionQuery' => 'applications/almanac/query/AlmanacServiceTransactionQuery.php', 'AlmanacServiceTransactionType' => 'applications/almanac/xaction/AlmanacServiceTransactionType.php', @@ -146,7 +153,6 @@ phutil_register_library_map(array( 'AlmanacServiceTypeTestCase' => 'applications/almanac/servicetype/__tests__/AlmanacServiceTypeTestCase.php', 'AlmanacServiceTypeTransaction' => 'applications/almanac/xaction/AlmanacServiceTypeTransaction.php', 'AlmanacServiceViewController' => 'applications/almanac/controller/AlmanacServiceViewController.php', - 'AlmanacTransaction' => 'applications/almanac/storage/AlmanacTransaction.php', 'AlmanacTransactionType' => 'applications/almanac/xaction/AlmanacTransactionType.php', 'AphlictDropdownDataQuery' => 'applications/aphlict/query/AphlictDropdownDataQuery.php', 'Aphront304Response' => 'aphront/response/Aphront304Response.php', @@ -5194,6 +5200,7 @@ phutil_register_library_map(array( 'PhabricatorDestructibleInterface', 'PhabricatorExtendedPolicyInterface', ), + 'AlmanacBindingDeletePropertyTransaction' => 'AlmanacBindingTransactionType', 'AlmanacBindingDisableController' => 'AlmanacServiceController', 'AlmanacBindingDisableTransaction' => 'AlmanacBindingTransactionType', 'AlmanacBindingEditController' => 'AlmanacServiceController', @@ -5202,8 +5209,9 @@ phutil_register_library_map(array( 'AlmanacBindingPHIDType' => 'PhabricatorPHIDType', 'AlmanacBindingPropertyEditEngine' => 'AlmanacPropertyEditEngine', 'AlmanacBindingQuery' => 'AlmanacQuery', + 'AlmanacBindingSetPropertyTransaction' => 'AlmanacBindingTransactionType', 'AlmanacBindingTableView' => 'AphrontView', - 'AlmanacBindingTransaction' => 'PhabricatorModularTransaction', + 'AlmanacBindingTransaction' => 'AlmanacModularTransaction', 'AlmanacBindingTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'AlmanacBindingTransactionType' => 'AlmanacTransactionType', 'AlmanacBindingViewController' => 'AlmanacServiceController', @@ -5233,6 +5241,7 @@ phutil_register_library_map(array( 'PhabricatorExtendedPolicyInterface', ), 'AlmanacDeviceController' => 'AlmanacController', + 'AlmanacDeviceDeletePropertyTransaction' => 'AlmanacDeviceTransactionType', 'AlmanacDeviceEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'AlmanacDeviceEditController' => 'AlmanacDeviceController', 'AlmanacDeviceEditEngine' => 'PhabricatorEditEngine', @@ -5245,7 +5254,8 @@ phutil_register_library_map(array( 'AlmanacDeviceQuery' => 'AlmanacQuery', 'AlmanacDeviceSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'AlmanacDeviceSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'AlmanacDeviceTransaction' => 'PhabricatorModularTransaction', + 'AlmanacDeviceSetPropertyTransaction' => 'AlmanacDeviceTransactionType', + 'AlmanacDeviceTransaction' => 'AlmanacModularTransaction', 'AlmanacDeviceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'AlmanacDeviceTransactionType' => 'AlmanacTransactionType', 'AlmanacDeviceViewController' => 'AlmanacDeviceController', @@ -5265,13 +5275,13 @@ phutil_register_library_map(array( 'AlmanacInterfaceDeviceTransaction' => 'AlmanacInterfaceTransactionType', 'AlmanacInterfaceEditController' => 'AlmanacDeviceController', 'AlmanacInterfaceEditEngine' => 'PhabricatorEditEngine', - 'AlmanacInterfaceEditor' => 'PhabricatorApplicationTransactionEditor', + 'AlmanacInterfaceEditor' => 'AlmanacEditor', 'AlmanacInterfaceNetworkTransaction' => 'AlmanacInterfaceTransactionType', 'AlmanacInterfacePHIDType' => 'PhabricatorPHIDType', 'AlmanacInterfacePortTransaction' => 'AlmanacInterfaceTransactionType', 'AlmanacInterfaceQuery' => 'AlmanacQuery', 'AlmanacInterfaceTableView' => 'AphrontView', - 'AlmanacInterfaceTransaction' => 'PhabricatorModularTransaction', + 'AlmanacInterfaceTransaction' => 'AlmanacModularTransaction', 'AlmanacInterfaceTransactionType' => 'AlmanacTransactionType', 'AlmanacKeys' => 'Phobject', 'AlmanacManageClusterServicesCapability' => 'PhabricatorPolicyCapability', @@ -5279,6 +5289,7 @@ phutil_register_library_map(array( 'AlmanacManagementTrustKeyWorkflow' => 'AlmanacManagementWorkflow', 'AlmanacManagementUntrustKeyWorkflow' => 'AlmanacManagementWorkflow', 'AlmanacManagementWorkflow' => 'PhabricatorManagementWorkflow', + 'AlmanacModularTransaction' => 'PhabricatorModularTransaction', 'AlmanacNames' => 'Phobject', 'AlmanacNamesTestCase' => 'PhabricatorTestCase', 'AlmanacNamespace' => array( @@ -5293,14 +5304,14 @@ phutil_register_library_map(array( 'AlmanacNamespaceController' => 'AlmanacController', 'AlmanacNamespaceEditController' => 'AlmanacNamespaceController', 'AlmanacNamespaceEditEngine' => 'PhabricatorEditEngine', - 'AlmanacNamespaceEditor' => 'PhabricatorApplicationTransactionEditor', + 'AlmanacNamespaceEditor' => 'AlmanacEditor', 'AlmanacNamespaceListController' => 'AlmanacNamespaceController', 'AlmanacNamespaceNameNgrams' => 'PhabricatorSearchNgrams', 'AlmanacNamespaceNameTransaction' => 'AlmanacNamespaceTransactionType', 'AlmanacNamespacePHIDType' => 'PhabricatorPHIDType', 'AlmanacNamespaceQuery' => 'AlmanacQuery', 'AlmanacNamespaceSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'AlmanacNamespaceTransaction' => 'PhabricatorModularTransaction', + 'AlmanacNamespaceTransaction' => 'AlmanacModularTransaction', 'AlmanacNamespaceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'AlmanacNamespaceTransactionType' => 'AlmanacTransactionType', 'AlmanacNamespaceViewController' => 'AlmanacNamespaceController', @@ -5314,14 +5325,14 @@ phutil_register_library_map(array( 'AlmanacNetworkController' => 'AlmanacController', 'AlmanacNetworkEditController' => 'AlmanacNetworkController', 'AlmanacNetworkEditEngine' => 'PhabricatorEditEngine', - 'AlmanacNetworkEditor' => 'PhabricatorApplicationTransactionEditor', + 'AlmanacNetworkEditor' => 'AlmanacEditor', 'AlmanacNetworkListController' => 'AlmanacNetworkController', 'AlmanacNetworkNameNgrams' => 'PhabricatorSearchNgrams', 'AlmanacNetworkNameTransaction' => 'AlmanacNetworkTransactionType', 'AlmanacNetworkPHIDType' => 'PhabricatorPHIDType', 'AlmanacNetworkQuery' => 'AlmanacQuery', 'AlmanacNetworkSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'AlmanacNetworkTransaction' => 'PhabricatorModularTransaction', + 'AlmanacNetworkTransaction' => 'AlmanacModularTransaction', 'AlmanacNetworkTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'AlmanacNetworkTransactionType' => 'AlmanacTransactionType', 'AlmanacNetworkViewController' => 'AlmanacNetworkController', @@ -5352,6 +5363,7 @@ phutil_register_library_map(array( ), 'AlmanacServiceController' => 'AlmanacController', 'AlmanacServiceDatasource' => 'PhabricatorTypeaheadDatasource', + 'AlmanacServiceDeletePropertyTransaction' => 'AlmanacServiceTransactionType', 'AlmanacServiceEditController' => 'AlmanacServiceController', 'AlmanacServiceEditEngine' => 'PhabricatorEditEngine', 'AlmanacServiceEditor' => 'AlmanacEditor', @@ -5363,7 +5375,8 @@ phutil_register_library_map(array( 'AlmanacServiceQuery' => 'AlmanacQuery', 'AlmanacServiceSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'AlmanacServiceSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'AlmanacServiceTransaction' => 'PhabricatorModularTransaction', + 'AlmanacServiceSetPropertyTransaction' => 'AlmanacServiceTransactionType', + 'AlmanacServiceTransaction' => 'AlmanacModularTransaction', 'AlmanacServiceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'AlmanacServiceTransactionType' => 'AlmanacTransactionType', 'AlmanacServiceType' => 'Phobject', @@ -5371,7 +5384,6 @@ phutil_register_library_map(array( 'AlmanacServiceTypeTestCase' => 'PhabricatorTestCase', 'AlmanacServiceTypeTransaction' => 'AlmanacServiceTransactionType', 'AlmanacServiceViewController' => 'AlmanacServiceController', - 'AlmanacTransaction' => 'PhabricatorApplicationTransaction', 'AlmanacTransactionType' => 'PhabricatorModularTransactionType', 'AphlictDropdownDataQuery' => 'Phobject', 'Aphront304Response' => 'AphrontResponse', diff --git a/src/applications/almanac/controller/AlmanacPropertyDeleteController.php b/src/applications/almanac/controller/AlmanacPropertyDeleteController.php index 93ec4ced64..f38670041b 100644 --- a/src/applications/almanac/controller/AlmanacPropertyDeleteController.php +++ b/src/applications/almanac/controller/AlmanacPropertyDeleteController.php @@ -39,8 +39,10 @@ final class AlmanacPropertyDeleteController $validation_exception = null; if ($request->isFormPost()) { + $xaction_type = $object->getAlmanacPropertyDeleteTransactionType(); + $xaction = $object->getApplicationTransactionTemplate() - ->setTransactionType(AlmanacTransaction::TYPE_PROPERTY_REMOVE) + ->setTransactionType($xaction_type) ->setMetadataValue('almanac.property', $key); $editor = $object->getApplicationTransactionEditor() diff --git a/src/applications/almanac/editor/AlmanacBindingEditor.php b/src/applications/almanac/editor/AlmanacBindingEditor.php index d84845722c..f0d164317a 100644 --- a/src/applications/almanac/editor/AlmanacBindingEditor.php +++ b/src/applications/almanac/editor/AlmanacBindingEditor.php @@ -15,4 +15,8 @@ final class AlmanacBindingEditor return pht('%s created %s.', $author, $object); } + protected function supportsSearch() { + return true; + } + } diff --git a/src/applications/almanac/editor/AlmanacDeviceEditor.php b/src/applications/almanac/editor/AlmanacDeviceEditor.php index ea04bffc07..71032f1482 100644 --- a/src/applications/almanac/editor/AlmanacDeviceEditor.php +++ b/src/applications/almanac/editor/AlmanacDeviceEditor.php @@ -7,6 +7,18 @@ final class AlmanacDeviceEditor return pht('Almanac Device'); } + public function getCreateObjectTitle($author, $object) { + return pht('%s created this device.', $author); + } + + public function getCreateObjectTitleForFeed($author, $object) { + return pht('%s created %s.', $author, $object); + } + + protected function supportsSearch() { + return true; + } + public function getTransactionTypes() { $types = parent::getTransactionTypes(); diff --git a/src/applications/almanac/editor/AlmanacEditor.php b/src/applications/almanac/editor/AlmanacEditor.php index a628606dbc..54c32c79cd 100644 --- a/src/applications/almanac/editor/AlmanacEditor.php +++ b/src/applications/almanac/editor/AlmanacEditor.php @@ -7,150 +7,4 @@ abstract class AlmanacEditor return 'PhabricatorAlmanacApplication'; } - protected function supportsSearch() { - return true; - } - - public function getTransactionTypes() { - $types = parent::getTransactionTypes(); - - $types[] = AlmanacTransaction::TYPE_PROPERTY_UPDATE; - $types[] = AlmanacTransaction::TYPE_PROPERTY_REMOVE; - - return $types; - } - - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case AlmanacTransaction::TYPE_PROPERTY_UPDATE: - case AlmanacTransaction::TYPE_PROPERTY_REMOVE: - $property_key = $xaction->getMetadataValue('almanac.property'); - $exists = $object->hasAlmanacProperty($property_key); - $value = $object->getAlmanacPropertyValue($property_key); - return array( - 'existed' => $exists, - 'value' => $value, - ); - } - - return parent::getCustomTransactionOldValue($object, $xaction); - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case AlmanacTransaction::TYPE_PROPERTY_UPDATE: - case AlmanacTransaction::TYPE_PROPERTY_REMOVE: - return $xaction->getNewValue(); - } - - return parent::getCustomTransactionNewValue($object, $xaction); - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case AlmanacTransaction::TYPE_PROPERTY_UPDATE: - case AlmanacTransaction::TYPE_PROPERTY_REMOVE: - return; - } - - return parent::applyCustomInternalTransaction($object, $xaction); - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case AlmanacTransaction::TYPE_PROPERTY_UPDATE: - $property_key = $xaction->getMetadataValue('almanac.property'); - if ($object->hasAlmanacProperty($property_key)) { - $property = $object->getAlmanacProperty($property_key); - } else { - $property = id(new AlmanacProperty()) - ->setObjectPHID($object->getPHID()) - ->setFieldName($property_key); - } - $property - ->setFieldValue($xaction->getNewValue()) - ->save(); - return; - case AlmanacTransaction::TYPE_PROPERTY_REMOVE: - $property_key = $xaction->getMetadataValue('almanac.property'); - if ($object->hasAlmanacProperty($property_key)) { - $property = $object->getAlmanacProperty($property_key); - $property->delete(); - } - return; - } - - return parent::applyCustomExternalTransaction($object, $xaction); - } - - protected function validateTransaction( - PhabricatorLiskDAO $object, - $type, - array $xactions) { - - $errors = parent::validateTransaction($object, $type, $xactions); - - switch ($type) { - case AlmanacTransaction::TYPE_PROPERTY_UPDATE: - foreach ($xactions as $xaction) { - $property_key = $xaction->getMetadataValue('almanac.property'); - - $message = null; - try { - AlmanacNames::validateName($property_key); - } catch (Exception $ex) { - $message = $ex->getMessage(); - } - - if ($message !== null) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - $message, - $xaction); - $errors[] = $error; - continue; - } - - $new_value = $xaction->getNewValue(); - try { - phutil_json_encode($new_value); - } catch (Exception $ex) { - $message = pht( - 'Almanac property values must be representable in JSON. %s', - $ex->getMessage()); - } - - if ($message !== null) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - $message, - $xaction); - $errors[] = $error; - continue; - } - } - break; - - case AlmanacTransaction::TYPE_PROPERTY_REMOVE: - // NOTE: No name validation on removals since it's OK to delete - // an invalid property that somehow came into existence. - break; - } - - return $errors; - } - } diff --git a/src/applications/almanac/editor/AlmanacInterfaceEditor.php b/src/applications/almanac/editor/AlmanacInterfaceEditor.php index 066b026dbd..865402db36 100644 --- a/src/applications/almanac/editor/AlmanacInterfaceEditor.php +++ b/src/applications/almanac/editor/AlmanacInterfaceEditor.php @@ -1,11 +1,7 @@ getPropertyKey(); - $xaction_type = AlmanacTransaction::TYPE_PROPERTY_UPDATE; + $xaction_type = $object->getAlmanacPropertySetTransactionType(); return array( id(new PhabricatorTextEditField()) diff --git a/src/applications/almanac/property/AlmanacPropertyInterface.php b/src/applications/almanac/property/AlmanacPropertyInterface.php index b50b4089a8..6f3224ddee 100644 --- a/src/applications/almanac/property/AlmanacPropertyInterface.php +++ b/src/applications/almanac/property/AlmanacPropertyInterface.php @@ -9,5 +9,7 @@ interface AlmanacPropertyInterface { public function getAlmanacPropertyValue($key, $default = null); public function getAlmanacPropertyFieldSpecifications(); public function newAlmanacPropertyEditEngine(); + public function getAlmanacPropertySetTransactionType(); + public function getAlmanacPropertyDeleteTransactionType(); } diff --git a/src/applications/almanac/storage/AlmanacBinding.php b/src/applications/almanac/storage/AlmanacBinding.php index 9d0fbc1622..59a7666f8c 100644 --- a/src/applications/almanac/storage/AlmanacBinding.php +++ b/src/applications/almanac/storage/AlmanacBinding.php @@ -134,6 +134,14 @@ final class AlmanacBinding return new AlmanacBindingPropertyEditEngine(); } + public function getAlmanacPropertySetTransactionType() { + return AlmanacBindingSetPropertyTransaction::TRANSACTIONTYPE; + } + + public function getAlmanacPropertyDeleteTransactionType() { + return AlmanacBindingDeletePropertyTransaction::TRANSACTIONTYPE; + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/almanac/storage/AlmanacBindingTransaction.php b/src/applications/almanac/storage/AlmanacBindingTransaction.php index 24b606639f..d17eb59000 100644 --- a/src/applications/almanac/storage/AlmanacBindingTransaction.php +++ b/src/applications/almanac/storage/AlmanacBindingTransaction.php @@ -1,20 +1,12 @@ setTransactionType(AlmanacTransaction::TYPE_PROPERTY_UPDATE) + ->setTransactionType($object->getAlmanacPropertySetTransactionType()) ->setMetadataValue('almanac.property', $name) ->setNewValue($property); } @@ -71,7 +71,7 @@ final class AlmanacProperty $xactions = array(); foreach ($properties as $property) { $xactions[] = id(clone $template) - ->setTransactionType(AlmanacTransaction::TYPE_PROPERTY_REMOVE) + ->setTransactionType($object->getAlmanacPropertyDeleteTransactionType()) ->setMetadataValue('almanac.property', $property) ->setNewValue(null); } diff --git a/src/applications/almanac/storage/AlmanacService.php b/src/applications/almanac/storage/AlmanacService.php index 9ef42d6407..361eabca2a 100644 --- a/src/applications/almanac/storage/AlmanacService.php +++ b/src/applications/almanac/storage/AlmanacService.php @@ -160,6 +160,14 @@ final class AlmanacService return new AlmanacServicePropertyEditEngine(); } + public function getAlmanacPropertySetTransactionType() { + return AlmanacServiceSetPropertyTransaction::TRANSACTIONTYPE; + } + + public function getAlmanacPropertyDeleteTransactionType() { + return AlmanacServiceDeletePropertyTransaction::TRANSACTIONTYPE; + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/almanac/storage/AlmanacServiceTransaction.php b/src/applications/almanac/storage/AlmanacServiceTransaction.php index 692cc2997e..62b4137e7b 100644 --- a/src/applications/almanac/storage/AlmanacServiceTransaction.php +++ b/src/applications/almanac/storage/AlmanacServiceTransaction.php @@ -1,15 +1,7 @@ getAuthorPHID(); - - switch ($this->getTransactionType()) { - case self::TYPE_PROPERTY_UPDATE: - $property_key = $this->getMetadataValue('almanac.property'); - return pht( - '%s updated the property "%s".', - $this->renderHandleLink($author_phid), - $property_key); - case self::TYPE_PROPERTY_REMOVE: - $property_key = $this->getMetadataValue('almanac.property'); - return pht( - '%s deleted the property "%s".', - $this->renderHandleLink($author_phid), - $property_key); - } - - return parent::getTitle(); - } - -} diff --git a/src/applications/almanac/xaction/AlmanacBindingDeletePropertyTransaction.php b/src/applications/almanac/xaction/AlmanacBindingDeletePropertyTransaction.php new file mode 100644 index 0000000000..aece12c9bf --- /dev/null +++ b/src/applications/almanac/xaction/AlmanacBindingDeletePropertyTransaction.php @@ -0,0 +1,20 @@ +getAlmanacPropertyOldValue($object); + } + + public function applyInternalEffects($object, $value) { + return $this->deleteAlmanacProperty($object); + } + + public function getTitle() { + return $this->getAlmanacDeletePropertyTitle(); + } + +} diff --git a/src/applications/almanac/xaction/AlmanacBindingSetPropertyTransaction.php b/src/applications/almanac/xaction/AlmanacBindingSetPropertyTransaction.php new file mode 100644 index 0000000000..cb1a7f9fa1 --- /dev/null +++ b/src/applications/almanac/xaction/AlmanacBindingSetPropertyTransaction.php @@ -0,0 +1,24 @@ +getAlmanacPropertyOldValue($object); + } + + public function applyExternalEffects($object, $value) { + return $this->setAlmanacProperty($object, $value); + } + + public function getTitle() { + return $this->getAlmanacSetPropertyTitle(); + } + + public function validateTransactions($object, array $xactions) { + return $this->validateAlmanacSetPropertyTransactions($object, $xactions); + } + +} diff --git a/src/applications/almanac/xaction/AlmanacDeviceDeletePropertyTransaction.php b/src/applications/almanac/xaction/AlmanacDeviceDeletePropertyTransaction.php new file mode 100644 index 0000000000..bbe5c3bc05 --- /dev/null +++ b/src/applications/almanac/xaction/AlmanacDeviceDeletePropertyTransaction.php @@ -0,0 +1,20 @@ +getAlmanacPropertyOldValue($object); + } + + public function applyInternalEffects($object, $value) { + return $this->deleteAlmanacProperty($object); + } + + public function getTitle() { + return $this->getAlmanacDeletePropertyTitle(); + } + +} diff --git a/src/applications/almanac/xaction/AlmanacDeviceSetPropertyTransaction.php b/src/applications/almanac/xaction/AlmanacDeviceSetPropertyTransaction.php new file mode 100644 index 0000000000..5170ec8ed9 --- /dev/null +++ b/src/applications/almanac/xaction/AlmanacDeviceSetPropertyTransaction.php @@ -0,0 +1,24 @@ +getAlmanacPropertyOldValue($object); + } + + public function applyExternalEffects($object, $value) { + return $this->setAlmanacProperty($object, $value); + } + + public function getTitle() { + return $this->getAlmanacSetPropertyTitle(); + } + + public function validateTransactions($object, array $xactions) { + return $this->validateAlmanacSetPropertyTransactions($object, $xactions); + } + +} diff --git a/src/applications/almanac/xaction/AlmanacServiceDeletePropertyTransaction.php b/src/applications/almanac/xaction/AlmanacServiceDeletePropertyTransaction.php new file mode 100644 index 0000000000..5824670576 --- /dev/null +++ b/src/applications/almanac/xaction/AlmanacServiceDeletePropertyTransaction.php @@ -0,0 +1,20 @@ +getAlmanacPropertyOldValue($object); + } + + public function applyInternalEffects($object, $value) { + return $this->deleteAlmanacProperty($object); + } + + public function getTitle() { + return $this->getAlmanacDeletePropertyTitle(); + } + +} diff --git a/src/applications/almanac/xaction/AlmanacServiceSetPropertyTransaction.php b/src/applications/almanac/xaction/AlmanacServiceSetPropertyTransaction.php new file mode 100644 index 0000000000..bd4863a986 --- /dev/null +++ b/src/applications/almanac/xaction/AlmanacServiceSetPropertyTransaction.php @@ -0,0 +1,24 @@ +getAlmanacPropertyOldValue($object); + } + + public function applyExternalEffects($object, $value) { + return $this->setAlmanacProperty($object, $value); + } + + public function getTitle() { + return $this->getAlmanacSetPropertyTitle(); + } + + public function validateTransactions($object, array $xactions) { + return $this->validateAlmanacSetPropertyTransactions($object, $xactions); + } + +} diff --git a/src/applications/almanac/xaction/AlmanacTransactionType.php b/src/applications/almanac/xaction/AlmanacTransactionType.php index 69ba5b61c4..c7f58ec7bb 100644 --- a/src/applications/almanac/xaction/AlmanacTransactionType.php +++ b/src/applications/almanac/xaction/AlmanacTransactionType.php @@ -1,4 +1,97 @@ getMetadataValue('almanac.property'); + $exists = $object->hasAlmanacProperty($property_key); + $value = $object->getAlmanacPropertyValue($property_key); + + return array( + 'existed' => $exists, + 'value' => $value, + ); + } + + protected function setAlmanacProperty($object, $value) { + $property_key = $this->getMetadataValue('almanac.property'); + + if ($object->hasAlmanacProperty($property_key)) { + $property = $object->getAlmanacProperty($property_key); + } else { + $property = id(new AlmanacProperty()) + ->setObjectPHID($object->getPHID()) + ->setFieldName($property_key); + } + + $property + ->setFieldValue($value) + ->save(); + } + + protected function deleteAlmanacProperty($object) { + $property_key = $this->getMetadataValue('almanac.property'); + if ($object->hasAlmanacProperty($property_key)) { + $property = $object->getAlmanacProperty($property_key); + $property->delete(); + } + } + + protected function getAlmanacSetPropertyTitle() { + $property_key = $this->getMetadataValue('almanac.property'); + + return pht( + '%s updated the property %s.', + $this->renderAuthor(), + $this->renderValue($property_key)); + } + + protected function getAlmanacDeletePropertyTitle() { + $property_key = $this->getMetadataValue('almanac.property'); + + return pht( + '%s removed the property %s.', + $this->renderAuthor(), + $this->renderValue($property_key)); + } + + protected function validateAlmanacSetPropertyTransactions( + $object, + array $xactions) { + $errors = array(); + + foreach ($xactions as $xaction) { + $property_key = $xaction->getMetadataValue('almanac.property'); + + $message = null; + try { + AlmanacNames::validateName($property_key); + } catch (Exception $ex) { + $message = $ex->getMessage(); + } + + if ($message !== null) { + $errors[] = $this->newInvalidError($message, $xaction); + continue; + } + + $new_value = $xaction->getNewValue(); + try { + phutil_json_encode($new_value); + } catch (Exception $ex) { + $message = pht( + 'Almanac property values must be representable in JSON. %s', + $ex->getMessage()); + } + + if ($message !== null) { + $errors[] = $this->newInvalidError($message, $xaction); + continue; + } + } + + return $errors; + } + +} From a8c4da13c0be09838da4d1ab1e149c3fd12e5f35 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 10 Apr 2018 11:08:42 -0700 Subject: [PATCH 32/53] Add "almanac.network.edit" and "almanac.network.search" API methods Summary: Depends on D19334. Ref T13120. Ref T12414. These are pretty straightforward, but no one really has a use case for them anyway today so they're primarily just for completeness. Test Plan: - Queried networks with `almanac.network.search`. - Created and edited networks with `almanac.network.edit`. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13120, T12414 Differential Revision: https://secure.phabricator.com/D19335 --- src/__phutil_library_map__.php | 5 ++++ .../AlmanacNetworkEditConduitAPIMethod.php | 19 ++++++++++++++ .../AlmanacNetworkSearchConduitAPIMethod.php | 18 +++++++++++++ .../almanac/storage/AlmanacNetwork.php | 26 ++++++++++++++++++- 4 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 src/applications/almanac/conduit/AlmanacNetworkEditConduitAPIMethod.php create mode 100644 src/applications/almanac/conduit/AlmanacNetworkSearchConduitAPIMethod.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 118196a5d7..df0110d332 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -104,6 +104,7 @@ phutil_register_library_map(array( 'AlmanacNamespaceViewController' => 'applications/almanac/controller/AlmanacNamespaceViewController.php', 'AlmanacNetwork' => 'applications/almanac/storage/AlmanacNetwork.php', 'AlmanacNetworkController' => 'applications/almanac/controller/AlmanacNetworkController.php', + 'AlmanacNetworkEditConduitAPIMethod' => 'applications/almanac/conduit/AlmanacNetworkEditConduitAPIMethod.php', 'AlmanacNetworkEditController' => 'applications/almanac/controller/AlmanacNetworkEditController.php', 'AlmanacNetworkEditEngine' => 'applications/almanac/editor/AlmanacNetworkEditEngine.php', 'AlmanacNetworkEditor' => 'applications/almanac/editor/AlmanacNetworkEditor.php', @@ -112,6 +113,7 @@ phutil_register_library_map(array( 'AlmanacNetworkNameTransaction' => 'applications/almanac/xaction/AlmanacNetworkNameTransaction.php', 'AlmanacNetworkPHIDType' => 'applications/almanac/phid/AlmanacNetworkPHIDType.php', 'AlmanacNetworkQuery' => 'applications/almanac/query/AlmanacNetworkQuery.php', + 'AlmanacNetworkSearchConduitAPIMethod' => 'applications/almanac/conduit/AlmanacNetworkSearchConduitAPIMethod.php', 'AlmanacNetworkSearchEngine' => 'applications/almanac/query/AlmanacNetworkSearchEngine.php', 'AlmanacNetworkTransaction' => 'applications/almanac/storage/AlmanacNetworkTransaction.php', 'AlmanacNetworkTransactionQuery' => 'applications/almanac/query/AlmanacNetworkTransactionQuery.php', @@ -5321,8 +5323,10 @@ phutil_register_library_map(array( 'PhabricatorPolicyInterface', 'PhabricatorDestructibleInterface', 'PhabricatorNgramsInterface', + 'PhabricatorConduitResultInterface', ), 'AlmanacNetworkController' => 'AlmanacController', + 'AlmanacNetworkEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'AlmanacNetworkEditController' => 'AlmanacNetworkController', 'AlmanacNetworkEditEngine' => 'PhabricatorEditEngine', 'AlmanacNetworkEditor' => 'AlmanacEditor', @@ -5331,6 +5335,7 @@ phutil_register_library_map(array( 'AlmanacNetworkNameTransaction' => 'AlmanacNetworkTransactionType', 'AlmanacNetworkPHIDType' => 'PhabricatorPHIDType', 'AlmanacNetworkQuery' => 'AlmanacQuery', + 'AlmanacNetworkSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'AlmanacNetworkSearchEngine' => 'PhabricatorApplicationSearchEngine', 'AlmanacNetworkTransaction' => 'AlmanacModularTransaction', 'AlmanacNetworkTransactionQuery' => 'PhabricatorApplicationTransactionQuery', diff --git a/src/applications/almanac/conduit/AlmanacNetworkEditConduitAPIMethod.php b/src/applications/almanac/conduit/AlmanacNetworkEditConduitAPIMethod.php new file mode 100644 index 0000000000..b2acdf2572 --- /dev/null +++ b/src/applications/almanac/conduit/AlmanacNetworkEditConduitAPIMethod.php @@ -0,0 +1,19 @@ +setKey('name') + ->setType('string') + ->setDescription(pht('The name of the network.')), + ); + } + + public function getFieldValuesForConduit() { + return array( + 'name' => $this->getName(), + ); + } + + public function getConduitSearchAttachments() { + return array(); + } + } From 9022e140821e45d8182155c0040fd0b0119a6900 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 10 Apr 2018 11:21:12 -0700 Subject: [PATCH 33/53] Use a more conventional spelling of "Almanac" for "almanac.service.edit" class Summary: Depends on D19335. Ref T13120. Ref T12414. There are many good ways to spell "almanac", but stick with convention here. Test Plan: (O_O) Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13120, T12414 Differential Revision: https://secure.phabricator.com/D19336 --- src/__phutil_library_map__.php | 4 ++-- ...itAPIMethod.php => AlmanacServiceEditConduitAPIMethod.php} | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/applications/almanac/conduit/{AlamancServiceEditConduitAPIMethod.php => AlmanacServiceEditConduitAPIMethod.php} (88%) diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index df0110d332..e00296b8cc 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -9,7 +9,6 @@ phutil_register_library_map(array( '__library_version__' => 2, 'class' => array( - 'AlamancServiceEditConduitAPIMethod' => 'applications/almanac/conduit/AlamancServiceEditConduitAPIMethod.php', 'AlmanacAddress' => 'applications/almanac/util/AlmanacAddress.php', 'AlmanacBinding' => 'applications/almanac/storage/AlmanacBinding.php', 'AlmanacBindingDeletePropertyTransaction' => 'applications/almanac/xaction/AlmanacBindingDeletePropertyTransaction.php', @@ -135,6 +134,7 @@ phutil_register_library_map(array( 'AlmanacServiceController' => 'applications/almanac/controller/AlmanacServiceController.php', 'AlmanacServiceDatasource' => 'applications/almanac/typeahead/AlmanacServiceDatasource.php', 'AlmanacServiceDeletePropertyTransaction' => 'applications/almanac/xaction/AlmanacServiceDeletePropertyTransaction.php', + 'AlmanacServiceEditConduitAPIMethod' => 'applications/almanac/conduit/AlmanacServiceEditConduitAPIMethod.php', 'AlmanacServiceEditController' => 'applications/almanac/controller/AlmanacServiceEditController.php', 'AlmanacServiceEditEngine' => 'applications/almanac/editor/AlmanacServiceEditEngine.php', 'AlmanacServiceEditor' => 'applications/almanac/editor/AlmanacServiceEditor.php', @@ -5192,7 +5192,6 @@ phutil_register_library_map(array( 'require_celerity_resource' => 'applications/celerity/api.php', ), 'xmap' => array( - 'AlamancServiceEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'AlmanacAddress' => 'Phobject', 'AlmanacBinding' => array( 'AlmanacDAO', @@ -5369,6 +5368,7 @@ phutil_register_library_map(array( 'AlmanacServiceController' => 'AlmanacController', 'AlmanacServiceDatasource' => 'PhabricatorTypeaheadDatasource', 'AlmanacServiceDeletePropertyTransaction' => 'AlmanacServiceTransactionType', + 'AlmanacServiceEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'AlmanacServiceEditController' => 'AlmanacServiceController', 'AlmanacServiceEditEngine' => 'PhabricatorEditEngine', 'AlmanacServiceEditor' => 'AlmanacEditor', diff --git a/src/applications/almanac/conduit/AlamancServiceEditConduitAPIMethod.php b/src/applications/almanac/conduit/AlmanacServiceEditConduitAPIMethod.php similarity index 88% rename from src/applications/almanac/conduit/AlamancServiceEditConduitAPIMethod.php rename to src/applications/almanac/conduit/AlmanacServiceEditConduitAPIMethod.php index d56b07a21d..3e1c07f7ea 100644 --- a/src/applications/almanac/conduit/AlamancServiceEditConduitAPIMethod.php +++ b/src/applications/almanac/conduit/AlmanacServiceEditConduitAPIMethod.php @@ -1,6 +1,6 @@ Date: Tue, 10 Apr 2018 11:24:34 -0700 Subject: [PATCH 34/53] Add "almanac.namespace.edit" and "almanac.namespace.search" API methods Summary: Depends on D19336. Ref T13120. Ref T12414. These are simple, straightforward, and uninteresting. Test Plan: - Searched for namespaces with "almanac.namespace.search". - Created and edited namespaces with "almanac.namespace.edit". Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13120, T12414 Differential Revision: https://secure.phabricator.com/D19337 --- src/__phutil_library_map__.php | 5 ++++ .../AlmanacNamespaceEditConduitAPIMethod.php | 19 +++++++++++++ ...AlmanacNamespaceSearchConduitAPIMethod.php | 18 +++++++++++++ .../almanac/storage/AlmanacNamespace.php | 27 ++++++++++++++++++- 4 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 src/applications/almanac/conduit/AlmanacNamespaceEditConduitAPIMethod.php create mode 100644 src/applications/almanac/conduit/AlmanacNamespaceSearchConduitAPIMethod.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index e00296b8cc..08c07e7083 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -88,6 +88,7 @@ phutil_register_library_map(array( 'AlmanacNamesTestCase' => 'applications/almanac/util/__tests__/AlmanacNamesTestCase.php', 'AlmanacNamespace' => 'applications/almanac/storage/AlmanacNamespace.php', 'AlmanacNamespaceController' => 'applications/almanac/controller/AlmanacNamespaceController.php', + 'AlmanacNamespaceEditConduitAPIMethod' => 'applications/almanac/conduit/AlmanacNamespaceEditConduitAPIMethod.php', 'AlmanacNamespaceEditController' => 'applications/almanac/controller/AlmanacNamespaceEditController.php', 'AlmanacNamespaceEditEngine' => 'applications/almanac/editor/AlmanacNamespaceEditEngine.php', 'AlmanacNamespaceEditor' => 'applications/almanac/editor/AlmanacNamespaceEditor.php', @@ -96,6 +97,7 @@ phutil_register_library_map(array( 'AlmanacNamespaceNameTransaction' => 'applications/almanac/xaction/AlmanacNamespaceNameTransaction.php', 'AlmanacNamespacePHIDType' => 'applications/almanac/phid/AlmanacNamespacePHIDType.php', 'AlmanacNamespaceQuery' => 'applications/almanac/query/AlmanacNamespaceQuery.php', + 'AlmanacNamespaceSearchConduitAPIMethod' => 'applications/almanac/conduit/AlmanacNamespaceSearchConduitAPIMethod.php', 'AlmanacNamespaceSearchEngine' => 'applications/almanac/query/AlmanacNamespaceSearchEngine.php', 'AlmanacNamespaceTransaction' => 'applications/almanac/storage/AlmanacNamespaceTransaction.php', 'AlmanacNamespaceTransactionQuery' => 'applications/almanac/query/AlmanacNamespaceTransactionQuery.php', @@ -5301,8 +5303,10 @@ phutil_register_library_map(array( 'AlmanacPropertyInterface', 'PhabricatorDestructibleInterface', 'PhabricatorNgramsInterface', + 'PhabricatorConduitResultInterface', ), 'AlmanacNamespaceController' => 'AlmanacController', + 'AlmanacNamespaceEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'AlmanacNamespaceEditController' => 'AlmanacNamespaceController', 'AlmanacNamespaceEditEngine' => 'PhabricatorEditEngine', 'AlmanacNamespaceEditor' => 'AlmanacEditor', @@ -5311,6 +5315,7 @@ phutil_register_library_map(array( 'AlmanacNamespaceNameTransaction' => 'AlmanacNamespaceTransactionType', 'AlmanacNamespacePHIDType' => 'PhabricatorPHIDType', 'AlmanacNamespaceQuery' => 'AlmanacQuery', + 'AlmanacNamespaceSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'AlmanacNamespaceSearchEngine' => 'PhabricatorApplicationSearchEngine', 'AlmanacNamespaceTransaction' => 'AlmanacModularTransaction', 'AlmanacNamespaceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', diff --git a/src/applications/almanac/conduit/AlmanacNamespaceEditConduitAPIMethod.php b/src/applications/almanac/conduit/AlmanacNamespaceEditConduitAPIMethod.php new file mode 100644 index 0000000000..e739cd6bb7 --- /dev/null +++ b/src/applications/almanac/conduit/AlmanacNamespaceEditConduitAPIMethod.php @@ -0,0 +1,19 @@ +setKey('name') + ->setType('string') + ->setDescription(pht('The name of the namespace.')), + ); + } + + public function getFieldValuesForConduit() { + return array( + 'name' => $this->getName(), + ); + } + + public function getConduitSearchAttachments() { + return array(); + } + + } From e502df509d5f8dd3106be50d7d83ac244ff96cb4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 10 Apr 2018 11:49:03 -0700 Subject: [PATCH 35/53] Implement "almanac.interface.search" and "almanac.interface.edit" Summary: Depends on D19337. Ref T13120. Ref T12414. These are slightly more substantive than namespace/network, but pretty much standard fare. Test Plan: - Searched for interfaces with "almanac.interface.search". - Created and edited interfaces with "almanac.interface.edit". - Created and edited interfaces with web UI since some stuff got tweaked. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13120, T12414 Differential Revision: https://secure.phabricator.com/D19338 --- src/__phutil_library_map__.php | 9 +++ .../AlmanacInterfaceEditConduitAPIMethod.php | 19 +++++ ...AlmanacInterfaceSearchConduitAPIMethod.php | 18 +++++ .../editor/AlmanacInterfaceEditEngine.php | 46 +++++++++++- .../query/AlmanacInterfaceSearchEngine.php | 71 +++++++++++++++++++ .../almanac/storage/AlmanacInterface.php | 41 ++++++++++- .../editfield/PhabricatorIntEditField.php | 14 ++++ 7 files changed, 216 insertions(+), 2 deletions(-) create mode 100644 src/applications/almanac/conduit/AlmanacInterfaceEditConduitAPIMethod.php create mode 100644 src/applications/almanac/conduit/AlmanacInterfaceSearchConduitAPIMethod.php create mode 100644 src/applications/almanac/query/AlmanacInterfaceSearchEngine.php create mode 100644 src/applications/transactions/editfield/PhabricatorIntEditField.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 08c07e7083..a45149a4b9 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -67,6 +67,7 @@ phutil_register_library_map(array( 'AlmanacInterfaceDeleteController' => 'applications/almanac/controller/AlmanacInterfaceDeleteController.php', 'AlmanacInterfaceDestroyTransaction' => 'applications/almanac/xaction/AlmanacInterfaceDestroyTransaction.php', 'AlmanacInterfaceDeviceTransaction' => 'applications/almanac/xaction/AlmanacInterfaceDeviceTransaction.php', + 'AlmanacInterfaceEditConduitAPIMethod' => 'applications/almanac/conduit/AlmanacInterfaceEditConduitAPIMethod.php', 'AlmanacInterfaceEditController' => 'applications/almanac/controller/AlmanacInterfaceEditController.php', 'AlmanacInterfaceEditEngine' => 'applications/almanac/editor/AlmanacInterfaceEditEngine.php', 'AlmanacInterfaceEditor' => 'applications/almanac/editor/AlmanacInterfaceEditor.php', @@ -74,6 +75,8 @@ phutil_register_library_map(array( 'AlmanacInterfacePHIDType' => 'applications/almanac/phid/AlmanacInterfacePHIDType.php', 'AlmanacInterfacePortTransaction' => 'applications/almanac/xaction/AlmanacInterfacePortTransaction.php', 'AlmanacInterfaceQuery' => 'applications/almanac/query/AlmanacInterfaceQuery.php', + 'AlmanacInterfaceSearchConduitAPIMethod' => 'applications/almanac/conduit/AlmanacInterfaceSearchConduitAPIMethod.php', + 'AlmanacInterfaceSearchEngine' => 'applications/almanac/query/AlmanacInterfaceSearchEngine.php', 'AlmanacInterfaceTableView' => 'applications/almanac/view/AlmanacInterfaceTableView.php', 'AlmanacInterfaceTransaction' => 'applications/almanac/storage/AlmanacInterfaceTransaction.php', 'AlmanacInterfaceTransactionType' => 'applications/almanac/xaction/AlmanacInterfaceTransactionType.php', @@ -3223,6 +3226,7 @@ phutil_register_library_map(array( 'PhabricatorInlineSummaryView' => 'infrastructure/diff/view/PhabricatorInlineSummaryView.php', 'PhabricatorInstructionsEditField' => 'applications/transactions/editfield/PhabricatorInstructionsEditField.php', 'PhabricatorIntConfigType' => 'applications/config/type/PhabricatorIntConfigType.php', + 'PhabricatorIntEditField' => 'applications/transactions/editfield/PhabricatorIntEditField.php', 'PhabricatorIntExportField' => 'infrastructure/export/field/PhabricatorIntExportField.php', 'PhabricatorInternalSetting' => 'applications/settings/setting/PhabricatorInternalSetting.php', 'PhabricatorInternationalizationManagementExtractWorkflow' => 'infrastructure/internationalization/management/PhabricatorInternationalizationManagementExtractWorkflow.php', @@ -5270,12 +5274,14 @@ phutil_register_library_map(array( 'PhabricatorDestructibleInterface', 'PhabricatorExtendedPolicyInterface', 'PhabricatorApplicationTransactionInterface', + 'PhabricatorConduitResultInterface', ), 'AlmanacInterfaceAddressTransaction' => 'AlmanacInterfaceTransactionType', 'AlmanacInterfaceDatasource' => 'PhabricatorTypeaheadDatasource', 'AlmanacInterfaceDeleteController' => 'AlmanacDeviceController', 'AlmanacInterfaceDestroyTransaction' => 'AlmanacInterfaceTransactionType', 'AlmanacInterfaceDeviceTransaction' => 'AlmanacInterfaceTransactionType', + 'AlmanacInterfaceEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'AlmanacInterfaceEditController' => 'AlmanacDeviceController', 'AlmanacInterfaceEditEngine' => 'PhabricatorEditEngine', 'AlmanacInterfaceEditor' => 'AlmanacEditor', @@ -5283,6 +5289,8 @@ phutil_register_library_map(array( 'AlmanacInterfacePHIDType' => 'PhabricatorPHIDType', 'AlmanacInterfacePortTransaction' => 'AlmanacInterfaceTransactionType', 'AlmanacInterfaceQuery' => 'AlmanacQuery', + 'AlmanacInterfaceSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', + 'AlmanacInterfaceSearchEngine' => 'PhabricatorApplicationSearchEngine', 'AlmanacInterfaceTableView' => 'AphrontView', 'AlmanacInterfaceTransaction' => 'AlmanacModularTransaction', 'AlmanacInterfaceTransactionType' => 'AlmanacTransactionType', @@ -8895,6 +8903,7 @@ phutil_register_library_map(array( 'PhabricatorInlineSummaryView' => 'AphrontView', 'PhabricatorInstructionsEditField' => 'PhabricatorEditField', 'PhabricatorIntConfigType' => 'PhabricatorTextConfigType', + 'PhabricatorIntEditField' => 'PhabricatorEditField', 'PhabricatorIntExportField' => 'PhabricatorExportField', 'PhabricatorInternalSetting' => 'PhabricatorSetting', 'PhabricatorInternationalizationManagementExtractWorkflow' => 'PhabricatorInternationalizationManagementWorkflow', diff --git a/src/applications/almanac/conduit/AlmanacInterfaceEditConduitAPIMethod.php b/src/applications/almanac/conduit/AlmanacInterfaceEditConduitAPIMethod.php new file mode 100644 index 0000000000..8f28b009ff --- /dev/null +++ b/src/applications/almanac/conduit/AlmanacInterfaceEditConduitAPIMethod.php @@ -0,0 +1,19 @@ +setDevice(new AlmanacDevice()); + return $this->newEditableObject(); + } + + protected function newEditableObjectFromConduit(array $raw_xactions) { + $device_phid = null; + foreach ($raw_xactions as $raw_xaction) { + if ($raw_xaction['type'] !== 'device') { + continue; + } + + $device_phid = $raw_xaction['value']; + } + + if ($device_phid === null) { + throw new Exception( + pht( + 'When creating a new Almanac interface via the Conduit API, you '. + 'must provide a "device" transaction to select a device.')); + } + + $device = id(new AlmanacDeviceQuery()) + ->setViewer($this->getViewer()) + ->withPHIDs(array($device_phid)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$device) { + throw new Exception( + pht( + 'Device "%s" is unrecognized, restricted, or you do not have '. + 'permission to edit it.', + $device_phid)); + } + + $this->setDevice($device); + + return $this->newEditableObject(); + } + protected function newObjectQuery() { return new AlmanacInterfaceQuery(); } @@ -126,7 +170,7 @@ final class AlmanacInterfaceEditEngine AlmanacInterfaceAddressTransaction::TRANSACTIONTYPE) ->setIsRequired(true) ->setValue($object->getAddress()), - id(new PhabricatorTextEditField()) + id(new PhabricatorIntEditField()) ->setKey('port') ->setLabel(pht('Port')) ->setDescription(pht('Port of the service.')) diff --git a/src/applications/almanac/query/AlmanacInterfaceSearchEngine.php b/src/applications/almanac/query/AlmanacInterfaceSearchEngine.php new file mode 100644 index 0000000000..9a3c5bc8f0 --- /dev/null +++ b/src/applications/almanac/query/AlmanacInterfaceSearchEngine.php @@ -0,0 +1,71 @@ +setLabel(pht('Devices')) + ->setKey('devicePHIDs') + ->setAliases(array('device', 'devicePHID', 'devices')) + ->setDescription(pht('Search for interfaces on particular devices.')), + ); + } + + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); + + if ($map['devicePHIDs']) { + $query->withDevicePHIDs($map['devicePHIDs']); + } + + return $query; + } + + protected function getURI($path) { + return '/almanac/interface/'.$path; + } + + protected function getBuiltinQueryNames() { + $names = array( + 'all' => pht('All Interfaces'), + ); + + return $names; + } + + public function buildSavedQueryFromBuiltin($query_key) { + $query = $this->newSavedQuery(); + $query->setQueryKey($query_key); + + switch ($query_key) { + case 'all': + return $query; + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + + protected function renderResultList( + array $devices, + PhabricatorSavedQuery $query, + array $handles) { + + // For now, this SearchEngine just supports API access via Conduit. + throw new PhutilMethodNotImplementedException(); + } + +} diff --git a/src/applications/almanac/storage/AlmanacInterface.php b/src/applications/almanac/storage/AlmanacInterface.php index 98d1c3be52..4002651d08 100644 --- a/src/applications/almanac/storage/AlmanacInterface.php +++ b/src/applications/almanac/storage/AlmanacInterface.php @@ -6,7 +6,8 @@ final class AlmanacInterface PhabricatorPolicyInterface, PhabricatorDestructibleInterface, PhabricatorExtendedPolicyInterface, - PhabricatorApplicationTransactionInterface { + PhabricatorApplicationTransactionInterface, + PhabricatorConduitResultInterface { protected $devicePHID; protected $networkPHID; @@ -177,4 +178,42 @@ final class AlmanacInterface return $timeline; } + +/* -( PhabricatorConduitResultInterface )---------------------------------- */ + + + public function getFieldSpecificationsForConduit() { + return array( + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('devicePHID') + ->setType('phid') + ->setDescription(pht('The device the interface is on.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('networkPHID') + ->setType('phid') + ->setDescription(pht('The network the interface is part of.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('address') + ->setType('string') + ->setDescription(pht('The address of the interface.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('port') + ->setType('int') + ->setDescription(pht('The port number of the interface.')), + ); + } + + public function getFieldValuesForConduit() { + return array( + 'devicePHID' => $this->getDevicePHID(), + 'networkPHID' => $this->getNetworkPHID(), + 'address' => (string)$this->getAddress(), + 'port' => (int)$this->getPort(), + ); + } + + public function getConduitSearchAttachments() { + return array(); + } + } diff --git a/src/applications/transactions/editfield/PhabricatorIntEditField.php b/src/applications/transactions/editfield/PhabricatorIntEditField.php new file mode 100644 index 0000000000..690b4c0b17 --- /dev/null +++ b/src/applications/transactions/editfield/PhabricatorIntEditField.php @@ -0,0 +1,14 @@ + Date: Wed, 11 Apr 2018 06:24:10 -0700 Subject: [PATCH 36/53] Provide "almanac.binding.search" and "almanac.binding.edit" Summary: Depends on D19338. Ref T13120. Ref T12414. These are the last of the new API methods. This stuff still doesn't work: - You can't actually enable/disable bindings yet. I want to take a look at the use cases and consider changing "disabled" to "status", or providing a different way to solve the problem. - You can't edit properties via the API. I expect to enable this for all `AlmanacPropertyInterface` objects with an extension in a future change. Test Plan: - Searched for bindings via API. - Viewed binding web UI for API methods. - Created bindings via API. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13120, T12414 Differential Revision: https://secure.phabricator.com/D19340 --- src/__phutil_library_map__.php | 11 ++ .../AlmanacBindingEditConduitAPIMethod.php | 19 +++ .../AlmanacBindingSearchConduitAPIMethod.php | 18 ++ .../editor/AlmanacBindingEditEngine.php | 158 ++++++++++++++++++ .../query/AlmanacBindingSearchEngine.php | 80 +++++++++ .../almanac/storage/AlmanacBinding.php | 36 +++- .../AlmanacBindingServiceTransaction.php | 64 +++++++ 7 files changed, 385 insertions(+), 1 deletion(-) create mode 100644 src/applications/almanac/conduit/AlmanacBindingEditConduitAPIMethod.php create mode 100644 src/applications/almanac/conduit/AlmanacBindingSearchConduitAPIMethod.php create mode 100644 src/applications/almanac/editor/AlmanacBindingEditEngine.php create mode 100644 src/applications/almanac/query/AlmanacBindingSearchEngine.php create mode 100644 src/applications/almanac/xaction/AlmanacBindingServiceTransaction.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index a45149a4b9..2e5a384ac8 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -14,12 +14,17 @@ phutil_register_library_map(array( 'AlmanacBindingDeletePropertyTransaction' => 'applications/almanac/xaction/AlmanacBindingDeletePropertyTransaction.php', 'AlmanacBindingDisableController' => 'applications/almanac/controller/AlmanacBindingDisableController.php', 'AlmanacBindingDisableTransaction' => 'applications/almanac/xaction/AlmanacBindingDisableTransaction.php', + 'AlmanacBindingEditConduitAPIMethod' => 'applications/almanac/conduit/AlmanacBindingEditConduitAPIMethod.php', 'AlmanacBindingEditController' => 'applications/almanac/controller/AlmanacBindingEditController.php', + 'AlmanacBindingEditEngine' => 'applications/almanac/editor/AlmanacBindingEditEngine.php', 'AlmanacBindingEditor' => 'applications/almanac/editor/AlmanacBindingEditor.php', 'AlmanacBindingInterfaceTransaction' => 'applications/almanac/xaction/AlmanacBindingInterfaceTransaction.php', 'AlmanacBindingPHIDType' => 'applications/almanac/phid/AlmanacBindingPHIDType.php', 'AlmanacBindingPropertyEditEngine' => 'applications/almanac/editor/AlmanacBindingPropertyEditEngine.php', 'AlmanacBindingQuery' => 'applications/almanac/query/AlmanacBindingQuery.php', + 'AlmanacBindingSearchConduitAPIMethod' => 'applications/almanac/conduit/AlmanacBindingSearchConduitAPIMethod.php', + 'AlmanacBindingSearchEngine' => 'applications/almanac/query/AlmanacBindingSearchEngine.php', + 'AlmanacBindingServiceTransaction' => 'applications/almanac/xaction/AlmanacBindingServiceTransaction.php', 'AlmanacBindingSetPropertyTransaction' => 'applications/almanac/xaction/AlmanacBindingSetPropertyTransaction.php', 'AlmanacBindingTableView' => 'applications/almanac/view/AlmanacBindingTableView.php', 'AlmanacBindingTransaction' => 'applications/almanac/storage/AlmanacBindingTransaction.php', @@ -5206,16 +5211,22 @@ phutil_register_library_map(array( 'AlmanacPropertyInterface', 'PhabricatorDestructibleInterface', 'PhabricatorExtendedPolicyInterface', + 'PhabricatorConduitResultInterface', ), 'AlmanacBindingDeletePropertyTransaction' => 'AlmanacBindingTransactionType', 'AlmanacBindingDisableController' => 'AlmanacServiceController', 'AlmanacBindingDisableTransaction' => 'AlmanacBindingTransactionType', + 'AlmanacBindingEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'AlmanacBindingEditController' => 'AlmanacServiceController', + 'AlmanacBindingEditEngine' => 'PhabricatorEditEngine', 'AlmanacBindingEditor' => 'AlmanacEditor', 'AlmanacBindingInterfaceTransaction' => 'AlmanacBindingTransactionType', 'AlmanacBindingPHIDType' => 'PhabricatorPHIDType', 'AlmanacBindingPropertyEditEngine' => 'AlmanacPropertyEditEngine', 'AlmanacBindingQuery' => 'AlmanacQuery', + 'AlmanacBindingSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', + 'AlmanacBindingSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'AlmanacBindingServiceTransaction' => 'AlmanacBindingTransactionType', 'AlmanacBindingSetPropertyTransaction' => 'AlmanacBindingTransactionType', 'AlmanacBindingTableView' => 'AphrontView', 'AlmanacBindingTransaction' => 'AlmanacModularTransaction', diff --git a/src/applications/almanac/conduit/AlmanacBindingEditConduitAPIMethod.php b/src/applications/almanac/conduit/AlmanacBindingEditConduitAPIMethod.php new file mode 100644 index 0000000000..e36d9e0ded --- /dev/null +++ b/src/applications/almanac/conduit/AlmanacBindingEditConduitAPIMethod.php @@ -0,0 +1,19 @@ +service = $service; + return $this; + } + + public function getService() { + if (!$this->service) { + throw new PhutilInvalidStateException('setService'); + } + return $this->service; + } + + public function isEngineConfigurable() { + return false; + } + + public function getEngineName() { + return pht('Almanac Bindings'); + } + + public function getSummaryHeader() { + return pht('Edit Almanac Binding Configurations'); + } + + public function getSummaryText() { + return pht('This engine is used to edit Almanac bindings.'); + } + + public function getEngineApplicationClass() { + return 'PhabricatorAlmanacApplication'; + } + + protected function newEditableObject() { + $service = $this->getService(); + return AlmanacBinding::initializeNewBinding($service); + } + + protected function newEditableObjectForDocumentation() { + $service_type = AlmanacCustomServiceType::SERVICETYPE; + $service = AlmanacService::initializeNewService($service_type); + $this->setService($service); + return $this->newEditableObject(); + } + + protected function newEditableObjectFromConduit(array $raw_xactions) { + $service_phid = null; + foreach ($raw_xactions as $raw_xaction) { + if ($raw_xaction['type'] !== 'service') { + continue; + } + + $service_phid = $raw_xaction['value']; + } + + if ($service_phid === null) { + throw new Exception( + pht( + 'When creating a new Almanac binding via the Conduit API, you '. + 'must provide a "service" transaction to select a service to bind.')); + } + + $service = id(new AlmanacServiceQuery()) + ->setViewer($this->getViewer()) + ->withPHIDs(array($service_phid)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$service) { + throw new Exception( + pht( + 'Service "%s" is unrecognized, restricted, or you do not have '. + 'permission to edit it.', + $service_phid)); + } + + $this->setService($service); + + return $this->newEditableObject(); + } + + protected function newObjectQuery() { + return new AlmanacBindingQuery(); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create Binding'); + } + + protected function getObjectCreateButtonText($object) { + return pht('Create Binding'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit Binding'); + } + + protected function getObjectEditShortText($object) { + return pht('Edit Binding'); + } + + protected function getObjectCreateShortText() { + return pht('Create Binding'); + } + + protected function getObjectName() { + return pht('Binding'); + } + + protected function getEditorURI() { + return '/almanac/binding/edit/'; + } + + protected function getObjectCreateCancelURI($object) { + return '/almanac/binding/'; + } + + protected function getObjectViewURI($object) { + return $object->getURI(); + } + + protected function buildCustomEditFields($object) { + return array( + id(new PhabricatorTextEditField()) + ->setKey('service') + ->setLabel(pht('Service')) + ->setIsConduitOnly(true) + ->setTransactionType( + AlmanacBindingServiceTransaction::TRANSACTIONTYPE) + ->setDescription(pht('Service to create a binding for.')) + ->setConduitDescription(pht('Select the service to bind.')) + ->setConduitTypeDescription(pht('Service PHID.')) + ->setValue($object->getServicePHID()), + id(new PhabricatorTextEditField()) + ->setKey('interface') + ->setLabel(pht('Interface')) + ->setIsConduitOnly(true) + ->setTransactionType( + AlmanacBindingInterfaceTransaction::TRANSACTIONTYPE) + ->setDescription(pht('Interface to bind the service to.')) + ->setConduitDescription(pht('Set the interface to bind.')) + ->setConduitTypeDescription(pht('Interface PHID.')) + ->setValue($object->getInterfacePHID()), + ); + } + +} diff --git a/src/applications/almanac/query/AlmanacBindingSearchEngine.php b/src/applications/almanac/query/AlmanacBindingSearchEngine.php new file mode 100644 index 0000000000..fbeb7644b9 --- /dev/null +++ b/src/applications/almanac/query/AlmanacBindingSearchEngine.php @@ -0,0 +1,80 @@ +setLabel(pht('Services')) + ->setKey('servicePHIDs') + ->setAliases(array('service', 'servicePHID', 'services')) + ->setDescription(pht('Search for bindings on particular services.')), + id(new PhabricatorPHIDsSearchField()) + ->setLabel(pht('Devices')) + ->setKey('devicePHIDs') + ->setAliases(array('device', 'devicePHID', 'devices')) + ->setDescription(pht('Search for bindings on particular devices.')), + ); + } + + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); + + if ($map['servicePHIDs']) { + $query->withServicePHIDs($map['servicePHIDs']); + } + + if ($map['devicePHIDs']) { + $query->withDevicePHIDs($map['devicePHIDs']); + } + + return $query; + } + + protected function getURI($path) { + return '/almanac/binding/'.$path; + } + + protected function getBuiltinQueryNames() { + $names = array( + 'all' => pht('All Bindings'), + ); + + return $names; + } + + public function buildSavedQueryFromBuiltin($query_key) { + $query = $this->newSavedQuery(); + $query->setQueryKey($query_key); + + switch ($query_key) { + case 'all': + return $query; + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + + protected function renderResultList( + array $devices, + PhabricatorSavedQuery $query, + array $handles) { + + // For now, this SearchEngine just supports API access via Conduit. + throw new PhutilMethodNotImplementedException(); + } + +} diff --git a/src/applications/almanac/storage/AlmanacBinding.php b/src/applications/almanac/storage/AlmanacBinding.php index 59a7666f8c..1641120900 100644 --- a/src/applications/almanac/storage/AlmanacBinding.php +++ b/src/applications/almanac/storage/AlmanacBinding.php @@ -7,7 +7,8 @@ final class AlmanacBinding PhabricatorApplicationTransactionInterface, AlmanacPropertyInterface, PhabricatorDestructibleInterface, - PhabricatorExtendedPolicyInterface { + PhabricatorExtendedPolicyInterface, + PhabricatorConduitResultInterface { protected $servicePHID; protected $devicePHID; @@ -23,6 +24,7 @@ final class AlmanacBinding public static function initializeNewBinding(AlmanacService $service) { return id(new AlmanacBinding()) ->setServicePHID($service->getPHID()) + ->attachService($service) ->attachAlmanacProperties(array()) ->setIsDisabled(0); } @@ -225,4 +227,36 @@ final class AlmanacBinding } +/* -( PhabricatorConduitResultInterface )---------------------------------- */ + + + public function getFieldSpecificationsForConduit() { + return array( + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('servicePHID') + ->setType('phid') + ->setDescription(pht('The bound service.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('devicePHID') + ->setType('phid') + ->setDescription(pht('The device the service is bound to.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('interfacePHID') + ->setType('phid') + ->setDescription(pht('The interface the service is bound to.')), + ); + } + + public function getFieldValuesForConduit() { + return array( + 'servicePHID' => $this->getServicePHID(), + 'devicePHID' => $this->getDevicePHID(), + 'interfacePHID' => $this->getInterfacePHID(), + ); + } + + public function getConduitSearchAttachments() { + return array(); + } + } diff --git a/src/applications/almanac/xaction/AlmanacBindingServiceTransaction.php b/src/applications/almanac/xaction/AlmanacBindingServiceTransaction.php new file mode 100644 index 0000000000..4b9f802b27 --- /dev/null +++ b/src/applications/almanac/xaction/AlmanacBindingServiceTransaction.php @@ -0,0 +1,64 @@ +getServicePHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setServicePHID($value); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $service_phid = $object->getServicePHID(); + if ($this->isEmptyTextTransaction($service_phid, $xactions)) { + $errors[] = $this->newRequiredError( + pht('Bindings must have a service.')); + } + + foreach ($xactions as $xaction) { + if (!$this->isNewObject()) { + $errors[] = $this->newInvalidError( + pht( + 'The service for a binding can not be changed once it has '. + 'been created.'), + $xaction); + continue; + } + + $service_phid = $xaction->getNewValue(); + $services = id(new AlmanacServiceQuery()) + ->setViewer($this->getActor()) + ->withPHIDs(array($service_phid)) + ->execute(); + if (!$services) { + $errors[] = $this->newInvalidError( + pht('You can not bind a nonexistent or restricted service.'), + $xaction); + continue; + } + + $service = head($services); + $can_edit = PhabricatorPolicyFilter::hasCapability( + $this->getActor(), + $service, + PhabricatorPolicyCapability::CAN_EDIT); + if (!$can_edit) { + $errors[] = $this->newInvalidError( + pht( + 'You can not bind a service which you do not have permission '. + 'to edit.')); + continue; + } + } + + return $errors; + } + +} From d56a37b636739d467bfc65112db7d9de2f97d0ab Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 11 Apr 2018 07:11:28 -0700 Subject: [PATCH 37/53] Allow Almanac Bindings to be enabled/disabled via API and support the "properties" attachment Summary: Depends on D19340. Ref T12414. Ref T13120. See T12414 for some discussion about direction here. Since I think retaining "enabled/disabled" as a simple flag is reasonable, expose it via the API for readers and writers. Also expose binding properties. Test Plan: - Searched for bindings and properties with "alamanc.binding.search". - Enabled and disabled bindings with "almanac.binding.edit". Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13120, T12414 Differential Revision: https://secure.phabricator.com/D19341 --- .../almanac/editor/AlmanacBindingEditEngine.php | 13 +++++++++++++ src/applications/almanac/storage/AlmanacBinding.php | 10 +++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/applications/almanac/editor/AlmanacBindingEditEngine.php b/src/applications/almanac/editor/AlmanacBindingEditEngine.php index dfcd9dedcd..07a9e36e9e 100644 --- a/src/applications/almanac/editor/AlmanacBindingEditEngine.php +++ b/src/applications/almanac/editor/AlmanacBindingEditEngine.php @@ -152,6 +152,19 @@ final class AlmanacBindingEditEngine ->setConduitDescription(pht('Set the interface to bind.')) ->setConduitTypeDescription(pht('Interface PHID.')) ->setValue($object->getInterfacePHID()), + id(new PhabricatorBoolEditField()) + ->setKey('disabled') + ->setLabel(pht('Disabled')) + ->setIsConduitOnly(true) + ->setTransactionType( + AlmanacBindingDisableTransaction::TRANSACTIONTYPE) + ->setDescription(pht('Disable or enable the binding.')) + ->setConduitDescription(pht('Disable or enable the binding.')) + ->setConduitTypeDescription(pht('True to disable the binding.')) + ->setValue($object->getIsDisabled()) + ->setOptions( + pht('Enable Binding'), + pht('Disable Binding')), ); } diff --git a/src/applications/almanac/storage/AlmanacBinding.php b/src/applications/almanac/storage/AlmanacBinding.php index 1641120900..be70a5ecdc 100644 --- a/src/applications/almanac/storage/AlmanacBinding.php +++ b/src/applications/almanac/storage/AlmanacBinding.php @@ -244,6 +244,10 @@ final class AlmanacBinding ->setKey('interfacePHID') ->setType('phid') ->setDescription(pht('The interface the service is bound to.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('disabled') + ->setType('bool') + ->setDescription(pht('Interface status.')), ); } @@ -252,11 +256,15 @@ final class AlmanacBinding 'servicePHID' => $this->getServicePHID(), 'devicePHID' => $this->getDevicePHID(), 'interfacePHID' => $this->getInterfacePHID(), + 'disabled' => (bool)$this->getIsDisabled(), ); } public function getConduitSearchAttachments() { - return array(); + return array( + id(new AlmanacPropertiesSearchEngineAttachment()) + ->setAttachmentKey('properties'), + ); } } From c1558031c29063d0195f78b0838522dac221e8fb Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 11 Apr 2018 08:36:05 -0700 Subject: [PATCH 38/53] Make various small quality-of-life improvements for Almanac properties Summary: Depends on D19341. Ref T12414. Ref T13120. - Fix a bug where default-valued properties didn't get rendered in grey as they're supposed to (as a hint that the value isn't customized). - When resetting a builtin property won't do anything, visually disable the button as a hint. - Allow Services to specify properties on their Bindings. - Specify that repository bindings have a "protocol" property, so it becomes an explicit thing in the UI. Previously, you had to read the documentation to figure this out. - When editing bindings, use the EditField and its configuration if possible. This turns the "Protocol" property into a dropdown in the UI where you select between "http", "https" and "ssh". - Give the "protocol" binding a smart default based on the port number of the corresponding interface. Test Plan: - Viewed properties on Services, Devices and Bindings. - Saw them render sensibly, and grey out + grey button when a builtin value has a default setting. - Saw "Protocol" appear as a default property on repository cluster bindings and get a smart value. - Edited "protocol", got a nice dropdown. {F5518791} Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13120, T12414 Differential Revision: https://secure.phabricator.com/D19342 --- .../almanac/controller/AlmanacController.php | 5 +-- .../editor/AlmanacPropertyEditEngine.php | 9 +++++- .../almanac/query/AlmanacQuery.php | 4 ++- .../AlmanacClusterRepositoryServiceType.php | 32 +++++++++++++++++++ .../servicetype/AlmanacServiceType.php | 4 +++ .../almanac/storage/AlmanacBinding.php | 6 +++- .../almanac/storage/AlmanacService.php | 5 +++ 7 files changed, 60 insertions(+), 5 deletions(-) diff --git a/src/applications/almanac/controller/AlmanacController.php b/src/applications/almanac/controller/AlmanacController.php index d6d7188511..24a8986766 100644 --- a/src/applications/almanac/controller/AlmanacController.php +++ b/src/applications/almanac/controller/AlmanacController.php @@ -38,7 +38,7 @@ abstract class AlmanacController )); $builtins = $object->getAlmanacPropertyFieldSpecifications(); - $defaults = mpull($builtins, null, 'getValueForTransaction'); + $defaults = mpull($builtins, 'getValueForTransaction'); // Sort fields so builtin fields appear first, then fields are ordered // alphabetically. @@ -65,6 +65,7 @@ abstract class AlmanacController $value = $property->getFieldValue(); $is_builtin = isset($builtins[$key]); + $is_persistent = (bool)$property->getID(); $delete_uri = id(new PhutilURI($delete_base)) ->setQueryParams( @@ -83,7 +84,7 @@ abstract class AlmanacController $delete = javelin_tag( 'a', array( - 'class' => ($can_edit + 'class' => (($can_edit && $is_persistent) ? 'button button-grey small' : 'button button-grey small disabled'), 'sigil' => 'workflow', diff --git a/src/applications/almanac/editor/AlmanacPropertyEditEngine.php b/src/applications/almanac/editor/AlmanacPropertyEditEngine.php index e5bfd2822a..38139f79fa 100644 --- a/src/applications/almanac/editor/AlmanacPropertyEditEngine.php +++ b/src/applications/almanac/editor/AlmanacPropertyEditEngine.php @@ -66,8 +66,15 @@ abstract class AlmanacPropertyEditEngine $property_key = $this->getPropertyKey(); $xaction_type = $object->getAlmanacPropertySetTransactionType(); + $specs = $object->getAlmanacPropertyFieldSpecifications(); + if (isset($specs[$property_key])) { + $field_template = clone $specs[$property_key]; + } else { + $field_template = new PhabricatorTextEditField(); + } + return array( - id(new PhabricatorTextEditField()) + $field_template ->setKey('value') ->setMetadataValue('almanac.property', $property_key) ->setLabel($property_key) diff --git a/src/applications/almanac/query/AlmanacQuery.php b/src/applications/almanac/query/AlmanacQuery.php index b046171d32..b5b6146c7f 100644 --- a/src/applications/almanac/query/AlmanacQuery.php +++ b/src/applications/almanac/query/AlmanacQuery.php @@ -34,10 +34,12 @@ abstract class AlmanacQuery $specs = $object->getAlmanacPropertyFieldSpecifications(); foreach ($specs as $key => $spec) { if (empty($object_properties[$key])) { + $default_value = $spec->getValueForTransaction(); + $object_properties[$key] = id(new AlmanacProperty()) ->setObjectPHID($object->getPHID()) ->setFieldName($key) - ->setFieldValue($spec->getValueForTransaction()); + ->setFieldValue($default_value); } } diff --git a/src/applications/almanac/servicetype/AlmanacClusterRepositoryServiceType.php b/src/applications/almanac/servicetype/AlmanacClusterRepositoryServiceType.php index 259092b5b1..fffe94997a 100644 --- a/src/applications/almanac/servicetype/AlmanacClusterRepositoryServiceType.php +++ b/src/applications/almanac/servicetype/AlmanacClusterRepositoryServiceType.php @@ -24,4 +24,36 @@ final class AlmanacClusterRepositoryServiceType ); } + public function getBindingFieldSpecifications(AlmanacBinding $binding) { + $protocols = array( + array( + 'value' => 'http', + 'port' => 80, + ), + array( + 'value' => 'https', + 'port' => 443, + ), + array( + 'value' => 'ssh', + 'port' => 22, + ), + ); + + $default_value = 'http'; + if ($binding->hasInterface()) { + $interface = $binding->getInterface(); + $port = $interface->getPort(); + + $default_ports = ipull($protocols, 'value', 'port'); + $default_value = idx($default_ports, $port, $default_value); + } + + return array( + 'protocol' => id(new PhabricatorSelectEditField()) + ->setOptions(ipull($protocols, 'value', 'value')) + ->setValue($default_value), + ); + } + } diff --git a/src/applications/almanac/servicetype/AlmanacServiceType.php b/src/applications/almanac/servicetype/AlmanacServiceType.php index 2eb56dc8c4..2f7f503fe8 100644 --- a/src/applications/almanac/servicetype/AlmanacServiceType.php +++ b/src/applications/almanac/servicetype/AlmanacServiceType.php @@ -60,6 +60,10 @@ abstract class AlmanacServiceType extends Phobject { return array(); } + public function getBindingFieldSpecifications(AlmanacBinding $binding) { + return array(); + } + /** * List all available service type implementations. * diff --git a/src/applications/almanac/storage/AlmanacBinding.php b/src/applications/almanac/storage/AlmanacBinding.php index be70a5ecdc..c593e40fa7 100644 --- a/src/applications/almanac/storage/AlmanacBinding.php +++ b/src/applications/almanac/storage/AlmanacBinding.php @@ -88,6 +88,10 @@ final class AlmanacBinding return $this; } + public function hasInterface() { + return ($this->interface !== self::ATTACHABLE); + } + public function getInterface() { return $this->assertAttached($this->interface); } @@ -129,7 +133,7 @@ final class AlmanacBinding } public function getAlmanacPropertyFieldSpecifications() { - return array(); + return $this->getService()->getBindingFieldSpecifications($this); } public function newAlmanacPropertyEditEngine() { diff --git a/src/applications/almanac/storage/AlmanacService.php b/src/applications/almanac/storage/AlmanacService.php index 361eabca2a..ee40d83400 100644 --- a/src/applications/almanac/storage/AlmanacService.php +++ b/src/applications/almanac/storage/AlmanacService.php @@ -156,6 +156,11 @@ final class AlmanacService return $this->getServiceImplementation()->getFieldSpecifications(); } + public function getBindingFieldSpecifications(AlmanacBinding $binding) { + $impl = $this->getServiceImplementation(); + return $impl->getBindingFieldSpecifications($binding); + } + public function newAlmanacPropertyEditEngine() { return new AlmanacServicePropertyEditEngine(); } From ea9187ea92ecdee9a28612c83205cb8c7ddde259 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 11 Apr 2018 09:02:11 -0700 Subject: [PATCH 39/53] Allow Almanac properties to be set and deleted via Conduit Summary: Depends on D19342. Ref T12414. Ref T13120. This adds an EditEngine extension for editing Almanac properties. The actual wire format is a little weird. Normally, we'd have a transaction for each property, but since you can pick any property names you want we can't really do that (we'd have to generate infinite transactions). The transaction wire format anticipates that transactions may eventually get some kind of metadata -- each transaction looks like this: ``` { "type": "title", "value": "Example title" } ``` ...and we can add more keys there. For example, I could have made this transaction look like this: ``` { "type": "property.set", "almanac.property.key": "some-key", "value": "some-value" } ``` However, I don't want to just accept any possible key freely, and it might be a decent chunk of work to formalize this better. It also doesn't feel great. I just built special transaction types intead, so you: ``` { "type": "property.set", "value": { "some-key": "some-value", ... } } ``` Internally, we may generate more than one transaction as a result (if the "value" has more than one key). This feels a bit more natural and is probably easier for clients to use anyway. Test Plan: Set and deleted Service, Device and Binding properties via the API. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13120, T12414 Differential Revision: https://secure.phabricator.com/D19343 --- src/__phutil_library_map__.php | 10 +++++ .../editor/AlmanacBindingEditEngine.php | 3 +- .../editor/AlmanacDeviceEditEngine.php | 3 +- .../editor/AlmanacServiceEditEngine.php | 3 +- .../AlmanacDeletePropertyEditField.php | 22 ++++++++++ .../AlmanacDeletePropertyEditType.php | 36 +++++++++++++++ .../AlmanacPropertiesEditEngineExtension.php | 44 +++++++++++++++++++ .../AlmanacSetPropertyEditField.php | 22 ++++++++++ .../AlmanacSetPropertyEditType.php | 28 ++++++++++++ 9 files changed, 168 insertions(+), 3 deletions(-) create mode 100644 src/applications/almanac/engineextension/AlmanacDeletePropertyEditField.php create mode 100644 src/applications/almanac/engineextension/AlmanacDeletePropertyEditType.php create mode 100644 src/applications/almanac/engineextension/AlmanacPropertiesEditEngineExtension.php create mode 100644 src/applications/almanac/engineextension/AlmanacSetPropertyEditField.php create mode 100644 src/applications/almanac/engineextension/AlmanacSetPropertyEditType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 2e5a384ac8..da5674cbbd 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -44,6 +44,8 @@ phutil_register_library_map(array( 'AlmanacCreateServicesCapability' => 'applications/almanac/capability/AlmanacCreateServicesCapability.php', 'AlmanacCustomServiceType' => 'applications/almanac/servicetype/AlmanacCustomServiceType.php', 'AlmanacDAO' => 'applications/almanac/storage/AlmanacDAO.php', + 'AlmanacDeletePropertyEditField' => 'applications/almanac/engineextension/AlmanacDeletePropertyEditField.php', + 'AlmanacDeletePropertyEditType' => 'applications/almanac/engineextension/AlmanacDeletePropertyEditType.php', 'AlmanacDevice' => 'applications/almanac/storage/AlmanacDevice.php', 'AlmanacDeviceController' => 'applications/almanac/controller/AlmanacDeviceController.php', 'AlmanacDeviceDeletePropertyTransaction' => 'applications/almanac/xaction/AlmanacDeviceDeletePropertyTransaction.php', @@ -129,6 +131,7 @@ phutil_register_library_map(array( 'AlmanacNetworkTransactionType' => 'applications/almanac/xaction/AlmanacNetworkTransactionType.php', 'AlmanacNetworkViewController' => 'applications/almanac/controller/AlmanacNetworkViewController.php', 'AlmanacPropertiesDestructionEngineExtension' => 'applications/almanac/engineextension/AlmanacPropertiesDestructionEngineExtension.php', + 'AlmanacPropertiesEditEngineExtension' => 'applications/almanac/engineextension/AlmanacPropertiesEditEngineExtension.php', 'AlmanacPropertiesSearchEngineAttachment' => 'applications/almanac/engineextension/AlmanacPropertiesSearchEngineAttachment.php', 'AlmanacProperty' => 'applications/almanac/storage/AlmanacProperty.php', 'AlmanacPropertyController' => 'applications/almanac/controller/AlmanacPropertyController.php', @@ -165,6 +168,8 @@ phutil_register_library_map(array( 'AlmanacServiceTypeTestCase' => 'applications/almanac/servicetype/__tests__/AlmanacServiceTypeTestCase.php', 'AlmanacServiceTypeTransaction' => 'applications/almanac/xaction/AlmanacServiceTypeTransaction.php', 'AlmanacServiceViewController' => 'applications/almanac/controller/AlmanacServiceViewController.php', + 'AlmanacSetPropertyEditField' => 'applications/almanac/engineextension/AlmanacSetPropertyEditField.php', + 'AlmanacSetPropertyEditType' => 'applications/almanac/engineextension/AlmanacSetPropertyEditType.php', 'AlmanacTransactionType' => 'applications/almanac/xaction/AlmanacTransactionType.php', 'AphlictDropdownDataQuery' => 'applications/aphlict/query/AphlictDropdownDataQuery.php', 'Aphront304Response' => 'aphront/response/Aphront304Response.php', @@ -5246,6 +5251,8 @@ phutil_register_library_map(array( 'AlmanacCreateServicesCapability' => 'PhabricatorPolicyCapability', 'AlmanacCustomServiceType' => 'AlmanacServiceType', 'AlmanacDAO' => 'PhabricatorLiskDAO', + 'AlmanacDeletePropertyEditField' => 'PhabricatorEditField', + 'AlmanacDeletePropertyEditType' => 'PhabricatorEditType', 'AlmanacDevice' => array( 'AlmanacDAO', 'PhabricatorPolicyInterface', @@ -5365,6 +5372,7 @@ phutil_register_library_map(array( 'AlmanacNetworkTransactionType' => 'AlmanacTransactionType', 'AlmanacNetworkViewController' => 'AlmanacNetworkController', 'AlmanacPropertiesDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension', + 'AlmanacPropertiesEditEngineExtension' => 'PhabricatorEditEngineExtension', 'AlmanacPropertiesSearchEngineAttachment' => 'AlmanacSearchEngineAttachment', 'AlmanacProperty' => array( 'AlmanacDAO', @@ -5413,6 +5421,8 @@ phutil_register_library_map(array( 'AlmanacServiceTypeTestCase' => 'PhabricatorTestCase', 'AlmanacServiceTypeTransaction' => 'AlmanacServiceTransactionType', 'AlmanacServiceViewController' => 'AlmanacServiceController', + 'AlmanacSetPropertyEditField' => 'PhabricatorEditField', + 'AlmanacSetPropertyEditType' => 'PhabricatorEditType', 'AlmanacTransactionType' => 'PhabricatorModularTransactionType', 'AphlictDropdownDataQuery' => 'Phobject', 'Aphront304Response' => 'AphrontResponse', diff --git a/src/applications/almanac/editor/AlmanacBindingEditEngine.php b/src/applications/almanac/editor/AlmanacBindingEditEngine.php index 07a9e36e9e..5146578fff 100644 --- a/src/applications/almanac/editor/AlmanacBindingEditEngine.php +++ b/src/applications/almanac/editor/AlmanacBindingEditEngine.php @@ -91,7 +91,8 @@ final class AlmanacBindingEditEngine } protected function newObjectQuery() { - return new AlmanacBindingQuery(); + return id(new AlmanacBindingQuery()) + ->needProperties(true); } protected function getObjectCreateTitleText($object) { diff --git a/src/applications/almanac/editor/AlmanacDeviceEditEngine.php b/src/applications/almanac/editor/AlmanacDeviceEditEngine.php index 84ca59d310..fa0625e6ed 100644 --- a/src/applications/almanac/editor/AlmanacDeviceEditEngine.php +++ b/src/applications/almanac/editor/AlmanacDeviceEditEngine.php @@ -30,7 +30,8 @@ final class AlmanacDeviceEditEngine } protected function newObjectQuery() { - return new AlmanacDeviceQuery(); + return id(new AlmanacDeviceQuery()) + ->needProperties(true); } protected function getObjectCreateTitleText($object) { diff --git a/src/applications/almanac/editor/AlmanacServiceEditEngine.php b/src/applications/almanac/editor/AlmanacServiceEditEngine.php index 93912fad8b..00e54962b3 100644 --- a/src/applications/almanac/editor/AlmanacServiceEditEngine.php +++ b/src/applications/almanac/editor/AlmanacServiceEditEngine.php @@ -79,7 +79,8 @@ final class AlmanacServiceEditEngine } protected function newObjectQuery() { - return new AlmanacServiceQuery(); + return id(new AlmanacServiceQuery()) + ->needProperties(true); } protected function getObjectCreateTitleText($object) { diff --git a/src/applications/almanac/engineextension/AlmanacDeletePropertyEditField.php b/src/applications/almanac/engineextension/AlmanacDeletePropertyEditField.php new file mode 100644 index 0000000000..82aa4b82b5 --- /dev/null +++ b/src/applications/almanac/engineextension/AlmanacDeletePropertyEditField.php @@ -0,0 +1,22 @@ + $property_key) { + if (!is_string($property_key)) { + throw new Exception( + pht( + 'When deleting Almanac properties, each property name must '. + 'be a string. The value at index "%s" is not a string.', + $idx)); + } + + $xactions[] = $this->newTransaction($template) + ->setMetadataValue('almanac.property', $property_key) + ->setNewValue(true); + } + + return $xactions; + } + +} diff --git a/src/applications/almanac/engineextension/AlmanacPropertiesEditEngineExtension.php b/src/applications/almanac/engineextension/AlmanacPropertiesEditEngineExtension.php new file mode 100644 index 0000000000..965c193f40 --- /dev/null +++ b/src/applications/almanac/engineextension/AlmanacPropertiesEditEngineExtension.php @@ -0,0 +1,44 @@ +setKey('property.set') + ->setTransactionType($object->getAlmanacPropertySetTransactionType()) + ->setConduitDescription( + pht('Pass a map of values to set one or more properties.')) + ->setConduitTypeDescription(pht('Map of property names to values.')) + ->setIsConduitOnly(true), + id(new AlmanacDeletePropertyEditField()) + ->setKey('property.delete') + ->setTransactionType($object->getAlmanacPropertyDeleteTransactionType()) + ->setConduitDescription( + pht('Pass a list of property names to delete properties.')) + ->setConduitTypeDescription(pht('List of property names.')) + ->setIsConduitOnly(true), + ); + } + +} diff --git a/src/applications/almanac/engineextension/AlmanacSetPropertyEditField.php b/src/applications/almanac/engineextension/AlmanacSetPropertyEditField.php new file mode 100644 index 0000000000..aec3ea6c9f --- /dev/null +++ b/src/applications/almanac/engineextension/AlmanacSetPropertyEditField.php @@ -0,0 +1,22 @@ + $property_value) { + $xactions[] = $this->newTransaction($template) + ->setMetadataValue('almanac.property', $property_key) + ->setNewValue($property_value); + } + + return $xactions; + } + +} From d6ef32a7b7546639626b4903473eec56687524e0 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 11 Apr 2018 09:40:05 -0700 Subject: [PATCH 40/53] Give the "Filetree" UI element an explicit background color Summary: See PHI568. If you make the file tree UI very wide so that the page generates a horizontal scrollbar and then scroll the page, the page content can paint underneath the menu. The menu already has a z-index to make it render above the content, but doesn't actually have a background. Give it a background. The "transparent" rule was added in D16346 but I don't see any reason why we actually need it there, so I think this probably won't break anything. Test Plan: {F5518822} Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13120 Differential Revision: https://secure.phabricator.com/D19344 --- resources/celerity/map.php | 6 +++--- webroot/rsrc/css/aphront/phabricator-nav-view.css | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index bb1dcfc5cd..9ca473d8b0 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'e68cf1fa', 'conpherence.pkg.js' => '15191c65', - 'core.pkg.css' => '3ced0b1d', + 'core.pkg.css' => '39061f68', 'core.pkg.js' => '1ea38af8', 'differential.pkg.css' => '06dc617c', 'differential.pkg.js' => 'c2ca903a', @@ -29,7 +29,7 @@ return array( 'rsrc/css/aphront/multi-column.css' => '84cc6640', 'rsrc/css/aphront/notification.css' => '457861ec', 'rsrc/css/aphront/panel-view.css' => '8427b78d', - 'rsrc/css/aphront/phabricator-nav-view.css' => 'a9e3e6d5', + 'rsrc/css/aphront/phabricator-nav-view.css' => '694d7723', 'rsrc/css/aphront/table-view.css' => '8c9bbafe', 'rsrc/css/aphront/tokenizer.css' => '15d5ff71', 'rsrc/css/aphront/tooltip.css' => '173b9431', @@ -769,7 +769,7 @@ return array( 'phabricator-keyboard-shortcut' => '1ae869f2', 'phabricator-keyboard-shortcut-manager' => 'c19dd9b9', 'phabricator-main-menu-view' => '1802a242', - 'phabricator-nav-view-css' => 'a9e3e6d5', + 'phabricator-nav-view-css' => '694d7723', 'phabricator-notification' => '4f774dac', 'phabricator-notification-css' => '457861ec', 'phabricator-notification-menu-css' => '10685bd4', diff --git a/webroot/rsrc/css/aphront/phabricator-nav-view.css b/webroot/rsrc/css/aphront/phabricator-nav-view.css index 6dbd0931a6..ce9fffa101 100644 --- a/webroot/rsrc/css/aphront/phabricator-nav-view.css +++ b/webroot/rsrc/css/aphront/phabricator-nav-view.css @@ -83,7 +83,10 @@ .device-desktop .phui-navigation-shell .has-drag-nav .phabricator-nav-local { width: 310px; padding: 0; - background: transparent; + + /* See PHI568. If we don't paint the background explicitly, the content can + render underneath it when scrolled horizontally. */ + background: {$page.background}; } .device-phone .phabricator-side-menu-home .phabricator-nav-content { From 55619e89642be727f2ff1d6e763d1b9469e614d2 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 11 Apr 2018 13:41:25 -0700 Subject: [PATCH 41/53] Restore an explicit white background color to files in Paste Summary: Ref T13105. Previously, the "source code" view in Paste rendered on a brown/orange-ish background. I've been using this element in more contexts (Files, Diffusion) and removed the colored background to make text (particularly syntax-highlighted text) easier to read and reduce visual noise with the new blame colors. In Diffusion the view is in a box with a white background so removing the background left us with white, but in Paste it's just directly on the page so the background was bleeding through. Instead, set it to white explicitly. Test Plan: Viewed source files in Files, Diffusion and Paste; saw text on a white background. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13105 Differential Revision: https://secure.phabricator.com/D19346 --- resources/celerity/map.php | 4 ++-- webroot/rsrc/css/layout/phabricator-source-code-view.css | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 9ca473d8b0..0cc1216ecd 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -119,7 +119,7 @@ return array( 'rsrc/css/font/font-lato.css' => 'c7ccd872', 'rsrc/css/font/phui-font-icon-base.css' => '870a7360', 'rsrc/css/layout/phabricator-filetree-view.css' => 'b912ad97', - 'rsrc/css/layout/phabricator-source-code-view.css' => 'a526a787', + 'rsrc/css/layout/phabricator-source-code-view.css' => '326df52d', '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' => '1863cc6e', @@ -780,7 +780,7 @@ return array( 'phabricator-search-results-css' => '505dd8cf', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-slowvote-css' => 'a94b7230', - 'phabricator-source-code-view-css' => 'a526a787', + 'phabricator-source-code-view-css' => '326df52d', 'phabricator-standard-page-view' => '34ee718b', 'phabricator-textareautils' => '320810c8', 'phabricator-title' => '485aaa6c', diff --git a/webroot/rsrc/css/layout/phabricator-source-code-view.css b/webroot/rsrc/css/layout/phabricator-source-code-view.css index 5eb2b36c8e..36b6c88d34 100644 --- a/webroot/rsrc/css/layout/phabricator-source-code-view.css +++ b/webroot/rsrc/css/layout/phabricator-source-code-view.css @@ -17,6 +17,7 @@ white-space: pre-wrap; padding: 2px 8px 1px; width: 100%; + background: #ffffff; } .phabricator-source-line { From ac570fd4bc4eb454bcaab203ef46eb8c54061cc2 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 11 Apr 2018 13:51:18 -0700 Subject: [PATCH 42/53] When you make the file tree huge, scroll to the right, and then toggle it, stop it from growing Summary: Depends on D19346. Ref PHI568. I love Javascript. Test Plan: - Viewed a revision. - Dragged file tree view really wide. - Scrolled document to the right. - Toggled file tree off and on by pressing "f" twice. - Before patch: file tree grew wider and wider after it was toggled. - After patch: file tree stayed the same size after it was toggled. - Dragged to various widths and reloaded to make sure the "sticky across reloads" behavior still works. - Scrolled right, dragged the tree a bit, then reloaded and didn't see it flip out. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D19347 --- resources/celerity/map.php | 26 +++++++++---------- .../rsrc/js/core/behavior-phabricator-nav.js | 15 ++++++++--- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 0cc1216ecd..a1fcc25086 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,7 +10,7 @@ return array( 'conpherence.pkg.css' => 'e68cf1fa', 'conpherence.pkg.js' => '15191c65', 'core.pkg.css' => '39061f68', - 'core.pkg.js' => '1ea38af8', + 'core.pkg.js' => 'e1f0f7bd', 'differential.pkg.css' => '06dc617c', 'differential.pkg.js' => 'c2ca903a', 'diffusion.pkg.css' => 'a2d17c7d', @@ -475,7 +475,7 @@ return array( 'rsrc/js/core/behavior-more.js' => 'a80d0378', 'rsrc/js/core/behavior-object-selector.js' => '77c1f0b0', 'rsrc/js/core/behavior-oncopy.js' => '2926fff2', - 'rsrc/js/core/behavior-phabricator-nav.js' => '836f966d', + 'rsrc/js/core/behavior-phabricator-nav.js' => '94b7c320', 'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => 'acd29eee', 'rsrc/js/core/behavior-read-only-warning.js' => 'ba158207', 'rsrc/js/core/behavior-redirect.js' => '0213259f', @@ -635,7 +635,7 @@ return array( 'javelin-behavior-phabricator-keyboard-pager' => 'a8da01f0', 'javelin-behavior-phabricator-keyboard-shortcuts' => '01fca1f0', 'javelin-behavior-phabricator-line-linker' => '1e017314', - 'javelin-behavior-phabricator-nav' => '836f966d', + 'javelin-behavior-phabricator-nav' => '94b7c320', 'javelin-behavior-phabricator-notification-example' => '8ce821c5', 'javelin-behavior-phabricator-object-selector' => '77c1f0b0', 'javelin-behavior-phabricator-oncopy' => '2926fff2', @@ -1547,16 +1547,6 @@ return array( 'javelin-behavior', 'javelin-scrollbar', ), - '836f966d' => array( - 'javelin-behavior', - 'javelin-behavior-device', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-magical-init', - 'javelin-vector', - 'javelin-request', - 'javelin-util', - ), '8499b6ab' => array( 'javelin-behavior', 'javelin-dom', @@ -1658,6 +1648,16 @@ return array( 'javelin-resource', 'javelin-routable', ), + '94b7c320' => array( + 'javelin-behavior', + 'javelin-behavior-device', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-magical-init', + 'javelin-vector', + 'javelin-request', + 'javelin-util', + ), '960f6a39' => array( 'javelin-behavior', 'javelin-dom', diff --git a/webroot/rsrc/js/core/behavior-phabricator-nav.js b/webroot/rsrc/js/core/behavior-phabricator-nav.js index 50d8e1c820..cf6dc880bc 100644 --- a/webroot/rsrc/js/core/behavior-phabricator-nav.js +++ b/webroot/rsrc/js/core/behavior-phabricator-nav.js @@ -49,7 +49,7 @@ JX.behavior('phabricator-nav', function(config) { { element: drag, parameter: 'left', - start: JX.$V(drag).x + start: get_width() }, { element: content, @@ -102,15 +102,24 @@ JX.behavior('phabricator-nav', function(config) { .setData( { key: 'filetree.width', - value: JX.$V(drag).x + value: get_width() }) .send(); }); + function get_width() { + // See PHI568. If the document has scrolled horizontally, the "x" position + // of the bar will be the actual width of the menu plus the horizontal + // scroll position (because the element is "position: fixed"). Subtract the + // document scroll position when saving the element width so that scrolling + // to the right and then toggling the filetree UI does not make it grow + // any wider. + return (JX.$V(drag).x - JX.Vector.getScroll().x); + } var saved_width = config.width; function savedrag() { - saved_width = JX.$V(drag).x; + saved_width = get_width(); local.style.width = ''; drag.style.left = ''; From c5c53e277a28c80e2c9cd8432d8acb750e48a8ed Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 11 Apr 2018 14:12:40 -0700 Subject: [PATCH 43/53] Make line selection in source code views less fragile and more consistent Summary: Depends on D19347. Ref T13105. See PHI565. The "highlight lines" behavior is interacting poorly with the new blame element in Diffusion. Make the behavior a little simpler and hopefully more robust. Test Plan: - Clicked commit/revision links in Diffusion, saw the links get followed instead of the lines highlighted. - Highlighted lines in Diffusion, saw just the line/code highlight instead of the whole thing. - Highlighted lines in Paste and new-style Harbormaster build logs, saw consistent behavior. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13105 Differential Revision: https://secure.phabricator.com/D19348 --- resources/celerity/map.php | 20 +++++------ .../layout/phabricator-source-code-view.css | 6 +++- webroot/rsrc/js/core/behavior-line-linker.js | 34 ++++++------------- 3 files changed, 25 insertions(+), 35 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index a1fcc25086..82d56b8c04 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -119,7 +119,7 @@ return array( 'rsrc/css/font/font-lato.css' => 'c7ccd872', 'rsrc/css/font/phui-font-icon-base.css' => '870a7360', 'rsrc/css/layout/phabricator-filetree-view.css' => 'b912ad97', - 'rsrc/css/layout/phabricator-source-code-view.css' => '326df52d', + 'rsrc/css/layout/phabricator-source-code-view.css' => 'c5edc888', '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' => '1863cc6e', @@ -471,7 +471,7 @@ return array( 'rsrc/js/core/behavior-keyboard-pager.js' => 'a8da01f0', 'rsrc/js/core/behavior-keyboard-shortcuts.js' => '01fca1f0', 'rsrc/js/core/behavior-lightbox-attachments.js' => '6b31879a', - 'rsrc/js/core/behavior-line-linker.js' => '1e017314', + 'rsrc/js/core/behavior-line-linker.js' => '69837bed', 'rsrc/js/core/behavior-more.js' => 'a80d0378', 'rsrc/js/core/behavior-object-selector.js' => '77c1f0b0', 'rsrc/js/core/behavior-oncopy.js' => '2926fff2', @@ -634,7 +634,7 @@ return array( 'javelin-behavior-phabricator-gesture-example' => '558829c2', 'javelin-behavior-phabricator-keyboard-pager' => 'a8da01f0', 'javelin-behavior-phabricator-keyboard-shortcuts' => '01fca1f0', - 'javelin-behavior-phabricator-line-linker' => '1e017314', + 'javelin-behavior-phabricator-line-linker' => '69837bed', 'javelin-behavior-phabricator-nav' => '94b7c320', 'javelin-behavior-phabricator-notification-example' => '8ce821c5', 'javelin-behavior-phabricator-object-selector' => '77c1f0b0', @@ -780,7 +780,7 @@ return array( 'phabricator-search-results-css' => '505dd8cf', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-slowvote-css' => 'a94b7230', - 'phabricator-source-code-view-css' => '326df52d', + 'phabricator-source-code-view-css' => 'c5edc888', 'phabricator-standard-page-view' => '34ee718b', 'phabricator-textareautils' => '320810c8', 'phabricator-title' => '485aaa6c', @@ -1003,12 +1003,6 @@ return array( 'javelin-workflow', 'javelin-magical-init', ), - '1e017314' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-history', - ), '1f6794f6' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1413,6 +1407,12 @@ return array( 'javelin-dom', 'phuix-button-view', ), + '69837bed' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-history', + ), '69adf288' => array( 'javelin-install', ), diff --git a/webroot/rsrc/css/layout/phabricator-source-code-view.css b/webroot/rsrc/css/layout/phabricator-source-code-view.css index 36b6c88d34..07448c9c63 100644 --- a/webroot/rsrc/css/layout/phabricator-source-code-view.css +++ b/webroot/rsrc/css/layout/phabricator-source-code-view.css @@ -52,10 +52,14 @@ th.phabricator-source-line a:hover { text-decoration: none; } -.phabricator-source-highlight { +.phabricator-source-highlight .phabricator-source-code { background: {$paste.highlight}; } +.phabricator-source-highlight .phabricator-source-line { + background: {$paste.border}; +} + .phabricator-source-code-summary { padding-bottom: 8px; } diff --git a/webroot/rsrc/js/core/behavior-line-linker.js b/webroot/rsrc/js/core/behavior-line-linker.js index 2506e205b8..e3eedfd50f 100644 --- a/webroot/rsrc/js/core/behavior-line-linker.js +++ b/webroot/rsrc/js/core/behavior-line-linker.js @@ -19,24 +19,7 @@ JX.behavior('phabricator-line-linker', function() { // Ignore. } - function getRowNumber(tr) { - // Starting from the left, find the rightmost "" tag among all - // "" tags at the start of the row. Our goal here is to skip over - // blame information in Diffusion. This could probably be significantly - // more graceful. - var th = null; - for (var ii = 0; ii < tr.childNodes.length; ii++) { - if (JX.DOM.isType(tr.childNodes[ii], 'th')) { - th = tr.childNodes[ii]; - continue; - } - break; - } - - if (!th) { - return null; - } - + function getRowNumber(th) { // If the "" tag contains an "" with "data-n" that we're using // to prevent copy/paste of line numbers, use that. if (th.firstChild) { @@ -51,7 +34,7 @@ JX.behavior('phabricator-line-linker', function() { JX.Stratcom.listen( ['click', 'mousedown'], - ['phabricator-source', 'tag:tr', 'tag:th', 'tag:a'], + ['phabricator-source', 'tag:th', 'tag:a'], function(e) { if (!e.isNormalMouseEvent()) { return; @@ -62,13 +45,13 @@ JX.behavior('phabricator-line-linker', function() { // table. The row's immediate ancestor table needs to be the table with // the "phabricator-source" sigil. - var row = e.getNode('tag:tr'); + var cell = e.getNode('tag:th'); var table = e.getNode('phabricator-source'); - if (JX.DOM.findAbove(row, 'table') !== table) { + if (JX.DOM.findAbove(cell, 'table') !== table) { return; } - var number = getRowNumber(row); + var number = getRowNumber(cell); if (!number) { return; } @@ -81,7 +64,7 @@ JX.behavior('phabricator-line-linker', function() { return; } - origin = row; + origin = cell; target = origin; root = table; @@ -95,7 +78,7 @@ JX.behavior('phabricator-line-linker', function() { if (e.getNode('phabricator-source') !== root) { return; } - target = e.getNode('tag:tr'); + target = e.getNode('tag:th'); var min; var max; @@ -130,6 +113,9 @@ JX.behavior('phabricator-line-linker', function() { highlighted = []; // Highlight the newly selected rows. + min = JX.DOM.findAbove(min, 'tr'); + max = JX.DOM.findAbove(max, 'tr'); + var cursor = min; while (true) { JX.DOM.alterClass(cursor, 'phabricator-source-highlight', true); From 5b3a351852a7fabde4c40d342a70ae6142bec57f Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 11 Apr 2018 14:23:25 -0700 Subject: [PATCH 44/53] Use pseudoelements, not Zero Width Space, to implement copy/paste behavior in Paste/Diffusion Summary: Depends on D19348. Ref T13105. When copying text from Paste or Diffusion, we'd like to copy only source, not line numbers. We currently accomplish this with zero-width spaces plus a trigger that fires on "copy" in Paste and Diffusion. This is quite gross. In the new-style Harbormaster logs, we use an approach that seems slightly better: CSS psuedoelements. This isn't a complete solution (see also PHI504 / T5032) but puts us in a slightly better place. Use it in Paste/Files/Diffusion too. This gives us good behavior in all browsers in Files and Paste. This gives us good behavior in Chrome and Firefox in Diffusion. Safari will copy (but not visually select) blame information in Diffusion. I think we can live with that for now. Test Plan: Selected and copy/pasted stuff in Diffusion, Files, and Paste. Got good behavior everywhere except Safari + Diffusion. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13105 Differential Revision: https://secure.phabricator.com/D19349 --- resources/celerity/map.php | 20 +++++++++---------- src/view/layout/PhabricatorSourceCodeView.php | 20 ++++++++----------- .../layout/phabricator-source-code-view.css | 13 ++++-------- webroot/rsrc/js/core/behavior-line-linker.js | 2 +- 4 files changed, 23 insertions(+), 32 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 82d56b8c04..9853c35217 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -119,7 +119,7 @@ return array( 'rsrc/css/font/font-lato.css' => 'c7ccd872', 'rsrc/css/font/phui-font-icon-base.css' => '870a7360', 'rsrc/css/layout/phabricator-filetree-view.css' => 'b912ad97', - 'rsrc/css/layout/phabricator-source-code-view.css' => 'c5edc888', + 'rsrc/css/layout/phabricator-source-code-view.css' => '09368218', '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' => '1863cc6e', @@ -471,7 +471,7 @@ return array( 'rsrc/js/core/behavior-keyboard-pager.js' => 'a8da01f0', 'rsrc/js/core/behavior-keyboard-shortcuts.js' => '01fca1f0', 'rsrc/js/core/behavior-lightbox-attachments.js' => '6b31879a', - 'rsrc/js/core/behavior-line-linker.js' => '69837bed', + 'rsrc/js/core/behavior-line-linker.js' => '36165eb1', 'rsrc/js/core/behavior-more.js' => 'a80d0378', 'rsrc/js/core/behavior-object-selector.js' => '77c1f0b0', 'rsrc/js/core/behavior-oncopy.js' => '2926fff2', @@ -634,7 +634,7 @@ return array( 'javelin-behavior-phabricator-gesture-example' => '558829c2', 'javelin-behavior-phabricator-keyboard-pager' => 'a8da01f0', 'javelin-behavior-phabricator-keyboard-shortcuts' => '01fca1f0', - 'javelin-behavior-phabricator-line-linker' => '69837bed', + 'javelin-behavior-phabricator-line-linker' => '36165eb1', 'javelin-behavior-phabricator-nav' => '94b7c320', 'javelin-behavior-phabricator-notification-example' => '8ce821c5', 'javelin-behavior-phabricator-object-selector' => '77c1f0b0', @@ -780,7 +780,7 @@ return array( 'phabricator-search-results-css' => '505dd8cf', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-slowvote-css' => 'a94b7230', - 'phabricator-source-code-view-css' => 'c5edc888', + 'phabricator-source-code-view-css' => '09368218', 'phabricator-standard-page-view' => '34ee718b', 'phabricator-textareautils' => '320810c8', 'phabricator-title' => '485aaa6c', @@ -1098,6 +1098,12 @@ return array( 'javelin-dom', 'javelin-vector', ), + '36165eb1' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-history', + ), '3ab51e2c' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -1407,12 +1413,6 @@ return array( 'javelin-dom', 'phuix-button-view', ), - '69837bed' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-history', - ), '69adf288' => array( 'javelin-install', ), diff --git a/src/view/layout/PhabricatorSourceCodeView.php b/src/view/layout/PhabricatorSourceCodeView.php index e6fb1803bc..eec571e5f3 100644 --- a/src/view/layout/PhabricatorSourceCodeView.php +++ b/src/view/layout/PhabricatorSourceCodeView.php @@ -66,7 +66,6 @@ final class PhabricatorSourceCodeView extends AphrontView { require_celerity_resource('phabricator-source-code-view-css'); require_celerity_resource('syntax-highlighting-css'); - Javelin::initBehavior('phabricator-oncopy', array()); if ($this->canClickHighlight) { Javelin::initBehavior('phabricator-line-linker'); } @@ -78,11 +77,11 @@ final class PhabricatorSourceCodeView extends AphrontView { $lines = $this->lines; if ($this->truncatedFirstLines) { $lines[] = phutil_tag( - 'span', - array( - 'class' => 'c', - ), - pht('...')); + 'span', + array( + 'class' => 'c', + ), + pht('...')); } else if ($this->truncatedFirstBytes) { $last_key = last_key($lines); $lines[$last_key] = hsprintf( @@ -98,9 +97,6 @@ final class PhabricatorSourceCodeView extends AphrontView { $base_uri = (string)$this->uri; foreach ($lines as $line) { - // NOTE: See phabricator-oncopy behavior. - $content_line = hsprintf("\xE2\x80\x8B%s", $line); - $row_attributes = array(); if (isset($this->highlights[$line_number])) { $row_attributes['class'] = 'phabricator-source-highlight'; @@ -117,8 +113,8 @@ final class PhabricatorSourceCodeView extends AphrontView { 'a', array( 'href' => $line_href, - ), - $line_number); + 'data-n' => $line_number, + )); } else { $tag_number = phutil_tag( 'span', @@ -172,7 +168,7 @@ final class PhabricatorSourceCodeView extends AphrontView { array( 'class' => 'phabricator-source-code', ), - $content_line), + $line), )); $line_number++; diff --git a/webroot/rsrc/css/layout/phabricator-source-code-view.css b/webroot/rsrc/css/layout/phabricator-source-code-view.css index 07448c9c63..6f2864d067 100644 --- a/webroot/rsrc/css/layout/phabricator-source-code-view.css +++ b/webroot/rsrc/css/layout/phabricator-source-code-view.css @@ -25,16 +25,11 @@ text-align: right; border-right: 1px solid {$paste.border}; color: {$sh-yellowtext}; +} - /* When the user selects rows of source, don't visibly select the line - numbers beside them. We use JS to strip the line numbers out when the user - copies the text. */ - -moz-user-select: -moz-none; - -khtml-user-select: none; - -webkit-user-select: none; - -ms-user-select: none; - user-select: none; - white-space: nowrap; +.phabricator-source-line > a::before { + /* Render the line numbers as a pseudo-element so they don't get copied. */ + content: attr(data-n); } th.phabricator-source-line a, diff --git a/webroot/rsrc/js/core/behavior-line-linker.js b/webroot/rsrc/js/core/behavior-line-linker.js index e3eedfd50f..c2aec18d8b 100644 --- a/webroot/rsrc/js/core/behavior-line-linker.js +++ b/webroot/rsrc/js/core/behavior-line-linker.js @@ -29,7 +29,7 @@ JX.behavior('phabricator-line-linker', function() { } } - return +(th.textContent || th.innerText); + return null; } JX.Stratcom.listen( From 37a03402bc44bfcfa95d67f766be390dfc701daf Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 11 Apr 2018 14:44:06 -0700 Subject: [PATCH 45/53] When following a link to a particular line ("/example.txt$12"), scroll to that line Summary: Depends on D19349. Ref T13105. This was the behavior in Diffusion before with a little hard-coded snippet. Remove that snippet ("diffusion-jump-to") and add a more general-purpose snippet to SourceView. This is a tiny bit hacky still (and probably doesn't work quite right with Quicksand) but gets things working again and works in all of Files, Paste, and Diffusion. Test Plan: Followed links to particular lines in Paste, Files and Diffusion; got scrolled to the right place. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13105 Differential Revision: https://secure.phabricator.com/D19350 --- resources/celerity/map.php | 23 +++++++------------ src/view/layout/PhabricatorSourceCodeView.php | 5 ++++ .../application/diffusion/behavior-jump-to.js | 15 ------------ webroot/rsrc/js/core/behavior-line-linker.js | 12 ++++++++++ 4 files changed, 25 insertions(+), 30 deletions(-) delete mode 100644 webroot/rsrc/js/application/diffusion/behavior-jump-to.js diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 9853c35217..6c1240fea0 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -385,7 +385,6 @@ return array( 'rsrc/js/application/diffusion/behavior-commit-branches.js' => 'bdaf4d04', 'rsrc/js/application/diffusion/behavior-commit-graph.js' => '75b83cbb', 'rsrc/js/application/diffusion/behavior-diffusion-browse-file.js' => '054a0f0b', - 'rsrc/js/application/diffusion/behavior-jump-to.js' => '73d09eef', 'rsrc/js/application/diffusion/behavior-locate-file.js' => '6d3e1947', 'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'f01586dc', 'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => '1db13e70', @@ -471,7 +470,7 @@ return array( 'rsrc/js/core/behavior-keyboard-pager.js' => 'a8da01f0', 'rsrc/js/core/behavior-keyboard-shortcuts.js' => '01fca1f0', 'rsrc/js/core/behavior-lightbox-attachments.js' => '6b31879a', - 'rsrc/js/core/behavior-line-linker.js' => '36165eb1', + 'rsrc/js/core/behavior-line-linker.js' => '66a62306', 'rsrc/js/core/behavior-more.js' => 'a80d0378', 'rsrc/js/core/behavior-object-selector.js' => '77c1f0b0', 'rsrc/js/core/behavior-oncopy.js' => '2926fff2', @@ -601,7 +600,6 @@ return array( 'javelin-behavior-diffusion-browse-file' => '054a0f0b', 'javelin-behavior-diffusion-commit-branches' => 'bdaf4d04', 'javelin-behavior-diffusion-commit-graph' => '75b83cbb', - 'javelin-behavior-diffusion-jump-to' => '73d09eef', 'javelin-behavior-diffusion-locate-file' => '6d3e1947', 'javelin-behavior-diffusion-pull-lastmodified' => 'f01586dc', 'javelin-behavior-document-engine' => '0333c0b6', @@ -634,7 +632,7 @@ return array( 'javelin-behavior-phabricator-gesture-example' => '558829c2', 'javelin-behavior-phabricator-keyboard-pager' => 'a8da01f0', 'javelin-behavior-phabricator-keyboard-shortcuts' => '01fca1f0', - 'javelin-behavior-phabricator-line-linker' => '36165eb1', + 'javelin-behavior-phabricator-line-linker' => '66a62306', 'javelin-behavior-phabricator-nav' => '94b7c320', 'javelin-behavior-phabricator-notification-example' => '8ce821c5', 'javelin-behavior-phabricator-object-selector' => '77c1f0b0', @@ -1098,12 +1096,6 @@ return array( 'javelin-dom', 'javelin-vector', ), - '36165eb1' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-history', - ), '3ab51e2c' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -1395,6 +1387,12 @@ return array( 'phabricator-darklog', 'phabricator-darkmessage', ), + '66a62306' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-history', + ), '66a6def1' => array( 'javelin-behavior', 'javelin-dom', @@ -1471,11 +1469,6 @@ return array( 'javelin-behavior', 'javelin-dom', ), - '73d09eef' => array( - 'javelin-behavior', - 'javelin-vector', - 'javelin-dom', - ), '758b4758' => array( 'javelin-install', 'javelin-workboard-card', diff --git a/src/view/layout/PhabricatorSourceCodeView.php b/src/view/layout/PhabricatorSourceCodeView.php index eec571e5f3..19126d1c6a 100644 --- a/src/view/layout/PhabricatorSourceCodeView.php +++ b/src/view/layout/PhabricatorSourceCodeView.php @@ -96,10 +96,15 @@ final class PhabricatorSourceCodeView extends AphrontView { } $base_uri = (string)$this->uri; + $wrote_anchor = false; foreach ($lines as $line) { $row_attributes = array(); if (isset($this->highlights[$line_number])) { $row_attributes['class'] = 'phabricator-source-highlight'; + if (!$wrote_anchor) { + $row_attributes['id'] = 'phabricator-line-linker-anchor'; + $wrote_anchor = true; + } } if ($this->canClickHighlight) { diff --git a/webroot/rsrc/js/application/diffusion/behavior-jump-to.js b/webroot/rsrc/js/application/diffusion/behavior-jump-to.js deleted file mode 100644 index 96b4947e02..0000000000 --- a/webroot/rsrc/js/application/diffusion/behavior-jump-to.js +++ /dev/null @@ -1,15 +0,0 @@ -/** - * @provides javelin-behavior-diffusion-jump-to - * @requires javelin-behavior - * javelin-vector - * javelin-dom - */ - -JX.behavior('diffusion-jump-to', function(config) { - - setTimeout(function() { - var pos = JX.Vector.getPosWithScroll(JX.$(config.target)); - JX.DOM.scrollToPosition(0, pos.y - 100); - }, 0); - -}); diff --git a/webroot/rsrc/js/core/behavior-line-linker.js b/webroot/rsrc/js/core/behavior-line-linker.js index c2aec18d8b..8cb3d0c615 100644 --- a/webroot/rsrc/js/core/behavior-line-linker.js +++ b/webroot/rsrc/js/core/behavior-line-linker.js @@ -176,4 +176,16 @@ JX.behavior('phabricator-line-linker', function() { } }); + + // Try to jump to the highlighted lines if we don't have an explicit anchor + // in the URI. + if (!window.location.hash.length) { + try { + var anchor = JX.$('phabricator-line-linker-anchor'); + JX.DOM.scrollToPosition(0, JX.$V(anchor).y - 60); + } catch (ex) { + // If we didn't hit an element on the page, just move on. + } + } + }); From 70056a9072d328d952e19a9735a00fd292ccbdb7 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 12 Apr 2018 11:02:07 -0700 Subject: [PATCH 46/53] When creating a file by downloading a URI, truncate the length of the default name Summary: See . When we download a file from a URI, we provide a default name based on the URI. However, if the URI is something like `http://example.com/very-very-very-....-long.jpg` with more than 255 characters, we may suggest a name which won't fit into the `name` column of `PhabricatorFile`. Instead, suggest a default name no longer than 64 bytes. Test Plan: - Used the `{image ...}` example from the Discourse report locally; got an image with a truncated name. - Used a normal `{image ...}`, got an image file with a normal name. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D19353 --- src/applications/files/storage/PhabricatorFile.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index a8d16b7651..b795678cd2 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -648,10 +648,17 @@ final class PhabricatorFile extends PhabricatorFileDAO // just bail out. throw $status; } else { - // This is HTTP 2XX, so use the response body to save the - // file data. + // This is HTTP 2XX, so use the response body to save the file data. + // Provide a default name based on the URI, truncating it if the URI + // is exceptionally long. + + $default_name = basename($uri); + $default_name = id(new PhutilUTF8StringTruncator()) + ->setMaximumBytes(64) + ->truncateString($default_name); + $params = $params + array( - 'name' => basename($uri), + 'name' => $default_name, ); return self::newFromFileData($body, $params); From c52e10d1ecf06b7077a190eab8dd05d5f9107d4a Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 12 Apr 2018 14:58:57 -0700 Subject: [PATCH 47/53] Respect external unmentionable PHIDs in Differential revision editor Summary: See PHI574. Ref T13120. When you `Ref Txx` or `Fixes Txxx`, we mark it "unmentionable" to prevent the task from generating both a reference and a mention. If you add a reference to an object (like a commit hash) to a custom remarkup field, there's currently no real way to prevent it from generating a mention, except that you can explicitly mark the PHID as unmentionable on the Editor. This isn't exactly a first-class feature, but we technically do it in `PhabricatorRepositoryCommitMessageParserWorker`, and it probably doesn't hurt or interfere with anything to support it slightly better. In Differential, respect any existing value and append new values to it rather than overwriting the value. Test Plan: Edited a revision summary to include `Ref Txxx`, saw only a reference (not a mention) generate. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13120 Differential Revision: https://secure.phabricator.com/D19361 --- .../editor/DifferentialTransactionEditor.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index 24f0fe7a07..50094b02f4 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -850,11 +850,13 @@ final class DifferentialTransactionEditor $revert_phids = array(); } - $this->setUnmentionablePHIDMap( - array_merge( - $task_phids, - $rev_phids, - $revert_phids)); + // See PHI574. Respect any unmentionable PHIDs which were set on the + // Editor by the caller. + $unmentionable_map = $this->getUnmentionablePHIDMap(); + $unmentionable_map += $task_phids; + $unmentionable_map += $rev_phids; + $unmentionable_map += $revert_phids; + $this->setUnmentionablePHIDMap($unmentionable_map); $result = array(); foreach ($edges as $type => $specs) { From 4068aaef61a07c6c990151a631c392fd388d00e4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 12 Apr 2018 14:46:21 -0700 Subject: [PATCH 48/53] Toggle revision "shouldBroadcast" correctly when "--draft" is used with prototypes off Summary: See PHI573. Ref T13120. Drafts were recently changed so that "draft" and "broadcast" are separate flags, and you can have non-broadcasting revisions in states other than "draft" if builds fail on a draft or you abandon a draft. However, when draft mode is entered with `arc diff --draft` and you have prototypes off, this flag wasn't being set correctly. Test Plan: Disabled prototypes, created a revision with `arc diff --draft`, observed that `draft.broadcast` is now correctly `false`. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13120 Differential Revision: https://secure.phabricator.com/D19360 --- .../xaction/DifferentialRevisionHoldDraftTransaction.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/applications/differential/xaction/DifferentialRevisionHoldDraftTransaction.php b/src/applications/differential/xaction/DifferentialRevisionHoldDraftTransaction.php index 2562f18209..4d31f39f02 100644 --- a/src/applications/differential/xaction/DifferentialRevisionHoldDraftTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionHoldDraftTransaction.php @@ -22,7 +22,9 @@ final class DifferentialRevisionHoldDraftTransaction // TODO: This can probably be removed once Draft is the universal default. if ($this->isNewObject()) { if ($object->isNeedsReview()) { - $object->setModernRevisionStatus(DifferentialRevisionStatus::DRAFT); + $object + ->setModernRevisionStatus(DifferentialRevisionStatus::DRAFT) + ->setShouldBroadcast(false); } } } From 6f810d7813081ae83d9eeb26ab7464486017c379 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 12 Apr 2018 06:10:21 -0700 Subject: [PATCH 49/53] Turn the "closed" property on cluster repositories into a nice boolean Summary: Ref T10883. Ref T13120. There's an existing "closed" property on repository services that stops new repositories from being allocated there. Turn it into a nice boolean. Test Plan: Toggled the value on/off using a nice `