From ffc5c95c2f3dca03a62db25130e89408b707538c Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 10 Feb 2018 06:09:22 -0800 Subject: [PATCH 01/58] Correct flipped transaction constants in "Closed Date" migration Summary: These transaction constants are flipped, which can produce the wrong result in some cases. Test Plan: `./bin/storage upgrade -f --apply phabricator:20180208.maniphest.02.populate.php` Differential Revision: https://secure.phabricator.com/D19054 --- resources/sql/autopatches/20180208.maniphest.02.populate.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/sql/autopatches/20180208.maniphest.02.populate.php b/resources/sql/autopatches/20180208.maniphest.02.populate.php index 16aa2bf57b..e11267496c 100644 --- a/resources/sql/autopatches/20180208.maniphest.02.populate.php +++ b/resources/sql/autopatches/20180208.maniphest.02.populate.php @@ -20,8 +20,8 @@ foreach (new LiskMigrationIterator($table) as $task) { // where the task was closed. A merge also counts as a close, even though // it doesn't currently produce a separate transaction. - $type_merge = ManiphestTaskStatusTransaction::TRANSACTIONTYPE; - $type_status = ManiphestTaskMergedIntoTransaction::TRANSACTIONTYPE; + $type_status = ManiphestTaskStatusTransaction::TRANSACTIONTYPE; + $type_merge = ManiphestTaskMergedIntoTransaction::TRANSACTIONTYPE; $xactions = id(new ManiphestTransactionQuery()) ->setViewer($viewer) From d47c5de9d0ae54b527c72726098f0400e42962d8 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 10 Feb 2018 16:02:34 -0800 Subject: [PATCH 02/58] Fix PHPMailer/SMTP configuration typo for legacy SMTP configurations Summary: See . Test Plan: Browsed a dictionary. (This doesn't get hit when configured via `cluster.mailers`, which is how I previously re-tested SMTP configuration.) Differential Revision: https://secure.phabricator.com/D19058 --- .../adapter/PhabricatorMailImplementationPHPMailerAdapter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/metamta/adapter/PhabricatorMailImplementationPHPMailerAdapter.php b/src/applications/metamta/adapter/PhabricatorMailImplementationPHPMailerAdapter.php index 3ca6366730..ae134a29b8 100644 --- a/src/applications/metamta/adapter/PhabricatorMailImplementationPHPMailerAdapter.php +++ b/src/applications/metamta/adapter/PhabricatorMailImplementationPHPMailerAdapter.php @@ -38,7 +38,7 @@ final class PhabricatorMailImplementationPHPMailerAdapter 'host' => PhabricatorEnv::getEnvConfig('phpmailer.smtp-host'), 'port' => PhabricatorEnv::getEnvConfig('phpmailer.smtp-port'), 'user' => PhabricatorEnv::getEnvConfig('phpmailer.smtp-user'), - 'password' => PhabricatorEnv::getEnvConfig('phpmailer.smtp-passsword'), + 'password' => PhabricatorEnv::getEnvConfig('phpmailer.smtp-password'), 'protocol' => PhabricatorEnv::getEnvConfig('phpmailer.smtp-protocol'), 'encoding' => PhabricatorEnv::getEnvConfig('phpmailer.smtp-encoding'), 'mailer' => PhabricatorEnv::getEnvConfig('phpmailer.mailer'), From a2d02aed2211c03d69b166f1d00d1bd2c06ed327 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 10 Feb 2018 11:34:18 -0800 Subject: [PATCH 03/58] When a build is aborted, fail the buildable Summary: Ref T13054. Fixes T10746. Fixes T11154. This is really a one-line fix (include `ABORTED` in `BuildEngine->updateBuildable()`) but try to structure the code a little more clearly too and reduce (at least slightly) the number of random lists of status attributes spread throughout the codebase. Also add a header tag for buildable status. Test Plan: Aborted a build, saw buildable fail properly. Subscribers: yelirekim, PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T13054, T11154, T10746 Differential Revision: https://secure.phabricator.com/D19055 --- .../constants/HarbormasterBuildStatus.php | 115 ++++++++++++------ .../HarbormasterBuildableViewController.php | 4 + .../engine/HarbormasterBuildEngine.php | 8 +- .../storage/HarbormasterBuildable.php | 12 ++ .../storage/build/HarbormasterBuild.php | 19 +-- 5 files changed, 105 insertions(+), 53 deletions(-) diff --git a/src/applications/harbormaster/constants/HarbormasterBuildStatus.php b/src/applications/harbormaster/constants/HarbormasterBuildStatus.php index b0e3105d78..dc63b5fd95 100644 --- a/src/applications/harbormaster/constants/HarbormasterBuildStatus.php +++ b/src/applications/harbormaster/constants/HarbormasterBuildStatus.php @@ -2,51 +2,56 @@ final class HarbormasterBuildStatus extends Phobject { - /** - * Not currently being built. - */ const STATUS_INACTIVE = 'inactive'; - - /** - * Pending pick up by the Harbormaster daemon. - */ const STATUS_PENDING = 'pending'; - - /** - * Current building the buildable. - */ const STATUS_BUILDING = 'building'; - - /** - * The build has passed. - */ const STATUS_PASSED = 'passed'; - - /** - * The build has failed. - */ const STATUS_FAILED = 'failed'; - - /** - * The build has aborted. - */ const STATUS_ABORTED = 'aborted'; - - /** - * The build encountered an unexpected error. - */ const STATUS_ERROR = 'error'; - - /** - * The build has been paused. - */ const STATUS_PAUSED = 'paused'; - - /** - * The build has been deadlocked. - */ const STATUS_DEADLOCKED = 'deadlocked'; + private $key; + private $properties; + + public function __construct($key, array $properties) { + $this->key = $key; + $this->properties = $properties; + } + + public static function newBuildStatusObject($status) { + $spec = self::getBuildStatusSpec($status); + return new self($status, $spec); + } + + private function getProperty($key) { + if (!array_key_exists($key, $this->properties)) { + throw new Exception( + pht( + 'Attempting to access unknown build status property ("%s").', + $key)); + } + + return $this->properties[$key]; + } + + public function isBuilding() { + return $this->getProperty('isBuilding'); + } + + public function isPaused() { + return ($this->key === self::STATUS_PAUSED); + } + + public function isComplete() { + return $this->getProperty('isComplete'); + } + + public function isPassed() { + return ($this->key === self::STATUS_PASSED); + } + /** * Get a human readable name for a build status constant. @@ -56,7 +61,7 @@ final class HarbormasterBuildStatus extends Phobject { */ public static function getBuildStatusName($status) { $spec = self::getBuildStatusSpec($status); - return idx($spec, 'name', pht('Unknown ("%s")', $status)); + return $spec['name']; } public static function getBuildStatusMap() { @@ -66,17 +71,17 @@ final class HarbormasterBuildStatus extends Phobject { public static function getBuildStatusIcon($status) { $spec = self::getBuildStatusSpec($status); - return idx($spec, 'icon', 'fa-question-circle'); + return $spec['icon']; } public static function getBuildStatusColor($status) { $spec = self::getBuildStatusSpec($status); - return idx($spec, 'color', 'bluegrey'); + return $spec['color']; } public static function getBuildStatusANSIColor($status) { $spec = self::getBuildStatusSpec($status); - return idx($spec, 'color.ansi', 'magenta'); + return $spec['color.ansi']; } public static function getWaitingStatusConstants() { @@ -110,60 +115,90 @@ final class HarbormasterBuildStatus extends Phobject { 'icon' => 'fa-circle-o', 'color' => 'dark', 'color.ansi' => 'yellow', + 'isBuilding' => false, + 'isComplete' => false, ), self::STATUS_PENDING => array( 'name' => pht('Pending'), 'icon' => 'fa-circle-o', 'color' => 'blue', 'color.ansi' => 'yellow', + 'isBuilding' => true, + 'isComplete' => false, ), self::STATUS_BUILDING => array( 'name' => pht('Building'), 'icon' => 'fa-chevron-circle-right', 'color' => 'blue', 'color.ansi' => 'yellow', + 'isBuilding' => true, + 'isComplete' => false, ), self::STATUS_PASSED => array( 'name' => pht('Passed'), 'icon' => 'fa-check-circle', 'color' => 'green', 'color.ansi' => 'green', + 'isBuilding' => false, + 'isComplete' => true, ), self::STATUS_FAILED => array( 'name' => pht('Failed'), 'icon' => 'fa-times-circle', 'color' => 'red', 'color.ansi' => 'red', + 'isBuilding' => false, + 'isComplete' => true, ), self::STATUS_ABORTED => array( 'name' => pht('Aborted'), 'icon' => 'fa-minus-circle', 'color' => 'red', 'color.ansi' => 'red', + 'isBuilding' => false, + 'isComplete' => true, ), self::STATUS_ERROR => array( 'name' => pht('Unexpected Error'), 'icon' => 'fa-minus-circle', 'color' => 'red', 'color.ansi' => 'red', + 'isBuilding' => false, + 'isComplete' => true, ), self::STATUS_PAUSED => array( 'name' => pht('Paused'), 'icon' => 'fa-minus-circle', 'color' => 'dark', 'color.ansi' => 'yellow', + 'isBuilding' => false, + 'isComplete' => false, ), self::STATUS_DEADLOCKED => array( 'name' => pht('Deadlocked'), 'icon' => 'fa-exclamation-circle', 'color' => 'red', 'color.ansi' => 'red', + 'isBuilding' => false, + 'isComplete' => true, ), ); } private static function getBuildStatusSpec($status) { - return idx(self::getBuildStatusSpecMap(), $status, array()); + $map = self::getBuildStatusSpecMap(); + if (isset($map[$status])) { + return $map[$status]; + } + + return array( + 'name' => pht('Unknown ("%s")', $status), + 'icon' => 'fa-question-circle', + 'color' => 'bluegrey', + 'color.ansi' => 'magenta', + 'isBuilding' => false, + 'isComplete' => false, + ); } } diff --git a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php index 6a47dae429..58e5cf8c49 100644 --- a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php @@ -36,6 +36,10 @@ final class HarbormasterBuildableViewController ->setHeader($title) ->setUser($viewer) ->setPolicyObject($buildable) + ->setStatus( + $buildable->getStatusIcon(), + $buildable->getStatusColor(), + $buildable->getStatusDisplayName()) ->setHeaderIcon('fa-recycle'); $timeline = $this->buildTransactionTimeline( diff --git a/src/applications/harbormaster/engine/HarbormasterBuildEngine.php b/src/applications/harbormaster/engine/HarbormasterBuildEngine.php index 0616e77b4e..9c4112e810 100644 --- a/src/applications/harbormaster/engine/HarbormasterBuildEngine.php +++ b/src/applications/harbormaster/engine/HarbormasterBuildEngine.php @@ -443,15 +443,11 @@ final class HarbormasterBuildEngine extends Phobject { $all_pass = true; $any_fail = false; foreach ($buildable->getBuilds() as $build) { - if ($build->getBuildStatus() != HarbormasterBuildStatus::STATUS_PASSED) { + if (!$build->isPassed()) { $all_pass = false; } - if (in_array($build->getBuildStatus(), array( - HarbormasterBuildStatus::STATUS_FAILED, - HarbormasterBuildStatus::STATUS_ERROR, - HarbormasterBuildStatus::STATUS_DEADLOCKED, - ))) { + if ($build->isComplete() && !$build->isPassed()) { $any_fail = true; } } diff --git a/src/applications/harbormaster/storage/HarbormasterBuildable.php b/src/applications/harbormaster/storage/HarbormasterBuildable.php index 9761fb287f..89c13b8b41 100644 --- a/src/applications/harbormaster/storage/HarbormasterBuildable.php +++ b/src/applications/harbormaster/storage/HarbormasterBuildable.php @@ -58,6 +58,18 @@ final class HarbormasterBuildable extends HarbormasterDAO } } + public function getStatusIcon() { + return self::getBuildableStatusIcon($this->getBuildableStatus()); + } + + public function getStatusDisplayName() { + return self::getBuildableStatusName($this->getBuildableStatus()); + } + + public function getStatusColor() { + return self::getBuildableStatusColor($this->getBuildableStatus()); + } + public static function initializeNewBuildable(PhabricatorUser $actor) { return id(new HarbormasterBuildable()) ->setIsManualBuildable(0) diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuild.php b/src/applications/harbormaster/storage/build/HarbormasterBuild.php index 92d4293913..d43b19d71f 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuild.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuild.php @@ -108,9 +108,7 @@ final class HarbormasterBuild extends HarbormasterDAO } public function isBuilding() { - return - $this->getBuildStatus() === HarbormasterBuildStatus::STATUS_PENDING || - $this->getBuildStatus() === HarbormasterBuildStatus::STATUS_BUILDING; + return $this->getBuildStatusObject()->isBuilding(); } public function isAutobuild() { @@ -173,13 +171,15 @@ final class HarbormasterBuild extends HarbormasterDAO } public function isComplete() { - return in_array( - $this->getBuildStatus(), - HarbormasterBuildStatus::getCompletedStatusConstants()); + return $this->getBuildStatusObject()->isComplete(); } public function isPaused() { - return ($this->getBuildStatus() == HarbormasterBuildStatus::STATUS_PAUSED); + return $this->getBuildStatusObject()->isPaused(); + } + + public function isPassed() { + return $this->getBuildStatusObject()->isPassed(); } public function getURI() { @@ -187,6 +187,11 @@ final class HarbormasterBuild extends HarbormasterDAO return "/harbormaster/build/{$id}/"; } + protected function getBuildStatusObject() { + $status_key = $this->getBuildStatus(); + return HarbormasterBuildStatus::newBuildStatusObject($status_key); + } + /* -( Build Commands )----------------------------------------------------- */ From b0d1d46a73f9240d9b9b131c604aea511df68669 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 10 Feb 2018 11:52:33 -0800 Subject: [PATCH 04/58] Drop the legacy hunk table Summary: Ref T13054. Ref T8475. This table has had no readers or writers for more than a year after it was migrated to the modern table. Test Plan: Ran migration, verified that all the data was still around. Maniphest Tasks: T13054, T8475 Differential Revision: https://secure.phabricator.com/D19056 --- .../20180210.hunk.01.droplegacy.sql | 1 + src/__phutil_library_map__.php | 2 - .../storage/DifferentialLegacyHunk.php | 37 ------------------- 3 files changed, 1 insertion(+), 39 deletions(-) create mode 100644 resources/sql/autopatches/20180210.hunk.01.droplegacy.sql delete mode 100644 src/applications/differential/storage/DifferentialLegacyHunk.php diff --git a/resources/sql/autopatches/20180210.hunk.01.droplegacy.sql b/resources/sql/autopatches/20180210.hunk.01.droplegacy.sql new file mode 100644 index 0000000000..129d3927d8 --- /dev/null +++ b/resources/sql/autopatches/20180210.hunk.01.droplegacy.sql @@ -0,0 +1 @@ +DROP TABLE {$NAMESPACE}_differential.differential_hunk; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 13dd7374d2..eebfd512d0 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -481,7 +481,6 @@ phutil_register_library_map(array( 'DifferentialInlineCommentQuery' => 'applications/differential/query/DifferentialInlineCommentQuery.php', 'DifferentialJIRAIssuesCommitMessageField' => 'applications/differential/field/DifferentialJIRAIssuesCommitMessageField.php', 'DifferentialJIRAIssuesField' => 'applications/differential/customfield/DifferentialJIRAIssuesField.php', - 'DifferentialLegacyHunk' => 'applications/differential/storage/DifferentialLegacyHunk.php', 'DifferentialLegacyQuery' => 'applications/differential/constants/DifferentialLegacyQuery.php', 'DifferentialLineAdjustmentMap' => 'applications/differential/parser/DifferentialLineAdjustmentMap.php', 'DifferentialLintField' => 'applications/differential/customfield/DifferentialLintField.php', @@ -5644,7 +5643,6 @@ phutil_register_library_map(array( 'DifferentialInlineCommentQuery' => 'PhabricatorOffsetPagedQuery', 'DifferentialJIRAIssuesCommitMessageField' => 'DifferentialCommitMessageCustomField', 'DifferentialJIRAIssuesField' => 'DifferentialStoredCustomField', - 'DifferentialLegacyHunk' => 'DifferentialHunk', 'DifferentialLegacyQuery' => 'Phobject', 'DifferentialLineAdjustmentMap' => 'Phobject', 'DifferentialLintField' => 'DifferentialHarbormasterField', diff --git a/src/applications/differential/storage/DifferentialLegacyHunk.php b/src/applications/differential/storage/DifferentialLegacyHunk.php deleted file mode 100644 index ae4673e46c..0000000000 --- a/src/applications/differential/storage/DifferentialLegacyHunk.php +++ /dev/null @@ -1,37 +0,0 @@ - array( - 'changes' => 'text?', - 'oldOffset' => 'uint32', - 'oldLen' => 'uint32', - 'newOffset' => 'uint32', - 'newLen' => 'uint32', - ), - self::CONFIG_KEY_SCHEMA => array( - 'changesetID' => array( - 'columns' => array('changesetID'), - ), - ), - ) + parent::getConfiguration(); - } - - public function getTableName() { - return 'differential_hunk'; - } - - public function getDataEncoding() { - return 'utf8'; - } - - public function forceEncoding($encoding) { - // Not supported, these are always utf8. - return $this; - } - -} From f43d08c2bbe6ed6d7f9b35a09f1d9e0564c13f9b Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 10 Feb 2018 12:04:42 -0800 Subject: [PATCH 05/58] Completely remove the legacy hunk table Summary: Depends on D19056. Fixes T8475. Ref T13054. Merges "ModernHunk" back into "Hunk". Test Plan: Grepped for `modernhunk`. Reviewed revisions. Created a new revision. Used `bin/differential migrate-hunk` to migrate hunks between storage formats and back. Maniphest Tasks: T13054, T8475 Differential Revision: https://secure.phabricator.com/D19057 --- .../autopatches/20161213.diff.01.hunks.php | 4 +- .../20180210.hunk.02.renamemodern.sql | 2 + src/__phutil_library_map__.php | 2 - .../DifferentialDiffExtractionEngine.php | 4 +- ...ricatorDifferentialMigrateHunkWorkflow.php | 10 +- .../DifferentialChangesetParserTestCase.php | 4 +- .../DifferentialHunkParserTestCase.php | 2 +- .../query/DifferentialHunkQuery.php | 33 +-- .../storage/DifferentialChangeset.php | 8 +- .../differential/storage/DifferentialDiff.php | 2 +- .../differential/storage/DifferentialHunk.php | 239 ++++++++++++++++- .../storage/DifferentialModernHunk.php | 249 ------------------ .../__tests__/DifferentialHunkTestCase.php | 4 +- 13 files changed, 267 insertions(+), 296 deletions(-) create mode 100644 resources/sql/autopatches/20180210.hunk.02.renamemodern.sql delete mode 100644 src/applications/differential/storage/DifferentialModernHunk.php diff --git a/resources/sql/autopatches/20161213.diff.01.hunks.php b/resources/sql/autopatches/20161213.diff.01.hunks.php index 574adb3b4f..a3863275f1 100644 --- a/resources/sql/autopatches/20161213.diff.01.hunks.php +++ b/resources/sql/autopatches/20161213.diff.01.hunks.php @@ -25,9 +25,9 @@ foreach (new LiskRawMigrationIterator($conn, $src_table) as $row) { $row['oldLen'], $row['newOffset'], $row['newLen'], - DifferentialModernHunk::DATATYPE_TEXT, + DifferentialHunk::DATATYPE_TEXT, 'utf8', - DifferentialModernHunk::DATAFORMAT_RAW, + DifferentialHunk::DATAFORMAT_RAW, // In rare cases, this could be NULL. See T12090. (string)$row['changes'], $row['dateCreated'], diff --git a/resources/sql/autopatches/20180210.hunk.02.renamemodern.sql b/resources/sql/autopatches/20180210.hunk.02.renamemodern.sql new file mode 100644 index 0000000000..d341fbedf2 --- /dev/null +++ b/resources/sql/autopatches/20180210.hunk.02.renamemodern.sql @@ -0,0 +1,2 @@ +RENAME TABLE {$NAMESPACE}_differential.differential_hunk_modern + TO {$NAMESPACE}_differential.differential_hunk; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index eebfd512d0..7b4f48d1f0 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -489,7 +489,6 @@ phutil_register_library_map(array( 'DifferentialMailEngineExtension' => 'applications/differential/engineextension/DifferentialMailEngineExtension.php', 'DifferentialMailView' => 'applications/differential/mail/DifferentialMailView.php', 'DifferentialManiphestTasksField' => 'applications/differential/customfield/DifferentialManiphestTasksField.php', - 'DifferentialModernHunk' => 'applications/differential/storage/DifferentialModernHunk.php', 'DifferentialParseCacheGarbageCollector' => 'applications/differential/garbagecollector/DifferentialParseCacheGarbageCollector.php', 'DifferentialParseCommitMessageConduitAPIMethod' => 'applications/differential/conduit/DifferentialParseCommitMessageConduitAPIMethod.php', 'DifferentialParseRenderTestCase' => 'applications/differential/__tests__/DifferentialParseRenderTestCase.php', @@ -5651,7 +5650,6 @@ phutil_register_library_map(array( 'DifferentialMailEngineExtension' => 'PhabricatorMailEngineExtension', 'DifferentialMailView' => 'Phobject', 'DifferentialManiphestTasksField' => 'DifferentialCoreCustomField', - 'DifferentialModernHunk' => 'DifferentialHunk', 'DifferentialParseCacheGarbageCollector' => 'PhabricatorGarbageCollector', 'DifferentialParseCommitMessageConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialParseRenderTestCase' => 'PhabricatorTestCase', diff --git a/src/applications/differential/engine/DifferentialDiffExtractionEngine.php b/src/applications/differential/engine/DifferentialDiffExtractionEngine.php index c426c411e7..6fb5e70de5 100644 --- a/src/applications/differential/engine/DifferentialDiffExtractionEngine.php +++ b/src/applications/differential/engine/DifferentialDiffExtractionEngine.php @@ -217,8 +217,8 @@ final class DifferentialDiffExtractionEngine extends Phobject { // -echo "test"; // -(empty line) - $hunk = id(new DifferentialModernHunk())->setChanges($context); - $vs_hunk = id(new DifferentialModernHunk())->setChanges($vs_context); + $hunk = id(new DifferentialHunk())->setChanges($context); + $vs_hunk = id(new DifferentialHunk())->setChanges($vs_context); if ($hunk->makeOldFile() != $vs_hunk->makeOldFile() || $hunk->makeNewFile() != $vs_hunk->makeNewFile()) { return true; diff --git a/src/applications/differential/management/PhabricatorDifferentialMigrateHunkWorkflow.php b/src/applications/differential/management/PhabricatorDifferentialMigrateHunkWorkflow.php index 9c0e0071e4..99125645e7 100644 --- a/src/applications/differential/management/PhabricatorDifferentialMigrateHunkWorkflow.php +++ b/src/applications/differential/management/PhabricatorDifferentialMigrateHunkWorkflow.php @@ -32,8 +32,8 @@ final class PhabricatorDifferentialMigrateHunkWorkflow $storage = $args->getArg('to'); switch ($storage) { - case DifferentialModernHunk::DATATYPE_TEXT: - case DifferentialModernHunk::DATATYPE_FILE: + case DifferentialHunk::DATATYPE_TEXT: + case DifferentialHunk::DATATYPE_FILE: break; default: throw new PhutilArgumentUsageException( @@ -44,13 +44,13 @@ final class PhabricatorDifferentialMigrateHunkWorkflow $old_data = $hunk->getChanges(); switch ($storage) { - case DifferentialModernHunk::DATATYPE_TEXT: + case DifferentialHunk::DATATYPE_TEXT: $hunk->saveAsText(); $this->logOkay( pht('TEXT'), pht('Convereted hunk to text storage.')); break; - case DifferentialModernHunk::DATATYPE_FILE: + case DifferentialHunk::DATATYPE_FILE: $hunk->saveAsFile(); $this->logOkay( pht('FILE'), @@ -71,7 +71,7 @@ final class PhabricatorDifferentialMigrateHunkWorkflow } private function loadHunk($id) { - $hunk = id(new DifferentialModernHunk())->load($id); + $hunk = id(new DifferentialHunk())->load($id); if (!$hunk) { throw new PhutilArgumentUsageException( pht( diff --git a/src/applications/differential/parser/__tests__/DifferentialChangesetParserTestCase.php b/src/applications/differential/parser/__tests__/DifferentialChangesetParserTestCase.php index 9ca88db8ce..d8d10169b5 100644 --- a/src/applications/differential/parser/__tests__/DifferentialChangesetParserTestCase.php +++ b/src/applications/differential/parser/__tests__/DifferentialChangesetParserTestCase.php @@ -3,7 +3,7 @@ final class DifferentialChangesetParserTestCase extends PhabricatorTestCase { public function testDiffChangesets() { - $hunk = new DifferentialModernHunk(); + $hunk = new DifferentialHunk(); $hunk->setChanges("+a\n b\n-c"); $hunk->setNewOffset(1); $hunk->setNewLen(2); @@ -20,7 +20,7 @@ final class DifferentialChangesetParserTestCase extends PhabricatorTestCase { ); foreach ($tests as $changes => $expected) { - $hunk = new DifferentialModernHunk(); + $hunk = new DifferentialHunk(); $hunk->setChanges($changes); $hunk->setNewOffset(11); $hunk->setNewLen(3); diff --git a/src/applications/differential/parser/__tests__/DifferentialHunkParserTestCase.php b/src/applications/differential/parser/__tests__/DifferentialHunkParserTestCase.php index d1d2501b94..644e7c0b88 100644 --- a/src/applications/differential/parser/__tests__/DifferentialHunkParserTestCase.php +++ b/src/applications/differential/parser/__tests__/DifferentialHunkParserTestCase.php @@ -14,7 +14,7 @@ final class DifferentialHunkParserTestCase extends PhabricatorTestCase { $new_len, $changes) { - $hunk = id(new DifferentialModernHunk()) + $hunk = id(new DifferentialHunk()) ->setOldOffset($old_offset) ->setOldLen($old_len) ->setNewOffset($new_offset) diff --git a/src/applications/differential/query/DifferentialHunkQuery.php b/src/applications/differential/query/DifferentialHunkQuery.php index c8e35cb607..981c2b4782 100644 --- a/src/applications/differential/query/DifferentialHunkQuery.php +++ b/src/applications/differential/query/DifferentialHunkQuery.php @@ -30,25 +30,12 @@ final class DifferentialHunkQuery } } + public function newResultObject() { + return new DifferentialHunk(); + } + protected function loadPage() { - $all_results = array(); - - // Load modern hunks. - $table = new DifferentialModernHunk(); - $conn_r = $table->establishConnection('r'); - - $modern_data = queryfx_all( - $conn_r, - 'SELECT * FROM %T %Q %Q %Q', - $table->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - $modern_results = $table->loadAllFromArray($modern_data); - - // Strip all the IDs off since they're not unique and nothing should be - // using them. - return array_values($modern_results); + return $this->loadStandardPage($this->newResultObject()); } protected function willFilterPage(array $hunks) { @@ -76,8 +63,8 @@ final class DifferentialHunkQuery return $hunks; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); if (!$this->changesets) { throw new Exception( @@ -87,13 +74,11 @@ final class DifferentialHunkQuery } $where[] = qsprintf( - $conn_r, + $conn, 'changesetID IN (%Ld)', mpull($this->changesets, 'getID')); - $where[] = $this->buildPagingClause($conn_r); - - return $this->formatWhereClause($where); + return $where; } public function getQueryApplicationClass() { diff --git a/src/applications/differential/storage/DifferentialChangeset.php b/src/applications/differential/storage/DifferentialChangeset.php index dbb06fe72b..4832f452e6 100644 --- a/src/applications/differential/storage/DifferentialChangeset.php +++ b/src/applications/differential/storage/DifferentialChangeset.php @@ -118,11 +118,11 @@ final class DifferentialChangeset public function delete() { $this->openTransaction(); - $modern_hunks = id(new DifferentialModernHunk())->loadAllWhere( + $hunks = id(new DifferentialHunk())->loadAllWhere( 'changesetID = %d', $this->getID()); - foreach ($modern_hunks as $modern_hunk) { - $modern_hunk->delete(); + foreach ($hunks as $hunk) { + $hunk->delete(); } $this->unsavedHunks = array(); @@ -292,7 +292,7 @@ final class DifferentialChangeset PhabricatorDestructionEngine $engine) { $this->openTransaction(); - $hunks = id(new DifferentialModernHunk())->loadAllWhere( + $hunks = id(new DifferentialHunk())->loadAllWhere( 'changesetID = %d', $this->getID()); foreach ($hunks as $hunk) { diff --git a/src/applications/differential/storage/DifferentialDiff.php b/src/applications/differential/storage/DifferentialDiff.php index d70cdbbdf5..85ffccc4a3 100644 --- a/src/applications/differential/storage/DifferentialDiff.php +++ b/src/applications/differential/storage/DifferentialDiff.php @@ -189,7 +189,7 @@ final class DifferentialDiff $hunks = $change->getHunks(); if ($hunks) { foreach ($hunks as $hunk) { - $dhunk = new DifferentialModernHunk(); + $dhunk = new DifferentialHunk(); $dhunk->setOldOffset($hunk->getOldOffset()); $dhunk->setOldLen($hunk->getOldLength()); $dhunk->setNewOffset($hunk->getNewOffset()); diff --git a/src/applications/differential/storage/DifferentialHunk.php b/src/applications/differential/storage/DifferentialHunk.php index a57d0035eb..3defb3566b 100644 --- a/src/applications/differential/storage/DifferentialHunk.php +++ b/src/applications/differential/storage/DifferentialHunk.php @@ -1,6 +1,6 @@ array( + 'data' => true, + ), + self::CONFIG_COLUMN_SCHEMA => array( + 'dataType' => 'bytes4', + 'dataEncoding' => 'text16?', + 'dataFormat' => 'bytes4', + 'oldOffset' => 'uint32', + 'oldLen' => 'uint32', + 'newOffset' => 'uint32', + 'newLen' => 'uint32', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_changeset' => array( + 'columns' => array('changesetID'), + ), + 'key_created' => array( + 'columns' => array('dateCreated'), + ), + ), + ) + parent::getConfiguration(); + } + public function getAddedLines() { return $this->makeContent($include = '+'); } @@ -214,6 +253,197 @@ abstract class DifferentialHunk } +/* -( Storage )------------------------------------------------------------ */ + + + public function setChanges($text) { + $this->rawData = $text; + + $this->dataEncoding = $this->detectEncodingForStorage($text); + $this->dataType = self::DATATYPE_TEXT; + + list($format, $data) = $this->formatDataForStorage($text); + + $this->dataFormat = $format; + $this->data = $data; + + return $this; + } + + public function getChanges() { + return $this->getUTF8StringFromStorage( + $this->getRawData(), + nonempty($this->forcedEncoding, $this->getDataEncoding())); + } + + public function forceEncoding($encoding) { + $this->forcedEncoding = $encoding; + return $this; + } + + private function formatDataForStorage($data) { + $deflated = PhabricatorCaches::maybeDeflateData($data); + if ($deflated !== null) { + return array(self::DATAFORMAT_DEFLATED, $deflated); + } + + return array(self::DATAFORMAT_RAW, $data); + } + + public function saveAsText() { + $old_type = $this->getDataType(); + $old_data = $this->getData(); + + if ($old_type == self::DATATYPE_TEXT) { + return $this; + } + + $raw_data = $this->getRawData(); + + $this->setDataType(self::DATATYPE_TEXT); + + list($format, $data) = $this->formatDataForStorage($raw_data); + $this->setDataFormat($format); + $this->setData($data); + + $result = $this->save(); + + $this->destroyData($old_type, $old_data); + + return $result; + } + + public function saveAsFile() { + $old_type = $this->getDataType(); + $old_data = $this->getData(); + + if ($old_type == self::DATATYPE_FILE) { + return $this; + } + + $raw_data = $this->getRawData(); + + list($format, $data) = $this->formatDataForStorage($raw_data); + $this->setDataFormat($format); + + $file = PhabricatorFile::newFromFileData( + $data, + array( + 'name' => 'differential-hunk', + 'mime-type' => 'application/octet-stream', + 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE, + )); + + $this->setDataType(self::DATATYPE_FILE); + $this->setData($file->getPHID()); + + // NOTE: Because hunks don't have a PHID and we just load hunk data with + // the omnipotent viewer, we do not need to attach the file to anything. + + $result = $this->save(); + + $this->destroyData($old_type, $old_data); + + return $result; + } + + private function getRawData() { + if ($this->rawData === null) { + $type = $this->getDataType(); + $data = $this->getData(); + + switch ($type) { + case self::DATATYPE_TEXT: + // In this storage type, the changes are stored on the object. + $data = $data; + break; + case self::DATATYPE_FILE: + $data = $this->loadFileData(); + break; + default: + throw new Exception( + pht('Hunk has unsupported data type "%s"!', $type)); + } + + $format = $this->getDataFormat(); + switch ($format) { + case self::DATAFORMAT_RAW: + // In this format, the changes are stored as-is. + $data = $data; + break; + case self::DATAFORMAT_DEFLATED: + $data = PhabricatorCaches::inflateData($data); + break; + default: + throw new Exception( + pht('Hunk has unsupported data encoding "%s"!', $type)); + } + + $this->rawData = $data; + } + + return $this->rawData; + } + + private function loadFileData() { + if ($this->fileData === null) { + $type = $this->getDataType(); + if ($type !== self::DATATYPE_FILE) { + throw new Exception( + pht( + 'Unable to load file data for hunk with wrong data type ("%s").', + $type)); + } + + $file_phid = $this->getData(); + + $file = $this->loadRawFile($file_phid); + $data = $file->loadFileData(); + + $this->fileData = $data; + } + + return $this->fileData; + } + + private function loadRawFile($file_phid) { + $viewer = PhabricatorUser::getOmnipotentUser(); + + + $files = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($file_phid)) + ->execute(); + if (!$files) { + throw new Exception( + pht( + 'Failed to load file ("%s") with hunk data.', + $file_phid)); + } + + $file = head($files); + + return $file; + } + + private function destroyData( + $type, + $data, + PhabricatorDestructionEngine $engine = null) { + + if (!$engine) { + $engine = new PhabricatorDestructionEngine(); + } + + switch ($type) { + case self::DATATYPE_FILE: + $file = $this->loadRawFile($data); + $engine->destroyObject($file); + break; + } + } + + /* -( PhabricatorPolicyInterface )----------------------------------------- */ @@ -237,8 +467,13 @@ abstract class DifferentialHunk public function destroyObjectPermanently( PhabricatorDestructionEngine $engine) { + + $type = $this->getDataType(); + $data = $this->getData(); + + $this->destroyData($type, $data, $engine); + $this->delete(); } - } diff --git a/src/applications/differential/storage/DifferentialModernHunk.php b/src/applications/differential/storage/DifferentialModernHunk.php deleted file mode 100644 index c3675c8adf..0000000000 --- a/src/applications/differential/storage/DifferentialModernHunk.php +++ /dev/null @@ -1,249 +0,0 @@ - array( - 'data' => true, - ), - self::CONFIG_COLUMN_SCHEMA => array( - 'dataType' => 'bytes4', - 'dataEncoding' => 'text16?', - 'dataFormat' => 'bytes4', - 'oldOffset' => 'uint32', - 'oldLen' => 'uint32', - 'newOffset' => 'uint32', - 'newLen' => 'uint32', - ), - self::CONFIG_KEY_SCHEMA => array( - 'key_changeset' => array( - 'columns' => array('changesetID'), - ), - 'key_created' => array( - 'columns' => array('dateCreated'), - ), - ), - ) + parent::getConfiguration(); - } - - public function setChanges($text) { - $this->rawData = $text; - - $this->dataEncoding = $this->detectEncodingForStorage($text); - $this->dataType = self::DATATYPE_TEXT; - - list($format, $data) = $this->formatDataForStorage($text); - - $this->dataFormat = $format; - $this->data = $data; - - return $this; - } - - public function getChanges() { - return $this->getUTF8StringFromStorage( - $this->getRawData(), - nonempty($this->forcedEncoding, $this->getDataEncoding())); - } - - public function forceEncoding($encoding) { - $this->forcedEncoding = $encoding; - return $this; - } - - private function formatDataForStorage($data) { - $deflated = PhabricatorCaches::maybeDeflateData($data); - if ($deflated !== null) { - return array(self::DATAFORMAT_DEFLATED, $deflated); - } - - return array(self::DATAFORMAT_RAW, $data); - } - - public function saveAsText() { - $old_type = $this->getDataType(); - $old_data = $this->getData(); - - if ($old_type == self::DATATYPE_TEXT) { - return $this; - } - - $raw_data = $this->getRawData(); - - $this->setDataType(self::DATATYPE_TEXT); - - list($format, $data) = $this->formatDataForStorage($raw_data); - $this->setDataFormat($format); - $this->setData($data); - - $result = $this->save(); - - $this->destroyData($old_type, $old_data); - - return $result; - } - - public function saveAsFile() { - $old_type = $this->getDataType(); - $old_data = $this->getData(); - - if ($old_type == self::DATATYPE_FILE) { - return $this; - } - - $raw_data = $this->getRawData(); - - list($format, $data) = $this->formatDataForStorage($raw_data); - $this->setDataFormat($format); - - $file = PhabricatorFile::newFromFileData( - $data, - array( - 'name' => 'differential-hunk', - 'mime-type' => 'application/octet-stream', - 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE, - )); - - $this->setDataType(self::DATATYPE_FILE); - $this->setData($file->getPHID()); - - // NOTE: Because hunks don't have a PHID and we just load hunk data with - // the omnipotent viewer, we do not need to attach the file to anything. - - $result = $this->save(); - - $this->destroyData($old_type, $old_data); - - return $result; - } - - private function getRawData() { - if ($this->rawData === null) { - $type = $this->getDataType(); - $data = $this->getData(); - - switch ($type) { - case self::DATATYPE_TEXT: - // In this storage type, the changes are stored on the object. - $data = $data; - break; - case self::DATATYPE_FILE: - $data = $this->loadFileData(); - break; - default: - throw new Exception( - pht('Hunk has unsupported data type "%s"!', $type)); - } - - $format = $this->getDataFormat(); - switch ($format) { - case self::DATAFORMAT_RAW: - // In this format, the changes are stored as-is. - $data = $data; - break; - case self::DATAFORMAT_DEFLATED: - $data = PhabricatorCaches::inflateData($data); - break; - default: - throw new Exception( - pht('Hunk has unsupported data encoding "%s"!', $type)); - } - - $this->rawData = $data; - } - - return $this->rawData; - } - - private function loadFileData() { - if ($this->fileData === null) { - $type = $this->getDataType(); - if ($type !== self::DATATYPE_FILE) { - throw new Exception( - pht( - 'Unable to load file data for hunk with wrong data type ("%s").', - $type)); - } - - $file_phid = $this->getData(); - - $file = $this->loadRawFile($file_phid); - $data = $file->loadFileData(); - - $this->fileData = $data; - } - - return $this->fileData; - } - - private function loadRawFile($file_phid) { - $viewer = PhabricatorUser::getOmnipotentUser(); - - - $files = id(new PhabricatorFileQuery()) - ->setViewer($viewer) - ->withPHIDs(array($file_phid)) - ->execute(); - if (!$files) { - throw new Exception( - pht( - 'Failed to load file ("%s") with hunk data.', - $file_phid)); - } - - $file = head($files); - - return $file; - } - - - public function destroyObjectPermanently( - PhabricatorDestructionEngine $engine) { - - $type = $this->getDataType(); - $data = $this->getData(); - - $this->destroyData($type, $data, $engine); - - return parent::destroyObjectPermanently($engine); - } - - - private function destroyData( - $type, - $data, - PhabricatorDestructionEngine $engine = null) { - - if (!$engine) { - $engine = new PhabricatorDestructionEngine(); - } - - switch ($type) { - case self::DATATYPE_FILE: - $file = $this->loadRawFile($data); - $engine->destroyObject($file); - break; - } - } - -} diff --git a/src/applications/differential/storage/__tests__/DifferentialHunkTestCase.php b/src/applications/differential/storage/__tests__/DifferentialHunkTestCase.php index 49866ac038..e334891e6b 100644 --- a/src/applications/differential/storage/__tests__/DifferentialHunkTestCase.php +++ b/src/applications/differential/storage/__tests__/DifferentialHunkTestCase.php @@ -5,7 +5,7 @@ final class DifferentialHunkTestCase extends PhutilTestCase { public function testMakeChanges() { $root = dirname(__FILE__).'/hunk/'; - $hunk = new DifferentialModernHunk(); + $hunk = new DifferentialHunk(); $hunk->setChanges(Filesystem::readFile($root.'basic.diff')); $hunk->setOldOffset(1); $hunk->setNewOffset(11); @@ -23,7 +23,7 @@ final class DifferentialHunkTestCase extends PhutilTestCase { ); $this->assertEqual($added, $hunk->getAddedLines()); - $hunk = new DifferentialModernHunk(); + $hunk = new DifferentialHunk(); $hunk->setChanges(Filesystem::readFile($root.'newline.diff')); $hunk->setOldOffset(1); $hunk->setNewOffset(11); From 653bc0fa019f5c14e92f4a04d708d9018a8ae1b1 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 10 Feb 2018 17:42:02 -0800 Subject: [PATCH 06/58] Read lock all transaction edits Summary: Ref T13054. Fixes T12714. Applies read locks to all transactions instead of only a very select subset (chat messages in Conpherence). Test Plan: See for discussion and testing. Maniphest Tasks: T13054, T12714 Differential Revision: https://secure.phabricator.com/D19059 --- .../engine/PhabricatorAuthPasswordEngine.php | 4 + .../conpherence/editor/ConpherenceEditor.php | 18 -- ...habricatorApplicationTransactionEditor.php | 166 +++++++----------- 3 files changed, 64 insertions(+), 124 deletions(-) diff --git a/src/applications/auth/engine/PhabricatorAuthPasswordEngine.php b/src/applications/auth/engine/PhabricatorAuthPasswordEngine.php index c09aa88308..067cea30e7 100644 --- a/src/applications/auth/engine/PhabricatorAuthPasswordEngine.php +++ b/src/applications/auth/engine/PhabricatorAuthPasswordEngine.php @@ -300,6 +300,10 @@ final class PhabricatorAuthPasswordEngine $password->upgradePasswordHasher($envelope, $this->getObject()); $new_hasher = $password->getHasher(); + // NOTE: We must save the change before applying transactions because + // the editor will reload the object to obtain a read lock. + $password->save(); + $xactions = array(); $xactions[] = $password->getApplicationTransactionTemplate() diff --git a/src/applications/conpherence/editor/ConpherenceEditor.php b/src/applications/conpherence/editor/ConpherenceEditor.php index 7896055f64..3d451e78d3 100644 --- a/src/applications/conpherence/editor/ConpherenceEditor.php +++ b/src/applications/conpherence/editor/ConpherenceEditor.php @@ -99,24 +99,6 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { return pht('%s created this room.', $author); } - /** - * We really only need a read lock if we have a comment. In that case, we - * must update the messagesCount field on the conpherence and - * seenMessagesCount(s) for the participant(s). - */ - protected function shouldReadLock( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - $lock = false; - switch ($xaction->getTransactionType()) { - case PhabricatorTransactions::TYPE_COMMENT: - $lock = true; - break; - } - - return $lock; - } protected function applyBuiltinInternalTransaction( PhabricatorLiskDAO $object, diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index d1413b49a6..7bc1f693c7 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -924,23 +924,13 @@ abstract class PhabricatorApplicationTransactionEditor if ($object->getID()) { $this->buildOldRecipientLists($object, $xactions); - foreach ($xactions as $xaction) { + $object->openTransaction(); + $transaction_open = true; - // If any of the transactions require a read lock, hold one and - // reload the object. We need to do this fairly early so that the - // call to `adjustTransactionValues()` (which populates old values) - // is based on the synchronized state of the object, which may differ - // from the state when it was originally loaded. + $object->beginReadLocking(); + $read_locking = true; - if ($this->shouldReadLock($object, $xaction)) { - $object->openTransaction(); - $object->beginReadLocking(); - $transaction_open = true; - $read_locking = true; - $object->reload(); - break; - } - } + $object->reload(); } if ($this->shouldApplyInitialEffects($object, $xactions)) { @@ -951,66 +941,57 @@ abstract class PhabricatorApplicationTransactionEditor } } - if ($this->shouldApplyInitialEffects($object, $xactions)) { - $this->applyInitialEffects($object, $xactions); - } - - foreach ($xactions as $xaction) { - $this->adjustTransactionValues($object, $xaction); - } - try { - $xactions = $this->filterTransactions($object, $xactions); - } catch (Exception $ex) { - if ($read_locking) { - $object->endReadLocking(); + if ($this->shouldApplyInitialEffects($object, $xactions)) { + $this->applyInitialEffects($object, $xactions); } - if ($transaction_open) { - $object->killTransaction(); - } - throw $ex; - } - // TODO: Once everything is on EditEngine, just use getIsNewObject() to - // figure this out instead. - $mark_as_create = false; - $create_type = PhabricatorTransactions::TYPE_CREATE; - foreach ($xactions as $xaction) { - if ($xaction->getTransactionType() == $create_type) { - $mark_as_create = true; - } - } - - if ($mark_as_create) { foreach ($xactions as $xaction) { - $xaction->setIsCreateTransaction(true); + $this->adjustTransactionValues($object, $xaction); } - } - // Now that we've merged, filtered, and combined transactions, check for - // required capabilities. - foreach ($xactions as $xaction) { - $this->requireCapabilities($object, $xaction); - } + $xactions = $this->filterTransactions($object, $xactions); - $xactions = $this->sortTransactions($xactions); - $file_phids = $this->extractFilePHIDs($object, $xactions); + // TODO: Once everything is on EditEngine, just use getIsNewObject() to + // figure this out instead. + $mark_as_create = false; + $create_type = PhabricatorTransactions::TYPE_CREATE; + foreach ($xactions as $xaction) { + if ($xaction->getTransactionType() == $create_type) { + $mark_as_create = true; + } + } - if ($is_preview) { - $this->loadHandles($xactions); - return $xactions; - } + if ($mark_as_create) { + foreach ($xactions as $xaction) { + $xaction->setIsCreateTransaction(true); + } + } - $comment_editor = id(new PhabricatorApplicationTransactionCommentEditor()) - ->setActor($actor) - ->setActingAsPHID($this->getActingAsPHID()) - ->setContentSource($this->getContentSource()); + // Now that we've merged, filtered, and combined transactions, check for + // required capabilities. + foreach ($xactions as $xaction) { + $this->requireCapabilities($object, $xaction); + } - if (!$transaction_open) { - $object->openTransaction(); - } + $xactions = $this->sortTransactions($xactions); + $file_phids = $this->extractFilePHIDs($object, $xactions); + + if ($is_preview) { + $this->loadHandles($xactions); + return $xactions; + } + + $comment_editor = id(new PhabricatorApplicationTransactionCommentEditor()) + ->setActor($actor) + ->setActingAsPHID($this->getActingAsPHID()) + ->setContentSource($this->getContentSource()); + + if (!$transaction_open) { + $object->openTransaction(); + $transaction_open = true; + } - try { foreach ($xactions as $xaction) { $this->applyInternalEffects($object, $xaction); } @@ -1070,14 +1051,27 @@ abstract class PhabricatorApplicationTransactionEditor } $xactions = $this->applyFinalEffects($object, $xactions); + if ($read_locking) { $object->endReadLocking(); $read_locking = false; } - $object->saveTransaction(); + if ($transaction_open) { + $object->saveTransaction(); + $transaction_open = false; + } } catch (Exception $ex) { - $object->killTransaction(); + if ($read_locking) { + $object->endReadLocking(); + $read_locking = false; + } + + if ($transaction_open) { + $object->killTransaction(); + $transaction_open = false; + } + throw $ex; } @@ -1319,46 +1313,6 @@ abstract class PhabricatorApplicationTransactionEditor return $xactions; } - - /** - * Determine if the editor should hold a read lock on the object while - * applying a transaction. - * - * If the editor does not hold a lock, two editors may read an object at the - * same time, then apply their changes without any synchronization. For most - * transactions, this does not matter much. However, it is important for some - * transactions. For example, if an object has a transaction count on it, both - * editors may read the object with `count = 23`, then independently update it - * and save the object with `count = 24` twice. This will produce the wrong - * state: the object really has 25 transactions, but the count is only 24. - * - * Generally, transactions fall into one of four buckets: - * - * - Append operations: Actions like adding a comment to an object purely - * add information to its state, and do not depend on the current object - * state in any way. These transactions never need to hold locks. - * - Overwrite operations: Actions like changing the title or description - * of an object replace the current value with a new value, so the end - * state is consistent without a lock. We currently do not lock these - * transactions, although we may in the future. - * - Edge operations: Edge and subscription operations have internal - * synchronization which limits the damage race conditions can cause. - * We do not currently lock these transactions, although we may in the - * future. - * - Update operations: Actions like incrementing a count on an object. - * These operations generally should use locks, unless it is not - * important that the state remain consistent in the presence of races. - * - * @param PhabricatorLiskDAO Object being updated. - * @param PhabricatorApplicationTransaction Transaction being applied. - * @return bool True to synchronize the edit with a lock. - */ - protected function shouldReadLock( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - return false; - } - private function loadHandles(array $xactions) { $phids = array(); foreach ($xactions as $key => $xaction) { From 5e6e9fcc56529b155c7f1e1f057eed8a4aaaf406 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 11 Feb 2018 05:47:50 -0800 Subject: [PATCH 07/58] When purging drafts after a transaction edit, purge all drafts Summary: Fixes T13071. See that task for discusison. I think this `<= version` constraint is needless in normal cases (it should match everything in the table anyway), and slightly harmful in bizarre cases where a draft somehow gets a much larger ID than it should have. Test Plan: - Gave a draft an unreasonably large ID. - Pre-patch, observed: submitting comments on the draft's object does not clear the draft. - Post-patch: submitting comments on the draft's object now clears the draft correctly. - Also added comments/actions, reloaded pages, saw drafts stick properly. Maniphest Tasks: T13071 Differential Revision: https://secure.phabricator.com/D19060 --- .../draft/storage/PhabricatorVersionedDraft.php | 9 +++------ .../transactions/editengine/PhabricatorEditEngine.php | 3 +-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/applications/draft/storage/PhabricatorVersionedDraft.php b/src/applications/draft/storage/PhabricatorVersionedDraft.php index 0daf13e8c5..58cdb67599 100644 --- a/src/applications/draft/storage/PhabricatorVersionedDraft.php +++ b/src/applications/draft/storage/PhabricatorVersionedDraft.php @@ -80,20 +80,17 @@ final class PhabricatorVersionedDraft extends PhabricatorDraftDAO { public static function purgeDrafts( $object_phid, - $viewer_phid, - $version) { + $viewer_phid) { $draft = new PhabricatorVersionedDraft(); $conn_w = $draft->establishConnection('w'); queryfx( $conn_w, - 'DELETE FROM %T WHERE objectPHID = %s AND authorPHID = %s - AND version <= %d', + 'DELETE FROM %T WHERE objectPHID = %s AND authorPHID = %s', $draft->getTableName(), $object_phid, - $viewer_phid, - $version); + $viewer_phid); } } diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php index ea3b987568..ea171a421c 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngine.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php @@ -1985,8 +1985,7 @@ abstract class PhabricatorEditEngine if (!$is_preview) { PhabricatorVersionedDraft::purgeDrafts( $object->getPHID(), - $viewer->getPHID(), - $this->loadDraftVersion($object)); + $viewer->getPHID()); $draft_engine = $this->newDraftEngine($object); if ($draft_engine) { From 4fa99374be1ca54077705fbc50796fa17f3e5b3f Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 11 Feb 2018 05:55:02 -0800 Subject: [PATCH 08/58] Prevent "Call webhooks" Herald action from appearing in UI for adapters which can't fire it Summary: See . The "Commit Hook" events don't operate on objects and don't use TransactionEditors. They can't call webhooks in a normal way and currently don't call webhooks at all. Stop offering these actions in the UI. The "Outbound Mail" event also fires oddly and likely doesn't make much sense to hook anyway. Test Plan: Verified that these events no longer offer "Call webhooks", while normal events still do. Differential Revision: https://secure.phabricator.com/D19061 --- src/applications/diffusion/herald/HeraldPreCommitAdapter.php | 4 ++++ src/applications/herald/action/HeraldCallWebhookAction.php | 4 ++++ src/applications/herald/adapter/HeraldAdapter.php | 5 +++++ .../herald/PhabricatorMailOutboundMailHeraldAdapter.php | 4 ++++ 4 files changed, 17 insertions(+) diff --git a/src/applications/diffusion/herald/HeraldPreCommitAdapter.php b/src/applications/diffusion/herald/HeraldPreCommitAdapter.php index 3f1bc6bfd0..f63f647da6 100644 --- a/src/applications/diffusion/herald/HeraldPreCommitAdapter.php +++ b/src/applications/diffusion/herald/HeraldPreCommitAdapter.php @@ -87,4 +87,8 @@ abstract class HeraldPreCommitAdapter extends HeraldAdapter { $this->hookEngine->getRepository()->getProjectPHIDs()); } + public function supportsWebhooks() { + return false; + } + } diff --git a/src/applications/herald/action/HeraldCallWebhookAction.php b/src/applications/herald/action/HeraldCallWebhookAction.php index a2003f4f33..953958e5c6 100644 --- a/src/applications/herald/action/HeraldCallWebhookAction.php +++ b/src/applications/herald/action/HeraldCallWebhookAction.php @@ -14,6 +14,10 @@ final class HeraldCallWebhookAction extends HeraldAction { } public function supportsObject($object) { + if (!$this->getAdapter()->supportsWebhooks()) { + return false; + } + return true; } diff --git a/src/applications/herald/adapter/HeraldAdapter.php b/src/applications/herald/adapter/HeraldAdapter.php index 7764332f11..08a0ca7a52 100644 --- a/src/applications/herald/adapter/HeraldAdapter.php +++ b/src/applications/herald/adapter/HeraldAdapter.php @@ -1211,6 +1211,11 @@ abstract class HeraldAdapter extends Phobject { /* -( Webhooks )----------------------------------------------------------- */ + public function supportsWebhooks() { + return true; + } + + final public function queueWebhook($webhook_phid, $rule_phid) { $this->webhookMap[$webhook_phid][] = $rule_phid; return $this; diff --git a/src/applications/metamta/herald/PhabricatorMailOutboundMailHeraldAdapter.php b/src/applications/metamta/herald/PhabricatorMailOutboundMailHeraldAdapter.php index f96a6f86bd..94c13c9e0a 100644 --- a/src/applications/metamta/herald/PhabricatorMailOutboundMailHeraldAdapter.php +++ b/src/applications/metamta/herald/PhabricatorMailOutboundMailHeraldAdapter.php @@ -64,4 +64,8 @@ final class PhabricatorMailOutboundMailHeraldAdapter return pht('Mail %d', $this->getObject()->getID()); } + public function supportsWebhooks() { + return false; + } + } From ed0ba41cd2d03b7267b24579a38c39512f63ff6c Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 12 Feb 2018 10:16:23 -0800 Subject: [PATCH 09/58] Allow a HarbormasterBuildMessage to be sent to any object Summary: See T13054. This prepares for Buildables to be sent messages ("attach", "done scheduling builds") to fix races between Harbormaster and Differential. The `buildTargetPHID` is replaced with a `recipientPHID` in the API. An additional change will fix the storage. In the future, this table could probably also replace `HarbormasterBuildCommand` now, which is approximately the same bus, but for Builds. Test Plan: Viewed builds with messages. Sent messages with `harbormaster.sendmessage`. Processed messages with `bin/phd debug task`. Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Differential Revision: https://secure.phabricator.com/D19062 --- ...arbormasterSendMessageConduitAPIMethod.php | 2 +- .../HarbormasterBuildViewController.php | 4 +- .../engine/HarbormasterBuildEngine.php | 4 +- .../query/HarbormasterBuildMessageQuery.php | 64 +++++++++---------- .../storage/HarbormasterBuildMessage.php | 28 +++++--- 5 files changed, 54 insertions(+), 48 deletions(-) diff --git a/src/applications/harbormaster/conduit/HarbormasterSendMessageConduitAPIMethod.php b/src/applications/harbormaster/conduit/HarbormasterSendMessageConduitAPIMethod.php index dbf1f18cfa..fc83b7ab42 100644 --- a/src/applications/harbormaster/conduit/HarbormasterSendMessageConduitAPIMethod.php +++ b/src/applications/harbormaster/conduit/HarbormasterSendMessageConduitAPIMethod.php @@ -245,7 +245,7 @@ final class HarbormasterSendMessageConduitAPIMethod } $save[] = HarbormasterBuildMessage::initializeNewMessage($viewer) - ->setBuildTargetPHID($build_target->getPHID()) + ->setReceiverPHID($build_target->getPHID()) ->setType($message_type); $build_target->openTransaction(); diff --git a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php index 5b7171a200..4507139d47 100644 --- a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php @@ -65,9 +65,9 @@ final class HarbormasterBuildViewController if ($build_targets) { $messages = id(new HarbormasterBuildMessageQuery()) ->setViewer($viewer) - ->withBuildTargetPHIDs(mpull($build_targets, 'getPHID')) + ->withReceiverPHIDs(mpull($build_targets, 'getPHID')) ->execute(); - $messages = mgroup($messages, 'getBuildTargetPHID'); + $messages = mgroup($messages, 'getReceiverPHID'); } else { $messages = array(); } diff --git a/src/applications/harbormaster/engine/HarbormasterBuildEngine.php b/src/applications/harbormaster/engine/HarbormasterBuildEngine.php index 9c4112e810..4079620f2a 100644 --- a/src/applications/harbormaster/engine/HarbormasterBuildEngine.php +++ b/src/applications/harbormaster/engine/HarbormasterBuildEngine.php @@ -382,12 +382,12 @@ final class HarbormasterBuildEngine extends Phobject { $messages = id(new HarbormasterBuildMessageQuery()) ->setViewer($this->getViewer()) - ->withBuildTargetPHIDs(array_keys($waiting_targets)) + ->withReceiverPHIDs(array_keys($waiting_targets)) ->withConsumed(false) ->execute(); foreach ($messages as $message) { - $target = $waiting_targets[$message->getBuildTargetPHID()]; + $target = $waiting_targets[$message->getReceiverPHID()]; switch ($message->getType()) { case HarbormasterMessageType::MESSAGE_PASS: diff --git a/src/applications/harbormaster/query/HarbormasterBuildMessageQuery.php b/src/applications/harbormaster/query/HarbormasterBuildMessageQuery.php index 749fdd76d4..1b55f2e781 100644 --- a/src/applications/harbormaster/query/HarbormasterBuildMessageQuery.php +++ b/src/applications/harbormaster/query/HarbormasterBuildMessageQuery.php @@ -4,7 +4,7 @@ final class HarbormasterBuildMessageQuery extends PhabricatorCursorPagedPolicyAwareQuery { private $ids; - private $buildTargetPHIDs; + private $receiverPHIDs; private $consumed; public function withIDs(array $ids) { @@ -12,8 +12,8 @@ final class HarbormasterBuildMessageQuery return $this; } - public function withBuildTargetPHIDs(array $phids) { - $this->buildTargetPHIDs = $phids; + public function withReceiverPHIDs(array $phids) { + $this->receiverPHIDs = $phids; return $this; } @@ -22,73 +22,67 @@ final class HarbormasterBuildMessageQuery return $this; } + public function newResultObject() { + return new HarbormasterBuildMessage(); + } + protected function loadPage() { - $table = new HarbormasterBuildMessage(); - $conn_r = $table->establishConnection('r'); - - $data = queryfx_all( - $conn_r, - 'SELECT * FROM %T %Q %Q %Q', - $table->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - - return $table->loadAllFromArray($data); + return $this->loadStandardPage($this->newResultObject()); } protected function willFilterPage(array $page) { - $build_target_phids = array_filter(mpull($page, 'getBuildTargetPHID')); - if ($build_target_phids) { - $build_targets = id(new PhabricatorObjectQuery()) + $receiver_phids = array_filter(mpull($page, 'getReceiverPHID')); + if ($receiver_phids) { + $receivers = id(new PhabricatorObjectQuery()) ->setViewer($this->getViewer()) - ->withPHIDs($build_target_phids) + ->withPHIDs($receiver_phids) ->setParentQuery($this) ->execute(); - $build_targets = mpull($build_targets, null, 'getPHID'); + $receivers = mpull($receivers, null, 'getPHID'); } else { - $build_targets = array(); + $receivers = array(); } foreach ($page as $key => $message) { - $build_target_phid = $message->getBuildTargetPHID(); - if (empty($build_targets[$build_target_phid])) { + $receiver_phid = $message->getReceiverPHID(); + + if (empty($receivers[$receiver_phid])) { unset($page[$key]); + $this->didRejectResult($message); continue; } - $message->attachBuildTarget($build_targets[$build_target_phid]); + + $message->attachReceiver($receivers[$receiver_phid]); } return $page; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); - if ($this->ids) { + if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } - if ($this->buildTargetPHIDs) { + if ($this->receiverPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'buildTargetPHID IN (%Ls)', - $this->buildTargetPHIDs); + $this->receiverPHIDs); } if ($this->consumed !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'isConsumed = %d', (int)$this->consumed); } - $where[] = $this->buildPagingClause($conn_r); - - return $this->formatWhereClause($where); + return $where; } public function getQueryApplicationClass() { diff --git a/src/applications/harbormaster/storage/HarbormasterBuildMessage.php b/src/applications/harbormaster/storage/HarbormasterBuildMessage.php index 20d138ad02..5c9c92b2fc 100644 --- a/src/applications/harbormaster/storage/HarbormasterBuildMessage.php +++ b/src/applications/harbormaster/storage/HarbormasterBuildMessage.php @@ -14,7 +14,7 @@ final class HarbormasterBuildMessage extends HarbormasterDAO protected $type; protected $isConsumed; - private $buildTarget = self::ATTACHABLE; + private $receiver = self::ATTACHABLE; public static function initializeNewMessage(PhabricatorUser $actor) { $actor_phid = $actor->getPHID(); @@ -41,12 +41,24 @@ final class HarbormasterBuildMessage extends HarbormasterDAO ) + parent::getConfiguration(); } - public function getBuildTarget() { - return $this->assertAttached($this->buildTarget); + public function getReceiverPHID() { + return $this->getBuildTargetPHID(); } - public function attachBuildTarget(HarbormasterBuildTarget $target) { - $this->buildTarget = $target; + public function setReceiverPHID($phid) { + return $this->setBuildTargetPHID($phid); + } + + public function getReceiver() { + return $this->assertAttached($this->receiver); + } + + public function getBuildTarget() { + return $this->getReceiver(); + } + + public function attachReceiver($receiver) { + $this->receiver = $receiver; return $this; } @@ -61,17 +73,17 @@ final class HarbormasterBuildMessage extends HarbormasterDAO } public function getPolicy($capability) { - return $this->getBuildTarget()->getPolicy($capability); + return $this->getReceiver()->getPolicy($capability); } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { - return $this->getBuildTarget()->hasAutomaticCapability( + return $this->getReceiver()->hasAutomaticCapability( $capability, $viewer); } public function describeAutomaticCapability($capability) { - return pht('Build messages have the same policies as their targets.'); + return pht('Build messages have the same policies as their receivers.'); } } From c42bbd6f5cff758610aec7fe2df27895dec78ed3 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 12 Feb 2018 10:31:08 -0800 Subject: [PATCH 10/58] Rename HarbormasterBuildMessage "buildTargetPHID" to "receiverPHID" Summary: Ref T13054. Companion storage change for D19062. Test Plan: Applied migration and adjustments. Viewed messages in Harbormaster; created them with `harbormaster.sendmessage`; processed them with `bin/phd debug task`. Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T13054 Differential Revision: https://secure.phabricator.com/D19063 --- .../20180212.harbor.01.receiver.sql | 2 ++ .../query/HarbormasterBuildMessageQuery.php | 2 +- .../storage/HarbormasterBuildMessage.php | 18 +++--------------- 3 files changed, 6 insertions(+), 16 deletions(-) create mode 100644 resources/sql/autopatches/20180212.harbor.01.receiver.sql diff --git a/resources/sql/autopatches/20180212.harbor.01.receiver.sql b/resources/sql/autopatches/20180212.harbor.01.receiver.sql new file mode 100644 index 0000000000..84e9611db2 --- /dev/null +++ b/resources/sql/autopatches/20180212.harbor.01.receiver.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_harbormaster.harbormaster_buildmessage + CHANGE buildTargetPHID receiverPHID VARBINARY(64) NOT NULL; diff --git a/src/applications/harbormaster/query/HarbormasterBuildMessageQuery.php b/src/applications/harbormaster/query/HarbormasterBuildMessageQuery.php index 1b55f2e781..134c68ce04 100644 --- a/src/applications/harbormaster/query/HarbormasterBuildMessageQuery.php +++ b/src/applications/harbormaster/query/HarbormasterBuildMessageQuery.php @@ -71,7 +71,7 @@ final class HarbormasterBuildMessageQuery if ($this->receiverPHIDs !== null) { $where[] = qsprintf( $conn, - 'buildTargetPHID IN (%Ls)', + 'receiverPHID IN (%Ls)', $this->receiverPHIDs); } diff --git a/src/applications/harbormaster/storage/HarbormasterBuildMessage.php b/src/applications/harbormaster/storage/HarbormasterBuildMessage.php index 5c9c92b2fc..1066a93610 100644 --- a/src/applications/harbormaster/storage/HarbormasterBuildMessage.php +++ b/src/applications/harbormaster/storage/HarbormasterBuildMessage.php @@ -10,7 +10,7 @@ final class HarbormasterBuildMessage extends HarbormasterDAO implements PhabricatorPolicyInterface { protected $authorPHID; - protected $buildTargetPHID; + protected $receiverPHID; protected $type; protected $isConsumed; @@ -34,29 +34,17 @@ final class HarbormasterBuildMessage extends HarbormasterDAO 'isConsumed' => 'bool', ), self::CONFIG_KEY_SCHEMA => array( - 'key_buildtarget' => array( - 'columns' => array('buildTargetPHID'), + 'key_receiver' => array( + 'columns' => array('receiverPHID'), ), ), ) + parent::getConfiguration(); } - public function getReceiverPHID() { - return $this->getBuildTargetPHID(); - } - - public function setReceiverPHID($phid) { - return $this->setBuildTargetPHID($phid); - } - public function getReceiver() { return $this->assertAttached($this->receiver); } - public function getBuildTarget() { - return $this->getReceiver(); - } - public function attachReceiver($receiver) { $this->receiver = $receiver; return $this; From f939a2b12ea3c2361bf536e63d24ee4e4f12c690 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 12 Feb 2018 10:55:19 -0800 Subject: [PATCH 11/58] Make Harbormaster buildable status more of a nice flexible map and less of a bunch of switch statements Summary: Depends on D19063. Ref T13054. Prepare for the addition of a new `PREPARING` status by getting rid of the "scattered mess of switch statements" pattern of status management. Test Plan: Searched/browsed buildables. Viewed buildables. Viewed revisions. Grepped for all affected symbols. Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T13054 Differential Revision: https://secure.phabricator.com/D19064 --- src/__phutil_library_map__.php | 2 + .../customfield/DifferentialUnitField.php | 4 +- .../diffusion/view/DiffusionView.php | 7 +- ...rmasterQueryBuildablesConduitAPIMethod.php | 2 +- .../constants/HarbormasterBuildableStatus.php | 82 +++++++++++++++++++ .../engine/HarbormasterBuildEngine.php | 13 +-- .../event/HarbormasterUIEventListener.php | 10 +-- .../HarbormasterBuildableSearchEngine.php | 10 +-- .../storage/HarbormasterBuildable.php | 78 +++++------------- .../PhabricatorApplicationTransaction.php | 14 ++-- 10 files changed, 133 insertions(+), 89 deletions(-) create mode 100644 src/applications/harbormaster/constants/HarbormasterBuildableStatus.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 7b4f48d1f0..59b375eb55 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1272,6 +1272,7 @@ phutil_register_library_map(array( 'HarbormasterBuildablePHIDType' => 'applications/harbormaster/phid/HarbormasterBuildablePHIDType.php', 'HarbormasterBuildableQuery' => 'applications/harbormaster/query/HarbormasterBuildableQuery.php', 'HarbormasterBuildableSearchEngine' => 'applications/harbormaster/query/HarbormasterBuildableSearchEngine.php', + 'HarbormasterBuildableStatus' => 'applications/harbormaster/constants/HarbormasterBuildableStatus.php', 'HarbormasterBuildableTransaction' => 'applications/harbormaster/storage/HarbormasterBuildableTransaction.php', 'HarbormasterBuildableTransactionEditor' => 'applications/harbormaster/editor/HarbormasterBuildableTransactionEditor.php', 'HarbormasterBuildableTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildableTransactionQuery.php', @@ -6549,6 +6550,7 @@ phutil_register_library_map(array( 'HarbormasterBuildablePHIDType' => 'PhabricatorPHIDType', 'HarbormasterBuildableQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'HarbormasterBuildableSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'HarbormasterBuildableStatus' => 'Phobject', 'HarbormasterBuildableTransaction' => 'PhabricatorApplicationTransaction', 'HarbormasterBuildableTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'HarbormasterBuildableTransactionQuery' => 'PhabricatorApplicationTransactionQuery', diff --git a/src/applications/differential/customfield/DifferentialUnitField.php b/src/applications/differential/customfield/DifferentialUnitField.php index 7b3d491cbd..4c1eb72cc8 100644 --- a/src/applications/differential/customfield/DifferentialUnitField.php +++ b/src/applications/differential/customfield/DifferentialUnitField.php @@ -44,12 +44,12 @@ final class DifferentialUnitField ->executeOne(); if ($buildable) { switch ($buildable->getBuildableStatus()) { - case HarbormasterBuildable::STATUS_BUILDING: + case HarbormasterBuildableStatus::STATUS_BUILDING: $warnings[] = pht( 'These changes have not finished building yet and may have build '. 'failures.'); break; - case HarbormasterBuildable::STATUS_FAILED: + case HarbormasterBuildableStatus::STATUS_FAILED: $warnings[] = pht( 'These changes have failed to build.'); break; diff --git a/src/applications/diffusion/view/DiffusionView.php b/src/applications/diffusion/view/DiffusionView.php index eb43e49f3c..eb7f3eb72f 100644 --- a/src/applications/diffusion/view/DiffusionView.php +++ b/src/applications/diffusion/view/DiffusionView.php @@ -205,12 +205,11 @@ abstract class DiffusionView extends AphrontView { final protected function renderBuildable( HarbormasterBuildable $buildable, $type = null) { - $status = $buildable->getBuildableStatus(); Javelin::initBehavior('phabricator-tooltips'); - $icon = HarbormasterBuildable::getBuildableStatusIcon($status); - $color = HarbormasterBuildable::getBuildableStatusColor($status); - $name = HarbormasterBuildable::getBuildableStatusName($status); + $icon = $buildable->getStatusIcon(); + $color = $buildable->getStatusColor(); + $name = $buildable->getStatusDisplayName(); if ($type == 'button') { return id(new PHUIButtonView()) diff --git a/src/applications/harbormaster/conduit/HarbormasterQueryBuildablesConduitAPIMethod.php b/src/applications/harbormaster/conduit/HarbormasterQueryBuildablesConduitAPIMethod.php index 818b983538..cae06d47c7 100644 --- a/src/applications/harbormaster/conduit/HarbormasterQueryBuildablesConduitAPIMethod.php +++ b/src/applications/harbormaster/conduit/HarbormasterQueryBuildablesConduitAPIMethod.php @@ -65,7 +65,7 @@ final class HarbormasterQueryBuildablesConduitAPIMethod $monogram = $buildable->getMonogram(); $status = $buildable->getBuildableStatus(); - $status_name = HarbormasterBuildable::getBuildableStatusName($status); + $status_name = $buildable->getStatusDisplayName(); $data[] = array( 'id' => $buildable->getID(), diff --git a/src/applications/harbormaster/constants/HarbormasterBuildableStatus.php b/src/applications/harbormaster/constants/HarbormasterBuildableStatus.php new file mode 100644 index 0000000000..9cd3dd0b6f --- /dev/null +++ b/src/applications/harbormaster/constants/HarbormasterBuildableStatus.php @@ -0,0 +1,82 @@ +key = $key; + $this->properties = $properties; + } + + public static function newBuildableStatusObject($status) { + $spec = self::getSpecification($status); + return new self($status, $spec); + } + + private function getProperty($key) { + if (!array_key_exists($key, $this->properties)) { + throw new Exception( + pht( + 'Attempting to access unknown buildable status property ("%s").', + $key)); + } + + return $this->properties[$key]; + } + + public function getIcon() { + return $this->getProperty('icon'); + } + + public function getDisplayName() { + return $this->getProperty('name'); + } + + public function getColor() { + return $this->getProperty('color'); + } + + public static function getOptionMap() { + return ipull(self::getSpecifications(), 'name'); + } + + private static function getSpecifications() { + return array( + self::STATUS_BUILDING => array( + 'name' => pht('Building'), + 'color' => 'blue', + 'icon' => 'fa-chevron-circle-right', + ), + self::STATUS_PASSED => array( + 'name' => pht('Passed'), + 'color' => 'green', + 'icon' => 'fa-check-circle', + ), + self::STATUS_FAILED => array( + 'name' => pht('Failed'), + 'color' => 'red', + 'icon' => 'fa-times-circle', + ), + ); + } + + private static function getSpecification($status) { + $map = self::getSpecifications(); + if (isset($map[$status])) { + return $map[$status]; + } + + return array( + 'name' => pht('Unknown ("%s")', $status), + 'icon' => 'fa-question-circle', + 'color' => 'bluegrey', + ); + } + +} diff --git a/src/applications/harbormaster/engine/HarbormasterBuildEngine.php b/src/applications/harbormaster/engine/HarbormasterBuildEngine.php index 4079620f2a..3749178418 100644 --- a/src/applications/harbormaster/engine/HarbormasterBuildEngine.php +++ b/src/applications/harbormaster/engine/HarbormasterBuildEngine.php @@ -453,11 +453,11 @@ final class HarbormasterBuildEngine extends Phobject { } if ($any_fail) { - $new_status = HarbormasterBuildable::STATUS_FAILED; + $new_status = HarbormasterBuildableStatus::STATUS_FAILED; } else if ($all_pass) { - $new_status = HarbormasterBuildable::STATUS_PASSED; + $new_status = HarbormasterBuildableStatus::STATUS_PASSED; } else { - $new_status = HarbormasterBuildable::STATUS_BUILDING; + $new_status = HarbormasterBuildableStatus::STATUS_BUILDING; } $old_status = $buildable->getBuildableStatus(); @@ -477,9 +477,10 @@ final class HarbormasterBuildEngine extends Phobject { // can look at the results themselves, and other users generally don't // care about the outcome. - $should_publish = $did_update && - $new_status != HarbormasterBuildable::STATUS_BUILDING && - !$buildable->getIsManualBuildable(); + $should_publish = + ($did_update) && + ($new_status != HarbormasterBuildableStatus::STATUS_BUILDING) && + (!$buildable->getIsManualBuildable()); if (!$should_publish) { return; diff --git a/src/applications/harbormaster/event/HarbormasterUIEventListener.php b/src/applications/harbormaster/event/HarbormasterUIEventListener.php index 219f556b3a..1227fbbe31 100644 --- a/src/applications/harbormaster/event/HarbormasterUIEventListener.php +++ b/src/applications/harbormaster/event/HarbormasterUIEventListener.php @@ -87,13 +87,9 @@ final class HarbormasterUIEventListener $status_view = new PHUIStatusListView(); - $buildable_status = $buildable->getBuildableStatus(); - $buildable_icon = HarbormasterBuildable::getBuildableStatusIcon( - $buildable_status); - $buildable_color = HarbormasterBuildable::getBuildableStatusColor( - $buildable_status); - $buildable_name = HarbormasterBuildable::getBuildableStatusName( - $buildable_status); + $buildable_icon = $buildable->getStatusIcon(); + $buildable_color = $buildable->getStatusColor(); + $buildable_name = $buildable->getStatusDisplayName(); $target = phutil_tag( 'a', diff --git a/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php b/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php index cfff27b1aa..36f5dc89c2 100644 --- a/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php +++ b/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php @@ -33,7 +33,7 @@ final class HarbormasterBuildableSearchEngine id(new PhabricatorSearchCheckboxesField()) ->setKey('statuses') ->setLabel(pht('Statuses')) - ->setOptions(HarbormasterBuildable::getBuildStatusMap()) + ->setOptions(HarbormasterBuildableStatus::getOptionMap()) ->setDescription(pht('Search for builds by buildable status.')), id(new PhabricatorSearchThreeStateField()) ->setLabel(pht('Manual')) @@ -169,11 +169,9 @@ final class HarbormasterBuildableSearchEngine $item->addIcon('fa-wrench grey', pht('Manual')); } - $status = $buildable->getBuildableStatus(); - - $status_icon = HarbormasterBuildable::getBuildableStatusIcon($status); - $status_color = HarbormasterBuildable::getBuildableStatusColor($status); - $status_label = HarbormasterBuildable::getBuildableStatusName($status); + $status_icon = $buildable->getStatusIcon(); + $status_color = $buildable->getStatusColor(); + $status_label = $buildable->getStatusDisplayName(); $item->setStatusIcon("{$status_icon} {$status_color}", $status_label); diff --git a/src/applications/harbormaster/storage/HarbormasterBuildable.php b/src/applications/harbormaster/storage/HarbormasterBuildable.php index 89c13b8b41..fc15aca063 100644 --- a/src/applications/harbormaster/storage/HarbormasterBuildable.php +++ b/src/applications/harbormaster/storage/HarbormasterBuildable.php @@ -15,65 +15,10 @@ final class HarbormasterBuildable extends HarbormasterDAO private $containerObject = self::ATTACHABLE; private $builds = self::ATTACHABLE; - const STATUS_BUILDING = 'building'; - const STATUS_PASSED = 'passed'; - const STATUS_FAILED = 'failed'; - - public static function getBuildableStatusName($status) { - $map = self::getBuildStatusMap(); - return idx($map, $status, pht('Unknown ("%s")', $status)); - } - - public static function getBuildStatusMap() { - return array( - self::STATUS_BUILDING => pht('Building'), - self::STATUS_PASSED => pht('Passed'), - self::STATUS_FAILED => pht('Failed'), - ); - } - - public static function getBuildableStatusIcon($status) { - switch ($status) { - case self::STATUS_BUILDING: - return PHUIStatusItemView::ICON_RIGHT; - case self::STATUS_PASSED: - return PHUIStatusItemView::ICON_ACCEPT; - case self::STATUS_FAILED: - return PHUIStatusItemView::ICON_REJECT; - default: - return PHUIStatusItemView::ICON_QUESTION; - } - } - - public static function getBuildableStatusColor($status) { - switch ($status) { - case self::STATUS_BUILDING: - return 'blue'; - case self::STATUS_PASSED: - return 'green'; - case self::STATUS_FAILED: - return 'red'; - default: - return 'bluegrey'; - } - } - - public function getStatusIcon() { - return self::getBuildableStatusIcon($this->getBuildableStatus()); - } - - public function getStatusDisplayName() { - return self::getBuildableStatusName($this->getBuildableStatus()); - } - - public function getStatusColor() { - return self::getBuildableStatusColor($this->getBuildableStatus()); - } - public static function initializeNewBuildable(PhabricatorUser $actor) { return id(new HarbormasterBuildable()) ->setIsManualBuildable(0) - ->setBuildableStatus(self::STATUS_BUILDING); + ->setBuildableStatus(HarbormasterBuildableStatus::STATUS_BUILDING); } public function getMonogram() { @@ -262,6 +207,27 @@ final class HarbormasterBuildable extends HarbormasterDAO } +/* -( Status )------------------------------------------------------------- */ + + + public function getBuildableStatusObject() { + $status = $this->getBuildableStatus(); + return HarbormasterBuildableStatus::newBuildableStatusObject($status); + } + + public function getStatusIcon() { + return $this->getBuildableStatusObject()->getIcon(); + } + + public function getStatusDisplayName() { + return $this->getBuildableStatusObject()->getDisplayName(); + } + + public function getStatusColor() { + return $this->getBuildableStatusObject()->getColor(); + } + + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php index d27370cd44..7682ab5ebb 100644 --- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php +++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php @@ -512,9 +512,9 @@ abstract class PhabricatorApplicationTransaction break; case PhabricatorTransactions::TYPE_BUILDABLE: switch ($this->getNewValue()) { - case HarbormasterBuildable::STATUS_PASSED: + case HarbormasterBuildableStatus::STATUS_PASSED: return 'green'; - case HarbormasterBuildable::STATUS_FAILED: + case HarbormasterBuildableStatus::STATUS_FAILED: return 'red'; } break; @@ -676,7 +676,7 @@ abstract class PhabricatorApplicationTransaction return true; case PhabricatorTransactions::TYPE_BUILDABLE: switch ($this->getNewValue()) { - case HarbormasterBuildable::STATUS_FAILED: + case HarbormasterBuildableStatus::STATUS_FAILED: // For now, only ever send mail when builds fail. We might let // you customize this later, but in most cases this is probably // completely uninteresting. @@ -739,7 +739,7 @@ abstract class PhabricatorApplicationTransaction return true; case PhabricatorTransactions::TYPE_BUILDABLE: switch ($this->getNewValue()) { - case HarbormasterBuildable::STATUS_FAILED: + case HarbormasterBuildableStatus::STATUS_FAILED: // For now, don't notify on build passes either. These are pretty // high volume and annoying, with very little present value. We // might want to turn them back on in the specific case of @@ -1024,19 +1024,19 @@ abstract class PhabricatorApplicationTransaction case PhabricatorTransactions::TYPE_BUILDABLE: switch ($this->getNewValue()) { - case HarbormasterBuildable::STATUS_BUILDING: + case HarbormasterBuildableStatus::STATUS_BUILDING: return pht( '%s started building %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink( $this->getMetadataValue('harbormaster:buildablePHID'))); - case HarbormasterBuildable::STATUS_PASSED: + case HarbormasterBuildableStatus::STATUS_PASSED: return pht( '%s completed building %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink( $this->getMetadataValue('harbormaster:buildablePHID'))); - case HarbormasterBuildable::STATUS_FAILED: + case HarbormasterBuildableStatus::STATUS_FAILED: return pht( '%s failed to build %s!', $this->renderHandleLink($author_phid), From 66f20595e4d276c292996821677b518df649d082 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 12 Feb 2018 11:28:13 -0800 Subject: [PATCH 12/58] Start buildables in "PREPARING", move them to "BUILDING" after builds queue Summary: Depends on D19064. Ref T13054. See that task for additional discussion. When buildables are created by `arc` and have lint/unit messages, they can currently pass or fail before Herald triggers actual builds. This puts them in a pre-build state where they can't complete until Herald says it's okay. On its own, this change intentionally strands `arc diff --only` diffs in the "PREPARING" stage forever. Test Plan: - Ran a build with `bin/harbormaster`, saw it build normally. - Ran a build with web UI, saw it build normally. - Ran a build with `arc diff`, saw it build normally. - Ran a build with `arc diff --only`, saw it hang in "PREPARING" forever. Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T13054 Differential Revision: https://secure.phabricator.com/D19065 --- .../constants/HarbormasterBuildableStatus.php | 10 +++ .../HarbormasterPlanRunController.php | 6 ++ .../engine/HarbormasterBuildEngine.php | 84 ++++++++++++++----- .../engine/HarbormasterMessageType.php | 2 + .../HarbormasterManagementBuildWorkflow.php | 5 ++ .../storage/HarbormasterBuildable.php | 34 +++++++- .../worker/HarbormasterBuildWorker.php | 36 ++++++-- ...habricatorApplicationTransactionEditor.php | 24 +++++- 8 files changed, 174 insertions(+), 27 deletions(-) diff --git a/src/applications/harbormaster/constants/HarbormasterBuildableStatus.php b/src/applications/harbormaster/constants/HarbormasterBuildableStatus.php index 9cd3dd0b6f..7ba2379f30 100644 --- a/src/applications/harbormaster/constants/HarbormasterBuildableStatus.php +++ b/src/applications/harbormaster/constants/HarbormasterBuildableStatus.php @@ -2,6 +2,7 @@ final class HarbormasterBuildableStatus extends Phobject { + const STATUS_PREPARING = 'preparing'; const STATUS_BUILDING = 'building'; const STATUS_PASSED = 'passed'; const STATUS_FAILED = 'failed'; @@ -42,12 +43,21 @@ final class HarbormasterBuildableStatus extends Phobject { return $this->getProperty('color'); } + public function isPreparing() { + return ($this->key === self::STATUS_PREPARING); + } + public static function getOptionMap() { return ipull(self::getSpecifications(), 'name'); } private static function getSpecifications() { return array( + self::STATUS_PREPARING => array( + 'name' => pht('Preparing'), + 'color' => 'blue', + 'icon' => 'fa-hourglass-o', + ), self::STATUS_BUILDING => array( 'name' => pht('Building'), 'color' => 'blue', diff --git a/src/applications/harbormaster/controller/HarbormasterPlanRunController.php b/src/applications/harbormaster/controller/HarbormasterPlanRunController.php index 85bdbd8b49..fd227ee554 100644 --- a/src/applications/harbormaster/controller/HarbormasterPlanRunController.php +++ b/src/applications/harbormaster/controller/HarbormasterPlanRunController.php @@ -59,6 +59,12 @@ final class HarbormasterPlanRunController extends HarbormasterPlanController { if (!$errors) { $buildable->save(); + + $buildable->sendMessage( + $viewer, + HarbormasterMessageType::BUILDABLE_BUILD, + false); + $buildable->applyPlan($plan, array(), $viewer->getPHID()); $buildable_uri = '/B'.$buildable->getID(); diff --git a/src/applications/harbormaster/engine/HarbormasterBuildEngine.php b/src/applications/harbormaster/engine/HarbormasterBuildEngine.php index 3749178418..ea20c64538 100644 --- a/src/applications/harbormaster/engine/HarbormasterBuildEngine.php +++ b/src/applications/harbormaster/engine/HarbormasterBuildEngine.php @@ -428,7 +428,7 @@ final class HarbormasterBuildEngine extends Phobject { * @param HarbormasterBuild The buildable to update. * @return void */ - private function updateBuildable(HarbormasterBuildable $buildable) { + public function updateBuildable(HarbormasterBuildable $buildable) { $viewer = $this->getViewer(); $lock_key = 'harbormaster.buildable:'.$buildable->getID(); @@ -440,35 +440,79 @@ final class HarbormasterBuildEngine extends Phobject { ->needBuilds(true) ->executeOne(); - $all_pass = true; - $any_fail = false; - foreach ($buildable->getBuilds() as $build) { - if (!$build->isPassed()) { - $all_pass = false; + $messages = id(new HarbormasterBuildMessageQuery()) + ->setViewer($viewer) + ->withReceiverPHIDs(array($buildable->getPHID())) + ->withConsumed(false) + ->execute(); + + $done_preparing = false; + foreach ($messages as $message) { + switch ($message->getType()) { + case HarbormasterMessageType::BUILDABLE_BUILD: + $done_preparing = true; + break; + default: + break; } - if ($build->isComplete() && !$build->isPassed()) { - $any_fail = true; + $message + ->setIsConsumed(true) + ->save(); + } + + // If we received a "build" command, all builds are scheduled and we can + // move out of "preparing" into "building". + + if ($done_preparing) { + if ($buildable->isPreparing()) { + $buildable + ->setBuildableStatus(HarbormasterBuildableStatus::STATUS_BUILDING) + ->save(); } } - if ($any_fail) { - $new_status = HarbormasterBuildableStatus::STATUS_FAILED; - } else if ($all_pass) { - $new_status = HarbormasterBuildableStatus::STATUS_PASSED; - } else { - $new_status = HarbormasterBuildableStatus::STATUS_BUILDING; - } + // Don't update the buildable status if we're still preparing builds: more + // builds may still be scheduled shortly, so even if every build we know + // about so far has passed, that doesn't mean the buildable has actually + // passed everything it needs to. - $old_status = $buildable->getBuildableStatus(); - $did_update = ($old_status != $new_status); - if ($did_update) { - $buildable->setBuildableStatus($new_status); - $buildable->save(); + if (!$buildable->isPreparing()) { + $all_pass = true; + $any_fail = false; + foreach ($buildable->getBuilds() as $build) { + if (!$build->isPassed()) { + $all_pass = false; + } + + if ($build->isComplete() && !$build->isPassed()) { + $any_fail = true; + } + } + + if ($any_fail) { + $new_status = HarbormasterBuildableStatus::STATUS_FAILED; + } else if ($all_pass) { + $new_status = HarbormasterBuildableStatus::STATUS_PASSED; + } else { + $new_status = HarbormasterBuildableStatus::STATUS_BUILDING; + } + + $old_status = $buildable->getBuildableStatus(); + $did_update = ($old_status != $new_status); + if ($did_update) { + $buildable->setBuildableStatus($new_status); + $buildable->save(); + } } $lock->unlock(); + // Don't publish anything if we're still preparing builds. + if ($buildable->isPreparing()) { + return; + } + // If we changed the buildable status, try to post a transaction to the // object about it. We can safely do this outside of the locked region. diff --git a/src/applications/harbormaster/engine/HarbormasterMessageType.php b/src/applications/harbormaster/engine/HarbormasterMessageType.php index 5900817c69..8b3ebe2e6f 100644 --- a/src/applications/harbormaster/engine/HarbormasterMessageType.php +++ b/src/applications/harbormaster/engine/HarbormasterMessageType.php @@ -6,6 +6,8 @@ final class HarbormasterMessageType extends Phobject { const MESSAGE_FAIL = 'fail'; const MESSAGE_WORK = 'work'; + const BUILDABLE_BUILD = 'build'; + public static function getAllMessages() { return array_keys(self::getMessageSpecifications()); } diff --git a/src/applications/harbormaster/management/HarbormasterManagementBuildWorkflow.php b/src/applications/harbormaster/management/HarbormasterManagementBuildWorkflow.php index 6fd3cf2ffd..2efd443a91 100644 --- a/src/applications/harbormaster/management/HarbormasterManagementBuildWorkflow.php +++ b/src/applications/harbormaster/management/HarbormasterManagementBuildWorkflow.php @@ -83,6 +83,11 @@ final class HarbormasterManagementBuildWorkflow ->setContainerPHID($buildable->getHarbormasterContainerPHID()) ->save(); + $buildable->sendMessage( + $viewer, + HarbormasterMessageType::BUILDABLE_BUILD, + false); + $console->writeOut( "%s\n", pht( diff --git a/src/applications/harbormaster/storage/HarbormasterBuildable.php b/src/applications/harbormaster/storage/HarbormasterBuildable.php index fc15aca063..5de2159e8d 100644 --- a/src/applications/harbormaster/storage/HarbormasterBuildable.php +++ b/src/applications/harbormaster/storage/HarbormasterBuildable.php @@ -18,7 +18,7 @@ final class HarbormasterBuildable extends HarbormasterDAO public static function initializeNewBuildable(PhabricatorUser $actor) { return id(new HarbormasterBuildable()) ->setIsManualBuildable(0) - ->setBuildableStatus(HarbormasterBuildableStatus::STATUS_BUILDING); + ->setBuildableStatus(HarbormasterBuildableStatus::STATUS_PREPARING); } public function getMonogram() { @@ -227,6 +227,38 @@ final class HarbormasterBuildable extends HarbormasterDAO return $this->getBuildableStatusObject()->getColor(); } + public function isPreparing() { + return $this->getBuildableStatusObject()->isPreparing(); + } + + +/* -( Messages )----------------------------------------------------------- */ + + + public function sendMessage( + PhabricatorUser $viewer, + $message_type, + $queue_update) { + + $message = HarbormasterBuildMessage::initializeNewMessage($viewer) + ->setReceiverPHID($this->getPHID()) + ->setType($message_type) + ->save(); + + if ($queue_update) { + PhabricatorWorker::scheduleTask( + 'HarbormasterBuildWorker', + array( + 'buildablePHID' => $this->getPHID(), + ), + array( + 'objectPHID' => $this->getPHID(), + )); + } + + return $message; + } + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/applications/harbormaster/worker/HarbormasterBuildWorker.php b/src/applications/harbormaster/worker/HarbormasterBuildWorker.php index 67eb6b3e78..9ce9d50f13 100644 --- a/src/applications/harbormaster/worker/HarbormasterBuildWorker.php +++ b/src/applications/harbormaster/worker/HarbormasterBuildWorker.php @@ -17,12 +17,21 @@ final class HarbormasterBuildWorker extends HarbormasterWorker { protected function doWork() { $viewer = $this->getViewer(); - $build = $this->loadBuild(); - id(new HarbormasterBuildEngine()) - ->setViewer($viewer) - ->setBuild($build) - ->continueBuild(); + $engine = id(new HarbormasterBuildEngine()) + ->setViewer($viewer); + + $data = $this->getTaskData(); + $build_id = idx($data, 'buildID'); + + if ($build_id) { + $build = $this->loadBuild(); + $engine->setBuild($build); + $engine->continueBuild(); + } else { + $buildable = $this->loadBuildable(); + $engine->updateBuildable($buildable); + } } private function loadBuild() { @@ -42,4 +51,21 @@ final class HarbormasterBuildWorker extends HarbormasterWorker { return $build; } + private function loadBuildable() { + $data = $this->getTaskData(); + $phid = idx($data, 'buildablePHID'); + + $viewer = $this->getViewer(); + $buildable = id(new HarbormasterBuildableQuery()) + ->setViewer($viewer) + ->withPHIDs(array($phid)) + ->executeOne(); + if (!$buildable) { + throw new PhabricatorWorkerPermanentFailureException( + pht('Invalid buildable PHID "%s".', $phid)); + } + + return $buildable; + } + } diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index 7bc1f693c7..7cb1300feb 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -3262,10 +3262,32 @@ abstract class PhabricatorApplicationTransactionEditor $this->setHeraldTranscript($xscript); if ($adapter instanceof HarbormasterBuildableAdapterInterface) { + $buildable_phid = $adapter->getHarbormasterBuildablePHID(); + HarbormasterBuildable::applyBuildPlans( - $adapter->getHarbormasterBuildablePHID(), + $buildable_phid, $adapter->getHarbormasterContainerPHID(), $adapter->getQueuedHarbormasterBuildRequests()); + + // Whether we queued any builds or not, any automatic buildable for this + // object is now done preparing builds and can transition into a + // completed status. + $buildables = id(new HarbormasterBuildableQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withManualBuildables(false) + ->withBuildablePHIDs(array($buildable_phid)) + ->execute(); + foreach ($buildables as $buildable) { + // If this buildable has already moved beyond preparation, we don't + // need to nudge it again. + if (!$buildable->isPreparing()) { + continue; + } + $buildable->sendMessage( + $this->getActor(), + HarbormasterMessageType::BUILDABLE_BUILD, + true); + } } $this->mustEncrypt = $adapter->getMustEncryptReasons(); From 6f508a22586991319a92f25300ea2085843d8ee2 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 12 Feb 2018 12:03:56 -0800 Subject: [PATCH 13/58] Update buildable containerPHIDs in a proper way via BuildWorker rather than via sneaky uncoordinated write Summary: Depends on D19065. Ref T13054. Instead of just updating `containerPHID` and hoping for the best, queue a proper BuildWorker to process a "your container has changed, update it" message. We also need to remove a (superfluous) `withContainerPHIDs()` when loading active diffs for a revision. Test Plan: - Without daemons, created a revision and saw builds stick in "preparing" with no container PHID, but also stay in draft mode. - With daemons, saw builds actually build and get the right container PHID. Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T13054 Differential Revision: https://secure.phabricator.com/D19066 --- .../editor/DifferentialTransactionEditor.php | 29 ++++++++++--------- .../storage/DifferentialRevision.php | 3 +- .../engine/HarbormasterBuildEngine.php | 19 +++++++++++- .../engine/HarbormasterMessageType.php | 1 + 4 files changed, 36 insertions(+), 16 deletions(-) diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index 063df6c602..a092bf2693 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -365,21 +365,22 @@ final class DifferentialTransactionEditor $diff->setRevisionID($object->getID()); $diff->save(); - // Update Harbormaster to set the containerPHID correctly for any - // existing buildables. We may otherwise have buildables stuck with - // the old (`null`) container. + // If there are any outstanding buildables for this diff, tell + // Harbormaster that their containers need to be updated. This is + // common, because `arc` creates buildables so it can upload lint + // and unit results. - // TODO: This is a bit iffy, maybe we can find a cleaner approach? - // In particular, this could (rarely) be overwritten by Harbormaster - // workers. - $table = new HarbormasterBuildable(); - $conn_w = $table->establishConnection('w'); - queryfx( - $conn_w, - 'UPDATE %T SET containerPHID = %s WHERE buildablePHID = %s', - $table->getTableName(), - $object->getPHID(), - $diff->getPHID()); + $buildables = id(new HarbormasterBuildableQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withManualBuildables(false) + ->withBuildablePHIDs(array($diff->getPHID())) + ->execute(); + foreach ($buildables as $buildable) { + $buildable->sendMessage( + $this->getActor(), + HarbormasterMessageType::BUILDABLE_CONTAINER, + true); + } return; } diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index e8fdf7e514..6813c124d7 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -743,9 +743,10 @@ final class DifferentialRevision extends DifferentialDAO public function loadActiveBuilds(PhabricatorUser $viewer) { $diff = $this->getActiveDiff(); + // NOTE: We can't use `withContainerPHIDs()` here because the container + // update in Harbormaster is not synchronous. $buildables = id(new HarbormasterBuildableQuery()) ->setViewer($viewer) - ->withContainerPHIDs(array($this->getPHID())) ->withBuildablePHIDs(array($diff->getPHID())) ->withManualBuildables(false) ->execute(); diff --git a/src/applications/harbormaster/engine/HarbormasterBuildEngine.php b/src/applications/harbormaster/engine/HarbormasterBuildEngine.php index ea20c64538..454fec7f37 100644 --- a/src/applications/harbormaster/engine/HarbormasterBuildEngine.php +++ b/src/applications/harbormaster/engine/HarbormasterBuildEngine.php @@ -447,11 +447,15 @@ final class HarbormasterBuildEngine extends Phobject { ->execute(); $done_preparing = false; + $update_container = false; foreach ($messages as $message) { switch ($message->getType()) { case HarbormasterMessageType::BUILDABLE_BUILD: $done_preparing = true; break; + case HarbormasterMessageType::BUILDABLE_CONTAINER: + $update_container = true; + break; default: break; } @@ -463,7 +467,6 @@ final class HarbormasterBuildEngine extends Phobject { // If we received a "build" command, all builds are scheduled and we can // move out of "preparing" into "building". - if ($done_preparing) { if ($buildable->isPreparing()) { $buildable @@ -472,6 +475,20 @@ final class HarbormasterBuildEngine extends Phobject { } } + // If we've been informed that the container for the buildable has + // changed, update it. + if ($update_container) { + $object = id(new PhabricatorObjectQuery()) + ->setViewer($viewer) + ->withPHIDs(array($buildable->getBuildablePHID())) + ->executeOne(); + if ($object) { + $buildable + ->setContainerPHID($object->getHarbormasterContainerPHID()) + ->save(); + } + } + // Don't update the buildable status if we're still preparing builds: more // builds may still be scheduled shortly, so even if every build we know // about so far has passed, that doesn't mean the buildable has actually diff --git a/src/applications/harbormaster/engine/HarbormasterMessageType.php b/src/applications/harbormaster/engine/HarbormasterMessageType.php index 8b3ebe2e6f..135fb23ffc 100644 --- a/src/applications/harbormaster/engine/HarbormasterMessageType.php +++ b/src/applications/harbormaster/engine/HarbormasterMessageType.php @@ -7,6 +7,7 @@ final class HarbormasterMessageType extends Phobject { const MESSAGE_WORK = 'work'; const BUILDABLE_BUILD = 'build'; + const BUILDABLE_CONTAINER = 'container'; public static function getAllMessages() { return array_keys(self::getMessageSpecifications()); From 11c9994134935ea6358f7714e5d4d4297a617b9c Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 12 Feb 2018 13:01:02 -0800 Subject: [PATCH 14/58] Allow "arc diff --plan-changes" to work with drafts enabled Summary: See PHI346. Ref T13054. If you have prototypes enabled on the server but use `master` / `stable` on the client and run `arc diff --plan-changes`, the transition is rejected because "Draft -> Changes Planned" isn't currently a legal transition. Allow this transition if not coming from the web UI (to keep it out of the dropdown). Test Plan: - Ran `arc diff --plan-changes` on `master`, got a "Changes Planned" revision instead of a validation error. - Ran `arc diff` without `--plan-changes`, got a draft, verified that "Plan Changes" still doesn't appear in the action dropdown. Maniphest Tasks: T13054 Differential Revision: https://secure.phabricator.com/D19067 --- ...DifferentialRevisionPlanChangesTransaction.php | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/applications/differential/xaction/DifferentialRevisionPlanChangesTransaction.php b/src/applications/differential/xaction/DifferentialRevisionPlanChangesTransaction.php index 231adc5bf8..e53426c8ba 100644 --- a/src/applications/differential/xaction/DifferentialRevisionPlanChangesTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionPlanChangesTransaction.php @@ -57,8 +57,19 @@ final class DifferentialRevisionPlanChangesTransaction protected function validateAction($object, PhabricatorUser $viewer) { if ($object->isDraft()) { - throw new Exception( - pht('You can not plan changes to a draft revision.')); + + // See PHI346. Until the "Draft" state fully unprototypes, allow drafts + // to be moved to "changes planned" via the API. This preserves the + // behavior of "arc diff --plan-changes". We still prevent this + // transition from the web UI. + // TODO: Remove this once drafts leave prototype. + + $editor = $this->getEditor(); + $type_web = PhabricatorWebContentSource::SOURCECONST; + if ($editor->getContentSource()->getSource() == $type_web) { + throw new Exception( + pht('You can not plan changes to a draft revision.')); + } } if ($object->isChangePlanned()) { From 894e9dd852fa0fe896c7c7932af49916d3fbacfd Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 12 Feb 2018 15:31:16 -0800 Subject: [PATCH 15/58] Update a handful of missed HarbormasterBuildableStatus constants Summary: See . Test Plan: Used `grep` more carefully. Differential Revision: https://secure.phabricator.com/D19068 --- .../storage/PhabricatorApplicationTransaction.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php index 7682ab5ebb..015e601b02 100644 --- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php +++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php @@ -1236,21 +1236,21 @@ abstract class PhabricatorApplicationTransaction } case PhabricatorTransactions::TYPE_BUILDABLE: switch ($this->getNewValue()) { - case HarbormasterBuildable::STATUS_BUILDING: + case HarbormasterBuildableStatus::STATUS_BUILDING: return pht( '%s started building %s for %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink( $this->getMetadataValue('harbormaster:buildablePHID')), $this->renderHandleLink($object_phid)); - case HarbormasterBuildable::STATUS_PASSED: + case HarbormasterBuildableStatus::STATUS_PASSED: return pht( '%s completed building %s for %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink( $this->getMetadataValue('harbormaster:buildablePHID')), $this->renderHandleLink($object_phid)); - case HarbormasterBuildable::STATUS_FAILED: + case HarbormasterBuildableStatus::STATUS_FAILED: return pht( '%s failed to build %s for %s.', $this->renderHandleLink($author_phid), @@ -1418,9 +1418,9 @@ abstract class PhabricatorApplicationTransaction return pht('Changed Subscribers'); case PhabricatorTransactions::TYPE_BUILDABLE: switch ($this->getNewValue()) { - case HarbormasterBuildable::STATUS_PASSED: + case HarbormasterBuildableStatus::STATUS_PASSED: return pht('Build Passed'); - case HarbormasterBuildable::STATUS_FAILED: + case HarbormasterBuildableStatus::STATUS_FAILED: return pht('Build Failed'); default: return pht('Build Status'); From 6a4d5ce3c93e79d26ccd349bd5131f4cc61749d4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 12 Feb 2018 15:34:09 -0800 Subject: [PATCH 16/58] Don't allow Herald Diff rules to "Call Webhooks" Summary: Like "Commit Hook" rules, these also fire oddly and don't have an object PHID or a list of transactions. Test Plan: Verified that "Call Webhooks" was no longer available from Diff rules, but still available from other rule types. Differential Revision: https://secure.phabricator.com/D19069 --- .../differential/herald/HeraldDifferentialDiffAdapter.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/applications/differential/herald/HeraldDifferentialDiffAdapter.php b/src/applications/differential/herald/HeraldDifferentialDiffAdapter.php index a6b2e7c36e..966b306580 100644 --- a/src/applications/differential/herald/HeraldDifferentialDiffAdapter.php +++ b/src/applications/differential/herald/HeraldDifferentialDiffAdapter.php @@ -57,4 +57,8 @@ final class HeraldDifferentialDiffAdapter extends HeraldDifferentialAdapter { return pht('New Diff'); } + public function supportsWebhooks() { + return false; + } + } From 4dd32dca3e8f48197438fda1f5a7e8c77fd13216 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 13 Feb 2018 03:08:58 -0800 Subject: [PATCH 17/58] When a Drydock Blueprint promises us a resource but can't deliver, continue believing in it Summary: Ref T13073. When a Blueprint says it will be able to allocate a resource but then throws an exception while attempting that allocation, we currently fail the lease permanently. This is excessively harsh. This blueprint may have the best of intentions and have encountered a legitimately unforseeable failure (like a `vm.new` call to build a VM failed) and be able to succeed in the future. Even if this blueprint is a dirty liar, other blueprints (or existing resources) may be able to satisfy the lease in the future. Even if every blueprint is implemented incorrectly, leaving the lease alive lets it converge to success after the blueprints are fixed. Instead of failing, log the issue and yield. (In the future, it might make sense to distinguish more narrowly between "actually, all the resources are used up" and all other failure types, since the former is likely more routine and less concerning.) Test Plan: - Wrote a broken `Hoax` blueprint which always claims it can allocate but never actually allocates (just `throw` in `allocateResource()`). - Used `bin/phd drydock lease` to acquire a Hoax lease. - Before patch: lease abruptly failed permanently. - After patch: lease yields after allocation fails. {F5427747} Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T13073 Differential Revision: https://secure.phabricator.com/D19070 --- src/__phutil_library_map__.php | 4 ++ .../DrydockLeaseAllocationFailureLogType.php | 26 +++++++++++++ ...rydockResourceAllocationFailureLogType.php | 26 +++++++++++++ .../worker/DrydockLeaseUpdateWorker.php | 38 ++++++++++++++++++- 4 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 src/applications/drydock/logtype/DrydockLeaseAllocationFailureLogType.php create mode 100644 src/applications/drydock/logtype/DrydockResourceAllocationFailureLogType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 59b375eb55..1656d1f398 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1055,6 +1055,7 @@ phutil_register_library_map(array( 'DrydockLeaseActivatedLogType' => 'applications/drydock/logtype/DrydockLeaseActivatedLogType.php', 'DrydockLeaseActivationFailureLogType' => 'applications/drydock/logtype/DrydockLeaseActivationFailureLogType.php', 'DrydockLeaseActivationYieldLogType' => 'applications/drydock/logtype/DrydockLeaseActivationYieldLogType.php', + 'DrydockLeaseAllocationFailureLogType' => 'applications/drydock/logtype/DrydockLeaseAllocationFailureLogType.php', 'DrydockLeaseController' => 'applications/drydock/controller/DrydockLeaseController.php', 'DrydockLeaseDatasource' => 'applications/drydock/typeahead/DrydockLeaseDatasource.php', 'DrydockLeaseDestroyedLogType' => 'applications/drydock/logtype/DrydockLeaseDestroyedLogType.php', @@ -1106,6 +1107,7 @@ phutil_register_library_map(array( 'DrydockResource' => 'applications/drydock/storage/DrydockResource.php', 'DrydockResourceActivationFailureLogType' => 'applications/drydock/logtype/DrydockResourceActivationFailureLogType.php', 'DrydockResourceActivationYieldLogType' => 'applications/drydock/logtype/DrydockResourceActivationYieldLogType.php', + 'DrydockResourceAllocationFailureLogType' => 'applications/drydock/logtype/DrydockResourceAllocationFailureLogType.php', 'DrydockResourceController' => 'applications/drydock/controller/DrydockResourceController.php', 'DrydockResourceDatasource' => 'applications/drydock/typeahead/DrydockResourceDatasource.php', 'DrydockResourceListController' => 'applications/drydock/controller/DrydockResourceListController.php', @@ -6273,6 +6275,7 @@ phutil_register_library_map(array( 'DrydockLeaseActivatedLogType' => 'DrydockLogType', 'DrydockLeaseActivationFailureLogType' => 'DrydockLogType', 'DrydockLeaseActivationYieldLogType' => 'DrydockLogType', + 'DrydockLeaseAllocationFailureLogType' => 'DrydockLogType', 'DrydockLeaseController' => 'DrydockController', 'DrydockLeaseDatasource' => 'PhabricatorTypeaheadDatasource', 'DrydockLeaseDestroyedLogType' => 'DrydockLogType', @@ -6333,6 +6336,7 @@ phutil_register_library_map(array( ), 'DrydockResourceActivationFailureLogType' => 'DrydockLogType', 'DrydockResourceActivationYieldLogType' => 'DrydockLogType', + 'DrydockResourceAllocationFailureLogType' => 'DrydockLogType', 'DrydockResourceController' => 'DrydockController', 'DrydockResourceDatasource' => 'PhabricatorTypeaheadDatasource', 'DrydockResourceListController' => 'DrydockResourceController', diff --git a/src/applications/drydock/logtype/DrydockLeaseAllocationFailureLogType.php b/src/applications/drydock/logtype/DrydockLeaseAllocationFailureLogType.php new file mode 100644 index 0000000000..33ad881f4f --- /dev/null +++ b/src/applications/drydock/logtype/DrydockLeaseAllocationFailureLogType.php @@ -0,0 +1,26 @@ +logEvent( + DrydockResourceAllocationFailureLogType::LOGCONST, + array( + 'class' => get_class($ex), + 'message' => $ex->getMessage(), + )); + $exceptions[] = $ex; } } if (!$resources) { - throw new PhutilAggregateException( + // If one or more blueprints claimed that they would be able to + // allocate resources but none are actually able to allocate resources, + // log the failure and yield so we try again soon. + + // This can happen if some unexpected issue occurs during allocation + // (for example, a call to build a VM fails for some reason) or if we + // raced another allocator and the blueprint is now full. + + $ex = new PhutilAggregateException( pht( 'All blueprints failed to allocate a suitable new resource when '. - 'trying to allocate lease "%s".', + 'trying to allocate lease ("%s").', $lease->getPHID()), $exceptions); + + $lease->logEvent( + DrydockLeaseAllocationFailureLogType::LOGCONST, + array( + 'class' => get_class($ex), + 'message' => $ex->getMessage(), + )); + + throw new PhabricatorWorkerYieldException(15); } $resources = $this->removeUnacquirableResources($resources, $lease); From 3ec80a36dbeea5631c84fb1054b7d438f16df71f Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 13 Feb 2018 03:34:51 -0800 Subject: [PATCH 18/58] In Drydock log views, respect newlines Summary: Depends on D19070. Ref T13073. Some messages contain an interesting story or a clever anecdote. Respect newlines during rendering to preserve authorial intent. Test Plan: Viewed a message with linebreaks and could still read it. {F5427754} Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T13073 Differential Revision: https://secure.phabricator.com/D19071 --- src/applications/drydock/view/DrydockLogListView.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/applications/drydock/view/DrydockLogListView.php b/src/applications/drydock/view/DrydockLogListView.php index 845457f9bc..cb66813507 100644 --- a/src/applications/drydock/view/DrydockLogListView.php +++ b/src/applications/drydock/view/DrydockLogListView.php @@ -53,6 +53,7 @@ final class DrydockLogListView extends AphrontView { $type = $type_object->getLogTypeName(); $icon = $type_object->getLogTypeIcon($log_data); $data = $type_object->renderLog($log_data); + $data = phutil_escape_html_newlines($data); } else { $type = pht('', $type_key); $data = null; From b833e324bd4a4a5a9d0190fa3db06fdf6a5642f5 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 13 Feb 2018 03:47:45 -0800 Subject: [PATCH 19/58] While waiting for a "bin/drydock" lease to activate, entertain the user with log output Summary: Depends on D19071. Ref T13073. While the daemons are supposedly doing things, show the user any logs they generate. There's often something relevant but unearthing it can be involved. Test Plan: {F5427773} Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T13073 Differential Revision: https://secure.phabricator.com/D19072 --- .../DrydockManagementLeaseWorkflow.php | 78 ++++++++++++++++++- .../drydock/storage/DrydockLease.php | 32 -------- 2 files changed, 77 insertions(+), 33 deletions(-) diff --git a/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php b/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php index 4992833c50..069f82339b 100644 --- a/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php +++ b/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php @@ -90,7 +90,7 @@ final class DrydockManagementLeaseWorkflow "%s\n", pht('Waiting for daemons to activate lease...')); - $lease->waitUntilActive(); + $this->waitUntilActive($lease); echo tsprintf( "%s\n", @@ -99,4 +99,80 @@ final class DrydockManagementLeaseWorkflow return 0; } + + private function waitUntilActive(DrydockLease $lease) { + $viewer = $this->getViewer(); + + $log_cursor = 0; + $log_types = DrydockLogType::getAllLogTypes(); + + $is_active = false; + while (!$is_active) { + $lease->reload(); + + // While we're waiting, show the user any logs which the daemons have + // generated to give them some clue about what's going on. + $logs = id(new DrydockLogQuery()) + ->setViewer($viewer) + ->withLeasePHIDs(array($lease->getPHID())) + ->setBeforeID($log_cursor) + ->execute(); + if ($logs) { + $logs = mpull($logs, null, 'getID'); + ksort($logs); + $log_cursor = last_key($logs); + } + + foreach ($logs as $log) { + $type_key = $log->getType(); + if (isset($log_types[$type_key])) { + $type_object = id(clone $log_types[$type_key]) + ->setLog($log) + ->setViewer($viewer); + + $log_data = $log->getData(); + + $type = $type_object->getLogTypeName(); + $data = $type_object->renderLog($log_data); + } else { + $type = pht('Unknown ("%s")', $type_key); + $data = null; + } + + echo tsprintf( + "<%s> %B\n", + $type, + $data); + } + + $status = $lease->getStatus(); + + switch ($status) { + case DrydockLeaseStatus::STATUS_ACTIVE: + $is_active = true; + break; + case DrydockLeaseStatus::STATUS_RELEASED: + throw new Exception(pht('Lease has already been released!')); + case DrydockLeaseStatus::STATUS_DESTROYED: + throw new Exception(pht('Lease has already been destroyed!')); + case DrydockLeaseStatus::STATUS_BROKEN: + throw new Exception(pht('Lease has been broken!')); + case DrydockLeaseStatus::STATUS_PENDING: + case DrydockLeaseStatus::STATUS_ACQUIRED: + break; + default: + throw new Exception( + pht( + 'Lease has unknown status "%s".', + $status)); + } + + if ($is_active) { + break; + } else { + sleep(1); + } + } + } + } diff --git a/src/applications/drydock/storage/DrydockLease.php b/src/applications/drydock/storage/DrydockLease.php index e60529afe4..e8cdc5b802 100644 --- a/src/applications/drydock/storage/DrydockLease.php +++ b/src/applications/drydock/storage/DrydockLease.php @@ -194,38 +194,6 @@ final class DrydockLease extends DrydockDAO return false; } - public function waitUntilActive() { - while (true) { - $lease = $this->reload(); - if (!$lease) { - throw new Exception(pht('Failed to reload lease.')); - } - - $status = $lease->getStatus(); - - switch ($status) { - case DrydockLeaseStatus::STATUS_ACTIVE: - return; - case DrydockLeaseStatus::STATUS_RELEASED: - throw new Exception(pht('Lease has already been released!')); - case DrydockLeaseStatus::STATUS_DESTROYED: - throw new Exception(pht('Lease has already been destroyed!')); - case DrydockLeaseStatus::STATUS_BROKEN: - throw new Exception(pht('Lease has been broken!')); - case DrydockLeaseStatus::STATUS_PENDING: - case DrydockLeaseStatus::STATUS_ACQUIRED: - break; - default: - throw new Exception( - pht( - 'Lease has unknown status "%s".', - $status)); - } - - sleep(1); - } - } - public function setActivateWhenAcquired($activate) { $this->activateWhenAcquired = true; return $this; From 07028cfc30cf717e4e7c4f1deb50713e4bac37e6 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 13 Feb 2018 04:14:48 -0800 Subject: [PATCH 20/58] When `bin/drydock lease` is interrupted, release leases Summary: Depends on D19072. Ref T13073. Currently, you can leave leases stranded by using `^C` to interrupt the script. Handle signals and release leases on destruction if they haven't activated yet. Also, print out more useful information before and after activation. Test Plan: Mashed ^C while runnning `bin/drydock lease ... --trace`, saw the lease release. Subscribers: yelirekim, PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T13073 Differential Revision: https://secure.phabricator.com/D19073 --- scripts/drydock/drydock_control.php | 2 +- scripts/init/init-script-with-signals.php | 11 ++++++ ...dockWorkingCopyBlueprintImplementation.php | 2 +- .../DrydockManagementLeaseWorkflow.php | 36 +++++++++++++++++-- .../drydock/storage/DrydockLease.php | 9 +++-- ...DrydockRepositoryOperationUpdateWorker.php | 2 +- 6 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 scripts/init/init-script-with-signals.php diff --git a/scripts/drydock/drydock_control.php b/scripts/drydock/drydock_control.php index 21ff9f8ee8..f22032b286 100755 --- a/scripts/drydock/drydock_control.php +++ b/scripts/drydock/drydock_control.php @@ -2,7 +2,7 @@ setTagline(pht('manage drydock software resources')); diff --git a/scripts/init/init-script-with-signals.php b/scripts/init/init-script-with-signals.php new file mode 100644 index 0000000000..a479c4b758 --- /dev/null +++ b/scripts/init/init-script-with-signals.php @@ -0,0 +1,11 @@ +releaseOnDestruction(); + $lease->setReleaseOnDestruction(true); if ($lease->isActive()) { // Destroy the working copy on disk. diff --git a/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php b/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php index 069f82339b..3a27fe9045 100644 --- a/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php +++ b/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php @@ -84,21 +84,51 @@ final class DrydockManagementLeaseWorkflow $lease->setUntil($until); } + // If something fatals or the user interrupts the process (for example, + // with "^C"), release the lease. We'll cancel this below, if the lease + // actually activates. + $lease->setReleaseOnDestruction(true); + + // TODO: This would probably be better handled with PhutilSignalRouter, + // but it currently doesn't route SIGINT. We're initializing it to setup + // SIGTERM handling and make eventual migration easier. + $router = PhutilSignalRouter::getRouter(); + pcntl_signal(SIGINT, array($this, 'didReceiveInterrupt')); + + $t_start = microtime(true); $lease->queueForActivation(); echo tsprintf( - "%s\n", + "%s\n\n __%s__\n\n%s\n", + pht('Queued lease for activation:'), + PhabricatorEnv::getProductionURI($lease->getURI()), pht('Waiting for daemons to activate lease...')); $this->waitUntilActive($lease); + // Now that we've survived activation and the lease is good, make it + // durable. + $lease->setReleaseOnDestruction(false); + $t_end = microtime(true); + echo tsprintf( - "%s\n", - pht('Activated lease "%s".', $lease->getID())); + "%s\n\n %s\n\n%s\n", + pht( + 'Activation complete. This lease is permanent until manually '. + 'released with:'), + pht('$ ./bin/drydock release-lease --id %d', $lease->getID()), + pht( + 'Lease activated in %sms.', + new PhutilNumber((int)(($t_end - $t_start) * 1000)))); return 0; } + public function didReceiveInterrupt($signo) { + // Doing this makes us run destructors, particularly the "release on + // destruction" trigger on the lease. + exit(128 + $signo); + } private function waitUntilActive(DrydockLease $lease) { $viewer = $this->getViewer(); diff --git a/src/applications/drydock/storage/DrydockLease.php b/src/applications/drydock/storage/DrydockLease.php index e8cdc5b802..e217223aa8 100644 --- a/src/applications/drydock/storage/DrydockLease.php +++ b/src/applications/drydock/storage/DrydockLease.php @@ -36,8 +36,8 @@ final class DrydockLease extends DrydockDAO * a lease, as you don't need to explicitly handle exceptions to properly * release the lease. */ - public function releaseOnDestruction() { - $this->releaseOnDestruction = true; + public function setReleaseOnDestruction($release) { + $this->releaseOnDestruction = $release; return $this; } @@ -436,6 +436,11 @@ final class DrydockLease extends DrydockDAO return $this; } + public function getURI() { + $id = $this->getID(); + return "/drydock/lease/{$id}/"; + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/drydock/worker/DrydockRepositoryOperationUpdateWorker.php b/src/applications/drydock/worker/DrydockRepositoryOperationUpdateWorker.php index 9038b3b6d7..dfd603fd12 100644 --- a/src/applications/drydock/worker/DrydockRepositoryOperationUpdateWorker.php +++ b/src/applications/drydock/worker/DrydockRepositoryOperationUpdateWorker.php @@ -62,7 +62,7 @@ final class DrydockRepositoryOperationUpdateWorker DrydockCommandInterface::INTERFACE_TYPE); // No matter what happens here, destroy the lease away once we're done. - $lease->releaseOnDestruction(true); + $lease->setReleaseOnDestruction(true); $operation->applyOperation($interface); From 27c3793d4087a51e1da30e9d5fa28ebb8faf5e8a Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 13 Feb 2018 04:55:23 -0800 Subject: [PATCH 21/58] Give Drydock Leases more modern status treatment Summary: Depends on D19073. Ref T13073. Give leases a normal header tag and try to wrangle their status constants a bit. Also, try to capture the "status class" pattern a bit. Since we target PHP 5.2.3 we can't use `static::` so the actual subclass is kind of a mess. Not exactly sure if I want to stick with this or not. We could consider targeting PHP 5.3.0 instead to get `static::` / late static binding. Test Plan: Viewed leases and lease lists, saw better and more conventional status information. Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T13073 Differential Revision: https://secure.phabricator.com/D19074 --- src/__phutil_library_map__.php | 4 +- .../drydock/constants/DrydockLeaseStatus.php | 106 ++++++++++++++++-- .../controller/DrydockLeaseViewController.php | 17 ++- .../drydock/storage/DrydockLease.php | 83 +++++++------- .../drydock/view/DrydockLeaseListView.php | 20 ++-- .../status/PhabricatorObjectStatus.php | 84 ++++++++++++++ 6 files changed, 242 insertions(+), 72 deletions(-) create mode 100644 src/infrastructure/status/PhabricatorObjectStatus.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 1656d1f398..9a77727720 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3416,6 +3416,7 @@ phutil_register_library_map(array( 'PhabricatorObjectRelationshipSource' => 'applications/search/relationship/PhabricatorObjectRelationshipSource.php', 'PhabricatorObjectRemarkupRule' => 'infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php', 'PhabricatorObjectSelectorDialog' => 'view/control/PhabricatorObjectSelectorDialog.php', + 'PhabricatorObjectStatus' => 'infrastructure/status/PhabricatorObjectStatus.php', 'PhabricatorOffsetPagedQuery' => 'infrastructure/query/PhabricatorOffsetPagedQuery.php', 'PhabricatorOldWorldContentSource' => 'infrastructure/contentsource/PhabricatorOldWorldContentSource.php', 'PhabricatorOlderInlinesSetting' => 'applications/settings/setting/PhabricatorOlderInlinesSetting.php', @@ -6290,7 +6291,7 @@ phutil_register_library_map(array( 'DrydockLeaseReleaseController' => 'DrydockLeaseController', 'DrydockLeaseReleasedLogType' => 'DrydockLogType', 'DrydockLeaseSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'DrydockLeaseStatus' => 'DrydockConstants', + 'DrydockLeaseStatus' => 'PhabricatorObjectStatus', 'DrydockLeaseUpdateWorker' => 'DrydockWorker', 'DrydockLeaseViewController' => 'DrydockLeaseController', 'DrydockLeaseWaitingForResourcesLogType' => 'DrydockLogType', @@ -8994,6 +8995,7 @@ phutil_register_library_map(array( 'PhabricatorObjectRelationshipSource' => 'Phobject', 'PhabricatorObjectRemarkupRule' => 'PhutilRemarkupRule', 'PhabricatorObjectSelectorDialog' => 'Phobject', + 'PhabricatorObjectStatus' => 'Phobject', 'PhabricatorOffsetPagedQuery' => 'PhabricatorQuery', 'PhabricatorOldWorldContentSource' => 'PhabricatorContentSource', 'PhabricatorOlderInlinesSetting' => 'PhabricatorSelectSetting', diff --git a/src/applications/drydock/constants/DrydockLeaseStatus.php b/src/applications/drydock/constants/DrydockLeaseStatus.php index f37e4ab9be..e5721e9d7a 100644 --- a/src/applications/drydock/constants/DrydockLeaseStatus.php +++ b/src/applications/drydock/constants/DrydockLeaseStatus.php @@ -1,6 +1,7 @@ getStatusSpecification($key)); + } + public static function getStatusMap() { - return array( - self::STATUS_PENDING => pht('Pending'), - self::STATUS_ACQUIRED => pht('Acquired'), - self::STATUS_ACTIVE => pht('Active'), - self::STATUS_RELEASED => pht('Released'), - self::STATUS_BROKEN => pht('Broken'), - self::STATUS_DESTROYED => pht('Destroyed'), - ); + $map = id(new self())->getStatusSpecifications(); + return ipull($map, 'name', 'key'); } public static function getNameForStatus($status) { - $map = self::getStatusMap(); - return idx($map, $status, pht('Unknown')); + $map = id(new self())->getStatusSpecification($status); + return $map['name']; } public static function getAllStatuses() { - return array_keys(self::getStatusMap()); + return array_keys(id(new self())->getStatusSpecifications()); + } + + public function isActivating() { + return $this->getStatusProperty('isActivating'); + } + + public function isActive() { + return ($this->getKey() === self::STATUS_ACTIVE); + } + + public function canRelease() { + return $this->getStatusProperty('isReleasable'); + } + + public function canReceiveCommands() { + return $this->getStatusProperty('isCommandable'); + } + + protected function newStatusSpecifications() { + return array( + array( + 'key' => self::STATUS_PENDING, + 'name' => pht('Pending'), + 'icon' => 'fa-clock-o', + 'color' => 'blue', + 'isReleasable' => true, + 'isCommandable' => true, + 'isActivating' => true, + ), + array( + 'key' => self::STATUS_ACQUIRED, + 'name' => pht('Acquired'), + 'icon' => 'fa-refresh', + 'color' => 'blue', + 'isReleasable' => true, + 'isCommandable' => true, + 'isActivating' => true, + ), + array( + 'key' => self::STATUS_ACTIVE, + 'name' => pht('Active'), + 'icon' => 'fa-check', + 'color' => 'green', + 'isReleasable' => true, + 'isCommandable' => true, + 'isActivating' => false, + ), + array( + 'key' => self::STATUS_RELEASED, + 'name' => pht('Released'), + 'icon' => 'fa-circle-o', + 'color' => 'blue', + 'isReleasable' => false, + 'isCommandable' => false, + 'isActivating' => false, + ), + array( + 'key' => self::STATUS_BROKEN, + 'name' => pht('Broken'), + 'icon' => 'fa-times', + 'color' => 'indigo', + 'isReleasable' => true, + 'isCommandable' => true, + 'isActivating' => false, + ), + array( + 'key' => self::STATUS_DESTROYED, + 'name' => pht('Destroyed'), + 'icon' => 'fa-times', + 'color' => 'red', + 'isReleasable' => false, + 'isCommandable' => false, + 'isActivating' => false, + ), + ); + } + + protected function newUnknownStatusSpecification($status) { + return array( + 'isReleasable' => false, + 'isCommandable' => false, + 'isActivating' => false, + ); } } diff --git a/src/applications/drydock/controller/DrydockLeaseViewController.php b/src/applications/drydock/controller/DrydockLeaseViewController.php index 1583d28df2..91a911277f 100644 --- a/src/applications/drydock/controller/DrydockLeaseViewController.php +++ b/src/applications/drydock/controller/DrydockLeaseViewController.php @@ -22,10 +22,19 @@ final class DrydockLeaseViewController extends DrydockLeaseController { $header = id(new PHUIHeaderView()) ->setHeader($title) - ->setHeaderIcon('fa-link'); + ->setHeaderIcon('fa-link') + ->setStatus( + $lease->getStatusIcon(), + $lease->getStatusColor(), + $lease->getStatusDisplayName()); if ($lease->isReleasing()) { - $header->setStatus('fa-exclamation-triangle', 'red', pht('Releasing')); + $header->addTag( + id(new PHUITagView()) + ->setType(PHUITagView::TYPE_SHADE) + ->setIcon('fa-exclamation-triangle') + ->setColor('red') + ->setName('Releasing')); } $curtain = $this->buildCurtain($lease); @@ -118,10 +127,6 @@ final class DrydockLeaseViewController extends DrydockLeaseController { $view = new PHUIPropertyListView(); - $view->addProperty( - pht('Status'), - DrydockLeaseStatus::getNameForStatus($lease->getStatus())); - $view->addProperty( pht('Resource Type'), $lease->getResourceType()); diff --git a/src/applications/drydock/storage/DrydockLease.php b/src/applications/drydock/storage/DrydockLease.php index e217223aa8..84c6a702af 100644 --- a/src/applications/drydock/storage/DrydockLease.php +++ b/src/applications/drydock/storage/DrydockLease.php @@ -175,25 +175,6 @@ final class DrydockLease extends DrydockDAO return $this; } - public function isActivating() { - switch ($this->getStatus()) { - case DrydockLeaseStatus::STATUS_PENDING: - case DrydockLeaseStatus::STATUS_ACQUIRED: - return true; - } - - return false; - } - - public function isActive() { - switch ($this->getStatus()) { - case DrydockLeaseStatus::STATUS_ACTIVE: - return true; - } - - return false; - } - public function setActivateWhenAcquired($activate) { $this->activateWhenAcquired = true; return $this; @@ -325,30 +306,6 @@ final class DrydockLease extends DrydockDAO return $this->isActivated; } - public function canRelease() { - if (!$this->getID()) { - return false; - } - - switch ($this->getStatus()) { - case DrydockLeaseStatus::STATUS_RELEASED: - case DrydockLeaseStatus::STATUS_DESTROYED: - return false; - default: - return true; - } - } - - public function canReceiveCommands() { - switch ($this->getStatus()) { - case DrydockLeaseStatus::STATUS_RELEASED: - case DrydockLeaseStatus::STATUS_DESTROYED: - return false; - default: - return true; - } - } - public function scheduleUpdate($epoch = null) { PhabricatorWorker::scheduleTask( 'DrydockLeaseUpdateWorker', @@ -442,6 +399,46 @@ final class DrydockLease extends DrydockDAO } +/* -( Status )------------------------------------------------------------- */ + + + public function getStatusObject() { + return DrydockLeaseStatus::newStatusObject($this->getStatus()); + } + + public function getStatusIcon() { + return $this->getStatusObject()->getIcon(); + } + + public function getStatusColor() { + return $this->getStatusObject()->getColor(); + } + + public function getStatusDisplayName() { + return $this->getStatusObject()->getDisplayName(); + } + + public function isActivating() { + return $this->getStatusObject()->isActivating(); + } + + public function isActive() { + return $this->getStatusObject()->isActive(); + } + + public function canRelease() { + if (!$this->getID()) { + return false; + } + + return $this->getStatusObject()->canRelease(); + } + + public function canReceiveCommands() { + return $this->getStatusObject()->canReceiveCommands(); + } + + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/drydock/view/DrydockLeaseListView.php b/src/applications/drydock/view/DrydockLeaseListView.php index 8c161b00ce..fb11457e86 100644 --- a/src/applications/drydock/view/DrydockLeaseListView.php +++ b/src/applications/drydock/view/DrydockLeaseListView.php @@ -26,20 +26,20 @@ final class DrydockLeaseListView extends AphrontView { if ($resource_phid) { $item->addAttribute( $viewer->renderHandle($resource_phid)); + } else { + $item->addAttribute( + pht( + 'Resource: %s', + $lease->getResourceType())); } - $status = DrydockLeaseStatus::getNameForStatus($lease->getStatus()); - $item->addAttribute($status); $item->setEpoch($lease->getDateCreated()); - // TODO: Tailor this for clarity. - if ($lease->isActivating()) { - $item->setStatusIcon('fa-dot-circle-o yellow'); - } else if ($lease->isActive()) { - $item->setStatusIcon('fa-dot-circle-o green'); - } else { - $item->setStatusIcon('fa-dot-circle-o red'); - } + $icon = $lease->getStatusIcon(); + $color = $lease->getStatusColor(); + $label = $lease->getStatusDisplayName(); + + $item->setStatusIcon("{$icon} {$color}", $label); $view->addItem($item); } diff --git a/src/infrastructure/status/PhabricatorObjectStatus.php b/src/infrastructure/status/PhabricatorObjectStatus.php new file mode 100644 index 0000000000..27dec48dc9 --- /dev/null +++ b/src/infrastructure/status/PhabricatorObjectStatus.php @@ -0,0 +1,84 @@ +key = $key; + $this->properties = $properties; + } + + protected function getStatusProperty($key) { + if (!array_key_exists($key, $this->properties)) { + throw new Exception( + pht( + 'Attempting to access unknown status property ("%s").', + $key)); + } + + return $this->properties[$key]; + } + + public function getKey() { + return $this->key; + } + + public function getIcon() { + return $this->getStatusProperty('icon'); + } + + public function getDisplayName() { + return $this->getStatusProperty('name'); + } + + public function getColor() { + return $this->getStatusProperty('color'); + } + + protected function getStatusSpecification($status) { + $map = self::getStatusSpecifications(); + if (isset($map[$status])) { + return $map[$status]; + } + + return array( + 'key' => $status, + 'name' => pht('Unknown ("%s")', $status), + 'icon' => 'fa-question-circle', + 'color' => 'indigo', + ) + $this->newUnknownStatusSpecification($status); + } + + protected function getStatusSpecifications() { + $map = $this->newStatusSpecifications(); + + $result = array(); + foreach ($map as $item) { + if (!array_key_exists('key', $item)) { + throw new Exception(pht('Status specification has no "key".')); + } + + $key = $item['key']; + if (isset($result[$key])) { + throw new Exception( + pht( + 'Multiple status definitions share the same key ("%s").', + $key)); + } + + $result[$key] = $item; + } + + return $result; + } + + abstract protected function newStatusSpecifications(); + + protected function newUnknownStatusSpecification($status) { + return array(); + } + +} From 06bbf237fe56e8ee6ee64cbba7dd31fd658f1147 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 13 Feb 2018 05:31:29 -0800 Subject: [PATCH 22/58] Give Drydock Resources more modern status treatment Summary: Ref T13073. Depends on D19074. Update icons and UI for resource status. Test Plan: Viewed resources in detail view and list view, saw better status icons. Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T13073 Differential Revision: https://secure.phabricator.com/D19075 --- src/__phutil_library_map__.php | 4 +- .../drydock/constants/DrydockConstants.php | 3 - .../drydock/constants/DrydockLeaseStatus.php | 2 +- .../constants/DrydockResourceStatus.php | 86 ++++++++++++++++--- .../DrydockResourceViewController.php | 19 ++-- .../drydock/storage/DrydockResource.php | 62 ++++++------- .../drydock/view/DrydockResourceListView.php | 20 +---- 7 files changed, 124 insertions(+), 72 deletions(-) delete mode 100644 src/applications/drydock/constants/DrydockConstants.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 9a77727720..116aa31629 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1041,7 +1041,6 @@ phutil_register_library_map(array( 'DrydockCommandInterface' => 'applications/drydock/interface/command/DrydockCommandInterface.php', 'DrydockCommandQuery' => 'applications/drydock/query/DrydockCommandQuery.php', 'DrydockConsoleController' => 'applications/drydock/controller/DrydockConsoleController.php', - 'DrydockConstants' => 'applications/drydock/constants/DrydockConstants.php', 'DrydockController' => 'applications/drydock/controller/DrydockController.php', 'DrydockCreateBlueprintsCapability' => 'applications/drydock/capability/DrydockCreateBlueprintsCapability.php', 'DrydockDAO' => 'applications/drydock/storage/DrydockDAO.php', @@ -6259,7 +6258,6 @@ phutil_register_library_map(array( 'DrydockCommandInterface' => 'DrydockInterface', 'DrydockCommandQuery' => 'DrydockQuery', 'DrydockConsoleController' => 'DrydockController', - 'DrydockConstants' => 'Phobject', 'DrydockController' => 'PhabricatorController', 'DrydockCreateBlueprintsCapability' => 'PhabricatorPolicyCapability', 'DrydockDAO' => 'PhabricatorLiskDAO', @@ -6347,7 +6345,7 @@ phutil_register_library_map(array( 'DrydockResourceReclaimLogType' => 'DrydockLogType', 'DrydockResourceReleaseController' => 'DrydockResourceController', 'DrydockResourceSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'DrydockResourceStatus' => 'DrydockConstants', + 'DrydockResourceStatus' => 'PhabricatorObjectStatus', 'DrydockResourceUpdateWorker' => 'DrydockWorker', 'DrydockResourceViewController' => 'DrydockResourceController', 'DrydockSFTPFilesystemInterface' => 'DrydockFilesystemInterface', diff --git a/src/applications/drydock/constants/DrydockConstants.php b/src/applications/drydock/constants/DrydockConstants.php deleted file mode 100644 index 48d06329bf..0000000000 --- a/src/applications/drydock/constants/DrydockConstants.php +++ /dev/null @@ -1,3 +0,0 @@ - self::STATUS_DESTROYED, 'name' => pht('Destroyed'), 'icon' => 'fa-times', - 'color' => 'red', + 'color' => 'grey', 'isReleasable' => false, 'isCommandable' => false, 'isActivating' => false, diff --git a/src/applications/drydock/constants/DrydockResourceStatus.php b/src/applications/drydock/constants/DrydockResourceStatus.php index d8a860d6a0..197a220630 100644 --- a/src/applications/drydock/constants/DrydockResourceStatus.php +++ b/src/applications/drydock/constants/DrydockResourceStatus.php @@ -1,6 +1,7 @@ getStatusSpecification($key)); + } + public static function getStatusMap() { - return array( - self::STATUS_PENDING => pht('Pending'), - self::STATUS_ACTIVE => pht('Active'), - self::STATUS_RELEASED => pht('Released'), - self::STATUS_BROKEN => pht('Broken'), - self::STATUS_DESTROYED => pht('Destroyed'), - ); + $map = id(new self())->getStatusSpecifications(); + return ipull($map, 'name', 'key'); } public static function getNameForStatus($status) { - $map = self::getStatusMap(); - return idx($map, $status, pht('Unknown')); + $map = id(new self())->getStatusSpecification($status); + return $map['name']; } public static function getAllStatuses() { - return array_keys(self::getStatusMap()); + return array_keys(id(new self())->getStatusSpecifications()); + } + + public function isActive() { + return ($this->getKey() === self::STATUS_ACTIVE); + } + + public function canRelease() { + return $this->getStatusProperty('isReleasable'); + } + + public function canReceiveCommands() { + return $this->getStatusProperty('isCommandable'); + } + + protected function newStatusSpecifications() { + return array( + array( + 'key' => self::STATUS_PENDING, + 'name' => pht('Pending'), + 'icon' => 'fa-clock-o', + 'color' => 'blue', + 'isReleasable' => true, + 'isCommandable' => true, + ), + array( + 'key' => self::STATUS_ACTIVE, + 'name' => pht('Active'), + 'icon' => 'fa-check', + 'color' => 'green', + 'isReleasable' => true, + 'isCommandable' => true, + ), + array( + 'key' => self::STATUS_RELEASED, + 'name' => pht('Released'), + 'icon' => 'fa-circle-o', + 'color' => 'blue', + 'isReleasable' => false, + 'isCommandable' => false, + ), + array( + 'key' => self::STATUS_BROKEN, + 'name' => pht('Broken'), + 'icon' => 'fa-times', + 'color' => 'indigo', + 'isReleasable' => true, + 'isCommandable' => false, + ), + array( + 'key' => self::STATUS_DESTROYED, + 'name' => pht('Destroyed'), + 'icon' => 'fa-times', + 'color' => 'grey', + 'isReleasable' => false, + 'isCommandable' => false, + ), + ); + } + + protected function newUnknownStatusSpecification($status) { + return array( + 'isReleasable' => false, + 'isCommandable' => false, + ); } } diff --git a/src/applications/drydock/controller/DrydockResourceViewController.php b/src/applications/drydock/controller/DrydockResourceViewController.php index c6771007ba..8718caa9e2 100644 --- a/src/applications/drydock/controller/DrydockResourceViewController.php +++ b/src/applications/drydock/controller/DrydockResourceViewController.php @@ -24,10 +24,19 @@ final class DrydockResourceViewController extends DrydockResourceController { ->setUser($viewer) ->setPolicyObject($resource) ->setHeader($title) - ->setHeaderIcon('fa-map'); + ->setHeaderIcon('fa-map') + ->setStatus( + $resource->getStatusIcon(), + $resource->getStatusColor(), + $resource->getStatusDisplayName()); if ($resource->isReleasing()) { - $header->setStatus('fa-exclamation-triangle', 'red', pht('Releasing')); + $header->addTag( + id(new PHUITagView()) + ->setType(PHUITagView::TYPE_SHADE) + ->setIcon('fa-exclamation-triangle') + ->setColor('red') + ->setName('Releasing')); } $curtain = $this->buildCurtain($resource); @@ -127,12 +136,6 @@ final class DrydockResourceViewController extends DrydockResourceController { $viewer = $this->getViewer(); $view = new PHUIPropertyListView(); - $status = $resource->getStatus(); - $status = DrydockResourceStatus::getNameForStatus($status); - - $view->addProperty( - pht('Status'), - $status); $until = $resource->getUntil(); if ($until) { diff --git a/src/applications/drydock/storage/DrydockResource.php b/src/applications/drydock/storage/DrydockResource.php index 3eceec8b77..8ec63cb097 100644 --- a/src/applications/drydock/storage/DrydockResource.php +++ b/src/applications/drydock/storage/DrydockResource.php @@ -235,16 +235,6 @@ final class DrydockResource extends DrydockDAO return $this->isActivated; } - public function canRelease() { - switch ($this->getStatus()) { - case DrydockResourceStatus::STATUS_RELEASED: - case DrydockResourceStatus::STATUS_DESTROYED: - return false; - default: - return true; - } - } - public function scheduleUpdate($epoch = null) { PhabricatorWorker::scheduleTask( 'DrydockResourceUpdateWorker', @@ -282,26 +272,6 @@ final class DrydockResource extends DrydockDAO } } - public function canReceiveCommands() { - switch ($this->getStatus()) { - case DrydockResourceStatus::STATUS_RELEASED: - case DrydockResourceStatus::STATUS_BROKEN: - case DrydockResourceStatus::STATUS_DESTROYED: - return false; - default: - return true; - } - } - - public function isActive() { - switch ($this->getStatus()) { - case DrydockResourceStatus::STATUS_ACTIVE: - return true; - } - - return false; - } - public function logEvent($type, array $data = array()) { $log = id(new DrydockLog()) ->setEpoch(PhabricatorTime::getNow()) @@ -315,6 +285,38 @@ final class DrydockResource extends DrydockDAO } +/* -( Status )------------------------------------------------------------- */ + + + public function getStatusObject() { + return DrydockResourceStatus::newStatusObject($this->getStatus()); + } + + public function getStatusIcon() { + return $this->getStatusObject()->getIcon(); + } + + public function getStatusColor() { + return $this->getStatusObject()->getColor(); + } + + public function getStatusDisplayName() { + return $this->getStatusObject()->getDisplayName(); + } + + public function canRelease() { + return $this->getStatusObject()->canRelease(); + } + + public function canReceiveCommands() { + return $this->getStatusObject()->canReceiveCommands(); + } + + public function isActive() { + return $this->getStatusObject()->isActive(); + } + + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/drydock/view/DrydockResourceListView.php b/src/applications/drydock/view/DrydockResourceListView.php index 739b464bdb..f2d105ecc8 100644 --- a/src/applications/drydock/view/DrydockResourceListView.php +++ b/src/applications/drydock/view/DrydockResourceListView.php @@ -23,23 +23,11 @@ final class DrydockResourceListView extends AphrontView { ->setObjectName(pht('Resource %d', $id)) ->setHeader($resource->getResourceName()); - $status = DrydockResourceStatus::getNameForStatus($resource->getStatus()); - $item->addAttribute($status); + $icon = $resource->getStatusIcon(); + $color = $resource->getStatusColor(); + $label = $resource->getStatusDisplayName(); - switch ($resource->getStatus()) { - case DrydockResourceStatus::STATUS_PENDING: - $item->setStatusIcon('fa-dot-circle-o yellow'); - break; - case DrydockResourceStatus::STATUS_ACTIVE: - $item->setStatusIcon('fa-dot-circle-o green'); - break; - case DrydockResourceStatus::STATUS_DESTROYED: - $item->setStatusIcon('fa-times-circle-o black'); - break; - default: - $item->setStatusIcon('fa-dot-circle-o red'); - break; - } + $item->setStatusIcon("{$icon} {$color}", $label); $view->addItem($item); } From 30a0b103e6f26c289f13caed7f365250b82f41fb Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 13 Feb 2018 06:12:32 -0800 Subject: [PATCH 23/58] When a lease acquires a resource but the resource fails to activate, throw the lease back in the pool Summary: Depends on D19075. Ref T13073. If a lease acquires a resource but finds that the resource builds directly into a dead state (which can happen for any number of reasonable reasons), reset the lease and throw it back in the pool. This isn't the lease's fault and it hasn't caused any side effects or done anything we can't undo, so we can safely reset it and throw it back in the pool. Test Plan: - Created a blueprint which throws from `allocateResource()` so that resources never activate. - Tried to lease it with `bin/drydock lease ...`. - Before patch: lease was broken and destroyed after a failed activation. - After patch: lease was returned to the pool after a failed activation. Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T13073 Differential Revision: https://secure.phabricator.com/D19076 --- src/__phutil_library_map__.php | 4 +++ ...DrydockAcquiredBrokenResourceException.php | 4 +++ .../logtype/DrydockLeaseReacquireLogType.php | 26 ++++++++++++++++++ .../worker/DrydockLeaseUpdateWorker.php | 27 +++++++++++++++++-- 4 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 src/applications/drydock/exception/DrydockAcquiredBrokenResourceException.php create mode 100644 src/applications/drydock/logtype/DrydockLeaseReacquireLogType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 116aa31629..e26d68f72b 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1000,6 +1000,7 @@ phutil_register_library_map(array( 'DoorkeeperSchemaSpec' => 'applications/doorkeeper/storage/DoorkeeperSchemaSpec.php', 'DoorkeeperTagView' => 'applications/doorkeeper/view/DoorkeeperTagView.php', 'DoorkeeperTagsController' => 'applications/doorkeeper/controller/DoorkeeperTagsController.php', + 'DrydockAcquiredBrokenResourceException' => 'applications/drydock/exception/DrydockAcquiredBrokenResourceException.php', 'DrydockAlmanacServiceHostBlueprintImplementation' => 'applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php', 'DrydockApacheWebrootInterface' => 'applications/drydock/interface/webroot/DrydockApacheWebrootInterface.php', 'DrydockAuthorization' => 'applications/drydock/storage/DrydockAuthorization.php', @@ -1065,6 +1066,7 @@ phutil_register_library_map(array( 'DrydockLeasePHIDType' => 'applications/drydock/phid/DrydockLeasePHIDType.php', 'DrydockLeaseQuery' => 'applications/drydock/query/DrydockLeaseQuery.php', 'DrydockLeaseQueuedLogType' => 'applications/drydock/logtype/DrydockLeaseQueuedLogType.php', + 'DrydockLeaseReacquireLogType' => 'applications/drydock/logtype/DrydockLeaseReacquireLogType.php', 'DrydockLeaseReclaimLogType' => 'applications/drydock/logtype/DrydockLeaseReclaimLogType.php', 'DrydockLeaseReleaseController' => 'applications/drydock/controller/DrydockLeaseReleaseController.php', 'DrydockLeaseReleasedLogType' => 'applications/drydock/logtype/DrydockLeaseReleasedLogType.php', @@ -6199,6 +6201,7 @@ phutil_register_library_map(array( 'DoorkeeperSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'DoorkeeperTagView' => 'AphrontView', 'DoorkeeperTagsController' => 'PhabricatorController', + 'DrydockAcquiredBrokenResourceException' => 'Exception', 'DrydockAlmanacServiceHostBlueprintImplementation' => 'DrydockBlueprintImplementation', 'DrydockApacheWebrootInterface' => 'DrydockWebrootInterface', 'DrydockAuthorization' => array( @@ -6285,6 +6288,7 @@ phutil_register_library_map(array( 'DrydockLeasePHIDType' => 'PhabricatorPHIDType', 'DrydockLeaseQuery' => 'DrydockQuery', 'DrydockLeaseQueuedLogType' => 'DrydockLogType', + 'DrydockLeaseReacquireLogType' => 'DrydockLogType', 'DrydockLeaseReclaimLogType' => 'DrydockLogType', 'DrydockLeaseReleaseController' => 'DrydockLeaseController', 'DrydockLeaseReleasedLogType' => 'DrydockLogType', diff --git a/src/applications/drydock/exception/DrydockAcquiredBrokenResourceException.php b/src/applications/drydock/exception/DrydockAcquiredBrokenResourceException.php new file mode 100644 index 0000000000..12b839d466 --- /dev/null +++ b/src/applications/drydock/exception/DrydockAcquiredBrokenResourceException.php @@ -0,0 +1,4 @@ +updateLease($lease); + } catch (DrydockAcquiredBrokenResourceException $ex) { + // If this lease acquired a resource but failed to activate, we don't + // need to break the lease. We can throw it back in the pool and let + // it take another shot at acquiring a new resource. + + // Before we throw it back, release any locks the lease is holding. + DrydockSlotLock::releaseLocks($lease->getPHID()); + + $lease + ->setStatus(DrydockLeaseStatus::STATUS_PENDING) + ->save(); + + $lease->logEvent( + DrydockLeaseReacquireLogType::LOGCONST, + array( + 'class' => get_class($ex), + 'message' => $ex->getMessage(), + )); + + $this->yieldLease($lease, $ex); } catch (Exception $ex) { if ($this->isTemporaryException($ex)) { $this->yieldLease($lease, $ex); @@ -749,9 +769,12 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { } if ($resource_status != DrydockResourceStatus::STATUS_ACTIVE) { - throw new Exception( + throw new DrydockAcquiredBrokenResourceException( pht( - 'Trying to activate lease on a dead resource (in status "%s").', + 'Trying to activate lease ("%s") on a resource ("%s") in '. + 'the wrong status ("%s").', + $lease->getPHID(), + $resource->getPHID(), $resource_status)); } From 2994753d23334f08b9a3daa5b14baaebaaba220e Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 13 Feb 2018 09:32:30 -0800 Subject: [PATCH 24/58] Don't let `bin/drydock lease --attributes` overwrite blueprints Summary: Depends on D19076. Ref T13073. Blueprints are stored as an attribute and `setAttributes()` overwrites all attributes. This is sorta junk but make it less obviously broken, at least. Test Plan: Ran `bin/drydock lease --type working-copy --attributes x=y` without instantly getting a fatal about "no blueprint PHIDs". Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T13073 Differential Revision: https://secure.phabricator.com/D19077 --- .../drydock/management/DrydockManagementLeaseWorkflow.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php b/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php index 3a27fe9045..8c3f682042 100644 --- a/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php +++ b/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php @@ -62,6 +62,10 @@ final class DrydockManagementLeaseWorkflow $drydock_phid = id(new PhabricatorDrydockApplication())->getPHID(); $lease->setAuthorizingPHID($drydock_phid); + if ($attributes) { + $lease->setAttributes($attributes); + } + // TODO: This is not hugely scalable, although this is a debugging workflow // so maybe it's fine. Do we even need `bin/drydock lease` in the long run? $all_blueprints = id(new DrydockBlueprintQuery()) @@ -76,10 +80,6 @@ final class DrydockManagementLeaseWorkflow } $lease->setAllowedBlueprintPHIDs($allowed_phids); - if ($attributes) { - $lease->setAttributes($attributes); - } - if ($until) { $lease->setUntil($until); } From 619943bea01c5e6ee31232bccd8da237eabdbd29 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 13 Feb 2018 09:49:11 -0800 Subject: [PATCH 25/58] Reduce collision rate for concurrency-limiting slot locks Summary: Depends on D19077. Ref T13073. When we're using slot locks to enforce a limit (e.g., maximum of 5 simultaneous things) we currently load locks owned by the blueprint to identify which slots are likely to be free. However, this isn't right: the blueprint doesn't own these locks. The resources do. We still get the right behavior eventually, but we incorrectly identify that every slot lock is always free, so as the slots fill up we'll tend to guess wrong more and more often. Instead, load the slot locks by name explicitly. Test Plan: Implemented lock-based limiting on `HoaxBlueprint`, `var_dump()`'d the candidate locks, saw correct test state for locks. Acquired leases without releasing, got all of the slots filled without any slot lock collisions (previously, the last slot or two tended to collide a lot). Subscribers: yelirekim, PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T13073 Differential Revision: https://secure.phabricator.com/D19078 --- .../DrydockBlueprintImplementation.php | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php index a050a859e3..5acf63bf6b 100644 --- a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php @@ -396,16 +396,20 @@ abstract class DrydockBlueprintImplementation extends Phobject { } // For reasonable limits, actually check for an available slot. - $locks = DrydockSlotLock::loadLocks($blueprint_phid); - $locks = mpull($locks, null, 'getLockKey'); - $slots = range(0, $limit - 1); shuffle($slots); + $lock_names = array(); foreach ($slots as $slot) { - $slot_lock = "allocator({$blueprint_phid}).limit({$slot})"; - if (empty($locks[$slot_lock])) { - return $slot_lock; + $lock_names[] = "allocator({$blueprint_phid}).limit({$slot})"; + } + + $locks = DrydockSlotLock::loadHeldLocks($lock_names); + $locks = mpull($locks, null, 'getLockKey'); + + foreach ($lock_names as $lock_name) { + if (empty($locks[$lock_name])) { + return $lock_name; } } @@ -414,7 +418,8 @@ abstract class DrydockBlueprintImplementation extends Phobject { // lock will be free by the time we try to take it, but usually we'll just // fail to grab the lock, throw an appropriate lock exception, and get back // on the right path to retry later. - return $slot_lock; + + return $lock_name; } From a1baedbd9a08ca2d41b81eefeaf4b3a4d214ab8e Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 13 Feb 2018 10:46:14 -0800 Subject: [PATCH 26/58] Lock resources briefly while acquiring leases on them to prevent acquiring near-death resources Summary: Depends on D19078. Ref T13073. Currently, there is a narrow window where we can acquire a resource after a reclaim has started against it. To prevent this, briefly lock resources before acquiring them and make sure they're still good. If a resource isn't good, throw the lease back in the pool. Test Plan: This is tricky. You need: - Hoax blueprint with limits and a rule where leases of a given "flavor" can only be satisfied by resources of the same flavor. - Reduce the 3-minute "wait before resources can be released" to 3 seconds. - Limit Hoaxes to 1. - Allocate one "cherry" flavored Hoax and release the lease. - Add a `sleep(15)` to `releaseResource()` in `DrydockResourceUpdateWorker`, after the `canReclaimResource()` check, with a `print`. Now: - Run `bin/phd debug task` in two windows. - Run `bin/drydock lease --type host --attributes flavor=banana` in a third window. - This will start to reclaim the existing "cherry" resource. Once one of the `phd` windows prints the "RECLAIMING" message run `bin/drydock lease --type host --attributes flavor=cherry` in a fourth window. - Before patch: the "cherry" lease acquired immediately, then was released and destroyed moments later. - After patch: the "cherry" lease yields. Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T13073 Differential Revision: https://secure.phabricator.com/D19080 --- src/__phutil_library_map__.php | 2 + .../DrydockResourceLockException.php | 4 + .../drydock/storage/DrydockLease.php | 83 ++++++++++++++----- .../worker/DrydockLeaseUpdateWorker.php | 26 ++++-- 4 files changed, 91 insertions(+), 24 deletions(-) create mode 100644 src/applications/drydock/exception/DrydockResourceLockException.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index e26d68f72b..80c70fd98e 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1113,6 +1113,7 @@ phutil_register_library_map(array( 'DrydockResourceDatasource' => 'applications/drydock/typeahead/DrydockResourceDatasource.php', 'DrydockResourceListController' => 'applications/drydock/controller/DrydockResourceListController.php', 'DrydockResourceListView' => 'applications/drydock/view/DrydockResourceListView.php', + 'DrydockResourceLockException' => 'applications/drydock/exception/DrydockResourceLockException.php', 'DrydockResourcePHIDType' => 'applications/drydock/phid/DrydockResourcePHIDType.php', 'DrydockResourceQuery' => 'applications/drydock/query/DrydockResourceQuery.php', 'DrydockResourceReclaimLogType' => 'applications/drydock/logtype/DrydockResourceReclaimLogType.php', @@ -6344,6 +6345,7 @@ phutil_register_library_map(array( 'DrydockResourceDatasource' => 'PhabricatorTypeaheadDatasource', 'DrydockResourceListController' => 'DrydockResourceController', 'DrydockResourceListView' => 'AphrontView', + 'DrydockResourceLockException' => 'Exception', 'DrydockResourcePHIDType' => 'PhabricatorPHIDType', 'DrydockResourceQuery' => 'DrydockQuery', 'DrydockResourceReclaimLogType' => 'DrydockLogType', diff --git a/src/applications/drydock/exception/DrydockResourceLockException.php b/src/applications/drydock/exception/DrydockResourceLockException.php new file mode 100644 index 0000000000..2b54538e08 --- /dev/null +++ b/src/applications/drydock/exception/DrydockResourceLockException.php @@ -0,0 +1,4 @@ +openTransaction(); + // Before we associate the lease with the resource, we lock the resource + // and reload it to make sure it is still pending or active. If we don't + // do this, the resource may have just been reclaimed. (Once we acquire + // the resource that stops it from being released, so we're nearly safe.) + + $resource_phid = $resource->getPHID(); + $hash = PhabricatorHash::digestForIndex($resource_phid); + $lock_key = 'drydock.resource:'.$hash; + $lock = PhabricatorGlobalLock::newLock($lock_key); try { - DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks); - $this->slotLocks = array(); - } catch (DrydockSlotLockException $ex) { - $this->killTransaction(); - - $this->logEvent( - DrydockSlotLockFailureLogType::LOGCONST, - array( - 'locks' => $ex->getLockMap(), - )); - - throw $ex; + $lock->lock(15); + } catch (Exception $ex) { + throw new DrydockResourceLockException( + pht( + 'Failed to acquire lock for resource ("%s") while trying to '. + 'acquire lease ("%s").', + $resource->getPHID(), + $this->getPHID())); } - $this - ->setResourcePHID($resource->getPHID()) - ->attachResource($resource) - ->setStatus($new_status) - ->save(); + $resource->reload(); - $this->saveTransaction(); + if (($resource->getStatus() !== DrydockResourceStatus::STATUS_ACTIVE) && + ($resource->getStatus() !== DrydockResourceStatus::STATUS_PENDING)) { + throw new DrydockAcquiredBrokenResourceException( + pht( + 'Trying to acquire lease ("%s") on a resource ("%s") in the '. + 'wrong status ("%s").', + $this->getPHID(), + $resource->getPHID(), + $resource->getStatus())); + } + + $caught = null; + try { + $this->openTransaction(); + + try { + DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks); + $this->slotLocks = array(); + } catch (DrydockSlotLockException $ex) { + $this->killTransaction(); + + $this->logEvent( + DrydockSlotLockFailureLogType::LOGCONST, + array( + 'locks' => $ex->getLockMap(), + )); + + throw $ex; + } + + $this + ->setResourcePHID($resource->getPHID()) + ->attachResource($resource) + ->setStatus($new_status) + ->save(); + + $this->saveTransaction(); + } catch (Exception $ex) { + $caught = $ex; + } + + $lock->unlock(); + + if ($caught) { + throw $caught; + } $this->isAcquired = true; diff --git a/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php index 55d9136cef..4a0cba8de2 100644 --- a/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php +++ b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php @@ -53,6 +53,7 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { $lease ->setStatus(DrydockLeaseStatus::STATUS_PENDING) + ->setResourcePHID(null) ->save(); $lease->logEvent( @@ -301,23 +302,38 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { $resources = $this->rankResources($resources, $lease); $exceptions = array(); + $yields = array(); $allocated = false; foreach ($resources as $resource) { try { $this->acquireLease($resource, $lease); $allocated = true; break; + } catch (DrydockResourceLockException $ex) { + // We need to lock the resource to actually acquire it. If we aren't + // able to acquire the lock quickly enough, we can yield and try again + // later. + $yields[] = $ex; + } catch (DrydockAcquiredBrokenResourceException $ex) { + // If a resource was reclaimed or destroyed by the time we actually + // got around to acquiring it, we just got unlucky. We can yield and + // try again later. + $yields[] = $ex; } catch (Exception $ex) { $exceptions[] = $ex; } } if (!$allocated) { - throw new PhutilAggregateException( - pht( - 'Unable to acquire lease "%s" on any resource.', - $lease->getPHID()), - $exceptions); + if ($yields) { + throw new PhabricatorWorkerYieldException(15); + } else { + throw new PhutilAggregateException( + pht( + 'Unable to acquire lease "%s" on any resource.', + $lease->getPHID()), + $exceptions); + } } } From 743f0d65eaec434689dc509e08a6cc254e7e214e Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 13 Feb 2018 17:53:36 -0800 Subject: [PATCH 27/58] Fix a mail stamp issue with blocking reviewers Summary: Revisions with blocking reviewers had this stamp built incorrectly, which cascaded into trying to use `array()` as a PHID. Recover so these tasks succeed. Test Plan: Will deploy production. Differential Revision: https://secure.phabricator.com/D19082 --- .../engineextension/DifferentialMailEngineExtension.php | 2 +- .../metamta/stamp/PhabricatorPHIDMailStamp.php | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/applications/differential/engineextension/DifferentialMailEngineExtension.php b/src/applications/differential/engineextension/DifferentialMailEngineExtension.php index 24aa9fb329..29019b9e73 100644 --- a/src/applications/differential/engineextension/DifferentialMailEngineExtension.php +++ b/src/applications/differential/engineextension/DifferentialMailEngineExtension.php @@ -53,7 +53,7 @@ final class DifferentialMailEngineExtension } else { $reviewers[] = $reviewer_phid; if ($reviewer->isBlocking()) { - $reviewers[] = $blocking; + $blocking[] = $reviewer_phid; } } } diff --git a/src/applications/metamta/stamp/PhabricatorPHIDMailStamp.php b/src/applications/metamta/stamp/PhabricatorPHIDMailStamp.php index 575ad16f6a..ea7233fe5b 100644 --- a/src/applications/metamta/stamp/PhabricatorPHIDMailStamp.php +++ b/src/applications/metamta/stamp/PhabricatorPHIDMailStamp.php @@ -15,6 +15,15 @@ final class PhabricatorPHIDMailStamp return null; } + // TODO: This recovers from a bug where blocking reviewers were serialized + // incorrectly into the flat mail stamp list in the worker queue as arrays. + // It can be removed some time after February 2018. + foreach ($value as $key => $v) { + if (is_array($v)) { + unset($value[$key]); + } + } + $viewer = $this->getViewer(); $handles = $viewer->loadHandles($value); From 6bfd0ff275c16a402196f40d0ab58da6a6b1d6a2 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 14 Feb 2018 10:49:27 -0800 Subject: [PATCH 28/58] Support "encoding", which is required by PHPMailerLite, in SES adapter Summary: See . The adapter class tree is a mess and this property is read by the parent class. Test Plan: Configured an SES mailer, used `bin/mail send-test` to reproduce the issue before the patch and observe it working after the patch. Differential Revision: https://secure.phabricator.com/D19083 --- .../adapter/PhabricatorMailImplementationAmazonSESAdapter.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/applications/metamta/adapter/PhabricatorMailImplementationAmazonSESAdapter.php b/src/applications/metamta/adapter/PhabricatorMailImplementationAmazonSESAdapter.php index 22cc102262..a26b074df9 100644 --- a/src/applications/metamta/adapter/PhabricatorMailImplementationAmazonSESAdapter.php +++ b/src/applications/metamta/adapter/PhabricatorMailImplementationAmazonSESAdapter.php @@ -26,6 +26,7 @@ final class PhabricatorMailImplementationAmazonSESAdapter 'access-key' => 'string', 'secret-key' => 'string', 'endpoint' => 'string', + 'encoding' => 'string', )); } @@ -34,6 +35,7 @@ final class PhabricatorMailImplementationAmazonSESAdapter 'access-key' => null, 'secret-key' => null, 'endpoint' => null, + 'encoding' => 'base64', ); } @@ -42,6 +44,7 @@ final class PhabricatorMailImplementationAmazonSESAdapter 'access-key' => PhabricatorEnv::getEnvConfig('amazon-ses.access-key'), 'secret-key' => PhabricatorEnv::getEnvConfig('amazon-ses.secret-key'), 'endpoint' => PhabricatorEnv::getEnvConfig('amazon-ses.endpoint'), + 'encoding' => PhabricatorEnv::getEnvConfig('phpmailer.smtp-encoding'), ); } From a2453706abf1f3b60a679f427fbff612c84aeeaf Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 14 Feb 2018 11:09:57 -0800 Subject: [PATCH 29/58] When "phabricator.silent" is enabled, don't call webhooks Summary: Ref T13078. The `phabricator.silent` configuration flag should disable webhook calls, since this is consistent with the documented and desired behavior. Test Plan: Enabled `phabricator.silent`, made test hook calls, saw them fail with a "silent" failure reason. Maniphest Tasks: T13078 Differential Revision: https://secure.phabricator.com/D19084 --- src/applications/herald/worker/HeraldWebhookWorker.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/applications/herald/worker/HeraldWebhookWorker.php b/src/applications/herald/worker/HeraldWebhookWorker.php index 837ec0bb23..150f98fd50 100644 --- a/src/applications/herald/worker/HeraldWebhookWorker.php +++ b/src/applications/herald/worker/HeraldWebhookWorker.php @@ -32,6 +32,13 @@ final class HeraldWebhookWorker $status)); } + // If we're in silent mode, permanently fail the webhook request and then + // return to complete this task. + if (PhabricatorEnv::getEnvConfig('phabricator.silent')) { + $this->failRequest($request, 'hook', 'silent'); + return; + } + $hook = $request->getWebhook(); if ($hook->isDisabled()) { From f74e6bbf8d70e957cebb2a47f30f9e3c9c301338 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 14 Feb 2018 11:30:25 -0800 Subject: [PATCH 30/58] Make "phabricator.silent" disable build steps which rely on external services Summary: Depends on D19084. Fixes T13078. When `phabricator.silent` is enabled, immediately fail the "HTTP Request", "CircleCI" and "Buildkite" build steps. This doesn't feel quite as clean as most of the other behavior of `phabricator.silent`, since these calls are not exactly notifications in the same way that email is, and failing to make these calls means that builds run differently (whereas failing to deliver email doesn't really do anything). However, I suspect that this behavior is almost always reasonable/correct, and that we can probably get away with it until this grey area between "notifications" and "external service calls" is more clearly defined. Test Plan: - Created a build with HTTP, CircleCI, and Buildkite steps. - Put install in `phabricator.silent` mode: all three steps failed with "declining, because silent" messages. - Put install back in normal mode: all three steps made HTTP requests. - Read updated documentation. Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T13078 Differential Revision: https://secure.phabricator.com/D19085 --- .../option/PhabricatorCoreConfigOptions.php | 34 +++++++++++-------- .../HarbormasterBuildStepImplementation.php | 12 +++++++ ...masterBuildkiteBuildStepImplementation.php | 5 +++ ...rmasterCircleCIBuildStepImplementation.php | 5 +++ ...sterHTTPRequestBuildStepImplementation.php | 6 ++++ 5 files changed, 47 insertions(+), 15 deletions(-) diff --git a/src/applications/config/option/PhabricatorCoreConfigOptions.php b/src/applications/config/option/PhabricatorCoreConfigOptions.php index 6efd27d9a3..0c9e69ebf2 100644 --- a/src/applications/config/option/PhabricatorCoreConfigOptions.php +++ b/src/applications/config/option/PhabricatorCoreConfigOptions.php @@ -37,6 +37,24 @@ final class PhabricatorCoreConfigOptions $proto_doc_name = pht('User Guide: Prototype Applications'); $applications_app_href = '/applications/'; + $silent_description = $this->deformat(pht(<<newOption('phabricator.base-uri', 'string', null) ->setLocked(true) @@ -232,21 +250,7 @@ final class PhabricatorCoreConfigOptions pht('Run Normally'), )) ->setSummary(pht('Stop Phabricator from sending any email, etc.')) - ->setDescription( - pht( - 'This option allows you to stop Phabricator from sending '. - 'any data to external services. Among other things, it will '. - 'disable email, SMS, repository mirroring, and HTTP hooks.'. - "\n\n". - 'This option is intended to allow a Phabricator instance to '. - 'be exported, copied, imported, and run in a test environment '. - 'without impacting users. For example, if you are migrating '. - 'to new hardware, you could perform a test migration first, '. - 'make sure things work, and then do a production cutover '. - 'later with higher confidence and less disruption. Without '. - 'this flag, users would receive duplicate email during the '. - 'time the test instance and old production instance were '. - 'both in operation.')), + ->setDescription($silent_description), ); } diff --git a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php index ca6f30bd36..47af992630 100644 --- a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php @@ -295,6 +295,18 @@ abstract class HarbormasterBuildStepImplementation extends Phobject { ->append($body); } + protected function logSilencedCall( + HarbormasterBuild $build, + HarbormasterBuildTarget $build_target, + $label) { + + $build_target + ->newLog($label, 'silenced') + ->append( + pht( + 'Declining to make service call because `phabricator.silent` is '. + 'enabled in configuration.')); + } /* -( Automatic Targets )-------------------------------------------------- */ diff --git a/src/applications/harbormaster/step/HarbormasterBuildkiteBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterBuildkiteBuildStepImplementation.php index 3ecd6553da..89d4002eef 100644 --- a/src/applications/harbormaster/step/HarbormasterBuildkiteBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterBuildkiteBuildStepImplementation.php @@ -72,6 +72,11 @@ EOTEXT HarbormasterBuildTarget $build_target) { $viewer = PhabricatorUser::getOmnipotentUser(); + if (PhabricatorEnv::getEnvConfig('phabricator.silent')) { + $this->logSilencedCall($build, $build_target, pht('Buildkite')); + throw new HarbormasterBuildFailureException(); + } + $buildable = $build->getBuildable(); $object = $buildable->getBuildableObject(); diff --git a/src/applications/harbormaster/step/HarbormasterCircleCIBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterCircleCIBuildStepImplementation.php index e74e25a395..f0104779ff 100644 --- a/src/applications/harbormaster/step/HarbormasterCircleCIBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterCircleCIBuildStepImplementation.php @@ -84,6 +84,11 @@ EOTEXT HarbormasterBuildTarget $build_target) { $viewer = PhabricatorUser::getOmnipotentUser(); + if (PhabricatorEnv::getEnvConfig('phabricator.silent')) { + $this->logSilencedCall($build, $build_target, pht('CircleCI')); + throw new HarbormasterBuildFailureException(); + } + $buildable = $build->getBuildable(); $object = $buildable->getBuildableObject(); diff --git a/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php index 016f29642e..59866d3b73 100644 --- a/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php @@ -43,6 +43,12 @@ final class HarbormasterHTTPRequestBuildStepImplementation HarbormasterBuildTarget $build_target) { $viewer = PhabricatorUser::getOmnipotentUser(); + + if (PhabricatorEnv::getEnvConfig('phabricator.silent')) { + $this->logSilencedCall($build, $build_target, pht('HTTP Request')); + throw new HarbormasterBuildFailureException(); + } + $settings = $this->getSettings(); $variables = $build_target->getVariables(); From a4053bb5808edda949d134f7fdca637f35510f7a Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 14 Feb 2018 15:18:10 -0800 Subject: [PATCH 31/58] When a ChangesetList sleeps after a Quicksand navigation, also hide any visible banner Summary: Fixes T13080. The banner wasn't properly included in the sleep/wake logic. Test Plan: Mentioned `Dxxx` on a task. Enabled persistent chat to activate Quicksand. Reloaded page. Clicked `Dxxx`. Scrolled down until a changeset header appeared. Pressed back button. - Before patch: ended up on task, with header still around. - After patch: ended up on task, with header properly vanquished. Pressed "forward", ended up back on the revision with the header again. Maniphest Tasks: T13080 Differential Revision: https://secure.phabricator.com/D19086 --- resources/celerity/map.php | 14 +++++++------- .../js/application/diff/DiffChangesetList.js | 16 +++++++++++----- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index f3ead4de53..a08f04658e 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '3ac6e174', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '113e692c', - 'differential.pkg.js' => '5d53d5ce', + 'differential.pkg.js' => 'f6d809c0', 'diffusion.pkg.css' => 'a2d17c7d', 'diffusion.pkg.js' => '6134c5a1', 'favicon.ico' => '30672e08', @@ -396,7 +396,7 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => 'b49b59d6', - 'rsrc/js/application/diff/DiffChangesetList.js' => '1f2e5265', + 'rsrc/js/application/diff/DiffChangesetList.js' => 'e74b7517', 'rsrc/js/application/diff/DiffInline.js' => 'e83d28f3', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => '51c5ad07', @@ -776,7 +776,7 @@ return array( 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => 'b49b59d6', - 'phabricator-diff-changeset-list' => '1f2e5265', + 'phabricator-diff-changeset-list' => 'e74b7517', 'phabricator-diff-inline' => 'e83d28f3', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', @@ -1044,10 +1044,6 @@ return array( 'javelin-uri', 'javelin-routable', ), - '1f2e5265' => array( - 'javelin-install', - 'phuix-button-view', - ), '1f6794f6' => array( 'javelin-behavior', 'javelin-stratcom', @@ -2093,6 +2089,10 @@ return array( 'javelin-workflow', 'javelin-magical-init', ), + 'e74b7517' => array( + 'javelin-install', + 'phuix-button-view', + ), 'e83d28f3' => array( 'javelin-dom', ), diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index e62d2f51dd..5eba051d93 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -128,6 +128,9 @@ JX.install('DiffChangesetList', { this._redrawFocus(); this._redrawSelection(); this.resetHover(); + + this._bannerChangeset = null; + this._redrawBanner(); }, wake: function() { @@ -136,6 +139,9 @@ JX.install('DiffChangesetList', { this._redrawFocus(); this._redrawSelection(); + this._bannerChangeset = null; + this._redrawBanner(); + if (this._initialized) { return; } @@ -1374,6 +1380,11 @@ JX.install('DiffChangesetList', { var node = this._getBannerNode(); var changeset = this._getVisibleChangeset(); + if (!changeset) { + JX.DOM.remove(node); + return; + } + // Don't do anything if nothing has changed. This seems to avoid some // flickering issues in Safari, at least. if (this._bannerChangeset === changeset) { @@ -1381,11 +1392,6 @@ JX.install('DiffChangesetList', { } this._bannerChangeset = changeset; - if (!changeset) { - JX.DOM.remove(node); - return; - } - var inlines = this._getInlinesByType(); var unsaved = inlines.unsaved; From abe5fd57b0de311519572a52a79546117835f4a5 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 14 Feb 2018 15:33:19 -0800 Subject: [PATCH 32/58] Rename "QuickSearch" Engine/EngineExtension to "Datasource" Summary: Ref T13079. This recently-introduced Engine/EngineExtension are a good fit for adding more datasource functions in general, but we didn't think quite big enough in naming them. Test Plan: Used quick search typeahead, hit applications/users/monograms/symbols/etc. Maniphest Tasks: T13079 Differential Revision: https://secure.phabricator.com/D19087 --- src/__phutil_library_map__.php | 28 ++++++++++--------- ...=> DiffusionDatasourceEngineExtension.php} | 4 +-- ...rDatasourceApplicationEngineExtension.php} | 4 +-- ...icatorPeopleDatasourceEngineExtension.php} | 4 +-- ...p => ProjectDatasourceEngineExtension.php} | 4 +-- .../engine/PhabricatorDatasourceEngine.php | 8 ++++++ .../engine/PhabricatorQuickSearchEngine.php | 8 ------ .../PhabricatorDatasourceEngineExtension.php | 19 +++++++++++++ .../PhabricatorQuickSearchEngineExtension.php | 20 +++---------- .../typeahead/PhabricatorSearchDatasource.php | 4 +-- ...atorMonogramDatasourceEngineExtension.php} | 4 +-- 11 files changed, 58 insertions(+), 49 deletions(-) rename src/applications/diffusion/engineextension/{DiffusionQuickSearchEngineExtension.php => DiffusionDatasourceEngineExtension.php} (63%) rename src/applications/meta/engineextension/{PhabricatorQuickSearchApplicationEngineExtension.php => PhabricatorDatasourceApplicationEngineExtension.php} (54%) rename src/applications/people/engineextension/{PhabricatorPeopleQuickSearchEngineExtension.php => PhabricatorPeopleDatasourceEngineExtension.php} (54%) rename src/applications/project/engineextension/{ProjectQuickSearchEngineExtension.php => ProjectDatasourceEngineExtension.php} (57%) create mode 100644 src/applications/search/engine/PhabricatorDatasourceEngine.php delete mode 100644 src/applications/search/engine/PhabricatorQuickSearchEngine.php create mode 100644 src/applications/search/engineextension/PhabricatorDatasourceEngineExtension.php rename src/applications/typeahead/engineextension/{PhabricatorMonogramQuickSearchEngineExtension.php => PhabricatorMonogramDatasourceEngineExtension.php} (56%) diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 80c70fd98e..771658e32e 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -716,6 +716,7 @@ phutil_register_library_map(array( 'DiffusionController' => 'applications/diffusion/controller/DiffusionController.php', 'DiffusionCreateRepositoriesCapability' => 'applications/diffusion/capability/DiffusionCreateRepositoriesCapability.php', 'DiffusionDaemonLockException' => 'applications/diffusion/exception/DiffusionDaemonLockException.php', + 'DiffusionDatasourceEngineExtension' => 'applications/diffusion/engineextension/DiffusionDatasourceEngineExtension.php', 'DiffusionDefaultEditCapability' => 'applications/diffusion/capability/DiffusionDefaultEditCapability.php', 'DiffusionDefaultPushCapability' => 'applications/diffusion/capability/DiffusionDefaultPushCapability.php', 'DiffusionDefaultViewCapability' => 'applications/diffusion/capability/DiffusionDefaultViewCapability.php', @@ -844,7 +845,6 @@ phutil_register_library_map(array( 'DiffusionQueryCommitsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionQueryCommitsConduitAPIMethod.php', 'DiffusionQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionQueryConduitAPIMethod.php', 'DiffusionQueryPathsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionQueryPathsConduitAPIMethod.php', - 'DiffusionQuickSearchEngineExtension' => 'applications/diffusion/engineextension/DiffusionQuickSearchEngineExtension.php', 'DiffusionRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionRawDiffQuery.php', 'DiffusionRawDiffQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionRawDiffQueryConduitAPIMethod.php', 'DiffusionReadmeView' => 'applications/diffusion/view/DiffusionReadmeView.php', @@ -2745,8 +2745,11 @@ phutil_register_library_map(array( 'PhabricatorDatabaseRef' => 'infrastructure/cluster/PhabricatorDatabaseRef.php', 'PhabricatorDatabaseRefParser' => 'infrastructure/cluster/PhabricatorDatabaseRefParser.php', 'PhabricatorDatabaseSetupCheck' => 'applications/config/check/PhabricatorDatabaseSetupCheck.php', + 'PhabricatorDatasourceApplicationEngineExtension' => 'applications/meta/engineextension/PhabricatorDatasourceApplicationEngineExtension.php', 'PhabricatorDatasourceEditField' => 'applications/transactions/editfield/PhabricatorDatasourceEditField.php', 'PhabricatorDatasourceEditType' => 'applications/transactions/edittype/PhabricatorDatasourceEditType.php', + 'PhabricatorDatasourceEngine' => 'applications/search/engine/PhabricatorDatasourceEngine.php', + 'PhabricatorDatasourceEngineExtension' => 'applications/search/engineextension/PhabricatorDatasourceEngineExtension.php', 'PhabricatorDateFormatSetting' => 'applications/settings/setting/PhabricatorDateFormatSetting.php', 'PhabricatorDateTimeSettingsPanel' => 'applications/settings/panel/PhabricatorDateTimeSettingsPanel.php', 'PhabricatorDebugController' => 'applications/system/controller/PhabricatorDebugController.php', @@ -3316,7 +3319,7 @@ phutil_register_library_map(array( 'PhabricatorMetronomicTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorMetronomicTriggerClock.php', 'PhabricatorModularTransaction' => 'applications/transactions/storage/PhabricatorModularTransaction.php', 'PhabricatorModularTransactionType' => 'applications/transactions/storage/PhabricatorModularTransactionType.php', - 'PhabricatorMonogramQuickSearchEngineExtension' => 'applications/typeahead/engineextension/PhabricatorMonogramQuickSearchEngineExtension.php', + 'PhabricatorMonogramDatasourceEngineExtension' => 'applications/typeahead/engineextension/PhabricatorMonogramDatasourceEngineExtension.php', 'PhabricatorMonospacedFontSetting' => 'applications/settings/setting/PhabricatorMonospacedFontSetting.php', 'PhabricatorMonospacedTextareasSetting' => 'applications/settings/setting/PhabricatorMonospacedTextareasSetting.php', 'PhabricatorMotivatorProfileMenuItem' => 'applications/search/menuitem/PhabricatorMotivatorProfileMenuItem.php', @@ -3615,6 +3618,7 @@ phutil_register_library_map(array( 'PhabricatorPeopleCreateController' => 'applications/people/controller/PhabricatorPeopleCreateController.php', 'PhabricatorPeopleCreateGuidanceContext' => 'applications/people/guidance/PhabricatorPeopleCreateGuidanceContext.php', 'PhabricatorPeopleDatasource' => 'applications/people/typeahead/PhabricatorPeopleDatasource.php', + 'PhabricatorPeopleDatasourceEngineExtension' => 'applications/people/engineextension/PhabricatorPeopleDatasourceEngineExtension.php', 'PhabricatorPeopleDeleteController' => 'applications/people/controller/PhabricatorPeopleDeleteController.php', 'PhabricatorPeopleDetailsProfileMenuItem' => 'applications/people/menuitem/PhabricatorPeopleDetailsProfileMenuItem.php', 'PhabricatorPeopleDisableController' => 'applications/people/controller/PhabricatorPeopleDisableController.php', @@ -3647,7 +3651,6 @@ phutil_register_library_map(array( 'PhabricatorPeopleProfileTasksController' => 'applications/people/controller/PhabricatorPeopleProfileTasksController.php', 'PhabricatorPeopleProfileViewController' => 'applications/people/controller/PhabricatorPeopleProfileViewController.php', 'PhabricatorPeopleQuery' => 'applications/people/query/PhabricatorPeopleQuery.php', - 'PhabricatorPeopleQuickSearchEngineExtension' => 'applications/people/engineextension/PhabricatorPeopleQuickSearchEngineExtension.php', 'PhabricatorPeopleRenameController' => 'applications/people/controller/PhabricatorPeopleRenameController.php', 'PhabricatorPeopleRevisionsProfileMenuItem' => 'applications/people/menuitem/PhabricatorPeopleRevisionsProfileMenuItem.php', 'PhabricatorPeopleSearchEngine' => 'applications/people/query/PhabricatorPeopleSearchEngine.php', @@ -3910,8 +3913,6 @@ phutil_register_library_map(array( 'PhabricatorQueryOrderItem' => 'infrastructure/query/order/PhabricatorQueryOrderItem.php', 'PhabricatorQueryOrderTestCase' => 'infrastructure/query/order/__tests__/PhabricatorQueryOrderTestCase.php', 'PhabricatorQueryOrderVector' => 'infrastructure/query/order/PhabricatorQueryOrderVector.php', - 'PhabricatorQuickSearchApplicationEngineExtension' => 'applications/meta/engineextension/PhabricatorQuickSearchApplicationEngineExtension.php', - 'PhabricatorQuickSearchEngine' => 'applications/search/engine/PhabricatorQuickSearchEngine.php', 'PhabricatorQuickSearchEngineExtension' => 'applications/search/engineextension/PhabricatorQuickSearchEngineExtension.php', 'PhabricatorRateLimitRequestExceptionHandler' => 'aphront/handler/PhabricatorRateLimitRequestExceptionHandler.php', 'PhabricatorRecaptchaConfigOptions' => 'applications/config/option/PhabricatorRecaptchaConfigOptions.php', @@ -4950,12 +4951,12 @@ phutil_register_library_map(array( 'ProjectConduitAPIMethod' => 'applications/project/conduit/ProjectConduitAPIMethod.php', 'ProjectCreateConduitAPIMethod' => 'applications/project/conduit/ProjectCreateConduitAPIMethod.php', 'ProjectCreateProjectsCapability' => 'applications/project/capability/ProjectCreateProjectsCapability.php', + 'ProjectDatasourceEngineExtension' => 'applications/project/engineextension/ProjectDatasourceEngineExtension.php', 'ProjectDefaultEditCapability' => 'applications/project/capability/ProjectDefaultEditCapability.php', 'ProjectDefaultJoinCapability' => 'applications/project/capability/ProjectDefaultJoinCapability.php', 'ProjectDefaultViewCapability' => 'applications/project/capability/ProjectDefaultViewCapability.php', 'ProjectEditConduitAPIMethod' => 'applications/project/conduit/ProjectEditConduitAPIMethod.php', 'ProjectQueryConduitAPIMethod' => 'applications/project/conduit/ProjectQueryConduitAPIMethod.php', - 'ProjectQuickSearchEngineExtension' => 'applications/project/engineextension/ProjectQuickSearchEngineExtension.php', 'ProjectRemarkupRule' => 'applications/project/remarkup/ProjectRemarkupRule.php', 'ProjectRemarkupRuleTestCase' => 'applications/project/remarkup/__tests__/ProjectRemarkupRuleTestCase.php', 'ProjectReplyHandler' => 'applications/project/mail/ProjectReplyHandler.php', @@ -5901,6 +5902,7 @@ phutil_register_library_map(array( 'DiffusionController' => 'PhabricatorController', 'DiffusionCreateRepositoriesCapability' => 'PhabricatorPolicyCapability', 'DiffusionDaemonLockException' => 'Exception', + 'DiffusionDatasourceEngineExtension' => 'PhabricatorDatasourceEngineExtension', 'DiffusionDefaultEditCapability' => 'PhabricatorPolicyCapability', 'DiffusionDefaultPushCapability' => 'PhabricatorPolicyCapability', 'DiffusionDefaultViewCapability' => 'PhabricatorPolicyCapability', @@ -6032,7 +6034,6 @@ phutil_register_library_map(array( 'DiffusionQueryCommitsConduitAPIMethod' => 'DiffusionConduitAPIMethod', 'DiffusionQueryConduitAPIMethod' => 'DiffusionConduitAPIMethod', 'DiffusionQueryPathsConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', - 'DiffusionQuickSearchEngineExtension' => 'PhabricatorQuickSearchEngineExtension', 'DiffusionRawDiffQuery' => 'DiffusionFileFutureQuery', 'DiffusionRawDiffQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionReadmeView' => 'DiffusionView', @@ -8254,8 +8255,11 @@ phutil_register_library_map(array( 'PhabricatorDatabaseRef' => 'Phobject', 'PhabricatorDatabaseRefParser' => 'Phobject', 'PhabricatorDatabaseSetupCheck' => 'PhabricatorSetupCheck', + 'PhabricatorDatasourceApplicationEngineExtension' => 'PhabricatorDatasourceEngineExtension', 'PhabricatorDatasourceEditField' => 'PhabricatorTokenizerEditField', 'PhabricatorDatasourceEditType' => 'PhabricatorPHIDListEditType', + 'PhabricatorDatasourceEngine' => 'Phobject', + 'PhabricatorDatasourceEngineExtension' => 'Phobject', 'PhabricatorDateFormatSetting' => 'PhabricatorSelectSetting', 'PhabricatorDateTimeSettingsPanel' => 'PhabricatorEditEngineSettingsPanel', 'PhabricatorDebugController' => 'PhabricatorController', @@ -8880,7 +8884,7 @@ phutil_register_library_map(array( 'PhabricatorMetronomicTriggerClock' => 'PhabricatorTriggerClock', 'PhabricatorModularTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorModularTransactionType' => 'Phobject', - 'PhabricatorMonogramQuickSearchEngineExtension' => 'PhabricatorQuickSearchEngineExtension', + 'PhabricatorMonogramDatasourceEngineExtension' => 'PhabricatorDatasourceEngineExtension', 'PhabricatorMonospacedFontSetting' => 'PhabricatorStringSetting', 'PhabricatorMonospacedTextareasSetting' => 'PhabricatorSelectSetting', 'PhabricatorMotivatorProfileMenuItem' => 'PhabricatorProfileMenuItem', @@ -9248,6 +9252,7 @@ phutil_register_library_map(array( 'PhabricatorPeopleCreateController' => 'PhabricatorPeopleController', 'PhabricatorPeopleCreateGuidanceContext' => 'PhabricatorGuidanceContext', 'PhabricatorPeopleDatasource' => 'PhabricatorTypeaheadDatasource', + 'PhabricatorPeopleDatasourceEngineExtension' => 'PhabricatorDatasourceEngineExtension', 'PhabricatorPeopleDeleteController' => 'PhabricatorPeopleController', 'PhabricatorPeopleDetailsProfileMenuItem' => 'PhabricatorProfileMenuItem', 'PhabricatorPeopleDisableController' => 'PhabricatorPeopleController', @@ -9280,7 +9285,6 @@ phutil_register_library_map(array( 'PhabricatorPeopleProfileTasksController' => 'PhabricatorPeopleProfileController', 'PhabricatorPeopleProfileViewController' => 'PhabricatorPeopleProfileController', 'PhabricatorPeopleQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', - 'PhabricatorPeopleQuickSearchEngineExtension' => 'PhabricatorQuickSearchEngineExtension', 'PhabricatorPeopleRenameController' => 'PhabricatorPeopleController', 'PhabricatorPeopleRevisionsProfileMenuItem' => 'PhabricatorProfileMenuItem', 'PhabricatorPeopleSearchEngine' => 'PhabricatorApplicationSearchEngine', @@ -9598,9 +9602,7 @@ phutil_register_library_map(array( 'Phobject', 'Iterator', ), - 'PhabricatorQuickSearchApplicationEngineExtension' => 'PhabricatorQuickSearchEngineExtension', - 'PhabricatorQuickSearchEngine' => 'Phobject', - 'PhabricatorQuickSearchEngineExtension' => 'Phobject', + 'PhabricatorQuickSearchEngineExtension' => 'PhabricatorDatasourceEngineExtension', 'PhabricatorRateLimitRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler', 'PhabricatorRecaptchaConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorRedirectController' => 'PhabricatorController', @@ -10893,12 +10895,12 @@ phutil_register_library_map(array( 'ProjectConduitAPIMethod' => 'ConduitAPIMethod', 'ProjectCreateConduitAPIMethod' => 'ProjectConduitAPIMethod', 'ProjectCreateProjectsCapability' => 'PhabricatorPolicyCapability', + 'ProjectDatasourceEngineExtension' => 'PhabricatorDatasourceEngineExtension', 'ProjectDefaultEditCapability' => 'PhabricatorPolicyCapability', 'ProjectDefaultJoinCapability' => 'PhabricatorPolicyCapability', 'ProjectDefaultViewCapability' => 'PhabricatorPolicyCapability', 'ProjectEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'ProjectQueryConduitAPIMethod' => 'ProjectConduitAPIMethod', - 'ProjectQuickSearchEngineExtension' => 'PhabricatorQuickSearchEngineExtension', 'ProjectRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'ProjectRemarkupRuleTestCase' => 'PhabricatorTestCase', 'ProjectReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', diff --git a/src/applications/diffusion/engineextension/DiffusionQuickSearchEngineExtension.php b/src/applications/diffusion/engineextension/DiffusionDatasourceEngineExtension.php similarity index 63% rename from src/applications/diffusion/engineextension/DiffusionQuickSearchEngineExtension.php rename to src/applications/diffusion/engineextension/DiffusionDatasourceEngineExtension.php index 80c705b4f8..fdfe2d02e1 100644 --- a/src/applications/diffusion/engineextension/DiffusionQuickSearchEngineExtension.php +++ b/src/applications/diffusion/engineextension/DiffusionDatasourceEngineExtension.php @@ -1,7 +1,7 @@ setAncestorClass(__CLASS__) + ->execute(); + + $datasources = array(); + foreach ($extensions as $extension) { + $datasources[] = $extension->newQuickSearchDatasources(); + } + + return array_mergev($datasources); + } +} diff --git a/src/applications/search/engineextension/PhabricatorQuickSearchEngineExtension.php b/src/applications/search/engineextension/PhabricatorQuickSearchEngineExtension.php index 43f0b4c3ca..082eb77e70 100644 --- a/src/applications/search/engineextension/PhabricatorQuickSearchEngineExtension.php +++ b/src/applications/search/engineextension/PhabricatorQuickSearchEngineExtension.php @@ -1,18 +1,6 @@ setAncestorClass(__CLASS__) - ->execute(); - - $datasources = array(); - foreach ($extensions as $extension) { - $datasources[] = $extension->newQuickSearchDatasources(); - } - return array_mergev($datasources); - } -} +// TODO: This is an older name "PhabricatorDatasourceEngineExtension" purely +// to preserve compatibility that should be removed soon. +abstract class PhabricatorQuickSearchEngineExtension + extends PhabricatorDatasourceEngineExtension {} diff --git a/src/applications/search/typeahead/PhabricatorSearchDatasource.php b/src/applications/search/typeahead/PhabricatorSearchDatasource.php index ed17a31185..2d8182a2a2 100644 --- a/src/applications/search/typeahead/PhabricatorSearchDatasource.php +++ b/src/applications/search/typeahead/PhabricatorSearchDatasource.php @@ -16,8 +16,8 @@ final class PhabricatorSearchDatasource } public function getComponentDatasources() { - $sources = id(new PhabricatorQuickSearchEngine()) - ->getAllDatasources(); + $sources = id(new PhabricatorDatasourceEngine()) + ->getAllQuickSearchDatasources(); // These results are always rendered in the full browse display mode, so // set the browse flag on all component sources. diff --git a/src/applications/typeahead/engineextension/PhabricatorMonogramQuickSearchEngineExtension.php b/src/applications/typeahead/engineextension/PhabricatorMonogramDatasourceEngineExtension.php similarity index 56% rename from src/applications/typeahead/engineextension/PhabricatorMonogramQuickSearchEngineExtension.php rename to src/applications/typeahead/engineextension/PhabricatorMonogramDatasourceEngineExtension.php index 078cb44685..694236cae4 100644 --- a/src/applications/typeahead/engineextension/PhabricatorMonogramQuickSearchEngineExtension.php +++ b/src/applications/typeahead/engineextension/PhabricatorMonogramDatasourceEngineExtension.php @@ -1,7 +1,7 @@ Date: Wed, 14 Feb 2018 15:49:00 -0800 Subject: [PATCH 33/58] Modularize the "jump nav" behaviors in global search Summary: Depends on D19087. Ref T13079. This still doesn't feel like the most clean, general system in the world, but is a step forward from hard-coded `switch()` stuff. Test Plan: - Jumped to `r`. - Jumped to `a`. - Jumped to `r poe` (multiple results). - Jumped to `r poetry` (one result). - Jumped to `r syzygy` (no results). - Jumped to `p`. - Jumped to `p robot` (multiple results); `p assessment` (one result). - The behavior for `p ` has changed slightly but should be more powerful now (it's consistent with `r `). - Jumped to `s ` and `s ->`. - Jumped to `d`. - Jumped to `f`. - Jumped to `t`. - Jumped to `T123`, `D123`, `@dog`, `PHID-DREV-abcd`, etc. Maniphest Tasks: T13079 Differential Revision: https://secure.phabricator.com/D19088 --- src/__phutil_library_map__.php | 2 - .../DiffusionDatasourceEngineExtension.php | 70 +++++++++ ...ricatorPeopleDatasourceEngineExtension.php | 25 ++++ .../ProjectDatasourceEngineExtension.php | 46 ++++++ .../PhabricatorSearchController.php | 18 +-- .../engine/PhabricatorDatasourceEngine.php | 29 ++++ .../engine/PhabricatorJumpNavHandler.php | 135 ------------------ .../PhabricatorDatasourceEngineExtension.php | 27 +++- ...catorMonogramDatasourceEngineExtension.php | 42 ++++++ 9 files changed, 246 insertions(+), 148 deletions(-) delete mode 100644 src/applications/search/engine/PhabricatorJumpNavHandler.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 771658e32e..22437bc182 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3154,7 +3154,6 @@ phutil_register_library_map(array( 'PhabricatorJSONExportFormat' => 'infrastructure/export/format/PhabricatorJSONExportFormat.php', 'PhabricatorJavelinLinter' => 'infrastructure/lint/linter/PhabricatorJavelinLinter.php', 'PhabricatorJiraIssueHasObjectEdgeType' => 'applications/doorkeeper/edge/PhabricatorJiraIssueHasObjectEdgeType.php', - 'PhabricatorJumpNavHandler' => 'applications/search/engine/PhabricatorJumpNavHandler.php', 'PhabricatorKeyValueDatabaseCache' => 'applications/cache/PhabricatorKeyValueDatabaseCache.php', 'PhabricatorKeyValueSerializingCacheProxy' => 'applications/cache/PhabricatorKeyValueSerializingCacheProxy.php', 'PhabricatorKeyboardRemarkupRule' => 'infrastructure/markup/rule/PhabricatorKeyboardRemarkupRule.php', @@ -8708,7 +8707,6 @@ phutil_register_library_map(array( 'PhabricatorJSONExportFormat' => 'PhabricatorExportFormat', 'PhabricatorJavelinLinter' => 'ArcanistLinter', 'PhabricatorJiraIssueHasObjectEdgeType' => 'PhabricatorEdgeType', - 'PhabricatorJumpNavHandler' => 'Phobject', 'PhabricatorKeyValueDatabaseCache' => 'PhutilKeyValueCache', 'PhabricatorKeyValueSerializingCacheProxy' => 'PhutilKeyValueCacheProxy', 'PhabricatorKeyboardRemarkupRule' => 'PhutilRemarkupRule', diff --git a/src/applications/diffusion/engineextension/DiffusionDatasourceEngineExtension.php b/src/applications/diffusion/engineextension/DiffusionDatasourceEngineExtension.php index fdfe2d02e1..9678b627b1 100644 --- a/src/applications/diffusion/engineextension/DiffusionDatasourceEngineExtension.php +++ b/src/applications/diffusion/engineextension/DiffusionDatasourceEngineExtension.php @@ -9,4 +9,74 @@ final class DiffusionDatasourceEngineExtension new DiffusionSymbolDatasource(), ); } + + public function newJumpURI($query) { + $viewer = $this->getViewer(); + + // Send "r" to Diffusion. + if (preg_match('/^r\z/i', $query)) { + return '/diffusion/'; + } + + // Send "a" to the commit list ("Audit"). + if (preg_match('/^a\z/i', $query)) { + return '/diffusion/commit/'; + } + + // Send "r " to a search for a matching repository. + $matches = null; + if (preg_match('/^r\s+(.+)\z/i', $query, $matches)) { + $raw_query = $matches[1]; + + $engine = id(new PhabricatorRepository()) + ->newFerretEngine(); + + $compiler = id(new PhutilSearchQueryCompiler()) + ->setEnableFunctions(true); + + $raw_tokens = $compiler->newTokens($raw_query); + + $fulltext_tokens = array(); + foreach ($raw_tokens as $raw_token) { + $fulltext_token = id(new PhabricatorFulltextToken()) + ->setToken($raw_token); + $fulltext_tokens[] = $fulltext_token; + } + + $repositories = id(new PhabricatorRepositoryQuery()) + ->setViewer($viewer) + ->withFerretConstraint($engine, $fulltext_tokens) + ->execute(); + if (count($repositories) == 1) { + // Just one match, jump to repository. + return head($repositories)->getURI(); + } else { + // More than one match, jump to search. + return urisprintf( + '/diffusion/?order=relevance&query=%s#R', + $raw_query); + } + } + + // Send "s " to a symbol search. + $matches = null; + if (preg_match('/^s\s+(.+)\z/i', $query, $matches)) { + $symbol = $matches[1]; + + $parts = null; + if (preg_match('/(.*)(?:\\.|::|->)(.*)/', $symbol, $parts)) { + return urisprintf( + '/diffusion/symbol/%s/?jump=true&context=%s', + $parts[2], + $parts[1]); + } else { + return urisprintf( + '/diffusion/symbol/%s/?jump=true', + $symbol); + } + } + + return null; + } + } diff --git a/src/applications/people/engineextension/PhabricatorPeopleDatasourceEngineExtension.php b/src/applications/people/engineextension/PhabricatorPeopleDatasourceEngineExtension.php index b9de70b99d..dc8599554e 100644 --- a/src/applications/people/engineextension/PhabricatorPeopleDatasourceEngineExtension.php +++ b/src/applications/people/engineextension/PhabricatorPeopleDatasourceEngineExtension.php @@ -8,4 +8,29 @@ final class PhabricatorPeopleDatasourceEngineExtension new PhabricatorPeopleDatasource(), ); } + + public function newJumpURI($query) { + $viewer = $this->getViewer(); + + // Send "u" to the user directory. + if (preg_match('/^u\z/i', $query)) { + return '/people/'; + } + + // Send "u " to the user's profile page. + $matches = null; + if (preg_match('/^u\s+(.+)\z/i', $query, $matches)) { + $raw_query = $matches[1]; + + // TODO: We could test that this is a valid username and jump to + // a search in the user directory if it isn't. + + return urisprintf( + '/p/%s/', + $raw_query); + } + + return null; + } + } diff --git a/src/applications/project/engineextension/ProjectDatasourceEngineExtension.php b/src/applications/project/engineextension/ProjectDatasourceEngineExtension.php index 6500db9b62..b566d58f93 100644 --- a/src/applications/project/engineextension/ProjectDatasourceEngineExtension.php +++ b/src/applications/project/engineextension/ProjectDatasourceEngineExtension.php @@ -8,4 +8,50 @@ final class ProjectDatasourceEngineExtension new PhabricatorProjectDatasource(), ); } + + public function newJumpURI($query) { + $viewer = $this->getViewer(); + + // Send "p" to Projects. + if (preg_match('/^p\z/i', $query)) { + return '/diffusion/'; + } + + // Send "p " to a search for similar projects. + $matches = null; + if (preg_match('/^p\s+(.+)\z/i', $query, $matches)) { + $raw_query = $matches[1]; + + $engine = id(new PhabricatorProject()) + ->newFerretEngine(); + + $compiler = id(new PhutilSearchQueryCompiler()) + ->setEnableFunctions(true); + + $raw_tokens = $compiler->newTokens($raw_query); + + $fulltext_tokens = array(); + foreach ($raw_tokens as $raw_token) { + $fulltext_token = id(new PhabricatorFulltextToken()) + ->setToken($raw_token); + $fulltext_tokens[] = $fulltext_token; + } + + $projects = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->withFerretConstraint($engine, $fulltext_tokens) + ->execute(); + if (count($projects) == 1) { + // Just one match, jump to project. + return head($projects)->getURI(); + } else { + // More than one match, jump to search. + return urisprintf( + '/project/?order=relevance&query=%s#R', + $raw_query); + } + } + + return null; + } } diff --git a/src/applications/search/controller/PhabricatorSearchController.php b/src/applications/search/controller/PhabricatorSearchController.php index a1f6fd68b5..b3d3ab18fa 100644 --- a/src/applications/search/controller/PhabricatorSearchController.php +++ b/src/applications/search/controller/PhabricatorSearchController.php @@ -11,13 +11,15 @@ final class PhabricatorSearchController public function handleRequest(AphrontRequest $request) { $viewer = $this->getViewer(); + $query = $request->getStr('query'); - if ($request->getStr('jump') != 'no') { - $response = PhabricatorJumpNavHandler::getJumpResponse( - $viewer, - $request->getStr('query')); - if ($response) { - return $response; + if ($request->getStr('jump') != 'no' && strlen($query)) { + $jump_uri = id(new PhabricatorDatasourceEngine()) + ->setViewer($viewer) + ->newJumpURI($query); + + if ($jump_uri !== null) { + return id(new AphrontRedirectResponse())->setURI($jump_uri); } } @@ -29,7 +31,7 @@ final class PhabricatorSearchController if ($request->getBool('search:primary')) { // If there's no query, just take the user to advanced search. - if (!strlen($request->getStr('query'))) { + if (!strlen($query)) { $advanced_uri = '/search/query/advanced/'; return id(new AphrontRedirectResponse())->setURI($advanced_uri); } @@ -71,7 +73,7 @@ final class PhabricatorSearchController // Add the user's query, then save this as a new saved query and send // the user to the results page. - $saved->setParameter('query', $request->getStr('query')); + $saved->setParameter('query', $query); $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); try { diff --git a/src/applications/search/engine/PhabricatorDatasourceEngine.php b/src/applications/search/engine/PhabricatorDatasourceEngine.php index 2f8103ce12..62304d26aa 100644 --- a/src/applications/search/engine/PhabricatorDatasourceEngine.php +++ b/src/applications/search/engine/PhabricatorDatasourceEngine.php @@ -2,7 +2,36 @@ final class PhabricatorDatasourceEngine extends Phobject { + private $viewer; + + public function setViewer(PhabricatorUser $viewer) { + $this->viewer = $viewer; + return $this; + } + + public function getViewer() { + return $this->viewer; + } + public function getAllQuickSearchDatasources() { return PhabricatorDatasourceEngineExtension::getAllQuickSearchDatasources(); } + + public function newJumpURI($query) { + $viewer = $this->getViewer(); + $extensions = PhabricatorDatasourceEngineExtension::getAllExtensions(); + + foreach ($extensions as $extension) { + $jump_uri = id(clone $extension) + ->setViewer($viewer) + ->newJumpURI($query); + + if ($jump_uri !== null) { + return $jump_uri; + } + } + + return null; + } + } diff --git a/src/applications/search/engine/PhabricatorJumpNavHandler.php b/src/applications/search/engine/PhabricatorJumpNavHandler.php deleted file mode 100644 index ddade36d84..0000000000 --- a/src/applications/search/engine/PhabricatorJumpNavHandler.php +++ /dev/null @@ -1,135 +0,0 @@ - 'uri:/diffusion/commit/', - '/^f$/i' => 'uri:/feed/', - '/^d$/i' => 'uri:/differential/', - '/^r$/i' => 'uri:/diffusion/', - '/^t$/i' => 'uri:/maniphest/', - '/^p$/i' => 'uri:/project/', - '/^u$/i' => 'uri:/people/', - '/^p\s+(.+)$/i' => 'project', - '/^u\s+(\S+)$/i' => 'user', - '/^(?:s)\s+(\S+)/i' => 'find-symbol', - '/^r\s+(.+)$/i' => 'find-repository', - ); - - foreach ($patterns as $pattern => $effect) { - $matches = null; - if (preg_match($pattern, $jump, $matches)) { - if (!strncmp($effect, 'uri:', 4)) { - return id(new AphrontRedirectResponse()) - ->setURI(substr($effect, 4)); - } else { - switch ($effect) { - case 'user': - return id(new AphrontRedirectResponse()) - ->setURI('/p/'.$matches[1].'/'); - case 'project': - $project = self::findCloselyNamedProject($matches[1]); - if ($project) { - return id(new AphrontRedirectResponse()) - ->setURI('/project/view/'.$project->getID().'/'); - } else { - $jump = $matches[1]; - } - break; - case 'find-symbol': - $context = ''; - $symbol = $matches[1]; - $parts = array(); - if (preg_match('/(.*)(?:\\.|::|->)(.*)/', $symbol, $parts)) { - $context = '&context='.phutil_escape_uri($parts[1]); - $symbol = $parts[2]; - } - return id(new AphrontRedirectResponse()) - ->setURI("/diffusion/symbol/$symbol/?jump=true$context"); - case 'find-repository': - $raw_query = $matches[1]; - - $engine = id(new PhabricatorRepository()) - ->newFerretEngine(); - - $compiler = id(new PhutilSearchQueryCompiler()) - ->setEnableFunctions(true); - - $raw_tokens = $compiler->newTokens($raw_query); - - $fulltext_tokens = array(); - foreach ($raw_tokens as $raw_token) { - $fulltext_token = id(new PhabricatorFulltextToken()) - ->setToken($raw_token); - $fulltext_tokens[] = $fulltext_token; - } - - $repositories = id(new PhabricatorRepositoryQuery()) - ->setViewer($viewer) - ->withFerretConstraint($engine, $fulltext_tokens) - ->execute(); - if (count($repositories) == 1) { - // Just one match, jump to repository. - $uri = head($repositories)->getURI(); - } else { - // More than one match, jump to search. - $uri = urisprintf( - '/diffusion/?order=name&query=%s', - $raw_query); - } - return id(new AphrontRedirectResponse())->setURI($uri); - default: - throw new Exception(pht("Unknown jump effect '%s'!", $effect)); - } - } - } - } - - // If none of the patterns matched, look for an object by name. - $objects = id(new PhabricatorObjectQuery()) - ->setViewer($viewer) - ->withNames(array($jump)) - ->execute(); - - if (count($objects) == 1) { - $handle = id(new PhabricatorHandleQuery()) - ->setViewer($viewer) - ->withPHIDs(mpull($objects, 'getPHID')) - ->executeOne(); - - return id(new AphrontRedirectResponse())->setURI($handle->getURI()); - } - - return null; - } - - private static function findCloselyNamedProject($name) { - $project = id(new PhabricatorProject())->loadOneWhere( - 'name = %s', - $name); - if ($project) { - return $project; - } else { // no exact match, try a fuzzy match - $projects = id(new PhabricatorProject())->loadAllWhere( - 'name LIKE %~', - $name); - if ($projects) { - $min_name_length = PHP_INT_MAX; - $best_project = null; - foreach ($projects as $project) { - $name_length = strlen($project->getName()); - if ($name_length <= $min_name_length) { - $min_name_length = $name_length; - $best_project = $project; - } - } - return $best_project; - } else { - return null; - } - } - } -} diff --git a/src/applications/search/engineextension/PhabricatorDatasourceEngineExtension.php b/src/applications/search/engineextension/PhabricatorDatasourceEngineExtension.php index 50758bc49d..be015526da 100644 --- a/src/applications/search/engineextension/PhabricatorDatasourceEngineExtension.php +++ b/src/applications/search/engineextension/PhabricatorDatasourceEngineExtension.php @@ -2,12 +2,33 @@ abstract class PhabricatorDatasourceEngineExtension extends Phobject { - abstract public function newQuickSearchDatasources(); + private $viewer; - final public static function getAllQuickSearchDatasources() { - $extensions = id(new PhutilClassMapQuery()) + final public function setViewer(PhabricatorUser $viewer) { + $this->viewer = $viewer; + return $this; + } + + final public function getViewer() { + return $this->viewer; + } + + public function newQuickSearchDatasources() { + return array(); + } + + public function newJumpURI($query) { + return null; + } + + final public static function getAllExtensions() { + return id(new PhutilClassMapQuery()) ->setAncestorClass(__CLASS__) ->execute(); + } + + final public static function getAllQuickSearchDatasources() { + $extensions = self::getAllExtensions(); $datasources = array(); foreach ($extensions as $extension) { diff --git a/src/applications/typeahead/engineextension/PhabricatorMonogramDatasourceEngineExtension.php b/src/applications/typeahead/engineextension/PhabricatorMonogramDatasourceEngineExtension.php index 694236cae4..ec34538dd9 100644 --- a/src/applications/typeahead/engineextension/PhabricatorMonogramDatasourceEngineExtension.php +++ b/src/applications/typeahead/engineextension/PhabricatorMonogramDatasourceEngineExtension.php @@ -8,4 +8,46 @@ final class PhabricatorMonogramDatasourceEngineExtension new PhabricatorTypeaheadMonogramDatasource(), ); } + + public function newJumpURI($query) { + $viewer = $this->getViewer(); + + // These first few rules are sort of random but don't fit anywhere else + // today and don't feel worth adding separate extensions for. + + // Send "f" to Feed. + if (preg_match('/^f\z/i', $query)) { + return '/feed/'; + } + + // Send "d" to Differential. + if (preg_match('/^d\z/i', $query)) { + return '/differential/'; + } + + // Send "t" to Maniphest. + if (preg_match('/^t\z/i', $query)) { + return '/maniphest/'; + } + + // Otherwise, if the user entered an object name, jump to that object. + $objects = id(new PhabricatorObjectQuery()) + ->setViewer($viewer) + ->withNames(array($query)) + ->execute(); + if (count($objects) == 1) { + $object = head($objects); + $object_phid = $object->getPHID(); + + $handles = $viewer->loadHandles(array($object_phid)); + $handle = $handles[$object_phid]; + + if ($handle->isComplete()) { + return $handle->getURI(); + } + } + + return null; + } + } From d6edc3f4cc1d7962cde6476a589c9352454eeec4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 14 Feb 2018 16:53:21 -0800 Subject: [PATCH 34/58] Support evaluation of complex tokenizer functions Summary: Depends on D19088. Ref T13079. > Any sufficiently complicated C or Fortran program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp. > - Greenspun's Tenth Rule Move us a step closer to this noble goal. This doesn't implement any `viewer(project())` stuff but it looks like the API doesn't need to change to do that in the future. Test Plan: Grimmaced in pain. Maniphest Tasks: T13079 Differential Revision: https://secure.phabricator.com/D19089 --- src/__phutil_library_map__.php | 2 + .../PhabricatorTypeaheadDatasource.php | 51 ++++++++++++-- ...PhabricatorTypeaheadDatasourceTestCase.php | 42 ++++++++++++ ...bricatorTypeaheadTestNumbersDatasource.php | 67 +++++++++++++++++++ 4 files changed, 157 insertions(+), 5 deletions(-) create mode 100644 src/applications/typeahead/datasource/__tests__/PhabricatorTypeaheadTestNumbersDatasource.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 22437bc182..b23d3cb90c 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4381,6 +4381,7 @@ phutil_register_library_map(array( 'PhabricatorTypeaheadMonogramDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadMonogramDatasource.php', 'PhabricatorTypeaheadResult' => 'applications/typeahead/storage/PhabricatorTypeaheadResult.php', 'PhabricatorTypeaheadRuntimeCompositeDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadRuntimeCompositeDatasource.php', + 'PhabricatorTypeaheadTestNumbersDatasource' => 'applications/typeahead/datasource/__tests__/PhabricatorTypeaheadTestNumbersDatasource.php', 'PhabricatorTypeaheadTokenView' => 'applications/typeahead/view/PhabricatorTypeaheadTokenView.php', 'PhabricatorUIConfigOptions' => 'applications/config/option/PhabricatorUIConfigOptions.php', 'PhabricatorUIExample' => 'applications/uiexample/examples/PhabricatorUIExample.php', @@ -10159,6 +10160,7 @@ phutil_register_library_map(array( 'PhabricatorTypeaheadMonogramDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorTypeaheadResult' => 'Phobject', 'PhabricatorTypeaheadRuntimeCompositeDatasource' => 'PhabricatorTypeaheadCompositeDatasource', + 'PhabricatorTypeaheadTestNumbersDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorTypeaheadTokenView' => 'AphrontTagView', 'PhabricatorUIConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorUIExample' => 'Phobject', diff --git a/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php b/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php index 8b747281b3..2e369a3f67 100644 --- a/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php +++ b/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php @@ -406,7 +406,7 @@ abstract class PhabricatorTypeaheadDatasource extends Phobject { $results = $this->evaluateValues($results); foreach ($evaluate as $result_key => $function) { - $function = self::parseFunction($function); + $function = $this->parseFunction($function); if (!$function) { throw new PhabricatorTypeaheadInvalidTokenException(); } @@ -459,28 +459,69 @@ abstract class PhabricatorTypeaheadDatasource extends Phobject { /** * @task functions */ - public function parseFunction($token, $allow_partial = false) { + protected function parseFunction($token, $allow_partial = false) { $matches = null; if ($allow_partial) { - $ok = preg_match('/^([^(]+)\((.*?)\)?$/', $token, $matches); + $ok = preg_match('/^([^(]+)\((.*?)\)?\z/', $token, $matches); } else { - $ok = preg_match('/^([^(]+)\((.*)\)$/', $token, $matches); + $ok = preg_match('/^([^(]+)\((.*)\)\z/', $token, $matches); } if (!$ok) { + if (!$allow_partial) { + throw new PhabricatorTypeaheadInvalidTokenException( + pht( + 'Unable to parse function and arguments for token "%s".', + $token)); + } return null; } $function = trim($matches[1]); if (!$this->canEvaluateFunction($function)) { + if (!$allow_partial) { + throw new PhabricatorTypeaheadInvalidTokenException( + pht( + 'This datasource ("%s") can not evaluate the function "%s(...)".', + get_class($this), + $function)); + } + return null; } + // TODO: There is currently no way to quote characters in arguments, so + // some characters can't be argument characters. Replace this with a real + // parser once we get use cases. + + $argv = $matches[2]; + $argv = trim($argv); + if (!strlen($argv)) { + $argv = array(); + } else { + $argv = preg_split('/,/', $matches[2]); + foreach ($argv as $key => $arg) { + $argv[$key] = trim($arg); + } + } + + foreach ($argv as $key => $arg) { + if (self::isFunctionToken($arg)) { + $subfunction = $this->parseFunction($arg); + + $results = $this->evaluateFunction( + $subfunction['name'], + array($subfunction['argv'])); + + $argv[$key] = head($results); + } + } + return array( 'name' => $function, - 'argv' => array(trim($matches[2])), + 'argv' => $argv, ); } diff --git a/src/applications/typeahead/datasource/__tests__/PhabricatorTypeaheadDatasourceTestCase.php b/src/applications/typeahead/datasource/__tests__/PhabricatorTypeaheadDatasourceTestCase.php index 150275a332..c58596f025 100644 --- a/src/applications/typeahead/datasource/__tests__/PhabricatorTypeaheadDatasourceTestCase.php +++ b/src/applications/typeahead/datasource/__tests__/PhabricatorTypeaheadDatasourceTestCase.php @@ -48,4 +48,46 @@ final class PhabricatorTypeaheadDatasourceTestCase pht('Tokenization of "%s"', $input)); } + public function testFunctionEvaluation() { + $viewer = PhabricatorUser::getOmnipotentUser(); + + $datasource = id(new PhabricatorTypeaheadTestNumbersDatasource()) + ->setViewer($viewer); + + $constraints = $datasource->evaluateTokens( + array( + 9, + 'seven()', + 12, + 3, + )); + + $this->assertEqual( + array(9, 7, 12, 3), + $constraints); + + $map = array( + 'inc(3)' => 4, + 'sum(3, 4)' => 7, + 'inc(seven())' => 8, + 'inc(inc(3))' => 5, + 'inc(inc(seven()))' => 9, + 'sum(seven(), seven())' => 14, + 'sum(inc(seven()), inc(inc(9)))' => 19, + ); + + foreach ($map as $input => $expect) { + $constraints = $datasource->evaluateTokens( + array( + $input, + )); + + $this->assertEqual( + array($expect), + $constraints, + pht('Constraints for input "%s".', $input)); + } + } + + } diff --git a/src/applications/typeahead/datasource/__tests__/PhabricatorTypeaheadTestNumbersDatasource.php b/src/applications/typeahead/datasource/__tests__/PhabricatorTypeaheadTestNumbersDatasource.php new file mode 100644 index 0000000000..f16a64bb9b --- /dev/null +++ b/src/applications/typeahead/datasource/__tests__/PhabricatorTypeaheadTestNumbersDatasource.php @@ -0,0 +1,67 @@ + array(), + 'inc' => array(), + 'sum' => array(), + ); + } + + public function loadResults() { + return array(); + } + + public function renderFunctionTokens($function, array $argv_list) { + return array(); + } + + protected function evaluateFunction($function, array $argv_list) { + $results = array(); + + foreach ($argv_list as $argv) { + foreach ($argv as $k => $arg) { + if (!is_scalar($arg) || !preg_match('/^\d+\z/', $arg)) { + throw new PhabricatorTypeaheadInvalidTokenException( + pht( + 'All arguments to "%s(...)" must be integers, found '. + '"%s" in position %d.', + $function, + (is_scalar($arg) ? $arg : gettype($arg)), + $k + 1)); + } + $argv[$k] = (int)$arg; + } + + switch ($function) { + case 'seven': + $results[] = 7; + break; + case 'inc': + $results[] = $argv[0] + 1; + break; + case 'sum': + $results[] = array_sum($argv); + break; + } + } + + return $results; + } + +} From fe294d4034d7482eaf65a5b37804e667a860ce45 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 14 Feb 2018 17:41:43 -0800 Subject: [PATCH 35/58] Allow third-party code to extend upstream datasources via EngineExtension Summary: Depends on D19089. Fixes T13079. This is likely not the final form of this, but creates a defensible extension point. Test Plan: See T13079 for discussion. Maniphest Tasks: T13079 Differential Revision: https://secure.phabricator.com/D19090 --- .../engine/PhabricatorDatasourceEngine.php | 18 ++++++++++++++++++ .../PhabricatorDatasourceEngineExtension.php | 5 +++++ ...PhabricatorTypeaheadCompositeDatasource.php | 11 ++++++++++- 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/applications/search/engine/PhabricatorDatasourceEngine.php b/src/applications/search/engine/PhabricatorDatasourceEngine.php index 62304d26aa..a9cb766242 100644 --- a/src/applications/search/engine/PhabricatorDatasourceEngine.php +++ b/src/applications/search/engine/PhabricatorDatasourceEngine.php @@ -34,4 +34,22 @@ final class PhabricatorDatasourceEngine extends Phobject { return null; } + public function newDatasourcesForCompositeDatasource( + PhabricatorTypeaheadCompositeDatasource $datasource) { + $viewer = $this->getViewer(); + $extensions = PhabricatorDatasourceEngineExtension::getAllExtensions(); + + $sources = array(); + foreach ($extensions as $extension) { + $extension_sources = id(clone $extension) + ->setViewer($viewer) + ->newDatasourcesForCompositeDatasource($datasource); + foreach ($extension_sources as $extension_source) { + $sources[] = $extension_source; + } + } + + return $sources; + } + } diff --git a/src/applications/search/engineextension/PhabricatorDatasourceEngineExtension.php b/src/applications/search/engineextension/PhabricatorDatasourceEngineExtension.php index be015526da..c568e9e14d 100644 --- a/src/applications/search/engineextension/PhabricatorDatasourceEngineExtension.php +++ b/src/applications/search/engineextension/PhabricatorDatasourceEngineExtension.php @@ -21,6 +21,11 @@ abstract class PhabricatorDatasourceEngineExtension extends Phobject { return null; } + public function newDatasourcesForCompositeDatasource( + PhabricatorTypeaheadCompositeDatasource $datasource) { + return array(); + } + final public static function getAllExtensions() { return id(new PhutilClassMapQuery()) ->setAncestorClass(__CLASS__) diff --git a/src/applications/typeahead/datasource/PhabricatorTypeaheadCompositeDatasource.php b/src/applications/typeahead/datasource/PhabricatorTypeaheadCompositeDatasource.php index 33f06e4ae5..5594044c42 100644 --- a/src/applications/typeahead/datasource/PhabricatorTypeaheadCompositeDatasource.php +++ b/src/applications/typeahead/datasource/PhabricatorTypeaheadCompositeDatasource.php @@ -223,8 +223,17 @@ abstract class PhabricatorTypeaheadCompositeDatasource private function getUsableDatasources() { if ($this->usable === null) { + $viewer = $this->getViewer(); + $sources = $this->getComponentDatasources(); + $extension_sources = id(new PhabricatorDatasourceEngine()) + ->setViewer($viewer) + ->newDatasourcesForCompositeDatasource($this); + foreach ($extension_sources as $extension_source) { + $sources[] = $extension_source; + } + $usable = array(); foreach ($sources as $source) { $application_class = $source->getDatasourceApplicationClass(); @@ -239,7 +248,7 @@ abstract class PhabricatorTypeaheadCompositeDatasource } } - $source->setViewer($this->getViewer()); + $source->setViewer($viewer); $usable[] = $source; } $this->usable = $usable; From 2b0f98900bcbffba081d6e97f77bcee44b8a3a59 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 14 Feb 2018 19:04:46 -0800 Subject: [PATCH 36/58] Fail outstanding buildables with aborted builds Summary: Ref T13072. See PHI361. The bug in T10746 where aborting builds didn't propagate properly to the buildable was fixed, but existing builds are still stuck "Building". Since it doesn't look like anything will moot this before these changes promote to `stable`, just migrate these builds into "failed". Test Plan: Ran migration, saw it affect only relevant builds and correctly fail them. Maniphest Tasks: T13072 Differential Revision: https://secure.phabricator.com/D19091 --- .../20180214.harbor.01.aborted.php | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 resources/sql/autopatches/20180214.harbor.01.aborted.php diff --git a/resources/sql/autopatches/20180214.harbor.01.aborted.php b/resources/sql/autopatches/20180214.harbor.01.aborted.php new file mode 100644 index 0000000000..689d5625f5 --- /dev/null +++ b/resources/sql/autopatches/20180214.harbor.01.aborted.php @@ -0,0 +1,27 @@ +establishConnection('w'); + +foreach (new LiskMigrationIterator($table) as $buildable) { + if ($buildable->getBuildableStatus() !== 'building') { + continue; + } + + $aborted = queryfx_one( + $conn, + 'SELECT * FROM %T WHERE buildablePHID = %s AND buildStatus = %s', + id(new HarbormasterBuild())->getTableName(), + $buildable->getPHID(), + 'aborted'); + if (!$aborted) { + continue; + } + + queryfx( + $conn, + 'UPDATE %T SET buildableStatus = %s WHERE id = %d', + $table->getTableName(), + 'failed', + $buildable->getID()); +} From 463dda98edd3ec0c4604c1bb4a70a7b61c4a178d Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 15 Feb 2018 15:35:20 -0800 Subject: [PATCH 37/58] Render Drydock logs in text from "bin/drydock lease"; in HTML in web views Summary: Ref T13073. The new log output from `bin/drydock lease` currently uses HTML handle rendering, but should render to text. Test Plan: Ran `bin/drydock lease` and saw normal text in log output. Viewed the same logs from the web UI and saw HTML. Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T13073 Differential Revision: https://secure.phabricator.com/D19101 --- .../DrydockLeaseNoAuthorizationsLogType.php | 3 +- .../logtype/DrydockLeaseReclaimLogType.php | 4 +- ...DrydockLeaseWaitingForResourcesLogType.php | 4 +- .../drydock/logtype/DrydockLogType.php | 37 ++++++++++++++++++- .../logtype/DrydockResourceReclaimLogType.php | 3 +- .../DrydockManagementLeaseWorkflow.php | 2 +- .../drydock/view/DrydockLogListView.php | 2 +- 7 files changed, 41 insertions(+), 14 deletions(-) diff --git a/src/applications/drydock/logtype/DrydockLeaseNoAuthorizationsLogType.php b/src/applications/drydock/logtype/DrydockLeaseNoAuthorizationsLogType.php index b5a7ca1b13..cce98956a1 100644 --- a/src/applications/drydock/logtype/DrydockLeaseNoAuthorizationsLogType.php +++ b/src/applications/drydock/logtype/DrydockLeaseNoAuthorizationsLogType.php @@ -13,14 +13,13 @@ final class DrydockLeaseNoAuthorizationsLogType extends DrydockLogType { } public function renderLog(array $data) { - $viewer = $this->getViewer(); $authorizing_phid = idx($data, 'authorizingPHID'); return pht( 'The object which authorized this lease (%s) is not authorized to use '. 'any of the blueprints the lease lists. Approve the authorizations '. 'before using the lease.', - $viewer->renderHandle($authorizing_phid)->render()); + $this->renderHandle($authorizing_phid)); } } diff --git a/src/applications/drydock/logtype/DrydockLeaseReclaimLogType.php b/src/applications/drydock/logtype/DrydockLeaseReclaimLogType.php index 8dbc13d9d4..6e145a24ae 100644 --- a/src/applications/drydock/logtype/DrydockLeaseReclaimLogType.php +++ b/src/applications/drydock/logtype/DrydockLeaseReclaimLogType.php @@ -13,13 +13,11 @@ final class DrydockLeaseReclaimLogType extends DrydockLogType { } public function renderLog(array $data) { - $viewer = $this->getViewer(); - $resource_phids = idx($data, 'resourcePHIDs', array()); return pht( 'Reclaimed resource %s.', - $viewer->renderHandleList($resource_phids)->render()); + $this->renderHandleList($resource_phids)); } } diff --git a/src/applications/drydock/logtype/DrydockLeaseWaitingForResourcesLogType.php b/src/applications/drydock/logtype/DrydockLeaseWaitingForResourcesLogType.php index 46ab965b36..bb91acb030 100644 --- a/src/applications/drydock/logtype/DrydockLeaseWaitingForResourcesLogType.php +++ b/src/applications/drydock/logtype/DrydockLeaseWaitingForResourcesLogType.php @@ -13,13 +13,11 @@ final class DrydockLeaseWaitingForResourcesLogType extends DrydockLogType { } public function renderLog(array $data) { - $viewer = $this->getViewer(); - $blueprint_phids = idx($data, 'blueprintPHIDs', array()); return pht( 'Waiting for available resources from: %s.', - $viewer->renderHandleList($blueprint_phids)->render()); + $this->renderHandleList($blueprint_phids)); } } diff --git a/src/applications/drydock/logtype/DrydockLogType.php b/src/applications/drydock/logtype/DrydockLogType.php index 7faab42dfe..690ffc5670 100644 --- a/src/applications/drydock/logtype/DrydockLogType.php +++ b/src/applications/drydock/logtype/DrydockLogType.php @@ -4,17 +4,18 @@ abstract class DrydockLogType extends Phobject { private $viewer; private $log; + private $renderingMode = 'text'; abstract public function getLogTypeName(); abstract public function getLogTypeIcon(array $data); abstract public function renderLog(array $data); - public function setViewer(PhabricatorUser $viewer) { + final public function setViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; return $this; } - public function getViewer() { + final public function getViewer() { return $this->viewer; } @@ -38,4 +39,36 @@ abstract class DrydockLogType extends Phobject { ->execute(); } + final public function renderLogForText($data) { + $this->renderingMode = 'text'; + return $this->renderLog($data); + } + + final public function renderLogForHTML($data) { + $this->renderingMode = 'html'; + return $this->renderLog($data); + } + + final protected function renderHandle($phid) { + $viewer = $this->getViewer(); + $handle = $viewer->renderHandle($phid); + + if ($this->renderingMode == 'html') { + return $handle->render(); + } else { + return $handle->setAsText(true)->render(); + } + } + + final protected function renderHandleList(array $phids) { + $viewer = $this->getViewer(); + $handle_list = $viewer->renderHandleList($phids); + + if ($this->renderingMode == 'html') { + return $handle_list->render(); + } else { + return $handle_list->setAsText(true)->render(); + } + } + } diff --git a/src/applications/drydock/logtype/DrydockResourceReclaimLogType.php b/src/applications/drydock/logtype/DrydockResourceReclaimLogType.php index 9e9d5fdef3..aa989e97df 100644 --- a/src/applications/drydock/logtype/DrydockResourceReclaimLogType.php +++ b/src/applications/drydock/logtype/DrydockResourceReclaimLogType.php @@ -13,12 +13,11 @@ final class DrydockResourceReclaimLogType extends DrydockLogType { } public function renderLog(array $data) { - $viewer = $this->getViewer(); $reclaimer_phid = idx($data, 'reclaimerPHID'); return pht( 'Resource reclaimed by %s.', - $viewer->renderHandle($reclaimer_phid)->render()); + $this->renderHandle($reclaimer_phid)); } } diff --git a/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php b/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php index 8c3f682042..75636dab09 100644 --- a/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php +++ b/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php @@ -163,7 +163,7 @@ final class DrydockManagementLeaseWorkflow $log_data = $log->getData(); $type = $type_object->getLogTypeName(); - $data = $type_object->renderLog($log_data); + $data = $type_object->renderLogForText($log_data); } else { $type = pht('Unknown ("%s")', $type_key); $data = null; diff --git a/src/applications/drydock/view/DrydockLogListView.php b/src/applications/drydock/view/DrydockLogListView.php index cb66813507..61fde7cee7 100644 --- a/src/applications/drydock/view/DrydockLogListView.php +++ b/src/applications/drydock/view/DrydockLogListView.php @@ -52,7 +52,7 @@ final class DrydockLogListView extends AphrontView { $type = $type_object->getLogTypeName(); $icon = $type_object->getLogTypeIcon($log_data); - $data = $type_object->renderLog($log_data); + $data = $type_object->renderLogForHTML($log_data); $data = phutil_escape_html_newlines($data); } else { $type = pht('', $type_key); From e492c717c657092bb80203f6379f0a34fb048458 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 15 Feb 2018 04:07:32 -0800 Subject: [PATCH 38/58] Give PhrictionContent objects (older versions of wiki pages) legitimate PHIDs Summary: Ref T13077. Prepares for modern API access to document history using standard "v3" APIs. Test Plan: Ran migration, verified PHIDs appeared in the database. Created/edited a document, got even more PHIDs in the database. Maniphest Tasks: T13077 Differential Revision: https://secure.phabricator.com/D19092 --- .../20180215.phriction.01.phidcol.sql | 2 + .../20180215.phriction.02.phidvalues.php | 17 +++++++ src/__phutil_library_map__.php | 4 ++ .../phid/PhrictionContentPHIDType.php | 38 ++++++++++++++ .../phriction/query/PhrictionContentQuery.php | 51 +++++++++++++++++++ .../phriction/storage/PhrictionContent.php | 8 ++- 6 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 resources/sql/autopatches/20180215.phriction.01.phidcol.sql create mode 100644 resources/sql/autopatches/20180215.phriction.02.phidvalues.php create mode 100644 src/applications/phriction/phid/PhrictionContentPHIDType.php create mode 100644 src/applications/phriction/query/PhrictionContentQuery.php diff --git a/resources/sql/autopatches/20180215.phriction.01.phidcol.sql b/resources/sql/autopatches/20180215.phriction.01.phidcol.sql new file mode 100644 index 0000000000..658b05d9e1 --- /dev/null +++ b/resources/sql/autopatches/20180215.phriction.01.phidcol.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_phriction.phriction_content + ADD phid VARBINARY(64) NOT NULL; diff --git a/resources/sql/autopatches/20180215.phriction.02.phidvalues.php b/resources/sql/autopatches/20180215.phriction.02.phidvalues.php new file mode 100644 index 0000000000..c0a55cac85 --- /dev/null +++ b/resources/sql/autopatches/20180215.phriction.02.phidvalues.php @@ -0,0 +1,17 @@ +establishConnection('w'); + +foreach (new LiskMigrationIterator($table) as $row) { + if (strlen($row->getPHID())) { + continue; + } + + queryfx( + $conn, + 'UPDATE %T SET phid = %s WHERE id = %d', + $table->getTableName(), + $table->generatePHID(), + $row->getID()); +} diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index b23d3cb90c..e69170e99f 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4845,6 +4845,8 @@ phutil_register_library_map(array( 'PhrictionConduitAPIMethod' => 'applications/phriction/conduit/PhrictionConduitAPIMethod.php', 'PhrictionConstants' => 'applications/phriction/constants/PhrictionConstants.php', 'PhrictionContent' => 'applications/phriction/storage/PhrictionContent.php', + 'PhrictionContentPHIDType' => 'applications/phriction/phid/PhrictionContentPHIDType.php', + 'PhrictionContentQuery' => 'applications/phriction/query/PhrictionContentQuery.php', 'PhrictionController' => 'applications/phriction/controller/PhrictionController.php', 'PhrictionCreateConduitAPIMethod' => 'applications/phriction/conduit/PhrictionCreateConduitAPIMethod.php', 'PhrictionDAO' => 'applications/phriction/storage/PhrictionDAO.php', @@ -10757,6 +10759,8 @@ phutil_register_library_map(array( 'PhrictionDAO', 'PhabricatorMarkupInterface', ), + 'PhrictionContentPHIDType' => 'PhabricatorPHIDType', + 'PhrictionContentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhrictionController' => 'PhabricatorController', 'PhrictionCreateConduitAPIMethod' => 'PhrictionConduitAPIMethod', 'PhrictionDAO' => 'PhabricatorLiskDAO', diff --git a/src/applications/phriction/phid/PhrictionContentPHIDType.php b/src/applications/phriction/phid/PhrictionContentPHIDType.php new file mode 100644 index 0000000000..b8f39c0ef4 --- /dev/null +++ b/src/applications/phriction/phid/PhrictionContentPHIDType.php @@ -0,0 +1,38 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $content = $objects[$phid]; + } + } + +} diff --git a/src/applications/phriction/query/PhrictionContentQuery.php b/src/applications/phriction/query/PhrictionContentQuery.php new file mode 100644 index 0000000000..4f76186a77 --- /dev/null +++ b/src/applications/phriction/query/PhrictionContentQuery.php @@ -0,0 +1,51 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + public function newResultObject() { + return new PhrictionContent(); + } + + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); + } + + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); + + if ($this->ids !== null) { + $where[] = qsprintf( + $conn, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->phids !== null) { + $where[] = qsprintf( + $conn, + 'phid IN (%Ls)', + $this->phids); + } + + return $where; + } + + public function getQueryApplicationClass() { + return 'PhabricatorPhrictionApplication'; + } + +} diff --git a/src/applications/phriction/storage/PhrictionContent.php b/src/applications/phriction/storage/PhrictionContent.php index 0492534797..30c1589a17 100644 --- a/src/applications/phriction/storage/PhrictionContent.php +++ b/src/applications/phriction/storage/PhrictionContent.php @@ -3,7 +3,8 @@ /** * @task markup Markup Interface */ -final class PhrictionContent extends PhrictionDAO +final class PhrictionContent + extends PhrictionDAO implements PhabricatorMarkupInterface { const MARKUP_FIELD_BODY = 'markup:body'; @@ -33,6 +34,7 @@ final class PhrictionContent extends PhrictionDAO protected function getConfiguration() { return array( + self::CONFIG_AUX_PHID => true, self::CONFIG_COLUMN_SCHEMA => array( 'version' => 'uint32', 'title' => 'sort', @@ -60,6 +62,10 @@ final class PhrictionContent extends PhrictionDAO ) + parent::getConfiguration(); } + public function getPHIDType() { + return PhrictionContentPHIDType::TYPECONST; + } + /* -( Markup Interface )--------------------------------------------------- */ From b2c829f2743b607de5dbd74a33f624002dd5575f Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 15 Feb 2018 04:41:05 -0800 Subject: [PATCH 39/58] Move PhrictionContent from RemarkupInterface (deprecated) to PHUIRemarkupView Summary: Depends on D19092. Ref T13077. This modernizes markup rendering for PhrictionContent. This is a little messy because table of contents generation isn't straightforward. Test Plan: Viewed Phriction documents with and without 3+ headers, saw ToC vs no ToC. Edited/previewed documents. Grepped for affected symbols. Checked DarkConsole for sensible cache behavior. Maniphest Tasks: T13077 Differential Revision: https://secure.phabricator.com/D19093 --- src/__phutil_library_map__.php | 5 +- .../PhrictionDocumentController.php | 13 ++- .../phriction/storage/PhrictionContent.php | 84 ++----------------- .../markup/PhabricatorMarkupOneOff.php | 37 ++++++++ .../markup/view/PHUIRemarkupView.php | 52 ++++++++++-- 5 files changed, 99 insertions(+), 92 deletions(-) diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index e69170e99f..b6e29fc5ed 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -10755,10 +10755,7 @@ phutil_register_library_map(array( 'PhrictionChangeType' => 'PhrictionConstants', 'PhrictionConduitAPIMethod' => 'ConduitAPIMethod', 'PhrictionConstants' => 'Phobject', - 'PhrictionContent' => array( - 'PhrictionDAO', - 'PhabricatorMarkupInterface', - ), + 'PhrictionContent' => 'PhrictionDAO', 'PhrictionContentPHIDType' => 'PhabricatorPHIDType', 'PhrictionContentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhrictionController' => 'PhabricatorController', diff --git a/src/applications/phriction/controller/PhrictionDocumentController.php b/src/applications/phriction/controller/PhrictionDocumentController.php index e455bd86d1..a5189cc72c 100644 --- a/src/applications/phriction/controller/PhrictionDocumentController.php +++ b/src/applications/phriction/controller/PhrictionDocumentController.php @@ -97,8 +97,12 @@ final class PhrictionDocumentController if ($current_status == PhrictionChangeType::CHANGE_EDIT || $current_status == PhrictionChangeType::CHANGE_MOVE_HERE) { - $core_content = $content->renderContent($viewer); - $toc = $this->getToc($content); + $remarkup_view = $content->newRemarkupView($viewer); + + $core_content = $remarkup_view->render(); + + $toc = $remarkup_view->getTableOfContents(); + $toc = $this->getToc($toc); } else if ($current_status == PhrictionChangeType::CHANGE_DELETE) { $notice = new PHUIInfoView(); @@ -474,8 +478,8 @@ final class PhrictionDocumentController return $this->slug; } - protected function getToc(PhrictionContent $content) { - $toc = $content->getRenderedTableOfContents(); + protected function getToc($toc) { + if ($toc) { $toc = phutil_tag_div('phui-document-toc-content', array( phutil_tag_div( @@ -484,6 +488,7 @@ final class PhrictionDocumentController $toc, )); } + return $toc; } diff --git a/src/applications/phriction/storage/PhrictionContent.php b/src/applications/phriction/storage/PhrictionContent.php index 30c1589a17..44c8184eb7 100644 --- a/src/applications/phriction/storage/PhrictionContent.php +++ b/src/applications/phriction/storage/PhrictionContent.php @@ -1,13 +1,7 @@ true, @@ -66,68 +50,10 @@ final class PhrictionContent return PhrictionContentPHIDType::TYPECONST; } - -/* -( Markup Interface )--------------------------------------------------- */ - - - /** - * @task markup - */ - public function getMarkupFieldKey($field) { - $content = $this->getMarkupText($field); - return PhabricatorMarkupEngine::digestRemarkupContent($this, $content); + public function newRemarkupView(PhabricatorUser $viewer) { + return id(new PHUIRemarkupView($viewer, $this->getContent())) + ->setRemarkupOption(PHUIRemarkupView::OPTION_GENERATE_TOC, true) + ->setGenerateTableOfContents(true); } - - /** - * @task markup - */ - public function getMarkupText($field) { - return $this->getContent(); - } - - - /** - * @task markup - */ - public function newMarkupEngine($field) { - return PhabricatorMarkupEngine::newPhrictionMarkupEngine(); - } - - - /** - * @task markup - */ - public function didMarkupText( - $field, - $output, - PhutilMarkupEngine $engine) { - - $this->renderedTableOfContents = - PhutilRemarkupHeaderBlockRule::renderTableOfContents($engine); - - return phutil_tag( - 'div', - array( - 'class' => 'phabricator-remarkup', - ), - $output); - } - - /** - * @task markup - */ - public function getRenderedTableOfContents() { - return $this->renderedTableOfContents; - } - - - /** - * @task markup - */ - public function shouldUseMarkupCache($field) { - return (bool)$this->getID(); - } - - } diff --git a/src/infrastructure/markup/PhabricatorMarkupOneOff.php b/src/infrastructure/markup/PhabricatorMarkupOneOff.php index 6bffcf7e2b..cc57adc647 100644 --- a/src/infrastructure/markup/PhabricatorMarkupOneOff.php +++ b/src/infrastructure/markup/PhabricatorMarkupOneOff.php @@ -12,6 +12,10 @@ final class PhabricatorMarkupOneOff private $engineRuleset; private $engine; private $disableCache; + private $contentCacheFragment; + + private $generateTableOfContents; + private $tableOfContents; public function setEngineRuleset($engine_ruleset) { $this->engineRuleset = $engine_ruleset; @@ -54,7 +58,34 @@ final class PhabricatorMarkupOneOff return $this->disableCache; } + public function setGenerateTableOfContents($generate) { + $this->generateTableOfContents = $generate; + return $this; + } + + public function getGenerateTableOfContents() { + return $this->generateTableOfContents; + } + + public function getTableOfContents() { + return $this->tableOfContents; + } + + public function setContentCacheFragment($fragment) { + $this->contentCacheFragment = $fragment; + return $this; + } + + public function getContentCacheFragment() { + return $this->contentCacheFragment; + } + public function getMarkupFieldKey($field) { + $fragment = $this->getContentCacheFragment(); + if ($fragment !== null) { + return $fragment; + } + return PhabricatorHash::digestForIndex($this->getContent()).':oneoff'; } @@ -81,7 +112,13 @@ final class PhabricatorMarkupOneOff $output, PhutilMarkupEngine $engine) { + if ($this->getGenerateTableOfContents()) { + $toc = PhutilRemarkupHeaderBlockRule::renderTableOfContents($engine); + $this->tableOfContents = $toc; + } + require_celerity_resource('phabricator-remarkup-css'); + return phutil_tag( 'div', array( diff --git a/src/infrastructure/markup/view/PHUIRemarkupView.php b/src/infrastructure/markup/view/PHUIRemarkupView.php index e5772e9d84..d60d28b5d3 100644 --- a/src/infrastructure/markup/view/PHUIRemarkupView.php +++ b/src/infrastructure/markup/view/PHUIRemarkupView.php @@ -14,11 +14,14 @@ final class PHUIRemarkupView extends AphrontView { private $corpus; private $contextObject; private $options; + private $oneoff; + private $generateTableOfContents; // TODO: In the long run, rules themselves should define available options. // For now, just define constants here so we can more easily replace things // later once this is cleaned up. const OPTION_PRESERVE_LINEBREAKS = 'preserve-linebreaks'; + const OPTION_GENERATE_TOC = 'header.generate-toc'; public function __construct(PhabricatorUser $viewer, $corpus) { $this->setUser($viewer); @@ -46,6 +49,19 @@ final class PHUIRemarkupView extends AphrontView { return $this; } + public function setGenerateTableOfContents($generate) { + $this->generateTableOfContents = $generate; + return $this; + } + + public function getGenerateTableOfContents() { + return $this->generateTableOfContents; + } + + public function getTableOfContents() { + return $this->oneoff->getTableOfContents(); + } + public function render() { $viewer = $this->getViewer(); $corpus = $this->corpus; @@ -54,7 +70,8 @@ final class PHUIRemarkupView extends AphrontView { $options = $this->options; $oneoff = id(new PhabricatorMarkupOneOff()) - ->setContent($corpus); + ->setContent($corpus) + ->setContentCacheFragment($this->getContentCacheFragment()); if ($options) { $oneoff->setEngine($this->getEngine()); @@ -62,6 +79,10 @@ final class PHUIRemarkupView extends AphrontView { $oneoff->setPreserveLinebreaks(true); } + $generate_toc = $this->getGenerateTableOfContents(); + $oneoff->setGenerateTableOfContents($generate_toc); + $this->oneoff = $oneoff; + $content = PhabricatorMarkupEngine::renderOneObject( $oneoff, 'default', @@ -76,10 +97,7 @@ final class PHUIRemarkupView extends AphrontView { $viewer = $this->getViewer(); $viewer_key = $viewer->getCacheFragment(); - - ksort($options); - $engine_key = serialize($options); - $engine_key = PhabricatorHash::digestForIndex($engine_key); + $engine_key = $this->getEngineCacheFragment(); $cache = PhabricatorCaches::getRequestCache(); $cache_key = "remarkup.engine({$viewer_key}, {$engine_key})"; @@ -93,4 +111,28 @@ final class PHUIRemarkupView extends AphrontView { return $engine; } + private function getEngineCacheFragment() { + $options = $this->options; + + ksort($options); + + $engine_key = serialize($options); + $engine_key = PhabricatorHash::digestForIndex($engine_key); + + return $engine_key; + } + + private function getContentCacheFragment() { + $corpus = $this->corpus; + + $content_fragment = PhabricatorHash::digestForIndex($corpus); + $options_fragment = array( + 'toc' => $this->getGenerateTableOfContents(), + ); + $options_fragment = serialize($options_fragment); + $options_fragment = PhabricatorHash::digestForIndex($options_fragment); + + return "remarkup({$content_fragment}, {$options_fragment})"; + } + } From 9404e2b3d425079aa3ca09c0e682ac18143bfd29 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 15 Feb 2018 04:52:09 -0800 Subject: [PATCH 40/58] Implement PolicyInterface, ExtendedPolicyInterface, and DestructibleInterface on PhrictionContent Summary: Depends on D19093. Ref T13077. Although content objects normally don't have any edges today, they may in the future. Also implement Policy stuff properly. Test Plan: Used `bin/remove destroy` to destroy a document, verified it also loaded and destroyed the correspoding Content correctly by looking at `--trace` and the database rows. Maniphest Tasks: T13077 Differential Revision: https://secure.phabricator.com/D19094 --- src/__phutil_library_map__.php | 6 +- .../phriction/query/PhrictionContentQuery.php | 68 ++++++++++++++++++- .../phriction/storage/PhrictionContent.php | 54 ++++++++++++++- .../phriction/storage/PhrictionDocument.php | 13 ++-- 4 files changed, 130 insertions(+), 11 deletions(-) diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index b6e29fc5ed..2eb9a1893b 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -10755,7 +10755,11 @@ phutil_register_library_map(array( 'PhrictionChangeType' => 'PhrictionConstants', 'PhrictionConduitAPIMethod' => 'ConduitAPIMethod', 'PhrictionConstants' => 'Phobject', - 'PhrictionContent' => 'PhrictionDAO', + 'PhrictionContent' => array( + 'PhrictionDAO', + 'PhabricatorPolicyInterface', + 'PhabricatorDestructibleInterface', + ), 'PhrictionContentPHIDType' => 'PhabricatorPHIDType', 'PhrictionContentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhrictionController' => 'PhabricatorController', diff --git a/src/applications/phriction/query/PhrictionContentQuery.php b/src/applications/phriction/query/PhrictionContentQuery.php index 4f76186a77..0ab132abdd 100644 --- a/src/applications/phriction/query/PhrictionContentQuery.php +++ b/src/applications/phriction/query/PhrictionContentQuery.php @@ -5,6 +5,7 @@ final class PhrictionContentQuery private $ids; private $phids; + private $documentPHIDs; public function withIDs(array $ids) { $this->ids = $ids; @@ -16,6 +17,11 @@ final class PhrictionContentQuery return $this; } + public function withDocumentPHIDs(array $phids) { + $this->documentPHIDs = $phids; + return $this; + } + public function newResultObject() { return new PhrictionContent(); } @@ -30,20 +36,78 @@ final class PhrictionContentQuery if ($this->ids !== null) { $where[] = qsprintf( $conn, - 'id IN (%Ld)', + 'c.id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( $conn, - 'phid IN (%Ls)', + 'c.phid IN (%Ls)', $this->phids); } + if ($this->documentPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'd.phid IN (%Ls)', + $this->documentPHIDs); + } + return $where; } + protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { + $joins = parent::buildJoinClauseParts($conn); + + if ($this->shouldJoinDocumentTable()) { + $joins[] = qsprintf( + $conn, + 'JOIN %T d ON d.id = c.documentID', + id(new PhrictionDocument())->getTableName()); + } + + return $joins; + } + + protected function willFilterPage(array $contents) { + $document_ids = mpull($contents, 'getDocumentID'); + + $documents = id(new PhrictionDocumentQuery()) + ->setViewer($this->getViewer()) + ->setParentQuery($this) + ->withIDs($document_ids) + ->execute(); + $documents = mpull($documents, null, 'getID'); + + foreach ($contents as $key => $content) { + $document_id = $content->getDocumentID(); + + $document = idx($documents, $document_id); + if (!$document) { + unset($contents[$key]); + $this->didRejectResult($content); + continue; + } + + $content->attachDocument($document); + } + + return $contents; + } + + private function shouldJoinDocumentTable() { + if ($this->documentPHIDs !== null) { + return true; + } + + return false; + } + + protected function getPrimaryTableAlias() { + return 'c'; + } + public function getQueryApplicationClass() { return 'PhabricatorPhrictionApplication'; } diff --git a/src/applications/phriction/storage/PhrictionContent.php b/src/applications/phriction/storage/PhrictionContent.php index 44c8184eb7..a6aee7226f 100644 --- a/src/applications/phriction/storage/PhrictionContent.php +++ b/src/applications/phriction/storage/PhrictionContent.php @@ -1,9 +1,11 @@ true, @@ -56,4 +60,50 @@ final class PhrictionContent ->setGenerateTableOfContents(true); } + public function attachDocument(PhrictionDocument $document) { + $this->document = $document; + return $this; + } + + public function getDocument() { + return $this->assertAttached($this->document); + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + ); + } + + public function getPolicy($capability) { + return PhabricatorPolicies::getMostOpenPolicy(); + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return false; + } + + +/* -( PhabricatorExtendedPolicyInterface )--------------------------------- */ + + + public function getExtendedPolicy($capability, PhabricatorUser $viewer) { + return array( + array($this->getDocument(), PhabricatorPolicyCapability::CAN_VIEW), + ); + } + + +/* -( PhabricatorDestructibleInterface )----------------------------------- */ + + + public function destroyObjectPermanently( + PhabricatorDestructionEngine $engine) { + $this->delete(); + } + } diff --git a/src/applications/phriction/storage/PhrictionDocument.php b/src/applications/phriction/storage/PhrictionDocument.php index 7a3e178882..01e85d4954 100644 --- a/src/applications/phriction/storage/PhrictionDocument.php +++ b/src/applications/phriction/storage/PhrictionDocument.php @@ -236,15 +236,16 @@ final class PhrictionDocument extends PhrictionDAO $this->openTransaction(); - $this->delete(); - - $contents = id(new PhrictionContent())->loadAllWhere( - 'documentID = %d', - $this->getID()); + $contents = id(new PhrictionContentQuery()) + ->setViewer($engine->getViewer()) + ->withDocumentPHIDs(array($this->getPHID())) + ->execute(); foreach ($contents as $content) { - $content->delete(); + $engine->destroyObject($content); } + $this->delete(); + $this->saveTransaction(); } From f742d00c28949ba009474b20042675dd31490e16 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 15 Feb 2018 05:22:16 -0800 Subject: [PATCH 41/58] Mostly use PhrictionContentQuery to load PhrictionContent objects Summary: Depends on D19094. Ref T13077. Use modern infrastructure to perform these loads. I left a couple of calls in the older API methods unconverted. Test Plan: Viewed documents. Viewed older versions. Viewed diffs. Did revert edits to older versions. Maniphest Tasks: T13077 Differential Revision: https://secure.phabricator.com/D19095 --- .../controller/PhrictionDiffController.php | 9 ++--- .../PhrictionDocumentController.php | 35 ++++++++++--------- .../controller/PhrictionEditController.php | 9 ++--- .../phriction/query/PhrictionContentQuery.php | 13 +++++++ .../query/PhrictionDocumentQuery.php | 9 +++-- .../phriction/storage/PhrictionDocument.php | 2 +- 6 files changed, 49 insertions(+), 28 deletions(-) diff --git a/src/applications/phriction/controller/PhrictionDiffController.php b/src/applications/phriction/controller/PhrictionDiffController.php index b66a3b3094..f443ad657b 100644 --- a/src/applications/phriction/controller/PhrictionDiffController.php +++ b/src/applications/phriction/controller/PhrictionDiffController.php @@ -29,10 +29,11 @@ final class PhrictionDiffController extends PhrictionController { list($l, $r) = explode(',', $ref); } - $content = id(new PhrictionContent())->loadAllWhere( - 'documentID = %d AND version IN (%Ld)', - $document->getID(), - array($l, $r)); + $content = id(new PhrictionContentQuery()) + ->setViewer($viewer) + ->withDocumentPHIDs(array($document->getPHID())) + ->withVersions(array($l, $r)) + ->execute(); $content = mpull($content, null, 'getVersion'); $content_l = idx($content, $l, null); diff --git a/src/applications/phriction/controller/PhrictionDocumentController.php b/src/applications/phriction/controller/PhrictionDocumentController.php index a5189cc72c..8377cf7d29 100644 --- a/src/applications/phriction/controller/PhrictionDocumentController.php +++ b/src/applications/phriction/controller/PhrictionDocumentController.php @@ -22,11 +22,6 @@ final class PhrictionDocumentController require_celerity_resource('phriction-document-css'); - $document = id(new PhrictionDocumentQuery()) - ->setViewer($viewer) - ->withSlugs(array($slug)) - ->executeOne(); - $version_note = null; $core_content = ''; $move_notice = ''; @@ -34,6 +29,11 @@ final class PhrictionDocumentController $content = null; $toc = null; + $document = id(new PhrictionDocumentQuery()) + ->setViewer($viewer) + ->withSlugs(array($slug)) + ->needContent(true) + ->executeOne(); if (!$document) { $document = PhrictionDocument::initializeNewDocument($viewer, $slug); @@ -69,25 +69,28 @@ final class PhrictionDocumentController $version = $request->getInt('v'); if ($version) { - $content = id(new PhrictionContent())->loadOneWhere( - 'documentID = %d AND version = %d', - $document->getID(), - $version); + $content = id(new PhrictionContentQuery()) + ->setViewer($viewer) + ->withDocumentPHIDs(array($document->getPHID())) + ->withVersions(array($version)) + ->executeOne(); if (!$content) { return new Aphront404Response(); } if ($content->getID() != $document->getContentID()) { - $vdate = phabricator_datetime($content->getDateCreated(), $viewer); - $version_note = new PHUIInfoView(); - $version_note->setSeverity(PHUIInfoView::SEVERITY_NOTICE); - $version_note->appendChild( - pht('You are viewing an older version of this document, as it '. - 'appeared on %s.', $vdate)); + $version_note = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) + ->appendChild( + pht( + 'You are viewing an older version of this document, as it '. + 'appeared on %s.', + phabricator_datetime($content->getDateCreated(), $viewer))); } } else { - $content = id(new PhrictionContent())->load($document->getContentID()); + $content = $document->getContent(); } + $page_title = $content->getTitle(); $properties = $this ->buildPropertyListView($document, $content, $slug); diff --git a/src/applications/phriction/controller/PhrictionEditController.php b/src/applications/phriction/controller/PhrictionEditController.php index 0ac3fc35bb..e7e2b40d87 100644 --- a/src/applications/phriction/controller/PhrictionEditController.php +++ b/src/applications/phriction/controller/PhrictionEditController.php @@ -28,10 +28,11 @@ final class PhrictionEditController $revert = $request->getInt('revert'); if ($revert) { - $content = id(new PhrictionContent())->loadOneWhere( - 'documentID = %d AND version = %d', - $document->getID(), - $revert); + $content = id(new PhrictionContentQuery()) + ->setViewer($viewer) + ->withDocumentPHIDs(array($document->getPHID())) + ->withVersions(array($revert)) + ->executeOne(); if (!$content) { return new Aphront404Response(); } diff --git a/src/applications/phriction/query/PhrictionContentQuery.php b/src/applications/phriction/query/PhrictionContentQuery.php index 0ab132abdd..053f40cf9c 100644 --- a/src/applications/phriction/query/PhrictionContentQuery.php +++ b/src/applications/phriction/query/PhrictionContentQuery.php @@ -6,6 +6,7 @@ final class PhrictionContentQuery private $ids; private $phids; private $documentPHIDs; + private $versions; public function withIDs(array $ids) { $this->ids = $ids; @@ -22,6 +23,11 @@ final class PhrictionContentQuery return $this; } + public function withVersions(array $versions) { + $this->versions = $versions; + return $this; + } + public function newResultObject() { return new PhrictionContent(); } @@ -47,6 +53,13 @@ final class PhrictionContentQuery $this->phids); } + if ($this->versions !== null) { + $where[] = qsprintf( + $conn, + 'version IN (%Ld)', + $this->versions); + } + if ($this->documentPHIDs !== null) { $where[] = qsprintf( $conn, diff --git a/src/applications/phriction/query/PhrictionDocumentQuery.php b/src/applications/phriction/query/PhrictionDocumentQuery.php index 2736d825b5..b5118146d3 100644 --- a/src/applications/phriction/query/PhrictionDocumentQuery.php +++ b/src/applications/phriction/query/PhrictionDocumentQuery.php @@ -145,9 +145,12 @@ final class PhrictionDocumentQuery } if ($this->needContent) { - $contents = id(new PhrictionContent())->loadAllWhere( - 'id IN (%Ld)', - mpull($documents, 'getContentID')); + $contents = id(new PhrictionContentQuery()) + ->setViewer($this->getViewer()) + ->setParentQuery($this) + ->withIDs(mpull($documents, 'getContentID')) + ->execute(); + $contents = mpull($contents, null, 'getID'); foreach ($documents as $key => $document) { $content_id = $document->getContentID(); diff --git a/src/applications/phriction/storage/PhrictionDocument.php b/src/applications/phriction/storage/PhrictionDocument.php index 01e85d4954..0a9f09afdd 100644 --- a/src/applications/phriction/storage/PhrictionDocument.php +++ b/src/applications/phriction/storage/PhrictionDocument.php @@ -61,7 +61,7 @@ final class PhrictionDocument extends PhrictionDAO $document = new PhrictionDocument(); $document->setSlug($slug); - $content = new PhrictionContent(); + $content = new PhrictionContent(); $content->setSlug($slug); $default_title = PhabricatorSlug::getDefaultTitle($slug); From a965d8d6ae5ac54744b0bbc07107b53e84493e51 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 15 Feb 2018 05:42:01 -0800 Subject: [PATCH 42/58] Make PhrictionContent "description" non-nullable Summary: Depends on D19095. Ref T6203. Ref T13077. This column is nullable in an inconsistent way. Make it non-nullable. Also clean up one more content query on the history view. Test Plan: Ran migration, then created and edited documents without providing a descriptino or hitting `NULL` exceptions. Maniphest Tasks: T13077, T6203 Differential Revision: https://secure.phabricator.com/D19096 --- .../20180215.phriction.03.descempty.sql | 2 ++ .../20180215.phriction.04.descnull.sql | 2 ++ .../conduit/PhrictionCreateConduitAPIMethod.php | 2 +- .../conduit/PhrictionEditConduitAPIMethod.php | 2 +- .../controller/PhrictionHistoryController.php | 15 ++++++--------- .../editor/PhrictionTransactionEditor.php | 5 ++++- .../phriction/storage/PhrictionContent.php | 5 +---- 7 files changed, 17 insertions(+), 16 deletions(-) create mode 100644 resources/sql/autopatches/20180215.phriction.03.descempty.sql create mode 100644 resources/sql/autopatches/20180215.phriction.04.descnull.sql diff --git a/resources/sql/autopatches/20180215.phriction.03.descempty.sql b/resources/sql/autopatches/20180215.phriction.03.descempty.sql new file mode 100644 index 0000000000..c41df5285a --- /dev/null +++ b/resources/sql/autopatches/20180215.phriction.03.descempty.sql @@ -0,0 +1,2 @@ +UPDATE {$NAMESPACE}_phriction.phriction_content + SET description = '' WHERE description IS NULL; diff --git a/resources/sql/autopatches/20180215.phriction.04.descnull.sql b/resources/sql/autopatches/20180215.phriction.04.descnull.sql new file mode 100644 index 0000000000..3ff017cd64 --- /dev/null +++ b/resources/sql/autopatches/20180215.phriction.04.descnull.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_phriction.phriction_content + CHANGE description description LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}; diff --git a/src/applications/phriction/conduit/PhrictionCreateConduitAPIMethod.php b/src/applications/phriction/conduit/PhrictionCreateConduitAPIMethod.php index fc28b53b66..300dbbfa80 100644 --- a/src/applications/phriction/conduit/PhrictionCreateConduitAPIMethod.php +++ b/src/applications/phriction/conduit/PhrictionCreateConduitAPIMethod.php @@ -57,7 +57,7 @@ final class PhrictionCreateConduitAPIMethod extends PhrictionConduitAPIMethod { ->setActor($request->getUser()) ->setContentSource($request->newContentSource()) ->setContinueOnNoEffect(true) - ->setDescription($request->getValue('description')); + ->setDescription((string)$request->getValue('description')); try { $editor->applyTransactions($doc, $xactions); diff --git a/src/applications/phriction/conduit/PhrictionEditConduitAPIMethod.php b/src/applications/phriction/conduit/PhrictionEditConduitAPIMethod.php index 70c02d376a..9d97ed7f8c 100644 --- a/src/applications/phriction/conduit/PhrictionEditConduitAPIMethod.php +++ b/src/applications/phriction/conduit/PhrictionEditConduitAPIMethod.php @@ -52,7 +52,7 @@ final class PhrictionEditConduitAPIMethod extends PhrictionConduitAPIMethod { ->setActor($request->getUser()) ->setContentSource($request->newContentSource()) ->setContinueOnNoEffect(true) - ->setDescription($request->getValue('description')); + ->setDescription((string)$request->getValue('description')); try { $editor->applyTransactions($doc, $xactions); diff --git a/src/applications/phriction/controller/PhrictionHistoryController.php b/src/applications/phriction/controller/PhrictionHistoryController.php index 9b0d3188a2..432b1dfdef 100644 --- a/src/applications/phriction/controller/PhrictionHistoryController.php +++ b/src/applications/phriction/controller/PhrictionHistoryController.php @@ -22,16 +22,13 @@ final class PhrictionHistoryController $current = $document->getContent(); - $pager = new PHUIPagerView(); - $pager->setOffset($request->getInt('page')); - $pager->setURI($request->getRequestURI(), 'page'); + $pager = id(new AphrontCursorPagerView()) + ->readFromRequest($request); - $history = id(new PhrictionContent())->loadAllWhere( - 'documentID = %d ORDER BY version DESC LIMIT %d, %d', - $document->getID(), - $pager->getOffset(), - $pager->getPageSize() + 1); - $history = $pager->sliceResults($history); + $history = id(new PhrictionContentQuery()) + ->setViewer($viewer) + ->withDocumentPHIDs(array($document->getPHID())) + ->executeWithCursorPager($pager); $author_phids = mpull($history, 'getAuthorPHID'); $handles = $this->loadViewerHandles($author_phids); diff --git a/src/applications/phriction/editor/PhrictionTransactionEditor.php b/src/applications/phriction/editor/PhrictionTransactionEditor.php index 73aee3fd4c..793c609517 100644 --- a/src/applications/phriction/editor/PhrictionTransactionEditor.php +++ b/src/applications/phriction/editor/PhrictionTransactionEditor.php @@ -596,10 +596,13 @@ final class PhrictionTransactionEditor ->setAuthorPHID($this->getActor()->getPHID()) ->setChangeType(PhrictionChangeType::CHANGE_EDIT) ->setTitle($this->getOldContent()->getTitle()) - ->setContent($this->getOldContent()->getContent()); + ->setContent($this->getOldContent()->getContent()) + ->setDescription(''); + if (strlen($this->getDescription())) { $new_content->setDescription($this->getDescription()); } + $new_content->setVersion($this->getOldContent()->getVersion() + 1); return $new_content; diff --git a/src/applications/phriction/storage/PhrictionContent.php b/src/applications/phriction/storage/PhrictionContent.php index a6aee7226f..390da64682 100644 --- a/src/applications/phriction/storage/PhrictionContent.php +++ b/src/applications/phriction/storage/PhrictionContent.php @@ -30,10 +30,7 @@ final class PhrictionContent 'content' => 'text', 'changeType' => 'uint32', 'changeRef' => 'uint32?', - - // T6203/NULLABILITY - // This should just be empty if not provided? - 'description' => 'text?', + 'description' => 'text', ), self::CONFIG_KEY_SCHEMA => array( 'documentID' => array( From 8101bf74e9ce8935808a615f4581f0821c8aa876 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 15 Feb 2018 06:08:52 -0800 Subject: [PATCH 43/58] Introduce a "phriction.content.search" API method to replace "phriction.history" Summary: Depends on D19096. Ref T13077. Adds a new "v3" API method for Phriction document content, to replace the existing "phriction.history" call. Test Plan: Made various calls via web API console. Maniphest Tasks: T13077 Differential Revision: https://secure.phabricator.com/D19097 --- src/__phutil_library_map__.php | 5 ++ ...PhrictionContentSearchConduitAPIMethod.php | 18 +++++ .../query/PhrictionContentSearchEngine.php | 76 +++++++++++++++++++ .../phriction/storage/PhrictionContent.php | 36 ++++++++- 4 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 src/applications/phriction/conduit/PhrictionContentSearchConduitAPIMethod.php create mode 100644 src/applications/phriction/query/PhrictionContentSearchEngine.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 2eb9a1893b..3f42c58c66 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4847,6 +4847,8 @@ phutil_register_library_map(array( 'PhrictionContent' => 'applications/phriction/storage/PhrictionContent.php', 'PhrictionContentPHIDType' => 'applications/phriction/phid/PhrictionContentPHIDType.php', 'PhrictionContentQuery' => 'applications/phriction/query/PhrictionContentQuery.php', + 'PhrictionContentSearchConduitAPIMethod' => 'applications/phriction/conduit/PhrictionContentSearchConduitAPIMethod.php', + 'PhrictionContentSearchEngine' => 'applications/phriction/query/PhrictionContentSearchEngine.php', 'PhrictionController' => 'applications/phriction/controller/PhrictionController.php', 'PhrictionCreateConduitAPIMethod' => 'applications/phriction/conduit/PhrictionCreateConduitAPIMethod.php', 'PhrictionDAO' => 'applications/phriction/storage/PhrictionDAO.php', @@ -10759,9 +10761,12 @@ phutil_register_library_map(array( 'PhrictionDAO', 'PhabricatorPolicyInterface', 'PhabricatorDestructibleInterface', + 'PhabricatorConduitResultInterface', ), 'PhrictionContentPHIDType' => 'PhabricatorPHIDType', 'PhrictionContentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhrictionContentSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', + 'PhrictionContentSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhrictionController' => 'PhabricatorController', 'PhrictionCreateConduitAPIMethod' => 'PhrictionConduitAPIMethod', 'PhrictionDAO' => 'PhabricatorLiskDAO', diff --git a/src/applications/phriction/conduit/PhrictionContentSearchConduitAPIMethod.php b/src/applications/phriction/conduit/PhrictionContentSearchConduitAPIMethod.php new file mode 100644 index 0000000000..2f40130782 --- /dev/null +++ b/src/applications/phriction/conduit/PhrictionContentSearchConduitAPIMethod.php @@ -0,0 +1,18 @@ +newQuery(); + + if ($map['documentPHIDs']) { + $query->withDocumentPHIDs($map['documentPHIDs']); + } + + if ($map['versions']) { + $query->withVersions($map['versions']); + } + + return $query; + } + + protected function buildCustomSearchFields() { + return array( + id(new PhabricatorPHIDsSearchField()) + ->setKey('documentPHIDs') + ->setAliases(array('document', 'documents', 'documentPHID')) + ->setLabel(pht('Documents')), + id(new PhabricatorIDsSearchField()) + ->setKey('versions') + ->setAliases(array('version')), + ); + } + + protected function getURI($path) { + // There's currently no web UI for this search interface, it exists purely + // to power the Conduit API. + throw new PhutilMethodNotImplementedException(); + } + + protected function getBuiltinQueryNames() { + return array( + 'all' => pht('All Content'), + ); + } + + 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 $contents, + PhabricatorSavedQuery $query, + array $handles) { + assert_instances_of($contents, 'PhrictionContent'); + throw new PhutilMethodNotImplementedException(); + } + +} diff --git a/src/applications/phriction/storage/PhrictionContent.php b/src/applications/phriction/storage/PhrictionContent.php index 390da64682..dc6bf19faa 100644 --- a/src/applications/phriction/storage/PhrictionContent.php +++ b/src/applications/phriction/storage/PhrictionContent.php @@ -4,7 +4,8 @@ final class PhrictionContent extends PhrictionDAO implements PhabricatorPolicyInterface, - PhabricatorDestructibleInterface { + PhabricatorDestructibleInterface, + PhabricatorConduitResultInterface { protected $documentID; protected $version; @@ -103,4 +104,37 @@ final class PhrictionContent $this->delete(); } + +/* -( PhabricatorConduitResultInterface )---------------------------------- */ + + + public function getFieldSpecificationsForConduit() { + return array( + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('documentPHID') + ->setType('phid') + ->setDescription(pht('Document this content is for.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('version') + ->setType('int') + ->setDescription(pht('Content version.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('authorPHID') + ->setType('phid') + ->setDescription(pht('Author of this version of the content.')), + ); + } + + public function getFieldValuesForConduit() { + return array( + 'documentPHID' => $this->getDocument()->getPHID(), + 'version' => (int)$this->getVersion(), + 'authorPHID' => $this->getAuthorPHID(), + ); + } + + public function getConduitSearchAttachments() { + return array(); + } + } From 48eb82f229be44917e9d99a3cb0848e3841565bf Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 15 Feb 2018 08:28:19 -0800 Subject: [PATCH 44/58] Freeze "phriction.history" in favor of "phriction.content.search" Summary: Depends on D19097. Ref T13077. Freeze the older method now that the newer one is available. Test Plan: Viewed the older method's page and saw it frozen; called it to make sure I didn't break it by accident. Maniphest Tasks: T13077 Differential Revision: https://secure.phabricator.com/D19098 --- .../conduit/PhrictionHistoryConduitAPIMethod.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/applications/phriction/conduit/PhrictionHistoryConduitAPIMethod.php b/src/applications/phriction/conduit/PhrictionHistoryConduitAPIMethod.php index 454af2934a..f030420594 100644 --- a/src/applications/phriction/conduit/PhrictionHistoryConduitAPIMethod.php +++ b/src/applications/phriction/conduit/PhrictionHistoryConduitAPIMethod.php @@ -10,6 +10,16 @@ final class PhrictionHistoryConduitAPIMethod extends PhrictionConduitAPIMethod { return pht('Retrieve history about a Phriction document.'); } + public function getMethodStatus() { + return self::METHOD_STATUS_FROZEN; + } + + public function getMethodStatusDescription() { + return pht( + 'This method is frozen and will eventually be deprecated. New code '. + 'should use "phriction.content.search" instead.'); + } + protected function defineParamTypes() { return array( 'slug' => 'required string', From c1056f6dab5352ac8d13ab74f4c2be8b88a13ce4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 15 Feb 2018 08:35:35 -0800 Subject: [PATCH 45/58] Partially clean up Phriction document status constants; introduce "phriction.document.search" Summary: Depends on D19098. Ref T13077. Phriction status constants currently use the "bag of statuses" approach typical of older code, and store integers in the database. This fixes the "bag of statuses" stuff; a future change will fix the integers. Also adds a skeleton for `phriction.document.search`, but doesn't implement the Conduit interface yet. Test Plan: Searched for documents with various status constraints. Grepped for removed status constants. Viewed document list. Maniphest Tasks: T13077 Differential Revision: https://secure.phabricator.com/D19099 --- src/__phutil_library_map__.php | 8 ++-- ...hrictionDocumentSearchConduitAPIMethod.php | 18 ++++++++ .../constants/PhrictionDocumentStatus.php | 44 ++++++++++++++++++- .../controller/PhrictionController.php | 2 +- .../controller/PhrictionListController.php | 2 +- .../query/PhrictionDocumentQuery.php | 42 ++---------------- ....php => PhrictionDocumentSearchEngine.php} | 44 ++++++++----------- .../search/PhrictionDocumentFerretEngine.php | 2 +- .../phriction/storage/PhrictionDocument.php | 23 ++++++++++ 9 files changed, 114 insertions(+), 71 deletions(-) create mode 100644 src/applications/phriction/conduit/PhrictionDocumentSearchConduitAPIMethod.php rename src/applications/phriction/query/{PhrictionSearchEngine.php => PhrictionDocumentSearchEngine.php} (72%) diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 3f42c58c66..4c9d40d9bc 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4870,6 +4870,8 @@ phutil_register_library_map(array( 'PhrictionDocumentPHIDType' => 'applications/phriction/phid/PhrictionDocumentPHIDType.php', 'PhrictionDocumentPathHeraldField' => 'applications/phriction/herald/PhrictionDocumentPathHeraldField.php', 'PhrictionDocumentQuery' => 'applications/phriction/query/PhrictionDocumentQuery.php', + 'PhrictionDocumentSearchConduitAPIMethod' => 'applications/phriction/conduit/PhrictionDocumentSearchConduitAPIMethod.php', + 'PhrictionDocumentSearchEngine' => 'applications/phriction/query/PhrictionDocumentSearchEngine.php', 'PhrictionDocumentStatus' => 'applications/phriction/constants/PhrictionDocumentStatus.php', 'PhrictionDocumentTitleHeraldField' => 'applications/phriction/herald/PhrictionDocumentTitleHeraldField.php', 'PhrictionDocumentTitleTransaction' => 'applications/phriction/xaction/PhrictionDocumentTitleTransaction.php', @@ -4886,7 +4888,6 @@ phutil_register_library_map(array( 'PhrictionRemarkupRule' => 'applications/phriction/markup/PhrictionRemarkupRule.php', 'PhrictionReplyHandler' => 'applications/phriction/mail/PhrictionReplyHandler.php', 'PhrictionSchemaSpec' => 'applications/phriction/storage/PhrictionSchemaSpec.php', - 'PhrictionSearchEngine' => 'applications/phriction/query/PhrictionSearchEngine.php', 'PhrictionTransaction' => 'applications/phriction/storage/PhrictionTransaction.php', 'PhrictionTransactionComment' => 'applications/phriction/storage/PhrictionTransactionComment.php', 'PhrictionTransactionEditor' => 'applications/phriction/editor/PhrictionTransactionEditor.php', @@ -10799,7 +10800,9 @@ phutil_register_library_map(array( 'PhrictionDocumentPHIDType' => 'PhabricatorPHIDType', 'PhrictionDocumentPathHeraldField' => 'PhrictionDocumentHeraldField', 'PhrictionDocumentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', - 'PhrictionDocumentStatus' => 'PhrictionConstants', + 'PhrictionDocumentSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', + 'PhrictionDocumentSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'PhrictionDocumentStatus' => 'PhabricatorObjectStatus', 'PhrictionDocumentTitleHeraldField' => 'PhrictionDocumentHeraldField', 'PhrictionDocumentTitleTransaction' => 'PhrictionDocumentTransactionType', 'PhrictionDocumentTransactionType' => 'PhabricatorModularTransactionType', @@ -10815,7 +10818,6 @@ phutil_register_library_map(array( 'PhrictionRemarkupRule' => 'PhutilRemarkupRule', 'PhrictionReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhrictionSchemaSpec' => 'PhabricatorConfigSchemaSpec', - 'PhrictionSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhrictionTransaction' => 'PhabricatorModularTransaction', 'PhrictionTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhrictionTransactionEditor' => 'PhabricatorApplicationTransactionEditor', diff --git a/src/applications/phriction/conduit/PhrictionDocumentSearchConduitAPIMethod.php b/src/applications/phriction/conduit/PhrictionDocumentSearchConduitAPIMethod.php new file mode 100644 index 0000000000..f51faede5f --- /dev/null +++ b/src/applications/phriction/conduit/PhrictionDocumentSearchConduitAPIMethod.php @@ -0,0 +1,18 @@ +getStatusSpecification($key)); + } + + public static function getStatusMap() { + $map = id(new self())->getStatusSpecifications(); + return ipull($map, 'name', 'key'); + } + + public function isActive() { + return ($this->getKey() == self::STATUS_EXISTS); + } + + protected function newStatusSpecifications() { + return array( + array( + 'key' => self::STATUS_EXISTS, + 'name' => pht('Active'), + 'icon' => 'fa-file-text-o', + 'color' => 'bluegrey', + ), + array( + 'key' => self::STATUS_DELETED, + 'name' => pht('Deleted'), + 'icon' => 'fa-file-text-o', + 'color' => 'grey', + ), + array( + 'key' => self::STATUS_MOVED, + 'name' => pht('Moved'), + 'icon' => 'fa-arrow-right', + 'color' => 'grey', + ), + array( + 'key' => self::STATUS_STUB, + 'name' => pht('Stub'), + 'icon' => 'fa-file-text-o', + 'color' => 'grey', + ), + ); + } } diff --git a/src/applications/phriction/controller/PhrictionController.php b/src/applications/phriction/controller/PhrictionController.php index 277692334f..51ffbcab1f 100644 --- a/src/applications/phriction/controller/PhrictionController.php +++ b/src/applications/phriction/controller/PhrictionController.php @@ -13,7 +13,7 @@ abstract class PhrictionController extends PhabricatorController { $nav->addFilter('/phriction/', pht('Index')); } - id(new PhrictionSearchEngine()) + id(new PhrictionDocumentSearchEngine()) ->setViewer($user) ->addNavigationItems($nav->getMenu()); diff --git a/src/applications/phriction/controller/PhrictionListController.php b/src/applications/phriction/controller/PhrictionListController.php index 44ef95f178..7da29b297d 100644 --- a/src/applications/phriction/controller/PhrictionListController.php +++ b/src/applications/phriction/controller/PhrictionListController.php @@ -12,7 +12,7 @@ final class PhrictionListController $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($querykey) - ->setSearchEngine(new PhrictionSearchEngine()) + ->setSearchEngine(new PhrictionDocumentSearchEngine()) ->setNavigation($this->buildSideNavView()); return $this->delegateToController($controller); diff --git a/src/applications/phriction/query/PhrictionDocumentQuery.php b/src/applications/phriction/query/PhrictionDocumentQuery.php index b5118146d3..f6073b2907 100644 --- a/src/applications/phriction/query/PhrictionDocumentQuery.php +++ b/src/applications/phriction/query/PhrictionDocumentQuery.php @@ -12,12 +12,7 @@ final class PhrictionDocumentQuery private $needContent; - private $status = 'status-any'; - const STATUS_ANY = 'status-any'; - const STATUS_OPEN = 'status-open'; - const STATUS_NONSTUB = 'status-nonstub'; - - const ORDER_HIERARCHY = 'order-hierarchy'; + const ORDER_HIERARCHY = 'hierarchy'; public function withIDs(array $ids) { $this->ids = $ids; @@ -49,11 +44,6 @@ final class PhrictionDocumentQuery return $this; } - public function withStatus($status) { - $this->status = $status; - return $this; - } - public function needContent($need_content) { $this->needContent = $need_content; return $this; @@ -224,42 +214,16 @@ final class PhrictionDocumentQuery $this->depths); } - switch ($this->status) { - case self::STATUS_OPEN: - $where[] = qsprintf( - $conn, - 'd.status NOT IN (%Ld)', - array( - PhrictionDocumentStatus::STATUS_DELETED, - PhrictionDocumentStatus::STATUS_MOVED, - PhrictionDocumentStatus::STATUS_STUB, - )); - break; - case self::STATUS_NONSTUB: - $where[] = qsprintf( - $conn, - 'd.status NOT IN (%Ld)', - array( - PhrictionDocumentStatus::STATUS_MOVED, - PhrictionDocumentStatus::STATUS_STUB, - )); - break; - case self::STATUS_ANY: - break; - default: - throw new Exception(pht("Unknown status '%s'!", $this->status)); - } - return $where; } public function getBuiltinOrders() { - return array( + return parent::getBuiltinOrders() + array( self::ORDER_HIERARCHY => array( 'vector' => array('depth', 'title', 'updated'), 'name' => pht('Hierarchy'), ), - ) + parent::getBuiltinOrders(); + ); } public function getOrderableColumns() { diff --git a/src/applications/phriction/query/PhrictionSearchEngine.php b/src/applications/phriction/query/PhrictionDocumentSearchEngine.php similarity index 72% rename from src/applications/phriction/query/PhrictionSearchEngine.php rename to src/applications/phriction/query/PhrictionDocumentSearchEngine.php index 245500e0ff..e578b48346 100644 --- a/src/applications/phriction/query/PhrictionSearchEngine.php +++ b/src/applications/phriction/query/PhrictionDocumentSearchEngine.php @@ -1,6 +1,6 @@ needContent(true) - ->withStatus(PhrictionDocumentQuery::STATUS_NONSTUB); + ->needContent(true); } protected function buildQueryFromParameters(array $map) { $query = $this->newQuery(); - if ($map['status']) { - $query->withStatus($map['status']); + if ($map['statuses']) { + $query->withStatuses($map['statuses']); } return $query; @@ -29,10 +28,10 @@ final class PhrictionSearchEngine protected function buildCustomSearchFields() { return array( - id(new PhabricatorSearchSelectField()) - ->setKey('status') + id(new PhabricatorSearchCheckboxesField()) + ->setKey('statuses') ->setLabel(pht('Status')) - ->setOptions($this->getStatusOptions()), + ->setOptions(PhrictionDocumentStatus::getStatusMap()), ); } @@ -59,19 +58,15 @@ final class PhrictionSearchEngine return $query; case 'active': return $query->setParameter( - 'status', PhrictionDocumentQuery::STATUS_OPEN); + 'statuses', + array( + PhrictionDocumentStatus::STATUS_EXISTS, + )); } return parent::buildSavedQueryFromBuiltin($query_key); } - private function getStatusOptions() { - return array( - PhrictionDocumentQuery::STATUS_OPEN => pht('Show Active Documents'), - PhrictionDocumentQuery::STATUS_NONSTUB => pht('Show All Documents'), - ); - } - protected function getRequiredHandlePHIDsForResultList( array $documents, PhabricatorSavedQuery $query) { @@ -118,15 +113,14 @@ final class PhrictionSearchEngine $item->addAttribute($slug_uri); - switch ($document->getStatus()) { - case PhrictionDocumentStatus::STATUS_DELETED: - $item->setDisabled(true); - $item->addIcon('delete', pht('Deleted')); - break; - case PhrictionDocumentStatus::STATUS_MOVED: - $item->setDisabled(true); - $item->addIcon('arrow-right', pht('Moved Away')); - break; + $icon = $document->getStatusIcon(); + $color = $document->getStatusColor(); + $label = $document->getStatusDisplayName(); + + $item->setStatusIcon("{$icon} {$color}", $label); + + if (!$document->isActive()) { + $item->setDisabled(true); } $list->addItem($item); diff --git a/src/applications/phriction/search/PhrictionDocumentFerretEngine.php b/src/applications/phriction/search/PhrictionDocumentFerretEngine.php index 76802391e7..498b9274f0 100644 --- a/src/applications/phriction/search/PhrictionDocumentFerretEngine.php +++ b/src/applications/phriction/search/PhrictionDocumentFerretEngine.php @@ -12,7 +12,7 @@ final class PhrictionDocumentFerretEngine } public function newSearchEngine() { - return new PhrictionSearchEngine(); + return new PhrictionDocumentSearchEngine(); } } diff --git a/src/applications/phriction/storage/PhrictionDocument.php b/src/applications/phriction/storage/PhrictionDocument.php index 0a9f09afdd..730b8e09bf 100644 --- a/src/applications/phriction/storage/PhrictionDocument.php +++ b/src/applications/phriction/storage/PhrictionDocument.php @@ -148,6 +148,29 @@ final class PhrictionDocument extends PhrictionDAO return $this; } +/* -( Status )------------------------------------------------------------- */ + + + public function getStatusObject() { + return PhrictionDocumentStatus::newStatusObject($this->getStatus()); + } + + public function getStatusIcon() { + return $this->getStatusObject()->getIcon(); + } + + public function getStatusColor() { + return $this->getStatusObject()->getColor(); + } + + public function getStatusDisplayName() { + return $this->getStatusObject()->getDisplayName(); + } + + public function isActive() { + return $this->getStatusObject()->isActive(); + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ From 143350fdba487388e31cba1fccaa1f3707596795 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 15 Feb 2018 12:34:56 -0800 Subject: [PATCH 46/58] Give Phriction documents modern string status constants instead of numeric constants Summary: Depends on D19099. Ref T13077. Updates Phriction documents to string constants to make API interactions cleaner and statuses more practical to extend. This does not seem to require any transaction migrations because none of the Phriction transactions actually store status values: status is always a side effect of other edits. Test Plan: Created, edited, deleted, moved documents. Saw appropriate UI cues. Browsed and filtered documents by status in the index. Maniphest Tasks: T13077 Differential Revision: https://secure.phabricator.com/D19100 --- .../autopatches/20180215.phriction.05.statustext.sql | 2 ++ .../autopatches/20180215.phriction.06.statusvalue.sql | 11 +++++++++++ .../phriction/constants/PhrictionDocumentStatus.php | 8 ++++---- .../phriction/query/PhrictionDocumentQuery.php | 2 +- .../phriction/storage/PhrictionDocument.php | 2 +- 5 files changed, 19 insertions(+), 6 deletions(-) create mode 100644 resources/sql/autopatches/20180215.phriction.05.statustext.sql create mode 100644 resources/sql/autopatches/20180215.phriction.06.statusvalue.sql diff --git a/resources/sql/autopatches/20180215.phriction.05.statustext.sql b/resources/sql/autopatches/20180215.phriction.05.statustext.sql new file mode 100644 index 0000000000..756f7ac968 --- /dev/null +++ b/resources/sql/autopatches/20180215.phriction.05.statustext.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_phriction.phriction_document + CHANGE status status VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20180215.phriction.06.statusvalue.sql b/resources/sql/autopatches/20180215.phriction.06.statusvalue.sql new file mode 100644 index 0000000000..381de77643 --- /dev/null +++ b/resources/sql/autopatches/20180215.phriction.06.statusvalue.sql @@ -0,0 +1,11 @@ +UPDATE {$NAMESPACE}_phriction.phriction_document + SET status = 'active' WHERE status = '0'; + +UPDATE {$NAMESPACE}_phriction.phriction_document + SET status = 'deleted' WHERE status = '1'; + +UPDATE {$NAMESPACE}_phriction.phriction_document + SET status = 'moved' WHERE status = '2'; + +UPDATE {$NAMESPACE}_phriction.phriction_document + SET status = 'stub' WHERE status = '3'; diff --git a/src/applications/phriction/constants/PhrictionDocumentStatus.php b/src/applications/phriction/constants/PhrictionDocumentStatus.php index b042cd78d3..1830f3ec95 100644 --- a/src/applications/phriction/constants/PhrictionDocumentStatus.php +++ b/src/applications/phriction/constants/PhrictionDocumentStatus.php @@ -3,10 +3,10 @@ final class PhrictionDocumentStatus extends PhabricatorObjectStatus { - const STATUS_EXISTS = 0; - const STATUS_DELETED = 1; - const STATUS_MOVED = 2; - const STATUS_STUB = 3; + const STATUS_EXISTS = 'active'; + const STATUS_DELETED = 'deleted'; + const STATUS_MOVED = 'moved'; + const STATUS_STUB = 'stub'; public static function getConduitConstant($const) { static $map = array( diff --git a/src/applications/phriction/query/PhrictionDocumentQuery.php b/src/applications/phriction/query/PhrictionDocumentQuery.php index f6073b2907..de3064fd42 100644 --- a/src/applications/phriction/query/PhrictionDocumentQuery.php +++ b/src/applications/phriction/query/PhrictionDocumentQuery.php @@ -196,7 +196,7 @@ final class PhrictionDocumentQuery if ($this->statuses !== null) { $where[] = qsprintf( $conn, - 'd.status IN (%Ld)', + 'd.status IN (%Ls)', $this->statuses); } diff --git a/src/applications/phriction/storage/PhrictionDocument.php b/src/applications/phriction/storage/PhrictionDocument.php index 730b8e09bf..1942f16efe 100644 --- a/src/applications/phriction/storage/PhrictionDocument.php +++ b/src/applications/phriction/storage/PhrictionDocument.php @@ -31,7 +31,7 @@ final class PhrictionDocument extends PhrictionDAO 'slug' => 'sort128', 'depth' => 'uint32', 'contentID' => 'id?', - 'status' => 'uint32', + 'status' => 'text32', 'mailKey' => 'bytes20', ), self::CONFIG_KEY_SCHEMA => array( From 45403b162aa52fad1eb739bcb409e58a432207ad Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 15 Feb 2018 17:02:23 -0800 Subject: [PATCH 47/58] Flesh out "phriction.document.search" slightly and provide page text for content/documents Summary: Depends on D19100. Ref T13077. Adds a "content" attachment to get the actual page text. This works on both "phriction.document.search" and "phriction.content.search". Test Plan: Called both API methods with the attachment, saw proper text content returned. Maniphest Tasks: T13077 Differential Revision: https://secure.phabricator.com/D19103 --- src/__phutil_library_map__.php | 3 ++ ...PhrictionContentSearchEngineAttachment.php | 31 +++++++++++++++ .../phriction/storage/PhrictionContent.php | 10 ++--- .../phriction/storage/PhrictionDocument.php | 38 ++++++++++++++++++- 4 files changed, 75 insertions(+), 7 deletions(-) create mode 100644 src/applications/phriction/engineextension/PhrictionContentSearchEngineAttachment.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 4c9d40d9bc..f04f930863 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4849,6 +4849,7 @@ phutil_register_library_map(array( 'PhrictionContentQuery' => 'applications/phriction/query/PhrictionContentQuery.php', 'PhrictionContentSearchConduitAPIMethod' => 'applications/phriction/conduit/PhrictionContentSearchConduitAPIMethod.php', 'PhrictionContentSearchEngine' => 'applications/phriction/query/PhrictionContentSearchEngine.php', + 'PhrictionContentSearchEngineAttachment' => 'applications/phriction/engineextension/PhrictionContentSearchEngineAttachment.php', 'PhrictionController' => 'applications/phriction/controller/PhrictionController.php', 'PhrictionCreateConduitAPIMethod' => 'applications/phriction/conduit/PhrictionCreateConduitAPIMethod.php', 'PhrictionDAO' => 'applications/phriction/storage/PhrictionDAO.php', @@ -10768,6 +10769,7 @@ phutil_register_library_map(array( 'PhrictionContentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhrictionContentSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'PhrictionContentSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'PhrictionContentSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment', 'PhrictionController' => 'PhabricatorController', 'PhrictionCreateConduitAPIMethod' => 'PhrictionConduitAPIMethod', 'PhrictionDAO' => 'PhabricatorLiskDAO', @@ -10784,6 +10786,7 @@ phutil_register_library_map(array( 'PhabricatorFerretInterface', 'PhabricatorProjectInterface', 'PhabricatorApplicationTransactionInterface', + 'PhabricatorConduitResultInterface', ), 'PhrictionDocumentAuthorHeraldField' => 'PhrictionDocumentHeraldField', 'PhrictionDocumentContentHeraldField' => 'PhrictionDocumentHeraldField', diff --git a/src/applications/phriction/engineextension/PhrictionContentSearchEngineAttachment.php b/src/applications/phriction/engineextension/PhrictionContentSearchEngineAttachment.php new file mode 100644 index 0000000000..3321f23eb0 --- /dev/null +++ b/src/applications/phriction/engineextension/PhrictionContentSearchEngineAttachment.php @@ -0,0 +1,31 @@ +getContent(); + } else { + $content = $object; + } + + return array( + 'title' => $content->getTitle(), + 'path' => $content->getSlug(), + 'authorPHID' => $content->getAuthorPHID(), + 'content' => array( + 'raw' => $content->getContent(), + ), + ); + } + +} diff --git a/src/applications/phriction/storage/PhrictionContent.php b/src/applications/phriction/storage/PhrictionContent.php index dc6bf19faa..80fabcaddc 100644 --- a/src/applications/phriction/storage/PhrictionContent.php +++ b/src/applications/phriction/storage/PhrictionContent.php @@ -118,10 +118,6 @@ final class PhrictionContent ->setKey('version') ->setType('int') ->setDescription(pht('Content version.')), - id(new PhabricatorConduitSearchFieldSpecification()) - ->setKey('authorPHID') - ->setType('phid') - ->setDescription(pht('Author of this version of the content.')), ); } @@ -129,12 +125,14 @@ final class PhrictionContent return array( 'documentPHID' => $this->getDocument()->getPHID(), 'version' => (int)$this->getVersion(), - 'authorPHID' => $this->getAuthorPHID(), ); } public function getConduitSearchAttachments() { - return array(); + return array( + id(new PhrictionContentSearchEngineAttachment()) + ->setAttachmentKey('content'), + ); } } diff --git a/src/applications/phriction/storage/PhrictionDocument.php b/src/applications/phriction/storage/PhrictionDocument.php index 1942f16efe..58d7cbee4a 100644 --- a/src/applications/phriction/storage/PhrictionDocument.php +++ b/src/applications/phriction/storage/PhrictionDocument.php @@ -10,7 +10,8 @@ final class PhrictionDocument extends PhrictionDAO PhabricatorFulltextInterface, PhabricatorFerretInterface, PhabricatorProjectInterface, - PhabricatorApplicationTransactionInterface { + PhabricatorApplicationTransactionInterface, + PhabricatorConduitResultInterface { protected $slug; protected $depth; @@ -288,4 +289,39 @@ final class PhrictionDocument extends PhrictionDAO return new PhrictionDocumentFerretEngine(); } + +/* -( PhabricatorConduitResultInterface )---------------------------------- */ + + + public function getFieldSpecificationsForConduit() { + return array( + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('path') + ->setType('string') + ->setDescription(pht('The path to the document.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('status') + ->setType('map') + ->setDescription(pht('Status information about the document.')), + ); + } + + public function getFieldValuesForConduit() { + $status = array( + 'value' => $this->getStatus(), + 'name' => $this->getStatusDisplayName(), + ); + + return array( + 'path' => $this->getSlug(), + 'status' => $status, + ); + } + + public function getConduitSearchAttachments() { + return array( + id(new PhrictionContentSearchEngineAttachment()) + ->setAttachmentKey('content'), + ); + } } From f713e1dfc160009a7ee0d1b2b73e0f9aff29e7d3 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 16 Feb 2018 04:30:48 -0800 Subject: [PATCH 48/58] Add Owners Package support for "Commit Hook: Content" Herald rules Summary: See PHI370. Support the "Affected packages" and "Affected package owners" Herald fields in pre-commit hooks. I believe there's no technical reason these fields aren't supported and this was just overlooked. Test Plan: Wrote a rule which makes use of the new fields, pushed commits through it. Checked transcripts and saw sensible-looking values. Differential Revision: https://secure.phabricator.com/D19104 --- src/__phutil_library_map__.php | 4 +++ ...sionPreCommitContentPackageHeraldField.php | 29 ++++++++++++++++ ...reCommitContentPackageOwnerHeraldField.php | 34 +++++++++++++++++++ .../herald/HeraldPreCommitContentAdapter.php | 14 ++++++++ .../PhabricatorEmailFormatSettingsPanel.php | 5 ++- 5 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 src/applications/diffusion/herald/DiffusionPreCommitContentPackageHeraldField.php create mode 100644 src/applications/diffusion/herald/DiffusionPreCommitContentPackageOwnerHeraldField.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index f04f930863..c2ee4966c1 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -812,6 +812,8 @@ phutil_register_library_map(array( 'DiffusionPreCommitContentHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentHeraldField.php', 'DiffusionPreCommitContentMergeHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentMergeHeraldField.php', 'DiffusionPreCommitContentMessageHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentMessageHeraldField.php', + 'DiffusionPreCommitContentPackageHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentPackageHeraldField.php', + 'DiffusionPreCommitContentPackageOwnerHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentPackageOwnerHeraldField.php', 'DiffusionPreCommitContentPusherHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentPusherHeraldField.php', 'DiffusionPreCommitContentPusherIsCommitterHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentPusherIsCommitterHeraldField.php', 'DiffusionPreCommitContentPusherProjectsHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentPusherProjectsHeraldField.php', @@ -6007,6 +6009,8 @@ phutil_register_library_map(array( 'DiffusionPreCommitContentHeraldField' => 'HeraldField', 'DiffusionPreCommitContentMergeHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitContentMessageHeraldField' => 'DiffusionPreCommitContentHeraldField', + 'DiffusionPreCommitContentPackageHeraldField' => 'DiffusionPreCommitContentHeraldField', + 'DiffusionPreCommitContentPackageOwnerHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitContentPusherHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitContentPusherIsCommitterHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitContentPusherProjectsHeraldField' => 'DiffusionPreCommitContentHeraldField', diff --git a/src/applications/diffusion/herald/DiffusionPreCommitContentPackageHeraldField.php b/src/applications/diffusion/herald/DiffusionPreCommitContentPackageHeraldField.php new file mode 100644 index 0000000000..084d3b2d00 --- /dev/null +++ b/src/applications/diffusion/herald/DiffusionPreCommitContentPackageHeraldField.php @@ -0,0 +1,29 @@ +getAdapter()->loadAffectedPackages(); + return mpull($packages, 'getPHID'); + } + + protected function getHeraldFieldStandardType() { + return self::STANDARD_PHID_LIST; + } + + protected function getDatasource() { + return new PhabricatorOwnersPackageDatasource(); + } + +} diff --git a/src/applications/diffusion/herald/DiffusionPreCommitContentPackageOwnerHeraldField.php b/src/applications/diffusion/herald/DiffusionPreCommitContentPackageOwnerHeraldField.php new file mode 100644 index 0000000000..564ef36827 --- /dev/null +++ b/src/applications/diffusion/herald/DiffusionPreCommitContentPackageOwnerHeraldField.php @@ -0,0 +1,34 @@ +getAdapter()->loadAffectedPackages(); + if (!$packages) { + return array(); + } + + $owners = PhabricatorOwnersOwner::loadAllForPackages($packages); + return mpull($owners, 'getUserPHID'); + } + + protected function getHeraldFieldStandardType() { + return self::STANDARD_PHID_LIST; + } + + protected function getDatasource() { + return new PhabricatorProjectOrUserDatasource(); + } + +} diff --git a/src/applications/diffusion/herald/HeraldPreCommitContentAdapter.php b/src/applications/diffusion/herald/HeraldPreCommitContentAdapter.php index c93ba32345..cee5f97419 100644 --- a/src/applications/diffusion/herald/HeraldPreCommitContentAdapter.php +++ b/src/applications/diffusion/herald/HeraldPreCommitContentAdapter.php @@ -7,6 +7,8 @@ final class HeraldPreCommitContentAdapter extends HeraldPreCommitAdapter { private $fields; private $revision = false; + private $affectedPackages; + public function getAdapterContentName() { return pht('Commit Hook: Commit Content'); } @@ -223,4 +225,16 @@ final class HeraldPreCommitContentAdapter extends HeraldPreCommitAdapter { $this->getObject()->getRefNew()); } + public function loadAffectedPackages() { + if ($this->affectedPackages === null) { + $packages = PhabricatorOwnersPackage::loadAffectedPackages( + $this->getHookEngine()->getRepository(), + $this->getDiffContent('name')); + $this->affectedPackages = $packages; + } + + return $this->affectedPackages; + } + + } diff --git a/src/applications/settings/panel/PhabricatorEmailFormatSettingsPanel.php b/src/applications/settings/panel/PhabricatorEmailFormatSettingsPanel.php index bdc3c994b3..51ff40ed9d 100644 --- a/src/applications/settings/panel/PhabricatorEmailFormatSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorEmailFormatSettingsPanel.php @@ -18,7 +18,9 @@ final class PhabricatorEmailFormatSettingsPanel } public function isManagementPanel() { - if (!$this->isUserPanel()) { + return false; +/* + if (!$this->isUserPanel()) { return false; } @@ -27,6 +29,7 @@ final class PhabricatorEmailFormatSettingsPanel } return false; +*/ } public function isTemplatePanel() { From bfdc9411f705cc47ad15e128e15b2c7f959715cb Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 16 Feb 2018 04:58:46 -0800 Subject: [PATCH 49/58] Provide context objects for remarkup mail rendering, fixing Phriction relative URIs in initial email Summary: Fixes T10969. Ref T13077. When you create a Phriction document with a relative link (`[[ ./path/to/page ]]`) the initial email currently points to the wrong place. This is because the context object (the page) isn't passed to the markup engine. Without this context, the relative link is rendered as though it appeared somewhere else (like a task or revision) where relative links don't make sense. Test Plan: Created a new Phriction document with a relative link to `[[ ./porcupine_facts/starmap ]]`, saw a usable link in the resulting email. Maniphest Tasks: T13077, T10969 Differential Revision: https://secure.phabricator.com/D19105 --- .../view/PhabricatorMetaMTAMailBody.php | 41 +++++++++++++++---- .../markup/PhrictionRemarkupRule.php | 38 ++++++++++++----- ...habricatorApplicationTransactionEditor.php | 6 ++- 3 files changed, 63 insertions(+), 22 deletions(-) diff --git a/src/applications/metamta/view/PhabricatorMetaMTAMailBody.php b/src/applications/metamta/view/PhabricatorMetaMTAMailBody.php index 8a21a75cfb..6aaf4fb052 100644 --- a/src/applications/metamta/view/PhabricatorMetaMTAMailBody.php +++ b/src/applications/metamta/view/PhabricatorMetaMTAMailBody.php @@ -13,6 +13,7 @@ final class PhabricatorMetaMTAMailBody extends Phobject { private $attachments = array(); private $viewer; + private $contextObject; public function getViewer() { return $this->viewer; @@ -23,6 +24,16 @@ final class PhabricatorMetaMTAMailBody extends Phobject { return $this; } + public function setContextObject($context_object) { + $this->contextObject = $context_object; + return $this; + } + + public function getContextObject() { + return $this->contextObject; + } + + /* -( Composition )-------------------------------------------------------- */ @@ -45,9 +56,9 @@ final class PhabricatorMetaMTAMailBody extends Phobject { public function addRemarkupSection($header, $text) { try { - $engine = PhabricatorMarkupEngine::newMarkupEngine(array()); - $engine->setConfig('viewer', $this->getViewer()); - $engine->setMode(PhutilRemarkupEngine::MODE_TEXT); + $engine = $this->newMarkupEngine() + ->setMode(PhutilRemarkupEngine::MODE_TEXT); + $styled_text = $engine->markupText($text); $this->addPlaintextSection($header, $styled_text); } catch (Exception $ex) { @@ -56,12 +67,9 @@ final class PhabricatorMetaMTAMailBody extends Phobject { } try { - $mail_engine = PhabricatorMarkupEngine::newMarkupEngine(array()); - $mail_engine->setConfig('viewer', $this->getViewer()); - $mail_engine->setMode(PhutilRemarkupEngine::MODE_HTML_MAIL); - $mail_engine->setConfig( - 'uri.base', - PhabricatorEnv::getProductionURI('/')); + $mail_engine = $this->newMarkupEngine() + ->setMode(PhutilRemarkupEngine::MODE_HTML_MAIL); + $html = $mail_engine->markupText($text); $this->addHTMLSection($header, $html); } catch (Exception $ex) { @@ -215,4 +223,19 @@ final class PhabricatorMetaMTAMailBody extends Phobject { private function indent($text) { return rtrim(" ".str_replace("\n", "\n ", $text)); } + + + private function newMarkupEngine() { + $engine = PhabricatorMarkupEngine::newMarkupEngine(array()) + ->setConfig('viewer', $this->getViewer()) + ->setConfig('uri.base', PhabricatorEnv::getProductionURI('/')); + + $context = $this->getContextObject(); + if ($context) { + $engine->setConfig('contextObject', $context); + } + + return $engine; + } + } diff --git a/src/applications/phriction/markup/PhrictionRemarkupRule.php b/src/applications/phriction/markup/PhrictionRemarkupRule.php index 9c5649bbbe..c4bc1f7249 100644 --- a/src/applications/phriction/markup/PhrictionRemarkupRule.php +++ b/src/applications/phriction/markup/PhrictionRemarkupRule.php @@ -28,17 +28,7 @@ final class PhrictionRemarkupRule extends PhutilRemarkupRule { // Handle relative links. if ((substr($link, 0, 2) === './') || (substr($link, 0, 3) === '../')) { - $base = null; - $context = $this->getEngine()->getConfig('contextObject'); - if ($context !== null && $context instanceof PhrictionContent) { - // Handle content when it's being rendered in document view. - $base = $context->getSlug(); - } - if ($context !== null && is_array($context) && - idx($context, 'phriction.isPreview')) { - // Handle content when it's a preview for the Phriction editor. - $base = idx($context, 'phriction.slug'); - } + $base = $this->getRelativeBaseURI(); if ($base !== null) { $base_parts = explode('/', rtrim($base, '/')); $rel_parts = explode('/', rtrim($link, '/')); @@ -195,4 +185,30 @@ final class PhrictionRemarkupRule extends PhutilRemarkupRule { } } + private function getRelativeBaseURI() { + $context = $this->getEngine()->getConfig('contextObject'); + + if (!$context) { + return null; + } + + // Handle content when it's a preview for the Phriction editor. + if (is_array($context)) { + if (idx($context, 'phriction.isPreview')) { + return idx($context, 'phriction.slug'); + } + } + + if ($context instanceof PhrictionContent) { + return $context->getSlug(); + } + + if ($context instanceof PhrictionDocument) { + return $context->getSlug(); + } + + return null; + } + + } diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index 7cb1300feb..1d02d0fdb0 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -2880,11 +2880,13 @@ abstract class PhabricatorApplicationTransactionEditor PhabricatorLiskDAO $object, array $xactions) { - $body = new PhabricatorMetaMTAMailBody(); - $body->setViewer($this->requireActor()); + $body = id(new PhabricatorMetaMTAMailBody()) + ->setViewer($this->requireActor()) + ->setContextObject($object); $this->addHeadersAndCommentsToMailBody($body, $xactions); $this->addCustomFieldsToMailBody($body, $object, $xactions); + return $body; } From b8bb4d3ad590a2b01c135302df145d50bc048765 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 16 Feb 2018 06:26:02 -0800 Subject: [PATCH 50/58] Accept either "[[ %24doge ]]" or "[[ $doge ]]" as references to the "/w/$doge/" Phriction document Summary: Depends on D19105. Ref T13077. Fixes T12344. The `[[ ... ]]` syntax accepts and handles characters which would require URL encoding if they appeared in URIs. For example, `[[ 100% Natural Cheese Dust ]]` is a legitimate, supported piece of remarkup syntax, and does not need to be written as `... 100%25 Natural ...`. Likewise, `[[ BUY $DOGE ]]` is legitimate and does not need to be written as `[[ BUY %24DOGE ]]`. This piece of syntax creates a link to `/w/buy_$doge/`. This may or may not appear in your browser's URL bar as `/w/buy_%24doge/`, but internally "$" is a valid slug character and you'll see `buy_$doge` over Conduit, etc. However, since users may reasonably copy paths from their browser URL bar, they may have unnecessary URL encoding. The syntax `[[ buy_$doge ]]` is legitimate, but a user copy/pasting may write `[[ buy_%24doge ]]` instead. Currently, this extra URL encoding causes links to break, since `[[ buy_%24doge ]]` is a treated as link to `/w/buy_24doge/`, just like `[[ Fresh 100%AB Blood ]]` is a link to `/w/fresh_100_ab_blood/`. To fix this: - When the target for a link can be URL decoded, try to do lookups on both the un-decoded and decoded variations. - If the un-decoded variation works, great: use it. This preserves behavior for all existing, working links. - If the un-decoded variation fails but the decoded variation works, okay: we'll assume you copy-pasted a URL-encoded version and strip URL encoding. - If both fail, same behavior as before. Also, use a different spelling for "existent". See T13084 for some "attacks" based on this behavior. I think the usability affordance this behavior provides greatly outweighs the very mild threat those attacks represent. Test Plan: - Created links to existing, nonexisting, and existing-but-not-visible documents, all of which worked normally. - Created links to `[[ $doge ]]` and `[[ %24doge ]]`, saw them both go to the right place. - Performed the "attacks" in T13084. Maniphest Tasks: T13077, T12344 Differential Revision: https://secure.phabricator.com/D19106 --- .../markup/PhrictionRemarkupRule.php | 105 +++++++++++++++--- 1 file changed, 88 insertions(+), 17 deletions(-) diff --git a/src/applications/phriction/markup/PhrictionRemarkupRule.php b/src/applications/phriction/markup/PhrictionRemarkupRule.php index c4bc1f7249..a6362d1624 100644 --- a/src/applications/phriction/markup/PhrictionRemarkupRule.php +++ b/src/applications/phriction/markup/PhrictionRemarkupRule.php @@ -83,28 +83,97 @@ final class PhrictionRemarkupRule extends PhutilRemarkupRule { return; } + $viewer = $engine->getConfig('viewer'); + $slugs = ipull($metadata, 'link'); - foreach ($slugs as $key => $slug) { - $slugs[$key] = PhabricatorSlug::normalize($slug); + + $load_map = array(); + foreach ($slugs as $key => $raw_slug) { + $lookup = PhabricatorSlug::normalize($raw_slug); + $load_map[$lookup][] = $key; + + // Also try to lookup the slug with URL decoding applied. The right + // way to link to a page titled "$cash" is to write "[[ $cash ]]" (and + // not the URL encoded form "[[ %24cash ]]"), but users may reasonably + // have copied URL encoded variations out of their browser location + // bar or be skeptical that "[[ $cash ]]" will actually work. + + $lookup = phutil_unescape_uri_path_component($raw_slug); + $lookup = phutil_utf8ize($lookup); + $lookup = PhabricatorSlug::normalize($lookup); + $load_map[$lookup][] = $key; } - // We have to make two queries here to distinguish between - // documents the user can't see, and documents that don't - // exist. $visible_documents = id(new PhrictionDocumentQuery()) - ->setViewer($engine->getConfig('viewer')) - ->withSlugs($slugs) + ->setViewer($viewer) + ->withSlugs(array_keys($load_map)) ->needContent(true) ->execute(); - $existant_documents = id(new PhrictionDocumentQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withSlugs($slugs) - ->execute(); - $visible_documents = mpull($visible_documents, null, 'getSlug'); - $existant_documents = mpull($existant_documents, null, 'getSlug'); + $document_map = array(); + foreach ($load_map as $lookup => $keys) { + $visible = idx($visible_documents, $lookup); + if (!$visible) { + continue; + } - foreach ($metadata as $spec) { + foreach ($keys as $key) { + $document_map[$key] = array( + 'visible' => true, + 'document' => $visible, + ); + } + + unset($load_map[$lookup]); + } + + // For each document we found, remove all remaining requests for it from + // the load map. If we remove all requests for a slug, remove the slug. + // This stops us from doing unnecessary lookups on alternate names for + // documents we already found. + foreach ($load_map as $lookup => $keys) { + foreach ($keys as $lookup_key => $key) { + if (isset($document_map[$key])) { + unset($keys[$lookup_key]); + } + } + + if (!$keys) { + unset($load_map[$lookup]); + continue; + } + + $load_map[$lookup] = $keys; + } + + + // If we still have links we haven't found a document for, do another + // query with the omnipotent viewer so we can distinguish between pages + // which do not exist and pages which exist but which the viewer does not + // have permission to see. + if ($load_map) { + $existent_documents = id(new PhrictionDocumentQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withSlugs(array_keys($load_map)) + ->execute(); + $existent_documents = mpull($existent_documents, null, 'getSlug'); + + foreach ($load_map as $lookup => $keys) { + $existent = idx($existent_documents, $lookup); + if (!$existent) { + continue; + } + + foreach ($keys as $key) { + $document_map[$key] = array( + 'visible' => false, + 'document' => null, + ); + } + } + } + + foreach ($metadata as $key => $spec) { $link = $spec['link']; $slug = PhabricatorSlug::normalize($link); $name = $spec['explicitName']; @@ -114,14 +183,16 @@ final class PhrictionRemarkupRule extends PhutilRemarkupRule { // in text as: "Title" . Otherwise, we'll just render: . $is_interesting_name = (bool)strlen($name); - if (idx($existant_documents, $slug) === null) { + $target = idx($document_map, $key, null); + + if ($target === null) { // The target document doesn't exist. if ($name === null) { $name = explode('/', trim($link, '/')); $name = end($name); } $class = 'phriction-link-missing'; - } else if (idx($visible_documents, $slug) === null) { + } else if (!$target['visible']) { // The document exists, but the user can't see it. if ($name === null) { $name = explode('/', trim($link, '/')); @@ -131,7 +202,7 @@ final class PhrictionRemarkupRule extends PhutilRemarkupRule { } else { if ($name === null) { // Use the title of the document if no name is set. - $name = $visible_documents[$slug] + $name = $target['document'] ->getContent() ->getTitle(); From f82206a4d1af78ce0de9046cae03aadf86355562 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 16 Feb 2018 07:09:17 -0800 Subject: [PATCH 51/58] Add a rough Quick Search datasource for Phriction documents Summary: Depends on D19106. Fixes T5941. Ref T13077. Allows you to find Phriction documents as suggestions from global quick search. Also supports `w` to jump to Phriction and `w query` to query Phriction. The actual query logic for the datasource may need some tweaking after it collides with reality, but seems to produce fairly reasonable results in local testing against synthetic data. Test Plan: Searched for "Porcupine Facts", "Travel Companions", and other useful local pages. Searched for `w`. Searched for `w zebra facts`. Maniphest Tasks: T13077, T5941 Differential Revision: https://secure.phabricator.com/D19107 --- resources/celerity/map.php | 28 +++--- src/__phutil_library_map__.php | 4 + .../PhrictionDatasourceEngineExtension.php | 56 ++++++++++++ .../phriction/storage/PhrictionDocument.php | 4 + .../typeahead/PhrictionDocumentDatasource.php | 88 +++++++++++++++++++ .../rsrc/js/core/behavior-search-typeahead.js | 1 + 6 files changed, 167 insertions(+), 14 deletions(-) create mode 100644 src/applications/phriction/engineextension/PhrictionDatasourceEngineExtension.php create mode 100644 src/applications/phriction/typeahead/PhrictionDocumentDatasource.php diff --git a/resources/celerity/map.php b/resources/celerity/map.php index a08f04658e..fc3961604b 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' => 'e4f098a5', - 'core.pkg.js' => '3ac6e174', + 'core.pkg.js' => 'bd19de1c', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '113e692c', 'differential.pkg.js' => 'f6d809c0', @@ -506,7 +506,7 @@ return array( 'rsrc/js/core/behavior-reorder-applications.js' => '76b9fc3e', 'rsrc/js/core/behavior-reveal-content.js' => '60821bc7', 'rsrc/js/core/behavior-scrollbar.js' => '834a1173', - 'rsrc/js/core/behavior-search-typeahead.js' => 'd0a99ab4', + 'rsrc/js/core/behavior-search-typeahead.js' => 'c3e917d9', 'rsrc/js/core/behavior-select-content.js' => 'bf5374ef', 'rsrc/js/core/behavior-select-on-click.js' => '4e3e79a6', 'rsrc/js/core/behavior-setup-check-https.js' => '491416b3', @@ -663,7 +663,7 @@ return array( 'javelin-behavior-phabricator-oncopy' => '2926fff2', 'javelin-behavior-phabricator-remarkup-assist' => 'acd29eee', 'javelin-behavior-phabricator-reveal-content' => '60821bc7', - 'javelin-behavior-phabricator-search-typeahead' => 'd0a99ab4', + 'javelin-behavior-phabricator-search-typeahead' => 'c3e917d9', 'javelin-behavior-phabricator-show-older-transactions' => '8f29b364', 'javelin-behavior-phabricator-tooltips' => 'c420b0b9', 'javelin-behavior-phabricator-transaction-comment-form' => 'b23b49e6', @@ -1918,6 +1918,17 @@ return array( 'javelin-dom', 'javelin-vector', ), + 'c3e917d9' => array( + 'javelin-behavior', + 'javelin-typeahead-ondemand-source', + 'javelin-typeahead', + 'javelin-dom', + 'javelin-uri', + 'javelin-util', + 'javelin-stratcom', + 'phabricator-prefab', + 'phuix-icon-view', + ), 'c420b0b9' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -1969,17 +1980,6 @@ return array( 'phabricator-notification', 'conpherence-thread-manager', ), - 'd0a99ab4' => array( - 'javelin-behavior', - 'javelin-typeahead-ondemand-source', - 'javelin-typeahead', - 'javelin-dom', - 'javelin-uri', - 'javelin-util', - 'javelin-stratcom', - 'phabricator-prefab', - 'phuix-icon-view', - ), 'd0c516d5' => array( 'javelin-behavior', 'javelin-dom', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index c2ee4966c1..29a957ae6e 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4855,6 +4855,7 @@ phutil_register_library_map(array( 'PhrictionController' => 'applications/phriction/controller/PhrictionController.php', 'PhrictionCreateConduitAPIMethod' => 'applications/phriction/conduit/PhrictionCreateConduitAPIMethod.php', 'PhrictionDAO' => 'applications/phriction/storage/PhrictionDAO.php', + 'PhrictionDatasourceEngineExtension' => 'applications/phriction/engineextension/PhrictionDatasourceEngineExtension.php', 'PhrictionDeleteController' => 'applications/phriction/controller/PhrictionDeleteController.php', 'PhrictionDiffController' => 'applications/phriction/controller/PhrictionDiffController.php', 'PhrictionDocument' => 'applications/phriction/storage/PhrictionDocument.php', @@ -4862,6 +4863,7 @@ phutil_register_library_map(array( 'PhrictionDocumentContentHeraldField' => 'applications/phriction/herald/PhrictionDocumentContentHeraldField.php', 'PhrictionDocumentContentTransaction' => 'applications/phriction/xaction/PhrictionDocumentContentTransaction.php', 'PhrictionDocumentController' => 'applications/phriction/controller/PhrictionDocumentController.php', + 'PhrictionDocumentDatasource' => 'applications/phriction/typeahead/PhrictionDocumentDatasource.php', 'PhrictionDocumentDeleteTransaction' => 'applications/phriction/xaction/PhrictionDocumentDeleteTransaction.php', 'PhrictionDocumentFerretEngine' => 'applications/phriction/search/PhrictionDocumentFerretEngine.php', 'PhrictionDocumentFulltextEngine' => 'applications/phriction/search/PhrictionDocumentFulltextEngine.php', @@ -10777,6 +10779,7 @@ phutil_register_library_map(array( 'PhrictionController' => 'PhabricatorController', 'PhrictionCreateConduitAPIMethod' => 'PhrictionConduitAPIMethod', 'PhrictionDAO' => 'PhabricatorLiskDAO', + 'PhrictionDatasourceEngineExtension' => 'PhabricatorDatasourceEngineExtension', 'PhrictionDeleteController' => 'PhrictionController', 'PhrictionDiffController' => 'PhrictionController', 'PhrictionDocument' => array( @@ -10796,6 +10799,7 @@ phutil_register_library_map(array( 'PhrictionDocumentContentHeraldField' => 'PhrictionDocumentHeraldField', 'PhrictionDocumentContentTransaction' => 'PhrictionDocumentTransactionType', 'PhrictionDocumentController' => 'PhrictionController', + 'PhrictionDocumentDatasource' => 'PhabricatorTypeaheadDatasource', 'PhrictionDocumentDeleteTransaction' => 'PhrictionDocumentTransactionType', 'PhrictionDocumentFerretEngine' => 'PhabricatorFerretEngine', 'PhrictionDocumentFulltextEngine' => 'PhabricatorFulltextEngine', diff --git a/src/applications/phriction/engineextension/PhrictionDatasourceEngineExtension.php b/src/applications/phriction/engineextension/PhrictionDatasourceEngineExtension.php new file mode 100644 index 0000000000..af262d541d --- /dev/null +++ b/src/applications/phriction/engineextension/PhrictionDatasourceEngineExtension.php @@ -0,0 +1,56 @@ +getViewer(); + + // Send "w" to Phriction. + if (preg_match('/^w\z/i', $query)) { + return '/w/'; + } + + // Send "w " to a search for similar wiki documents. + $matches = null; + if (preg_match('/^w\s+(.+)\z/i', $query, $matches)) { + $raw_query = $matches[1]; + + $engine = id(new PhrictionDocument()) + ->newFerretEngine(); + + $compiler = id(new PhutilSearchQueryCompiler()) + ->setEnableFunctions(true); + + $raw_tokens = $compiler->newTokens($raw_query); + + $fulltext_tokens = array(); + foreach ($raw_tokens as $raw_token) { + $fulltext_token = id(new PhabricatorFulltextToken()) + ->setToken($raw_token); + $fulltext_tokens[] = $fulltext_token; + } + + $documents = id(new PhrictionDocumentQuery()) + ->setViewer($viewer) + ->withFerretConstraint($engine, $fulltext_tokens) + ->execute(); + if (count($documents) == 1) { + return head($documents)->getURI(); + } else { + // More than one match, jump to search. + return urisprintf( + '/phriction/?order=relevance&query=%s#R', + $raw_query); + } + } + + return null; + } +} diff --git a/src/applications/phriction/storage/PhrictionDocument.php b/src/applications/phriction/storage/PhrictionDocument.php index 58d7cbee4a..729d3e4ca5 100644 --- a/src/applications/phriction/storage/PhrictionDocument.php +++ b/src/applications/phriction/storage/PhrictionDocument.php @@ -149,6 +149,10 @@ final class PhrictionDocument extends PhrictionDAO return $this; } + public function getURI() { + return self::getSlugURI($this->getSlug()); + } + /* -( Status )------------------------------------------------------------- */ diff --git a/src/applications/phriction/typeahead/PhrictionDocumentDatasource.php b/src/applications/phriction/typeahead/PhrictionDocumentDatasource.php new file mode 100644 index 0000000000..dc6af6de7a --- /dev/null +++ b/src/applications/phriction/typeahead/PhrictionDocumentDatasource.php @@ -0,0 +1,88 @@ +getViewer(); + + $raw_query = $this->getRawQuery(); + + $engine = id(new PhrictionDocument()) + ->newFerretEngine(); + + $compiler = id(new PhutilSearchQueryCompiler()) + ->setEnableFunctions(true); + + $raw_tokens = $compiler->newTokens($raw_query); + + $fulltext_tokens = array(); + foreach ($raw_tokens as $raw_token) { + + // This is a little hacky and could maybe be cleaner. We're treating + // every search term as though the user had entered "title:dog" insead + // of "dog". + + $alternate_token = PhutilSearchQueryToken::newFromDictionary( + array( + 'quoted' => $raw_token->isQuoted(), + 'value' => $raw_token->getValue(), + 'operator' => PhutilSearchQueryCompiler::OPERATOR_SUBSTRING, + 'function' => 'title', + )); + + $fulltext_token = id(new PhabricatorFulltextToken()) + ->setToken($alternate_token); + $fulltext_tokens[] = $fulltext_token; + } + + $documents = id(new PhrictionDocumentQuery()) + ->setViewer($viewer) + ->withFerretConstraint($engine, $fulltext_tokens) + ->needContent(true) + ->execute(); + + $results = array(); + foreach ($documents as $document) { + $content = $document->getContent(); + + if (!$document->isActive()) { + $closed = $document->getStatusDisplayName(); + } else { + $closed = null; + } + + $slug = $document->getSlug(); + $title = $content->getTitle(); + + $sprite = 'phabricator-search-icon phui-font-fa phui-icon-view fa-book'; + + $result = id(new PhabricatorTypeaheadResult()) + ->setName($title) + ->setDisplayName($title) + ->setURI($document->getURI()) + ->setPHID($document->getPHID()) + ->setDisplayType($slug) + ->setPriorityType('wiki') + ->setImageSprite($sprite) + ->setClosed($closed); + + $results[] = $result; + } + + return $results; + } + +} diff --git a/webroot/rsrc/js/core/behavior-search-typeahead.js b/webroot/rsrc/js/core/behavior-search-typeahead.js index ddcc2e6aaa..0f5b4d1639 100644 --- a/webroot/rsrc/js/core/behavior-search-typeahead.js +++ b/webroot/rsrc/js/core/behavior-search-typeahead.js @@ -70,6 +70,7 @@ JX.behavior('phabricator-search-typeahead', function(config) { 'proj', 'user', 'repo', + 'wiki', 'symb', 'misc' ]; From 8771b7d5c40cc9bbaf417919c778835d13113878 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 16 Feb 2018 09:19:32 -0800 Subject: [PATCH 52/58] Add autocomplete for Phriction documents on "[[ ..." in Remarkup Summary: Depends on D19107. Ref T13077. The underlying datasource may need some adjustment but this appears to work properly locally. Test Plan: Typed `[[ por` locally, was suggested "Porcupine Facts". Typed `[[ / ]]`, saw it render as a reference to the wiki root instead of the install root. Maniphest Tasks: T13077 Differential Revision: https://secure.phabricator.com/D19108 --- resources/celerity/map.php | 16 ++++++------- .../typeahead/PhrictionDocumentDatasource.php | 2 ++ .../control/PhabricatorRemarkupControl.php | 14 +++++++++++ webroot/rsrc/js/phuix/PHUIXAutocomplete.js | 24 ++++++++++++++++--- 4 files changed, 45 insertions(+), 11 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index fc3961604b..1bb3bfbeae 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -528,7 +528,7 @@ return array( 'rsrc/js/phui/behavior-phui-tab-group.js' => '0a0b10e9', 'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8', 'rsrc/js/phuix/PHUIXActionView.js' => '442efd08', - 'rsrc/js/phuix/PHUIXAutocomplete.js' => 'e0731603', + 'rsrc/js/phuix/PHUIXAutocomplete.js' => 'bb19ed2c', 'rsrc/js/phuix/PHUIXButtonView.js' => '8a91e1ac', 'rsrc/js/phuix/PHUIXDropdownMenu.js' => '04b2ae03', 'rsrc/js/phuix/PHUIXExample.js' => '68af71ca', @@ -881,7 +881,7 @@ return array( 'phui-workpanel-view-css' => 'a3a63478', 'phuix-action-list-view' => 'b5c256b8', 'phuix-action-view' => '442efd08', - 'phuix-autocomplete' => 'e0731603', + 'phuix-autocomplete' => 'bb19ed2c', 'phuix-button-view' => '8a91e1ac', 'phuix-dropdown-menu' => '04b2ae03', 'phuix-form-control-view' => '16ad6224', @@ -1868,6 +1868,12 @@ return array( 'javelin-uri', 'phabricator-notification', ), + 'bb19ed2c' => array( + 'javelin-install', + 'javelin-dom', + 'phuix-icon-view', + 'phabricator-prefab', + ), 'bcaccd64' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -2033,12 +2039,6 @@ return array( 'javelin-typeahead-ondemand-source', 'javelin-dom', ), - 'e0731603' => array( - 'javelin-install', - 'javelin-dom', - 'phuix-icon-view', - 'phabricator-prefab', - ), 'e1d25dfb' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/src/applications/phriction/typeahead/PhrictionDocumentDatasource.php b/src/applications/phriction/typeahead/PhrictionDocumentDatasource.php index dc6af6de7a..bb9608edb8 100644 --- a/src/applications/phriction/typeahead/PhrictionDocumentDatasource.php +++ b/src/applications/phriction/typeahead/PhrictionDocumentDatasource.php @@ -68,6 +68,7 @@ final class PhrictionDocumentDatasource $title = $content->getTitle(); $sprite = 'phabricator-search-icon phui-font-fa phui-icon-view fa-book'; + $autocomplete = '[[ '.$slug.' ]]'; $result = id(new PhabricatorTypeaheadResult()) ->setName($title) @@ -77,6 +78,7 @@ final class PhrictionDocumentDatasource ->setDisplayType($slug) ->setPriorityType('wiki') ->setImageSprite($sprite) + ->setAutocomplete($autocomplete) ->setClosed($closed); $results[] = $result; diff --git a/src/view/form/control/PhabricatorRemarkupControl.php b/src/view/form/control/PhabricatorRemarkupControl.php index 5d0cc079e7..219702b2c7 100644 --- a/src/view/form/control/PhabricatorRemarkupControl.php +++ b/src/view/form/control/PhabricatorRemarkupControl.php @@ -72,6 +72,8 @@ final class PhabricatorRemarkupControl extends AphrontFormTextAreaControl { 'autocomplete' => 1, )); + $phriction_datasource = new PhrictionDocumentDatasource(); + Javelin::initBehavior( 'phabricator-remarkup-assist', array( @@ -118,6 +120,18 @@ final class PhabricatorRemarkupControl extends AphrontFormTextAreaControl { '/', ), ), + 91 => array( // "[" + 'datasourceURI' => $phriction_datasource->getDatasourceURI(), + 'headerIcon' => 'fa-book', + 'headerText' => pht('Find Document:'), + 'hintText' => $phriction_datasource->getPlaceholderText(), + 'cancel' => array( + ':', // Cancel on "http:" and similar. + '|', + ']', + ), + 'prefix' => '^\\[*', + ), ), )); Javelin::initBehavior('phabricator-tooltips', array()); diff --git a/webroot/rsrc/js/phuix/PHUIXAutocomplete.js b/webroot/rsrc/js/phuix/PHUIXAutocomplete.js index a7f719dab4..e5928b0297 100644 --- a/webroot/rsrc/js/phuix/PHUIXAutocomplete.js +++ b/webroot/rsrc/js/phuix/PHUIXAutocomplete.js @@ -335,7 +335,9 @@ JX.install('PHUIXAutocomplete', { _getCancelCharacters: function() { // The "." character does not cancel because of projects named // "node.js" or "blog.mycompany.com". - return ['#', '@', ',', '!', '?', '{', '}']; + var defaults = ['#', '@', ',', '!', '?', '{', '}']; + + return this._map[this._active].cancel || defaults; }, _getTerminators: function() { @@ -498,8 +500,6 @@ JX.install('PHUIXAutocomplete', { this._cursorHead, this._cursorTail); - this._value = text; - var pixels = JX.TextAreaUtils.getPixelDimensions( area, range.start, @@ -517,6 +517,24 @@ JX.install('PHUIXAutocomplete', { var trim = this._trim(text); + // If this rule has a prefix pattern, like the "[[ document ]]" rule, + // require it match and throw it away before we begin suggesting + // results. The autocomplete remains active, it's just dormant until + // the user gives us more to work with. + var prefix = this._map[this._active].prefix; + if (prefix) { + var pattern = new RegExp(prefix); + if (!trim.match(pattern)) { + return; + } + trim = trim.replace(pattern, ''); + trim = trim.trim(); + } + + // Store the current value now that we've finished mutating the text. + // This needs to match what we pass to the typeahead datasource. + this._value = trim; + // Deactivate immediately if a user types a character that we are // reasonably sure means they don't want to use the autocomplete. For // example, "##" is almost certainly a header or monospaced text, not From 0202c36b62d7a8179893946be86f1d6da8a7ce1f Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 16 Feb 2018 09:37:13 -0800 Subject: [PATCH 53/58] Suggest Phurl URLs on "((..." in Remarkup text areas Summary: Depends on D19108. Ref T12241. Ref T13077. See D19108. This extends the `[[ ...` autocompleter to `((...` for Phurl URLs. Test Plan: Typed `((th`, got `((thing))` suggested. Reviewers: avivey Reviewed By: avivey Maniphest Tasks: T13077, T12241 Differential Revision: https://secure.phabricator.com/D19109 --- resources/celerity/map.php | 16 ++++++++-------- .../typeahead/PhabricatorPhurlURLDatasource.php | 1 + .../form/control/PhabricatorRemarkupControl.php | 13 ++++++++++++- webroot/rsrc/js/phuix/PHUIXAutocomplete.js | 6 ++++++ 4 files changed, 27 insertions(+), 9 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 1bb3bfbeae..527cd6a39e 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -528,7 +528,7 @@ return array( 'rsrc/js/phui/behavior-phui-tab-group.js' => '0a0b10e9', 'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8', 'rsrc/js/phuix/PHUIXActionView.js' => '442efd08', - 'rsrc/js/phuix/PHUIXAutocomplete.js' => 'bb19ed2c', + 'rsrc/js/phuix/PHUIXAutocomplete.js' => '623a766b', 'rsrc/js/phuix/PHUIXButtonView.js' => '8a91e1ac', 'rsrc/js/phuix/PHUIXDropdownMenu.js' => '04b2ae03', 'rsrc/js/phuix/PHUIXExample.js' => '68af71ca', @@ -881,7 +881,7 @@ return array( 'phui-workpanel-view-css' => 'a3a63478', 'phuix-action-list-view' => 'b5c256b8', 'phuix-action-view' => '442efd08', - 'phuix-autocomplete' => 'bb19ed2c', + 'phuix-autocomplete' => '623a766b', 'phuix-button-view' => '8a91e1ac', 'phuix-dropdown-menu' => '04b2ae03', 'phuix-form-control-view' => '16ad6224', @@ -1407,6 +1407,12 @@ return array( 'javelin-magical-init', 'javelin-util', ), + '623a766b' => array( + 'javelin-install', + 'javelin-dom', + 'phuix-icon-view', + 'phabricator-prefab', + ), '628f59de' => array( 'phui-oi-list-view-css', ), @@ -1868,12 +1874,6 @@ return array( 'javelin-uri', 'phabricator-notification', ), - 'bb19ed2c' => array( - 'javelin-install', - 'javelin-dom', - 'phuix-icon-view', - 'phabricator-prefab', - ), 'bcaccd64' => array( 'javelin-behavior', 'javelin-behavior-device', diff --git a/src/applications/phurl/typeahead/PhabricatorPhurlURLDatasource.php b/src/applications/phurl/typeahead/PhabricatorPhurlURLDatasource.php index cd01c265c8..f8d3cda0cd 100644 --- a/src/applications/phurl/typeahead/PhabricatorPhurlURLDatasource.php +++ b/src/applications/phurl/typeahead/PhabricatorPhurlURLDatasource.php @@ -24,6 +24,7 @@ final class PhabricatorPhurlURLDatasource ->setDisplayName($url->getName()) ->setName($url->getName()." ".$url->getAlias()) ->setPHID($url->getPHID()) + ->setAutocomplete('(('.$url->getAlias().'))') ->addAttribute($url->getLongURL()); $results[] = $result; diff --git a/src/view/form/control/PhabricatorRemarkupControl.php b/src/view/form/control/PhabricatorRemarkupControl.php index 219702b2c7..dba0ef0546 100644 --- a/src/view/form/control/PhabricatorRemarkupControl.php +++ b/src/view/form/control/PhabricatorRemarkupControl.php @@ -73,6 +73,7 @@ final class PhabricatorRemarkupControl extends AphrontFormTextAreaControl { )); $phriction_datasource = new PhrictionDocumentDatasource(); + $phurl_datasource = new PhabricatorPhurlURLDatasource(); Javelin::initBehavior( 'phabricator-remarkup-assist', @@ -130,7 +131,17 @@ final class PhabricatorRemarkupControl extends AphrontFormTextAreaControl { '|', ']', ), - 'prefix' => '^\\[*', + 'prefix' => '^\\[', + ), + 40 => array( // "(" + 'datasourceURI' => $phurl_datasource->getDatasourceURI(), + 'headerIcon' => 'fa-compress', + 'headerText' => pht('Find Phurl:'), + 'hintText' => $phurl_datasource->getPlaceholderText(), + 'cancel' => array( + ')', + ), + 'prefix' => '^\\(', ), ), )); diff --git a/webroot/rsrc/js/phuix/PHUIXAutocomplete.js b/webroot/rsrc/js/phuix/PHUIXAutocomplete.js index e5928b0297..b534af83f0 100644 --- a/webroot/rsrc/js/phuix/PHUIXAutocomplete.js +++ b/webroot/rsrc/js/phuix/PHUIXAutocomplete.js @@ -107,6 +107,12 @@ JX.install('PHUIXAutocomplete', { prior = ''; } + // If this is a repeating sequence and the previous character is the + // same as the one the user just typed, like "((", don't reactivate. + if (prior === String.fromCharCode(code)) { + return; + } + switch (prior) { case '': case ' ': From 8796a6036ed1f2ee13415339e4567acf7ef3f7b1 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 16 Feb 2018 11:00:03 -0800 Subject: [PATCH 54/58] Let users escape more easily from the autosuggester after typing "[" or "(" Summary: Ref T13077. The autosuggester is a little too eager right now, and will eat carriage returns after typing `[` if you never activate the tokenizer. To fix this, try just canceling sooner. If that doesn't work, we might need to cancel more eagerly by testing to see if the tokenizer is actually open. Test Plan: Typed `[x]`, got my return instead of getting trapped by the autosuggester. Maniphest Tasks: T13077 Differential Revision: https://secure.phabricator.com/D19110 --- resources/celerity/map.php | 16 +++++++-------- webroot/rsrc/js/phuix/PHUIXAutocomplete.js | 24 +++++++++++----------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 527cd6a39e..32ccb9e5f9 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -528,7 +528,7 @@ return array( 'rsrc/js/phui/behavior-phui-tab-group.js' => '0a0b10e9', 'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8', 'rsrc/js/phuix/PHUIXActionView.js' => '442efd08', - 'rsrc/js/phuix/PHUIXAutocomplete.js' => '623a766b', + 'rsrc/js/phuix/PHUIXAutocomplete.js' => '7fa5c915', 'rsrc/js/phuix/PHUIXButtonView.js' => '8a91e1ac', 'rsrc/js/phuix/PHUIXDropdownMenu.js' => '04b2ae03', 'rsrc/js/phuix/PHUIXExample.js' => '68af71ca', @@ -881,7 +881,7 @@ return array( 'phui-workpanel-view-css' => 'a3a63478', 'phuix-action-list-view' => 'b5c256b8', 'phuix-action-view' => '442efd08', - 'phuix-autocomplete' => '623a766b', + 'phuix-autocomplete' => '7fa5c915', 'phuix-button-view' => '8a91e1ac', 'phuix-dropdown-menu' => '04b2ae03', 'phuix-form-control-view' => '16ad6224', @@ -1407,12 +1407,6 @@ return array( 'javelin-magical-init', 'javelin-util', ), - '623a766b' => array( - 'javelin-install', - 'javelin-dom', - 'phuix-icon-view', - 'phabricator-prefab', - ), '628f59de' => array( 'phui-oi-list-view-css', ), @@ -1563,6 +1557,12 @@ return array( '7f243deb' => array( 'javelin-install', ), + '7fa5c915' => array( + 'javelin-install', + 'javelin-dom', + 'phuix-icon-view', + 'phabricator-prefab', + ), '81144dfa' => array( 'javelin-behavior', 'javelin-behavior-device', diff --git a/webroot/rsrc/js/phuix/PHUIXAutocomplete.js b/webroot/rsrc/js/phuix/PHUIXAutocomplete.js index b534af83f0..8f062d900e 100644 --- a/webroot/rsrc/js/phuix/PHUIXAutocomplete.js +++ b/webroot/rsrc/js/phuix/PHUIXAutocomplete.js @@ -521,6 +521,18 @@ JX.install('PHUIXAutocomplete', { return; } + // Deactivate immediately if a user types a character that we are + // reasonably sure means they don't want to use the autocomplete. For + // example, "##" is almost certainly a header or monospaced text, not + // a project autocompletion. + var cancels = this._getCancelCharacters(); + for (var ii = 0; ii < cancels.length; ii++) { + if (text.indexOf(cancels[ii]) !== -1) { + this._deactivate(); + return; + } + } + var trim = this._trim(text); // If this rule has a prefix pattern, like the "[[ document ]]" rule, @@ -541,18 +553,6 @@ JX.install('PHUIXAutocomplete', { // This needs to match what we pass to the typeahead datasource. this._value = trim; - // Deactivate immediately if a user types a character that we are - // reasonably sure means they don't want to use the autocomplete. For - // example, "##" is almost certainly a header or monospaced text, not - // a project autocompletion. - var cancels = this._getCancelCharacters(); - for (var ii = 0; ii < cancels.length; ii++) { - if (trim.indexOf(cancels[ii]) !== -1) { - this._deactivate(); - return; - } - } - // Deactivate immediately if the user types an ignored token like ":)", // the smiley face emoticon. Note that we test against "text", not // "trim", because the ignore list and suffix list can otherwise From 6d3177a3bf295fd705a5332d057bd553581746ef Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 16 Feb 2018 11:36:03 -0800 Subject: [PATCH 55/58] Allow "phriction.document.search" to query by path Summary: Ref T13077. Adds a "paths" constraint to the API query. Test Plan: Used paths constraint to fetch documents. Maniphest Tasks: T13077 Differential Revision: https://secure.phabricator.com/D19112 --- .../phriction/query/PhrictionDocumentSearchEngine.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/applications/phriction/query/PhrictionDocumentSearchEngine.php b/src/applications/phriction/query/PhrictionDocumentSearchEngine.php index e578b48346..e0781ec81f 100644 --- a/src/applications/phriction/query/PhrictionDocumentSearchEngine.php +++ b/src/applications/phriction/query/PhrictionDocumentSearchEngine.php @@ -23,6 +23,10 @@ final class PhrictionDocumentSearchEngine $query->withStatuses($map['statuses']); } + if ($map['paths']) { + $query->withSlugs($map['paths']); + } + return $query; } @@ -32,6 +36,10 @@ final class PhrictionDocumentSearchEngine ->setKey('statuses') ->setLabel(pht('Status')) ->setOptions(PhrictionDocumentStatus::getStatusMap()), + id(new PhabricatorSearchStringListField()) + ->setKey('paths') + ->setIsHidden(true) + ->setLabel(pht('Paths')), ); } From db3ef4021a5d4110d5bc5838a1a9b1b04ff133d2 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 16 Feb 2018 11:42:59 -0800 Subject: [PATCH 56/58] Freeze the "phriction.info" Conduit API method Summary: Ref T13077. Freeze "phriction.info" in favor of the more modern "phriction.document.search". Test Plan: Reviewed older method in web UI, saw frozen markers. Maniphest Tasks: T13077 Differential Revision: https://secure.phabricator.com/D19113 --- .../conduit/PhrictionInfoConduitAPIMethod.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/applications/phriction/conduit/PhrictionInfoConduitAPIMethod.php b/src/applications/phriction/conduit/PhrictionInfoConduitAPIMethod.php index 360b3e7b93..d6da3d6aee 100644 --- a/src/applications/phriction/conduit/PhrictionInfoConduitAPIMethod.php +++ b/src/applications/phriction/conduit/PhrictionInfoConduitAPIMethod.php @@ -10,6 +10,16 @@ final class PhrictionInfoConduitAPIMethod extends PhrictionConduitAPIMethod { return pht('Retrieve information about a Phriction document.'); } + public function getMethodStatus() { + return self::METHOD_STATUS_FROZEN; + } + + public function getMethodStatusDescription() { + return pht( + 'This method is frozen and will eventually be deprecated. New code '. + 'should use "phriction.document.search" instead.'); + } + protected function defineParamTypes() { return array( 'slug' => 'required string', From 66a7ca49b20cb55008a45fec904f4ee90333889f Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 16 Feb 2018 12:30:33 -0800 Subject: [PATCH 57/58] Fix incorrect context extraction for relative Phriction links on Phriction pages Summary: Ref T13077. This content extraction rule wasn't right and caused rendering on Phriction pages to extract context improperly. Test Plan: Viewed pages in Phriction with relative links to other documents. Maniphest Tasks: T13077 Differential Revision: https://secure.phabricator.com/D19114 --- src/applications/phriction/markup/PhrictionRemarkupRule.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/phriction/markup/PhrictionRemarkupRule.php b/src/applications/phriction/markup/PhrictionRemarkupRule.php index a6362d1624..d17de331e5 100644 --- a/src/applications/phriction/markup/PhrictionRemarkupRule.php +++ b/src/applications/phriction/markup/PhrictionRemarkupRule.php @@ -275,7 +275,7 @@ final class PhrictionRemarkupRule extends PhutilRemarkupRule { } if ($context instanceof PhrictionDocument) { - return $context->getSlug(); + return $context->getContent()->getSlug(); } return null; From cb2f7106069b1d40a7a753671fc7704cd364c32c Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 16 Feb 2018 12:39:54 -0800 Subject: [PATCH 58/58] Provide the document content as a context object when rendering Phriction documents Summary: Ref T13077. The context object wasn't being passed into the engine properly here, affecting relative link rendering in Phriction. Test Plan: Viewed rendered Phriction documents with relative links, got clean renders. Maniphest Tasks: T13077 Differential Revision: https://secure.phabricator.com/D19115 --- src/applications/phriction/storage/PhrictionContent.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/applications/phriction/storage/PhrictionContent.php b/src/applications/phriction/storage/PhrictionContent.php index 80fabcaddc..515fc6b7d5 100644 --- a/src/applications/phriction/storage/PhrictionContent.php +++ b/src/applications/phriction/storage/PhrictionContent.php @@ -54,6 +54,7 @@ final class PhrictionContent public function newRemarkupView(PhabricatorUser $viewer) { return id(new PHUIRemarkupView($viewer, $this->getContent())) + ->setContextObject($this) ->setRemarkupOption(PHUIRemarkupView::OPTION_GENERATE_TOC, true) ->setGenerateTableOfContents(true); }