diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1531,6 +1531,7 @@ '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 @@ '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 @@ 'PhabricatorFlaggableInterface', 'PhabricatorPolicyInterface', 'PhabricatorDestructibleInterface', + 'PhabricatorIndexableInterface', 'PhabricatorSubscribableInterface', ), + 'HeraldRuleActionAffectsObjectEdgeType' => 'PhabricatorEdgeType', 'HeraldRuleAdapter' => 'HeraldAdapter', 'HeraldRuleAdapterField' => 'HeraldRuleField', 'HeraldRuleController' => 'HeraldController', @@ -7200,7 +7205,9 @@ 'HeraldRuleEditor' => 'PhabricatorApplicationTransactionEditor', 'HeraldRuleField' => 'HeraldField', 'HeraldRuleFieldGroup' => 'HeraldFieldGroup', + 'HeraldRuleIndexEngineExtension' => 'PhabricatorIndexEngineExtension', 'HeraldRuleListController' => 'HeraldController', + 'HeraldRuleListView' => 'AphrontView', 'HeraldRuleNameTransaction' => 'HeraldRuleTransactionType', 'HeraldRulePHIDType' => 'PhabricatorPHIDType', 'HeraldRuleQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', diff --git a/src/applications/harbormaster/controller/HarbormasterPlanViewController.php b/src/applications/harbormaster/controller/HarbormasterPlanViewController.php --- a/src/applications/harbormaster/controller/HarbormasterPlanViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterPlanViewController.php @@ -62,6 +62,7 @@ $builds_view = $this->newBuildsView($plan); $options_view = $this->newOptionsView($plan); + $rules_view = $this->newRulesView($plan); $timeline = $this->buildTransactionTimeline( $plan, @@ -76,6 +77,7 @@ $error, $step_list, $options_view, + $rules_view, $builds_view, $timeline, )); @@ -486,6 +488,42 @@ ->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(); diff --git a/src/applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php b/src/applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php --- a/src/applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php +++ b/src/applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php @@ -91,4 +91,9 @@ 'Run build plans: %s.', $this->renderHandleList($value)); } + + public function getPHIDsAffectedByAction(HeraldActionRecord $record) { + return $record->getTarget(); + } + } diff --git a/src/applications/herald/action/HeraldAction.php b/src/applications/herald/action/HeraldAction.php --- a/src/applications/herald/action/HeraldAction.php +++ b/src/applications/herald/action/HeraldAction.php @@ -401,4 +401,8 @@ return null; } + public function getPHIDsAffectedByAction(HeraldActionRecord $record) { + return array(); + } + } diff --git a/src/applications/herald/edge/HeraldRuleActionAffectsObjectEdgeType.php b/src/applications/herald/edge/HeraldRuleActionAffectsObjectEdgeType.php new file mode 100644 --- /dev/null +++ b/src/applications/herald/edge/HeraldRuleActionAffectsObjectEdgeType.php @@ -0,0 +1,8 @@ +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); + } + +} diff --git a/src/applications/herald/query/HeraldRuleQuery.php b/src/applications/herald/query/HeraldRuleQuery.php --- a/src/applications/herald/query/HeraldRuleQuery.php +++ b/src/applications/herald/query/HeraldRuleQuery.php @@ -11,6 +11,7 @@ private $active; private $datasourceQuery; private $triggerObjectPHIDs; + private $affectedObjectPHIDs; private $needConditionsAndActions; private $needAppliedToPHIDs; @@ -61,6 +62,11 @@ 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 @@ $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) { diff --git a/src/applications/herald/query/HeraldRuleSearchEngine.php b/src/applications/herald/query/HeraldRuleSearchEngine.php --- a/src/applications/herald/query/HeraldRuleSearchEngine.php +++ b/src/applications/herald/query/HeraldRuleSearchEngine.php @@ -55,6 +55,10 @@ 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 @@ $query->withActive($map['active']); } + if ($map['affectedPHIDs']) { + $query->withAffectedObjectPHIDs($map['affectedPHIDs']); + } + return $query; } @@ -127,54 +135,18 @@ 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() { diff --git a/src/applications/herald/storage/HeraldRule.php b/src/applications/herald/storage/HeraldRule.php --- a/src/applications/herald/storage/HeraldRule.php +++ b/src/applications/herald/storage/HeraldRule.php @@ -6,6 +6,7 @@ PhabricatorFlaggableInterface, PhabricatorPolicyInterface, PhabricatorDestructibleInterface, + PhabricatorIndexableInterface, PhabricatorSubscribableInterface { const TABLE_RULE_APPLIED = 'herald_ruleapplied'; diff --git a/src/applications/herald/view/HeraldRuleListView.php b/src/applications/herald/view/HeraldRuleListView.php new file mode 100644 --- /dev/null +++ b/src/applications/herald/view/HeraldRuleListView.php @@ -0,0 +1,65 @@ +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; + } + +}