1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-18 11:30:55 +01:00

Modularize Herald "flag" action, plus update transcripts

Summary:
Ref T8726. This modularizes "Mark with flag", plus rebuilds transcripts in a more modern/flexible way. The big transcript stuff is:

  - Transcripts are now translatable.
  - Transcripts can now show multiple outputs from a single action. For example, an action like "add A, B, C to subscribers" can now say "added A; B is invalid; C was already subscribed".

Test Plan: {F637784}

Reviewers: chad, btrahan

Reviewed By: btrahan

Subscribers: joshuaspence, eadler, epriestley

Maniphest Tasks: T8726

Differential Revision: https://secure.phabricator.com/D13649
This commit is contained in:
epriestley 2015-07-18 05:54:26 -07:00
parent 56dd5211f0
commit 8d9bd791f7
15 changed files with 349 additions and 425 deletions

View file

@ -73,7 +73,7 @@ return array(
'rsrc/css/application/files/global-drag-and-drop.css' => '697324ad',
'rsrc/css/application/flag/flag.css' => '5337623f',
'rsrc/css/application/harbormaster/harbormaster.css' => '49d64eb4',
'rsrc/css/application/herald/herald-test.css' => '778b008e',
'rsrc/css/application/herald/herald-test.css' => 'a52e323e',
'rsrc/css/application/herald/herald.css' => '826075fa',
'rsrc/css/application/maniphest/batch-editor.css' => 'b0f0b6d5',
'rsrc/css/application/maniphest/report.css' => 'f6931fdf',
@ -539,7 +539,7 @@ return array(
'harbormaster-css' => '49d64eb4',
'herald-css' => '826075fa',
'herald-rule-editor' => '91a6031b',
'herald-test-css' => '778b008e',
'herald-test-css' => 'a52e323e',
'inline-comment-summary-css' => '51efda3a',
'javelin-aphlict' => '5359e785',
'javelin-behavior' => '61cbc29a',

View file

@ -2110,6 +2110,7 @@ phutil_register_library_map(array(
'PhabricatorFilesManagementWorkflow' => 'applications/files/management/PhabricatorFilesManagementWorkflow.php',
'PhabricatorFilesOutboundRequestAction' => 'applications/files/action/PhabricatorFilesOutboundRequestAction.php',
'PhabricatorFlag' => 'applications/flag/storage/PhabricatorFlag.php',
'PhabricatorFlagAddFlagHeraldAction' => 'applications/flag/herald/PhabricatorFlagAddFlagHeraldAction.php',
'PhabricatorFlagColor' => 'applications/flag/constants/PhabricatorFlagColor.php',
'PhabricatorFlagConstants' => 'applications/flag/constants/PhabricatorFlagConstants.php',
'PhabricatorFlagController' => 'applications/flag/controller/PhabricatorFlagController.php',
@ -5988,6 +5989,7 @@ phutil_register_library_map(array(
'PhabricatorFlagDAO',
'PhabricatorPolicyInterface',
),
'PhabricatorFlagAddFlagHeraldAction' => 'HeraldAction',
'PhabricatorFlagColor' => 'PhabricatorFlagConstants',
'PhabricatorFlagConstants' => 'Phobject',
'PhabricatorFlagController' => 'PhabricatorController',

View file

@ -177,7 +177,6 @@ final class HeraldDifferentialRevisionAdapter
self::ACTION_ADD_CC,
self::ACTION_REMOVE_CC,
self::ACTION_EMAIL,
self::ACTION_FLAG,
self::ACTION_ADD_REVIEWERS,
self::ACTION_ADD_BLOCKING_REVIEWERS,
),

View file

@ -102,7 +102,6 @@ final class HeraldCommitAdapter extends HeraldAdapter {
self::ACTION_ADD_CC,
self::ACTION_REMOVE_CC,
self::ACTION_EMAIL,
self::ACTION_FLAG,
self::ACTION_AUDIT,
),
parent::getActions($rule_type));

View file

@ -0,0 +1,88 @@
<?php
final class PhabricatorFlagAddFlagHeraldAction extends HeraldAction {
const ACTIONCONST = 'flag';
const DO_FLAG = 'do.flag';
const DO_IGNORE = 'do.flagged';
public function getHeraldActionName() {
return pht('Mark with flag');
}
public function getActionGroupKey() {
return HeraldSupportActionGroup::ACTIONGROUPKEY;
}
public function supportsObject($object) {
return ($object instanceof PhabricatorFlaggableInterface);
}
public function supportsRuleType($rule_type) {
return ($rule_type == HeraldRuleTypeConfig::RULE_TYPE_PERSONAL);
}
public function applyEffect($object, HeraldEffect $effect) {
$phid = $this->getAdapter()->getPHID();
$rule = $effect->getRule();
$author = $rule->getAuthor();
$flag = PhabricatorFlagQuery::loadUserFlag($author, $phid);
if ($flag) {
$this->logEffect(self::DO_IGNORE, $flag->getColor());
return;
}
$flag = id(new PhabricatorFlag())
->setOwnerPHID($author->getPHID())
->setType(phid_get_type($phid))
->setObjectPHID($phid)
->setReasonPHID($rule->getPHID())
->setColor($effect->getTarget())
->setNote('')
->save();
$this->logEffect(self::DO_FLAG, $flag->getColor());
}
public function getHeraldActionValueType() {
return id(new HeraldSelectFieldValue())
->setKey('flag.color')
->setOptions(PhabricatorFlagColor::getColorNameMap())
->setDefault(PhabricatorFlagColor::COLOR_BLUE);
}
protected function getActionEffectMap() {
return array(
self::DO_IGNORE => array(
'icon' => 'fa-times',
'color' => 'grey',
'name' => pht('Already Marked'),
),
self::DO_FLAG => array(
'icon' => 'fa-flag',
'name' => pht('Flagged'),
),
);
}
public function renderActionDescription($value) {
$color = PhabricatorFlagColor::getColorName($value);
return pht('Mark with %s flag.', $color);
}
public function renderActionEffectDescription($type, $data) {
switch ($type) {
case self::DO_IGNORE:
return pht(
'Already marked with %s flag.',
PhabricatorFlagColor::getColorName($data));
case self::DO_FLAG:
return pht(
'Marked with "%s" flag.',
PhabricatorFlagColor::getColorName($data));
}
}
}

View file

@ -3,6 +3,7 @@
abstract class HeraldAction extends Phobject {
private $adapter;
private $viewer;
private $applyLog = array();
const STANDARD_NONE = 'standard.none';
@ -12,6 +13,7 @@ abstract class HeraldAction extends Phobject {
abstract public function supportsObject($object);
abstract public function supportsRuleType($rule_type);
abstract public function applyEffect($object, HeraldEffect $effect);
abstract public function renderActionEffectDescription($type, $data);
public function getActionGroupKey() {
return null;
@ -66,6 +68,15 @@ abstract class HeraldAction extends Phobject {
return $this->adapter;
}
final public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
final public function getViewer() {
return $this->viewer;
}
final public function getActionConstant() {
$class = new ReflectionClass($this);
@ -106,13 +117,49 @@ abstract class HeraldAction extends Phobject {
}
protected function logEffect($type, $data = null) {
return;
if (!is_string($type)) {
throw new Exception(
pht(
'Effect type passed to "%s" must be a scalar string.',
'logEffect()'));
}
$this->applyLog[] = array(
'type' => $type,
'data' => $data,
);
return $this;
}
final public function getApplyTranscript(HeraldEffect $effect) {
$context = 'v2/'.phutil_json_encode($this->applyLog);
$context = $this->applyLog;
$this->applyLog = array();
return new HeraldApplyTranscript($effect, true, $context);
}
protected function getActionEffectMap() {
throw new PhutilMethodNotImplementedException();
}
private function getActionEffectSpec($type) {
$map = $this->getActionEffectMap();
return idx($map, $type, array());
}
public function renderActionEffectIcon($type, $data) {
$map = $this->getActionEffectSpec($type);
return idx($map, 'icon');
}
public function renderActionEffectColor($type, $data) {
$map = $this->getActionEffectSpec($type);
return idx($map, 'color');
}
public function renderActionEffectName($type, $data) {
$map = $this->getActionEffectSpec($type);
return idx($map, 'name');
}
}

View file

@ -22,11 +22,29 @@ final class HeraldDoNothingAction extends HeraldAction {
}
public function applyEffect($object, HeraldEffect $effect) {
$this->logEffect($effect, self::DO_NOTHING);
$this->logEffect(self::DO_NOTHING);
}
public function getHeraldActionStandardType() {
return self::STANDARD_NONE;
}
protected function getActionEffectMap() {
return array(
self::DO_NOTHING => array(
'icon' => 'fa-check',
'color' => 'grey',
'name' => pht('Did Nothing'),
),
);
}
public function renderActionDescription($value) {
return pht('Do nothing.');
}
public function renderActionEffectDescription($type, $data) {
return pht('Did nothing.');
}
}

View file

@ -30,7 +30,6 @@ abstract class HeraldAdapter extends Phobject {
const ACTION_REMOVE_CC = 'remcc';
const ACTION_EMAIL = 'email';
const ACTION_AUDIT = 'audit';
const ACTION_FLAG = 'flag';
const ACTION_ASSIGN_TASK = 'assigntask';
const ACTION_ADD_PROJECTS = 'addprojects';
const ACTION_REMOVE_PROJECTS = 'removeprojects';
@ -670,7 +669,7 @@ abstract class HeraldAdapter extends Phobject {
return $actions;
}
private function getActionImplementation($key) {
public function getActionImplementation($key) {
return idx($this->getActionImplementationMap(), $key);
}
@ -728,7 +727,6 @@ abstract class HeraldAdapter extends Phobject {
self::ACTION_REMOVE_CC => pht('Remove Subscribers'),
self::ACTION_EMAIL => pht('Send an email to'),
self::ACTION_AUDIT => pht('Trigger an Audit by'),
self::ACTION_FLAG => pht('Mark with flag'),
self::ACTION_ASSIGN_TASK => pht('Assign task to'),
self::ACTION_ADD_PROJECTS => pht('Add projects'),
self::ACTION_REMOVE_PROJECTS => pht('Remove projects'),
@ -745,7 +743,6 @@ abstract class HeraldAdapter extends Phobject {
self::ACTION_REMOVE_CC => pht('Remove me as a subscriber'),
self::ACTION_EMAIL => pht('Send me an email'),
self::ACTION_AUDIT => pht('Trigger an Audit by me'),
self::ACTION_FLAG => pht('Mark with flag'),
self::ACTION_ASSIGN_TASK => pht('Assign task to me'),
self::ACTION_ADD_REVIEWERS => pht('Add me as a reviewer'),
self::ACTION_ADD_BLOCKING_REVIEWERS =>
@ -798,13 +795,6 @@ abstract class HeraldAdapter extends Phobject {
// For personal rules, force these actions to target the rule owner.
$target = array($author_phid);
break;
case self::ACTION_FLAG:
// Make sure flag color is valid; set to blue if not.
$color_map = PhabricatorFlagColor::getColorNameMap();
if (empty($color_map[$target])) {
$target = PhabricatorFlagColor::COLOR_BLUE;
}
break;
case self::ACTION_BLOCK:
break;
default:
@ -846,8 +836,6 @@ abstract class HeraldAdapter extends Phobject {
case self::ACTION_ADD_REVIEWERS:
case self::ACTION_ADD_BLOCKING_REVIEWERS:
return new HeraldEmptyFieldValue();
case self::ACTION_FLAG:
return $this->buildFlagColorFieldValue();
case self::ACTION_ADD_PROJECTS:
case self::ACTION_REMOVE_PROJECTS:
return $this->buildTokenizerFieldValue(
@ -864,8 +852,6 @@ abstract class HeraldAdapter extends Phobject {
case self::ACTION_REMOVE_PROJECTS:
return $this->buildTokenizerFieldValue(
new PhabricatorProjectDatasource());
case self::ACTION_FLAG:
return $this->buildFlagColorFieldValue();
case self::ACTION_ASSIGN_TASK:
return $this->buildTokenizerFieldValue(
new PhabricatorPeopleDatasource());
@ -893,13 +879,6 @@ abstract class HeraldAdapter extends Phobject {
throw new Exception(pht("Unknown or invalid action '%s'.", $action));
}
private function buildFlagColorFieldValue() {
return id(new HeraldSelectFieldValue())
->setKey('flag.color')
->setOptions(PhabricatorFlagColor::getColorNameMap())
->setDefault(PhabricatorFlagColor::COLOR_BLUE);
}
private function buildTokenizerFieldValue(
PhabricatorTypeaheadDatasource $datasource) {
@ -1051,7 +1030,7 @@ abstract class HeraldAdapter extends Phobject {
),
array(
$icon,
$this->renderActionAsText($action, $handles),
$this->renderActionAsText($viewer, $action, $handles),
));
}
@ -1083,8 +1062,16 @@ abstract class HeraldAdapter extends Phobject {
}
private function renderActionAsText(
PhabricatorUser $viewer,
HeraldActionRecord $action,
PhabricatorHandleList $handles) {
$impl = $this->getActionImplementation($action->getAction());
if ($impl) {
$value = $action->getTarget();
return $impl->renderActionDescription($viewer, $value);
}
$rule_global = HeraldRuleTypeConfig::RULE_TYPE_GLOBAL;
$action_type = $action->getAction();
@ -1118,15 +1105,14 @@ abstract class HeraldAdapter extends Phobject {
HeraldActionRecord $action,
PhabricatorHandleList $handles) {
// TODO: This should be driven through HeraldAction.
$target = $action->getTarget();
if (!is_array($target)) {
$target = array($target);
}
foreach ($target as $index => $val) {
switch ($action->getAction()) {
case self::ACTION_FLAG:
$target[$index] = PhabricatorFlagColor::getColorName($val);
break;
default:
$handle = $handles->getHandleIfExists($val);
if ($handle) {
@ -1222,8 +1208,6 @@ abstract class HeraldAdapter extends Phobject {
case self::ACTION_ADD_CC:
case self::ACTION_REMOVE_CC:
return $this->applySubscribersEffect($effect);
case self::ACTION_FLAG:
return $this->applyFlagEffect($effect);
case self::ACTION_EMAIL:
return $this->applyEmailEffect($effect);
default:
@ -1364,50 +1348,6 @@ abstract class HeraldAdapter extends Phobject {
return new HeraldApplyTranscript($effect, true, $message);
}
/**
* @task apply
*/
private function applyFlagEffect(HeraldEffect $effect) {
$phid = $this->getPHID();
$color = $effect->getTarget();
$rule = $effect->getRule();
$user = $rule->getAuthor();
$flag = PhabricatorFlagQuery::loadUserFlag($user, $phid);
if ($flag) {
return new HeraldApplyTranscript(
$effect,
false,
pht('Object already flagged.'));
}
$handle = id(new PhabricatorHandleQuery())
->setViewer($user)
->withPHIDs(array($phid))
->executeOne();
$flag = new PhabricatorFlag();
$flag->setOwnerPHID($user->getPHID());
$flag->setType($handle->getType());
$flag->setObjectPHID($handle->getPHID());
// TOOD: Should really be transcript PHID, but it doesn't exist yet.
$flag->setReasonPHID($user->getPHID());
$flag->setColor($color);
$flag->setNote(
pht('Flagged by Herald Rule "%s".', $rule->getName()));
$flag->save();
return new HeraldApplyTranscript(
$effect,
true,
pht('Added flag.'));
}
/**
* @task apply
*/

View file

@ -58,7 +58,7 @@ final class PhabricatorHeraldApplication extends PhabricatorApplication {
'transcript/' => array(
'' => 'HeraldTranscriptListController',
'(?:query/(?P<queryKey>[^/]+)/)?' => 'HeraldTranscriptListController',
'(?P<id>[1-9]\d*)/(?:(?P<filter>\w+)/)?'
'(?P<id>[1-9]\d*)/'
=> 'HeraldTranscriptController',
),
),

View file

@ -367,7 +367,6 @@ final class HeraldRuleController extends HeraldController {
$serial_actions = array();
foreach ($rule->getActions() as $action) {
switch ($action->getAction()) {
case HeraldAdapter::ACTION_FLAG:
case HeraldAdapter::ACTION_BLOCK:
$current_value = $action->getTarget();
break;

View file

@ -2,43 +2,26 @@
final class HeraldTranscriptController extends HeraldController {
const FILTER_AFFECTED = 'affected';
const FILTER_OWNED = 'owned';
const FILTER_ALL = 'all';
private $id;
private $filter;
private $handles;
private $adapter;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
$map = $this->getFilterMap();
$this->filter = idx($data, 'filter');
if (empty($map[$this->filter])) {
$this->filter = self::FILTER_ALL;
}
}
private function getAdapter() {
return $this->adapter;
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$xscript = id(new HeraldTranscriptQuery())
->setViewer($viewer)
->withIDs(array($this->id))
->withIDs(array($request->getURIData('id')))
->executeOne();
if (!$xscript) {
return new Aphront404Response();
}
require_celerity_resource('herald-test-css');
$nav = $this->buildSideNav();
$content = array();
$object_xscript = $xscript->getObjectTranscript();
if (!$object_xscript) {
@ -49,7 +32,7 @@ final class HeraldTranscriptController extends HeraldController {
'p',
array(),
pht('Details of this transcript have been garbage collected.')));
$nav->appendChild($notice);
$content[] = $notice;
} else {
$map = HeraldAdapter::getEnabledAdapterMap($viewer);
$object_type = $object_xscript->getType();
@ -65,9 +48,7 @@ final class HeraldTranscriptController extends HeraldController {
$this->adapter = HeraldAdapter::getAdapterForContentType($object_type);
$filter = $this->getFilterPHIDs();
$this->filterTranscript($xscript, $filter);
$phids = array_merge($filter, $this->getTranscriptPHIDs($xscript));
$phids = $this->getTranscriptPHIDs($xscript);
$phids = array_unique($phids);
$phids = array_filter($phids);
@ -82,23 +63,16 @@ final class HeraldTranscriptController extends HeraldController {
pht(
'This was a dry run to test Herald rules, '.
'no actions were executed.'));
$nav->appendChild($notice);
$content[] = $notice;
}
$warning_panel = $this->buildWarningPanel($xscript);
$nav->appendChild($warning_panel);
$content[] = $warning_panel;
$apply_xscript_panel = $this->buildApplyTranscriptPanel(
$xscript);
$nav->appendChild($apply_xscript_panel);
$action_xscript_panel = $this->buildActionTranscriptPanel(
$xscript);
$nav->appendChild($action_xscript_panel);
$object_xscript_panel = $this->buildObjectTranscriptPanel(
$xscript);
$nav->appendChild($object_xscript_panel);
$content[] = array(
$this->buildActionTranscriptPanel($xscript),
$this->buildObjectTranscriptPanel($xscript),
);
}
$crumbs = id($this->buildApplicationCrumbs())
@ -106,10 +80,12 @@ final class HeraldTranscriptController extends HeraldController {
pht('Transcripts'),
$this->getApplicationURI('/transcript/'))
->addTextCrumb($xscript->getID());
$nav->setCrumbs($crumbs);
return $this->buildApplicationPage(
$nav,
array(
$crumbs,
$content,
),
array(
'title' => pht('Transcript'),
));
@ -146,33 +122,6 @@ final class HeraldTranscriptController extends HeraldController {
return phutil_tag('span', array('class' => 'condition-test-value'), $value);
}
private function buildSideNav() {
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI('/herald/transcript/'.$this->id.'/'));
$items = array();
$filters = $this->getFilterMap();
foreach ($filters as $key => $name) {
$nav->addFilter($key, $name);
}
$nav->selectFilter($this->filter, null);
return $nav;
}
protected function getFilterMap() {
return array(
self::FILTER_ALL => pht('All Rules'),
self::FILTER_OWNED => pht('Rules I Own'),
self::FILTER_AFFECTED => pht('Rules that Affected Me'),
);
}
protected function getFilterPHIDs() {
return array($this->getRequest()->getUser()->getPHID());
}
protected function getTranscriptPHIDs($xscript) {
$phids = array();
@ -228,71 +177,6 @@ final class HeraldTranscriptController extends HeraldController {
return $phids;
}
protected function filterTranscript($xscript, $filter_phids) {
$filter_owned = ($this->filter == self::FILTER_OWNED);
$filter_affected = ($this->filter == self::FILTER_AFFECTED);
if (!$filter_owned && !$filter_affected) {
// No filtering to be done.
return;
}
if (!$xscript->getObjectTranscript()) {
return;
}
$user_phid = $this->getRequest()->getUser()->getPHID();
$keep_apply_xscripts = array();
$keep_rule_xscripts = array();
$filter_phids = array_fill_keys($filter_phids, true);
$rule_xscripts = $xscript->getRuleTranscripts();
foreach ($xscript->getApplyTranscripts() as $id => $apply_xscript) {
$rule_id = $apply_xscript->getRuleID();
if ($filter_owned) {
if (empty($rule_xscripts[$rule_id])) {
// No associated rule so you can't own this effect.
continue;
}
if ($rule_xscripts[$rule_id]->getRuleOwner() != $user_phid) {
continue;
}
} else if ($filter_affected) {
$targets = (array)$apply_xscript->getTarget();
if (!array_select_keys($filter_phids, $targets)) {
continue;
}
}
$keep_apply_xscripts[$id] = true;
if ($rule_id) {
$keep_rule_xscripts[$rule_id] = true;
}
}
foreach ($rule_xscripts as $rule_id => $rule_xscript) {
if ($filter_owned && $rule_xscript->getRuleOwner() == $user_phid) {
$keep_rule_xscripts[$rule_id] = true;
}
}
$xscript->setRuleTranscripts(
array_intersect_key(
$xscript->getRuleTranscripts(),
$keep_rule_xscripts));
$xscript->setApplyTranscripts(
array_intersect_key(
$xscript->getApplyTranscripts(),
$keep_apply_xscripts));
$xscript->setConditionTranscripts(
array_intersect_key(
$xscript->getConditionTranscripts(),
$keep_rule_xscripts));
}
private function buildWarningPanel(HeraldTranscript $xscript) {
$request = $this->getRequest();
$panel = null;
@ -333,165 +217,185 @@ final class HeraldTranscriptController extends HeraldController {
return $panel;
}
private function buildApplyTranscriptPanel(HeraldTranscript $xscript) {
$handles = $this->handles;
$adapter = $this->getAdapter();
$rule_type_global = HeraldRuleTypeConfig::RULE_TYPE_GLOBAL;
$action_names = $adapter->getActionNameMap($rule_type_global);
$list = new PHUIObjectItemListView();
$list->setStates(true);
$list->setNoDataString(pht('No actions were taken.'));
foreach ($xscript->getApplyTranscripts() as $apply_xscript) {
$target = $apply_xscript->getTarget();
switch ($apply_xscript->getAction()) {
case HeraldAdapter::ACTION_FLAG:
$target = PhabricatorFlagColor::getColorName($target);
break;
case HeraldAdapter::ACTION_BLOCK:
// Target is a text string.
$target = $target;
break;
default:
// TODO: This should be driven by HeraldActions.
if (is_array($target) && $target) {
foreach ($target as $k => $phid) {
if (isset($handles[$phid])) {
$target[$k] = $handles[$phid]->getName();
}
}
$target = implode(', ', $target);
} else if (is_string($target)) {
$target = $target;
} else {
$target = '<empty>';
}
break;
}
$item = new PHUIObjectItemView();
if ($apply_xscript->getApplied()) {
$item->setState(PHUIObjectItemView::STATE_SUCCESS);
} else {
$item->setState(PHUIObjectItemView::STATE_FAIL);
}
$rule = idx(
$action_names,
$apply_xscript->getAction(),
pht('Unknown Action "%s"', $apply_xscript->getAction()));
$item->setHeader(pht('%s: %s', $rule, $target));
$item->addAttribute($apply_xscript->getReason());
// TODO: This is a bit of a mess while actions convert.
$item->addAttribute(
pht('Outcome: %s', $apply_xscript->getAppliedReason()));
$list->addItem($item);
}
$box = new PHUIObjectBoxView();
$box->setHeaderText(pht('Actions Taken'));
$box->appendChild($list);
return $box;
}
private function buildActionTranscriptPanel(HeraldTranscript $xscript) {
$action_xscript = mgroup($xscript->getApplyTranscripts(), 'getRuleID');
$adapter = $this->getAdapter();
$field_names = $adapter->getFieldNameMap();
$condition_names = $adapter->getConditionNameMap();
$handles = $this->handles;
$rule_markup = array();
foreach ($xscript->getRuleTranscripts() as $rule_id => $rule) {
$cond_markup = array();
foreach ($xscript->getConditionTranscriptsForRule($rule_id) as $cond) {
if ($cond->getNote()) {
$note = phutil_tag_div('herald-condition-note', $cond->getNote());
$action_map = $xscript->getApplyTranscripts();
$action_map = mgroup($action_map, 'getRuleID');
$rule_list = id(new PHUIObjectItemListView())
->setNoDataString(pht('No Herald rules applied to this object.'));
foreach ($xscript->getRuleTranscripts() as $rule_xscript) {
$rule_id = $rule_xscript->getRuleID();
$rule_item = id(new PHUIObjectItemView())
->setObjectName(pht('H%d', $rule_id))
->setHeader($rule_xscript->getRuleName());
if (!$rule_xscript->getResult()) {
$rule_item->setDisabled(true);
}
$rule_list->addItem($rule_item);
// Build the field/condition transcript.
$cond_xscripts = $xscript->getConditionTranscriptsForRule($rule_id);
$cond_list = id(new PHUIStatusListView());
$cond_list->addItem(
id(new PHUIStatusItemView())
->setTarget(phutil_tag('strong', array(), pht('Conditions'))));
foreach ($cond_xscripts as $cond_xscript) {
if ($cond_xscript->getResult()) {
$icon = 'fa-check';
$color = 'green';
$result = pht('Passed');
} else {
$icon = 'fa-times';
$color = 'red';
$result = pht('Failed');
}
if ($cond_xscript->getNote()) {
$note = phutil_tag(
'div',
array(
'class' => 'herald-condition-note',
),
$cond_xscript->getNote());
} else {
$note = null;
}
if ($cond->getResult()) {
$result = phutil_tag(
'span',
array('class' => 'herald-outcome condition-pass'),
"\xE2\x9C\x93");
// TODO: This is not really translatable and should be driven through
// HeraldField.
$explanation = pht(
'%s %s %s',
idx($field_names, $cond_xscript->getFieldName(), pht('Unknown')),
idx($condition_names, $cond_xscript->getCondition(), pht('Unknown')),
$this->renderConditionTestValue($cond_xscript, $handles));
$cond_item = id(new PHUIStatusItemView())
->setIcon($icon, $color)
->setTarget($result)
->setNote(array($explanation, $note));
$cond_list->addItem($cond_item);
}
if ($rule_xscript->getResult()) {
$last_icon = 'fa-check-circle';
$last_color = 'green';
$last_result = pht('Passed');
$last_note = pht('Rule passed.');
} else {
$last_icon = 'fa-times-circle';
$last_color = 'red';
$last_result = pht('Failed');
$last_note = pht('Rule failed.');
}
$cond_last = id(new PHUIStatusItemView())
->setIcon($last_icon, $last_color)
->setTarget(phutil_tag('strong', array(), $last_result))
->setNote($last_note);
$cond_list->addItem($cond_last);
$cond_box = id(new PHUIBoxView())
->appendChild($cond_list)
->addMargin(PHUI::MARGIN_LARGE_LEFT);
$rule_item->appendChild($cond_box);
if (!$rule_xscript->getResult()) {
// If the rule didn't pass, don't generate an action transcript since
// actions didn't apply.
continue;
}
$cond_box->addMargin(PHUI::MARGIN_MEDIUM_BOTTOM);
$action_xscripts = idx($action_map, $rule_id, array());
foreach ($action_xscripts as $action_xscript) {
$action_key = $action_xscript->getAction();
$action = $adapter->getActionImplementation($action_key);
if ($action) {
$name = $action->getHeraldActionName();
$action->setViewer($this->getViewer());
} else {
$result = phutil_tag(
'span',
array('class' => 'herald-outcome condition-fail'),
"\xE2\x9C\x98");
$name = pht('Unknown Action ("%s")', $action_key);
}
$cond_markup[] = phutil_tag(
'li',
array(),
pht(
'%s Condition: %s %s %s%s',
$result,
idx($field_names, $cond->getFieldName(), pht('Unknown')),
idx($condition_names, $cond->getCondition(), pht('Unknown')),
$this->renderConditionTestValue($cond, $handles),
$note));
$name = pht('Action: %s', $name);
$action_list = id(new PHUIStatusListView());
$action_list->addItem(
id(new PHUIStatusItemView())
->setTarget(phutil_tag('strong', array(), $name)));
$action_box = id(new PHUIBoxView())
->appendChild($action_list)
->addMargin(PHUI::MARGIN_LARGE_LEFT);
$rule_item->appendChild($action_box);
$log = $action_xscript->getAppliedReason();
// Handle older transcripts which used a static string to record
// action results.
if (!is_array($log)) {
$action_list->addItem(
id(new PHUIStatusItemView())
->setIcon('fa-clock-o', 'grey')
->setTarget(pht('Old Transcript'))
->setNote(
pht(
'This is an old transcript which uses an obsolete log '.
'format. Detailed action information is not available.')));
continue;
}
foreach ($log as $entry) {
$type = idx($entry, 'type');
$data = idx($entry, 'data');
if ($action) {
$icon = $action->renderActionEffectIcon($type, $data);
$color = $action->renderActionEffectColor($type, $data);
$name = $action->renderActionEffectName($type, $data);
$note = $action->renderActionEffectDescription($type, $data);
} else {
$icon = 'fa-question-circle';
$color = 'indigo';
$name = pht('Unknown Effect ("%s")', $type);
$note = null;
}
$action_item = id(new PHUIStatusItemView())
->setIcon($icon, $color)
->setTarget($name)
->setNote($note);
$action_list->addItem($action_item);
}
}
if ($rule->getResult()) {
$result = phutil_tag(
'span',
array('class' => 'herald-outcome rule-pass'),
pht('PASS'));
$class = 'herald-rule-pass';
} else {
$result = phutil_tag(
'span',
array('class' => 'herald-outcome rule-fail'),
pht('FAIL'));
$class = 'herald-rule-fail';
}
$cond_markup[] = phutil_tag(
'li',
array(),
array($result, $rule->getReason()));
$user_phid = $this->getRequest()->getUser()->getPHID();
$name = $rule->getRuleName();
$rule_markup[] =
phutil_tag(
'li',
array(
'class' => $class,
),
phutil_tag_div('rule-name', array(
phutil_tag('strong', array(), $name),
' ',
phutil_tag('ul', array(), $cond_markup),
)));
}
$box = null;
if ($rule_markup) {
$box = new PHUIObjectBoxView();
$box->setHeaderText(pht('Rule Details'));
$box->appendChild(phutil_tag(
'ul',
array('class' => 'herald-explain-list'),
$rule_markup));
}
$box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Rule Transcript'))
->appendChild($rule_list);
return $box;
}

View file

@ -84,7 +84,6 @@ final class HeraldManiphestTaskAdapter extends HeraldAdapter {
self::ACTION_ADD_CC,
self::ACTION_REMOVE_CC,
self::ACTION_EMAIL,
self::ACTION_FLAG,
self::ACTION_ASSIGN_TASK,
),
parent::getActions($rule_type));

View file

@ -61,7 +61,6 @@ final class HeraldPholioMockAdapter extends HeraldAdapter {
array(
self::ACTION_ADD_CC,
self::ACTION_REMOVE_CC,
self::ACTION_FLAG,
),
parent::getActions($rule_type));
}

View file

@ -64,7 +64,6 @@ final class PhrictionDocumentHeraldAdapter extends HeraldAdapter {
self::ACTION_ADD_CC,
self::ACTION_REMOVE_CC,
self::ACTION_EMAIL,
self::ACTION_FLAG,
),
parent::getActions($rule_type));
}

View file

@ -2,79 +2,10 @@
* @provides herald-test-css
*/
ul.herald-explain-list {
margin: 12px;
}
div.herald-condition-note {
clear: both;
margin: .5em 0em .53em 86px;
padding: .5em 1em;
background: #FFFF00;
font-weight: bold;
}
ul.herald-explain-list .herald-outcome {
margin-right: 6px;
width: 50px;
text-align: center;
position: relative;
float: left;
font-weight: bold;
padding: 1px 2px;
}
ul.herald-explain-list .condition-fail,
ul.herald-explain-list .rule-fail {
.herald-condition-note {
color: {$red};
}
ul.herald-explain-list .condition-pass,
ul.herald-explain-list .rule-pass {
color: {$green};
}
ul.herald-explain-list li.herald-rule-pass,
ul.herald-explain-list li.herald-rule-fail {
margin: 0 0 8px;
}
ul.herald-explain-list div.rule-name {
padding: 4px 0 8px 0;
}
ul.herald-explain-list li.herald-rule-fail,
ul.herald-explain-list li.herald-rule-pass {
background: #ffffff;
}
ul.herald-explain-list ul {
margin: 4px;
}
ul.herald-explain-list ul li {
padding: 2px 0;
}
.outcome-failure,
.outcome-success {
font-weight: bold;
}
.outcome-failure {
color: {$red};
}
.outcome-success {
color: {$green};
}
.action-header {
font-weight: bold;
padding-top: 12px;
border-bottom: 1px solid {$thinblueborder};
}
textarea.herald-field-value-transcript {
width: 100%;
height: 10em;