mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-15 19:32:40 +01:00
2cb779575d
Summary: Ref T11179. This splits "Edit Blocking Tasks" into two options now that we have more room ("Edit Parent Tasks", "Edit Subtasks"). This also renames "Blocking" tasks to "Subtasks", and "Blocked" tasks to "Parent" tasks. My goals here are: - Make the relationship direction more clear: it's more clear which way is up with "parent" and "subtask" at a glance than with "blocking" and "blocked" or "dependent" and "dependency". - Align language with "Create Subtask". - To some small degree, use more flexible/general-purpose language, although I haven't seen any real confusion here. Fixes T6815. I think I narrowed this down to two issues: - Just throwing a bare exeception (we now return a dialog explicitly). - Not killing open transactions when the cyclec check fails (we now kill them). Test Plan: - Edited parent tasks. - Edited subtasks. - Tried to introduce graph cycles, got a nice error dialog. {F1697087} {F1697088} Reviewers: chad Reviewed By: chad Maniphest Tasks: T6815, T11179 Differential Revision: https://secure.phabricator.com/D16166
205 lines
6.4 KiB
PHP
205 lines
6.4 KiB
PHP
<?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();
|
|
$initial_phids = $dst_phids;
|
|
|
|
if ($request->isFormPost()) {
|
|
$phids = explode(';', $request->getStr('phids'));
|
|
$phids = array_filter($phids);
|
|
$phids = array_values($phids);
|
|
|
|
$initial_phids = $request->getStrList('initialPHIDs');
|
|
|
|
// Apply the changes as adds and removes relative to the original state
|
|
// of the object when the dialog was rendered so that two users adding
|
|
// relationships at the same time don't race and overwrite one another.
|
|
$add_phids = array_diff($phids, $initial_phids);
|
|
$rem_phids = array_diff($initial_phids, $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',
|
|
ManiphestTaskDependsOnTaskEdgeType::EDGECONST => 'TASK',
|
|
ManiphestTaskDependedOnByTaskEdgeType::EDGECONST => 'TASK',
|
|
);
|
|
|
|
$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)
|
|
->setInitialPHIDs($initial_phids)
|
|
->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:');
|
|
|
|
$list = implode(" \xE2\x86\x92 ", $names);
|
|
|
|
return $this->newDialog()
|
|
->setTitle(pht('Circular Dependency'))
|
|
->appendParagraph($message)
|
|
->appendParagraph($list)
|
|
->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);
|
|
}
|
|
|
|
}
|