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:
parent
2c9257b394
commit
468f785845
13 changed files with 154 additions and 19 deletions
|
@ -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),
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.'),
|
||||
|
|
|
@ -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.'),
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -35,7 +35,7 @@ final class PhabricatorCustomFieldEditEngineExtension
|
|||
|
||||
$field_list->setViewer($viewer);
|
||||
|
||||
if (!$engine->getIsCreate()) {
|
||||
if ($object->getID()) {
|
||||
$field_list->readFieldsFromStorage($object);
|
||||
}
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
Loading…
Reference in a new issue