mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-19 20:10:55 +01:00
Drive modular task relationships through a new "relationships" controller
Summary: Ref T11179. This is basically a "pro" controller to replace the SearchAttach controller. It does basically the same stuff, just in a (mostly) more modern and modular way. Test Plan: - Added and removed mocks. - Added and removed revisions. - Everything worked just like it did before. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11179 Differential Revision: https://secure.phabricator.com/D16163
This commit is contained in:
parent
bf62badfda
commit
b5d90b4714
8 changed files with 266 additions and 18 deletions
|
@ -3394,6 +3394,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorSearchOrderController' => 'applications/search/controller/PhabricatorSearchOrderController.php',
|
||||
'PhabricatorSearchOrderField' => 'applications/search/field/PhabricatorSearchOrderField.php',
|
||||
'PhabricatorSearchRelationship' => 'applications/search/constants/PhabricatorSearchRelationship.php',
|
||||
'PhabricatorSearchRelationshipController' => 'applications/search/controller/PhabricatorSearchRelationshipController.php',
|
||||
'PhabricatorSearchResultBucket' => 'applications/search/buckets/PhabricatorSearchResultBucket.php',
|
||||
'PhabricatorSearchResultBucketGroup' => 'applications/search/buckets/PhabricatorSearchResultBucketGroup.php',
|
||||
'PhabricatorSearchResultView' => 'applications/search/view/PhabricatorSearchResultView.php',
|
||||
|
@ -8213,6 +8214,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorSearchOrderController' => 'PhabricatorSearchBaseController',
|
||||
'PhabricatorSearchOrderField' => 'PhabricatorSearchField',
|
||||
'PhabricatorSearchRelationship' => 'Phobject',
|
||||
'PhabricatorSearchRelationshipController' => 'PhabricatorSearchBaseController',
|
||||
'PhabricatorSearchResultBucket' => 'Phobject',
|
||||
'PhabricatorSearchResultBucketGroup' => 'Phobject',
|
||||
'PhabricatorSearchResultView' => 'AphrontView',
|
||||
|
|
|
@ -24,4 +24,20 @@ final class ManiphestTaskHasCommitRelationship
|
|||
return false;
|
||||
}
|
||||
|
||||
public function canRelateObjects($src, $dst) {
|
||||
return ($dst instanceof PhabricatorRepositoryCommit);
|
||||
}
|
||||
|
||||
public function getDialogTitleText() {
|
||||
return pht('Edit Related Commits');
|
||||
}
|
||||
|
||||
public function getDialogHeaderText() {
|
||||
return pht('Current Commits');
|
||||
}
|
||||
|
||||
public function getDialogButtonText() {
|
||||
return pht('Save Related Commits');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,4 +17,20 @@ final class ManiphestTaskHasMockRelationship
|
|||
return 'fa-camera-retro';
|
||||
}
|
||||
|
||||
public function canRelateObjects($src, $dst) {
|
||||
return ($dst instanceof PholioMock);
|
||||
}
|
||||
|
||||
public function getDialogTitleText() {
|
||||
return pht('Edit Related Mocks');
|
||||
}
|
||||
|
||||
public function getDialogHeaderText() {
|
||||
return pht('Current Mocks');
|
||||
}
|
||||
|
||||
public function getDialogButtonText() {
|
||||
return pht('Save Related Mocks');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,4 +17,20 @@ final class ManiphestTaskHasRevisionRelationship
|
|||
return 'fa-cog';
|
||||
}
|
||||
|
||||
public function canRelateObjects($src, $dst) {
|
||||
return ($dst instanceof DifferentialRevision);
|
||||
}
|
||||
|
||||
public function getDialogTitleText() {
|
||||
return pht('Edit Related Revisions');
|
||||
}
|
||||
|
||||
public function getDialogHeaderText() {
|
||||
return pht('Current Revisions');
|
||||
}
|
||||
|
||||
public function getDialogButtonText() {
|
||||
return pht('Save Related Revisions');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -41,6 +41,8 @@ final class PhabricatorSearchApplication extends PhabricatorApplication {
|
|||
'delete/(?P<queryKey>[^/]+)/(?P<engine>[^/]+)/'
|
||||
=> 'PhabricatorSearchDeleteController',
|
||||
'order/(?P<engine>[^/]+)/' => 'PhabricatorSearchOrderController',
|
||||
'rel/(?P<relationshipKey>[^/]+)/(?P<sourcePHID>[^/]+)/'
|
||||
=> 'PhabricatorSearchRelationshipController',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,198 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorSearchRelationshipController
|
||||
extends PhabricatorSearchBaseController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$phid = $request->getURIData('sourcePHID');
|
||||
$object = id(new PhabricatorObjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($phid))
|
||||
->requireCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
))
|
||||
->executeOne();
|
||||
if (!$object) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$list = PhabricatorObjectRelationshipList::newForObject(
|
||||
$viewer,
|
||||
$object);
|
||||
|
||||
$relationship_key = $request->getURIData('relationshipKey');
|
||||
$relationship = $list->getRelationship($relationship_key);
|
||||
if (!$relationship) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$src_phid = $object->getPHID();
|
||||
$edge_type = $relationship->getEdgeConstant();
|
||||
|
||||
$dst_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
|
||||
$src_phid,
|
||||
$edge_type);
|
||||
|
||||
$all_phids = $dst_phids;
|
||||
$all_phids[] = $src_phid;
|
||||
|
||||
$handles = $viewer->loadHandles($all_phids);
|
||||
$src_handle = $handles[$src_phid];
|
||||
|
||||
$done_uri = $src_handle->getURI();
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
$phids = explode(';', $request->getStr('phids'));
|
||||
$phids = array_filter($phids);
|
||||
$phids = array_values($phids);
|
||||
|
||||
// TODO: Embed these in the form instead, to gracefully resolve
|
||||
// concurrent edits like we do for subscribers and projects.
|
||||
$old_phids = $dst_phids;
|
||||
|
||||
$add_phids = $phids;
|
||||
$rem_phids = array_diff($old_phids, $add_phids);
|
||||
|
||||
if ($add_phids) {
|
||||
$dst_objects = id(new PhabricatorObjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs($phids)
|
||||
->setRaisePolicyExceptions(true)
|
||||
->execute();
|
||||
$dst_objects = mpull($dst_objects, null, 'getPHID');
|
||||
} else {
|
||||
$dst_objects = array();
|
||||
}
|
||||
|
||||
try {
|
||||
foreach ($add_phids as $add_phid) {
|
||||
$dst_object = idx($dst_objects, $add_phid);
|
||||
if (!$dst_object) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'You can not create a relationship to object "%s" because '.
|
||||
'the object does not exist or could not be loaded.',
|
||||
$add_phid));
|
||||
}
|
||||
|
||||
if (!$relationship->canRelateObjects($object, $dst_object)) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'You can not create a relationship (of type "%s") to object '.
|
||||
'"%s" because it is not the right type of object for this '.
|
||||
'relationship.',
|
||||
$relationship->getRelationshipConstant(),
|
||||
$add_phid));
|
||||
}
|
||||
}
|
||||
} catch (Exception $ex) {
|
||||
return $this->newUnrelatableObjectResponse($ex, $done_uri);
|
||||
}
|
||||
|
||||
$editor = $object->getApplicationTransactionEditor()
|
||||
->setActor($viewer)
|
||||
->setContentSourceFromRequest($request)
|
||||
->setContinueOnMissingFields(true)
|
||||
->setContinueOnNoEffect(true);
|
||||
|
||||
$xactions = array();
|
||||
$xactions[] = $object->getApplicationTransactionTemplate()
|
||||
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
|
||||
->setMetadataValue('edge:type', $edge_type)
|
||||
->setNewValue(array(
|
||||
'+' => array_fuse($add_phids),
|
||||
'-' => array_fuse($rem_phids),
|
||||
));
|
||||
|
||||
try {
|
||||
$editor->applyTransactions($object, $xactions);
|
||||
|
||||
return id(new AphrontRedirectResponse())->setURI($done_uri);
|
||||
} catch (PhabricatorEdgeCycleException $ex) {
|
||||
return $this->newGraphCycleResponse($ex, $done_uri);
|
||||
}
|
||||
}
|
||||
|
||||
$handles = iterator_to_array($handles);
|
||||
$handles = array_select_keys($handles, $dst_phids);
|
||||
|
||||
// TODO: These are hard-coded for now.
|
||||
$filters = array(
|
||||
'assigned' => pht('Assigned to Me'),
|
||||
'created' => pht('Created By Me'),
|
||||
'open' => pht('All Open Objects'),
|
||||
'all' => pht('All Objects'),
|
||||
);
|
||||
|
||||
$dialog_title = $relationship->getDialogTitleText();
|
||||
$dialog_header = $relationship->getDialogHeaderText();
|
||||
$dialog_button = $relationship->getDialogButtonText();
|
||||
$dialog_instructions = $relationship->getDialogInstructionsText();
|
||||
|
||||
// TODO: Remove this, this is just legacy support.
|
||||
$legacy_kinds = array(
|
||||
ManiphestTaskHasCommitEdgeType::EDGECONST => 'CMIT',
|
||||
ManiphestTaskHasMockEdgeType::EDGECONST => 'MOCK',
|
||||
ManiphestTaskHasRevisionEdgeType::EDGECONST => 'DREV',
|
||||
);
|
||||
|
||||
$edge_type = $relationship->getEdgeConstant();
|
||||
$legacy_kind = idx($legacy_kinds, $edge_type);
|
||||
if (!$legacy_kind) {
|
||||
throw new Exception(
|
||||
pht('Only specific legacy relationships are supported!'));
|
||||
}
|
||||
|
||||
return id(new PhabricatorObjectSelectorDialog())
|
||||
->setUser($viewer)
|
||||
->setHandles($handles)
|
||||
->setFilters($filters)
|
||||
->setSelectedFilter('created')
|
||||
->setExcluded($phid)
|
||||
->setCancelURI($done_uri)
|
||||
->setSearchURI("/search/select/{$legacy_kind}/edge/")
|
||||
->setTitle($dialog_title)
|
||||
->setHeader($dialog_header)
|
||||
->setButtonText($dialog_button)
|
||||
->setInstructions($dialog_instructions)
|
||||
->buildDialog();
|
||||
}
|
||||
|
||||
private function newGraphCycleResponse(
|
||||
PhabricatorEdgeCycleException $ex,
|
||||
$done_uri) {
|
||||
|
||||
$viewer = $this->getViewer();
|
||||
$cycle = $ex->getCycle();
|
||||
|
||||
$handles = $this->loadViewerHandles($cycle);
|
||||
$names = array();
|
||||
foreach ($cycle as $cycle_phid) {
|
||||
$names[] = $handles[$cycle_phid]->getFullName();
|
||||
}
|
||||
|
||||
$message = pht(
|
||||
'You can not create that relationship because it would create a '.
|
||||
'circular dependency: %s.',
|
||||
implode(" \xE2\x86\x92 ", $names));
|
||||
|
||||
return $this->newDialog()
|
||||
->setTitle(pht('Circular Dependency'))
|
||||
->appendParagraph($message)
|
||||
->addCancelButton($done_uri);
|
||||
}
|
||||
|
||||
private function newUnrelatableObjectResponse(Exception $ex, $done_uri) {
|
||||
$message = $ex->getMessage();
|
||||
|
||||
return $this->newDialog()
|
||||
->setTitle(pht('Invalid Relationship'))
|
||||
->appendParagraph($message)
|
||||
->addCancelButton($done_uri);
|
||||
}
|
||||
|
||||
}
|
|
@ -24,6 +24,16 @@ abstract class PhabricatorObjectRelationship extends Phobject {
|
|||
abstract protected function getActionName();
|
||||
abstract protected function getActionIcon();
|
||||
|
||||
abstract public function canRelateObjects($src, $dst);
|
||||
|
||||
abstract public function getDialogTitleText();
|
||||
abstract public function getDialogHeaderText();
|
||||
abstract public function getDialogButtonText();
|
||||
|
||||
public function getDialogInstructionsText() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public function shouldAppearInActionMenu() {
|
||||
return true;
|
||||
}
|
||||
|
@ -58,24 +68,8 @@ abstract class PhabricatorObjectRelationship extends Phobject {
|
|||
|
||||
private function getActionURI($object) {
|
||||
$phid = $object->getPHID();
|
||||
|
||||
// TODO: Remove this, this is just legacy support for the current
|
||||
// controller until a new one gets built.
|
||||
$legacy_kinds = array(
|
||||
ManiphestTaskHasCommitEdgeType::EDGECONST => 'CMIT',
|
||||
ManiphestTaskHasMockEdgeType::EDGECONST => 'MOCK',
|
||||
ManiphestTaskHasRevisionEdgeType::EDGECONST => 'DREV',
|
||||
);
|
||||
|
||||
$edge_type = $this->getEdgeConstant();
|
||||
$legacy_kind = idx($legacy_kinds, $edge_type);
|
||||
if (!$legacy_kind) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Only specific legacy relationships are supported!'));
|
||||
}
|
||||
|
||||
return "/search/attach/{$phid}/{$legacy_kind}/";
|
||||
$type = $this->getRelationshipConstant();
|
||||
return "/search/rel/{$type}/{$phid}/";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -71,6 +71,10 @@ final class PhabricatorObjectRelationshipList extends Phobject {
|
|||
->setSubmenu($actions);
|
||||
}
|
||||
|
||||
public function getRelationship($key) {
|
||||
return idx($this->relationships, $key);
|
||||
}
|
||||
|
||||
public static function newForObject(PhabricatorUser $viewer, $object) {
|
||||
$relationships = PhabricatorObjectRelationship::getAllRelationships();
|
||||
|
||||
|
|
Loading…
Reference in a new issue