mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-23 14:00:56 +01:00
On Harbormaster build plans, show which Herald rules trigger builds
Summary: Ref T13258. Provide an easy way to find rules which trigger a particular build plan from the build plan page. The implementation here ends up a little messy: we can't just search for `actionType = 'build' AND targetPHID = '<build plan PHID>'` since the field is a blob of JSON. Instead, make rules indexable and write a "build plan is affected by rule actions" edge when indexing rules, then search on that edge. For now, only "Run Build Plan: ..." rules actually write this edge, since I think (?) that it doesn't really have meaningful values for other edge types today. Maybe "Call Webhooks", and you could get a link from a hook to rules that trigger it? Reasonable to do in the future. Things end up a little bit rough overall, but I think this panel is pretty useful to add to the Build Plan page. This index needs to be rebuilt with `bin/search index --type HeraldRule`. I'll call this out in the changelog but I'm not planning to explicitly migrate or add an activity, since this is only really important for larger installs and they probably (?) read the changelog. As rules are edited over time, this will converge to the right behavior even if you don't rebuild the index. Test Plan: {F6260095} Reviewers: amckinley Reviewed By: amckinley Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T13258 Differential Revision: https://secure.phabricator.com/D20259
This commit is contained in:
parent
77221bee72
commit
950a7bbb19
11 changed files with 264 additions and 40 deletions
|
@ -1531,6 +1531,7 @@ phutil_register_library_map(array(
|
|||
'HeraldRemarkupFieldValue' => 'applications/herald/value/HeraldRemarkupFieldValue.php',
|
||||
'HeraldRemarkupRule' => 'applications/herald/remarkup/HeraldRemarkupRule.php',
|
||||
'HeraldRule' => 'applications/herald/storage/HeraldRule.php',
|
||||
'HeraldRuleActionAffectsObjectEdgeType' => 'applications/herald/edge/HeraldRuleActionAffectsObjectEdgeType.php',
|
||||
'HeraldRuleAdapter' => 'applications/herald/adapter/HeraldRuleAdapter.php',
|
||||
'HeraldRuleAdapterField' => 'applications/herald/field/rule/HeraldRuleAdapterField.php',
|
||||
'HeraldRuleController' => 'applications/herald/controller/HeraldRuleController.php',
|
||||
|
@ -1540,7 +1541,9 @@ phutil_register_library_map(array(
|
|||
'HeraldRuleEditor' => 'applications/herald/editor/HeraldRuleEditor.php',
|
||||
'HeraldRuleField' => 'applications/herald/field/rule/HeraldRuleField.php',
|
||||
'HeraldRuleFieldGroup' => 'applications/herald/field/rule/HeraldRuleFieldGroup.php',
|
||||
'HeraldRuleIndexEngineExtension' => 'applications/herald/engineextension/HeraldRuleIndexEngineExtension.php',
|
||||
'HeraldRuleListController' => 'applications/herald/controller/HeraldRuleListController.php',
|
||||
'HeraldRuleListView' => 'applications/herald/view/HeraldRuleListView.php',
|
||||
'HeraldRuleNameTransaction' => 'applications/herald/xaction/HeraldRuleNameTransaction.php',
|
||||
'HeraldRulePHIDType' => 'applications/herald/phid/HeraldRulePHIDType.php',
|
||||
'HeraldRuleQuery' => 'applications/herald/query/HeraldRuleQuery.php',
|
||||
|
@ -7189,8 +7192,10 @@ phutil_register_library_map(array(
|
|||
'PhabricatorFlaggableInterface',
|
||||
'PhabricatorPolicyInterface',
|
||||
'PhabricatorDestructibleInterface',
|
||||
'PhabricatorIndexableInterface',
|
||||
'PhabricatorSubscribableInterface',
|
||||
),
|
||||
'HeraldRuleActionAffectsObjectEdgeType' => 'PhabricatorEdgeType',
|
||||
'HeraldRuleAdapter' => 'HeraldAdapter',
|
||||
'HeraldRuleAdapterField' => 'HeraldRuleField',
|
||||
'HeraldRuleController' => 'HeraldController',
|
||||
|
@ -7200,7 +7205,9 @@ phutil_register_library_map(array(
|
|||
'HeraldRuleEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||
'HeraldRuleField' => 'HeraldField',
|
||||
'HeraldRuleFieldGroup' => 'HeraldFieldGroup',
|
||||
'HeraldRuleIndexEngineExtension' => 'PhabricatorIndexEngineExtension',
|
||||
'HeraldRuleListController' => 'HeraldController',
|
||||
'HeraldRuleListView' => 'AphrontView',
|
||||
'HeraldRuleNameTransaction' => 'HeraldRuleTransactionType',
|
||||
'HeraldRulePHIDType' => 'PhabricatorPHIDType',
|
||||
'HeraldRuleQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
|
|
|
@ -62,6 +62,7 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController {
|
|||
|
||||
$builds_view = $this->newBuildsView($plan);
|
||||
$options_view = $this->newOptionsView($plan);
|
||||
$rules_view = $this->newRulesView($plan);
|
||||
|
||||
$timeline = $this->buildTransactionTimeline(
|
||||
$plan,
|
||||
|
@ -76,6 +77,7 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController {
|
|||
$error,
|
||||
$step_list,
|
||||
$options_view,
|
||||
$rules_view,
|
||||
$builds_view,
|
||||
$timeline,
|
||||
));
|
||||
|
@ -486,6 +488,42 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController {
|
|||
->appendChild($list);
|
||||
}
|
||||
|
||||
private function newRulesView(HarbormasterBuildPlan $plan) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$rules = id(new HeraldRuleQuery())
|
||||
->setViewer($viewer)
|
||||
->withDisabled(false)
|
||||
->withAffectedObjectPHIDs(array($plan->getPHID()))
|
||||
->needValidateAuthors(true)
|
||||
->execute();
|
||||
|
||||
$list = id(new HeraldRuleListView())
|
||||
->setViewer($viewer)
|
||||
->setRules($rules)
|
||||
->newObjectList();
|
||||
|
||||
$list->setNoDataString(pht('No active Herald rules trigger this build.'));
|
||||
|
||||
$more_href = new PhutilURI(
|
||||
'/herald/',
|
||||
array('affectedPHID' => $plan->getPHID()));
|
||||
|
||||
$more_link = id(new PHUIButtonView())
|
||||
->setTag('a')
|
||||
->setIcon('fa-list-ul')
|
||||
->setText(pht('View All Rules'))
|
||||
->setHref($more_href);
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader(pht('Run By Herald Rules'))
|
||||
->addActionLink($more_link);
|
||||
|
||||
return id(new PHUIObjectBoxView())
|
||||
->setHeader($header)
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->appendChild($list);
|
||||
}
|
||||
|
||||
private function newOptionsView(HarbormasterBuildPlan $plan) {
|
||||
$viewer = $this->getViewer();
|
||||
|
|
|
@ -91,4 +91,9 @@ final class HarbormasterRunBuildPlansHeraldAction
|
|||
'Run build plans: %s.',
|
||||
$this->renderHandleList($value));
|
||||
}
|
||||
|
||||
public function getPHIDsAffectedByAction(HeraldActionRecord $record) {
|
||||
return $record->getTarget();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -401,4 +401,8 @@ abstract class HeraldAction extends Phobject {
|
|||
return null;
|
||||
}
|
||||
|
||||
public function getPHIDsAffectedByAction(HeraldActionRecord $record) {
|
||||
return array();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
final class HeraldRuleActionAffectsObjectEdgeType
|
||||
extends PhabricatorEdgeType {
|
||||
|
||||
const EDGECONST = 69;
|
||||
|
||||
}
|
|
@ -74,4 +74,8 @@ final class HeraldRuleEditor
|
|||
return $body;
|
||||
}
|
||||
|
||||
protected function supportsSearch() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
final class HeraldRuleIndexEngineExtension
|
||||
extends PhabricatorIndexEngineExtension {
|
||||
|
||||
const EXTENSIONKEY = 'herald.actions';
|
||||
|
||||
public function getExtensionName() {
|
||||
return pht('Herald Actions');
|
||||
}
|
||||
|
||||
public function shouldIndexObject($object) {
|
||||
if (!($object instanceof HeraldRule)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function indexObject(
|
||||
PhabricatorIndexEngine $engine,
|
||||
$object) {
|
||||
|
||||
$edge_type = HeraldRuleActionAffectsObjectEdgeType::EDGECONST;
|
||||
|
||||
$old_edges = PhabricatorEdgeQuery::loadDestinationPHIDs(
|
||||
$object->getPHID(),
|
||||
$edge_type);
|
||||
$old_edges = array_fuse($old_edges);
|
||||
|
||||
$new_edges = $this->getPHIDsAffectedByActions($object);
|
||||
$new_edges = array_fuse($new_edges);
|
||||
|
||||
$add_edges = array_diff_key($new_edges, $old_edges);
|
||||
$rem_edges = array_diff_key($old_edges, $new_edges);
|
||||
|
||||
if (!$add_edges && !$rem_edges) {
|
||||
return;
|
||||
}
|
||||
|
||||
$editor = new PhabricatorEdgeEditor();
|
||||
|
||||
foreach ($add_edges as $phid) {
|
||||
$editor->addEdge($object->getPHID(), $edge_type, $phid);
|
||||
}
|
||||
|
||||
foreach ($rem_edges as $phid) {
|
||||
$editor->removeEdge($object->getPHID(), $edge_type, $phid);
|
||||
}
|
||||
|
||||
$editor->save();
|
||||
}
|
||||
|
||||
public function getIndexVersion($object) {
|
||||
$phids = $this->getPHIDsAffectedByActions($object);
|
||||
sort($phids);
|
||||
$phids = implode(':', $phids);
|
||||
return PhabricatorHash::digestForIndex($phids);
|
||||
}
|
||||
|
||||
private function getPHIDsAffectedByActions(HeraldRule $rule) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$rule = id(new HeraldRuleQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($rule->getID()))
|
||||
->needConditionsAndActions(true)
|
||||
->executeOne();
|
||||
if (!$rule) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$phids = array();
|
||||
|
||||
$actions = HeraldAction::getAllActions();
|
||||
foreach ($rule->getActions() as $action_record) {
|
||||
$action = idx($actions, $action_record->getAction());
|
||||
|
||||
if (!$action) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($action->getPHIDsAffectedByAction($action_record) as $phid) {
|
||||
$phids[] = $phid;
|
||||
}
|
||||
}
|
||||
|
||||
$phids = array_fuse($phids);
|
||||
return array_keys($phids);
|
||||
}
|
||||
|
||||
}
|
|
@ -11,6 +11,7 @@ final class HeraldRuleQuery extends PhabricatorCursorPagedPolicyAwareQuery {
|
|||
private $active;
|
||||
private $datasourceQuery;
|
||||
private $triggerObjectPHIDs;
|
||||
private $affectedObjectPHIDs;
|
||||
|
||||
private $needConditionsAndActions;
|
||||
private $needAppliedToPHIDs;
|
||||
|
@ -61,6 +62,11 @@ final class HeraldRuleQuery extends PhabricatorCursorPagedPolicyAwareQuery {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function withAffectedObjectPHIDs(array $phids) {
|
||||
$this->affectedObjectPHIDs = $phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function needConditionsAndActions($need) {
|
||||
$this->needConditionsAndActions = $need;
|
||||
return $this;
|
||||
|
@ -261,9 +267,31 @@ final class HeraldRuleQuery extends PhabricatorCursorPagedPolicyAwareQuery {
|
|||
$this->triggerObjectPHIDs);
|
||||
}
|
||||
|
||||
if ($this->affectedObjectPHIDs !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'edge_affects.dst IN (%Ls)',
|
||||
$this->affectedObjectPHIDs);
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
|
||||
$joins = parent::buildJoinClauseParts($conn);
|
||||
|
||||
if ($this->affectedObjectPHIDs !== null) {
|
||||
$joins[] = qsprintf(
|
||||
$conn,
|
||||
'JOIN %T edge_affects ON rule.phid = edge_affects.src
|
||||
AND edge_affects.type = %d',
|
||||
PhabricatorEdgeConfig::TABLE_NAME_EDGE,
|
||||
HeraldRuleActionAffectsObjectEdgeType::EDGECONST);
|
||||
}
|
||||
|
||||
return $joins;
|
||||
}
|
||||
|
||||
private function validateRuleAuthors(array $rules) {
|
||||
// "Global" and "Object" rules always have valid authors.
|
||||
foreach ($rules as $key => $rule) {
|
||||
|
|
|
@ -55,6 +55,10 @@ final class HeraldRuleSearchEngine extends PhabricatorApplicationSearchEngine {
|
|||
pht('(Show All)'),
|
||||
pht('Show Only Disabled Rules'),
|
||||
pht('Show Only Enabled Rules')),
|
||||
id(new PhabricatorPHIDsSearchField())
|
||||
->setLabel(pht('Affected Objects'))
|
||||
->setKey('affectedPHIDs')
|
||||
->setAliases(array('affectedPHID')),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -81,6 +85,10 @@ final class HeraldRuleSearchEngine extends PhabricatorApplicationSearchEngine {
|
|||
$query->withActive($map['active']);
|
||||
}
|
||||
|
||||
if ($map['affectedPHIDs']) {
|
||||
$query->withAffectedObjectPHIDs($map['affectedPHIDs']);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
|
@ -127,54 +135,18 @@ final class HeraldRuleSearchEngine extends PhabricatorApplicationSearchEngine {
|
|||
PhabricatorSavedQuery $query,
|
||||
array $handles) {
|
||||
assert_instances_of($rules, 'HeraldRule');
|
||||
|
||||
$viewer = $this->requireViewer();
|
||||
$handles = $viewer->loadHandles(mpull($rules, 'getAuthorPHID'));
|
||||
|
||||
$content_type_map = HeraldAdapter::getEnabledAdapterMap($viewer);
|
||||
|
||||
$list = id(new PHUIObjectItemListView())
|
||||
->setUser($viewer);
|
||||
foreach ($rules as $rule) {
|
||||
$monogram = $rule->getMonogram();
|
||||
|
||||
$item = id(new PHUIObjectItemView())
|
||||
->setObjectName($monogram)
|
||||
->setHeader($rule->getName())
|
||||
->setHref("/{$monogram}");
|
||||
|
||||
if ($rule->isPersonalRule()) {
|
||||
$item->addIcon('fa-user', pht('Personal Rule'));
|
||||
$item->addByline(
|
||||
pht(
|
||||
'Authored by %s',
|
||||
$handles[$rule->getAuthorPHID()]->renderLink()));
|
||||
} else if ($rule->isObjectRule()) {
|
||||
$item->addIcon('fa-briefcase', pht('Object Rule'));
|
||||
} else {
|
||||
$item->addIcon('fa-globe', pht('Global Rule'));
|
||||
}
|
||||
|
||||
if ($rule->getIsDisabled()) {
|
||||
$item->setDisabled(true);
|
||||
$item->addIcon('fa-lock grey', pht('Disabled'));
|
||||
} else if (!$rule->hasValidAuthor()) {
|
||||
$item->setDisabled(true);
|
||||
$item->addIcon('fa-user grey', pht('Author Not Active'));
|
||||
}
|
||||
|
||||
$content_type_name = idx($content_type_map, $rule->getContentType());
|
||||
$item->addAttribute(pht('Affects: %s', $content_type_name));
|
||||
|
||||
$list->addItem($item);
|
||||
}
|
||||
$list = id(new HeraldRuleListView())
|
||||
->setViewer($viewer)
|
||||
->setRules($rules)
|
||||
->newObjectList();
|
||||
|
||||
$result = new PhabricatorApplicationSearchResultView();
|
||||
$result->setObjectList($list);
|
||||
$result->setNoDataString(pht('No rules found.'));
|
||||
|
||||
return $result;
|
||||
|
||||
}
|
||||
|
||||
protected function getNewUserBody() {
|
||||
|
|
|
@ -6,6 +6,7 @@ final class HeraldRule extends HeraldDAO
|
|||
PhabricatorFlaggableInterface,
|
||||
PhabricatorPolicyInterface,
|
||||
PhabricatorDestructibleInterface,
|
||||
PhabricatorIndexableInterface,
|
||||
PhabricatorSubscribableInterface {
|
||||
|
||||
const TABLE_RULE_APPLIED = 'herald_ruleapplied';
|
||||
|
|
65
src/applications/herald/view/HeraldRuleListView.php
Normal file
65
src/applications/herald/view/HeraldRuleListView.php
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
final class HeraldRuleListView
|
||||
extends AphrontView {
|
||||
|
||||
private $rules;
|
||||
|
||||
public function setRules(array $rules) {
|
||||
assert_instances_of($rules, 'HeraldRule');
|
||||
$this->rules = $rules;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
return $this->newObjectList();
|
||||
}
|
||||
|
||||
public function newObjectList() {
|
||||
$viewer = $this->getViewer();
|
||||
$rules = $this->rules;
|
||||
|
||||
$handles = $viewer->loadHandles(mpull($rules, 'getAuthorPHID'));
|
||||
|
||||
$content_type_map = HeraldAdapter::getEnabledAdapterMap($viewer);
|
||||
|
||||
$list = id(new PHUIObjectItemListView())
|
||||
->setViewer($viewer);
|
||||
foreach ($rules as $rule) {
|
||||
$monogram = $rule->getMonogram();
|
||||
|
||||
$item = id(new PHUIObjectItemView())
|
||||
->setObjectName($monogram)
|
||||
->setHeader($rule->getName())
|
||||
->setHref($rule->getURI());
|
||||
|
||||
if ($rule->isPersonalRule()) {
|
||||
$item->addIcon('fa-user', pht('Personal Rule'));
|
||||
$item->addByline(
|
||||
pht(
|
||||
'Authored by %s',
|
||||
$handles[$rule->getAuthorPHID()]->renderLink()));
|
||||
} else if ($rule->isObjectRule()) {
|
||||
$item->addIcon('fa-briefcase', pht('Object Rule'));
|
||||
} else {
|
||||
$item->addIcon('fa-globe', pht('Global Rule'));
|
||||
}
|
||||
|
||||
if ($rule->getIsDisabled()) {
|
||||
$item->setDisabled(true);
|
||||
$item->addIcon('fa-lock grey', pht('Disabled'));
|
||||
} else if (!$rule->hasValidAuthor()) {
|
||||
$item->setDisabled(true);
|
||||
$item->addIcon('fa-user grey', pht('Author Not Active'));
|
||||
}
|
||||
|
||||
$content_type_name = idx($content_type_map, $rule->getContentType());
|
||||
$item->addAttribute(pht('Affects: %s', $content_type_name));
|
||||
|
||||
$list->addItem($item);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue