Page MenuHomePhabricator

D17020.id41026.diff
No OneTemporary

D17020.id41026.diff

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
@@ -4462,6 +4462,30 @@
'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',
+ 'ReleaseChangeRequestCustomField' => 'applications/release/customfield/ReleaseChangeRequestCustomField.php',
+ 'ReleaseChangeRequestCustomFieldNumericIndex' => 'applications/release/storage/ReleaseChangeRequestCustomFieldNumericIndex.php',
+ 'ReleaseChangeRequestCustomFieldStorage' => 'applications/release/storage/ReleaseChangeRequestCustomFieldStorage.php',
+ 'ReleaseChangeRequestCustomFieldStringIndex' => 'applications/release/storage/ReleaseChangeRequestCustomFieldStringIndex.php',
+ 'ReleaseChangeRequestDetailsController' => 'applications/release/controller/ReleaseChangeRequestDetailsController.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',
+ 'ReleaseChangeRequestListController' => 'applications/release/controller/ReleaseChangeRequestListController.php',
+ 'ReleaseChangeRequestMarkStateAction' => 'applications/release/changes/actions/ReleaseChangeRequestMarkStateAction.php',
+ 'ReleaseChangeRequestPHIDType' => 'applications/release/phid/ReleaseChangeRequestPHIDType.php',
+ 'ReleaseChangeRequestQuery' => 'applications/release/query/ReleaseChangeRequestQuery.php',
+ 'ReleaseChangeRequestRevisionImplementation' => 'applications/release/changes/ReleaseChangeRequestRevisionImplementation.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',
'ReleaseCustomField' => 'applications/release/customfield/ReleaseCustomField.php',
'ReleaseCustomFieldNumericIndex' => 'applications/release/storage/ReleaseCustomFieldNumericIndex.php',
'ReleaseCustomFieldStorage' => 'applications/release/storage/ReleaseCustomFieldStorage.php',
@@ -4469,6 +4493,7 @@
'ReleaseReleaseEditController' => 'applications/release/controller/ReleaseReleaseEditController.php',
'ReleaseReleaseListController' => 'applications/release/controller/ReleaseReleaseListController.php',
'ReleaseReleaseViewController' => 'applications/release/controller/ReleaseReleaseViewController.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',
@@ -4566,6 +4591,7 @@
'SlowvoteRemarkupRule' => 'applications/slowvote/remarkup/SlowvoteRemarkupRule.php',
'SubscriptionListDialogBuilder' => 'applications/subscriptions/view/SubscriptionListDialogBuilder.php',
'SubscriptionListStringBuilder' => 'applications/subscriptions/view/SubscriptionListStringBuilder.php',
+ 'TestTemplate' => 'applications/release/TestTemplate.php',
'TokenConduitAPIMethod' => 'applications/tokens/conduit/TokenConduitAPIMethod.php',
'TokenGiveConduitAPIMethod' => 'applications/tokens/conduit/TokenGiveConduitAPIMethod.php',
'TokenGivenConduitAPIMethod' => 'applications/tokens/conduit/TokenGivenConduitAPIMethod.php',
@@ -9865,6 +9891,35 @@
'ProjectReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
'ProjectSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'QueryFormattingTestCase' => 'PhabricatorTestCase',
+ 'ReleaseChangeRequest' => array(
+ 'PhabricatorReleaseDAO',
+ 'PhabricatorApplicationTransactionInterface',
+ 'PhabricatorPolicyInterface',
+ 'PhabricatorCustomFieldInterface',
+ ),
+ 'ReleaseChangeRequestAction' => 'Phobject',
+ 'ReleaseChangeRequestActionController' => 'PhabricatorController',
+ 'ReleaseChangeRequestCommitImplementation' => 'Phobject',
+ 'ReleaseChangeRequestCustomField' => 'PhabricatorCustomField',
+ 'ReleaseChangeRequestCustomFieldNumericIndex' => 'PhabricatorCustomFieldNumericIndexStorage',
+ 'ReleaseChangeRequestCustomFieldStorage' => 'PhabricatorCustomFieldStorage',
+ 'ReleaseChangeRequestCustomFieldStringIndex' => 'PhabricatorCustomFieldStringIndexStorage',
+ 'ReleaseChangeRequestDetailsController' => 'PhabricatorController',
+ 'ReleaseChangeRequestEditController' => 'PhabricatorController',
+ 'ReleaseChangeRequestEditEngine' => 'PhabricatorEditEngine',
+ 'ReleaseChangeRequestEditor' => 'PhabricatorApplicationTransactionEditor',
+ 'ReleaseChangeRequestFromRevisionController' => 'PhabricatorController',
+ 'ReleaseChangeRequestImplementation' => 'Phobject',
+ 'ReleaseChangeRequestListController' => 'PhabricatorController',
+ 'ReleaseChangeRequestMarkStateAction' => 'ReleaseChangeRequestAction',
+ 'ReleaseChangeRequestPHIDType' => 'PhabricatorPHIDType',
+ 'ReleaseChangeRequestQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
+ 'ReleaseChangeRequestRevisionImplementation' => 'ReleaseChangeRequestImplementation',
+ 'ReleaseChangeRequestSearchEngine' => 'PhabricatorApplicationSearchEngine',
+ 'ReleaseChangeRequestStateTransaction' => 'ReleaseChangeRequestTransactionType',
+ 'ReleaseChangeRequestTransaction' => 'PhabricatorModularTransaction',
+ 'ReleaseChangeRequestTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
+ 'ReleaseChangeRequestTransactionType' => 'PhabricatorModularTransactionType',
'ReleaseCustomField' => 'PhabricatorCustomField',
'ReleaseCustomFieldNumericIndex' => 'PhabricatorCustomFieldNumericIndexStorage',
'ReleaseCustomFieldStorage' => 'PhabricatorCustomFieldStorage',
@@ -9872,6 +9927,7 @@
'ReleaseReleaseEditController' => 'PhabricatorController',
'ReleaseReleaseListController' => 'PhabricatorController',
'ReleaseReleaseViewController' => 'PhabricatorController',
+ 'ReleaseRenderEventListener' => 'PhabricatorEventListener',
'ReleephAuthorFieldSpecification' => 'ReleephFieldSpecification',
'ReleephBranch' => array(
'ReleephDAO',
@@ -9985,6 +10041,7 @@
'SlowvoteRemarkupRule' => 'PhabricatorObjectRemarkupRule',
'SubscriptionListDialogBuilder' => 'Phobject',
'SubscriptionListStringBuilder' => 'Phobject',
+ 'TestTemplate' => 'PhabricatorReleaseTemplate',
'TokenConduitAPIMethod' => 'ConduitAPIMethod',
'TokenGiveConduitAPIMethod' => 'TokenConduitAPIMethod',
'TokenGivenConduitAPIMethod' => 'TokenConduitAPIMethod',
diff --git a/src/applications/meta/controller/PhabricatorApplicationUninstallController.php b/src/applications/meta/controller/PhabricatorApplicationUninstallController.php
--- a/src/applications/meta/controller/PhabricatorApplicationUninstallController.php
+++ b/src/applications/meta/controller/PhabricatorApplicationUninstallController.php
@@ -44,7 +44,7 @@
phutil_tag('tt', array(), 'phabricator.show-prototypes')));
return id(new AphrontDialogResponse())->setDialog($dialog);
}
-
+phlog($view_uri);
if ($request->isDialogFormPost()) {
$this->manageApplication();
return id(new AphrontRedirectResponse())->setURI($view_uri);
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/TestTemplate.php b/src/applications/release/TestTemplate.php
--- a/src/applications/release/TestTemplate.php
+++ b/src/applications/release/TestTemplate.php
@@ -6,4 +6,4 @@
public function validateRepositories(array $repositories) {
}
-}
\ No newline at end of file
+}
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*)' => 'ReleaseReleaseViewController',
+ '/X(?P<id>[1-9]\d*)/?' => 'ReleaseReleaseViewController',
+ '/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,39 @@
+<?php
+
+abstract class ReleaseChangeRequestImplementation extends Phobject {
+
+ public abstract function getTitle(ReleaseChangeRequest $request);
+
+
+ final public function getImplementationKey() {
+ return $this->getPhobjectClassConstant('IMPLEMENTATION_KEY');
+ }
+
+ // TODO does this need caching if we load lots of requests?
+ public static function getImplementationByKey($key) {
+ $all = id(new PhutilClassMapQuery())
+ ->setAncestorClass(__CLASS__)
+ ->setUniqueMethod('getImplementationKey')
+ ->execute();
+ $implementation = idx($all, $key);
+ if (!$implementation) {
+ throw new Exception(pht('Implementation not found for key %s', $key));
+ }
+ return $implementation;
+ }
+
+ public function createRequest() {
+ return id(new ReleaseChangeRequest())
+ ->setImplementationKey($this->getImplementationKey())
+ ->setRequestedObjectPHID($this->getRequestReference());
+ }
+
+
+ /**
+ * Return the value to use for the requestReference field in the Request.
+ */
+ protected abstract function getRequestReference();
+
+
+
+}
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,25 @@
+<?php
+
+/**
+ * A Change Request implementation for landing a Revision.
+ */
+final class ReleaseChangeRequestRevisionImplementation
+ extends ReleaseChangeRequestImplementation {
+
+ const IMPLEMENTATION_KEY = 'revision';
+
+ private $revisionPHID;
+
+ public function setRevisionPHID($phid) {
+ $this->revisionPHID = $phid;
+ return $this;
+ }
+
+ public function getTitle(ReleaseChangeRequest $request) {
+ return $request->getRequestedObject()->getTitle();
+ }
+
+ protected function getRequestReference() {
+ return $this->revisionPHID;
+ }
+}
diff --git a/src/applications/release/changes/actions/ReleaseChangeRequestAction.php b/src/applications/release/changes/actions/ReleaseChangeRequestAction.php
new file mode 100755
--- /dev/null
+++ b/src/applications/release/changes/actions/ReleaseChangeRequestAction.php
@@ -0,0 +1,62 @@
+<?php
+
+abstract class ReleaseChangeRequestAction extends Phobject {
+
+ public abstract function getActionName();
+ public abstract function getActionIcon();
+ public abstract function getActionKey();
+ /**
+ * Actually do work. Should return a URI to redirect to.
+ */
+ public abstract function act(
+ ReleaseChangeRequest $change,
+ AphrontRequest $request);
+
+ /**
+ * return prompt to show the user in the confirmation dialog for this action.
+ */
+ public abstract function getPrompt(ReleaseChangeRequest $change);
+ /**
+ * return Title for the confirmation dialog for this action.
+ */
+ public abstract function getFormTitle(ReleaseChangeRequest $change);
+
+ public function isEnabledForRequest(ReleaseChangeRequest $change) {
+ return true;
+ }
+
+ public function assertPolicy(
+ PhabricatorUser $user,
+ ReleaseChangeRequest $change) {
+ // TBD
+ // 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/ReleaseChangeRequestMarkStateAction.php b/src/applications/release/changes/actions/ReleaseChangeRequestMarkStateAction.php
new file mode 100755
--- /dev/null
+++ b/src/applications/release/changes/actions/ReleaseChangeRequestMarkStateAction.php
@@ -0,0 +1,98 @@
+<?php
+
+final class ReleaseChangeRequestMarkStateAction
+ extends ReleaseChangeRequestAction {
+
+ private $name;
+ private $icon;
+ private $key;
+
+ const ACTION_REJECT = 'reject';
+ const ACTION_MARK_MERGED = 'markmerged';
+
+ public function getPrompt(ReleaseChangeRequest $change) {
+ return pht('Set this Change Request as %s?', ReleaseChangeRequest::translateStatusName($this->getChangeStatus()));
+ }
+ public function getFormTitle(ReleaseChangeRequest $change) {
+ return pht('Mark as %s', ReleaseChangeRequest::translateStatusName($this->getChangeStatus()));
+ }
+
+ public function act(ReleaseChangeRequest $change, AphrontRequest $request) {
+ $viewer = $request->getViewer();
+ $xaction_type = ReleaseChangeRequestStateTransaction::TRANSACTIONTYPE;
+ $status = $this->getChangeStatus();
+
+ $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();
+ }
+
+ private function getChangeStatus() {
+ $statuses = array(
+ self::ACTION_REJECT => ReleaseChangeRequest::STATUS_REJECTED,
+ self::ACTION_MARK_MERGED => ReleaseChangeRequest::STATUS_INCLUDED,
+ );
+ $status = idx($statuses, $this->key);
+ if ($status) {
+ return $status;
+ }
+ throw new Exception(
+ pht('Status not found for action key %s', $action_key));
+ }
+
+ public function setActionName($name) {
+ $this->name = $name;
+ return $this;
+ }
+ public function setActionIcon($icon) {
+ $this->icon = $icon;
+ return $this;
+ }
+ public function setActionKey($action_key) {
+ $this->key = $action_key;
+ return $this;
+ }
+
+ public function getActionName() {
+ return $this->name;
+ }
+ public function getActionIcon() {
+ return $this->icon;
+ }
+ public function getActionKey() {
+ return $this->key;
+ }
+
+ public function generateActions() {
+ return array(
+ id(new ReleaseChangeRequestMarkStateAction())
+ ->setActionName(pht('Reject'))
+ ->setActionKey(self::ACTION_REJECT)
+ ->setActionIcon('fa-times'),
+
+
+ // We should try to detect commits like with Revisions, and
+ // automatically mark a Change as Merged, but I'm not sure how complex
+ // that would be (And how scalable across Implmentations).
+ // Until then, I'd like to hide this option locally, and use my hack for
+ // T182 to do the merge.
+ // Actually, I think I'm just going to delete this, and offer it as an
+ // Extension for the happy few who want to try it. Preview FTW :-)
+ // Also replace the rest of the class with a non-generic ...RejectAction
+ id(new ReleaseChangeRequestMarkStateAction())
+ ->setActionName(pht('Mark as Merged'))
+ ->setActionKey(self::ACTION_MARK_MERGED)
+ ->setActionIcon('fa-check'),
+ );
+ }
+
+}
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,50 @@
+<?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 ($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);
+
+ return $this->newDialog()
+ ->setSubmitURI($request->getRequestURI())
+ ->setTitle($action->getFormTitle($change))
+ ->appendChild($prompt)
+ ->setErrors($errors)
+ ->addSubmitButton(pht('Submit'))
+ ->addCancelButton('#');
+ }
+}
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,149 @@
+<?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,
+ // $comment_view, // Should we have comments on each Request?
+ ))
+ ->addPropertySection($release->getName(), $release_properties)
+ ->addPropertySection(pht('Change Request'), $change_properties);
+
+
+ // release information (lite)
+ // request properties
+ // message
+ // actions
+ // timeline
+
+ // crumbs = release crumbs + this change.
+ $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();
+
+ $requestor = $viewer->renderHandle($change->getRequestorPHID())
+ ->setShowHovercard(true);
+ $status = $change->getStatus(); // houmanize
+
+ $properties = id(new PHUIPropertyListView())
+ ->addProperty(pht('Type'), $change->getImplementationKey()) // humanize
+ ->addProperty(pht('Requested By'), $requestor)
+ ->addProperty(pht('Status'), $status);
+// author - implementation specific!
+
+
+ $description = $change->getDescription();
+ if (strlen($description)) {
+ $properties
+ ->addSectionHeader('Description')
+ ->addTextContent(
+ new PHUIRemarkupView($this->getViewer(), $description));
+ }
+
+ // TODO custom fields
+
+ 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();
+ // TODO these actions should be extensible.
+
+
+ $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)));
+ }
+
+
+ // mark merged
+ // rejected
+
+ 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,118 @@
+<?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 PhabricatorReleaseReleaseQuery())
+ ->setViewer($viewer)
+ ->withPHIDs($v_release)
+ ->executeOne();
+
+ // if (!$release->canAcceptChangeRequests()) { // TODO
+ // $e_release = pht('Invalid');
+ // $errors[] =
+ // pht('This release can not accept cany change requests at this time.');
+ // }
+ }
+
+ $v_message = $request->getStr('message');
+
+ $actor_phid = $viewer->getPHID();
+ $revision_id = $revision->getID();
+
+ if (!$errors) {
+
+ $implementation = id(new ReleaseChangeRequestRevisionImplementation())
+ ->setRevisionPHID($revision_phid);
+ $change_request = $implementation->createRequest()
+ ->setRequestorPHID($actor_phid)
+ ->setReleasePHID($release->getPHID())
+ ->setDescription($v_message);
+
+ $xactions = array(
+ id(new ReleaseChangeRequestTransaction())
+ ->setTransactionType(PhabricatorTransactions::TYPE_CREATE),
+ );
+ $editor = id(new ReleaseChangeRequestEditor())
+ ->setActor($viewer)
+ ->setContentSource(
+ PhabricatorContentSource::newFromRequest($request));
+ $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 PhabricatorReleaseReleaseDatasource());
+
+ $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,40 @@
+<?php
+
+final class ReleaseChangeRequestListController extends PhabricatorController {
+
+ public function handleRequest(AphrontRequest $request) {
+ $viewer = $this->getViewer();
+
+ $crumbs = $this->buildApplicationCrumbs();
+ $crumbs->addTextCrumb('Change Requests');
+
+ $querykey = $request->getURIData('queryKey');
+
+ $release_id = $request->getURIData('id');
+ if ($release_id) {
+ phlog('dd');
+ }
+
+ $controller = id(new PhabricatorApplicationSearchController())
+ ->setQueryKey($querykey)
+ ->setSearchEngine(new ReleaseChangeRequestSearchEngine())
+ ->setNavigation($this->buildSideNavView());
+ return $this->delegateToController($controller);
+ }
+
+ public function buildSideNavView() {
+ $viewer = $this->getViewer();
+
+ $nav = new AphrontSideNavFilterView();
+ $nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
+
+ id(new ReleaseChangeRequestSearchEngine())
+ ->setViewer($viewer)
+ ->addNavigationItems($nav->getMenu());
+
+ $nav->selectFilter(null);
+
+ return $nav;
+ }
+
+}
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 PhabricatorReleaseReleaseEditEngine())
->setController($this);
- $id = $request->getURIData('id');
- if (!$id) {
- // ??
- }
-
return $engine->buildResponse();
}
diff --git a/src/applications/release/controller/ReleaseReleaseViewController.php b/src/applications/release/controller/ReleaseReleaseViewController.php
--- a/src/applications/release/controller/ReleaseReleaseViewController.php
+++ b/src/applications/release/controller/ReleaseReleaseViewController.php
@@ -63,6 +63,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)
@@ -72,7 +84,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)
@@ -87,6 +100,46 @@
));
}
+ 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/customfield/ReleaseChangeRequestCustomField.php b/src/applications/release/customfield/ReleaseChangeRequestCustomField.php
new file mode 100755
--- /dev/null
+++ b/src/applications/release/customfield/ReleaseChangeRequestCustomField.php
@@ -0,0 +1,24 @@
+<?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,99 @@
+<?php
+
+final class ReleaseChangeRequestEditEngine
+ extends PhabricatorEditEngine {
+
+ const ENGINECONST = 'release.change';
+
+ public function getEngineName() {
+ return pht('Release Change Requests');
+ }
+
+ public function isEngineConfigurable() {
+ // This hides it from the quick-create menu, because the built-in form
+ // doesn't actually do anything.
+ 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(
+ PhabricatorReleaseReleaseDefaultEditCapability::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();
+
+ $is_edit_form = !$this->getIsCreate();
+
+ return array(
+ id(new PhabricatorSelectEditField())
+ ->setKey('state')
+ ->setLabel(pht('State'))
+ ->setTransactionType(
+ ReleaseChangeRequestStateTransaction::TRANSACTIONTYPE)
+ ->setIsCopyable(true)
+ ->setOptions($states)
+ ->setValue($object->getStatus()),
+ );
+ }
+
+}
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 PhabricatorReleaseReleaseReplyHandler())
+ ->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,205 @@
+<?php
+
+final class ReleaseChangeRequestQuery
+ extends PhabricatorCursorPagedPolicyAwareQuery {
+
+ private $ids;
+ private $phids;
+ private $releasePHIDs;
+ private $statuses;
+ private $implementationKeys;
+ private $datasourceQuery;
+
+ private $needReleases;
+ private $needRequestObjects;
+
+ 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;
+ return $this;
+ }
+
+ public function needRequestObjects($need_request_objects) {
+ $this->needRequestObjects = $need_request_objects;
+ 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 didFilterPage(array $changes) {
+ $viewer = $this->getViewer();
+
+ if ($this->needReleases) {
+ $release_phids = mpull($changes, 'getReleasePHID');
+ $releases = id(new PhabricatorReleaseReleaseQuery())
+ ->setViewer($viewer)
+ ->setParentQuery($this)
+ ->withPHIDs($release_phids)
+ ->execute();
+ $releases = mpull($releases, null, 'getPHID');
+
+ foreach ($changes as $change) {
+ $change->attachRelease(idx($releases, $change->getReleasePHID()));
+ }
+ }
+
+ if ($this->needRequestObjects) {
+ $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 PhabricatorReleaseReleaseDatasource()),
+ 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,200 @@
+<?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;
+ // 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 setDescription($description) {
+ // TODO: Description as a built-in Custom Field ?
+ return $this;
+ }
+ public function getDescription() {
+ // TODO: Description as a built-in Custom Field ?
+ return 'All the good things come to an end.';
+ }
+
+ public function getTitle() {
+ if ($this->requestedObject == self::ATTACHABLE) {
+ return 'TBD';
+ }
+ return $this->getImplementation()->getTitle($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(
+ '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(PhabricatorReleaseRelease $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) {
+ switch ($capability) {
+ case PhabricatorPolicyCapability::CAN_VIEW:
+ case PhabricatorPolicyCapability::CAN_EDIT: // TODO
+ return PhabricatorPolicies::POLICY_USER;
+ }
+ }
+
+ public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
+ return false;
+ }
+
+
+/* -( PhabricatorCustomFieldInterface )------------------------------------ */
+
+ private $customFields = self::ATTACHABLE;
+
+ public function getCustomFieldSpecificationForRole($role) {
+ return array(); // TODO
+ // return PhabricatorEnv::getEnvConfig(<<<'application.fields'>>>);
+ }
+
+ 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 PhabricatorReleaseReleaseTransactionComment();
+ }
+
+ public function getMailTags() {
+ $tags = parent::getMailTags();
+
+ switch ($this->getTransactionType()) {
+ default:
+ $tags[] = self::MAILTAG_OTHER;
+ break;
+ }
+ return $tags;
+ }
+}
diff --git a/src/applications/release/xaction/ReleaseChangeRequestStateTransaction.php b/src/applications/release/xaction/ReleaseChangeRequestStateTransaction.php
new file mode 100755
--- /dev/null
+++ b/src/applications/release/xaction/ReleaseChangeRequestStateTransaction.php
@@ -0,0 +1,39 @@
+<?php
+
+class ReleaseChangeRequestStateTransaction
+ extends ReleaseChangeRequestTransactionType {
+
+ const TRANSACTIONTYPE = 'change:status';
+
+ public function generateOldValue($object) {
+ return $object->getStatus();
+ }
+
+ public function applyInternalEffects($object, $value) {
+ $object->setStatus($value);
+ }
+
+ public function shouldHide() {
+ $old = $this->getOldValue();
+
+ if (!strlen($old)) {
+ return true;
+ }
+ }
+
+ public function getTitle() {
+ return pht(
+ '%s updated the status of this change to "%s".',
+ $this->renderAuthor(),
+ ReleaseChangeRequest::translateStatusName($this->getNewValue()));
+ }
+
+ public function getTitleForFeed() {
+ return pht(
+ '%s updated the status of %s to "%s".',
+ $this->renderAuthor(),
+ $this->renderObject(),
+ ReleaseChangeRequest::translateStatusName($this->getNewValue()));
+ }
+
+}
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,28 @@
+<?php
+
+abstract class ReleaseChangeRequestTransactionType
+ extends PhabricatorModularTransactionType {
+
+ public function getIcon() {
+ return 'fa-compress';
+ }
+
+ // TODO move to parent class
+ public function expandTransaction($object) {
+ return array();
+ }
+
+ 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);
+ }
+}

File Metadata

Mime Type
text/plain
Expires
Mon, Mar 10, 2:36 PM (1 d, 17 h ago)
Storage Engine
amazon-s3
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
phabricator/secure/rh/uk/df6olsex4hvbsgkm
Default Alt Text
D17020.id41026.diff (63 KB)

Event Timeline