diff --git a/src/applications/herald/controller/HeraldDisableController.php b/src/applications/herald/controller/HeraldDisableController.php index bdbefa55ea..def87049f7 100644 --- a/src/applications/herald/controller/HeraldDisableController.php +++ b/src/applications/herald/controller/HeraldDisableController.php @@ -1,66 +1,66 @@ getViewer(); $id = $request->getURIData('id'); $action = $request->getURIData('action'); $rule = id(new HeraldRuleQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$rule) { return new Aphront404Response(); } if ($rule->isGlobalRule()) { $this->requireApplicationCapability( HeraldManageGlobalRulesCapability::CAPABILITY); } $view_uri = '/'.$rule->getMonogram(); $is_disable = ($action === 'disable'); if ($request->isFormPost()) { $xaction = id(new HeraldRuleTransaction()) ->setTransactionType(HeraldRuleTransaction::TYPE_DISABLE) ->setNewValue($is_disable); id(new HeraldRuleEditor()) ->setActor($viewer) ->setContinueOnNoEffect(true) ->setContentSourceFromRequest($request) ->applyTransactions($rule, array($xaction)); return id(new AphrontRedirectResponse())->setURI($view_uri); } if ($is_disable) { - $title = pht('Really archive this rule?'); + $title = pht('Really disable this rule?'); $body = pht('This rule will no longer activate.'); - $button = pht('Archive Rule'); + $button = pht('Disable Rule'); } else { - $title = pht('Really activate this rule?'); + $title = pht('Really enable this rule?'); $body = pht('This rule will become active again.'); - $button = pht('Activate Rule'); + $button = pht('Enable Rule'); } $dialog = id(new AphrontDialogView()) ->setUser($viewer) ->setTitle($title) ->appendChild($body) ->addSubmitButton($button) ->addCancelButton($view_uri); return id(new AphrontDialogResponse())->setDialog($dialog); } } diff --git a/src/applications/herald/controller/HeraldRuleViewController.php b/src/applications/herald/controller/HeraldRuleViewController.php index 9e696c23c4..70a2d20224 100644 --- a/src/applications/herald/controller/HeraldRuleViewController.php +++ b/src/applications/herald/controller/HeraldRuleViewController.php @@ -1,162 +1,158 @@ getViewer(); $id = $request->getURIData('id'); $rule = id(new HeraldRuleQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->needConditionsAndActions(true) + ->needValidateAuthors(true) ->executeOne(); if (!$rule) { return new Aphront404Response(); } $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($rule->getName()) ->setPolicyObject($rule) ->setHeaderIcon('fa-bullhorn'); if ($rule->getIsDisabled()) { - $header->setStatus( - 'fa-ban', - 'red', - pht('Archived')); + $header->setStatus('fa-ban', 'red', pht('Disabled')); + } else if (!$rule->hasValidAuthor()) { + $header->setStatus('fa-user', 'red', pht('Author Not Active')); } else { - $header->setStatus( - 'fa-check', - 'bluegrey', - pht('Active')); + $header->setStatus('fa-check', 'bluegrey', pht('Active')); } $curtain = $this->buildCurtain($rule); $details = $this->buildPropertySectionView($rule); $description = $this->buildDescriptionView($rule); $id = $rule->getID(); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb("H{$id}"); $crumbs->setBorder(true); $timeline = $this->buildTransactionTimeline( $rule, new HeraldTransactionQuery()); $timeline->setShouldTerminate(true); $title = $rule->getName(); $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setCurtain($curtain) ->setMainColumn($timeline) ->addPropertySection(pht('Details'), $details) ->addPropertySection(pht('Description'), $description); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->appendChild($view); } private function buildCurtain(HeraldRule $rule) { $viewer = $this->getViewer(); $id = $rule->getID(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $rule, PhabricatorPolicyCapability::CAN_EDIT); $curtain = $this->newCurtainView($rule); $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Rule')) ->setHref($this->getApplicationURI("edit/{$id}/")) ->setIcon('fa-pencil') ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); if ($rule->getIsDisabled()) { $disable_uri = "disable/{$id}/enable/"; $disable_icon = 'fa-check'; - $disable_name = pht('Activate Rule'); + $disable_name = pht('Enable Rule'); } else { $disable_uri = "disable/{$id}/disable/"; $disable_icon = 'fa-ban'; - $disable_name = pht('Archive Rule'); + $disable_name = pht('Disable Rule'); } $curtain->addAction( id(new PhabricatorActionView()) - ->setName(pht('Disable Rule')) ->setHref($this->getApplicationURI($disable_uri)) ->setIcon($disable_icon) ->setName($disable_name) ->setDisabled(!$can_edit) ->setWorkflow(true)); return $curtain; } private function buildPropertySectionView( HeraldRule $rule) { $viewer = $this->getRequest()->getUser(); $view = id(new PHUIPropertyListView()) ->setUser($viewer); $view->addProperty( pht('Rule Type'), idx(HeraldRuleTypeConfig::getRuleTypeMap(), $rule->getRuleType())); if ($rule->isPersonalRule()) { $view->addProperty( pht('Author'), $viewer->renderHandle($rule->getAuthorPHID())); } $adapter = HeraldAdapter::getAdapterForContentType($rule->getContentType()); if ($adapter) { $view->addProperty( pht('Applies To'), idx( HeraldAdapter::getEnabledAdapterMap($viewer), $rule->getContentType())); if ($rule->isObjectRule()) { $view->addProperty( pht('Trigger Object'), $viewer->renderHandle($rule->getTriggerObjectPHID())); } } return $view; } private function buildDescriptionView(HeraldRule $rule) { $viewer = $this->getRequest()->getUser(); $view = id(new PHUIPropertyListView()) ->setUser($viewer); $adapter = HeraldAdapter::getAdapterForContentType($rule->getContentType()); if ($adapter) { $handles = $viewer->loadHandles(HeraldAdapter::getHandlePHIDs($rule)); $rule_text = $adapter->renderRuleAsText($rule, $handles, $viewer); $view->addTextContent($rule_text); return $view; } return null; } } diff --git a/src/applications/herald/query/HeraldRuleQuery.php b/src/applications/herald/query/HeraldRuleQuery.php index 4fd48d3109..e6dba43c7a 100644 --- a/src/applications/herald/query/HeraldRuleQuery.php +++ b/src/applications/herald/query/HeraldRuleQuery.php @@ -1,279 +1,313 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withAuthorPHIDs(array $author_phids) { $this->authorPHIDs = $author_phids; return $this; } public function withRuleTypes(array $types) { $this->ruleTypes = $types; return $this; } public function withContentTypes(array $types) { $this->contentTypes = $types; return $this; } public function withDisabled($disabled) { $this->disabled = $disabled; return $this; } + public function withActive($active) { + $this->active = $active; + return $this; + } + public function withDatasourceQuery($query) { $this->datasourceQuery = $query; return $this; } public function withTriggerObjectPHIDs(array $phids) { $this->triggerObjectPHIDs = $phids; return $this; } public function needConditionsAndActions($need) { $this->needConditionsAndActions = $need; return $this; } public function needAppliedToPHIDs(array $phids) { $this->needAppliedToPHIDs = $phids; return $this; } public function needValidateAuthors($need) { $this->needValidateAuthors = $need; return $this; } public function newResultObject() { return new HeraldRule(); } protected function loadPage() { return $this->loadStandardPage($this->newResultObject()); } protected function willFilterPage(array $rules) { $rule_ids = mpull($rules, 'getID'); // Filter out any rules that have invalid adapters, or have adapters the // viewer isn't permitted to see or use (for example, Differential rules // if the user can't use Differential or Differential is not installed). $types = HeraldAdapter::getEnabledAdapterMap($this->getViewer()); foreach ($rules as $key => $rule) { if (empty($types[$rule->getContentType()])) { $this->didRejectResult($rule); unset($rules[$key]); } } - if ($this->needValidateAuthors) { + if ($this->needValidateAuthors || ($this->active !== null)) { $this->validateRuleAuthors($rules); } + if ($this->active !== null) { + $need_active = (bool)$this->active; + foreach ($rules as $key => $rule) { + if ($rule->getIsDisabled()) { + $is_active = false; + } else if (!$rule->hasValidAuthor()) { + $is_active = false; + } else { + $is_active = true; + } + + if ($is_active != $need_active) { + unset($rules[$key]); + } + } + } + + if (!$rules) { + return array(); + } + if ($this->needConditionsAndActions) { $conditions = id(new HeraldCondition())->loadAllWhere( 'ruleID IN (%Ld)', $rule_ids); $conditions = mgroup($conditions, 'getRuleID'); $actions = id(new HeraldActionRecord())->loadAllWhere( 'ruleID IN (%Ld)', $rule_ids); $actions = mgroup($actions, 'getRuleID'); foreach ($rules as $rule) { $rule->attachActions(idx($actions, $rule->getID(), array())); $rule->attachConditions(idx($conditions, $rule->getID(), array())); } } if ($this->needAppliedToPHIDs) { $conn_r = id(new HeraldRule())->establishConnection('r'); $applied = queryfx_all( $conn_r, 'SELECT * FROM %T WHERE ruleID IN (%Ld) AND phid IN (%Ls)', HeraldRule::TABLE_RULE_APPLIED, $rule_ids, $this->needAppliedToPHIDs); $map = array(); foreach ($applied as $row) { $map[$row['ruleID']][$row['phid']] = true; } foreach ($rules as $rule) { foreach ($this->needAppliedToPHIDs as $phid) { $rule->setRuleApplied( $phid, isset($map[$rule->getID()][$phid])); } } } $object_phids = array(); foreach ($rules as $rule) { if ($rule->isObjectRule()) { $object_phids[] = $rule->getTriggerObjectPHID(); } } if ($object_phids) { $objects = id(new PhabricatorObjectQuery()) ->setParentQuery($this) ->setViewer($this->getViewer()) ->withPHIDs($object_phids) ->execute(); $objects = mpull($objects, null, 'getPHID'); } else { $objects = array(); } foreach ($rules as $key => $rule) { if ($rule->isObjectRule()) { $object = idx($objects, $rule->getTriggerObjectPHID()); if (!$object) { unset($rules[$key]); continue; } $rule->attachTriggerObject($object); } } return $rules; } protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { $where = parent::buildWhereClauseParts($conn); if ($this->ids !== null) { $where[] = qsprintf( $conn, 'rule.id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( $conn, 'rule.phid IN (%Ls)', $this->phids); } if ($this->authorPHIDs !== null) { $where[] = qsprintf( $conn, 'rule.authorPHID IN (%Ls)', $this->authorPHIDs); } if ($this->ruleTypes !== null) { $where[] = qsprintf( $conn, 'rule.ruleType IN (%Ls)', $this->ruleTypes); } if ($this->contentTypes !== null) { $where[] = qsprintf( $conn, 'rule.contentType IN (%Ls)', $this->contentTypes); } if ($this->disabled !== null) { $where[] = qsprintf( $conn, 'rule.isDisabled = %d', (int)$this->disabled); } + if ($this->active !== null) { + $where[] = qsprintf( + $conn, + 'rule.isDisabled = %d', + (int)(!$this->active)); + } + if ($this->datasourceQuery !== null) { $where[] = qsprintf( $conn, 'rule.name LIKE %>', $this->datasourceQuery); } if ($this->triggerObjectPHIDs !== null) { $where[] = qsprintf( $conn, 'rule.triggerObjectPHID IN (%Ls)', $this->triggerObjectPHIDs); } return $where; } private function validateRuleAuthors(array $rules) { // "Global" and "Object" rules always have valid authors. foreach ($rules as $key => $rule) { if ($rule->isGlobalRule() || $rule->isObjectRule()) { $rule->attachValidAuthor(true); unset($rules[$key]); continue; } } if (!$rules) { return; } // For personal rules, the author needs to exist and not be disabled. $user_phids = mpull($rules, 'getAuthorPHID'); $users = id(new PhabricatorPeopleQuery()) ->setViewer($this->getViewer()) ->withPHIDs($user_phids) ->execute(); $users = mpull($users, null, 'getPHID'); foreach ($rules as $key => $rule) { $author_phid = $rule->getAuthorPHID(); if (empty($users[$author_phid])) { $rule->attachValidAuthor(false); continue; } if (!$users[$author_phid]->isUserActivated()) { $rule->attachValidAuthor(false); continue; } $rule->attachValidAuthor(true); $rule->attachAuthor($users[$author_phid]); } } public function getQueryApplicationClass() { return 'PhabricatorHeraldApplication'; } protected function getPrimaryTableAlias() { return 'rule'; } } diff --git a/src/applications/herald/query/HeraldRuleSearchEngine.php b/src/applications/herald/query/HeraldRuleSearchEngine.php index 3a6da6145a..47a6832731 100644 --- a/src/applications/herald/query/HeraldRuleSearchEngine.php +++ b/src/applications/herald/query/HeraldRuleSearchEngine.php @@ -1,184 +1,200 @@ needValidateAuthors(true); } protected function buildCustomSearchFields() { $viewer = $this->requireViewer(); $rule_types = HeraldRuleTypeConfig::getRuleTypeMap(); $content_types = HeraldAdapter::getEnabledAdapterMap($viewer); return array( id(new PhabricatorUsersSearchField()) ->setLabel(pht('Authors')) ->setKey('authorPHIDs') ->setAliases(array('author', 'authors', 'authorPHID')) ->setDescription( pht('Search for rules with given authors.')), id(new PhabricatorSearchCheckboxesField()) ->setKey('ruleTypes') ->setAliases(array('ruleType')) ->setLabel(pht('Rule Type')) ->setDescription( pht('Search for rules of given types.')) ->setOptions($rule_types), id(new PhabricatorSearchCheckboxesField()) ->setKey('contentTypes') ->setLabel(pht('Content Type')) ->setDescription( pht('Search for rules affecting given types of content.')) ->setOptions($content_types), id(new PhabricatorSearchThreeStateField()) - ->setLabel(pht('Rule Status')) + ->setLabel(pht('Active Rules')) + ->setKey('active') + ->setOptions( + pht('(Show All)'), + pht('Show Only Active Rules'), + pht('Show Only Inactive Rules')), + id(new PhabricatorSearchThreeStateField()) + ->setLabel(pht('Disabled Rules')) ->setKey('disabled') ->setOptions( pht('(Show All)'), pht('Show Only Disabled Rules'), pht('Show Only Enabled Rules')), ); } protected function buildQueryFromParameters(array $map) { $query = $this->newQuery(); if ($map['authorPHIDs']) { $query->withAuthorPHIDs($map['authorPHIDs']); } if ($map['contentTypes']) { $query->withContentTypes($map['contentTypes']); } if ($map['ruleTypes']) { $query->withRuleTypes($map['ruleTypes']); } if ($map['disabled'] !== null) { $query->withDisabled($map['disabled']); } + if ($map['active'] !== null) { + $query->withActive($map['active']); + } + return $query; } protected function getURI($path) { return '/herald/'.$path; } protected function getBuiltinQueryNames() { $names = array(); if ($this->requireViewer()->isLoggedIn()) { $names['authored'] = pht('Authored'); } $names['active'] = pht('Active'); $names['all'] = pht('All'); return $names; } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); $viewer_phid = $this->requireViewer()->getPHID(); switch ($query_key) { case 'all': return $query; case 'active': - return $query->setParameter('disabled', false); + return $query + ->setParameter('active', true); case 'authored': return $query ->setParameter('authorPHIDs', array($viewer_phid)) ->setParameter('disabled', false); } return parent::buildSavedQueryFromBuiltin($query_key); } protected function renderResultList( array $rules, 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); } $result = new PhabricatorApplicationSearchResultView(); $result->setObjectList($list); $result->setNoDataString(pht('No rules found.')); return $result; } protected function getNewUserBody() { $create_button = id(new PHUIButtonView()) ->setTag('a') ->setText(pht('Create Herald Rule')) ->setHref('/herald/create/') ->setColor(PHUIButtonView::GREEN); $icon = $this->getApplication()->getIcon(); $app_name = $this->getApplication()->getName(); $view = id(new PHUIBigInfoView()) ->setIcon($icon) ->setTitle(pht('Welcome to %s', $app_name)) ->setDescription( pht('A flexible rules engine that can notify and act on '. 'other actions such as tasks, diffs, and commits.')) ->addAction($create_button); return $view; } }