Make "Highlight As..." sticky across reloads in Diffusion and Differential
Summary: Ref T13455. Add container-level storage for persistent view state, and persist "Highlight As..." inside it. The storage generates a "PhabricatorChangesetViewState" configuration object as an output. When preferences are expressed on a diff and that diff is later attached to a revision, we attempt to copy the preferences. The internal storage tracks per-changeset settings, but currently always uses "last update wins" to apply the settings in the UI. Test Plan: - Viewed revisions, changed highlighting, reloaded. Saw highlighting stick in revision view and standalone view. - Viewed commits, changed highlighting, reloaded. Saw highlighting stick. - Created a diff, changed highlighting, turned it into a revision, saw highlighting persist. Subscribers: jmeador, PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T13455 Differential Revision: https://secure.phabricator.com/D21137
This commit is contained in:
		| @@ -0,0 +1,9 @@ | |||||||
|  | CREATE TABLE {$NAMESPACE}_differential.differential_viewstate ( | ||||||
|  |   id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, | ||||||
|  |   viewerPHID VARBINARY(64) NOT NULL, | ||||||
|  |   objectPHID VARBINARY(64) NOT NULL, | ||||||
|  |   viewState LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, | ||||||
|  |   dateCreated INT UNSIGNED NOT NULL, | ||||||
|  |   dateModified INT UNSIGNED NOT NULL, | ||||||
|  |   UNIQUE KEY `key_viewer` (viewerPHID, objectPHID) | ||||||
|  | ) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; | ||||||
| @@ -713,6 +713,8 @@ phutil_register_library_map(array( | |||||||
|     'DifferentialUnitStatus' => 'applications/differential/constants/DifferentialUnitStatus.php', |     'DifferentialUnitStatus' => 'applications/differential/constants/DifferentialUnitStatus.php', | ||||||
|     'DifferentialUnitTestResult' => 'applications/differential/constants/DifferentialUnitTestResult.php', |     'DifferentialUnitTestResult' => 'applications/differential/constants/DifferentialUnitTestResult.php', | ||||||
|     'DifferentialUpdateRevisionConduitAPIMethod' => 'applications/differential/conduit/DifferentialUpdateRevisionConduitAPIMethod.php', |     'DifferentialUpdateRevisionConduitAPIMethod' => 'applications/differential/conduit/DifferentialUpdateRevisionConduitAPIMethod.php', | ||||||
|  |     'DifferentialViewState' => 'applications/differential/storage/DifferentialViewState.php', | ||||||
|  |     'DifferentialViewStateQuery' => 'applications/differential/query/DifferentialViewStateQuery.php', | ||||||
|     'DiffusionAuditorDatasource' => 'applications/diffusion/typeahead/DiffusionAuditorDatasource.php', |     'DiffusionAuditorDatasource' => 'applications/diffusion/typeahead/DiffusionAuditorDatasource.php', | ||||||
|     'DiffusionAuditorFunctionDatasource' => 'applications/diffusion/typeahead/DiffusionAuditorFunctionDatasource.php', |     'DiffusionAuditorFunctionDatasource' => 'applications/diffusion/typeahead/DiffusionAuditorFunctionDatasource.php', | ||||||
|     'DiffusionAuditorsAddAuditorsHeraldAction' => 'applications/diffusion/herald/DiffusionAuditorsAddAuditorsHeraldAction.php', |     'DiffusionAuditorsAddAuditorsHeraldAction' => 'applications/diffusion/herald/DiffusionAuditorsAddAuditorsHeraldAction.php', | ||||||
| @@ -2735,6 +2737,8 @@ phutil_register_library_map(array( | |||||||
|     'PhabricatorChangePasswordUserLogType' => 'applications/people/userlog/PhabricatorChangePasswordUserLogType.php', |     'PhabricatorChangePasswordUserLogType' => 'applications/people/userlog/PhabricatorChangePasswordUserLogType.php', | ||||||
|     'PhabricatorChangesetCachePurger' => 'applications/cache/purger/PhabricatorChangesetCachePurger.php', |     'PhabricatorChangesetCachePurger' => 'applications/cache/purger/PhabricatorChangesetCachePurger.php', | ||||||
|     'PhabricatorChangesetResponse' => 'infrastructure/diff/PhabricatorChangesetResponse.php', |     'PhabricatorChangesetResponse' => 'infrastructure/diff/PhabricatorChangesetResponse.php', | ||||||
|  |     'PhabricatorChangesetViewState' => 'infrastructure/diff/viewstate/PhabricatorChangesetViewState.php', | ||||||
|  |     'PhabricatorChangesetViewStateEngine' => 'infrastructure/diff/viewstate/PhabricatorChangesetViewStateEngine.php', | ||||||
|     'PhabricatorChartAxis' => 'applications/fact/chart/PhabricatorChartAxis.php', |     'PhabricatorChartAxis' => 'applications/fact/chart/PhabricatorChartAxis.php', | ||||||
|     'PhabricatorChartDataQuery' => 'applications/fact/chart/PhabricatorChartDataQuery.php', |     'PhabricatorChartDataQuery' => 'applications/fact/chart/PhabricatorChartDataQuery.php', | ||||||
|     'PhabricatorChartDataset' => 'applications/fact/chart/PhabricatorChartDataset.php', |     'PhabricatorChartDataset' => 'applications/fact/chart/PhabricatorChartDataset.php', | ||||||
| @@ -6783,6 +6787,11 @@ phutil_register_library_map(array( | |||||||
|     'DifferentialUnitStatus' => 'Phobject', |     'DifferentialUnitStatus' => 'Phobject', | ||||||
|     'DifferentialUnitTestResult' => 'Phobject', |     'DifferentialUnitTestResult' => 'Phobject', | ||||||
|     'DifferentialUpdateRevisionConduitAPIMethod' => 'DifferentialConduitAPIMethod', |     'DifferentialUpdateRevisionConduitAPIMethod' => 'DifferentialConduitAPIMethod', | ||||||
|  |     'DifferentialViewState' => array( | ||||||
|  |       'DifferentialDAO', | ||||||
|  |       'PhabricatorPolicyInterface', | ||||||
|  |     ), | ||||||
|  |     'DifferentialViewStateQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', | ||||||
|     'DiffusionAuditorDatasource' => 'PhabricatorTypeaheadCompositeDatasource', |     'DiffusionAuditorDatasource' => 'PhabricatorTypeaheadCompositeDatasource', | ||||||
|     'DiffusionAuditorFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', |     'DiffusionAuditorFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', | ||||||
|     'DiffusionAuditorsAddAuditorsHeraldAction' => 'DiffusionAuditorsHeraldAction', |     'DiffusionAuditorsAddAuditorsHeraldAction' => 'DiffusionAuditorsHeraldAction', | ||||||
| @@ -9130,6 +9139,8 @@ phutil_register_library_map(array( | |||||||
|     'PhabricatorChangePasswordUserLogType' => 'PhabricatorUserLogType', |     'PhabricatorChangePasswordUserLogType' => 'PhabricatorUserLogType', | ||||||
|     'PhabricatorChangesetCachePurger' => 'PhabricatorCachePurger', |     'PhabricatorChangesetCachePurger' => 'PhabricatorCachePurger', | ||||||
|     'PhabricatorChangesetResponse' => 'AphrontProxyResponse', |     'PhabricatorChangesetResponse' => 'AphrontProxyResponse', | ||||||
|  |     'PhabricatorChangesetViewState' => 'Phobject', | ||||||
|  |     'PhabricatorChangesetViewStateEngine' => 'Phobject', | ||||||
|     'PhabricatorChartAxis' => 'Phobject', |     'PhabricatorChartAxis' => 'Phobject', | ||||||
|     'PhabricatorChartDataQuery' => 'Phobject', |     'PhabricatorChartDataQuery' => 'Phobject', | ||||||
|     'PhabricatorChartDataset' => 'Phobject', |     'PhabricatorChartDataset' => 'Phobject', | ||||||
|   | |||||||
| @@ -89,13 +89,16 @@ final class DifferentialParseRenderTestCase extends PhabricatorTestCase { | |||||||
|     $engine = new PhabricatorMarkupEngine(); |     $engine = new PhabricatorMarkupEngine(); | ||||||
|     $engine->setViewer(new PhabricatorUser()); |     $engine->setViewer(new PhabricatorUser()); | ||||||
|  |  | ||||||
|  |     $viewstate = new PhabricatorChangesetViewState(); | ||||||
|  |  | ||||||
|     $parsers = array(); |     $parsers = array(); | ||||||
|     foreach ($changesets as $changeset) { |     foreach ($changesets as $changeset) { | ||||||
|       $cparser = new DifferentialChangesetParser(); |       $cparser = id(new DifferentialChangesetParser()) | ||||||
|       $cparser->setUser(new PhabricatorUser()); |         ->setViewer(new PhabricatorUser()) | ||||||
|       $cparser->setDisableCache(true); |         ->setDisableCache(true) | ||||||
|       $cparser->setChangeset($changeset); |         ->setChangeset($changeset) | ||||||
|       $cparser->setMarkupEngine($engine); |         ->setMarkupEngine($engine) | ||||||
|  |         ->setViewState($viewstate); | ||||||
|  |  | ||||||
|       if ($type == 'one') { |       if ($type == 'one') { | ||||||
|         $cparser->setRenderer(new DifferentialChangesetOneUpTestRenderer()); |         $cparser->setRenderer(new DifferentialChangesetOneUpTestRenderer()); | ||||||
|   | |||||||
| @@ -165,21 +165,6 @@ final class DifferentialChangesetViewController extends DifferentialController { | |||||||
|     list($range_s, $range_e, $mask) = |     list($range_s, $range_e, $mask) = | ||||||
|       DifferentialChangesetParser::parseRangeSpecification($spec); |       DifferentialChangesetParser::parseRangeSpecification($spec); | ||||||
|  |  | ||||||
|     $parser = id(new DifferentialChangesetParser()) |  | ||||||
|       ->setViewer($viewer) |  | ||||||
|       ->setCoverage($coverage) |  | ||||||
|       ->setChangeset($changeset) |  | ||||||
|       ->setRenderingReference($rendering_reference) |  | ||||||
|       ->setRenderCacheKey($render_cache_key) |  | ||||||
|       ->setRightSideCommentMapping($right_source, $right_new) |  | ||||||
|       ->setLeftSideCommentMapping($left_source, $left_new); |  | ||||||
|  |  | ||||||
|     $parser->readParametersFromRequest($request); |  | ||||||
|  |  | ||||||
|     if ($left && $right) { |  | ||||||
|       $parser->setOriginals($left, $right); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     $diff = $changeset->getDiff(); |     $diff = $changeset->getDiff(); | ||||||
|     $revision_id = $diff->getRevisionID(); |     $revision_id = $diff->getRevisionID(); | ||||||
|  |  | ||||||
| @@ -197,6 +182,35 @@ final class DifferentialChangesetViewController extends DifferentialController { | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     if ($revision) { | ||||||
|  |       $container_phid = $revision->getPHID(); | ||||||
|  |     } else { | ||||||
|  |       $container_phid = $diff->getPHID(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $viewstate_engine = id(new PhabricatorChangesetViewStateEngine()) | ||||||
|  |       ->setViewer($viewer) | ||||||
|  |       ->setObjectPHID($container_phid) | ||||||
|  |       ->setChangeset($changeset); | ||||||
|  |  | ||||||
|  |     $viewstate = $viewstate_engine->newViewStateFromRequest($request); | ||||||
|  |  | ||||||
|  |     $parser = id(new DifferentialChangesetParser()) | ||||||
|  |       ->setViewer($viewer) | ||||||
|  |       ->setViewState($viewstate) | ||||||
|  |       ->setCoverage($coverage) | ||||||
|  |       ->setChangeset($changeset) | ||||||
|  |       ->setRenderingReference($rendering_reference) | ||||||
|  |       ->setRenderCacheKey($render_cache_key) | ||||||
|  |       ->setRightSideCommentMapping($right_source, $right_new) | ||||||
|  |       ->setLeftSideCommentMapping($left_source, $left_new); | ||||||
|  |  | ||||||
|  |     $parser->readParametersFromRequest($request); | ||||||
|  |  | ||||||
|  |     if ($left && $right) { | ||||||
|  |       $parser->setOriginals($left, $right); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     // Load both left-side and right-side inline comments. |     // Load both left-side and right-side inline comments. | ||||||
|     if ($revision) { |     if ($revision) { | ||||||
|       $query = id(new DifferentialInlineCommentQuery()) |       $query = id(new DifferentialInlineCommentQuery()) | ||||||
| @@ -249,7 +263,7 @@ final class DifferentialChangesetViewController extends DifferentialController { | |||||||
|     $engine->process(); |     $engine->process(); | ||||||
|  |  | ||||||
|     $parser |     $parser | ||||||
|       ->setUser($viewer) |       ->setViewer($viewer) | ||||||
|       ->setMarkupEngine($engine) |       ->setMarkupEngine($engine) | ||||||
|       ->setShowEditAndReplyLinks(true) |       ->setShowEditAndReplyLinks(true) | ||||||
|       ->setCanMarkDone($can_mark) |       ->setCanMarkDone($can_mark) | ||||||
|   | |||||||
| @@ -45,7 +45,6 @@ final class DifferentialChangesetParser extends Phobject { | |||||||
|   private $disableCache; |   private $disableCache; | ||||||
|   private $renderer; |   private $renderer; | ||||||
|   private $characterEncoding; |   private $characterEncoding; | ||||||
|   private $highlightAs; |  | ||||||
|   private $highlightingDisabled; |   private $highlightingDisabled; | ||||||
|   private $showEditAndReplyLinks = true; |   private $showEditAndReplyLinks = true; | ||||||
|   private $canMarkDone; |   private $canMarkDone; | ||||||
| @@ -61,6 +60,8 @@ final class DifferentialChangesetParser extends Phobject { | |||||||
|   private $viewer; |   private $viewer; | ||||||
|   private $documentEngineKey; |   private $documentEngineKey; | ||||||
|  |  | ||||||
|  |   private $viewState; | ||||||
|  |  | ||||||
|   public function setRange($start, $end) { |   public function setRange($start, $end) { | ||||||
|     $this->rangeStart = $start; |     $this->rangeStart = $start; | ||||||
|     $this->rangeEnd = $end; |     $this->rangeEnd = $end; | ||||||
| @@ -85,13 +86,13 @@ final class DifferentialChangesetParser extends Phobject { | |||||||
|     return $this->showEditAndReplyLinks; |     return $this->showEditAndReplyLinks; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public function setHighlightAs($highlight_as) { |   public function setViewState(PhabricatorChangesetViewState $view_state) { | ||||||
|     $this->highlightAs = $highlight_as; |     $this->viewState = $view_state; | ||||||
|     return $this; |     return $this; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public function getHighlightAs() { |   public function getViewState() { | ||||||
|     return $this->highlightAs; |     return $this->viewState; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public function setCharacterEncoding($character_encoding) { |   public function setCharacterEncoding($character_encoding) { | ||||||
| @@ -183,7 +184,6 @@ final class DifferentialChangesetParser extends Phobject { | |||||||
|  |  | ||||||
|   public function readParametersFromRequest(AphrontRequest $request) { |   public function readParametersFromRequest(AphrontRequest $request) { | ||||||
|     $this->setCharacterEncoding($request->getStr('encoding')); |     $this->setCharacterEncoding($request->getStr('encoding')); | ||||||
|     $this->setHighlightAs($request->getStr('highlight')); |  | ||||||
|     $this->setDocumentEngineKey($request->getStr('engine')); |     $this->setDocumentEngineKey($request->getStr('engine')); | ||||||
|  |  | ||||||
|     $renderer = null; |     $renderer = null; | ||||||
| @@ -378,15 +378,6 @@ final class DifferentialChangesetParser extends Phobject { | |||||||
|     return $this; |     return $this; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public function setUser(PhabricatorUser $user) { |  | ||||||
|     $this->user = $user; |  | ||||||
|     return $this; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   public function getUser() { |  | ||||||
|     return $this->user; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   public function setCoverage($coverage) { |   public function setCoverage($coverage) { | ||||||
|     $this->coverage = $coverage; |     $this->coverage = $coverage; | ||||||
|     return $this; |     return $this; | ||||||
| @@ -604,7 +595,7 @@ final class DifferentialChangesetParser extends Phobject { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   private function getHighlightFuture($corpus) { |   private function getHighlightFuture($corpus) { | ||||||
|     $language = $this->highlightAs; |     $language = $this->getViewState()->getHighlightLanguage(); | ||||||
|  |  | ||||||
|     if (!$language) { |     if (!$language) { | ||||||
|       $language = $this->highlightEngine->getLanguageFromFilename( |       $language = $this->highlightEngine->getLanguageFromFilename( | ||||||
| @@ -634,6 +625,8 @@ final class DifferentialChangesetParser extends Phobject { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   private function tryCacheStuff() { |   private function tryCacheStuff() { | ||||||
|  |     $viewstate = $this->getViewState(); | ||||||
|  |  | ||||||
|     $skip_cache = false; |     $skip_cache = false; | ||||||
|  |  | ||||||
|     if ($this->disableCache) { |     if ($this->disableCache) { | ||||||
| @@ -644,7 +637,8 @@ final class DifferentialChangesetParser extends Phobject { | |||||||
|       $skip_cache = true; |       $skip_cache = true; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if ($this->highlightAs) { |     $highlight_language = $viewstate->getHighlightLanguage(); | ||||||
|  |     if ($highlight_language !== null) { | ||||||
|       $skip_cache = true; |       $skip_cache = true; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -844,7 +838,7 @@ final class DifferentialChangesetParser extends Phobject { | |||||||
|       count($this->new)); |       count($this->new)); | ||||||
|  |  | ||||||
|     $renderer = $this->getRenderer() |     $renderer = $this->getRenderer() | ||||||
|       ->setUser($this->getUser()) |       ->setUser($this->getViewer()) | ||||||
|       ->setChangeset($this->changeset) |       ->setChangeset($this->changeset) | ||||||
|       ->setRenderPropertyChangeHeader($render_pch) |       ->setRenderPropertyChangeHeader($render_pch) | ||||||
|       ->setIsTopLevel($this->isTopLevel) |       ->setIsTopLevel($this->isTopLevel) | ||||||
|   | |||||||
| @@ -0,0 +1,64 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | final class DifferentialViewStateQuery | ||||||
|  |   extends PhabricatorCursorPagedPolicyAwareQuery { | ||||||
|  |  | ||||||
|  |   private $ids; | ||||||
|  |   private $viewerPHIDs; | ||||||
|  |   private $objectPHIDs; | ||||||
|  |  | ||||||
|  |   public function withIDs(array $ids) { | ||||||
|  |     $this->ids = $ids; | ||||||
|  |     return $this; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function withViewerPHIDs(array $phids) { | ||||||
|  |     $this->viewerPHIDs = $phids; | ||||||
|  |     return $this; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function withObjectPHIDs(array $phids) { | ||||||
|  |     $this->objectPHIDs = $phids; | ||||||
|  |     return $this; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function newResultObject() { | ||||||
|  |     return new DifferentialViewState(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   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->viewerPHIDs !== null) { | ||||||
|  |       $where[] = qsprintf( | ||||||
|  |         $conn, | ||||||
|  |         'viewerPHID IN (%Ls)', | ||||||
|  |         $this->viewerPHIDs); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if ($this->objectPHIDs !== null) { | ||||||
|  |       $where[] = qsprintf( | ||||||
|  |         $conn, | ||||||
|  |         'objectPHID IN (%Ls)', | ||||||
|  |         $this->objectPHIDs); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return $where; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getQueryApplicationClass() { | ||||||
|  |     return 'PhabricatorDifferentialApplication'; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -716,6 +716,8 @@ final class DifferentialDiff | |||||||
|   public function destroyObjectPermanently( |   public function destroyObjectPermanently( | ||||||
|     PhabricatorDestructionEngine $engine) { |     PhabricatorDestructionEngine $engine) { | ||||||
|  |  | ||||||
|  |     $viewer = $engine->getViewer(); | ||||||
|  |  | ||||||
|     $this->openTransaction(); |     $this->openTransaction(); | ||||||
|       $this->delete(); |       $this->delete(); | ||||||
|  |  | ||||||
| @@ -730,6 +732,13 @@ final class DifferentialDiff | |||||||
|         $prop->delete(); |         $prop->delete(); | ||||||
|       } |       } | ||||||
|  |  | ||||||
|  |       $viewstates = id(new DifferentialViewStateQuery()) | ||||||
|  |         ->setViewer($viewer) | ||||||
|  |         ->withObjectPHIDs(array($this->getPHID())); | ||||||
|  |       foreach ($viewstates as $viewstate) { | ||||||
|  |         $viewstate->delete(); | ||||||
|  |       } | ||||||
|  |  | ||||||
|     $this->saveTransaction(); |     $this->saveTransaction(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1002,9 +1002,11 @@ final class DifferentialRevision extends DifferentialDAO | |||||||
|   public function destroyObjectPermanently( |   public function destroyObjectPermanently( | ||||||
|     PhabricatorDestructionEngine $engine) { |     PhabricatorDestructionEngine $engine) { | ||||||
|  |  | ||||||
|  |     $viewer = $engine->getViewer(); | ||||||
|  |  | ||||||
|     $this->openTransaction(); |     $this->openTransaction(); | ||||||
|       $diffs = id(new DifferentialDiffQuery()) |       $diffs = id(new DifferentialDiffQuery()) | ||||||
|         ->setViewer($engine->getViewer()) |         ->setViewer($viewer) | ||||||
|         ->withRevisionIDs(array($this->getID())) |         ->withRevisionIDs(array($this->getID())) | ||||||
|         ->execute(); |         ->execute(); | ||||||
|       foreach ($diffs as $diff) { |       foreach ($diffs as $diff) { | ||||||
| @@ -1022,6 +1024,13 @@ final class DifferentialRevision extends DifferentialDAO | |||||||
|         $dummy_path->getTableName(), |         $dummy_path->getTableName(), | ||||||
|         $this->getID()); |         $this->getID()); | ||||||
|  |  | ||||||
|  |       $viewstates = id(new DifferentialViewStateQuery()) | ||||||
|  |         ->setViewer($viewer) | ||||||
|  |         ->withObjectPHIDs(array($this->getPHID())); | ||||||
|  |       foreach ($viewstates as $viewstate) { | ||||||
|  |         $viewstate->delete(); | ||||||
|  |       } | ||||||
|  |  | ||||||
|       $this->delete(); |       $this->delete(); | ||||||
|     $this->saveTransaction(); |     $this->saveTransaction(); | ||||||
|   } |   } | ||||||
|   | |||||||
							
								
								
									
										132
									
								
								src/applications/differential/storage/DifferentialViewState.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								src/applications/differential/storage/DifferentialViewState.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,132 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | final class DifferentialViewState | ||||||
|  |   extends DifferentialDAO | ||||||
|  |   implements PhabricatorPolicyInterface { | ||||||
|  |  | ||||||
|  |   protected $viewerPHID; | ||||||
|  |   protected $objectPHID; | ||||||
|  |   protected $viewState = array(); | ||||||
|  |  | ||||||
|  |   private $hasModifications; | ||||||
|  |  | ||||||
|  |   protected function getConfiguration() { | ||||||
|  |     return array( | ||||||
|  |       self::CONFIG_SERIALIZATION => array( | ||||||
|  |         'viewState' => self::SERIALIZATION_JSON, | ||||||
|  |       ), | ||||||
|  |       self::CONFIG_KEY_SCHEMA => array( | ||||||
|  |         'key_viewer' => array( | ||||||
|  |           'columns' => array('viewerPHID', 'objectPHID'), | ||||||
|  |           'unique' => true, | ||||||
|  |         ), | ||||||
|  |         'key_object' => array( | ||||||
|  |           'columns' => array('objectPHID'), | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |     ) + parent::getConfiguration(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function setChangesetProperty( | ||||||
|  |     DifferentialChangeset $changeset, | ||||||
|  |     $key, | ||||||
|  |     $value) { | ||||||
|  |  | ||||||
|  |     if ($this->getChangesetProperty($changeset, $key) === $value) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $properties = array( | ||||||
|  |       'value' => $value, | ||||||
|  |       'epoch' => PhabricatorTime::getNow(), | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     $diff_id = $changeset->getDiffID(); | ||||||
|  |     if ($diff_id !== null) { | ||||||
|  |       $properties['diffID'] = (int)$diff_id; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $path_hash = $this->getChangesetPathHash($changeset); | ||||||
|  |     $changeset_phid = $this->getChangesetKey($changeset); | ||||||
|  |  | ||||||
|  |     $this->hasModifications = true; | ||||||
|  |  | ||||||
|  |     $this->viewState['changesets'][$path_hash][$key][$changeset_phid] = | ||||||
|  |       $properties; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getChangesetProperty( | ||||||
|  |     DifferentialChangeset $changeset, | ||||||
|  |     $key, | ||||||
|  |     $default = null) { | ||||||
|  |  | ||||||
|  |     $path_hash = $this->getChangesetPathHash($changeset); | ||||||
|  |  | ||||||
|  |     $entries = idxv($this->viewState, array('changesets', $path_hash, $key)); | ||||||
|  |     if (!is_array($entries)) { | ||||||
|  |       $entries = array(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $entries = isort($entries, 'epoch'); | ||||||
|  |  | ||||||
|  |     $entry = last($entries); | ||||||
|  |     if (!is_array($entry)) { | ||||||
|  |       $entry = array(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return idx($entry, 'value', $default); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getHasModifications() { | ||||||
|  |     return $this->hasModifications; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private function getChangesetPathHash(DifferentialChangeset $changeset) { | ||||||
|  |     $path = $changeset->getFilename(); | ||||||
|  |     return PhabricatorHash::digestForIndex($path); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private function getChangesetKey(DifferentialChangeset $changeset) { | ||||||
|  |     $key = $changeset->getID(); | ||||||
|  |  | ||||||
|  |     if ($key === null) { | ||||||
|  |       return '*'; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return (string)$key; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public static function copyViewStatesToObject($src_phid, $dst_phid) { | ||||||
|  |     $table = new self(); | ||||||
|  |     $conn = $table->establishConnection('w'); | ||||||
|  |  | ||||||
|  |     queryfx( | ||||||
|  |       $conn, | ||||||
|  |       'INSERT IGNORE INTO %R | ||||||
|  |           (viewerPHID, objectPHID, viewState, dateCreated, dateModified) | ||||||
|  |         SELECT viewerPHID, %s, viewState, dateCreated, dateModified | ||||||
|  |           FROM %R WHERE objectPHID = %s', | ||||||
|  |       $table, | ||||||
|  |       $dst_phid, | ||||||
|  |       $table, | ||||||
|  |       $src_phid); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | /* -(  PhabricatorPolicyInterface  )----------------------------------------- */ | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   public function getCapabilities() { | ||||||
|  |     return array( | ||||||
|  |       PhabricatorPolicyCapability::CAN_VIEW, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getPolicy($capability) { | ||||||
|  |     return PhabricatorPolicies::POLICY_NOONE; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { | ||||||
|  |     return ($viewer->getPHID() === $this->getViewerPHID()); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -100,6 +100,14 @@ final class DifferentialRevisionUpdateTransaction | |||||||
|         HarbormasterMessageType::BUILDABLE_CONTAINER, |         HarbormasterMessageType::BUILDABLE_CONTAINER, | ||||||
|         true); |         true); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     // See T13455. If users have set view properites on a diff and the diff | ||||||
|  |     // is then attached to a revision, attempt to copy their view preferences | ||||||
|  |     // to the revision. | ||||||
|  |  | ||||||
|  |     DifferentialViewState::copyViewStatesToObject( | ||||||
|  |       $diff->getPHID(), | ||||||
|  |       $object->getPHID()); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public function getColor() { |   public function getColor() { | ||||||
|   | |||||||
| @@ -60,9 +60,20 @@ final class DiffusionDiffController extends DiffusionController { | |||||||
|       return new Aphront404Response(); |       return new Aphront404Response(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     $parser = new DifferentialChangesetParser(); |     $commit = $drequest->loadCommit(); | ||||||
|     $parser->setUser($viewer); |  | ||||||
|     $parser->setChangeset($changeset); |     $viewstate_engine = id(new PhabricatorChangesetViewStateEngine()) | ||||||
|  |       ->setViewer($viewer) | ||||||
|  |       ->setObjectPHID($commit->getPHID()) | ||||||
|  |       ->setChangeset($changeset); | ||||||
|  |  | ||||||
|  |     $viewstate = $viewstate_engine->newViewStateFromRequest($request); | ||||||
|  |  | ||||||
|  |     $parser = id(new DifferentialChangesetParser()) | ||||||
|  |       ->setViewer($viewer) | ||||||
|  |       ->setChangeset($changeset) | ||||||
|  |       ->setViewState($viewstate); | ||||||
|  |  | ||||||
|     $parser->setRenderingReference($drequest->generateURI( |     $parser->setRenderingReference($drequest->generateURI( | ||||||
|       array( |       array( | ||||||
|         'action' => 'rendering-ref', |         'action' => 'rendering-ref', | ||||||
| @@ -75,8 +86,6 @@ final class DiffusionDiffController extends DiffusionController { | |||||||
|       $parser->setCoverage($coverage); |       $parser->setCoverage($coverage); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     $commit = $drequest->loadCommit(); |  | ||||||
|  |  | ||||||
|     $pquery = new DiffusionPathIDQuery(array($changeset->getFilename())); |     $pquery = new DiffusionPathIDQuery(array($changeset->getFilename())); | ||||||
|     $ids = $pquery->loadPathIDs(); |     $ids = $pquery->loadPathIDs(); | ||||||
|     $path_id = $ids[$changeset->getFilename()]; |     $path_id = $ids[$changeset->getFilename()]; | ||||||
|   | |||||||
| @@ -0,0 +1,17 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | final class PhabricatorChangesetViewState | ||||||
|  |   extends Phobject { | ||||||
|  |  | ||||||
|  |   private $highlightLanguage; | ||||||
|  |  | ||||||
|  |   public function setHighlightLanguage($highlight_language) { | ||||||
|  |     $this->highlightLanguage = $highlight_language; | ||||||
|  |     return $this; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getHighlightLanguage() { | ||||||
|  |     return $this->highlightLanguage; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,145 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | final class PhabricatorChangesetViewStateEngine | ||||||
|  |   extends Phobject { | ||||||
|  |  | ||||||
|  |   private $viewer; | ||||||
|  |   private $objectPHID; | ||||||
|  |   private $changeset; | ||||||
|  |   private $storage; | ||||||
|  |  | ||||||
|  |   public function setViewer(PhabricatorUser $viewer) { | ||||||
|  |     $this->viewer = $viewer; | ||||||
|  |     return $this; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getViewer() { | ||||||
|  |     return $this->viewer; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function setObjectPHID($object_phid) { | ||||||
|  |     $this->objectPHID = $object_phid; | ||||||
|  |     return $this; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getObjectPHID() { | ||||||
|  |     return $this->objectPHID; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function setChangeset(DifferentialChangeset $changeset) { | ||||||
|  |     $this->changeset = $changeset; | ||||||
|  |     return $this; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getChangeset() { | ||||||
|  |     return $this->changeset; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function newViewStateFromRequest(AphrontRequest $request) { | ||||||
|  |     $storage = $this->loadViewStateStorage(); | ||||||
|  |  | ||||||
|  |     $this->setStorage($storage); | ||||||
|  |  | ||||||
|  |     $highlight = $request->getStr('highlight'); | ||||||
|  |     if ($highlight !== null && strlen($highlight)) { | ||||||
|  |       $this->setChangesetProperty('highlight', $highlight); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $this->saveViewStateStorage(); | ||||||
|  |  | ||||||
|  |     $state = new PhabricatorChangesetViewState(); | ||||||
|  |  | ||||||
|  |     $highlight_language = $this->getChangesetProperty('highlight'); | ||||||
|  |     $state->setHighlightLanguage($highlight_language); | ||||||
|  |  | ||||||
|  |     return $state; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private function setStorage(DifferentialViewState $storage) { | ||||||
|  |     $this->storage = $storage; | ||||||
|  |     return $this; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private function getStorage() { | ||||||
|  |     return $this->storage; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private function setChangesetProperty( | ||||||
|  |     $key, | ||||||
|  |     $value) { | ||||||
|  |  | ||||||
|  |     $storage = $this->getStorage(); | ||||||
|  |     $changeset = $this->getChangeset(); | ||||||
|  |  | ||||||
|  |     $storage->setChangesetProperty($changeset, $key, $value); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private function getChangesetProperty( | ||||||
|  |     $key, | ||||||
|  |     $default = null) { | ||||||
|  |  | ||||||
|  |     $storage = $this->getStorage(); | ||||||
|  |     $changeset = $this->getChangeset(); | ||||||
|  |  | ||||||
|  |     return $storage->getChangesetProperty($changeset, $key, $default); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private function loadViewStateStorage() { | ||||||
|  |     $viewer = $this->getViewer(); | ||||||
|  |  | ||||||
|  |     $object_phid = $this->getObjectPHID(); | ||||||
|  |     $viewer_phid = $viewer->getPHID(); | ||||||
|  |  | ||||||
|  |     $storage = null; | ||||||
|  |  | ||||||
|  |     if ($viewer_phid !== null) { | ||||||
|  |       $storage = id(new DifferentialViewStateQuery()) | ||||||
|  |         ->setViewer($viewer) | ||||||
|  |         ->withViewerPHIDs(array($viewer_phid)) | ||||||
|  |         ->withObjectPHIDs(array($object_phid)) | ||||||
|  |         ->executeOne(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if ($storage === null) { | ||||||
|  |       $storage = id(new DifferentialViewState()) | ||||||
|  |         ->setObjectPHID($object_phid); | ||||||
|  |  | ||||||
|  |       if ($viewer_phid !== null) { | ||||||
|  |         $storage->setViewerPHID($viewer_phid); | ||||||
|  |       } else { | ||||||
|  |         $storage->makeEphemeral(); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return $storage; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private function saveViewStateStorage() { | ||||||
|  |     if (PhabricatorEnv::isReadOnly()) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $storage = $this->getStorage(); | ||||||
|  |  | ||||||
|  |     $viewer_phid = $storage->getViewerPHID(); | ||||||
|  |     if ($viewer_phid === null) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (!$storage->getHasModifications()) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); | ||||||
|  |  | ||||||
|  |     try { | ||||||
|  |       $storage->save(); | ||||||
|  |     } catch (AphrontDuplicateKeyQueryException $ex) { | ||||||
|  |       // We may race another process to save view state. For now, just discard | ||||||
|  |       // our state if we do. | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     unset($unguarded); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user
	 epriestley
					epriestley