diff --git a/resources/celerity/map.php b/resources/celerity/map.php --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ 'names' => array( 'conpherence.pkg.css' => '0b64e988', 'conpherence.pkg.js' => '6249a1cf', - 'core.pkg.css' => '404132bb', + 'core.pkg.css' => '202700e2', 'core.pkg.js' => '28e8cda8', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => 'a4ba74b5', @@ -146,7 +146,7 @@ 'rsrc/css/phui/phui-document.css' => 'c32e8dec', 'rsrc/css/phui/phui-feed-story.css' => '44a9c8e9', 'rsrc/css/phui/phui-fontkit.css' => '9cda225e', - 'rsrc/css/phui/phui-form-view.css' => 'cd79ff6a', + 'rsrc/css/phui/phui-form-view.css' => '04cc4771', 'rsrc/css/phui/phui-form.css' => '2342b0e5', 'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f', 'rsrc/css/phui/phui-header-view.css' => '6ec8f155', @@ -542,7 +542,7 @@ 'rsrc/js/phuix/PHUIXActionView.js' => '8cf6d262', 'rsrc/js/phuix/PHUIXAutocomplete.js' => '6d86ce8b', 'rsrc/js/phuix/PHUIXDropdownMenu.js' => '82e270da', - 'rsrc/js/phuix/PHUIXFormControl.js' => '301b7812', + 'rsrc/js/phuix/PHUIXFormControl.js' => 'bbece68d', 'rsrc/js/phuix/PHUIXIconView.js' => 'bff6884b', ), 'symbols' => array( @@ -860,7 +860,7 @@ 'phui-font-icon-base-css' => '870a7360', 'phui-fontkit-css' => '9cda225e', 'phui-form-css' => '2342b0e5', - 'phui-form-view-css' => 'cd79ff6a', + 'phui-form-view-css' => '04cc4771', 'phui-head-thing-view-css' => 'fd311e5f', 'phui-header-view-css' => '6ec8f155', 'phui-hovercard' => '1bd28176', @@ -901,7 +901,7 @@ 'phuix-action-view' => '8cf6d262', 'phuix-autocomplete' => '6d86ce8b', 'phuix-dropdown-menu' => '82e270da', - 'phuix-form-control-view' => '301b7812', + 'phuix-form-control-view' => 'bbece68d', 'phuix-icon-view' => 'bff6884b', 'policy-css' => '957ea14c', 'policy-edit-css' => '815c66f7', @@ -1159,10 +1159,6 @@ '2ee659ce' => array( 'javelin-install', ), - '301b7812' => array( - 'javelin-install', - 'javelin-dom', - ), '320810c8' => array( 'javelin-install', 'javelin-dom', @@ -1916,6 +1912,10 @@ 'javelin-vector', 'javelin-install', ), + 'bbece68d' => array( + 'javelin-install', + 'javelin-dom', + ), 'bcaccd64' => array( 'javelin-behavior', 'javelin-behavior-device', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -505,6 +505,8 @@ 'DifferentialReviewersHeraldAction' => 'applications/differential/herald/DifferentialReviewersHeraldAction.php', 'DifferentialReviewersView' => 'applications/differential/view/DifferentialReviewersView.php', 'DifferentialRevision' => 'applications/differential/storage/DifferentialRevision.php', + 'DifferentialRevisionAbandonTransaction' => 'applications/differential/xaction/DifferentialRevisionAbandonTransaction.php', + 'DifferentialRevisionActionTransaction' => 'applications/differential/xaction/DifferentialRevisionActionTransaction.php', 'DifferentialRevisionAffectedFilesHeraldField' => 'applications/differential/herald/DifferentialRevisionAffectedFilesHeraldField.php', 'DifferentialRevisionAuthorHeraldField' => 'applications/differential/herald/DifferentialRevisionAuthorHeraldField.php', 'DifferentialRevisionAuthorProjectsHeraldField' => 'applications/differential/herald/DifferentialRevisionAuthorProjectsHeraldField.php', @@ -539,6 +541,7 @@ 'DifferentialRevisionPackageHeraldField' => 'applications/differential/herald/DifferentialRevisionPackageHeraldField.php', 'DifferentialRevisionPackageOwnerHeraldField' => 'applications/differential/herald/DifferentialRevisionPackageOwnerHeraldField.php', 'DifferentialRevisionQuery' => 'applications/differential/query/DifferentialRevisionQuery.php', + 'DifferentialRevisionReclaimTransaction' => 'applications/differential/xaction/DifferentialRevisionReclaimTransaction.php', 'DifferentialRevisionRelationship' => 'applications/differential/relationships/DifferentialRevisionRelationship.php', 'DifferentialRevisionRelationshipSource' => 'applications/search/relationship/DifferentialRevisionRelationshipSource.php', 'DifferentialRevisionRepositoryHeraldField' => 'applications/differential/herald/DifferentialRevisionRepositoryHeraldField.php', @@ -1840,6 +1843,7 @@ 'PhabricatorApplicationsApplication' => 'applications/meta/application/PhabricatorApplicationsApplication.php', 'PhabricatorApplicationsController' => 'applications/meta/controller/PhabricatorApplicationsController.php', 'PhabricatorApplicationsListController' => 'applications/meta/controller/PhabricatorApplicationsListController.php', + 'PhabricatorApplyEditField' => 'applications/transactions/editfield/PhabricatorApplyEditField.php', 'PhabricatorAsanaAuthProvider' => 'applications/auth/provider/PhabricatorAsanaAuthProvider.php', 'PhabricatorAsanaConfigOptions' => 'applications/doorkeeper/option/PhabricatorAsanaConfigOptions.php', 'PhabricatorAsanaSubtaskHasObjectEdgeType' => 'applications/doorkeeper/edge/PhabricatorAsanaSubtaskHasObjectEdgeType.php', @@ -2572,6 +2576,7 @@ 'PhabricatorEditEngineSearchEngine' => 'applications/transactions/query/PhabricatorEditEngineSearchEngine.php', 'PhabricatorEditEngineSelectCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineSelectCommentAction.php', 'PhabricatorEditEngineSettingsPanel' => 'applications/settings/panel/PhabricatorEditEngineSettingsPanel.php', + 'PhabricatorEditEngineStaticCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineStaticCommentAction.php', 'PhabricatorEditEngineTokenizerCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineTokenizerCommentAction.php', 'PhabricatorEditField' => 'applications/transactions/editfield/PhabricatorEditField.php', 'PhabricatorEditPage' => 'applications/transactions/editengine/PhabricatorEditPage.php', @@ -5169,6 +5174,8 @@ 'PhabricatorFulltextInterface', 'PhabricatorConduitResultInterface', ), + 'DifferentialRevisionAbandonTransaction' => 'DifferentialRevisionActionTransaction', + 'DifferentialRevisionActionTransaction' => 'DifferentialRevisionTransactionType', 'DifferentialRevisionAffectedFilesHeraldField' => 'DifferentialRevisionHeraldField', 'DifferentialRevisionAuthorHeraldField' => 'DifferentialRevisionHeraldField', 'DifferentialRevisionAuthorProjectsHeraldField' => 'DifferentialRevisionHeraldField', @@ -5203,6 +5210,7 @@ 'DifferentialRevisionPackageHeraldField' => 'DifferentialRevisionHeraldField', 'DifferentialRevisionPackageOwnerHeraldField' => 'DifferentialRevisionHeraldField', 'DifferentialRevisionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'DifferentialRevisionReclaimTransaction' => 'DifferentialRevisionActionTransaction', 'DifferentialRevisionRelationship' => 'PhabricatorObjectRelationship', 'DifferentialRevisionRelationshipSource' => 'PhabricatorObjectRelationshipSource', 'DifferentialRevisionRepositoryHeraldField' => 'DifferentialRevisionHeraldField', @@ -6688,6 +6696,7 @@ 'PhabricatorApplicationsApplication' => 'PhabricatorApplication', 'PhabricatorApplicationsController' => 'PhabricatorController', 'PhabricatorApplicationsListController' => 'PhabricatorApplicationsController', + 'PhabricatorApplyEditField' => 'PhabricatorEditField', 'PhabricatorAsanaAuthProvider' => 'PhabricatorOAuth2AuthProvider', 'PhabricatorAsanaConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorAsanaSubtaskHasObjectEdgeType' => 'PhabricatorEdgeType', @@ -7545,6 +7554,7 @@ 'PhabricatorEditEngineSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorEditEngineSelectCommentAction' => 'PhabricatorEditEngineCommentAction', 'PhabricatorEditEngineSettingsPanel' => 'PhabricatorSettingsPanel', + 'PhabricatorEditEngineStaticCommentAction' => 'PhabricatorEditEngineCommentAction', 'PhabricatorEditEngineTokenizerCommentAction' => 'PhabricatorEditEngineCommentAction', 'PhabricatorEditField' => 'Phobject', 'PhabricatorEditPage' => 'Phobject', diff --git a/src/applications/differential/editor/DifferentialRevisionEditEngine.php b/src/applications/differential/editor/DifferentialRevisionEditEngine.php --- a/src/applications/differential/editor/DifferentialRevisionEditEngine.php +++ b/src/applications/differential/editor/DifferentialRevisionEditEngine.php @@ -87,6 +87,7 @@ } protected function buildCustomEditFields($object) { + $viewer = $this->getViewer(); $plan_required = PhabricatorEnv::getEnvConfig( 'differential.require-test-plan-field'); @@ -180,7 +181,7 @@ ->setUseEdgeTransactions(true) ->setTransactionType( DifferentialRevisionReviewersTransaction::TRANSACTIONTYPE) - ->setCommentActionLabel(pht('Edit Reviewers')) + ->setCommentActionLabel(pht('Change Reviewers')) ->setDescription(pht('Reviewers for this revision.')) ->setConduitDescription(pht('Change the reviewers for this revision.')) ->setConduitTypeDescription(pht('New reviewers.')) @@ -212,6 +213,11 @@ ->setConduitTypeDescription(pht('List of tasks.')) ->setValue(array()); + $actions = DifferentialRevisionActionTransaction::loadAllActions(); + foreach ($actions as $key => $action) { + $fields[] = $action->newEditField($object, $viewer); + } + return $fields; } diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -442,6 +442,11 @@ return DifferentialRevisionStatus::isClosedStatus($this->getStatus()); } + public function isAbandoned() { + $status_abandoned = ArcanistDifferentialRevisionStatus::ABANDONED; + return ($this->getStatus() == $status_abandoned); + } + public function getStatusIcon() { $map = array( ArcanistDifferentialRevisionStatus::NEEDS_REVIEW diff --git a/src/applications/differential/xaction/DifferentialRevisionAbandonTransaction.php b/src/applications/differential/xaction/DifferentialRevisionAbandonTransaction.php new file mode 100644 --- /dev/null +++ b/src/applications/differential/xaction/DifferentialRevisionAbandonTransaction.php @@ -0,0 +1,67 @@ +isAbandoned(); + } + + public function applyInternalEffects($object, $value) { + $object->setStatus(ArcanistDifferentialRevisionStatus::ABANDONED); + } + + protected function validateAction($object, PhabricatorUser $viewer) { + if ($object->isClosed()) { + throw new Exception( + pht( + 'You can not abandon this revision because it has already been '. + 'closed. Only open revisions can be abandoned.')); + } + + $config_key = 'differential.always-allow-abandon'; + if (!PhabricatorEnv::getEnvConfig($config_key)) { + if (!$this->isViewerRevisionAuthor($object, $viewer)) { + throw new Exception( + pht( + 'You can not abandon this revision because you are not the '. + 'author. You can only abandon revisions you own. You can change '. + 'this behavior by adjusting the "%s" setting in Config', + $config_key)); + } + } + } + + public function getTitle() { + return pht( + '%s abandoned this revision.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s abandoned %s.', + $this->renderAuthor(), + $this->renderObject()); + } + +} diff --git a/src/applications/differential/xaction/DifferentialRevisionActionTransaction.php b/src/applications/differential/xaction/DifferentialRevisionActionTransaction.php new file mode 100644 --- /dev/null +++ b/src/applications/differential/xaction/DifferentialRevisionActionTransaction.php @@ -0,0 +1,88 @@ +getPhobjectClassConstant('ACTIONKEY', 32); + } + + public function isActionAvailable($object, PhabricatorUser $viewer) { + try { + $this->validateAction($object, $viewer); + return true; + } catch (Exception $ex) { + return false; + } + } + + abstract protected function validateAction($object, PhabricatorUser $viewer); + abstract protected function getRevisionActionLabel(); + + protected function getRevisionActionDescription() { + return null; + } + + public static function loadAllActions() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getRevisionActionKey') + ->execute(); + } + + protected function isViewerRevisionAuthor( + DifferentialRevision $revision, + PhabricatorUser $viewer) { + + if (!$viewer->getPHID()) { + return false; + } + + return ($viewer->getPHID() === $revision->getAuthorPHID()); + } + + public function newEditField( + DifferentialRevision $revision, + PhabricatorUser $viewer) { + + $field = id(new PhabricatorApplyEditField()) + ->setKey($this->getRevisionActionKey()) + ->setTransactionType($this->getTransactionTypeConstant()) + ->setValue(true); + + if ($this->isActionAvailable($revision, $viewer)) { + $label = $this->getRevisionActionLabel(); + if ($label !== null) { + $field->setCommentActionLabel($label); + + $description = $this->getRevisionActionDescription(); + $field->setActionDescription($description); + } + } + + return $field; + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + $actor = $this->getActor(); + + $action_exception = null; + try { + $this->validateAction($object, $actor); + } catch (Exception $ex) { + $action_exception = $ex; + } + + foreach ($xactions as $xaction) { + if ($action_exception) { + $errors[] = $this->newInvalidError( + $action_exception->getMessage(), + $xaction); + } + } + + return $errors; + } + +} diff --git a/src/applications/differential/xaction/DifferentialRevisionReclaimTransaction.php b/src/applications/differential/xaction/DifferentialRevisionReclaimTransaction.php new file mode 100644 --- /dev/null +++ b/src/applications/differential/xaction/DifferentialRevisionReclaimTransaction.php @@ -0,0 +1,62 @@ +isAbandoned(); + } + + public function applyInternalEffects($object, $value) { + $object->setStatus(ArcanistDifferentialRevisionStatus::NEEDS_REVIEW); + } + + protected function validateAction($object, PhabricatorUser $viewer) { + if (!$object->isAbandoned()) { + throw new Exception( + pht( + 'You can not reclaim this revision because it has not been '. + 'abandoned. Only abandoned revisions can be reclaimed.')); + } + + if (!$this->isViewerRevisionAuthor($object, $viewer)) { + throw new Exception( + pht( + 'You can not reclaim this revision because you are not the '. + 'revision author. You can only reclaim revisions you own.')); + } + } + + public function getTitle() { + return pht( + '%s reclaimed this revision.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s reclaimed %s.', + $this->renderAuthor(), + $this->renderObject()); + } + +} diff --git a/src/applications/transactions/commentaction/PhabricatorEditEngineStaticCommentAction.php b/src/applications/transactions/commentaction/PhabricatorEditEngineStaticCommentAction.php new file mode 100644 --- /dev/null +++ b/src/applications/transactions/commentaction/PhabricatorEditEngineStaticCommentAction.php @@ -0,0 +1,28 @@ +description = $description; + return $this; + } + + public function getDescription() { + return $this->description; + } + + public function getPHUIXControlType() { + return 'static'; + } + + public function getPHUIXControlSpecification() { + return array( + 'value' => $this->getValue(), + 'description' => $this->getDescription(), + ); + } + +} diff --git a/src/applications/transactions/editfield/PhabricatorApplyEditField.php b/src/applications/transactions/editfield/PhabricatorApplyEditField.php new file mode 100644 --- /dev/null +++ b/src/applications/transactions/editfield/PhabricatorApplyEditField.php @@ -0,0 +1,40 @@ +actionDescription = $action_description; + return $this; + } + + public function getActionDescription() { + return $this->actionDescription; + } + + protected function newHTTPParameterType() { + return new AphrontBoolHTTPParameterType(); + } + + protected function newConduitParameterType() { + return new ConduitBoolParameterType(); + } + + public function shouldGenerateTransactionsFromSubmit() { + // This type of edit field just applies a prebuilt action, like "Accept + // Revision", and can not be submitted as part of an "Edit Object" form. + return false; + } + + protected function newCommentAction() { + return id(new PhabricatorEditEngineStaticCommentAction()) + ->setDescription($this->getActionDescription()); + } + +} diff --git a/webroot/rsrc/css/phui/phui-form-view.css b/webroot/rsrc/css/phui/phui-form-view.css --- a/webroot/rsrc/css/phui/phui-form-view.css +++ b/webroot/rsrc/css/phui/phui-form-view.css @@ -545,3 +545,8 @@ .device-desktop .aphront-form-error .phui-icon-view:hover { color: {$red}; } + +.phui-form-static-action { + padding: 4px; + color: {$bluetext}; +} diff --git a/webroot/rsrc/js/phuix/PHUIXFormControl.js b/webroot/rsrc/js/phuix/PHUIXFormControl.js --- a/webroot/rsrc/js/phuix/PHUIXFormControl.js +++ b/webroot/rsrc/js/phuix/PHUIXFormControl.js @@ -47,6 +47,9 @@ case 'optgroups': input = this._newOptgroups(spec); break; + case 'static': + input = this._newStatic(spec); + break; default: // TODO: Default or better error? JX.$E('Bad Input Type'); @@ -172,6 +175,25 @@ }; }, + _newStatic: function(spec) { + var node = JX.$N( + 'div', + { + className: 'phui-form-static-action' + }, + spec.description || ''); + + return { + node: node, + get: function() { + return true; + }, + set: function() { + return; + } + }; + }, + _newPoints: function(spec) { var attrs = { type: 'text',