Page MenuHomePhabricator

D17020.id41522.diff
No OneTemporary

D17020.id41522.diff

diff --git a/resources/sql/autopatches/20161220.release.changes.sql b/resources/sql/autopatches/20161220.release.changes.sql
new file mode 100755
--- /dev/null
+++ b/resources/sql/autopatches/20161220.release.changes.sql
@@ -0,0 +1,63 @@
+CREATE TABLE {$NAMESPACE}_release.release_changerequest (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ phid VARBINARY(64) NOT NULL,
+ requestedObjectPHID VARBINARY(64) NOT NULL,
+ description longtext COLLATE {$COLLATE_TEXT} NOT NULL,
+ status VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL,
+ requestorPHID VARBINARY(64) NOT NULL,
+ releasePHID VARBINARY(64) NOT NULL,
+ details longtext COLLATE {$COLLATE_TEXT} NOT NULL,
+ implementationKey VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL,
+ dateCreated INT(10) UNSIGNED NOT NULL,
+ dateModified INT(10) UNSIGNED NOT NULL,
+
+ UNIQUE KEY `key_phid` (`phid`),
+ KEY `key_release` (`releasePHID`)
+) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
+
+CREATE TABLE {$NAMESPACE}_release.release_changerequesttransaction (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ phid VARBINARY(64) NOT NULL,
+ authorPHID VARBINARY(64) NOT NULL,
+ objectPHID VARBINARY(64) NOT NULL,
+ viewPolicy VARBINARY(64) NOT NULL,
+ editPolicy VARBINARY(64) NOT NULL,
+ commentPHID VARBINARY(64) DEFAULT NULL,
+ commentVersion INT UNSIGNED NOT NULL,
+ transactionType VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL,
+ oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
+ newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
+ contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
+ metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
+ dateCreated INT UNSIGNED NOT NULL,
+ dateModified INT UNSIGNED NOT NULL,
+
+ UNIQUE KEY `key_phid` (`phid`),
+ KEY `key_object` (`objectPHID`)
+) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
+
+CREATE TABLE {$NAMESPACE}_release.release_changerequestcustomfieldstorage (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ objectPHID VARBINARY(64) NOT NULL,
+ fieldIndex BINARY(12) NOT NULL,
+ fieldValue LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT},
+ UNIQUE KEY (objectPHID, fieldIndex)
+) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
+
+CREATE TABLE {$NAMESPACE}_release.release_changerequestcustomfieldstringindex (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ objectPHID VARBINARY(64) NOT NULL,
+ indexKey BINARY(12) NOT NULL,
+ indexValue LONGTEXT NOT NULL COLLATE {$COLLATE_SORT},
+ KEY `key_join` (objectPHID, indexKey, indexValue(64)),
+ KEY `key_find` (indexKey, indexValue(64))
+) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
+
+CREATE TABLE {$NAMESPACE}_release.release_changerequestcustomfieldnumericindex (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ objectPHID VARBINARY(64) NOT NULL,
+ indexKey BINARY(12) NOT NULL,
+ indexValue BIGINT NOT NULL,
+ KEY `key_join` (objectPHID, indexKey, indexValue),
+ KEY `key_find` (indexKey, indexValue)
+) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
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
@@ -4497,12 +4497,46 @@
'ProjectReplyHandler' => 'applications/project/mail/ProjectReplyHandler.php',
'ProjectSearchConduitAPIMethod' => 'applications/project/conduit/ProjectSearchConduitAPIMethod.php',
'QueryFormattingTestCase' => 'infrastructure/storage/__tests__/QueryFormattingTestCase.php',
+ 'ReleaseChangeRequest' => 'applications/release/storage/ReleaseChangeRequest.php',
+ 'ReleaseChangeRequestAction' => 'applications/release/changes/actions/ReleaseChangeRequestAction.php',
+ 'ReleaseChangeRequestActionController' => 'applications/release/controller/ReleaseChangeRequestActionController.php',
+ 'ReleaseChangeRequestCommitImplementation' => 'applications/release/changes/ReleaseChangeRequestCommitImplementation.php',
+ 'ReleaseChangeRequestConfiguredCustomField' => 'applications/release/customfield/ReleaseChangeRequestConfiguredCustomField.php',
+ 'ReleaseChangeRequestCustomField' => 'applications/release/customfield/ReleaseChangeRequestCustomField.php',
+ 'ReleaseChangeRequestCustomFieldNumericIndex' => 'applications/release/storage/ReleaseChangeRequestCustomFieldNumericIndex.php',
+ 'ReleaseChangeRequestCustomFieldStorage' => 'applications/release/storage/ReleaseChangeRequestCustomFieldStorage.php',
+ 'ReleaseChangeRequestCustomFieldStringIndex' => 'applications/release/storage/ReleaseChangeRequestCustomFieldStringIndex.php',
+ 'ReleaseChangeRequestDescribeTransaction' => 'applications/release/xaction/ReleaseChangeRequestDescribeTransaction.php',
+ 'ReleaseChangeRequestDetailsController' => 'applications/release/controller/ReleaseChangeRequestDetailsController.php',
+ 'ReleaseChangeRequestEditConduitAPIMethod' => 'applications/release/conduit/ReleaseChangeRequestEditConduitAPIMethod.php',
+ 'ReleaseChangeRequestEditController' => 'applications/release/controller/ReleaseChangeRequestEditController.php',
+ 'ReleaseChangeRequestEditEngine' => 'applications/release/editor/ReleaseChangeRequestEditEngine.php',
+ 'ReleaseChangeRequestEditor' => 'applications/release/editor/ReleaseChangeRequestEditor.php',
+ 'ReleaseChangeRequestFromRevisionController' => 'applications/release/controller/ReleaseChangeRequestFromRevisionController.php',
+ 'ReleaseChangeRequestImplementation' => 'applications/release/changes/ReleaseChangeRequestImplementation.php',
+ 'ReleaseChangeRequestImplementationTransaction' => 'applications/release/xaction/ReleaseChangeRequestImplementationTransaction.php',
+ 'ReleaseChangeRequestListController' => 'applications/release/controller/ReleaseChangeRequestListController.php',
+ 'ReleaseChangeRequestMergeAction' => 'applications/release/changes/actions/ReleaseChangeRequestMergeAction.php',
+ 'ReleaseChangeRequestPHIDType' => 'applications/release/phid/ReleaseChangeRequestPHIDType.php',
+ 'ReleaseChangeRequestQuery' => 'applications/release/query/ReleaseChangeRequestQuery.php',
+ 'ReleaseChangeRequestRejectAction' => 'applications/release/changes/actions/ReleaseChangeRequestRejectAction.php',
+ 'ReleaseChangeRequestReleaseTransaction' => 'applications/release/xaction/ReleaseChangeRequestReleaseTransaction.php',
+ 'ReleaseChangeRequestRequestedObjectTransaction' => 'applications/release/xaction/ReleaseChangeRequestRequestedObjectTransaction.php',
+ 'ReleaseChangeRequestRequestorTransaction' => 'applications/release/xaction/ReleaseChangeRequestRequestorTransaction.php',
+ 'ReleaseChangeRequestRevisionImplementation' => 'applications/release/changes/ReleaseChangeRequestRevisionImplementation.php',
+ 'ReleaseChangeRequestSearchConduitAPIMethod' => 'applications/release/conduit/ReleaseChangeRequestSearchConduitAPIMethod.php',
+ 'ReleaseChangeRequestSearchEngine' => 'applications/release/query/ReleaseChangeRequestSearchEngine.php',
+ 'ReleaseChangeRequestStateTransaction' => 'applications/release/xaction/ReleaseChangeRequestStateTransaction.php',
+ 'ReleaseChangeRequestTransaction' => 'applications/release/storage/ReleaseChangeRequestTransaction.php',
+ 'ReleaseChangeRequestTransactionQuery' => 'applications/release/query/ReleaseChangeRequestTransactionQuery.php',
+ 'ReleaseChangeRequestTransactionType' => 'applications/release/xaction/ReleaseChangeRequestTransactionType.php',
'ReleaseConfiguredCustomField' => 'applications/release/customfield/ReleaseConfiguredCustomField.php',
'ReleaseCustomField' => 'applications/release/customfield/ReleaseCustomField.php',
'ReleaseCustomFieldNumericIndex' => 'applications/release/storage/ReleaseCustomFieldNumericIndex.php',
'ReleaseCustomFieldStorage' => 'applications/release/storage/ReleaseCustomFieldStorage.php',
'ReleaseCustomFieldStringIndex' => 'applications/release/storage/ReleaseCustomFieldStringIndex.php',
'ReleaseRelease' => 'applications/release/storage/ReleaseRelease.php',
+ 'ReleaseReleaseChangeRequestAddedTransaction' => 'applications/release/xaction/ReleaseReleaseChangeRequestAddedTransaction.php',
'ReleaseReleaseCreateReleaseCapability' => 'applications/release/capability/ReleaseReleaseCreateReleaseCapability.php',
'ReleaseReleaseCurrentRefTransaction' => 'applications/release/xaction/ReleaseReleaseCurrentRefTransaction.php',
'ReleaseReleaseCutpointTransaction' => 'applications/release/xaction/ReleaseReleaseCutpointTransaction.php',
@@ -4528,6 +4562,7 @@
'ReleaseReleaseTransactionComment' => 'applications/release/storage/ReleaseReleaseTransactionComment.php',
'ReleaseReleaseTransactionQuery' => 'applications/release/query/ReleaseReleaseTransactionQuery.php',
'ReleaseReleaseTransactionType' => 'applications/release/xaction/ReleaseReleaseTransactionType.php',
+ 'ReleaseRenderEventListener' => 'applications/release/ReleaseRenderEventListener.php',
'ReleephAuthorFieldSpecification' => 'applications/releeph/field/specification/ReleephAuthorFieldSpecification.php',
'ReleephBranch' => 'applications/releeph/storage/ReleephBranch.php',
'ReleephBranchAccessController' => 'applications/releeph/controller/branch/ReleephBranchAccessController.php',
@@ -9956,6 +9991,47 @@
'ProjectReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
'ProjectSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'QueryFormattingTestCase' => 'PhabricatorTestCase',
+ 'ReleaseChangeRequest' => array(
+ 'PhabricatorReleaseDAO',
+ 'PhabricatorApplicationTransactionInterface',
+ 'PhabricatorPolicyInterface',
+ 'PhabricatorCustomFieldInterface',
+ ),
+ 'ReleaseChangeRequestAction' => 'Phobject',
+ 'ReleaseChangeRequestActionController' => 'PhabricatorController',
+ 'ReleaseChangeRequestCommitImplementation' => 'Phobject',
+ 'ReleaseChangeRequestConfiguredCustomField' => array(
+ 'ReleaseChangeRequestCustomField',
+ 'PhabricatorStandardCustomFieldInterface',
+ ),
+ 'ReleaseChangeRequestCustomField' => 'PhabricatorCustomField',
+ 'ReleaseChangeRequestCustomFieldNumericIndex' => 'PhabricatorCustomFieldNumericIndexStorage',
+ 'ReleaseChangeRequestCustomFieldStorage' => 'PhabricatorCustomFieldStorage',
+ 'ReleaseChangeRequestCustomFieldStringIndex' => 'PhabricatorCustomFieldStringIndexStorage',
+ 'ReleaseChangeRequestDescribeTransaction' => 'ReleaseChangeRequestTransactionType',
+ 'ReleaseChangeRequestDetailsController' => 'PhabricatorController',
+ 'ReleaseChangeRequestEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
+ 'ReleaseChangeRequestEditController' => 'PhabricatorController',
+ 'ReleaseChangeRequestEditEngine' => 'PhabricatorEditEngine',
+ 'ReleaseChangeRequestEditor' => 'PhabricatorApplicationTransactionEditor',
+ 'ReleaseChangeRequestFromRevisionController' => 'PhabricatorController',
+ 'ReleaseChangeRequestImplementation' => 'Phobject',
+ 'ReleaseChangeRequestImplementationTransaction' => 'ReleaseChangeRequestTransactionType',
+ 'ReleaseChangeRequestListController' => 'PhabricatorController',
+ 'ReleaseChangeRequestMergeAction' => 'ReleaseChangeRequestAction',
+ 'ReleaseChangeRequestPHIDType' => 'PhabricatorPHIDType',
+ 'ReleaseChangeRequestQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
+ 'ReleaseChangeRequestRejectAction' => 'ReleaseChangeRequestAction',
+ 'ReleaseChangeRequestReleaseTransaction' => 'ReleaseChangeRequestTransactionType',
+ 'ReleaseChangeRequestRequestedObjectTransaction' => 'ReleaseChangeRequestTransactionType',
+ 'ReleaseChangeRequestRequestorTransaction' => 'ReleaseChangeRequestTransactionType',
+ 'ReleaseChangeRequestRevisionImplementation' => 'ReleaseChangeRequestImplementation',
+ 'ReleaseChangeRequestSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
+ 'ReleaseChangeRequestSearchEngine' => 'PhabricatorApplicationSearchEngine',
+ 'ReleaseChangeRequestStateTransaction' => 'ReleaseChangeRequestTransactionType',
+ 'ReleaseChangeRequestTransaction' => 'PhabricatorModularTransaction',
+ 'ReleaseChangeRequestTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
+ 'ReleaseChangeRequestTransactionType' => 'PhabricatorModularTransactionType',
'ReleaseConfiguredCustomField' => array(
'ReleaseCustomField',
'PhabricatorStandardCustomFieldInterface',
@@ -9974,6 +10050,7 @@
'PhabricatorCustomFieldInterface',
'PhabricatorPolicyInterface',
),
+ 'ReleaseReleaseChangeRequestAddedTransaction' => 'ReleaseReleaseTransactionType',
'ReleaseReleaseCreateReleaseCapability' => 'PhabricatorPolicyCapability',
'ReleaseReleaseCurrentRefTransaction' => 'ReleaseReleaseTransactionType',
'ReleaseReleaseCutpointTransaction' => 'ReleaseReleaseTransactionType',
@@ -9999,6 +10076,7 @@
'ReleaseReleaseTransactionComment' => 'PhabricatorApplicationTransactionComment',
'ReleaseReleaseTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'ReleaseReleaseTransactionType' => 'PhabricatorModularTransactionType',
+ 'ReleaseRenderEventListener' => 'PhabricatorEventListener',
'ReleephAuthorFieldSpecification' => 'ReleephFieldSpecification',
'ReleephBranch' => array(
'ReleephDAO',
diff --git a/src/applications/release/OPEN_QUESTIONS b/src/applications/release/OPEN_QUESTIONS
--- a/src/applications/release/OPEN_QUESTIONS
+++ b/src/applications/release/OPEN_QUESTIONS
@@ -8,8 +8,16 @@
how the old transaction code handled this, but it didn't make it to the
modular code.
Should I just find a new way to do it?
-- What kind of Edges do we need? RepositoryInRelease, ?
-
+- What kind of Edges do we need? RepositoryInRelease, ObjectRequestedAsChangeForRelease,
+ ChangeRequestInRelease, ?
+- Should Change Request have their own policy, subscribers, comments, etc.?
+- Transactions Metadata: That one didn't make the cut for modular-transactions
+ either. For the case of "Updates on CR show up on the Release page", they
+ would be very useful. Do we hate it?
+- In Releeph, there's a "want" status, that's corresponds to Differential's
+ Accept, and distinct from "Pull" status. Is that an important enough feature
+ to include as first-class? Is there a way to include something like that as
+ a Custom Field?
General questions:
diff --git a/src/applications/release/ReleaseRenderEventListener.php b/src/applications/release/ReleaseRenderEventListener.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/ReleaseRenderEventListener.php
@@ -0,0 +1,51 @@
+<?php
+
+final class ReleaseRenderEventListener extends PhabricatorEventListener {
+
+ public function register() {
+ $this->listen(PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS);
+ }
+
+ public function handleEvent(PhutilEvent $event) {
+ switch ($event->getType()) {
+ case PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS:
+ $this->handleActionsEvent($event);
+ break;
+ }
+ }
+
+ private function handleActionsEvent(PhutilEvent $event) {
+ if (!$this->canUseApplication($event->getUser())) {
+ return;
+ }
+
+ $object = $event->getValue('object');
+ if ($object instanceof DifferentialRevision) {
+ $this->addRevisionAction($event, $object);
+ }
+ // if ($object instanceof PhabricatorRepository) {
+ // $this->addRepositoryActions($event);
+ // }
+ // if ($object instanceof PhabricatorRepositoryCommit) {
+ // $this->addCommitActions($event);
+ // }
+ }
+
+ private function addRevisionAction(
+ PhutilEvent $event,
+ DifferentialRevision $revision) {
+
+ $repository = $revision->getRepository();
+ if (!$repository) {
+ return;
+ }
+ $revision_phid = $revision->getPHID();
+ $actions[] = id(new PhabricatorActionView())
+ ->setWorkflow(true)
+ ->setName('Request Pick To Release')
+ ->setIcon('fa-steam')
+ ->setHref("/release/request/revision/{$revision_phid}/");
+
+ $this->addActionMenuItems($event, $actions);
+ }
+}
diff --git a/src/applications/release/TODO b/src/applications/release/TODO
--- a/src/applications/release/TODO
+++ b/src/applications/release/TODO
@@ -1,2 +1,10 @@
+Things to complete before D17020 makes sense:
+
+- Maybe CR from a Commit ?
+- wire in some CC/TO mail addresses
+- At least a local hacked version of "merge action" that really merges and
+ marks the CR.
+- Implementation selector shows keys, should show nice names.
+
Things to complete before D16981 makes sense:
- show a workflow for creating a release
diff --git a/src/applications/release/application/PhabricatorReleaseApplication.php b/src/applications/release/application/PhabricatorReleaseApplication.php
--- a/src/applications/release/application/PhabricatorReleaseApplication.php
+++ b/src/applications/release/application/PhabricatorReleaseApplication.php
@@ -30,6 +30,12 @@
return true;
}
+ public function getEventListeners() {
+ return array(
+ new ReleaseRenderEventListener(),
+ );
+ }
+
public function getRemarkupRules() {
return array(
new PhabricatorReleaseRemarkupRule(),
@@ -54,12 +60,24 @@
public function getRoutes() {
return array(
- '/X(?P<id>[1-9]\d*)' => 'ReleaseReleaseDetailsController',
+ '/X(?P<id>[1-9]\d*)/?' => 'ReleaseReleaseDetailsController',
+ '/Y(?P<id>[1-9]\d*)' => 'ReleaseChangeRequestDetailsController',
'/release/' => array(
$this->getEditRoutePattern('edit/') => 'ReleaseReleaseEditController',
'(?:query/(?P<queryKey>[^/]+)/)?' => 'ReleaseReleaseListController',
// TODO new release
-
+ 'request/' => array(
+ 'revision/(?P<revision>[^/]*)/' =>
+ 'ReleaseChangeRequestFromRevisionController',
+ ),
+ 'changerequest/' => array(
+ '(?:query/(?P<queryKey>[^/]+)/)?' =>
+ 'ReleaseChangeRequestListController',
+ $this->getEditRoutePattern('edit/') =>
+ 'ReleaseChangeRequestEditController',
+ 'action/(?P<phid>[^/]*)/(?P<action>[^/]*)/' =>
+ 'ReleaseChangeRequestActionController',
+ ),
),
);
}
diff --git a/src/applications/release/changes/ReleaseChangeRequestCommitImplementation.php b/src/applications/release/changes/ReleaseChangeRequestCommitImplementation.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/changes/ReleaseChangeRequestCommitImplementation.php
@@ -0,0 +1,7 @@
+<?php
+
+/**
+ * A Change Request implementation for cherry-picking a Commit.
+ */
+abstract class ReleaseChangeRequestCommitImplementation
+ extends Phobject {}
diff --git a/src/applications/release/changes/ReleaseChangeRequestImplementation.php b/src/applications/release/changes/ReleaseChangeRequestImplementation.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/changes/ReleaseChangeRequestImplementation.php
@@ -0,0 +1,33 @@
+<?php
+
+abstract class ReleaseChangeRequestImplementation extends Phobject {
+
+ abstract public function getTitle(ReleaseChangeRequest $request);
+ abstract public function getImplementationName();
+
+ final public function getImplementationKey() {
+ return $this->getPhobjectClassConstant('IMPLEMENTATION_KEY');
+ }
+
+ public static function getImplementationByKey($key) {
+ $all = self::getAllImplementations();
+ $implementation = idx($all, $key);
+ if (!$implementation) {
+ throw new Exception(pht('Implementation not found for key %s', $key));
+ }
+ return $implementation;
+ }
+
+ public static function getAllImplementationKeys() {
+ $all = self::getAllImplementations();
+ return mpull($all, 'getImplementationKey');
+ }
+
+ // TODO does this need caching if we load lots of requests?
+ private static function getAllImplementations() {
+ return id(new PhutilClassMapQuery())
+ ->setAncestorClass(__CLASS__)
+ ->setUniqueMethod('getImplementationKey')
+ ->execute();
+ }
+}
diff --git a/src/applications/release/changes/ReleaseChangeRequestRevisionImplementation.php b/src/applications/release/changes/ReleaseChangeRequestRevisionImplementation.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/changes/ReleaseChangeRequestRevisionImplementation.php
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * A Change Request implementation for landing a Revision.
+ */
+final class ReleaseChangeRequestRevisionImplementation
+ extends ReleaseChangeRequestImplementation {
+
+ const IMPLEMENTATION_KEY = 'revision';
+
+ public function getImplementationName() {
+ return pht('Differential Revision');
+ }
+
+ public function getTitle(ReleaseChangeRequest $request) {
+ return $request->getRequestedObject()->getTitle();
+ }
+
+ public function getChangeAuthorPHID(ReleaseChangeRequest $request) {
+ return $request->getRequestedObject()->getAuthorPHID();
+ }
+}
diff --git a/src/applications/release/changes/actions/ReleaseChangeRequestAction.php b/src/applications/release/changes/actions/ReleaseChangeRequestAction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/changes/actions/ReleaseChangeRequestAction.php
@@ -0,0 +1,62 @@
+<?php
+
+abstract class ReleaseChangeRequestAction extends Phobject {
+
+ abstract public function getActionName();
+ abstract public function getActionIcon();
+ abstract public function getActionKey();
+ /**
+ * Actually do work. Should return a URI to redirect to.
+ */
+ abstract public function act(
+ ReleaseChangeRequest $change,
+ AphrontRequest $request);
+
+ /**
+ * return prompt to show the user in the confirmation dialog for this action.
+ */
+ abstract public function getPrompt(ReleaseChangeRequest $change);
+ /**
+ * return Title for the confirmation dialog for this action.
+ */
+ abstract public function getFormTitle(ReleaseChangeRequest $change);
+
+ public function isEnabledForRequest(ReleaseChangeRequest $change) {
+ return true;
+ }
+
+ public function assertPolicy(
+ PhabricatorUser $user,
+ ReleaseChangeRequest $change) {
+ // TBD TODO
+ // default to view + edit?
+ }
+
+ public function getActionHref(ReleaseChangeRequest $change) {
+ $phid = $change->getPHID();
+ $key = $this->getActionKey();
+ return "/release/changerequest/action/{$phid}/{$key}/";
+ }
+
+ public function generateActions() {
+ return array($this);
+ }
+
+ public static function getAllActions() {
+ return id(new PhutilClassMapQuery())
+ ->setAncestorClass(__CLASS__)
+ ->setExpandMethod('generateActions')
+ ->setUniqueMethod('getActionKey')
+ ->execute();
+ }
+
+ public static function getActionByKey($action_key) {
+ $action = idx(self::getAllActions(), $action_key);
+ if ($action) {
+ return $action;
+ }
+ throw new Exception(pht(
+ 'Change Request Action not found for action key "%s".',
+ $action_key));
+ }
+}
diff --git a/src/applications/release/changes/actions/ReleaseChangeRequestMergeAction.php b/src/applications/release/changes/actions/ReleaseChangeRequestMergeAction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/changes/actions/ReleaseChangeRequestMergeAction.php
@@ -0,0 +1,74 @@
+<?php
+
+final class ReleaseChangeRequestMergeAction
+ extends ReleaseChangeRequestAction {
+
+ public function getPrompt(ReleaseChangeRequest $change) {
+ return array(
+ pht(
+ 'This action will just mark this Change as "Merged", without actually '.
+ 'doing any work; You would need to perform the actual merging on your '.
+ 'own.'),
+ phutil_tag('br'),
+ pht(
+ 'You might want to implement a replacement for this action as an '.
+ 'extension, which will implement both merging and marking the change.'),
+ phutil_tag('br'),
+ pht(
+ 'You can hide this button by configuring %s to %s.',
+ phutil_tag('tt', array(), 'release.show-debug-tools'),
+ phutil_tag('tt', array(), 'false')),
+ );
+ }
+
+ public function getFormTitle(ReleaseChangeRequest $change) {
+ return pht('Merge Change Request');
+ }
+
+ public function isEnabledForRequest(ReleaseChangeRequest $change) {
+ return $change->getStatus() == ReleaseChangeRequest::STATUS_PENDING;
+ }
+
+ public function act(ReleaseChangeRequest $change, AphrontRequest $request) {
+ $viewer = $request->getViewer();
+ $xaction_type = ReleaseChangeRequestStateTransaction::TRANSACTIONTYPE;
+ $status = ReleaseChangeRequest::STATUS_INCLUDED;
+
+ $xaction = id(new ReleaseChangeRequestTransaction())
+ ->setTransactionType($xaction_type)
+ ->setNewValue($status);
+
+ $editor = id(new ReleaseChangeRequestEditor())
+ ->setActor($viewer)
+ ->setContentSourceFromRequest($request)
+ ->setContinueOnNoEffect(true);
+
+ $editor->applyTransactions($change, array($xaction));
+
+ return $change->getURI();
+ }
+
+ public function getActionName() {
+ return 'Mark Merged';
+ }
+ public function getActionIcon() {
+ return 'fa-plane';
+ }
+ public function getActionKey() {
+ return 'markmerged';
+ }
+
+ public function generateActions() {
+ // Ideally, we should detect the commit in Diffusion, like with Revisions,
+ // but I'm not sure how complex that would be (And how scalable across
+ // Implmentations).
+
+ // Or maybe just out-right remove it and let users download it from the docs
+ if (PhabricatorEnv::getEnvConfig('release.show-debug-tools')) {
+ return array($this);
+ } else {
+ return array();
+ }
+ }
+
+}
diff --git a/src/applications/release/changes/actions/ReleaseChangeRequestRejectAction.php b/src/applications/release/changes/actions/ReleaseChangeRequestRejectAction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/changes/actions/ReleaseChangeRequestRejectAction.php
@@ -0,0 +1,45 @@
+<?php
+
+final class ReleaseChangeRequestRejectAction
+ extends ReleaseChangeRequestAction {
+
+ public function getPrompt(ReleaseChangeRequest $change) {
+ return pht('Reject this Change Request?');
+ }
+ public function getFormTitle(ReleaseChangeRequest $change) {
+ return pht('Reject');
+ }
+
+ public function isEnabledForRequest(ReleaseChangeRequest $change) {
+ return $change->getStatus() == ReleaseChangeRequest::STATUS_PENDING;
+ }
+
+ public function act(ReleaseChangeRequest $change, AphrontRequest $request) {
+ $viewer = $request->getViewer();
+ $xaction_type = ReleaseChangeRequestStateTransaction::TRANSACTIONTYPE;
+ $status = ReleaseChangeRequest::STATUS_REJECTED;
+
+ $xaction = id(new ReleaseChangeRequestTransaction())
+ ->setTransactionType($xaction_type)
+ ->setNewValue($status);
+
+ $editor = id(new ReleaseChangeRequestEditor())
+ ->setActor($viewer)
+ ->setContentSourceFromRequest($request)
+ ->setContinueOnNoEffect(true);
+
+ $editor->applyTransactions($change, array($xaction));
+
+ return $change->getURI();
+ }
+
+ public function getActionName() {
+ return pht('Reject');
+ }
+ public function getActionIcon() {
+ return 'fa-times';
+ }
+ public function getActionKey() {
+ return 'reject';
+ }
+}
diff --git a/src/applications/release/conduit/ReleaseChangeRequestEditConduitAPIMethod.php b/src/applications/release/conduit/ReleaseChangeRequestEditConduitAPIMethod.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/conduit/ReleaseChangeRequestEditConduitAPIMethod.php
@@ -0,0 +1,20 @@
+<?php
+
+final class ReleaseChangeRequestEditConduitAPIMethod
+ extends PhabricatorEditEngineAPIMethod {
+
+ public function getAPIMethodName() {
+ return 'release.changerequest.edit';
+ }
+
+ public function newEditEngine() {
+ return new ReleaseChangeRequestEditEngine();
+ }
+
+ public function getMethodSummary() {
+ return pht(
+ 'Apply transactions to create a new Change Request or edit an existing '.
+ 'one.');
+ }
+
+}
diff --git a/src/applications/release/conduit/ReleaseChangeRequestSearchConduitAPIMethod.php b/src/applications/release/conduit/ReleaseChangeRequestSearchConduitAPIMethod.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/conduit/ReleaseChangeRequestSearchConduitAPIMethod.php
@@ -0,0 +1,18 @@
+<?php
+
+final class ReleaseChangeRequestSearchConduitAPIMethod
+ extends PhabricatorSearchEngineAPIMethod {
+
+ public function getAPIMethodName() {
+ return 'release.changerequest.search';
+ }
+
+ public function newSearchEngine() {
+ return new ReleaseChangeRequestSearchEngine();
+ }
+
+ public function getMethodSummary() {
+ return pht('Read information about Change Requests.');
+ }
+
+}
diff --git a/src/applications/release/conduit/ReleaseReleaseEditConduitAPIMethod.php b/src/applications/release/conduit/ReleaseReleaseEditConduitAPIMethod.php
--- a/src/applications/release/conduit/ReleaseReleaseEditConduitAPIMethod.php
+++ b/src/applications/release/conduit/ReleaseReleaseEditConduitAPIMethod.php
@@ -4,7 +4,7 @@
extends PhabricatorEditEngineAPIMethod {
public function getAPIMethodName() {
- return 'release.edit';
+ return 'release.edit'; // TODO maybe rename to release.release.edit ?
}
public function newEditEngine() {
diff --git a/src/applications/release/config/PhabricatorReleaseConfigOptions.php b/src/applications/release/config/PhabricatorReleaseConfigOptions.php
--- a/src/applications/release/config/PhabricatorReleaseConfigOptions.php
+++ b/src/applications/release/config/PhabricatorReleaseConfigOptions.php
@@ -34,6 +34,15 @@
'**[[ %s | Configuring Custom Fields ]]** in the documentation.',
$custom_fields_href)),
$this->newOption(
+ 'release.changerequest.customFields',
+ 'wild',
+ array())
+ ->setSummary(pht('Custom fields for Change Requests.'))
+ ->setDescription(pht(
+ 'Array of custom fields for Change Requests. For details, see '.
+ '**[[ %s | Configuring Custom Fields ]]** in the documentation.',
+ $custom_fields_href)),
+ $this->newOption(
'release.show-debug-tools',
'bool',
true)
diff --git a/src/applications/release/controller/ReleaseChangeRequestActionController.php b/src/applications/release/controller/ReleaseChangeRequestActionController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/controller/ReleaseChangeRequestActionController.php
@@ -0,0 +1,60 @@
+<?php
+
+final class ReleaseChangeRequestActionController extends PhabricatorController {
+
+ public function handleRequest(AphrontRequest $request) {
+ $viewer = $this->getViewer();
+
+ $change_phid = $request->getURIData('phid');
+
+ $change = id(new ReleaseChangeRequestQuery())
+ ->setViewer($viewer)
+ ->withPHIDs(array($change_phid))
+ ->needReleases(true)
+ ->needRequestObjects(true)
+ ->executeOne();
+ if (!$change) {
+ return new Aphront404Response();
+ }
+ $release = $change->getRelease();
+
+ $action_key = $request->getURIData('action');
+ $action = ReleaseChangeRequestAction::getActionByKey($action_key);
+
+ $action->assertPolicy($viewer, $change);
+
+ $errors = array();
+
+ if (!$action->isEnabledForRequest($change)) {
+ $errors[] = pht(
+ 'This action can not be applied to this Change Request at this time.');
+ }
+
+ if ($request->isDialogFormPost()) {
+
+ if (!$errors) {
+ try {
+ $redirect_uri = $action->act($change, $request);
+ return id(new AphrontRedirectResponse())->setURI($redirect_uri);
+ } catch (PhabricatorApplicationTransactionValidationException $ex) {
+ $errors[] = 'Failed to invoke action! '.$ex->getMessage();
+ }
+ }
+ }
+
+ $prompt = $action->getPrompt($change);
+
+ $dialog = $this->newDialog()
+ ->setSubmitURI($request->getRequestURI())
+ ->setTitle($action->getFormTitle($change))
+ ->appendChild($prompt)
+ ->setErrors($errors)
+ ->addCancelButton('#');
+
+ if ($action->isEnabledForRequest($change)) {
+ $dialog->addSubmitButton(pht('Submit'));
+ }
+
+ return $dialog;
+ }
+}
diff --git a/src/applications/release/controller/ReleaseChangeRequestDetailsController.php b/src/applications/release/controller/ReleaseChangeRequestDetailsController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/controller/ReleaseChangeRequestDetailsController.php
@@ -0,0 +1,145 @@
+<?php
+
+final class ReleaseChangeRequestDetailsController
+ extends PhabricatorController {
+
+ public function handleRequest(AphrontRequest $request) {
+ $viewer = $this->getViewer();
+ $id = $request->getURIData('id');
+
+ $change = id(new ReleaseChangeRequestQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($id))
+ ->needReleases(true)
+ ->needRequestObjects(true)
+ ->executeOne();
+ if (!$change) {
+ return new Aphront404Response();
+ }
+ $release = $change->getRelease();
+
+ $header = id(new PHUIHeaderView())
+ ->setHeader(
+ hsprintf('%s %s', $change->getMonogram(), $change->getTitle()))
+ ->setUser($viewer)
+ ->setPolicyObject($release);
+ $curtain = $this->buildCurtain($change);
+
+ $release_properties = $this->buildReleaseProperties($release);
+ $change_properties = $this->buildChangeProperties($change);
+
+ $timeline = $this->buildTransactionTimeline(
+ $change,
+ new ReleaseChangeRequestTransactionQuery());
+ $timeline->setQuoteRef($change->getMonogram());
+
+ $view = id(new PHUITwoColumnView())
+ ->setHeader($header)
+ ->setCurtain($curtain)
+ ->setMainColumn(array(
+ $timeline,
+ ))
+ ->addPropertySection($release->getName(), $release_properties)
+ ->addPropertySection(pht('Change Request'), $change_properties);
+
+ $crumbs = $this->buildApplicationCrumbs();
+ $crumbs->addTextCrumb($release->getMonogram(), $release->getURI());
+ $crumbs->addTextCrumb($change->getMonogram());
+
+ $title = $change->getTitle();
+ return $this->newPage()
+ ->setTitle($change->getMonogram().' '.$title)
+ ->setCrumbs($crumbs)
+ ->setPageObjectPHIDs(
+ array(
+ $release->getPHID(),
+ $change->getPHID(),
+ ))
+ ->appendChild(
+ array(
+ $view,
+ ));
+
+ }
+
+
+ private function buildChangeProperties($change) {
+ $viewer = $this->getViewer();
+
+ $properties = id(new PHUIPropertyListView())
+ ->addProperty(
+ pht('Change Type'),
+ $change->getImplementation()->getImplementationName())
+ ->addProperty(
+ pht('Requested By'),
+ $viewer->renderHandle($change->getRequestorPHID()))
+ ->addProperty(
+ pht('Content'),
+ $viewer->renderHandle($change->getRequestedObjectPHID()))
+ ->addProperty(
+ pht('Author'),
+ $viewer->renderHandle($change->getChangeAuthorPHID()))
+ ->addProperty(pht('Status'), $change->getStatusName());
+
+ $description = $change->getDescription();
+ if (strlen($description)) {
+ $properties
+ ->addSectionHeader('Description')
+ ->addTextContent(
+ new PHUIRemarkupView($this->getViewer(), $description));
+ }
+
+ $field_list = PhabricatorCustomField::getObjectFields(
+ $change,
+ PhabricatorCustomField::ROLE_VIEW);
+ $field_list->appendFieldsToPropertyList(
+ $change,
+ $viewer,
+ $properties);
+
+ return $properties;
+ }
+
+ private function buildReleaseProperties($release) {
+ $properties = id(new PHUIPropertyListView())
+ ->addProperty(pht('Release Type'), $release->getReleaseTemplateName())
+ ->addProperty(pht('Status'), $release->getStateName());
+
+ return $properties;
+ }
+
+ private function buildCurtain($change) {
+ $curtain = $this->newCurtainView($change);
+ $viewer = $this->getViewer();
+
+ $change_phid = $change->getPHID();
+
+ $id = $change->getID();
+ $edit_uri = $this->getApplicationURI("changerequest/edit/{$id}/");
+
+ $can_edit = PhabricatorPolicyFilter::hasCapability(
+ $viewer,
+ $change,
+ PhabricatorPolicyCapability::CAN_EDIT);
+
+ $curtain->addAction(id(new PhabricatorActionView())
+ ->setName(pht('Edit Change Request'))
+ ->setIcon('fa-pencil')
+ ->setHref($edit_uri)
+ ->setDisabled(!$can_edit));
+
+ $actions = ReleaseChangeRequestAction::getAllActions();
+ foreach ($actions as $action) {
+ $curtain->addAction(
+ id(new PhabricatorActionView())
+ ->setName($action->getActionName())
+ ->setWorkflow(true)
+ ->setDisabled(!$action->isEnabledForRequest($change))
+ ->setIcon($action->getActionIcon())
+ ->setHref($action->getActionHref($change)));
+ }
+
+ return $curtain;
+ }
+
+}
diff --git a/src/applications/release/controller/ReleaseChangeRequestEditController.php b/src/applications/release/controller/ReleaseChangeRequestEditController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/controller/ReleaseChangeRequestEditController.php
@@ -0,0 +1,14 @@
+<?php
+
+final class ReleaseChangeRequestEditController extends PhabricatorController {
+
+ public function handleRequest(AphrontRequest $request) {
+ $viewer = $this->getViewer();
+
+ $engine = id(new ReleaseChangeRequestEditEngine())
+ ->setController($this);
+
+ return $engine->buildResponse();
+ }
+
+}
diff --git a/src/applications/release/controller/ReleaseChangeRequestFromRevisionController.php b/src/applications/release/controller/ReleaseChangeRequestFromRevisionController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/controller/ReleaseChangeRequestFromRevisionController.php
@@ -0,0 +1,128 @@
+<?php
+
+final class ReleaseChangeRequestFromRevisionController
+ extends PhabricatorController {
+
+ public function handleRequest(AphrontRequest $request) {
+ $viewer = $this->getViewer();
+
+ $revision_phid = $request->getURIData('revision');
+
+ $revision = id(new DifferentialRevisionQuery())
+ ->withPHIDs(array($revision_phid))
+ ->setViewer($viewer)
+ ->executeOne();
+ if (!$revision) {
+ return new Aphront404Response();
+ }
+
+ $repository = $revision->getRepository();
+ $errors = array();
+
+ // TODO requireCapability
+
+ $v_release = null;
+ $e_release = true;
+
+ $v_message = null;
+
+ if ($request->isDialogFormPost()) {
+
+ $v_release = $request->getArr('release');
+ if (!$v_release) {
+ $e_release = pht('Required');
+ $errors[] = 'Specify target release';
+ } else {
+ $release = id(new ReleaseReleaseQuery())
+ ->setViewer($viewer)
+ ->withPHIDs($v_release)
+ ->executeOne();
+
+ if (!$release->canAcceptChangeRequests()) {
+ $e_release = pht('Invalid');
+ $errors[] = pht(
+ 'This release can not accept any change requests at this time.');
+ }
+ }
+
+ $v_message = $request->getStr('message');
+
+ $actor_phid = $viewer->getPHID();
+
+ if (!$errors) {
+
+ $change_request = new ReleaseChangeRequest();
+
+ $xactions = array(
+ id(new ReleaseChangeRequestTransaction())
+ ->setTransactionType(PhabricatorTransactions::TYPE_CREATE),
+ id(new ReleaseChangeRequestTransaction())
+ ->setTransactionType(ReleaseChangeRequestRequestorTransaction::TRANSACTIONTYPE)
+ ->setNewValue($actor_phid),
+ id(new ReleaseChangeRequestTransaction())
+ ->setTransactionType(ReleaseChangeRequestReleaseTransaction::TRANSACTIONTYPE)
+ ->setNewValue($release->getPHID()),
+ id(new ReleaseChangeRequestTransaction())
+ ->setTransactionType(ReleaseChangeRequestImplementationTransaction::TRANSACTIONTYPE)
+ ->setNewValue(ReleaseChangeRequestRevisionImplementation::IMPLEMENTATION_KEY),
+ id(new ReleaseChangeRequestTransaction())
+ ->setTransactionType(ReleaseChangeRequestRequestedObjectTransaction::TRANSACTIONTYPE)
+ ->setNewValue($revision_phid),
+ id(new ReleaseChangeRequestTransaction())
+ ->setTransactionType(ReleaseChangeRequestDescribeTransaction::TRANSACTIONTYPE)
+ ->setNewValue($v_message),
+ );
+ $editor = id(new ReleaseChangeRequestEditor())
+ ->setActor($viewer)
+ ->setContentSource(
+ PhabricatorContentSource::newFromRequest($request))
+ ->setContinueOnNoEffect(true);
+ $editor->applyTransactions($change_request, $xactions);
+
+ // maybe add some xactions to the Release here.
+
+ return id(new AphrontRedirectResponse())->setURI($change_request->getURI());
+ }
+ }
+
+ $prompt = hsprintf(
+ 'This will request the Release Managers to include this Revision in the '.
+ 'selected Release.');
+
+ // TODO filter only to relevant releases
+ $datasource = id(new ReleaseReleaseDatasource());
+
+ $form = id(new AphrontFormView())
+ ->setUser($viewer)
+ ->appendControl(
+ id(new AphrontFormTokenizerControl())
+ ->setDatasource($datasource)
+ ->setLimit(1)
+ ->setName('release')
+ ->setLabel(pht('Release'))
+ ->setValue($v_release)
+ ->setError($e_release))
+ ->appendControl(
+ id(new PhabricatorRemarkupControl())
+ ->setLabel('Reason')
+ ->setValue($v_message)
+ ->setName('message'));
+
+ $errors_view = null;
+ if ($errors) {
+ $errors_view = id(new PHUIInfoView())
+ ->setErrors($errors);
+ }
+
+ $dialog = $this->newDialog()
+ ->setTitle(pht('Pick Revision to Release?'))
+ ->appendChild($prompt)
+ ->appendChild($errors_view)
+ ->appendForm($form)
+ ->addSubmitButton(pht('Please'))
+ ->addCancelButton('#');
+
+ return id(new AphrontDialogResponse())->setDialog($dialog);
+ }
+
+}
diff --git a/src/applications/release/controller/ReleaseChangeRequestListController.php b/src/applications/release/controller/ReleaseChangeRequestListController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/controller/ReleaseChangeRequestListController.php
@@ -0,0 +1,25 @@
+<?php
+
+final class ReleaseChangeRequestListController extends PhabricatorController {
+
+ public function handleRequest(AphrontRequest $request) {
+ return id(new ReleaseChangeRequestSearchEngine())
+ ->setController($this)
+ ->buildResponse();
+ }
+
+
+ protected function buildApplicationCrumbs() {
+ $crumbs = parent::buildApplicationCrumbs();
+
+ $crumbs->addTextCrumb(pht('Change Requests'));
+
+ return $crumbs;
+ }
+
+
+ public function shouldAllowPublic() {
+ return true;
+ }
+
+}
diff --git a/src/applications/release/controller/ReleaseReleaseDetailsController.php b/src/applications/release/controller/ReleaseReleaseDetailsController.php
--- a/src/applications/release/controller/ReleaseReleaseDetailsController.php
+++ b/src/applications/release/controller/ReleaseReleaseDetailsController.php
@@ -1,6 +1,5 @@
<?php
-// TODO rename to DetailsController
final class ReleaseReleaseDetailsController extends PhabricatorController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
@@ -62,6 +61,18 @@
$comment_view->setTransactionTimeline($timeline);
$repositories = $this->buildRepositoriesSection($viewer, $release);
+ $changes = $this->buildChangesSection($viewer, $release);
+
+ $phid = $release->getPHID();
+ $button = id(new PHUIButtonView())
+ ->setText(pht('See All'))
+ ->setHref($this->getApplicationURI("changerequest/?release={$phid}"))
+ ->setTag('a')
+ ->setIcon('fa-list-ul');
+
+ $changes_header = id(new PHUIHeaderView())
+ ->setHeader(pht('Pending Changes'))
+ ->addActionLink($button);
$view = id(new PHUITwoColumnView())
->setHeader($header)
@@ -71,7 +82,8 @@
$comment_view,
))
->addPropertySection(pht('Details'), $properties)
- ->addPropertySection(pht('Repositories'), $repositories);
+ ->addPropertySection(pht('Repositories'), $repositories)
+ ->addPropertySection($changes_header, $changes);
return $this->newPage()
->setTitle('X'.$release->getID().' '.$release_name)
@@ -86,6 +98,47 @@
));
}
+ private function buildChangesSection($viewer, $release) {
+ // TODO see all
+ $changes = id(new ReleaseChangeRequestQuery())
+ ->setViewer($viewer)
+ ->setLimit(5)
+ ->withReleasePHIDs(array($release->getPHID()))
+ ->withStatuses(array(ReleaseChangeRequest::STATUS_PENDING))
+ ->needRequestObjects(true)
+ ->execute();
+
+ if (!$changes) {
+ return null;
+ }
+
+ $icon = id(new PHUIIconView())
+ ->setIcon('fa-list-alt');
+
+
+ $list = id(new PHUIObjectItemListView());
+
+ foreach ($changes as $change) {
+ $item = id(new PHUIObjectItemView())
+ ->setObjectName($change->getMonogram())
+ ->setHref($change->getURI())
+ ->setHeader($change->getTitle())
+ ->addAttribute(pht('(impl: %s)', $change->getImplementationKey()));
+
+/* D17004
+ $field_list = PhabricatorCustomField::getObjectFields(
+ $change,
+ PhabricatorCustomField::ROLE_LIST);
+ $field_list
+ ->appendFieldsToListItem($change, $this->getViewer(), $item);
+*/
+ $list->addItem($item);
+ }
+
+ return $list;
+ }
+
+
private function buildRepositoriesSection($viewer, $release) {
$cutpoints = $release->getCutpoints();
$currentrefs = $release->getCurrentRefs();
diff --git a/src/applications/release/controller/ReleaseReleaseEditController.php b/src/applications/release/controller/ReleaseReleaseEditController.php
--- a/src/applications/release/controller/ReleaseReleaseEditController.php
+++ b/src/applications/release/controller/ReleaseReleaseEditController.php
@@ -8,11 +8,6 @@
$engine = id(new ReleaseReleaseEditEngine())
->setController($this);
- $id = $request->getURIData('id');
- if (!$id) {
- // ??
- }
-
return $engine->buildResponse();
}
diff --git a/src/applications/release/controller/ReleaseReleaseListController.php b/src/applications/release/controller/ReleaseReleaseListController.php
--- a/src/applications/release/controller/ReleaseReleaseListController.php
+++ b/src/applications/release/controller/ReleaseReleaseListController.php
@@ -3,8 +3,19 @@
final class ReleaseReleaseListController extends PhabricatorController {
public function handleRequest(AphrontRequest $request) {
+ $nav_items = array(
+ id(new PHUIListItemView())
+ ->setType(PHUIListItemView::TYPE_LABEL)
+ ->setName(pht('Change Requests')),
+ id(new PHUIListItemView())
+ ->setType(PHUIListItemView::TYPE_LINK)
+ ->setName(pht('List Change Requests'))
+ ->setHref($this->getApplicationURI('changerequest/')),
+ );
+
return id(new ReleaseReleaseSearchEngine())
->setController($this)
+ ->setNavigationItems($nav_items)
->buildResponse();
}
diff --git a/src/applications/release/customfield/ReleaseChangeRequestConfiguredCustomField.php b/src/applications/release/customfield/ReleaseChangeRequestConfiguredCustomField.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/customfield/ReleaseChangeRequestConfiguredCustomField.php
@@ -0,0 +1,22 @@
+<?php
+
+final class ReleaseChangeRequestConfiguredCustomField
+ extends ReleaseChangeRequestCustomField
+ implements PhabricatorStandardCustomFieldInterface {
+
+ public function getStandardCustomFieldNamespace() {
+ return 'release.cr';
+ }
+
+ public function createFields($object) {
+ $config = PhabricatorEnv::getEnvConfig(
+ 'release.changerequest.customFields',
+ array());
+ $fields = PhabricatorStandardCustomField::buildStandardFields(
+ $this,
+ $config);
+
+ return $fields;
+ }
+
+}
diff --git a/src/applications/release/customfield/ReleaseChangeRequestCustomField.php b/src/applications/release/customfield/ReleaseChangeRequestCustomField.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/customfield/ReleaseChangeRequestCustomField.php
@@ -0,0 +1,17 @@
+<?php
+
+abstract class ReleaseChangeRequestCustomField
+ extends PhabricatorCustomField {
+
+ public function newStorageObject() {
+ return new ReleaseChangeRequestCustomFieldStorage();
+ }
+
+ protected function newStringIndexStorage() {
+ return new ReleaseChangeRequestCustomFieldStringIndex();
+ }
+
+ protected function newNumericIndexStorage() {
+ return new ReleaseChangeRequestCustomFieldNumericIndex();
+ }
+}
diff --git a/src/applications/release/editor/ReleaseChangeRequestEditEngine.php b/src/applications/release/editor/ReleaseChangeRequestEditEngine.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/editor/ReleaseChangeRequestEditEngine.php
@@ -0,0 +1,147 @@
+<?php
+
+final class ReleaseChangeRequestEditEngine
+ extends PhabricatorEditEngine {
+
+ const ENGINECONST = 'release.change';
+
+ public function getEngineName() {
+ return pht('Release Change Requests');
+ }
+
+ public function isEngineConfigurable() {
+ return false;
+ }
+
+ protected function getObjectName() {
+ return pht('Release Change Request');
+ }
+
+ public function getSummaryHeader() {
+ return pht('Configure Change Requests Forms');
+ }
+
+ public function getSummaryText() {
+ return pht('Configure editing Release Change Requests.');
+ }
+
+ public function getEngineApplicationClass() {
+ return 'PhabricatorReleaseApplication';
+ }
+
+ protected function newEditableObject() {
+ return new ReleaseChangeRequest();
+ }
+
+ protected function newObjectQuery() {
+ return id(new ReleaseChangeRequestQuery())
+ ->needRequestObjects(true);
+ }
+
+ protected function getObjectCreateTitleText($object) {
+ return pht('Request a new Change to a Release');
+ }
+
+ protected function getObjectEditTitleText($object) {
+ return pht('Edit Change Request %s', $object->getTitle());
+ }
+
+ protected function getEditorURI() {
+ return '/release/change/edit/';
+ }
+
+ protected function getObjectEditShortText($object) {
+ return $object->getTitle();
+ }
+
+ protected function getObjectCreateShortText() {
+ return pht('New Change Request');
+ }
+
+ protected function getCreateNewObjectPolicy() {
+ // TODO new capability.
+ return $this->getApplication()->getPolicy(
+ ReleaseReleaseDefaultEditCapability::CAPABILITY);
+ }
+
+ protected function getCommentViewHeaderText($object) {
+ return pht('Discuss Change');
+ }
+
+ protected function getCommentViewButtonText($object) {
+ return pht('Comment');
+ }
+
+ protected function getObjectViewURI($object) {
+ return $object->getURI();
+ }
+
+ protected function buildCustomEditFields($object) {
+
+ $states = ReleaseChangeRequest::getStatusMap();
+
+ $implementation_keys =
+ ReleaseChangeRequestImplementation::getAllImplementationKeys();
+
+ $is_edit_form = !$this->getIsCreate();
+
+ return array(
+ id(new PhabricatorUsersEditField())
+ ->setKey('requestor')
+ ->setLabel(pht('Requested By'))
+ ->setDescription(pht('The user who asks this Change to be included.'))
+ ->setTransactionType(
+ ReleaseChangeRequestRequestorTransaction::TRANSACTIONTYPE)
+ ->setIsCopyable(true)
+ ->setSingleValue($object->getRequestorPHID()),
+ id(new PhabricatorSelectEditField())
+ ->setKey('state')
+ ->setLabel(pht('State'))
+ ->setTransactionType(
+ ReleaseChangeRequestStateTransaction::TRANSACTIONTYPE)
+ ->setIsCopyable(true)
+ ->setOptions($states)
+ ->setValue($object->getStatus()),
+ id(new PhabricatorHandlesEditField())
+ ->setKey('release')
+ ->setLabel('Release')
+ ->setDescription('Release to request the change for.')
+ ->setTransactionType(
+ ReleaseChangeRequestReleaseTransaction::TRANSACTIONTYPE)
+ ->setIsCopyable(true)
+ ->setSingleValue($object->getReleasePHID()),
+ id(new PhabricatorHandlesEditField())
+ ->setKey('targetobject')
+ ->setLabel(pht('Requested Object'))
+ ->setDescription(pht('Object that contains the change itself.'))
+ ->setTransactionType(
+ ReleaseChangeRequestRequestedObjectTransaction::TRANSACTIONTYPE)
+ ->setIsLockable(false)
+ ->setIsLocked($is_edit_form)
+ ->setSingleValue($object->getRequestedObjectPHID()),
+ id(new PhabricatorSelectEditField())
+ ->setKey('implkey')
+ ->setLabel(pht('Implementation'))
+ ->setDescription(
+ pht('An Implementation Key of a valid implementation.'))
+ ->setTransactionType(
+ ReleaseChangeRequestImplementationTransaction::TRANSACTIONTYPE)
+ ->setIsCopyable(true)
+ // is there a way to do this for conduit?
+ ->setOptions($implementation_keys)
+ ->setIsLockable(false)
+ ->setIsLocked($is_edit_form)
+ ->setValue($object->getImplementationKey()),
+ id(new PhabricatorRemarkupEditField())
+ ->setKey('description')
+ ->setLabel(pht('Description'))
+ ->setTransactionType(
+ ReleaseChangeRequestDescribeTransaction::TRANSACTIONTYPE)
+ ->setDescription(pht('ISO-9001 Change Request explanation.'))
+ ->setConduitDescription(pht('Description.'))
+ ->setConduitTypeDescription(pht('Remarkup'))
+ ->setValue($object->getDescription()),
+ );
+ }
+
+}
diff --git a/src/applications/release/editor/ReleaseChangeRequestEditor.php b/src/applications/release/editor/ReleaseChangeRequestEditor.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/editor/ReleaseChangeRequestEditor.php
@@ -0,0 +1,125 @@
+<?php
+
+final class ReleaseChangeRequestEditor
+ extends PhabricatorApplicationTransactionEditor {
+
+ private $modularTypes;
+
+ public function getEditorApplicationClass() {
+ return 'PhabricatorReleaseApplication';
+ }
+
+ public function getEditorObjectsDescription() {
+ return pht('Release Change Request');
+ }
+
+ public function getCreateObjectTitle($author, $object) {
+ return pht(
+ '%s requested this change to be included in a Release.',
+ $author);
+ }
+
+ public function getCreateObjectTitleForFeed($author, $object) {
+ return pht(
+ '%s requested %s to be included in a Release.',
+ $author,
+ $object);
+ }
+
+ protected function applyInitialEffects(
+ PhabricatorLiskDAO $object,
+ array $xactions) {}
+
+ public function getTransactionTypes() {
+ $types = parent::getTransactionTypes();
+
+ return $types;
+ }
+
+ protected function shouldApplyInitialEffects(
+ PhabricatorLiskDAO $object,
+ array $xactions) {
+ return true;
+ }
+
+ protected function shouldSendMail(
+ PhabricatorLiskDAO $object,
+ array $xactions) {
+
+ return true;
+ }
+
+ protected function getMailCC(PhabricatorLiskDAO $object) {
+ // TODO
+ return array();
+ }
+
+ protected function getMailSubjectPrefix() {
+ return '[Release Change Request]';
+ }
+
+ public function getMailTagsMap() {
+ return array(
+ ReleaseChangeRequestTransaction::MAILTAG_OTHER =>
+ pht('Other activity not listed above occurs.'),
+ );
+ }
+
+ // protected function buildReplyHandler(PhabricatorLiskDAO $object) {
+ // return id(new ReleaseReleaseReplyHandler()) // TODO
+ // ->setMailReceiver($object);
+ // }
+
+
+ protected function buildMailTemplate(PhabricatorLiskDAO $object) {
+ $id = $object->getID();
+ $name = $object->getName();
+
+ return id(new PhabricatorMetaMTAMail())
+ ->setSubject("Y{$id} {$name}")
+ ->addHeader('Thread-Topic', "Change Request {$id}");
+ }
+
+ protected function shouldPublishFeedStory(
+ PhabricatorLiskDAO $object,
+ array $xactions) {
+
+ return true;
+ }
+
+ protected function supportsSearch() {
+ return false;
+ }
+
+ protected function expandTransaction(
+ PhabricatorLiskDAO $release,
+ PhabricatorApplicationTransaction $xaction) {
+ $xactions = parent::expandTransaction($release, $xaction);
+
+ return $xactions;
+ }
+
+ protected function getMailTo(PhabricatorLiskDAO $object) {
+ $tos = array();
+ return $tos;
+ }
+
+ protected function buildMailBody(
+ PhabricatorLiskDAO $object,
+ array $xactions) {
+
+ $body = parent::buildMailBody($object, $xactions);
+
+ foreach ($xactions as $xaction) {
+ $type = $xaction->getTransactionType();
+ $new = $xaction->getNewValue();
+ }
+
+ $body->addLinkSection(
+ pht('REQUEST DETAILS'),
+ PhabricatorEnv::getProductionURI($object->getURI()));
+
+ return $body;
+ }
+
+}
diff --git a/src/applications/release/phid/ReleaseChangeRequestPHIDType.php b/src/applications/release/phid/ReleaseChangeRequestPHIDType.php
new file mode 100755
--- /dev/null
+++ b/src/applications/release/phid/ReleaseChangeRequestPHIDType.php
@@ -0,0 +1,73 @@
+<?php
+
+final class ReleaseChangeRequestPHIDType extends PhabricatorPHIDType {
+
+ const TYPECONST = 'RECR';
+
+ public function getTypeName() {
+ return pht('Release Change Request');
+ }
+
+ public function newObject() {
+ return new ReleaseChangeRequest();
+ }
+
+ public function getPHIDTypeApplicationClass() {
+ return 'PhabricatorReleaseApplication';
+ }
+
+ protected function buildQueryForObjects(
+ PhabricatorObjectQuery $query,
+ array $phids) {
+
+ return id(new ReleaseChangeRequestQuery())
+ ->withPHIDs($phids);
+ }
+
+ public function loadHandles(
+ PhabricatorHandleQuery $query,
+ array $handles,
+ array $objects) {
+
+ foreach ($handles as $phid => $handle) { /// modernize this? support lookup by monogram.
+ $change = $objects[$phid];
+
+ $id = $change->getID();
+ $title = $change->getTitle();
+
+ $handle->setURI($change->getURI());
+ $handle->setName($title);
+ $handle->setFullName("Y{$id}: {$title}");
+ }
+ }
+
+ public function canLoadNamedObject($name) {
+ return preg_match('/^Y[1-9]\d*$/i', $name);
+ }
+
+ public function loadNamedObjects(
+ PhabricatorObjectQuery $query,
+ array $names) {
+
+ $id_map = array();
+ foreach ($names as $name) {
+ $id = (int)substr($name, 1);
+ $id_map[$id][] = $name;
+ }
+
+ $objects = id(new ReleaseChangeRequestQuery())
+ ->setViewer($query->getViewer())
+ ->withIDs(array_keys($id_map))
+ ->execute();
+
+ $results = array();
+ foreach ($objects as $id => $object) {
+ foreach (idx($id_map, $id, array()) as $name) {
+ $results[$name] = $object;
+ }
+ }
+
+ return $results;
+ }
+
+}
diff --git a/src/applications/release/query/ReleaseChangeRequestQuery.php b/src/applications/release/query/ReleaseChangeRequestQuery.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/query/ReleaseChangeRequestQuery.php
@@ -0,0 +1,204 @@
+<?php
+
+final class ReleaseChangeRequestQuery
+ extends PhabricatorCursorPagedPolicyAwareQuery {
+
+ private $ids;
+ private $phids;
+ private $releasePHIDs;
+ private $statuses;
+ private $implementationKeys;
+ private $datasourceQuery;
+
+
+ private $groupBy = 'group-none';
+ const GROUP_NONE = 'group-none';
+ const GROUP_STATE = 'group-state';
+
+ public function withIDs(array $ids) {
+ $this->ids = $ids;
+ return $this;
+ }
+
+ public function withPHIDs(array $phids) {
+ $this->phids = $phids;
+ return $this;
+ }
+
+ public function withReleasePHIDs(array $release_phids) {
+ $this->releasePHIDs = $release_phids;
+ return $this;
+ }
+
+ public function withStatuses(array $statuses) {
+ $this->statuses = $statuses;
+ return $this;
+ }
+
+ public function withImplementationKeys(array $implementation_keys) {
+ $this->implementationKeys = $implementation_keys;
+ return $this;
+ }
+
+ public function withDatasourceQuery($query) {
+ $this->datasourceQuery = $query;
+ return $this;
+ }
+
+ public function needReleases($need_releases) {
+ // $this->needReleases = $need_releases;
+ // TODO drop method - always take them.
+ return $this;
+ }
+
+ public function needRequestObjects($need_request_objects) {
+ // TODO drop method - always take them.
+ return $this;
+ }
+
+ public function newResultObject() {
+ return new ReleaseChangeRequest();
+ }
+
+ 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);
+ }
+
+ if ($this->releasePHIDs !== null) {
+ $where[] = qsprintf(
+ $conn,
+ 'releasePHID IN (%Ls)',
+ $this->releasePHIDs);
+ }
+
+ if ($this->statuses !== null) {
+ $where[] = qsprintf(
+ $conn,
+ 'status IN (%Ls)',
+ $this->statuses);
+ }
+
+ if ($this->implementationKeys !== null) {
+ $where[] = qsprintf(
+ $conn,
+ 'implementationKey IN (%Ls)',
+ $this->implementationKeys);
+ }
+
+ if (strlen($this->datasourceQuery)) {
+ // TODO
+ // $where[] = qsprintf(
+ // $conn,
+ // 'name LIKE %~',
+ // $this->datasourceQuery);
+ }
+
+ return $where;
+ }
+
+ protected function willFilterPage(array $changes) {
+ $viewer = $this->getViewer();
+
+ $release_phids = mpull($changes, 'getReleasePHID');
+ $releases = id(new ReleaseReleaseQuery())
+ ->setViewer($viewer)
+ ->setParentQuery($this)
+ ->withPHIDs($release_phids)
+ ->execute();
+ $releases = mpull($releases, null, 'getPHID');
+
+ foreach ($changes as $key => $change) {
+ $change->attachRelease(idx($releases, $change->getReleasePHID()));
+ // TODO if !$release
+ // $this->didRejectResult($change);
+ // unset($changes[$key]);
+ // continue;
+ }
+
+ $object_phids = mpull($changes, 'getRequestedObjectPHID');
+ $objects = id(new PhabricatorObjectQuery())
+ ->setViewer($viewer)
+ ->setParentQuery($this)
+ ->withPHIDs($object_phids)
+ ->execute();
+ $objects = mpull($objects, null, 'getPHID');
+
+ foreach ($changes as $change) {
+ $change->attachRequestedObject(idx($objects, $change->getRequestedObjectPHID()));
+ }
+
+ return $changes;
+ }
+
+ // protected function getDefaultOrderVector() {
+ // return array('name');
+ // }
+
+ public function getBuiltinOrders() {
+ return array(
+ 'select' => array(
+ 'vector' => array('status', '-id'),
+ 'name' => pht('Status'),
+ ),
+ ) + parent::getBuiltinOrders();
+ }
+
+ public function setGroupBy($group) {
+ $this->groupBy = $group;
+ $vector = array();
+
+ switch ($this->groupBy) {
+ case self::GROUP_NONE:
+ $vector = array();
+ break;
+ case self::GROUP_STATE:
+ $vector = array('status');
+ break;
+ }
+
+ // $this->setGroupVector($vector);
+
+ return $this;
+ }
+
+ public function getOrderableColumns() {
+ return parent::getOrderableColumns() + array(
+ 'release' => array(
+ 'table' => $this->getPrimaryTableAlias(),
+ 'column' => 'releasePHID',
+ 'reverse' => false,
+ 'type' => 'phid',
+ 'unique' => false,
+ ),
+ 'status' => array(
+ 'table' => $this->getPrimaryTableAlias(),
+ 'column' => 'status',
+ 'reverse' => false,
+ 'type' => 'string',
+ 'unique' => false,
+ ),
+ );
+ }
+
+ public function getQueryApplicationClass() {
+ return 'PhabricatorReleaseApplication';
+ }
+
+}
diff --git a/src/applications/release/query/ReleaseChangeRequestSearchEngine.php b/src/applications/release/query/ReleaseChangeRequestSearchEngine.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/query/ReleaseChangeRequestSearchEngine.php
@@ -0,0 +1,125 @@
+<?php
+
+final class ReleaseChangeRequestSearchEngine
+ extends PhabricatorApplicationSearchEngine {
+
+ public function getResultTypeDescription() {
+ return pht('Change Requests');
+ }
+
+ public function getApplicationClassName() {
+ return 'PhabricatorReleaseApplication';
+ }
+
+ public function newQuery() {
+ return id(new ReleaseChangeRequestQuery())
+ ->needRequestObjects(true);
+ }
+
+ protected function buildCustomSearchFields() {
+ return array(
+ id(new PhabricatorSearchDatasourceField())
+ ->setLabel('Release')
+ ->setKey('release')
+ ->setDatasource(new ReleaseReleaseDatasource()),
+ id(new PhabricatorSearchSelectField())
+ ->setLabel('Status')
+ ->setKey('status')
+ ->setOptions($this->getStateOptions())
+ ->setDefault('all'),
+ );
+ }
+
+ protected function buildQueryFromParameters(array $map) {
+ $query = $this->newQuery();
+
+ $release = idx($map, 'release');
+ if ($release) {
+ $query->withReleasePHIDs($release);
+ }
+
+ $status = idx($map, 'status', 'all');
+ if ($status != 'all') {
+ $query->withStatuses(array($status));
+ }
+
+
+ return $query;
+ }
+
+ protected function getURI($path) {
+ return '/release/changerequest/'.$path;
+ }
+
+ protected function getBuiltinQueryNames() {
+ return array(
+ 'all' => pht('All'),
+ 'pending' => pht('Pending'),
+ );
+ }
+
+ public function buildSavedQueryFromBuiltin($query_key) {
+ $query = $this->newSavedQuery();
+ $query->setQueryKey($query_key);
+
+ $viewer_phid = $this->requireViewer()->getPHID();
+
+ switch ($query_key) {
+ case 'all':
+ return $query;
+ case 'pending':
+ return $query
+ ->setParameter('status', 'pending'); // TODO use const
+ }
+
+ return parent::buildSavedQueryFromBuiltin($query_key);
+ }
+
+ private function getTemplateOptions() {
+ return array('all' => 'All Types') +
+ PhabricatorReleaseTemplate::getTemplatesMap();
+ }
+
+ private function getStateOptions() {
+ return array(
+ 'all' => 'Any status',
+ ) + ReleaseChangeRequest::getStatusMap();
+ }
+
+ protected function getRequiredHandlePHIDsForResultList(
+ array $changes,
+ PhabricatorSavedQuery $query) {
+
+ return mpull($changes, 'getReleasePHID');
+ }
+
+ protected function renderResultList(
+ array $changes,
+ PhabricatorSavedQuery $query,
+ array $handles) {
+
+ assert_instances_of($changes, 'ReleaseChangeRequest');
+ $viewer = $this->requireViewer();
+
+ $list = new PHUIObjectItemListView();
+
+ foreach ($changes as $change) {
+ $name = $change->getTitle();
+
+ $item = id(new PHUIObjectItemView())
+ ->setObjectName($change->getMonogram())
+ ->setHeader($name)
+ ->setHref($change->getURI())
+ ->addAttribute($change->getStatusName())
+ ->addAttribute(pht(
+ 'Release: %s',
+ $viewer->renderHandle($change->getReleasePHID())));
+
+ $list->addItem($item);
+ }
+
+ return id(new PhabricatorApplicationSearchResultView())
+ ->setObjectList($list)
+ ->setNoDataString(pht('No change requests found.'));
+ }
+}
diff --git a/src/applications/release/query/ReleaseChangeRequestTransactionQuery.php b/src/applications/release/query/ReleaseChangeRequestTransactionQuery.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/query/ReleaseChangeRequestTransactionQuery.php
@@ -0,0 +1,9 @@
+<?php
+
+final class ReleaseChangeRequestTransactionQuery
+ extends PhabricatorApplicationTransactionQuery {
+
+ public function getTemplateApplicationTransaction() {
+ return new ReleaseChangeRequestTransaction();
+ }
+}
diff --git a/src/applications/release/storage/ReleaseChangeRequest.php b/src/applications/release/storage/ReleaseChangeRequest.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/storage/ReleaseChangeRequest.php
@@ -0,0 +1,199 @@
+<?php
+
+// TODO subscribers? Real view policy?
+
+final class ReleaseChangeRequest extends PhabricatorReleaseDAO
+ implements
+ PhabricatorApplicationTransactionInterface,
+ PhabricatorPolicyInterface,
+ PhabricatorCustomFieldInterface {
+
+ protected $requestorPHID;
+ protected $releasePHID;
+ protected $status = self::STATUS_PENDING;
+ protected $implementationKey;
+ protected $details;
+ protected $description = ''; // TODO: make this a built-in Custom Field ?
+ // This is a PHID. We'll use RefCursor for Branches now, and maybe doorkeeper
+ // for external things later.
+ protected $requestedObjectPHID;
+
+
+ private $release = self::ATTACHABLE;
+ private $requestedObject = self::ATTACHABLE;
+ private $implementation;
+
+ // In some contexts (like "implement GitHub"), a Change Request can be as
+ // complex as a Revision; For now, hope for the best?
+ const STATUS_PENDING = 'pending';
+ const STATUS_REJECTED = 'rejected';
+ const STATUS_INCLUDED = 'included';
+
+ public function getTitle() {
+ if ($this->requestedObject == self::ATTACHABLE) {
+ return 'TBD'; // TODO just copy this when creating?
+ }
+ return $this->getImplementation()->getTitle($this);
+ }
+
+ public function getChangeAuthorPHID() {
+ if ($this->requestedObject == self::ATTACHABLE) {
+ return 'TBD'; // TODO
+ }
+ return $this->getImplementation()->getChangeAuthorPHID($this);
+ }
+
+ public function getDetail($key, $default = null) {
+ return idx($this->getDetails(), $key, $default);
+ }
+
+ public function setDetail($key, $value) {
+ $this->details[$key] = $value;
+ return $this;
+ }
+
+ public function getURI() {
+ return '/Y'.$this->getID();
+ }
+
+ public function getMonogram() {
+ return 'Y'.$this->getID();
+ }
+
+ protected function getConfiguration() {
+ return array(
+ self::CONFIG_AUX_PHID => true,
+ self::CONFIG_SERIALIZATION => array(
+ 'details' => self::SERIALIZATION_JSON,
+ ),
+ self::CONFIG_COLUMN_SCHEMA => array(
+ 'description' => 'text',
+ 'requestorPHID' => 'phid',
+ 'releasePHID' => 'phid',
+ 'requestedObjectPHID' => 'phid',
+ 'status' => 'text32',
+ 'implementationKey' => 'text32',
+ ),
+ self::CONFIG_KEY_SCHEMA => array(
+ 'key_release' => array(
+ 'columns' => array('releasePHID'),
+ 'unique' => false,
+ ),
+ ),
+ ) + parent::getConfiguration();
+ }
+
+ public function getPHIDType() {
+ return ReleaseChangeRequestPHIDType::TYPECONST;
+ }
+
+ public function attachRelease(ReleaseRelease $release) {
+ $this->release = $release;
+ return $this;
+ }
+ public function getRelease() {
+ return $this->assertAttached($this->release);
+ }
+
+ public function attachRequestedObject($object) {
+ $this->requestedObject = $object;
+ return $this;
+ }
+ public function getRequestedObject() {
+ return $this->assertAttached($this->requestedObject);
+ }
+
+ public function getImplementation() {
+ if (!$this->implementation) {
+ $this->implementation =
+ ReleaseChangeRequestImplementation::getImplementationByKey(
+ $this->getImplementationKey());
+ }
+ return $this->implementation;
+ }
+
+ public static function getStatusMap() {
+ return array(
+ self::STATUS_PENDING => pht('Pending'),
+ self::STATUS_REJECTED => pht('Rejected'),
+ self::STATUS_INCLUDED => pht('Merged'),
+ );
+ }
+
+ public static function translateStatusName($status) {
+ $names = self::getStatusMap();
+ return idx($names, $status, $status);
+ }
+
+ public function getStatusName() {
+ return self::translateStatusName($this->getStatus());
+ }
+
+/* -( PhabricatorApplicationTransactionInterface )------------------------- */
+
+ public function getApplicationTransactionEditor() {
+ return new ReleaseChangeRequestEditor();
+ }
+
+ public function getApplicationTransactionObject() {
+ return $this;
+ }
+
+ public function getApplicationTransactionTemplate() {
+ return new ReleaseChangeRequestTransaction();
+ }
+
+ public function willRenderTimeline(
+ PhabricatorApplicationTransactionView $timeline,
+ AphrontRequest $request) {
+
+ return $timeline;
+ }
+
+
+/* -( PhabricatorPolicyInterface )----------------------------------------- */
+
+ public function getCapabilities() {
+ return array(
+ PhabricatorPolicyCapability::CAN_VIEW,
+ PhabricatorPolicyCapability::CAN_EDIT,
+ );
+ }
+
+ // TODO need to see both release and target object.
+ public function getPolicy($capability) {
+ return $this->getRelease()->getPolicy($capability);
+ }
+
+ public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
+ return $this->getRelease()->hasAutomaticCapability($capability, $viewer);
+ }
+
+ public function describeAutomaticCapability($capability) {
+ return pht(
+ 'Change Requests inherit the policies of the release they belong to.');
+ }
+
+
+/* -( PhabricatorCustomFieldInterface )------------------------------------ */
+
+ private $customFields = self::ATTACHABLE;
+
+ public function getCustomFieldSpecificationForRole($role) {
+ return array();
+ }
+
+ public function getCustomFieldBaseClass() {
+ return 'ReleaseChangeRequestCustomField';
+ }
+
+ public function getCustomFields() {
+ return $this->assertAttached($this->customFields);
+ }
+
+ public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) {
+ $this->customFields = $fields;
+ return $this;
+ }
+
+}
diff --git a/src/applications/release/storage/ReleaseChangeRequestCustomFieldNumericIndex.php b/src/applications/release/storage/ReleaseChangeRequestCustomFieldNumericIndex.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/storage/ReleaseChangeRequestCustomFieldNumericIndex.php
@@ -0,0 +1,10 @@
+<?php
+
+final class ReleaseChangeRequestCustomFieldNumericIndex // TODO db create tables
+ extends PhabricatorCustomFieldNumericIndexStorage {
+
+ public function getApplicationName() {
+ return 'release';
+ }
+
+}
diff --git a/src/applications/release/storage/ReleaseChangeRequestCustomFieldStorage.php b/src/applications/release/storage/ReleaseChangeRequestCustomFieldStorage.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/storage/ReleaseChangeRequestCustomFieldStorage.php
@@ -0,0 +1,10 @@
+<?php
+
+final class ReleaseChangeRequestCustomFieldStorage
+ extends PhabricatorCustomFieldStorage {
+
+ public function getApplicationName() {
+ return 'release';
+ }
+
+}
diff --git a/src/applications/release/storage/ReleaseChangeRequestCustomFieldStringIndex.php b/src/applications/release/storage/ReleaseChangeRequestCustomFieldStringIndex.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/storage/ReleaseChangeRequestCustomFieldStringIndex.php
@@ -0,0 +1,10 @@
+<?php
+
+final class ReleaseChangeRequestCustomFieldStringIndex
+ extends PhabricatorCustomFieldStringIndexStorage {
+
+ public function getApplicationName() {
+ return 'release';
+ }
+
+}
diff --git a/src/applications/release/storage/ReleaseChangeRequestTransaction.php b/src/applications/release/storage/ReleaseChangeRequestTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/storage/ReleaseChangeRequestTransaction.php
@@ -0,0 +1,34 @@
+<?php
+
+final class ReleaseChangeRequestTransaction
+ extends PhabricatorModularTransaction {
+
+ const MAILTAG_OTHER = 'release-change-content';
+
+ public function getApplicationName() {
+ return 'release';
+ }
+
+ public function getApplicationTransactionType() {
+ return ReleaseChangeRequestPHIDType::TYPECONST;
+ }
+
+ public function getBaseTransactionClass() {
+ return 'ReleaseChangeRequestTransactionType';
+ }
+
+ public function getApplicationTransactionCommentObject() {
+ return new ReleaseReleaseTransactionComment();
+ }
+
+ public function getMailTags() {
+ $tags = parent::getMailTags();
+
+ switch ($this->getTransactionType()) {
+ default:
+ $tags[] = self::MAILTAG_OTHER;
+ break;
+ }
+ return $tags;
+ }
+}
diff --git a/src/applications/release/storage/ReleaseRelease.php b/src/applications/release/storage/ReleaseRelease.php
--- a/src/applications/release/storage/ReleaseRelease.php
+++ b/src/applications/release/storage/ReleaseRelease.php
@@ -147,6 +147,14 @@
);
}
+ public function canAcceptChangeRequests() {
+ $states = array(
+ self::STATE_PLANNED => true,
+ self::STATE_TESTING => true,
+ );
+ return idx($states, $this->getState(), false);
+ }
+
public function getCurrentRefs() {
return $this->getDetail(self::DETAIL_CURRENTREF, array());
}
diff --git a/src/applications/release/xaction/ReleaseChangeRequestDescribeTransaction.php b/src/applications/release/xaction/ReleaseChangeRequestDescribeTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/xaction/ReleaseChangeRequestDescribeTransaction.php
@@ -0,0 +1,41 @@
+<?php
+
+final class ReleaseChangeRequestDescribeTransaction
+ extends ReleaseChangeRequestTransactionType {
+
+ const TRANSACTIONTYPE = 'change:describe';
+
+ public function generateOldValue($object) {
+ return $object->getDescription();
+ }
+
+ public function applyInternalEffects($object, $value) {
+ $object->setDescription($value);
+ }
+
+ public function getTitle() {
+ return pht(
+ '%s edited the Request\'s description.',
+ $this->renderAuthor());
+ }
+
+ public function getTitleForFeed() {
+ return pht(
+ '%s edited the description of %s.',
+ $this->renderAuthor(),
+ $this->renderObject());
+ }
+
+ public function hasChangeDetailView() {
+ return ($this->getOldValue() !== null);
+ }
+
+ public function newChangeDetailView() {
+ $viewer = $this->getViewer();
+
+ return id(new PhabricatorApplicationTransactionTextDiffDetailView())
+ ->setViewer($viewer)
+ ->setOldText($this->getOldValue())
+ ->setNewText($this->getNewValue());
+ }
+}
diff --git a/src/applications/release/xaction/ReleaseChangeRequestImplementationTransaction.php b/src/applications/release/xaction/ReleaseChangeRequestImplementationTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/xaction/ReleaseChangeRequestImplementationTransaction.php
@@ -0,0 +1,28 @@
+<?php
+
+class ReleaseChangeRequestImplementationTransaction
+ extends ReleaseChangeRequestTransactionType {
+
+ const TRANSACTIONTYPE = 'change:implkey';
+
+ public function generateOldValue($object) {
+ return $object->getImplementationKey();
+ }
+
+ public function applyInternalEffects($object, $value) {
+ $object->setImplementationKey($value);
+ }
+
+ public function shouldHide() {
+ return true;
+ }
+
+ public function validateTransactions($object, array $xactions) {
+ $errors = $this->validateRequiredFieldTransactions(
+ $object,
+ $xactions,
+ pht('Implementation'));
+
+ return $errors;
+ }
+}
diff --git a/src/applications/release/xaction/ReleaseChangeRequestReleaseTransaction.php b/src/applications/release/xaction/ReleaseChangeRequestReleaseTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/xaction/ReleaseChangeRequestReleaseTransaction.php
@@ -0,0 +1,77 @@
+<?php
+
+class ReleaseChangeRequestReleaseTransaction
+ extends ReleaseChangeRequestTransactionType {
+
+ const TRANSACTIONTYPE = 'change:release';
+
+ public function generateOldValue($object) {
+ return $object->getReleasePHID();
+ }
+
+ public function applyInternalEffects($object, $value) {
+ $object->setReleasePHID($value);
+ }
+
+
+ public function applyExternalEffects($object, $value) {
+ $release_phid = $value;
+ $actor = $this->getActor();
+
+ $release = id(new ReleaseReleaseQuery())
+ ->setViewer($actor)
+ ->withPHIDs(array($release_phid))
+ ->executeOne();
+ $sub_xactions = array(
+ id(new ReleaseReleaseTransaction())
+ ->setTransactionType(
+ ReleaseReleaseChangeRequestAddedTransaction::TRANSACTIONTYPE)
+ ->setNewValue($object->getPHID()),
+ );
+
+ $sub_editor = id(new ReleaseReleaseEditor())
+ ->setActor($actor)
+ ->setContentSource($this->getEditor()->getContentSource())
+ ->setContinueOnNoEffect(true);
+ $sub_editor->applyTransactions($release, $sub_xactions);
+
+ }
+
+ public function shouldHide() {
+ return true;
+ }
+
+ public function validateTransactions($object, array $xactions) {
+ $errors = $this->validateRequiredFieldTransactions(
+ $object,
+ $xactions,
+ pht('Release'));
+
+ $viewer = $this->getActor();
+ foreach ($xactions as $xaction) {
+ $release_phid = $xaction->getNewValue();
+
+ $target_object = id(new ReleaseReleaseQuery())
+ ->setViewer($viewer)
+ ->withPHIDs(array($release_phid))
+ ->setRaisePolicyExceptions(false)
+ ->requireCapabilities(
+ array(
+ PhabricatorPolicyCapability::CAN_VIEW,
+ ))
+ ->executeOne();
+
+ if (!$target_object) {
+ $errors[] = $this->newInvalidError(
+ pht(
+ 'Release "%s" is invalid: the release must exist and you '.
+ 'must have permission to view it in order to create a new Change'.
+ 'Request.',
+ $release_phid),
+ $xaction);
+ }
+ }
+
+ return $errors;
+ }
+}
diff --git a/src/applications/release/xaction/ReleaseChangeRequestRequestedObjectTransaction.php b/src/applications/release/xaction/ReleaseChangeRequestRequestedObjectTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/xaction/ReleaseChangeRequestRequestedObjectTransaction.php
@@ -0,0 +1,54 @@
+<?php
+
+class ReleaseChangeRequestRequestedObjectTransaction
+ extends ReleaseChangeRequestTransactionType {
+
+ const TRANSACTIONTYPE = 'change:requestobject';
+
+ public function generateOldValue($object) {
+ return $object->getRequestedObjectPHID();
+ }
+
+ public function applyInternalEffects($object, $value) {
+ $object->setRequestedObjectPHID($value);
+ }
+
+ public function shouldHide() {
+ return true;
+ }
+
+ public function validateTransactions($object, array $xactions) {
+ $errors = $this->validateRequiredFieldTransactions(
+ $object,
+ $xactions,
+ pht('Requested Object'));
+
+ $viewer = $this->getActor();
+ foreach ($xactions as $xaction) {
+ $object_phid = $xaction->getNewValue();
+
+ $target_object = id(new PhabricatorObjectQuery())
+ ->setViewer($viewer)
+ ->withPHIDs(array($object_phid))
+ ->setRaisePolicyExceptions(false)
+ ->requireCapabilities(
+ array(
+ PhabricatorPolicyCapability::CAN_VIEW,
+ ))
+ ->executeOne();
+
+ if (!$target_object) {
+ $errors[] = $this->newInvalidError(
+ pht(
+ 'Requested object "%s" is invalid: the object must exist and you '.
+ 'must have permission to view it in order to create a new Change'.
+ 'Request.',
+ $object_phid),
+ $xaction);
+ }
+ }
+
+ return $errors;
+ }
+
+}
diff --git a/src/applications/release/xaction/ReleaseChangeRequestRequestorTransaction.php b/src/applications/release/xaction/ReleaseChangeRequestRequestorTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/xaction/ReleaseChangeRequestRequestorTransaction.php
@@ -0,0 +1,24 @@
+<?php
+
+class ReleaseChangeRequestRequestorTransaction
+ extends ReleaseChangeRequestTransactionType {
+
+ const TRANSACTIONTYPE = 'change:requestor';
+
+ public function generateOldValue($object) {
+ return $object->getRequestorPHID();
+ }
+
+ public function applyInternalEffects($object, $value) {
+ $object->setRequestorPHID($value);
+ }
+
+ public function validateTransactions($object, array $xactions) {
+ $errors = $this->validateRequiredFieldTransactions(
+ $object,
+ $xactions,
+ pht('Requestor'));
+
+ return $errors;
+ }
+}
diff --git a/src/applications/release/xaction/ReleaseChangeRequestStateTransaction.php b/src/applications/release/xaction/ReleaseChangeRequestStateTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/xaction/ReleaseChangeRequestStateTransaction.php
@@ -0,0 +1,61 @@
+<?php
+
+class ReleaseChangeRequestStateTransaction // RENAME to "status"
+ extends ReleaseChangeRequestTransactionType {
+
+ const TRANSACTIONTYPE = 'change:status';
+
+ public function generateOldValue($object) {
+ return $object->getStatus();
+ }
+
+ public function applyInternalEffects($object, $value) {
+ $object->setStatus($value);
+ }
+
+
+ /* TODO This was supposed to create a xaction on the Release, but w/o
+ metadata in modular-transactions, showing anything useful there is too
+ hacky. Rethink later.
+ public function applyExternalEffects($object, $value) {
+ $release_phid = $object->getReleasePHID();
+ $actor = $this->getActor();
+
+ $release = id(new ReleaseReleaseQuery())
+ ->setViewer($actor)
+ ->withPHIDs(array($release_phid))
+ ->executeOne();
+ $sub_xactions = array(
+ id(new ReleaseReleaseTransaction())
+ ->setTransactionType(
+ ReleaseReleaseChangeRequestStatusTransaction::TRANSACTIONTYPE)
+ ->setNewValue($value)
+ ->setMetadataValue('changerequest_phid', $object->getPHID())
+ ->setMetadataValue('changerequest:old_status', $object->getStatus()),
+ );
+
+ $sub_editor = id(new ReleaseReleaseEditor())
+ ->setActor($actor)
+ ->setContentSource($this->getEditor()->getContentSource())
+ ->setContinueOnNoEffect(true);
+ $sub_editor->applyTransactions($release, $sub_xactions);
+ } */
+
+ public function getTitle() {
+ $status = ReleaseChangeRequest::translateStatusName($this->getNewValue());
+ return pht(
+ '%s updated the status of this change to %s.',
+ $this->renderAuthor(),
+ $this->renderValue($status));
+ }
+
+ public function getTitleForFeed() {
+ $status = ReleaseChangeRequest::translateStatusName($this->getNewValue());
+ return pht(
+ '%s updated the status of %s to %s.',
+ $this->renderAuthor(),
+ $this->renderObject(),
+ $this->renderValue($status));
+ }
+
+}
diff --git a/src/applications/release/xaction/ReleaseChangeRequestTransactionType.php b/src/applications/release/xaction/ReleaseChangeRequestTransactionType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/xaction/ReleaseChangeRequestTransactionType.php
@@ -0,0 +1,50 @@
+<?php
+
+abstract class ReleaseChangeRequestTransactionType
+ extends PhabricatorModularTransactionType {
+
+ public function getIcon() {
+ return 'fa-compress';
+ }
+
+ // TODO either move down or up the hirarchy
+ // also, probably only works for string/phid types?
+ // TODO There should be a way to get a good message from just
+ // "newRequiredError" and the edit engine.
+ protected function validateRequiredFieldTransactions(
+ $object,
+ array $xactions,
+ $field_name) {
+
+ $errors = array();
+ $old = $this->generateOldValue($object);
+
+ if ($old === null && !$xactions) {
+ $errors[] = $this->newRequiredError(pht('%s is required.', $field_name));
+ }
+
+ foreach ($xactions as $xaction) {
+ $new = $xaction->getNewValue();
+ if (!strlen($new)) {
+ $errors[] = $this->newRequiredError(
+ pht('%s is required.', $field_name));
+ }
+ }
+
+ return $errors;
+ }
+
+ public function newChangeDetailView() {
+ $viewer = $this->getViewer();
+
+ $old = $this->getOldValue();
+ $new = $this->getNewValue();
+
+ $json = new PhutilJSON();
+
+ return id(new PhabricatorApplicationTransactionTextDiffDetailView())
+ ->setViewer($viewer)
+ ->setOldText($old ? $json->encodeFormatted($old) : null)
+ ->setNewText($new ? $json->encodeFormatted($new) : null);
+ }
+}
diff --git a/src/applications/release/xaction/ReleaseReleaseChangeRequestAddedTransaction.php b/src/applications/release/xaction/ReleaseReleaseChangeRequestAddedTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/xaction/ReleaseReleaseChangeRequestAddedTransaction.php
@@ -0,0 +1,28 @@
+<?php
+
+final class ReleaseReleaseChangeRequestAddedTransaction
+extends ReleaseReleaseTransactionType {
+
+ const TRANSACTIONTYPE = 'release:crnew';
+
+ public function generateOldValue($object) {
+ return null;
+ }
+
+ public function getTitle() {
+ $viewer = $this->getViewer();
+ return pht(
+ '%s requested a new change to this release: %s',
+ $this->renderAuthor(),
+ $viewer->renderNewHandle());
+ }
+
+ public function getTitleForFeed() {
+ $viewer = $this->getViewer();
+ return pht(
+ '%s requested changes to release %s: %s',
+ $this->renderAuthor(),
+ $this->renderObject(),
+ $viewer->renderNewHandle());
+ }
+}
diff --git a/src/applications/release/xaction/ReleaseReleaseNameTransaction.php b/src/applications/release/xaction/ReleaseReleaseNameTransaction.php
--- a/src/applications/release/xaction/ReleaseReleaseNameTransaction.php
+++ b/src/applications/release/xaction/ReleaseReleaseNameTransaction.php
@@ -1,6 +1,6 @@
<?php
-final class ReleaseReleaseNameTransaction
+final class ReleaseReleaseNameTransaction// TODO remove phabricator from all the names
extends ReleaseReleaseTransactionType {
const TRANSACTIONTYPE = 'release:name';

File Metadata

Mime Type
text/plain
Expires
Sun, Mar 16, 5:29 PM (2 d, 13 h ago)
Storage Engine
amazon-s3
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
phabricator/secure/hd/ib/xeyoeyuqmmm6j2rh
Default Alt Text
D17020.id41522.diff (88 KB)

Event Timeline