Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F18540992
D17020.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
88 KB
Referenced Files
None
Subscribers
None
D17020.diff
View Options
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
Details
Attached
Mime Type
text/plain
Expires
Sep 8 2025, 8:01 PM (5 w, 6 d ago)
Storage Engine
amazon-s3
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
phabricator/secure/2b/ms/5g3itqkfhbtmnxz5
Default Alt Text
D17020.diff (88 KB)
Attached To
Mode
D17020: Release: start adding changerequests
Attached
Detach File
Event Timeline
Log In to Comment