mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-27 09:12:41 +01:00
When performing complex edits, pause sub-editors before they publish to propagate "Must Encrypt" and other state
Summary: See PHI1134. Previously, see T13082 and D19969 for some sort-of-related stuff. Currently, edits work roughly like this: - Suppose we're editing object X, and we're also going to edit some other object, Y, because X mentioned Y or the edit is making X a child or parent of Y, or unblocking Y. - Do the actual edit to X, including inverse edits ("alice mentioned Y on X.", "alice added a child revision: X", etc) which apply to Y. - Run Herald rules on X. - Publish the edit to X. The "inverse edits" currently do this whole process inline, in a sub-editor. So the flow expands like this: - Begin editing X. - Update properties on X. - Begin inverse-edge editing Y. - Update properties on Y. - Run (actually, skip) Herald rules on Y. - Publish edits to Y. - Run Herald rules on X. - Publish edits to X. Notably, the "Y" stuff publishes before the "X" Herald rules run. This creates potential problems: - Herald rules may change the name or visibility policy of "X", but we'll publish mail about it via the edits to Y before those edits apply. This is a problem only in theory, we don't ship any upstream rules like this today. - Herald rules may "Require Secure Mail", but we won't know that at the time we're building mail about the indirect change to "Y". This is a problem in practice. Instead, switch to this new flow, where we stop the sub-editors before they publish, then publish everything at the very end once all the edits are complete: - Begin editing X. - Update properties on X. - Begin inverse-edge editing Y. - Update properties on Y. - Skip Herald on Y. - Run Herald rules on X. - Publish X. - Publish all child-editors of X. - Publish Y. Test Plan: - Created "Must Encrypt" Herald rules for Tasks and Revisions. - Edited object "A", an object which the rules applied to directly, and set object "B" (a different object which the rules did not hit) as its parent/child and/or unblocked it. - In `bin/mail list-outbound`, saw: - Mail about object "A" all flagged as "Must Encrypt". - Normal mail from object B not flagged "Must Encrypt". - Mail from object B about changing relationships to object A flagged as "Must Encrypt". Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D20283
This commit is contained in:
parent
b469a5134d
commit
a6fd8f0479
2 changed files with 72 additions and 19 deletions
|
@ -134,10 +134,7 @@ final class ManiphestTransactionEditor
|
||||||
$parent_xaction->setMetadataValue('blocker.new', true);
|
$parent_xaction->setMetadataValue('blocker.new', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
id(new ManiphestTransactionEditor())
|
$this->newSubEditor()
|
||||||
->setActor($this->getActor())
|
|
||||||
->setActingAsPHID($this->getActingAsPHID())
|
|
||||||
->setContentSource($this->getContentSource())
|
|
||||||
->setContinueOnNoEffect(true)
|
->setContinueOnNoEffect(true)
|
||||||
->setContinueOnMissingFields(true)
|
->setContinueOnMissingFields(true)
|
||||||
->applyTransactions($blocked_task, array($parent_xaction));
|
->applyTransactions($blocked_task, array($parent_xaction));
|
||||||
|
|
|
@ -72,7 +72,7 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
private $mailShouldSend = false;
|
private $mailShouldSend = false;
|
||||||
private $modularTypes;
|
private $modularTypes;
|
||||||
private $silent;
|
private $silent;
|
||||||
private $mustEncrypt;
|
private $mustEncrypt = array();
|
||||||
private $stampTemplates = array();
|
private $stampTemplates = array();
|
||||||
private $mailStamps = array();
|
private $mailStamps = array();
|
||||||
private $oldTo = array();
|
private $oldTo = array();
|
||||||
|
@ -90,6 +90,11 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
private $cancelURI;
|
private $cancelURI;
|
||||||
private $extensions;
|
private $extensions;
|
||||||
|
|
||||||
|
private $parentEditor;
|
||||||
|
private $subEditors = array();
|
||||||
|
private $publishableObject;
|
||||||
|
private $publishableTransactions;
|
||||||
|
|
||||||
const STORAGE_ENCODING_BINARY = 'binary';
|
const STORAGE_ENCODING_BINARY = 'binary';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1272,10 +1277,9 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
$herald_source = PhabricatorContentSource::newForSource(
|
$herald_source = PhabricatorContentSource::newForSource(
|
||||||
PhabricatorHeraldContentSource::SOURCECONST);
|
PhabricatorHeraldContentSource::SOURCECONST);
|
||||||
|
|
||||||
$herald_editor = newv(get_class($this), array())
|
$herald_editor = $this->newEditorCopy()
|
||||||
->setContinueOnNoEffect(true)
|
->setContinueOnNoEffect(true)
|
||||||
->setContinueOnMissingFields(true)
|
->setContinueOnMissingFields(true)
|
||||||
->setParentMessageID($this->getParentMessageID())
|
|
||||||
->setIsHeraldEditor(true)
|
->setIsHeraldEditor(true)
|
||||||
->setActor($herald_actor)
|
->setActor($herald_actor)
|
||||||
->setActingAsPHID($herald_phid)
|
->setActingAsPHID($herald_phid)
|
||||||
|
@ -1330,6 +1334,38 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
}
|
}
|
||||||
$this->heraldHeader = $herald_header;
|
$this->heraldHeader = $herald_header;
|
||||||
|
|
||||||
|
// See PHI1134. If we're a subeditor, we don't publish information about
|
||||||
|
// the edit yet. Our parent editor still needs to finish applying
|
||||||
|
// transactions and execute Herald, which may change the information we
|
||||||
|
// publish.
|
||||||
|
|
||||||
|
// For example, Herald actions may change the parent object's title or
|
||||||
|
// visibility, or Herald may apply rules like "Must Encrypt" that affect
|
||||||
|
// email.
|
||||||
|
|
||||||
|
// Once the parent finishes work, it will queue its own publish step and
|
||||||
|
// then queue publish steps for its children.
|
||||||
|
|
||||||
|
$this->publishableObject = $object;
|
||||||
|
$this->publishableTransactions = $xactions;
|
||||||
|
if (!$this->parentEditor) {
|
||||||
|
$this->queuePublishing();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $xactions;
|
||||||
|
}
|
||||||
|
|
||||||
|
final private function queuePublishing() {
|
||||||
|
$object = $this->publishableObject;
|
||||||
|
$xactions = $this->publishableTransactions;
|
||||||
|
|
||||||
|
if (!$object) {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'Editor method "queuePublishing()" was called, but no publishable '.
|
||||||
|
'object is present. This Editor is not ready to publish.'));
|
||||||
|
}
|
||||||
|
|
||||||
// We're going to compute some of the data we'll use to publish these
|
// We're going to compute some of the data we'll use to publish these
|
||||||
// transactions here, before queueing a worker.
|
// transactions here, before queueing a worker.
|
||||||
//
|
//
|
||||||
|
@ -1392,9 +1428,11 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
'priority' => PhabricatorWorker::PRIORITY_ALERTS,
|
'priority' => PhabricatorWorker::PRIORITY_ALERTS,
|
||||||
));
|
));
|
||||||
|
|
||||||
$this->flushTransactionQueue($object);
|
foreach ($this->subEditors as $sub_editor) {
|
||||||
|
$sub_editor->queuePublishing();
|
||||||
|
}
|
||||||
|
|
||||||
return $xactions;
|
$this->flushTransactionQueue($object);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function didCatchDuplicateKeyException(
|
protected function didCatchDuplicateKeyException(
|
||||||
|
@ -3818,6 +3856,11 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
|
|
||||||
$this->mustEncrypt = $adapter->getMustEncryptReasons();
|
$this->mustEncrypt = $adapter->getMustEncryptReasons();
|
||||||
|
|
||||||
|
// See PHI1134. Propagate "Must Encrypt" state to sub-editors.
|
||||||
|
foreach ($this->subEditors as $sub_editor) {
|
||||||
|
$sub_editor->mustEncrypt = $this->mustEncrypt;
|
||||||
|
}
|
||||||
|
|
||||||
$apply_xactions = $this->didApplyHeraldRules($object, $adapter, $xscript);
|
$apply_xactions = $this->didApplyHeraldRules($object, $adapter, $xscript);
|
||||||
assert_instances_of($apply_xactions, 'PhabricatorApplicationTransaction');
|
assert_instances_of($apply_xactions, 'PhabricatorApplicationTransaction');
|
||||||
|
|
||||||
|
@ -4034,15 +4077,10 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
->setOldValue($old_phids)
|
->setOldValue($old_phids)
|
||||||
->setNewValue($new_phids);
|
->setNewValue($new_phids);
|
||||||
|
|
||||||
$editor
|
$editor = $this->newSubEditor($editor)
|
||||||
->setContinueOnNoEffect(true)
|
->setContinueOnNoEffect(true)
|
||||||
->setContinueOnMissingFields(true)
|
->setContinueOnMissingFields(true)
|
||||||
->setParentMessageID($this->getParentMessageID())
|
->setIsInverseEdgeEditor(true);
|
||||||
->setIsInverseEdgeEditor(true)
|
|
||||||
->setIsSilent($this->getIsSilent())
|
|
||||||
->setActor($this->requireActor())
|
|
||||||
->setActingAsPHID($this->getActingAsPHID())
|
|
||||||
->setContentSource($this->getContentSource());
|
|
||||||
|
|
||||||
$editor->applyTransactions($node, array($template));
|
$editor->applyTransactions($node, array($template));
|
||||||
}
|
}
|
||||||
|
@ -4551,23 +4589,41 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
$xactions = $this->transactionQueue;
|
$xactions = $this->transactionQueue;
|
||||||
$this->transactionQueue = array();
|
$this->transactionQueue = array();
|
||||||
|
|
||||||
$editor = $this->newQueueEditor();
|
$editor = $this->newEditorCopy();
|
||||||
|
|
||||||
return $editor->applyTransactions($object, $xactions);
|
return $editor->applyTransactions($object, $xactions);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function newQueueEditor() {
|
final protected function newSubEditor(
|
||||||
$editor = id(newv(get_class($this), array()))
|
PhabricatorApplicationTransactionEditor $template = null) {
|
||||||
|
$editor = $this->newEditorCopy($template);
|
||||||
|
|
||||||
|
$editor->parentEditor = $this;
|
||||||
|
$this->subEditors[] = $editor;
|
||||||
|
|
||||||
|
return $editor;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function newEditorCopy(
|
||||||
|
PhabricatorApplicationTransactionEditor $template = null) {
|
||||||
|
if ($template === null) {
|
||||||
|
$template = newv(get_class($this), array());
|
||||||
|
}
|
||||||
|
|
||||||
|
$editor = id(clone $template)
|
||||||
->setActor($this->getActor())
|
->setActor($this->getActor())
|
||||||
->setContentSource($this->getContentSource())
|
->setContentSource($this->getContentSource())
|
||||||
->setContinueOnNoEffect($this->getContinueOnNoEffect())
|
->setContinueOnNoEffect($this->getContinueOnNoEffect())
|
||||||
->setContinueOnMissingFields($this->getContinueOnMissingFields())
|
->setContinueOnMissingFields($this->getContinueOnMissingFields())
|
||||||
|
->setParentMessageID($this->getParentMessageID())
|
||||||
->setIsSilent($this->getIsSilent());
|
->setIsSilent($this->getIsSilent());
|
||||||
|
|
||||||
if ($this->actingAsPHID !== null) {
|
if ($this->actingAsPHID !== null) {
|
||||||
$editor->setActingAsPHID($this->actingAsPHID);
|
$editor->setActingAsPHID($this->actingAsPHID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$editor->mustEncrypt = $this->mustEncrypt;
|
||||||
|
|
||||||
return $editor;
|
return $editor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue