1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-20 13:52:40 +01:00

Support "template objects" generically in EditEngine

Summary:
Ref T9132. Ref T9908. Fixes T5622. This allows you to copy some fields (projects, subscribers, custom fields, some per-application) from another object when creating a new object by passing the `?template=xyz` parameter.

Extend "copy" support to work with all custom fields.

Test Plan:
  - Created new pastes, packages, tasks using `?template=...`
  - Viewed new template docs page.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T5622, T9132, T9908

Differential Revision: https://secure.phabricator.com/D14699
This commit is contained in:
epriestley 2015-12-07 11:37:51 -08:00
parent 2c9257b394
commit 468f785845
13 changed files with 154 additions and 19 deletions

View file

@ -81,6 +81,7 @@ final class ManiphestEditEngine
->setLabel(pht('Status'))
->setDescription(pht('Status of the task.'))
->setTransactionType(ManiphestTransaction::TYPE_STATUS)
->setIsCopyable(true)
->setValue($object->getStatus())
->setOptions($status_map)
->setCommentActionLabel(pht('Change Status'))
@ -91,6 +92,7 @@ final class ManiphestEditEngine
->setLabel(pht('Assigned To'))
->setDescription(pht('User who is responsible for the task.'))
->setTransactionType(ManiphestTransaction::TYPE_OWNER)
->setIsCopyable(true)
->setSingleValue($object->getOwnerPHID())
->setCommentActionLabel(pht('Assign / Claim'))
->setCommentActionDefaultValue($owner_value),
@ -99,6 +101,7 @@ final class ManiphestEditEngine
->setLabel(pht('Priority'))
->setDescription(pht('Priority of the task.'))
->setTransactionType(ManiphestTransaction::TYPE_PRIORITY)
->setIsCopyable(true)
->setValue($object->getPriority())
->setOptions($priority_map)
->setCommentActionLabel($priority_label),

View file

@ -58,6 +58,7 @@ final class PhabricatorOwnersPackageEditEngine
->setDescription(pht('Users and projects which own the package.'))
->setTransactionType(PhabricatorOwnersPackageTransaction::TYPE_OWNERS)
->setDatasource(new PhabricatorProjectOrUserDatasource())
->setIsCopyable(true)
->setValue($object->getOwnerPHIDs()),
id(new PhabricatorSelectEditField())
->setKey('status')
@ -74,6 +75,7 @@ final class PhabricatorOwnersPackageEditEngine
'Automatically trigger audits for commits affecting files in '.
'this package.'))
->setTransactionType(PhabricatorOwnersPackageTransaction::TYPE_AUDITING)
->setIsCopyable(true)
->setValue($object->getAuditingEnabled())
->setOptions(
array(

View file

@ -73,6 +73,7 @@ final class PhabricatorPasteEditEngine
'title.'))
->setAliases(array('lang'))
->setTransactionType(PhabricatorPasteTransaction::TYPE_LANGUAGE)
->setIsCopyable(true)
->setValue($object->getLanguage())
->setOptions($langs),
id(new PhabricatorSelectEditField())

View file

@ -83,6 +83,7 @@ final class PhabricatorPolicyEditEngineExtension
->setLabel($label)
->setDescription($description)
->setAliases($aliases)
->setIsCopyable(true)
->setCapability($capability)
->setPolicies($policies)
->setTransactionType($type)
@ -100,6 +101,7 @@ final class PhabricatorPolicyEditEngineExtension
->setEditTypeKey('space')
->setDescription(
pht('Shifts the object in the Spaces application.'))
->setIsCopyable(true)
->setIsReorderable(false)
->setAliases(array('space', 'policy.space'))
->setTransactionType($type_space)

View file

@ -48,6 +48,7 @@ final class PhabricatorProjectsEditEngineExtension
->setEditTypeKey('projects')
->setDescription(pht('Add or remove associated projects.'))
->setAliases(array('project', 'projects'))
->setIsCopyable(true)
->setUseEdgeTransactions(true)
->setEdgeTransactionDescriptions(
pht('Add projects.'),

View file

@ -45,6 +45,7 @@ final class PhabricatorSubscriptionsEditEngineExtension
->setEditTypeKey('subscribers')
->setDescription(pht('Manage subscribers.'))
->setAliases(array('subscriber', 'subscribers'))
->setIsCopyable(true)
->setUseEdgeTransactions(true)
->setEdgeTransactionDescriptions(
pht('Add subscribers.'),

View file

@ -95,7 +95,7 @@ abstract class PhabricatorEditEngine
}
$config = $this->getEditEngineConfiguration();
$fields = $config->applyConfigurationToFields($this, $fields);
$fields = $config->applyConfigurationToFields($this, $object, $fields);
foreach ($fields as $field) {
$field
@ -443,12 +443,16 @@ abstract class PhabricatorEditEngine
* to make Conduit a little easier to use.
*
* @param wild ID, PHID, or monogram.
* @param list<const> List of required capability constants, or omit for
* defaults.
* @return object Corresponding editable object.
* @task load
*/
private function newObjectFromIdentifier($identifier) {
private function newObjectFromIdentifier(
$identifier,
array $capabilities = array()) {
if (is_int($identifier) || ctype_digit($identifier)) {
$object = $this->newObjectFromID($identifier);
$object = $this->newObjectFromID($identifier, $capabilities);
if (!$object) {
throw new Exception(
@ -462,7 +466,7 @@ abstract class PhabricatorEditEngine
$type_unknown = PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN;
if (phid_get_type($identifier) != $type_unknown) {
$object = $this->newObjectFromPHID($identifier);
$object = $this->newObjectFromPHID($identifier, $capabilities);
if (!$object) {
throw new Exception(
@ -503,7 +507,7 @@ abstract class PhabricatorEditEngine
// sure it's really valid, goes through standard policy check logic, and
// picks up any `need...()` clauses we want it to load with.
$object = $this->newObjectFromPHID($target->getPHID());
$object = $this->newObjectFromPHID($target->getPHID(), $capabilities);
if (!$object) {
throw new Exception(
pht(
@ -536,14 +540,16 @@ abstract class PhabricatorEditEngine
* Load an object by PHID.
*
* @param phid Object PHID.
* @param list<const> List of required capability constants, or omit for
* defaults.
* @return object|null Object, or null if no such object exists.
* @task load
*/
private function newObjectFromPHID($phid) {
private function newObjectFromPHID($phid, array $capabilities = array()) {
$query = $this->newObjectQuery()
->withPHIDs(array($phid));
return $this->newObjectFromQuery($query);
return $this->newObjectFromQuery($query, $capabilities);
}
@ -629,6 +635,9 @@ abstract class PhabricatorEditEngine
);
$use_default = true;
break;
case 'parameters':
$use_default = true;
break;
default:
break;
}
@ -769,11 +778,43 @@ abstract class PhabricatorEditEngine
}
} else {
if ($this->getIsCreate()) {
$template = $request->getStr('template');
if (strlen($template)) {
$template_object = $this->newObjectFromIdentifier(
$template,
array(
PhabricatorPolicyCapability::CAN_VIEW,
));
if (!$template_object) {
return new Aphront404Response();
}
} else {
$template_object = null;
}
if ($template_object) {
$copy_fields = $this->buildEditFields($template_object);
$copy_fields = mpull($copy_fields, null, 'getKey');
foreach ($copy_fields as $copy_key => $copy_field) {
if (!$copy_field->getIsCopyable()) {
unset($copy_fields[$copy_key]);
}
}
} else {
$copy_fields = array();
}
foreach ($fields as $field) {
if ($field->getIsLocked() || $field->getIsHidden()) {
continue;
}
$field_key = $field->getKey();
if (isset($copy_fields[$field_key])) {
$field->readValueFromField($copy_fields[$field_key]);
}
$field->readValueFromRequest($request);
}
}

View file

@ -28,6 +28,7 @@ abstract class PhabricatorEditField extends Phobject {
private $isReorderable = true;
private $isDefaultable = true;
private $isLockable = true;
private $isCopyable = false;
public function setKey($key) {
$this->key = $key;
@ -146,6 +147,15 @@ abstract class PhabricatorEditField extends Phobject {
return $this->isHidden;
}
public function setIsCopyable($is_copyable) {
$this->isCopyable = $is_copyable;
return $this;
}
public function getIsCopyable() {
return $this->isCopyable;
}
public function setIsSubmittedForm($is_submitted) {
$this->isSubmittedForm = $is_submitted;
return $this;
@ -366,6 +376,15 @@ abstract class PhabricatorEditField extends Phobject {
return $this->getHTTPParameterValue($request, $key);
}
public function readValueFromField(PhabricatorEditField $other) {
$this->value = $this->getValueFromField($other);
return $this;
}
protected function getValueFromField(PhabricatorEditField $other) {
return $other->getValue();
}
/**
* Read and return the value the object had when the user first loaded the

View file

@ -89,12 +89,15 @@ final class PhabricatorEditEngineConfiguration
public function applyConfigurationToFields(
PhabricatorEditEngine $engine,
$object,
array $fields) {
$fields = mpull($fields, null, 'getKey');
$is_new = !$object->getID();
$values = $this->getProperty('defaults', array());
foreach ($fields as $key => $field) {
if ($engine->getIsCreate()) {
if ($is_new) {
if (array_key_exists($key, $values)) {
$field->readDefaultValueFromConfiguration($values[$key]);
}

View file

@ -176,6 +176,59 @@ EOTEXT
'wide',
));
$template_text = pht(<<<EOTEXT
Template Objects
----------------
Instead of specifying each field value individually, you can specify another
object to use as a template. Some of the initial fields will be copied from the
template object.
Specify a template object with the `template` parameter. You can use an ID,
PHID, or monogram (for objects which have monograms). For example, you might
use URIs like these:
```
%s?template=123
%s?template=PHID-WXYZ-abcdef...
%s?template=T123
```
You can combine the `template` parameter with HTTP parameters: the template
object will be copied first, then any HTTP parameters will be read.
When using `template`, these fields will be copied:
EOTEXT
,
$uri,
$uri,
$uri);
$yes = id(new PHUIIconView())->setIconFont('fa-check-circle green');
$no = id(new PHUIIconView())->setIconFont('fa-times grey');
$rows = array();
foreach ($fields as $field) {
$rows[] = array(
$field->getLabel(),
$field->getIsCopyable() ? $yes : $no,
);
}
$template_table = id(new AphrontTableView($rows))
->setNoDataString(
pht('None of the fields on this object support templating.'))
->setHeaders(
array(
pht('Field'),
pht('Will Copy'),
))
->setColumnClasses(
array(
'pri',
'wide',
));
$select_text = pht(<<<EOTEXT
Select Fields
-------------
@ -243,6 +296,8 @@ EOTEXT
$main_table,
$this->renderInstructions($aliases_text),
$alias_table,
$this->renderInstructions($template_text),
$template_table,
$this->renderInstructions($select_text),
$select_table,
$this->renderInstructions($types_text),

View file

@ -119,6 +119,8 @@ When defining custom fields using a configuration option like
above the control when rendered on the edit view.
- **placeholder**: A placeholder text that appears on text boxes. Only
supported in text, int and remarkup fields (optional).
- **copy**: If true, this field's value will be copied when an object is
created using another object as a template.
The `strings` value supports different strings per control type. They are:
@ -128,15 +130,6 @@ The `strings` value supports different strings per control type. They are:
- **search.default** Text for the search interface, defaults to "(Any)".
- **search.require** Text for the search interface, defaults to "Require".
Some applications have specific options which only work in that application.
In **Maniphest**:
- **copy**: When a user creates a task, the UI gives them an option to
"Create Another Similar Task". Some fields from the original task are copied
into the new task, while others are not; by default, fields are not copied.
If you want this field to be copied, specify `true` for the `copy` property.
Internally, Phabricator implements some additional custom field types and
options. These are not intended for general use and are subject to abrupt
change, but are documented here for completeness:

View file

@ -35,7 +35,7 @@ final class PhabricatorCustomFieldEditEngineExtension
$field_list->setViewer($viewer);
if (!$engine->getIsCreate()) {
if ($object->getID()) {
$field_list->readFieldsFromStorage($object);
}

View file

@ -15,6 +15,7 @@ abstract class PhabricatorStandardCustomField
private $fieldError;
private $required;
private $default;
private $isCopyable;
abstract public function getFieldType();
@ -115,6 +116,9 @@ abstract class PhabricatorStandardCustomField
case 'default':
$this->setFieldValue($value);
break;
case 'copy':
$this->setIsCopyable($value);
break;
case 'type':
// We set this earlier on.
break;
@ -185,6 +189,15 @@ abstract class PhabricatorStandardCustomField
return idx($this->strings, $key, $default);
}
public function setIsCopyable($is_copyable) {
$this->isCopyable = $is_copyable;
return $this;
}
public function getIsCopyable() {
return $this->isCopyable;
}
public function shouldUseStorage() {
try {
$object = $this->newStorageObject();
@ -430,7 +443,8 @@ abstract class PhabricatorStandardCustomField
$short = 'custom.'.$this->getRawStandardFieldKey();
return parent::newStandardEditField()
->setEditTypeKey($short);
->setEditTypeKey($short)
->setIsCopyable($this->getIsCopyable());
}
public function shouldAppearInConduitTransactions() {