1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-03-13 12:54:53 +01:00
phorge-phorge/src/applications/transactions/storage/PhabricatorApplicationTransaction.php

1765 lines
53 KiB
PHP
Raw Normal View History

<?php
abstract class PhabricatorApplicationTransaction
extends PhabricatorLiskDAO
implements
PhabricatorPolicyInterface,
PhabricatorDestructibleInterface {
const TARGET_TEXT = 'text';
const TARGET_HTML = 'html';
protected $phid;
protected $objectPHID;
protected $authorPHID;
protected $viewPolicy;
protected $editPolicy;
protected $commentPHID;
protected $commentVersion = 0;
protected $transactionType;
protected $oldValue;
protected $newValue;
protected $metadata = array();
protected $contentSource;
private $comment;
private $commentNotLoaded;
private $handles;
private $renderingTarget = self::TARGET_HTML;
private $transactionGroup = array();
private $viewer = self::ATTACHABLE;
private $object = self::ATTACHABLE;
private $oldValueHasBeenSet = false;
private $ignoreOnNoEffect;
/**
* Flag this transaction as a pure side-effect which should be ignored when
* applying transactions if it has no effect, even if transaction application
* would normally fail. This both provides users with better error messages
* and allows transactions to perform optional side effects.
*/
public function setIgnoreOnNoEffect($ignore) {
$this->ignoreOnNoEffect = $ignore;
return $this;
}
public function getIgnoreOnNoEffect() {
return $this->ignoreOnNoEffect;
}
public function shouldGenerateOldValue() {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_TOKEN:
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
case PhabricatorTransactions::TYPE_INLINESTATE:
return false;
}
return true;
}
abstract public function getApplicationTransactionType();
private function getApplicationObjectTypeName() {
$types = PhabricatorPHIDType::getAllTypes();
$type = idx($types, $this->getApplicationTransactionType());
if ($type) {
return $type->getTypeName();
}
return pht('Object');
}
public function getApplicationTransactionCommentObject() {
return null;
}
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 = PhabricatorApplicationTransactionTransactionPHIDType::TYPECONST;
$subtype = $this->getApplicationTransactionType();
return PhabricatorPHID::generateNewPHID($type, $subtype);
}
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'oldValue' => self::SERIALIZATION_JSON,
'newValue' => self::SERIALIZATION_JSON,
'metadata' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'commentPHID' => 'phid?',
'commentVersion' => 'uint32',
'contentSource' => 'text',
'transactionType' => 'text32',
),
self::CONFIG_KEY_SCHEMA => array(
'key_object' => array(
'columns' => array('objectPHID'),
),
),
) + parent::getConfiguration();
}
public function setContentSource(PhabricatorContentSource $content_source) {
$this->contentSource = $content_source->serialize();
return $this;
}
public function getContentSource() {
return PhabricatorContentSource::newFromSerialized($this->contentSource);
}
public function hasComment() {
return $this->getComment() && strlen($this->getComment()->getContent());
}
public function getComment() {
if ($this->commentNotLoaded) {
throw new Exception(pht('Comment for this transaction was not loaded.'));
}
return $this->comment;
}
Show fewer useless transactions when creating objects, especially with EditEngine forms Summary: Fixes T7661. Ref T9527. When you create a task, especially with an EditEngine form, you currently get more noise than is useful. For example: > alice created this task. > alice changed the edit policy from "All Users" to "Community (Project)". > alice added projects: Feature Request, Differential. > alice added a subscriber: alice. Transaction (1) is a little useful, since it saves us from a weird empty state and shows the object creation time. Transaction (2) is totally useless (and even misleading) because that's the default policy for the form. Transaction (3) isn't //completely// useless but isn't very interesting, and probably not worth the real-estate. Transaction (4) is totally useless. (These transactions are uniquely useless when creating objects -- when editing them later, they're fine.) This adds two new rules to hide transactions: - Hide transactions from object creation if the old value is empty (e.g., set title, set projects, set subscribers). - Hide transactions from object creation if the old value is the same as the form default value (e.g., set policy to default, set priorities to default, set status to default). NOTE: These rules also hide the "created this object" transaction, since it's really one of those transaction types in all cases. I want to keep that around in the long term, but just have it be a separate `TYPE_CREATE` action -- currently, it is this weird, inconsistent action where we pick some required field (like title) and special-case the rendering if the old value is `null`. So fixing that is a bit more involved. For now, I'm just dropping these transactions completely, but intend to restore them later. Test Plan: - Created objects. - Usually saw no extra create transactions. - Saw extra create transactions when making an important change away from form defaults (e.g., overriding form policy). Reviewers: chad Reviewed By: chad Maniphest Tasks: T7661, T9527 Differential Revision: https://secure.phabricator.com/D14810
2015-12-17 10:04:20 -08:00
public function setIsCreateTransaction($create) {
return $this->setMetadataValue('core.create', $create);
}
public function getIsCreateTransaction() {
return (bool)$this->getMetadataValue('core.create', false);
}
public function setIsDefaultTransaction($default) {
return $this->setMetadataValue('core.default', $default);
}
public function getIsDefaultTransaction() {
return (bool)$this->getMetadataValue('core.default', false);
}
public function setIsSilentTransaction($silent) {
return $this->setMetadataValue('core.silent', $silent);
}
public function getIsSilentTransaction() {
return (bool)$this->getMetadataValue('core.silent', false);
}
public function setIsMFATransaction($mfa) {
return $this->setMetadataValue('core.mfa', $mfa);
}
public function getIsMFATransaction() {
return (bool)$this->getMetadataValue('core.mfa', false);
}
public function setIsLockOverrideTransaction($override) {
return $this->setMetadataValue('core.lock-override', $override);
}
public function getIsLockOverrideTransaction() {
return (bool)$this->getMetadataValue('core.lock-override', false);
}
public function attachComment(
PhabricatorApplicationTransactionComment $comment) {
$this->comment = $comment;
$this->commentNotLoaded = false;
return $this;
}
public function setCommentNotLoaded($not_loaded) {
$this->commentNotLoaded = $not_loaded;
return $this;
}
public function attachObject($object) {
$this->object = $object;
return $this;
}
public function getObject() {
return $this->assertAttached($this->object);
}
public function getRemarkupChanges() {
$changes = $this->newRemarkupChanges();
assert_instances_of($changes, 'PhabricatorTransactionRemarkupChange');
// Convert older-style remarkup blocks into newer-style remarkup changes.
// This builds changes that do not have the correct "old value", so rules
// that operate differently against edits (like @user mentions) won't work
// properly.
foreach ($this->getRemarkupBlocks() as $block) {
$changes[] = $this->newRemarkupChange()
->setOldValue(null)
->setNewValue($block);
}
$comment = $this->getComment();
if ($comment) {
if ($comment->hasOldComment()) {
$old_value = $comment->getOldComment()->getContent();
} else {
$old_value = null;
}
$new_value = $comment->getContent();
$changes[] = $this->newRemarkupChange()
->setOldValue($old_value)
->setNewValue($new_value);
}
return $changes;
}
protected function newRemarkupChanges() {
return array();
}
protected function newRemarkupChange() {
return id(new PhabricatorTransactionRemarkupChange())
->setTransaction($this);
}
/**
* @deprecated
*/
public function getRemarkupBlocks() {
$blocks = array();
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$field = $this->getTransactionCustomField();
if ($field) {
$custom_blocks = $field->getApplicationTransactionRemarkupBlocks(
$this);
foreach ($custom_blocks as $custom_block) {
$blocks[] = $custom_block;
}
}
break;
}
return $blocks;
}
public function setOldValue($value) {
$this->oldValueHasBeenSet = true;
$this->writeField('oldValue', $value);
return $this;
}
public function hasOldValue() {
return $this->oldValueHasBeenSet;
}
public function newChronologicalSortVector() {
return id(new PhutilSortVector())
->addInt((int)$this->getDateCreated())
->addInt((int)$this->getID());
}
/* -( Rendering )---------------------------------------------------------- */
public function setRenderingTarget($rendering_target) {
$this->renderingTarget = $rendering_target;
return $this;
}
public function getRenderingTarget() {
return $this->renderingTarget;
}
public function attachViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->assertAttached($this->viewer);
}
public function getRequiredHandlePHIDs() {
$phids = array();
$old = $this->getOldValue();
$new = $this->getNewValue();
$phids[] = array($this->getAuthorPHID());
Modularize application transactions in Paste, mostly Summary: Ref T9789. `Transaction` and `Editor` classes are the last major pieces of infrastructure that haven't been fully modularized. Some of the specific issues are: - `Editor` classes rely on a bunch of `instanceof` stuff in the base class to pick up transaction types like "subscribe", "projects", etc. Instead, applications should be adding these, and third-party applications should be able to add them. - Code is spread across `Transaction` and `Editor` classes somewhat oddly. For example, generating old/new values would probably make more sense at the `Transaction` level, but it currently exists at the `Editor` level. - Both types of classes have a lot of functions based on `switch()` statements, which require a ton of boilerplate and are just generally kind of hard to work with. This creates classes for each type of transaction, and moves almost all of the logic to them. These classes are simpler and more focused than the old stuff was, and can organize related code better. This starts inching toward defining `CoreTransactions` for features shared across applications. It only defines the "Create" transaction so far, but at some point I plan to move all the other shared transactions to Core and let them control which objects they're available for. Test Plan: - Created pastes with web UI and API. - Edited all paste properites. - Archived/activated. - Verified files got reasonable names. - Reviewed timeline and feed. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9789 Differential Revision: https://secure.phabricator.com/D16111
2016-06-09 16:00:06 -07:00
$phids[] = array($this->getObjectPHID());
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$field = $this->getTransactionCustomField();
if ($field) {
$phids[] = $field->getApplicationTransactionRequiredHandlePHIDs(
$this);
}
break;
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
$phids[] = $old;
$phids[] = $new;
break;
case PhabricatorTransactions::TYPE_EDGE:
$record = PhabricatorEdgeChangeRecord::newFromTransaction($this);
$phids[] = $record->getChangedPHIDs();
break;
case PhabricatorTransactions::TYPE_COLUMNS:
foreach ($new as $move) {
$phids[] = array(
$move['columnPHID'],
$move['boardPHID'],
);
$phids[] = $move['fromColumnPHIDs'];
}
break;
case PhabricatorTransactions::TYPE_EDIT_POLICY:
case PhabricatorTransactions::TYPE_VIEW_POLICY:
case PhabricatorTransactions::TYPE_JOIN_POLICY:
if (!PhabricatorPolicyQuery::isSpecialPolicy($old)) {
$phids[] = array($old);
}
if (!PhabricatorPolicyQuery::isSpecialPolicy($new)) {
$phids[] = array($new);
}
break;
case PhabricatorTransactions::TYPE_SPACE:
if ($old) {
$phids[] = array($old);
}
if ($new) {
$phids[] = array($new);
}
break;
case PhabricatorTransactions::TYPE_TOKEN:
break;
}
if ($this->getComment()) {
$phids[] = array($this->getComment()->getAuthorPHID());
}
return array_mergev($phids);
}
public function setHandles(array $handles) {
$this->handles = $handles;
return $this;
}
public function getHandle($phid) {
if (empty($this->handles[$phid])) {
throw new Exception(
pht(
'Transaction ("%s", of type "%s") requires a handle ("%s") that it '.
'did not load.',
$this->getPHID(),
$this->getTransactionType(),
$phid));
}
return $this->handles[$phid];
}
public function getHandleIfExists($phid) {
return idx($this->handles, $phid);
}
public function getHandles() {
if ($this->handles === null) {
throw new Exception(
pht('Transaction requires handles and it did not load them.'));
}
return $this->handles;
}
public function renderHandleLink($phid) {
if ($this->renderingTarget == self::TARGET_HTML) {
return $this->getHandle($phid)->renderLink();
} else {
return $this->getHandle($phid)->getLinkName();
}
}
public function renderHandleList(array $phids) {
$links = array();
foreach ($phids as $phid) {
$links[] = $this->renderHandleLink($phid);
}
if ($this->renderingTarget == self::TARGET_HTML) {
return phutil_implode_html(', ', $links);
} else {
return implode(', ', $links);
}
}
private function renderSubscriberList(array $phids, $change_type) {
if ($this->getRenderingTarget() == self::TARGET_TEXT) {
return $this->renderHandleList($phids);
} else {
$handles = array_select_keys($this->getHandles(), $phids);
return id(new SubscriptionListStringBuilder())
->setHandles($handles)
->setObjectPHID($this->getPHID())
->buildTransactionString($change_type);
}
}
protected function renderPolicyName($phid, $state = 'old') {
$policy = PhabricatorPolicy::newFromPolicyAndHandle(
$phid,
$this->getHandleIfExists($phid));
if ($this->renderingTarget == self::TARGET_HTML) {
switch ($policy->getType()) {
case PhabricatorPolicyType::TYPE_CUSTOM:
$policy->setHref('/transactions/'.$state.'/'.$this->getPHID().'/');
$policy->setWorkflow(true);
break;
default:
break;
}
$output = $policy->renderDescription();
} else {
$output = hsprintf('%s', $policy->getFullName());
}
return $output;
}
public function getIcon() {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
$comment = $this->getComment();
if ($comment && $comment->getIsRemoved()) {
return 'fa-trash';
}
return 'fa-comment';
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
$old = $this->getOldValue();
$new = $this->getNewValue();
$add = array_diff($new, $old);
$rem = array_diff($old, $new);
if ($add && $rem) {
return 'fa-user';
} else if ($add) {
return 'fa-user-plus';
} else if ($rem) {
return 'fa-user-times';
} else {
return 'fa-user';
}
case PhabricatorTransactions::TYPE_VIEW_POLICY:
case PhabricatorTransactions::TYPE_EDIT_POLICY:
case PhabricatorTransactions::TYPE_JOIN_POLICY:
return 'fa-lock';
case PhabricatorTransactions::TYPE_EDGE:
switch ($this->getMetadataValue('edge:type')) {
case DiffusionCommitRevertedByCommitEdgeType::EDGECONST:
return 'fa-undo';
case DiffusionCommitRevertsCommitEdgeType::EDGECONST:
return 'fa-ambulance';
}
return 'fa-link';
case PhabricatorTransactions::TYPE_TOKEN:
return 'fa-trophy';
case PhabricatorTransactions::TYPE_SPACE:
return 'fa-th-large';
case PhabricatorTransactions::TYPE_COLUMNS:
return 'fa-columns';
case PhabricatorTransactions::TYPE_MFA:
return 'fa-vcard';
}
return 'fa-pencil';
}
public function getToken() {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_TOKEN:
$old = $this->getOldValue();
$new = $this->getNewValue();
if ($new) {
$icon = substr($new, 10);
} else {
$icon = substr($old, 10);
}
return array($icon, !$this->getNewValue());
}
return array(null, null);
}
public function getColor() {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT;
$comment = $this->getComment();
if ($comment && $comment->getIsRemoved()) {
return 'black';
}
break;
case PhabricatorTransactions::TYPE_EDGE:
switch ($this->getMetadataValue('edge:type')) {
case DiffusionCommitRevertedByCommitEdgeType::EDGECONST:
return 'pink';
case DiffusionCommitRevertsCommitEdgeType::EDGECONST:
return 'sky';
}
break;
case PhabricatorTransactions::TYPE_MFA;
return 'pink';
}
return null;
}
protected function getTransactionCustomField() {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$key = $this->getMetadataValue('customfield:key');
if (!$key) {
return null;
}
$object = $this->getObject();
if (!($object instanceof PhabricatorCustomFieldInterface)) {
return null;
}
$field = PhabricatorCustomField::getObjectField(
$object,
PhabricatorCustomField::ROLE_APPLICATIONTRANSACTIONS,
$key);
if (!$field) {
return null;
}
$field->setViewer($this->getViewer());
return $field;
}
return null;
}
public function shouldHide() {
Show fewer useless transactions when creating objects, especially with EditEngine forms Summary: Fixes T7661. Ref T9527. When you create a task, especially with an EditEngine form, you currently get more noise than is useful. For example: > alice created this task. > alice changed the edit policy from "All Users" to "Community (Project)". > alice added projects: Feature Request, Differential. > alice added a subscriber: alice. Transaction (1) is a little useful, since it saves us from a weird empty state and shows the object creation time. Transaction (2) is totally useless (and even misleading) because that's the default policy for the form. Transaction (3) isn't //completely// useless but isn't very interesting, and probably not worth the real-estate. Transaction (4) is totally useless. (These transactions are uniquely useless when creating objects -- when editing them later, they're fine.) This adds two new rules to hide transactions: - Hide transactions from object creation if the old value is empty (e.g., set title, set projects, set subscribers). - Hide transactions from object creation if the old value is the same as the form default value (e.g., set policy to default, set priorities to default, set status to default). NOTE: These rules also hide the "created this object" transaction, since it's really one of those transaction types in all cases. I want to keep that around in the long term, but just have it be a separate `TYPE_CREATE` action -- currently, it is this weird, inconsistent action where we pick some required field (like title) and special-case the rendering if the old value is `null`. So fixing that is a bit more involved. For now, I'm just dropping these transactions completely, but intend to restore them later. Test Plan: - Created objects. - Usually saw no extra create transactions. - Saw extra create transactions when making an important change away from form defaults (e.g., overriding form policy). Reviewers: chad Reviewed By: chad Maniphest Tasks: T7661, T9527 Differential Revision: https://secure.phabricator.com/D14810
2015-12-17 10:04:20 -08:00
// Never hide comments.
if ($this->hasComment()) {
return false;
}
$xaction_type = $this->getTransactionType();
// Always hide requests for object history.
if ($xaction_type === PhabricatorTransactions::TYPE_HISTORY) {
return true;
}
Show fewer useless transactions when creating objects, especially with EditEngine forms Summary: Fixes T7661. Ref T9527. When you create a task, especially with an EditEngine form, you currently get more noise than is useful. For example: > alice created this task. > alice changed the edit policy from "All Users" to "Community (Project)". > alice added projects: Feature Request, Differential. > alice added a subscriber: alice. Transaction (1) is a little useful, since it saves us from a weird empty state and shows the object creation time. Transaction (2) is totally useless (and even misleading) because that's the default policy for the form. Transaction (3) isn't //completely// useless but isn't very interesting, and probably not worth the real-estate. Transaction (4) is totally useless. (These transactions are uniquely useless when creating objects -- when editing them later, they're fine.) This adds two new rules to hide transactions: - Hide transactions from object creation if the old value is empty (e.g., set title, set projects, set subscribers). - Hide transactions from object creation if the old value is the same as the form default value (e.g., set policy to default, set priorities to default, set status to default). NOTE: These rules also hide the "created this object" transaction, since it's really one of those transaction types in all cases. I want to keep that around in the long term, but just have it be a separate `TYPE_CREATE` action -- currently, it is this weird, inconsistent action where we pick some required field (like title) and special-case the rendering if the old value is `null`. So fixing that is a bit more involved. For now, I'm just dropping these transactions completely, but intend to restore them later. Test Plan: - Created objects. - Usually saw no extra create transactions. - Saw extra create transactions when making an important change away from form defaults (e.g., overriding form policy). Reviewers: chad Reviewed By: chad Maniphest Tasks: T7661, T9527 Differential Revision: https://secure.phabricator.com/D14810
2015-12-17 10:04:20 -08:00
// Hide creation transactions if the old value is empty. These are
// transactions like "alice set the task title to: ...", which are
Show fewer useless transactions when creating objects, especially with EditEngine forms Summary: Fixes T7661. Ref T9527. When you create a task, especially with an EditEngine form, you currently get more noise than is useful. For example: > alice created this task. > alice changed the edit policy from "All Users" to "Community (Project)". > alice added projects: Feature Request, Differential. > alice added a subscriber: alice. Transaction (1) is a little useful, since it saves us from a weird empty state and shows the object creation time. Transaction (2) is totally useless (and even misleading) because that's the default policy for the form. Transaction (3) isn't //completely// useless but isn't very interesting, and probably not worth the real-estate. Transaction (4) is totally useless. (These transactions are uniquely useless when creating objects -- when editing them later, they're fine.) This adds two new rules to hide transactions: - Hide transactions from object creation if the old value is empty (e.g., set title, set projects, set subscribers). - Hide transactions from object creation if the old value is the same as the form default value (e.g., set policy to default, set priorities to default, set status to default). NOTE: These rules also hide the "created this object" transaction, since it's really one of those transaction types in all cases. I want to keep that around in the long term, but just have it be a separate `TYPE_CREATE` action -- currently, it is this weird, inconsistent action where we pick some required field (like title) and special-case the rendering if the old value is `null`. So fixing that is a bit more involved. For now, I'm just dropping these transactions completely, but intend to restore them later. Test Plan: - Created objects. - Usually saw no extra create transactions. - Saw extra create transactions when making an important change away from form defaults (e.g., overriding form policy). Reviewers: chad Reviewed By: chad Maniphest Tasks: T7661, T9527 Differential Revision: https://secure.phabricator.com/D14810
2015-12-17 10:04:20 -08:00
// essentially never interesting.
if ($this->getIsCreateTransaction()) {
switch ($xaction_type) {
case PhabricatorTransactions::TYPE_CREATE:
Improve the clarity of transactions that affect policies and spaces during object creation Summary: Ref T10004. Fixes T9527. Currently, we render two kinds of bad policy/space transactions during object creation. First, we render a transaction showing a change from the default policy/space to the selected policy/space: > alice shifted this object from space S1 Default to space S2 Secret. This is a //good transaction// (it's showing that the default was changed, which could be important for policy stuff!) but it's confusing because it makes it sound like the object briefly existed in space S1, when it did not. Instead, render this: > alice created this object in space S2 Secret. This retains the value (show that the object was created in an unusual space) without the confusion. Second, when you create a "New Bug Report", we render a transaction like this: > alice changed the visibility of this task from "All Users" to "Community". This is distracting and not useful, becasue it's a locked default of the form. This was essentially fixed by D14810. The new behavior is to show this, //only// if the value was changed from the form value: > alice created this object with visibility "Administrators". This should reduce confusion, reduce fluff in the default cases, and do a better job of calling out important changes (basically, unusual spaces/policies). Test Plan: - Created an edit form with a default space and policies. - Used that form to create task with: - same values as form; - different values from form. When I changed the form value, I got transactions. When I left it the same, I didn't. The transactions rendered in the non-confusing "created with ..." variant. Editing the values created normal transactions with "changed policy from X to Y". Reviewers: chad Reviewed By: chad Maniphest Tasks: T9527, T10004 Differential Revision: https://secure.phabricator.com/D14811
2015-12-17 10:29:11 -08:00
case PhabricatorTransactions::TYPE_VIEW_POLICY:
case PhabricatorTransactions::TYPE_EDIT_POLICY:
case PhabricatorTransactions::TYPE_JOIN_POLICY:
case PhabricatorTransactions::TYPE_SPACE:
break;
case PhabricatorTransactions::TYPE_SUBTYPE:
return true;
Improve the clarity of transactions that affect policies and spaces during object creation Summary: Ref T10004. Fixes T9527. Currently, we render two kinds of bad policy/space transactions during object creation. First, we render a transaction showing a change from the default policy/space to the selected policy/space: > alice shifted this object from space S1 Default to space S2 Secret. This is a //good transaction// (it's showing that the default was changed, which could be important for policy stuff!) but it's confusing because it makes it sound like the object briefly existed in space S1, when it did not. Instead, render this: > alice created this object in space S2 Secret. This retains the value (show that the object was created in an unusual space) without the confusion. Second, when you create a "New Bug Report", we render a transaction like this: > alice changed the visibility of this task from "All Users" to "Community". This is distracting and not useful, becasue it's a locked default of the form. This was essentially fixed by D14810. The new behavior is to show this, //only// if the value was changed from the form value: > alice created this object with visibility "Administrators". This should reduce confusion, reduce fluff in the default cases, and do a better job of calling out important changes (basically, unusual spaces/policies). Test Plan: - Created an edit form with a default space and policies. - Used that form to create task with: - same values as form; - different values from form. When I changed the form value, I got transactions. When I left it the same, I didn't. The transactions rendered in the non-confusing "created with ..." variant. Editing the values created normal transactions with "changed policy from X to Y". Reviewers: chad Reviewed By: chad Maniphest Tasks: T9527, T10004 Differential Revision: https://secure.phabricator.com/D14811
2015-12-17 10:29:11 -08:00
default:
$old = $this->getOldValue();
Show fewer useless transactions when creating objects, especially with EditEngine forms Summary: Fixes T7661. Ref T9527. When you create a task, especially with an EditEngine form, you currently get more noise than is useful. For example: > alice created this task. > alice changed the edit policy from "All Users" to "Community (Project)". > alice added projects: Feature Request, Differential. > alice added a subscriber: alice. Transaction (1) is a little useful, since it saves us from a weird empty state and shows the object creation time. Transaction (2) is totally useless (and even misleading) because that's the default policy for the form. Transaction (3) isn't //completely// useless but isn't very interesting, and probably not worth the real-estate. Transaction (4) is totally useless. (These transactions are uniquely useless when creating objects -- when editing them later, they're fine.) This adds two new rules to hide transactions: - Hide transactions from object creation if the old value is empty (e.g., set title, set projects, set subscribers). - Hide transactions from object creation if the old value is the same as the form default value (e.g., set policy to default, set priorities to default, set status to default). NOTE: These rules also hide the "created this object" transaction, since it's really one of those transaction types in all cases. I want to keep that around in the long term, but just have it be a separate `TYPE_CREATE` action -- currently, it is this weird, inconsistent action where we pick some required field (like title) and special-case the rendering if the old value is `null`. So fixing that is a bit more involved. For now, I'm just dropping these transactions completely, but intend to restore them later. Test Plan: - Created objects. - Usually saw no extra create transactions. - Saw extra create transactions when making an important change away from form defaults (e.g., overriding form policy). Reviewers: chad Reviewed By: chad Maniphest Tasks: T7661, T9527 Differential Revision: https://secure.phabricator.com/D14810
2015-12-17 10:04:20 -08:00
Improve the clarity of transactions that affect policies and spaces during object creation Summary: Ref T10004. Fixes T9527. Currently, we render two kinds of bad policy/space transactions during object creation. First, we render a transaction showing a change from the default policy/space to the selected policy/space: > alice shifted this object from space S1 Default to space S2 Secret. This is a //good transaction// (it's showing that the default was changed, which could be important for policy stuff!) but it's confusing because it makes it sound like the object briefly existed in space S1, when it did not. Instead, render this: > alice created this object in space S2 Secret. This retains the value (show that the object was created in an unusual space) without the confusion. Second, when you create a "New Bug Report", we render a transaction like this: > alice changed the visibility of this task from "All Users" to "Community". This is distracting and not useful, becasue it's a locked default of the form. This was essentially fixed by D14810. The new behavior is to show this, //only// if the value was changed from the form value: > alice created this object with visibility "Administrators". This should reduce confusion, reduce fluff in the default cases, and do a better job of calling out important changes (basically, unusual spaces/policies). Test Plan: - Created an edit form with a default space and policies. - Used that form to create task with: - same values as form; - different values from form. When I changed the form value, I got transactions. When I left it the same, I didn't. The transactions rendered in the non-confusing "created with ..." variant. Editing the values created normal transactions with "changed policy from X to Y". Reviewers: chad Reviewed By: chad Maniphest Tasks: T9527, T10004 Differential Revision: https://secure.phabricator.com/D14811
2015-12-17 10:29:11 -08:00
if (is_array($old) && !$old) {
return true;
}
Show fewer useless transactions when creating objects, especially with EditEngine forms Summary: Fixes T7661. Ref T9527. When you create a task, especially with an EditEngine form, you currently get more noise than is useful. For example: > alice created this task. > alice changed the edit policy from "All Users" to "Community (Project)". > alice added projects: Feature Request, Differential. > alice added a subscriber: alice. Transaction (1) is a little useful, since it saves us from a weird empty state and shows the object creation time. Transaction (2) is totally useless (and even misleading) because that's the default policy for the form. Transaction (3) isn't //completely// useless but isn't very interesting, and probably not worth the real-estate. Transaction (4) is totally useless. (These transactions are uniquely useless when creating objects -- when editing them later, they're fine.) This adds two new rules to hide transactions: - Hide transactions from object creation if the old value is empty (e.g., set title, set projects, set subscribers). - Hide transactions from object creation if the old value is the same as the form default value (e.g., set policy to default, set priorities to default, set status to default). NOTE: These rules also hide the "created this object" transaction, since it's really one of those transaction types in all cases. I want to keep that around in the long term, but just have it be a separate `TYPE_CREATE` action -- currently, it is this weird, inconsistent action where we pick some required field (like title) and special-case the rendering if the old value is `null`. So fixing that is a bit more involved. For now, I'm just dropping these transactions completely, but intend to restore them later. Test Plan: - Created objects. - Usually saw no extra create transactions. - Saw extra create transactions when making an important change away from form defaults (e.g., overriding form policy). Reviewers: chad Reviewed By: chad Maniphest Tasks: T7661, T9527 Differential Revision: https://secure.phabricator.com/D14810
2015-12-17 10:04:20 -08:00
if (!is_array($old)) {
if (!strlen($old)) {
return true;
}
// The integer 0 is also uninteresting by default; this is often
// an "off" flag for something like "All Day Event".
if ($old === 0) {
return true;
}
Improve the clarity of transactions that affect policies and spaces during object creation Summary: Ref T10004. Fixes T9527. Currently, we render two kinds of bad policy/space transactions during object creation. First, we render a transaction showing a change from the default policy/space to the selected policy/space: > alice shifted this object from space S1 Default to space S2 Secret. This is a //good transaction// (it's showing that the default was changed, which could be important for policy stuff!) but it's confusing because it makes it sound like the object briefly existed in space S1, when it did not. Instead, render this: > alice created this object in space S2 Secret. This retains the value (show that the object was created in an unusual space) without the confusion. Second, when you create a "New Bug Report", we render a transaction like this: > alice changed the visibility of this task from "All Users" to "Community". This is distracting and not useful, becasue it's a locked default of the form. This was essentially fixed by D14810. The new behavior is to show this, //only// if the value was changed from the form value: > alice created this object with visibility "Administrators". This should reduce confusion, reduce fluff in the default cases, and do a better job of calling out important changes (basically, unusual spaces/policies). Test Plan: - Created an edit form with a default space and policies. - Used that form to create task with: - same values as form; - different values from form. When I changed the form value, I got transactions. When I left it the same, I didn't. The transactions rendered in the non-confusing "created with ..." variant. Editing the values created normal transactions with "changed policy from X to Y". Reviewers: chad Reviewed By: chad Maniphest Tasks: T9527, T10004 Differential Revision: https://secure.phabricator.com/D14811
2015-12-17 10:29:11 -08:00
}
break;
Show fewer useless transactions when creating objects, especially with EditEngine forms Summary: Fixes T7661. Ref T9527. When you create a task, especially with an EditEngine form, you currently get more noise than is useful. For example: > alice created this task. > alice changed the edit policy from "All Users" to "Community (Project)". > alice added projects: Feature Request, Differential. > alice added a subscriber: alice. Transaction (1) is a little useful, since it saves us from a weird empty state and shows the object creation time. Transaction (2) is totally useless (and even misleading) because that's the default policy for the form. Transaction (3) isn't //completely// useless but isn't very interesting, and probably not worth the real-estate. Transaction (4) is totally useless. (These transactions are uniquely useless when creating objects -- when editing them later, they're fine.) This adds two new rules to hide transactions: - Hide transactions from object creation if the old value is empty (e.g., set title, set projects, set subscribers). - Hide transactions from object creation if the old value is the same as the form default value (e.g., set policy to default, set priorities to default, set status to default). NOTE: These rules also hide the "created this object" transaction, since it's really one of those transaction types in all cases. I want to keep that around in the long term, but just have it be a separate `TYPE_CREATE` action -- currently, it is this weird, inconsistent action where we pick some required field (like title) and special-case the rendering if the old value is `null`. So fixing that is a bit more involved. For now, I'm just dropping these transactions completely, but intend to restore them later. Test Plan: - Created objects. - Usually saw no extra create transactions. - Saw extra create transactions when making an important change away from form defaults (e.g., overriding form policy). Reviewers: chad Reviewed By: chad Maniphest Tasks: T7661, T9527 Differential Revision: https://secure.phabricator.com/D14810
2015-12-17 10:04:20 -08:00
}
}
// Hide creation transactions setting values to defaults, even if
// the old value is not empty. For example, tasks may have a global
// default view policy of "All Users", but a particular form sets the
// policy to "Administrators". The transaction corresponding to this
// change is not interesting, since it is the default behavior of the
// form.
if ($this->getIsCreateTransaction()) {
if ($this->getIsDefaultTransaction()) {
return true;
}
}
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_VIEW_POLICY:
case PhabricatorTransactions::TYPE_EDIT_POLICY:
case PhabricatorTransactions::TYPE_JOIN_POLICY:
case PhabricatorTransactions::TYPE_SPACE:
Improve the clarity of transactions that affect policies and spaces during object creation Summary: Ref T10004. Fixes T9527. Currently, we render two kinds of bad policy/space transactions during object creation. First, we render a transaction showing a change from the default policy/space to the selected policy/space: > alice shifted this object from space S1 Default to space S2 Secret. This is a //good transaction// (it's showing that the default was changed, which could be important for policy stuff!) but it's confusing because it makes it sound like the object briefly existed in space S1, when it did not. Instead, render this: > alice created this object in space S2 Secret. This retains the value (show that the object was created in an unusual space) without the confusion. Second, when you create a "New Bug Report", we render a transaction like this: > alice changed the visibility of this task from "All Users" to "Community". This is distracting and not useful, becasue it's a locked default of the form. This was essentially fixed by D14810. The new behavior is to show this, //only// if the value was changed from the form value: > alice created this object with visibility "Administrators". This should reduce confusion, reduce fluff in the default cases, and do a better job of calling out important changes (basically, unusual spaces/policies). Test Plan: - Created an edit form with a default space and policies. - Used that form to create task with: - same values as form; - different values from form. When I changed the form value, I got transactions. When I left it the same, I didn't. The transactions rendered in the non-confusing "created with ..." variant. Editing the values created normal transactions with "changed policy from X to Y". Reviewers: chad Reviewed By: chad Maniphest Tasks: T9527, T10004 Differential Revision: https://secure.phabricator.com/D14811
2015-12-17 10:29:11 -08:00
if ($this->getIsCreateTransaction()) {
break;
}
// TODO: Remove this eventually, this is handling old changes during
// object creation prior to the introduction of "create" and "default"
// transaction display flags.
// NOTE: We can also hit this case with Space transactions that later
// update a default space (`null`) to an explicit space, so handling
// the Space case may require some finesse.
if ($this->getOldValue() === null) {
return true;
} else {
return false;
}
break;
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$field = $this->getTransactionCustomField();
if ($field) {
return $field->shouldHideInApplicationTransactions($this);
}
break;
case PhabricatorTransactions::TYPE_COLUMNS:
return !$this->getInterestingMoves($this->getNewValue());
case PhabricatorTransactions::TYPE_EDGE:
$edge_type = $this->getMetadataValue('edge:type');
switch ($edge_type) {
case PhabricatorObjectMentionsObjectEdgeType::EDGECONST:
case ManiphestTaskHasDuplicateTaskEdgeType::EDGECONST:
case ManiphestTaskIsDuplicateOfTaskEdgeType::EDGECONST:
case PhabricatorMutedEdgeType::EDGECONST:
case PhabricatorMutedByEdgeType::EDGECONST:
return true;
break;
case PhabricatorObjectMentionedByObjectEdgeType::EDGECONST:
$record = PhabricatorEdgeChangeRecord::newFromTransaction($this);
$add = $record->getAddedPHIDs();
$add_value = reset($add);
$add_handle = $this->getHandle($add_value);
if ($add_handle->getPolicyFiltered()) {
return true;
}
return false;
break;
default:
break;
}
break;
When users mark their own inline comments as "Done", suppress the timeline/mail stories Summary: Depends on D19858. Ref T13222. See PHI995. In D19635 and related revisions, inline behavior changed to allow you to pre-mark your own inlines as done (as a reviewer) and to pre-mark your inlines for you (as an author). These actions generate low-value stories in the timeline, like "alice marked 3 comments done." when an author adds some notes to their own revision. These aren't helpful and can be a little misleading. Instead, just don't count it when someone marks their own inlines as "done". If we throw away all the marks after throwing away the self-marks, hide the whole story. This happens in three cases: # You comment on your own revision, and don't uncheck the "Done" checkbox. # You comment on someone else's revision, and check the "Done" checkbox before submitting. # You leave a not-"Done" inline on your own revision, then "Done" it later. Cases (1) and (2) seem unambiguously good/clear. Case (3) is a little more questionable, but I think this still isn't very useful for reviewers. If there's still a clarity issue around case (3), we could change the story text to "alice marked 3 inline comments by other users as done.", but I think this is probably needlessly verbose and that no one will be confused by the behavior as written here. (Also note that this story is never shown in feed.) Test Plan: Created and marked a bunch of inlines as "Done" in Differential and Diffusion, as the author and reviewer/auditor. My own marks didn't generate timeline stories; marking others' comments still does. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13222 Differential Revision: https://secure.phabricator.com/D19859
2018-12-10 10:58:51 -08:00
case PhabricatorTransactions::TYPE_INLINESTATE:
list($done, $undone) = $this->getInterestingInlineStateChangeCounts();
if (!$done && !$undone) {
return true;
}
break;
}
return false;
}
public function shouldHideForMail(array $xactions) {
if ($this->isSelfSubscription()) {
return true;
}
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_TOKEN:
return true;
case PhabricatorTransactions::TYPE_EDGE:
$edge_type = $this->getMetadataValue('edge:type');
switch ($edge_type) {
case PhabricatorObjectMentionsObjectEdgeType::EDGECONST:
case PhabricatorObjectMentionedByObjectEdgeType::EDGECONST:
return true;
case PhabricatorProjectObjectHasProjectEdgeType::EDGECONST:
// When an object is first created, we hide any corresponding
// project transactions in the web UI because you can just look at
// the UI element elsewhere on screen to see which projects it
// is tagged with. However, in mail there's no other way to get
// this information, and it has some amount of value to users, so
// we keep the transaction. See T10493.
return false;
default:
break;
}
break;
}
if ($this->isInlineCommentTransaction()) {
$inlines = array();
// If there's a normal comment, we don't need to publish the inline
// transaction, since the normal comment covers things.
foreach ($xactions as $xaction) {
if ($xaction->isInlineCommentTransaction()) {
$inlines[] = $xaction;
continue;
}
// We found a normal comment, so hide this inline transaction.
if ($xaction->hasComment()) {
return true;
}
}
// If there are several inline comments, only publish the first one.
if ($this !== head($inlines)) {
return true;
}
}
return $this->shouldHide();
}
public function shouldHideForFeed() {
if ($this->isSelfSubscription()) {
return true;
}
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_TOKEN:
case PhabricatorTransactions::TYPE_MFA:
return true;
case PhabricatorTransactions::TYPE_EDGE:
$edge_type = $this->getMetadataValue('edge:type');
switch ($edge_type) {
case PhabricatorObjectMentionsObjectEdgeType::EDGECONST:
case PhabricatorObjectMentionedByObjectEdgeType::EDGECONST:
return true;
break;
default:
break;
}
break;
case PhabricatorTransactions::TYPE_INLINESTATE:
return true;
}
return $this->shouldHide();
}
public function shouldHideForNotifications() {
return $this->shouldHideForFeed();
}
private function getTitleForMailWithRenderingTarget($new_target) {
$old_target = $this->getRenderingTarget();
try {
$this->setRenderingTarget($new_target);
$result = $this->getTitleForMail();
} catch (Exception $ex) {
$this->setRenderingTarget($old_target);
throw $ex;
}
$this->setRenderingTarget($old_target);
return $result;
}
public function getTitleForMail() {
return $this->getTitle();
}
public function getTitleForTextMail() {
return $this->getTitleForMailWithRenderingTarget(self::TARGET_TEXT);
}
public function getTitleForHTMLMail() {
// TODO: For now, rendering this with TARGET_HTML generates links with
// bad targets ("/x/y/" instead of "https://dev.example.com/x/y/"). Throw
// a rug over the issue for the moment. See T12921.
$title = $this->getTitleForMailWithRenderingTarget(self::TARGET_TEXT);
if ($title === null) {
return null;
}
if ($this->hasChangeDetails()) {
$details_uri = $this->getChangeDetailsURI();
$details_uri = PhabricatorEnv::getProductionURI($details_uri);
$show_details = phutil_tag(
'a',
array(
'href' => $details_uri,
),
pht('(Show Details)'));
$title = array($title, ' ', $show_details);
}
return $title;
}
public function getChangeDetailsURI() {
return '/transactions/detail/'.$this->getPHID().'/';
}
public function getBodyForMail() {
if ($this->isInlineCommentTransaction()) {
// We don't return inline comment content as mail body content, because
// applications need to contextualize it (by adding line numbers, for
// example) in order for it to make sense.
return null;
}
$comment = $this->getComment();
if ($comment && strlen($comment->getContent())) {
return $comment->getContent();
}
return null;
}
Add ApplicationTransaction handling for transactions with no effect Summary: When a user submits an action with no effect (like an empty comment, an "abandon" on an already-accepted revision, or a "close, resolved" on a closed task) we want to alert them that their action isn't effective. These warnings fall into two general buckets: - User is submitting two or more actions, and some aren't effective but some are. Prompt them to apply the effective actions only. - A special case of this is where the only effective action is a comment. We provide tailored text ("Post Comment") in this case. - User is submitting one action, which isn't effective. Tell them they're out of luck. - A special case of this is an empty comment. We provide tailored text in this case. By default, the transaction editor throws when transactions have no effect. The caller can then deal with this, or use `PhabricatorApplicationTransactionNoEffectResponse` to provide a standard dialog that gives the user information as above. For cases where we expect transactions to have no effect (notably, "edit" forms) we just continue on no-effect unconditionally. Also fix an issue where new, combined or filtered transactions would not be represented properly in the Ajax response (i.e., return final transactions from `applyTransactions()`), and translate some strings. Test Plan: - Submitted empty and nonempy comments in Macro and Pholio. - Submitted comments with new and existing "@mentions". - Submitted edits in both applications. Reviewers: btrahan, vrana Reviewed By: btrahan CC: aran Maniphest Tasks: T912, T2104 Differential Revision: https://secure.phabricator.com/D4160
2012-12-11 17:27:40 -08:00
public function getNoEffectDescription() {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
return pht('You can not post an empty comment.');
case PhabricatorTransactions::TYPE_VIEW_POLICY:
return pht(
'This %s already has that view policy.',
$this->getApplicationObjectTypeName());
case PhabricatorTransactions::TYPE_EDIT_POLICY:
return pht(
'This %s already has that edit policy.',
$this->getApplicationObjectTypeName());
case PhabricatorTransactions::TYPE_JOIN_POLICY:
return pht(
'This %s already has that join policy.',
$this->getApplicationObjectTypeName());
Add ApplicationTransaction handling for transactions with no effect Summary: When a user submits an action with no effect (like an empty comment, an "abandon" on an already-accepted revision, or a "close, resolved" on a closed task) we want to alert them that their action isn't effective. These warnings fall into two general buckets: - User is submitting two or more actions, and some aren't effective but some are. Prompt them to apply the effective actions only. - A special case of this is where the only effective action is a comment. We provide tailored text ("Post Comment") in this case. - User is submitting one action, which isn't effective. Tell them they're out of luck. - A special case of this is an empty comment. We provide tailored text in this case. By default, the transaction editor throws when transactions have no effect. The caller can then deal with this, or use `PhabricatorApplicationTransactionNoEffectResponse` to provide a standard dialog that gives the user information as above. For cases where we expect transactions to have no effect (notably, "edit" forms) we just continue on no-effect unconditionally. Also fix an issue where new, combined or filtered transactions would not be represented properly in the Ajax response (i.e., return final transactions from `applyTransactions()`), and translate some strings. Test Plan: - Submitted empty and nonempy comments in Macro and Pholio. - Submitted comments with new and existing "@mentions". - Submitted edits in both applications. Reviewers: btrahan, vrana Reviewed By: btrahan CC: aran Maniphest Tasks: T912, T2104 Differential Revision: https://secure.phabricator.com/D4160
2012-12-11 17:27:40 -08:00
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
return pht(
'All users are already subscribed to this %s.',
$this->getApplicationObjectTypeName());
case PhabricatorTransactions::TYPE_SPACE:
return pht('This object is already in that space.');
case PhabricatorTransactions::TYPE_EDGE:
return pht('Edges already exist; transaction has no effect.');
case PhabricatorTransactions::TYPE_COLUMNS:
return pht(
'You have not moved this object to any columns it is not '.
'already in.');
case PhabricatorTransactions::TYPE_MFA:
return pht(
'You can not sign a transaction group that has no other '.
'effects.');
Add ApplicationTransaction handling for transactions with no effect Summary: When a user submits an action with no effect (like an empty comment, an "abandon" on an already-accepted revision, or a "close, resolved" on a closed task) we want to alert them that their action isn't effective. These warnings fall into two general buckets: - User is submitting two or more actions, and some aren't effective but some are. Prompt them to apply the effective actions only. - A special case of this is where the only effective action is a comment. We provide tailored text ("Post Comment") in this case. - User is submitting one action, which isn't effective. Tell them they're out of luck. - A special case of this is an empty comment. We provide tailored text in this case. By default, the transaction editor throws when transactions have no effect. The caller can then deal with this, or use `PhabricatorApplicationTransactionNoEffectResponse` to provide a standard dialog that gives the user information as above. For cases where we expect transactions to have no effect (notably, "edit" forms) we just continue on no-effect unconditionally. Also fix an issue where new, combined or filtered transactions would not be represented properly in the Ajax response (i.e., return final transactions from `applyTransactions()`), and translate some strings. Test Plan: - Submitted empty and nonempy comments in Macro and Pholio. - Submitted comments with new and existing "@mentions". - Submitted edits in both applications. Reviewers: btrahan, vrana Reviewed By: btrahan CC: aran Maniphest Tasks: T912, T2104 Differential Revision: https://secure.phabricator.com/D4160
2012-12-11 17:27:40 -08:00
}
return pht(
'Transaction (of type "%s") has no effect.',
$this->getTransactionType());
Add ApplicationTransaction handling for transactions with no effect Summary: When a user submits an action with no effect (like an empty comment, an "abandon" on an already-accepted revision, or a "close, resolved" on a closed task) we want to alert them that their action isn't effective. These warnings fall into two general buckets: - User is submitting two or more actions, and some aren't effective but some are. Prompt them to apply the effective actions only. - A special case of this is where the only effective action is a comment. We provide tailored text ("Post Comment") in this case. - User is submitting one action, which isn't effective. Tell them they're out of luck. - A special case of this is an empty comment. We provide tailored text in this case. By default, the transaction editor throws when transactions have no effect. The caller can then deal with this, or use `PhabricatorApplicationTransactionNoEffectResponse` to provide a standard dialog that gives the user information as above. For cases where we expect transactions to have no effect (notably, "edit" forms) we just continue on no-effect unconditionally. Also fix an issue where new, combined or filtered transactions would not be represented properly in the Ajax response (i.e., return final transactions from `applyTransactions()`), and translate some strings. Test Plan: - Submitted empty and nonempy comments in Macro and Pholio. - Submitted comments with new and existing "@mentions". - Submitted edits in both applications. Reviewers: btrahan, vrana Reviewed By: btrahan CC: aran Maniphest Tasks: T912, T2104 Differential Revision: https://secure.phabricator.com/D4160
2012-12-11 17:27:40 -08:00
}
public function getTitle() {
$author_phid = $this->getAuthorPHID();
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_CREATE:
return pht(
'%s created this object.',
$this->renderHandleLink($author_phid));
case PhabricatorTransactions::TYPE_COMMENT:
return pht(
'%s added a comment.',
$this->renderHandleLink($author_phid));
case PhabricatorTransactions::TYPE_VIEW_POLICY:
Improve the clarity of transactions that affect policies and spaces during object creation Summary: Ref T10004. Fixes T9527. Currently, we render two kinds of bad policy/space transactions during object creation. First, we render a transaction showing a change from the default policy/space to the selected policy/space: > alice shifted this object from space S1 Default to space S2 Secret. This is a //good transaction// (it's showing that the default was changed, which could be important for policy stuff!) but it's confusing because it makes it sound like the object briefly existed in space S1, when it did not. Instead, render this: > alice created this object in space S2 Secret. This retains the value (show that the object was created in an unusual space) without the confusion. Second, when you create a "New Bug Report", we render a transaction like this: > alice changed the visibility of this task from "All Users" to "Community". This is distracting and not useful, becasue it's a locked default of the form. This was essentially fixed by D14810. The new behavior is to show this, //only// if the value was changed from the form value: > alice created this object with visibility "Administrators". This should reduce confusion, reduce fluff in the default cases, and do a better job of calling out important changes (basically, unusual spaces/policies). Test Plan: - Created an edit form with a default space and policies. - Used that form to create task with: - same values as form; - different values from form. When I changed the form value, I got transactions. When I left it the same, I didn't. The transactions rendered in the non-confusing "created with ..." variant. Editing the values created normal transactions with "changed policy from X to Y". Reviewers: chad Reviewed By: chad Maniphest Tasks: T9527, T10004 Differential Revision: https://secure.phabricator.com/D14811
2015-12-17 10:29:11 -08:00
if ($this->getIsCreateTransaction()) {
return pht(
'%s created this object with visibility "%s".',
$this->renderHandleLink($author_phid),
$this->renderPolicyName($new, 'new'));
} else {
return pht(
'%s changed the visibility from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$this->renderPolicyName($old, 'old'),
$this->renderPolicyName($new, 'new'));
}
case PhabricatorTransactions::TYPE_EDIT_POLICY:
Improve the clarity of transactions that affect policies and spaces during object creation Summary: Ref T10004. Fixes T9527. Currently, we render two kinds of bad policy/space transactions during object creation. First, we render a transaction showing a change from the default policy/space to the selected policy/space: > alice shifted this object from space S1 Default to space S2 Secret. This is a //good transaction// (it's showing that the default was changed, which could be important for policy stuff!) but it's confusing because it makes it sound like the object briefly existed in space S1, when it did not. Instead, render this: > alice created this object in space S2 Secret. This retains the value (show that the object was created in an unusual space) without the confusion. Second, when you create a "New Bug Report", we render a transaction like this: > alice changed the visibility of this task from "All Users" to "Community". This is distracting and not useful, becasue it's a locked default of the form. This was essentially fixed by D14810. The new behavior is to show this, //only// if the value was changed from the form value: > alice created this object with visibility "Administrators". This should reduce confusion, reduce fluff in the default cases, and do a better job of calling out important changes (basically, unusual spaces/policies). Test Plan: - Created an edit form with a default space and policies. - Used that form to create task with: - same values as form; - different values from form. When I changed the form value, I got transactions. When I left it the same, I didn't. The transactions rendered in the non-confusing "created with ..." variant. Editing the values created normal transactions with "changed policy from X to Y". Reviewers: chad Reviewed By: chad Maniphest Tasks: T9527, T10004 Differential Revision: https://secure.phabricator.com/D14811
2015-12-17 10:29:11 -08:00
if ($this->getIsCreateTransaction()) {
return pht(
'%s created this object with edit policy "%s".',
$this->renderHandleLink($author_phid),
$this->renderPolicyName($new, 'new'));
} else {
return pht(
'%s changed the edit policy from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$this->renderPolicyName($old, 'old'),
$this->renderPolicyName($new, 'new'));
}
case PhabricatorTransactions::TYPE_JOIN_POLICY:
Improve the clarity of transactions that affect policies and spaces during object creation Summary: Ref T10004. Fixes T9527. Currently, we render two kinds of bad policy/space transactions during object creation. First, we render a transaction showing a change from the default policy/space to the selected policy/space: > alice shifted this object from space S1 Default to space S2 Secret. This is a //good transaction// (it's showing that the default was changed, which could be important for policy stuff!) but it's confusing because it makes it sound like the object briefly existed in space S1, when it did not. Instead, render this: > alice created this object in space S2 Secret. This retains the value (show that the object was created in an unusual space) without the confusion. Second, when you create a "New Bug Report", we render a transaction like this: > alice changed the visibility of this task from "All Users" to "Community". This is distracting and not useful, becasue it's a locked default of the form. This was essentially fixed by D14810. The new behavior is to show this, //only// if the value was changed from the form value: > alice created this object with visibility "Administrators". This should reduce confusion, reduce fluff in the default cases, and do a better job of calling out important changes (basically, unusual spaces/policies). Test Plan: - Created an edit form with a default space and policies. - Used that form to create task with: - same values as form; - different values from form. When I changed the form value, I got transactions. When I left it the same, I didn't. The transactions rendered in the non-confusing "created with ..." variant. Editing the values created normal transactions with "changed policy from X to Y". Reviewers: chad Reviewed By: chad Maniphest Tasks: T9527, T10004 Differential Revision: https://secure.phabricator.com/D14811
2015-12-17 10:29:11 -08:00
if ($this->getIsCreateTransaction()) {
return pht(
'%s created this object with join policy "%s".',
$this->renderHandleLink($author_phid),
$this->renderPolicyName($new, 'new'));
} else {
return pht(
'%s changed the join policy from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$this->renderPolicyName($old, 'old'),
$this->renderPolicyName($new, 'new'));
}
case PhabricatorTransactions::TYPE_SPACE:
Improve the clarity of transactions that affect policies and spaces during object creation Summary: Ref T10004. Fixes T9527. Currently, we render two kinds of bad policy/space transactions during object creation. First, we render a transaction showing a change from the default policy/space to the selected policy/space: > alice shifted this object from space S1 Default to space S2 Secret. This is a //good transaction// (it's showing that the default was changed, which could be important for policy stuff!) but it's confusing because it makes it sound like the object briefly existed in space S1, when it did not. Instead, render this: > alice created this object in space S2 Secret. This retains the value (show that the object was created in an unusual space) without the confusion. Second, when you create a "New Bug Report", we render a transaction like this: > alice changed the visibility of this task from "All Users" to "Community". This is distracting and not useful, becasue it's a locked default of the form. This was essentially fixed by D14810. The new behavior is to show this, //only// if the value was changed from the form value: > alice created this object with visibility "Administrators". This should reduce confusion, reduce fluff in the default cases, and do a better job of calling out important changes (basically, unusual spaces/policies). Test Plan: - Created an edit form with a default space and policies. - Used that form to create task with: - same values as form; - different values from form. When I changed the form value, I got transactions. When I left it the same, I didn't. The transactions rendered in the non-confusing "created with ..." variant. Editing the values created normal transactions with "changed policy from X to Y". Reviewers: chad Reviewed By: chad Maniphest Tasks: T9527, T10004 Differential Revision: https://secure.phabricator.com/D14811
2015-12-17 10:29:11 -08:00
if ($this->getIsCreateTransaction()) {
return pht(
'%s created this object in space %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($new));
} else {
return pht(
'%s shifted this object from the %s space to the %s space.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($old),
$this->renderHandleLink($new));
}
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
$add = array_diff($new, $old);
$rem = array_diff($old, $new);
if ($add && $rem) {
return pht(
'%s edited subscriber(s), added %d: %s; removed %d: %s.',
$this->renderHandleLink($author_phid),
count($add),
$this->renderSubscriberList($add, 'add'),
count($rem),
$this->renderSubscriberList($rem, 'rem'));
} else if ($add) {
return pht(
'%s added %d subscriber(s): %s.',
$this->renderHandleLink($author_phid),
count($add),
$this->renderSubscriberList($add, 'add'));
} else if ($rem) {
return pht(
'%s removed %d subscriber(s): %s.',
$this->renderHandleLink($author_phid),
count($rem),
$this->renderSubscriberList($rem, 'rem'));
} else {
// This is used when rendering previews, before the user actually
// selects any CCs.
return pht(
'%s updated subscribers...',
$this->renderHandleLink($author_phid));
}
break;
case PhabricatorTransactions::TYPE_EDGE:
$record = PhabricatorEdgeChangeRecord::newFromTransaction($this);
$add = $record->getAddedPHIDs();
$rem = $record->getRemovedPHIDs();
$type = $this->getMetadata('edge:type');
$type = head($type);
try {
$type_obj = PhabricatorEdgeType::getByConstant($type);
} catch (Exception $ex) {
// Recover somewhat gracefully from edge transactions which
// we don't have the classes for.
return pht(
'%s edited an edge.',
$this->renderHandleLink($author_phid));
}
if ($add && $rem) {
return $type_obj->getTransactionEditString(
$this->renderHandleLink($author_phid),
new PhutilNumber(count($add) + count($rem)),
phutil_count($add),
$this->renderHandleList($add),
phutil_count($rem),
$this->renderHandleList($rem));
} else if ($add) {
return $type_obj->getTransactionAddString(
$this->renderHandleLink($author_phid),
phutil_count($add),
$this->renderHandleList($add));
} else if ($rem) {
return $type_obj->getTransactionRemoveString(
$this->renderHandleLink($author_phid),
phutil_count($rem),
$this->renderHandleList($rem));
} else {
return $type_obj->getTransactionPreviewString(
$this->renderHandleLink($author_phid));
}
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$field = $this->getTransactionCustomField();
if ($field) {
return $field->getApplicationTransactionTitle($this);
} else {
$developer_mode = 'phabricator.developer-mode';
$is_developer = PhabricatorEnv::getEnvConfig($developer_mode);
if ($is_developer) {
return pht(
'%s edited a custom field (with key "%s").',
$this->renderHandleLink($author_phid),
$this->getMetadata('customfield:key'));
} else {
return pht(
'%s edited a custom field.',
$this->renderHandleLink($author_phid));
}
}
case PhabricatorTransactions::TYPE_TOKEN:
if ($old && $new) {
return pht(
'%s updated a token.',
$this->renderHandleLink($author_phid));
} else if ($old) {
return pht(
'%s rescinded a token.',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s awarded a token.',
$this->renderHandleLink($author_phid));
}
case PhabricatorTransactions::TYPE_INLINESTATE:
When users mark their own inline comments as "Done", suppress the timeline/mail stories Summary: Depends on D19858. Ref T13222. See PHI995. In D19635 and related revisions, inline behavior changed to allow you to pre-mark your own inlines as done (as a reviewer) and to pre-mark your inlines for you (as an author). These actions generate low-value stories in the timeline, like "alice marked 3 comments done." when an author adds some notes to their own revision. These aren't helpful and can be a little misleading. Instead, just don't count it when someone marks their own inlines as "done". If we throw away all the marks after throwing away the self-marks, hide the whole story. This happens in three cases: # You comment on your own revision, and don't uncheck the "Done" checkbox. # You comment on someone else's revision, and check the "Done" checkbox before submitting. # You leave a not-"Done" inline on your own revision, then "Done" it later. Cases (1) and (2) seem unambiguously good/clear. Case (3) is a little more questionable, but I think this still isn't very useful for reviewers. If there's still a clarity issue around case (3), we could change the story text to "alice marked 3 inline comments by other users as done.", but I think this is probably needlessly verbose and that no one will be confused by the behavior as written here. (Also note that this story is never shown in feed.) Test Plan: Created and marked a bunch of inlines as "Done" in Differential and Diffusion, as the author and reviewer/auditor. My own marks didn't generate timeline stories; marking others' comments still does. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13222 Differential Revision: https://secure.phabricator.com/D19859
2018-12-10 10:58:51 -08:00
list($done, $undone) = $this->getInterestingInlineStateChangeCounts();
if ($done && $undone) {
return pht(
'%s marked %s inline comment(s) as done and %s inline comment(s) '.
'as not done.',
$this->renderHandleLink($author_phid),
new PhutilNumber($done),
new PhutilNumber($undone));
} else if ($done) {
return pht(
'%s marked %s inline comment(s) as done.',
$this->renderHandleLink($author_phid),
new PhutilNumber($done));
} else {
return pht(
'%s marked %s inline comment(s) as not done.',
$this->renderHandleLink($author_phid),
new PhutilNumber($undone));
}
break;
case PhabricatorTransactions::TYPE_COLUMNS:
$moves = $this->getInterestingMoves($new);
if (count($moves) == 1) {
$move = head($moves);
$from_columns = $move['fromColumnPHIDs'];
$to_column = $move['columnPHID'];
$board_phid = $move['boardPHID'];
if (count($from_columns) == 1) {
return pht(
'%s moved this task from %s to %s on the %s board.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink(head($from_columns)),
$this->renderHandleLink($to_column),
$this->renderHandleLink($board_phid));
} else {
return pht(
'%s moved this task to %s on the %s board.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($to_column),
$this->renderHandleLink($board_phid));
}
} else {
$fragments = array();
foreach ($moves as $move) {
$fragments[] = pht(
'%s (%s)',
$this->renderHandleLink($board_phid),
$this->renderHandleLink($to_column));
}
return pht(
'%s moved this task on %s board(s): %s.',
$this->renderHandleLink($author_phid),
phutil_count($moves),
phutil_implode_html(', ', $fragments));
}
break;
case PhabricatorTransactions::TYPE_MFA:
return pht(
'%s signed these changes with MFA.',
$this->renderHandleLink($author_phid));
default:
// In developer mode, provide a better hint here about which string
// we're missing.
$developer_mode = 'phabricator.developer-mode';
$is_developer = PhabricatorEnv::getEnvConfig($developer_mode);
if ($is_developer) {
return pht(
'%s edited this object (transaction type "%s").',
$this->renderHandleLink($author_phid),
$this->getTransactionType());
} else {
return pht(
'%s edited this %s.',
$this->renderHandleLink($author_phid),
$this->getApplicationObjectTypeName());
}
}
}
public function getTitleForFeed() {
$author_phid = $this->getAuthorPHID();
$object_phid = $this->getObjectPHID();
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_CREATE:
return pht(
'%s created %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
case PhabricatorTransactions::TYPE_COMMENT:
return pht(
'%s added a comment to %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
case PhabricatorTransactions::TYPE_VIEW_POLICY:
return pht(
'%s changed the visibility for %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
case PhabricatorTransactions::TYPE_EDIT_POLICY:
return pht(
'%s changed the edit policy for %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
case PhabricatorTransactions::TYPE_JOIN_POLICY:
return pht(
'%s changed the join policy for %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
return pht(
'%s updated subscribers of %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
case PhabricatorTransactions::TYPE_SPACE:
if ($this->getIsCreateTransaction()) {
return pht(
'%s created %s in the %s space.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
$this->renderHandleLink($new));
} else {
return pht(
'%s shifted %s from the %s space to the %s space.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
$this->renderHandleLink($old),
$this->renderHandleLink($new));
}
case PhabricatorTransactions::TYPE_EDGE:
$record = PhabricatorEdgeChangeRecord::newFromTransaction($this);
$add = $record->getAddedPHIDs();
$rem = $record->getRemovedPHIDs();
$type = $this->getMetadata('edge:type');
$type = head($type);
$type_obj = PhabricatorEdgeType::getByConstant($type);
if ($add && $rem) {
return $type_obj->getFeedEditString(
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
new PhutilNumber(count($add) + count($rem)),
phutil_count($add),
$this->renderHandleList($add),
phutil_count($rem),
$this->renderHandleList($rem));
} else if ($add) {
return $type_obj->getFeedAddString(
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
phutil_count($add),
$this->renderHandleList($add));
} else if ($rem) {
return $type_obj->getFeedRemoveString(
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
phutil_count($rem),
$this->renderHandleList($rem));
} else {
return pht(
'%s edited edge metadata for %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
}
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$field = $this->getTransactionCustomField();
if ($field) {
return $field->getApplicationTransactionTitleForFeed($this);
} else {
return pht(
'%s edited a custom field on %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
}
case PhabricatorTransactions::TYPE_COLUMNS:
$moves = $this->getInterestingMoves($new);
if (count($moves) == 1) {
$move = head($moves);
$from_columns = $move['fromColumnPHIDs'];
$to_column = $move['columnPHID'];
$board_phid = $move['boardPHID'];
if (count($from_columns) == 1) {
return pht(
'%s moved %s from %s to %s on the %s board.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
$this->renderHandleLink(head($from_columns)),
$this->renderHandleLink($to_column),
$this->renderHandleLink($board_phid));
} else {
return pht(
'%s moved %s to %s on the %s board.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
$this->renderHandleLink($to_column),
$this->renderHandleLink($board_phid));
}
} else {
$fragments = array();
foreach ($moves as $move) {
$fragments[] = pht(
'%s (%s)',
$this->renderHandleLink($board_phid),
$this->renderHandleLink($to_column));
}
return pht(
'%s moved %s on %s board(s): %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
phutil_count($moves),
phutil_implode_html(', ', $fragments));
}
break;
case PhabricatorTransactions::TYPE_MFA:
return null;
}
return $this->getTitle();
}
public function getMarkupFieldsForFeed(PhabricatorFeedStory $story) {
$fields = array();
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
$text = $this->getComment()->getContent();
if (strlen($text)) {
$fields[] = 'comment/'.$this->getID();
}
break;
}
return $fields;
}
public function getMarkupTextForFeed(PhabricatorFeedStory $story, $field) {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
$text = $this->getComment()->getContent();
return PhabricatorMarkupEngine::summarize($text);
}
return null;
}
public function getBodyForFeed(PhabricatorFeedStory $story) {
$remarkup = $this->getRemarkupBodyForFeed($story);
if ($remarkup !== null) {
$remarkup = PhabricatorMarkupEngine::summarize($remarkup);
return new PHUIRemarkupView($this->viewer, $remarkup);
}
$old = $this->getOldValue();
$new = $this->getNewValue();
$body = null;
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
$text = $this->getComment()->getContent();
if (strlen($text)) {
$body = $story->getMarkupFieldOutput('comment/'.$this->getID());
}
break;
}
return $body;
}
public function getRemarkupBodyForFeed(PhabricatorFeedStory $story) {
return null;
}
public function getActionStrength() {
if ($this->isInlineCommentTransaction()) {
return 0.25;
}
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
return 0.5;
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
if ($this->isSelfSubscription()) {
// Make this weaker than TYPE_COMMENT.
return 0.25;
}
if ($this->isApplicationAuthor()) {
// When applications (most often: Herald) change subscriptions it
// is very uninteresting.
return 0.000000001;
}
// In other cases, subscriptions are more interesting than comments
// (which are shown anyway) but less interesting than any other type of
// transaction.
return 0.75;
case PhabricatorTransactions::TYPE_MFA:
// We want MFA signatures to render at the top of transaction groups,
// on top of the things they signed.
return 10;
}
return 1.0;
}
public function isCommentTransaction() {
if ($this->hasComment()) {
return true;
}
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
return true;
}
return false;
}
public function isInlineCommentTransaction() {
return false;
}
public function getActionName() {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
return pht('Commented On');
case PhabricatorTransactions::TYPE_VIEW_POLICY:
case PhabricatorTransactions::TYPE_EDIT_POLICY:
case PhabricatorTransactions::TYPE_JOIN_POLICY:
return pht('Changed Policy');
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
return pht('Changed Subscribers');
default:
return pht('Updated');
}
}
public function getMailTags() {
return array();
}
public function hasChangeDetails() {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$field = $this->getTransactionCustomField();
if ($field) {
return $field->getApplicationTransactionHasChangeDetails($this);
}
break;
}
return false;
}
public function hasChangeDetailsForMail() {
return $this->hasChangeDetails();
}
public function renderChangeDetailsForMail(PhabricatorUser $viewer) {
$view = $this->renderChangeDetails($viewer);
if ($view instanceof PhabricatorApplicationTransactionTextDiffDetailView) {
return $view->renderForMail();
}
return null;
}
public function renderChangeDetails(PhabricatorUser $viewer) {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$field = $this->getTransactionCustomField();
if ($field) {
return $field->getApplicationTransactionChangeDetails($this, $viewer);
}
break;
}
return $this->renderTextCorpusChangeDetails(
$viewer,
$this->getOldValue(),
$this->getNewValue());
}
public function renderTextCorpusChangeDetails(
PhabricatorUser $viewer,
$old,
$new) {
return id(new PhabricatorApplicationTransactionTextDiffDetailView())
->setUser($viewer)
->setOldText($old)
->setNewText($new);
}
public function attachTransactionGroup(array $group) {
assert_instances_of($group, __CLASS__);
$this->transactionGroup = $group;
return $this;
}
public function getTransactionGroup() {
return $this->transactionGroup;
}
/**
* Should this transaction be visually grouped with an existing transaction
* group?
*
* @param list<PhabricatorApplicationTransaction> List of transactions.
* @return bool True to display in a group with the other transactions.
*/
public function shouldDisplayGroupWith(array $group) {
$this_source = null;
if ($this->getContentSource()) {
$this_source = $this->getContentSource()->getSource();
}
$type_mfa = PhabricatorTransactions::TYPE_MFA;
foreach ($group as $xaction) {
// Don't group transactions by different authors.
if ($xaction->getAuthorPHID() != $this->getAuthorPHID()) {
return false;
}
// Don't group transactions for different objects.
if ($xaction->getObjectPHID() != $this->getObjectPHID()) {
return false;
}
// Don't group anything into a group which already has a comment.
if ($xaction->isCommentTransaction()) {
return false;
}
// Don't group transactions from different content sources.
$other_source = null;
if ($xaction->getContentSource()) {
$other_source = $xaction->getContentSource()->getSource();
}
if ($other_source != $this_source) {
return false;
}
// Don't group transactions which happened more than 2 minutes apart.
$apart = abs($xaction->getDateCreated() - $this->getDateCreated());
if ($apart > (60 * 2)) {
return false;
}
// Don't group silent and nonsilent transactions together.
$is_silent = $this->getIsSilentTransaction();
if ($is_silent != $xaction->getIsSilentTransaction()) {
return false;
}
// Don't group MFA and non-MFA transactions together.
$is_mfa = $this->getIsMFATransaction();
if ($is_mfa != $xaction->getIsMFATransaction()) {
return false;
}
// Don't group two "Sign with MFA" transactions together.
if ($this->getTransactionType() === $type_mfa) {
if ($xaction->getTransactionType() === $type_mfa) {
return false;
}
}
// Don't group lock override and non-override transactions together.
$is_override = $this->getIsLockOverrideTransaction();
if ($is_override != $xaction->getIsLockOverrideTransaction()) {
return false;
}
}
return true;
}
public function renderExtraInformationLink() {
$herald_xscript_id = $this->getMetadataValue('herald:transcriptID');
if ($herald_xscript_id) {
return phutil_tag(
'a',
array(
'href' => '/herald/transcript/'.$herald_xscript_id.'/',
),
pht('View Herald Transcript'));
}
return null;
}
public function renderAsTextForDoorkeeper(
DoorkeeperFeedStoryPublisher $publisher,
PhabricatorFeedStory $story,
array $xactions) {
$text = array();
$body = array();
foreach ($xactions as $xaction) {
$xaction_body = $xaction->getBodyForMail();
if ($xaction_body !== null) {
$body[] = $xaction_body;
}
if ($xaction->shouldHideForMail($xactions)) {
continue;
}
$old_target = $xaction->getRenderingTarget();
$new_target = self::TARGET_TEXT;
$xaction->setRenderingTarget($new_target);
if ($publisher->getRenderWithImpliedContext()) {
$text[] = $xaction->getTitle();
} else {
$text[] = $xaction->getTitleForFeed();
}
$xaction->setRenderingTarget($old_target);
}
$text = implode("\n", $text);
$body = implode("\n\n", $body);
return rtrim($text."\n\n".$body);
}
/**
* Test if this transaction is just a user subscribing or unsubscribing
* themselves.
*/
private function isSelfSubscription() {
$type = $this->getTransactionType();
if ($type != PhabricatorTransactions::TYPE_SUBSCRIBERS) {
return false;
}
$old = $this->getOldValue();
$new = $this->getNewValue();
$add = array_diff($old, $new);
$rem = array_diff($new, $old);
if ((count($add) + count($rem)) != 1) {
// More than one user affected.
return false;
}
$affected_phid = head(array_merge($add, $rem));
if ($affected_phid != $this->getAuthorPHID()) {
// Affected user is someone else.
return false;
}
return true;
}
private function isApplicationAuthor() {
$author_phid = $this->getAuthorPHID();
$author_type = phid_get_type($author_phid);
$application_type = PhabricatorApplicationApplicationPHIDType::TYPECONST;
return ($author_type == $application_type);
}
private function getInterestingMoves(array $moves) {
// Remove moves which only shift the position of a task within a column.
foreach ($moves as $key => $move) {
$from_phids = array_fuse($move['fromColumnPHIDs']);
if (isset($from_phids[$move['columnPHID']])) {
unset($moves[$key]);
}
}
return $moves;
}
When users mark their own inline comments as "Done", suppress the timeline/mail stories Summary: Depends on D19858. Ref T13222. See PHI995. In D19635 and related revisions, inline behavior changed to allow you to pre-mark your own inlines as done (as a reviewer) and to pre-mark your inlines for you (as an author). These actions generate low-value stories in the timeline, like "alice marked 3 comments done." when an author adds some notes to their own revision. These aren't helpful and can be a little misleading. Instead, just don't count it when someone marks their own inlines as "done". If we throw away all the marks after throwing away the self-marks, hide the whole story. This happens in three cases: # You comment on your own revision, and don't uncheck the "Done" checkbox. # You comment on someone else's revision, and check the "Done" checkbox before submitting. # You leave a not-"Done" inline on your own revision, then "Done" it later. Cases (1) and (2) seem unambiguously good/clear. Case (3) is a little more questionable, but I think this still isn't very useful for reviewers. If there's still a clarity issue around case (3), we could change the story text to "alice marked 3 inline comments by other users as done.", but I think this is probably needlessly verbose and that no one will be confused by the behavior as written here. (Also note that this story is never shown in feed.) Test Plan: Created and marked a bunch of inlines as "Done" in Differential and Diffusion, as the author and reviewer/auditor. My own marks didn't generate timeline stories; marking others' comments still does. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13222 Differential Revision: https://secure.phabricator.com/D19859
2018-12-10 10:58:51 -08:00
private function getInterestingInlineStateChangeCounts() {
// See PHI995. Newer inline state transactions have additional details
// which we use to tailor the rendering behavior. These details are not
// present on older transactions.
$details = $this->getMetadataValue('inline.details', array());
$new = $this->getNewValue();
$done = 0;
$undone = 0;
foreach ($new as $phid => $state) {
$is_done = ($state == PhabricatorInlineCommentInterface::STATE_DONE);
// See PHI995. If you're marking your own inline comments as "Done",
// don't count them when rendering a timeline story. In the case where
// you're only affecting your own comments, this will hide the
// "alice marked X comments as done" story entirely.
// Usually, this happens when you pre-mark inlines as "done" and submit
// them yourself. We'll still generate an "alice added inline comments"
// story (in most cases/contexts), but the state change story is largely
// just clutter and slightly confusing/misleading.
$inline_details = idx($details, $phid, array());
$inline_author_phid = idx($inline_details, 'authorPHID');
if ($inline_author_phid) {
if ($inline_author_phid == $this->getAuthorPHID()) {
if ($is_done) {
continue;
}
}
}
if ($is_done) {
$done++;
} else {
$undone++;
}
}
return array($done, $undone);
}
/* -( PhabricatorPolicyInterface Implementation )-------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return $this->getViewPolicy();
case PhabricatorPolicyCapability::CAN_EDIT:
return $this->getEditPolicy();
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return ($viewer->getPHID() == $this->getAuthorPHID());
}
public function describeAutomaticCapability($capability) {
return pht(
'Transactions are visible to users that can see the object which was '.
'acted upon. Some transactions - in particular, comments - are '.
'editable by the transaction author.');
}
public function getModularType() {
return null;
}
public function setForceNotifyPHIDs(array $phids) {
$this->setMetadataValue('notify.force', $phids);
return $this;
}
public function getForceNotifyPHIDs() {
return $this->getMetadataValue('notify.force', array());
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
$this->openTransaction();
$comment_template = $this->getApplicationTransactionCommentObject();
if ($comment_template) {
$comments = $comment_template->loadAllWhere(
'transactionPHID = %s',
$this->getPHID());
foreach ($comments as $comment) {
$engine->destroyObject($comment);
}
}
$this->delete();
$this->saveTransaction();
}
}