Permalink
Browse files

Support edge transactions in ApplicationTransactions

Summary: Fixes T2655. Adds generic support for edge edits (e.g., membership or attached objects).

Test Plan: See next diff.

Reviewers: chad, btrahan

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T2655

Differential Revision: https://secure.phabricator.com/D5434
  • Loading branch information...
1 parent 1ee7bbe commit 696498934c9d073bd0935739a7d9ad19a33229f9 @epriestley epriestley committed Mar 28, 2013
@@ -0,0 +1,24 @@
+ALTER TABLE `{$NAMESPACE}_file`.`macro_transaction`
+ ADD metadata LONGTEXT NOT NULL COLLATE utf8_bin;
+UPDATE `{$NAMESPACE}_file`.macro_transaction SET metadata = '{}'
+ WHERE metadata = '';
+
+ALTER TABLE `{$NAMESPACE}_pholio`.`pholio_transaction`
+ ADD metadata LONGTEXT NOT NULL COLLATE utf8_bin;
+UPDATE `{$NAMESPACE}_pholio`.pholio_transaction SET metadata = '{}'
+ WHERE metadata = '';
+
+ALTER TABLE `{$NAMESPACE}_config`.`config_transaction`
+ ADD metadata LONGTEXT NOT NULL COLLATE utf8_bin;
+UPDATE `{$NAMESPACE}_config`.config_transaction SET metadata = '{}'
+ WHERE metadata = '';
+
+ALTER TABLE `{$NAMESPACE}_conpherence`.`conpherence_transaction`
+ ADD metadata LONGTEXT NOT NULL COLLATE utf8_bin;
+UPDATE `{$NAMESPACE}_conpherence`.conpherence_transaction SET metadata = '{}'
+ WHERE metadata = '';
+
+ALTER TABLE `{$NAMESPACE}_phlux`.`phlux_transaction`
+ ADD metadata LONGTEXT NOT NULL COLLATE utf8_bin;
+UPDATE `{$NAMESPACE}_phlux`.phlux_transaction SET metadata = '{}'
+ WHERE metadata = '';
@@ -192,7 +192,7 @@ protected function mergeTransactions(
return $v;
case ConpherenceTransactionType::TYPE_FILES:
case ConpherenceTransactionType::TYPE_PARTICIPANTS:
- return $this->mergePHIDTransactions($u, $v);
+ return $this->mergePHIDOrEdgeTransactions($u, $v);
}
return parent::mergeTransactions($u, $v);
@@ -6,6 +6,7 @@
const TYPE_SUBSCRIBERS = 'core:subscribers';
const TYPE_VIEW_POLICY = 'core:view-policy';
const TYPE_EDIT_POLICY = 'core:edit-policy';
+ const TYPE_EDGE = 'core:edge';
const COLOR_RED = 'red';
const COLOR_ORANGE = 'orange';
@@ -101,6 +101,26 @@ private function getTransactionOldValue(
return $object->getViewPolicy();
case PhabricatorTransactions::TYPE_EDIT_POLICY:
return $object->getEditPolicy();
+ case PhabricatorTransactions::TYPE_EDGE:
+ $edge_type = $xaction->getMetadataValue('edge:type');
+ if (!$edge_type) {
+ throw new Exception("Edge transaction has no 'edge:type'!");
+ }
+
+ $old_edges = array();
+ if ($object->getPHID()) {
+ $edge_src = $object->getPHID();
+
+ $old_edges = id(new PhabricatorEdgeQuery())
+ ->setViewer($this->getActor())
+ ->withSourcePHIDs(array($edge_src))
+ ->withEdgeTypes(array($edge_type))
+ ->needEdgeData(true)
+ ->execute();
+
+ $old_edges = $old_edges[$edge_src][$edge_type];
+ }
+ return $old_edges;
default:
return $this->getCustomTransactionOldValue($object, $xaction);
}
@@ -115,6 +135,8 @@ private function getTransactionNewValue(
case PhabricatorTransactions::TYPE_VIEW_POLICY:
case PhabricatorTransactions::TYPE_EDIT_POLICY:
return $xaction->getNewValue();
+ case PhabricatorTransactions::TYPE_EDGE:
+ return $this->getEdgeTransactionNewValue($xaction);
default:
return $this->getCustomTransactionNewValue($object, $xaction);
}
@@ -180,7 +202,46 @@ private function applyExternalEffects(
$subeditor->save();
break;
+ case PhabricatorTransactions::TYPE_EDGE:
+ $old = $xaction->getOldValue();
+ $new = $xaction->getNewValue();
+ $src = $object->getPHID();
+ $type = $xaction->getMetadataValue('edge:type');
+
+ foreach ($new as $dst_phid => $edge) {
+ $new[$dst_phid]['src'] = $src;
+ }
+
+ $editor = id(new PhabricatorEdgeEditor())
+ ->setActor($this->getActor());
+
+ foreach ($old as $dst_phid => $edge) {
+ if (!empty($new[$dst_phid])) {
+ if ($old[$dst_phid]['data'] === $new[$dst_phid]['data']) {
+ continue;
+ }
+ }
+ $editor->removeEdge($src, $type, $dst_phid);
+ }
+
+ foreach ($new as $dst_phid => $edge) {
+ if (!empty($old[$dst_phid])) {
+ if ($old[$dst_phid]['data'] === $new[$dst_phid]['data']) {
+ continue;
+ }
+ }
+
+ $data = array(
+ 'data' => $edge['data'],
+ );
+
+ $editor->addEdge($src, $type, $dst_phid, $data);
+ }
+
+ $editor->save();
+ break;
}
+
return $this->applyCustomExternalTransaction($object, $xaction);
}
@@ -460,7 +521,14 @@ protected function mergeTransactions(
switch ($type) {
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
- return $this->mergePHIDTransactions($u, $v);
+ return $this->mergePHIDOrEdgeTransactions($u, $v);
+ case PhabricatorTransactions::TYPE_EDGE:
+ $u_type = $u->getMetadataValue('edge:type');
+ $v_type = $v->getMetadataValue('edge:type');
+ if ($u_type == $v_type) {
+ return $this->mergePHIDOrEdgeTransactions($u, $v);
+ }
+ return null;
}
// By default, do not merge the transactions.
@@ -516,7 +584,7 @@ private function combineTransactions(array $xactions) {
return array_values($result);
}
- protected function mergePHIDTransactions(
+ protected function mergePHIDOrEdgeTransactions(
PhabricatorApplicationTransaction $u,
PhabricatorApplicationTransaction $v) {
@@ -529,7 +597,6 @@ protected function mergePHIDTransactions(
return $u;
}
-
protected function getPHIDTransactionNewValue(
PhabricatorApplicationTransaction $xaction) {
@@ -578,6 +645,110 @@ protected function getPHIDTransactionNewValue(
return array_values($result);
}
+ protected function getEdgeTransactionNewValue(
+ PhabricatorApplicationTransaction $xaction) {
+
+ $new = $xaction->getNewValue();
+ $new_add = idx($new, '+', array());
+ unset($new['+']);
+ $new_rem = idx($new, '-', array());
+ unset($new['-']);
+ $new_set = idx($new, '=', null);
+ unset($new['=']);
+
+ if ($new) {
+ throw new Exception(
+ "Invalid 'new' value for Edge transaction. Value should contain only ".
+ "keys '+' (add edges), '-' (remove edges) and '=' (set edges).");
+ }
+
+ $old = $xaction->getOldValue();
+
+ $lists = array($new_set, $new_add, $new_rem);
+ foreach ($lists as $list) {
+ $this->checkEdgeList($list);
+ }
+
+ $result = array();
+ foreach ($old as $dst_phid => $edge) {
+ if ($new_set !== null && empty($new_set[$dst_phid])) {
+ continue;
+ }
+ $result[$dst_phid] = $this->normalizeEdgeTransactionValue(
+ $xaction,
+ $edge);
+ }
+
+ if ($new_set !== null) {
+ foreach ($new_set as $dst_phid => $edge) {
+ $result[$dst_phid] = $this->normalizeEdgeTransactionValue(
+ $xaction,
+ $edge);
+ }
+ }
+
+ foreach ($new_add as $dst_phid => $edge) {
+ $result[$dst_phid] = $this->normalizeEdgeTransactionValue(
+ $xaction,
+ $edge);
+ }
+
+ foreach ($new_rem as $dst_phid => $edge) {
+ unset($result[$dst_phid]);
+ }
+
+ return $result;
+ }
+
+ private function checkEdgeList($list) {
+ if (!$list) {
+ return;
+ }
+ foreach ($list as $key => $item) {
+ if (phid_get_type($key) === PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN) {
+ throw new Exception(
+ "Edge transactions must have destination PHIDs as in edge ".
+ "lists (found key '{$key}').");
+ }
+ if (!is_array($item) && $item !== $key) {
+ throw new Exception(
+ "Edge transactions must have PHIDs or edge specs as values ".
+ "(found value '{$item}').");
+ }
+ }
+ }
+
+ protected function normalizeEdgeTransactionValue(
+ PhabricatorApplicationTransaction $xaction,
+ $edge) {
+
+ if (!is_array($edge)) {
+ $edge = array(
+ 'dst' => $edge,
+ );
+ }
+
+ $edge_type = $xaction->getMetadataValue('edge:type');
+
+ if (empty($edge['type'])) {
+ $edge['type'] = $edge_type;
+ } else {
+ if ($edge['type'] != $edge_type) {
+ $this_type = $edge['type'];
+ throw new Exception(
+ "Edge transaction includes edge of type '{$this_type}', but ".
+ "transaction is of type '{$edge_type}'. Each edge transaction must ".
+ "alter edges of only one type.");
+ }
+ }
+
+ if (!isset($edge['data'])) {
+ $edge['data'] = null;
+ }
+
+ return $edge;
+ }
+
protected function sortTransactions(array $xactions) {
$head = array();
$tail = array();
@@ -18,6 +18,7 @@
protected $transactionType;
protected $oldValue;
protected $newValue;
+ protected $metadata = array();
protected $contentSource;
@@ -36,6 +37,15 @@ public function getApplicationTransactionViewObject() {
return new PhabricatorApplicationTransactionView();
}
+ public function getMetadataValue($key, $default = null) {
+ return idx($this->metadata, $key, $default);
+ }
+
+ public function setMetadataValue($key, $value) {
+ $this->metadata[$key] = $value;
+ return $this;
+ }
+
public function generatePHID() {
$type = PhabricatorPHIDConstants::PHID_TYPE_XACT;
$subtype = $this->getApplicationTransactionType();
@@ -49,6 +59,7 @@ public function getConfiguration() {
self::CONFIG_SERIALIZATION => array(
'oldValue' => self::SERIALIZATION_JSON,
'newValue' => self::SERIALIZATION_JSON,
+ 'metadata' => self::SERIALIZATION_JSON,
),
) + parent::getConfiguration();
}
@@ -108,6 +119,10 @@ public function getRequiredHandlePHIDs() {
$phids[] = $old;
$phids[] = $new;
break;
+ case PhabricatorTransactions::TYPE_EDGE:
+ $phids[] = ipull($old, 'dst');
+ $phids[] = ipull($new, 'dst');
+ break;
}
return array_mergev($phids);
@@ -160,6 +175,8 @@ public function getIcon() {
case PhabricatorTransactions::TYPE_VIEW_POLICY:
case PhabricatorTransactions::TYPE_EDIT_POLICY:
return 'lock';
+ case PhabricatorTransactions::TYPE_EDGE:
+ return 'link';
}
return null;
@@ -201,6 +218,8 @@ public function getNoEffectDescription() {
return pht(
'All users are already subscribed to this %s.',
$this->getApplicationObjectTypeName());
+ case PhabricatorTransactions::TYPE_EDGE:
+ return pht('Edges already exist; transaction has no effect.');
}
return pht('Transaction has no effect.');
@@ -259,6 +278,12 @@ public function getTitle() {
$this->renderHandleList($rem));
}
break;
+ case PhabricatorTransactions::TYPE_EDGE:
+ $type = $this->getMetadata('edge:type');
+ return pht(
+ '%s edited edges of type %s.',
+ $this->renderHandleLink($author_phid),
+ $type);
default:
return pht(
'%s edited this %s.',
@@ -295,6 +320,11 @@ public function getTitleForFeed() {
'%s updated subscribers of %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
+ case PhabricatorTransactions::TYPE_EDGE:
+ return pht(
+ '%s updated edges of %s.',
+ $this->renderHandleLink($author_phid),
+ $this->renderHandleLink($object_phid));
}
return $this->getTitle();
@@ -1194,6 +1194,10 @@ public function getPatches() {
'type' => 'sql',
'name' => $this->getPatchPath('20130317.phrictionedge.sql'),
),
+ '20130310.xactionmeta.sql' => array(
+ 'type' => 'sql',
+ 'name' => $this->getPatchPath('20130310.xactionmeta.sql'),
+ ),
);
}

0 comments on commit 6964989

Please sign in to comment.