diff --git a/src/applications/herald/controller/HeraldController.php b/src/applications/herald/controller/HeraldController.php index b3ba2f17c3..2151c62aa9 100644 --- a/src/applications/herald/controller/HeraldController.php +++ b/src/applications/herald/controller/HeraldController.php @@ -1,45 +1,40 @@ buildSideNavView(true)->getMenu(); } protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); $crumbs->addAction( id(new PHUIListItemView()) ->setName(pht('Create Herald Rule')) ->setHref($this->getApplicationURI('new/')) ->setIcon('fa-plus-square')); return $crumbs; } - public function buildSideNavView($for_app = false) { - $user = $this->getRequest()->getUser(); + public function buildSideNavView() { + $viewer = $this->getViewer(); $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - if ($for_app) { - $nav->addFilter('new', pht('Create Rule')); - } - id(new HeraldRuleSearchEngine()) - ->setViewer($user) + ->setViewer($viewer) ->addNavigationItems($nav->getMenu()); - $nav - ->addLabel(pht('Utilities')) - ->addFilter('test', pht('Test Console')) - ->addFilter('transcript', pht('Transcripts')); + $nav->addLabel(pht('Utilities')) + ->addFilter('test', pht('Test Console')) + ->addFilter('transcript', pht('Transcripts')); $nav->selectFilter(null); return $nav; } } diff --git a/src/applications/herald/controller/HeraldNewController.php b/src/applications/herald/controller/HeraldNewController.php index 53e1faf7d6..c25b4c839b 100644 --- a/src/applications/herald/controller/HeraldNewController.php +++ b/src/applications/herald/controller/HeraldNewController.php @@ -1,316 +1,317 @@ getViewer(); $content_type_map = HeraldAdapter::getEnabledAdapterMap($viewer); $rule_type_map = HeraldRuleTypeConfig::getRuleTypeMap(); $errors = array(); $e_type = null; $e_rule = null; $e_object = null; $step = $request->getInt('step'); if ($request->isFormPost()) { $content_type = $request->getStr('content_type'); if (empty($content_type_map[$content_type])) { $errors[] = pht('You must choose a content type for this rule.'); $e_type = pht('Required'); $step = 0; } if (!$errors && $step > 1) { $rule_type = $request->getStr('rule_type'); if (empty($rule_type_map[$rule_type])) { $errors[] = pht('You must choose a rule type for this rule.'); $e_rule = pht('Required'); $step = 1; } } if (!$errors && $step >= 2) { $target_phid = null; $object_name = $request->getStr('objectName'); $done = false; if ($rule_type != HeraldRuleTypeConfig::RULE_TYPE_OBJECT) { $done = true; } else if (strlen($object_name)) { $target_object = id(new PhabricatorObjectQuery()) ->setViewer($viewer) ->withNames(array($object_name)) ->executeOne(); if ($target_object) { $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $target_object, PhabricatorPolicyCapability::CAN_EDIT); if (!$can_edit) { $errors[] = pht( 'You can not create a rule for that object, because you do '. 'not have permission to edit it. You can only create rules '. 'for objects you can edit.'); $e_object = pht('Not Editable'); $step = 2; } else { $adapter = HeraldAdapter::getAdapterForContentType($content_type); if (!$adapter->canTriggerOnObject($target_object)) { $errors[] = pht( 'This object is not of an allowed type for the rule. '. 'Rules can only trigger on certain objects.'); $e_object = pht('Invalid'); $step = 2; } else { $target_phid = $target_object->getPHID(); $done = true; } } } else { $errors[] = pht('No object exists by that name.'); $e_object = pht('Invalid'); $step = 2; } } else if ($step > 2) { $errors[] = pht( 'You must choose an object to associate this rule with.'); $e_object = pht('Required'); $step = 2; } if (!$errors && $done) { $uri = id(new PhutilURI('edit/')) ->setQueryParams( array( 'content_type' => $content_type, 'rule_type' => $rule_type, 'targetPHID' => $target_phid, )); $uri = $this->getApplicationURI($uri); return id(new AphrontRedirectResponse())->setURI($uri); } } } $content_type = $request->getStr('content_type'); $rule_type = $request->getStr('rule_type'); $form = id(new AphrontFormView()) ->setUser($viewer) ->setAction($this->getApplicationURI('new/')); switch ($step) { case 0: default: $content_types = $this->renderContentTypeControl( $content_type_map, $e_type); $form ->addHiddenInput('step', 1) ->appendChild($content_types); $cancel_text = null; $cancel_uri = $this->getApplicationURI(); break; case 1: $rule_types = $this->renderRuleTypeControl( $rule_type_map, $e_rule); $form ->addHiddenInput('content_type', $content_type) ->addHiddenInput('step', 2) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel(pht('Rule for')) ->setValue( phutil_tag( 'strong', array(), idx($content_type_map, $content_type)))) ->appendChild($rule_types); $cancel_text = pht('Back'); $cancel_uri = id(new PhutilURI('new/')) ->setQueryParams( array( 'content_type' => $content_type, 'step' => 0, )); $cancel_uri = $this->getApplicationURI($cancel_uri); break; case 2: $adapter = HeraldAdapter::getAdapterForContentType($content_type); $form ->addHiddenInput('content_type', $content_type) ->addHiddenInput('rule_type', $rule_type) ->addHiddenInput('step', 3) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel(pht('Rule for')) ->setValue( phutil_tag( 'strong', array(), idx($content_type_map, $content_type)))) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel(pht('Rule Type')) ->setValue( phutil_tag( 'strong', array(), idx($rule_type_map, $rule_type)))) ->appendRemarkupInstructions( pht( 'Choose the object this rule will act on (for example, enter '. '`rX` to act on the `rX` repository, or `#project` to act on '. 'a project).')) ->appendRemarkupInstructions( $adapter->explainValidTriggerObjects()) ->appendChild( id(new AphrontFormTextControl()) ->setName('objectName') ->setError($e_object) ->setValue($request->getStr('objectName')) ->setLabel(pht('Object'))); $cancel_text = pht('Back'); $cancel_uri = id(new PhutilURI('new/')) ->setQueryParams( array( 'content_type' => $content_type, 'rule_type' => $rule_type, 'step' => 1, )); $cancel_uri = $this->getApplicationURI($cancel_uri); break; } $form ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Continue')) ->addCancelButton($cancel_uri, $cancel_text)); $form_box = id(new PHUIObjectBoxView()) ->setFormErrors($errors) ->setHeaderText(pht('Create Herald Rule')) ->setForm($form); $crumbs = $this ->buildApplicationCrumbs() ->addTextCrumb(pht('Create Rule')); - return $this->buildApplicationPage( - array( - $crumbs, - $form_box, - ), - array( - 'title' => pht('Create Herald Rule'), + $title = pht('Create Herald Rule'); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $form_box, )); } private function renderContentTypeControl(array $content_type_map, $e_type) { $request = $this->getRequest(); $radio = id(new AphrontFormRadioButtonControl()) ->setLabel(pht('New Rule for')) ->setName('content_type') ->setValue($request->getStr('content_type')) ->setError($e_type); foreach ($content_type_map as $value => $name) { $adapter = HeraldAdapter::getAdapterForContentType($value); $radio->addButton( $value, $name, phutil_escape_html_newlines($adapter->getAdapterContentDescription())); } return $radio; } private function renderRuleTypeControl(array $rule_type_map, $e_rule) { $request = $this->getRequest(); // Reorder array to put less powerful rules first. $rule_type_map = array_select_keys( $rule_type_map, array( HeraldRuleTypeConfig::RULE_TYPE_PERSONAL, HeraldRuleTypeConfig::RULE_TYPE_OBJECT, HeraldRuleTypeConfig::RULE_TYPE_GLOBAL, )) + $rule_type_map; list($can_global, $global_link) = $this->explainApplicationCapability( HeraldManageGlobalRulesCapability::CAPABILITY, pht('You have permission to create and manage global rules.'), pht('You do not have permission to create or manage global rules.')); $captions = array( HeraldRuleTypeConfig::RULE_TYPE_PERSONAL => pht( 'Personal rules notify you about events. You own them, but they can '. 'only affect you. Personal rules only trigger for objects you have '. 'permission to see.'), HeraldRuleTypeConfig::RULE_TYPE_OBJECT => pht( 'Object rules notify anyone about events. They are bound to an '. 'object (like a repository) and can only act on that object. You '. 'must be able to edit an object to create object rules for it. '. 'Other users who can edit the object can edit its rules.'), HeraldRuleTypeConfig::RULE_TYPE_GLOBAL => array( pht( 'Global rules notify anyone about events. Global rules can '. 'bypass access control policies and act on any object.'), $global_link, ), ); $radio = id(new AphrontFormRadioButtonControl()) ->setLabel(pht('Rule Type')) ->setName('rule_type') ->setValue($request->getStr('rule_type')) ->setError($e_rule); $adapter = HeraldAdapter::getAdapterForContentType( $request->getStr('content_type')); foreach ($rule_type_map as $value => $name) { $caption = idx($captions, $value); $disabled = ($value == HeraldRuleTypeConfig::RULE_TYPE_GLOBAL) && (!$can_global); if (!$adapter->supportsRuleType($value)) { $disabled = true; $caption = array( $caption, "\n\n", phutil_tag( 'em', array(), pht( 'This rule type is not supported by the selected content type.')), ); } $radio->addButton( $value, $name, phutil_escape_html_newlines($caption), $disabled ? 'disabled' : null, $disabled); } return $radio; } } diff --git a/src/applications/herald/controller/HeraldRuleController.php b/src/applications/herald/controller/HeraldRuleController.php index 510113b0de..d8e5e42df3 100644 --- a/src/applications/herald/controller/HeraldRuleController.php +++ b/src/applications/herald/controller/HeraldRuleController.php @@ -1,692 +1,693 @@ getViewer(); $id = $request->getURIData('id'); $content_type_map = HeraldAdapter::getEnabledAdapterMap($viewer); $rule_type_map = HeraldRuleTypeConfig::getRuleTypeMap(); if ($id) { $rule = id(new HeraldRuleQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$rule) { return new Aphront404Response(); } $cancel_uri = '/'.$rule->getMonogram(); } else { $new_uri = $this->getApplicationURI('new/'); $rule = new HeraldRule(); $rule->setAuthorPHID($viewer->getPHID()); $rule->setMustMatchAll(1); $content_type = $request->getStr('content_type'); $rule->setContentType($content_type); $rule_type = $request->getStr('rule_type'); if (!isset($rule_type_map[$rule_type])) { return $this->newDialog() ->setTitle(pht('Invalid Rule Type')) ->appendParagraph( pht( 'The selected rule type ("%s") is not recognized by Herald.', $rule_type)) ->addCancelButton($new_uri); } $rule->setRuleType($rule_type); try { $adapter = HeraldAdapter::getAdapterForContentType( $rule->getContentType()); } catch (Exception $ex) { return $this->newDialog() ->setTitle(pht('Invalid Content Type')) ->appendParagraph( pht( 'The selected content type ("%s") is not recognized by '. 'Herald.', $rule->getContentType())) ->addCancelButton($new_uri); } if (!$adapter->supportsRuleType($rule->getRuleType())) { return $this->newDialog() ->setTitle(pht('Rule/Content Mismatch')) ->appendParagraph( pht( 'The selected rule type ("%s") is not supported by the selected '. 'content type ("%s").', $rule->getRuleType(), $rule->getContentType())) ->addCancelButton($new_uri); } if ($rule->isObjectRule()) { $rule->setTriggerObjectPHID($request->getStr('targetPHID')); $object = id(new PhabricatorObjectQuery()) ->setViewer($viewer) ->withPHIDs(array($rule->getTriggerObjectPHID())) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$object) { throw new Exception( pht('No valid object provided for object rule!')); } if (!$adapter->canTriggerOnObject($object)) { throw new Exception( pht('Object is of wrong type for adapter!')); } } $cancel_uri = $this->getApplicationURI(); } if ($rule->isGlobalRule()) { $this->requireApplicationCapability( HeraldManageGlobalRulesCapability::CAPABILITY); } $adapter = HeraldAdapter::getAdapterForContentType($rule->getContentType()); $local_version = id(new HeraldRule())->getConfigVersion(); if ($rule->getConfigVersion() > $local_version) { throw new Exception( pht( 'This rule was created with a newer version of Herald. You can not '. 'view or edit it in this older version. Upgrade your Phabricator '. 'deployment.')); } // Upgrade rule version to our version, since we might add newly-defined // conditions, etc. $rule->setConfigVersion($local_version); $rule_conditions = $rule->loadConditions(); $rule_actions = $rule->loadActions(); $rule->attachConditions($rule_conditions); $rule->attachActions($rule_actions); $e_name = true; $errors = array(); if ($request->isFormPost() && $request->getStr('save')) { list($e_name, $errors) = $this->saveRule($adapter, $rule, $request); if (!$errors) { $id = $rule->getID(); $uri = '/'.$rule->getMonogram(); return id(new AphrontRedirectResponse())->setURI($uri); } } $must_match_selector = $this->renderMustMatchSelector($rule); $repetition_selector = $this->renderRepetitionSelector($rule, $adapter); $handles = $this->loadHandlesForRule($rule); require_celerity_resource('herald-css'); $content_type_name = $content_type_map[$rule->getContentType()]; $rule_type_name = $rule_type_map[$rule->getRuleType()]; $form = id(new AphrontFormView()) ->setUser($viewer) ->setID('herald-rule-edit-form') ->addHiddenInput('content_type', $rule->getContentType()) ->addHiddenInput('rule_type', $rule->getRuleType()) ->addHiddenInput('save', 1) ->appendChild( // Build this explicitly (instead of using addHiddenInput()) // so we can add a sigil to it. javelin_tag( 'input', array( 'type' => 'hidden', 'name' => 'rule', 'sigil' => 'rule', ))) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Rule Name')) ->setName('name') ->setError($e_name) ->setValue($rule->getName())); $trigger_object_control = false; if ($rule->isObjectRule()) { $trigger_object_control = id(new AphrontFormStaticControl()) ->setValue( pht( 'This rule triggers for %s.', $handles[$rule->getTriggerObjectPHID()]->renderLink())); } $form ->appendChild( id(new AphrontFormMarkupControl()) ->setValue(pht( 'This %s rule triggers for %s.', phutil_tag('strong', array(), $rule_type_name), phutil_tag('strong', array(), $content_type_name)))) ->appendChild($trigger_object_control) ->appendChild( id(new PHUIFormInsetView()) ->setTitle(pht('Conditions')) ->setRightButton(javelin_tag( 'a', array( 'href' => '#', 'class' => 'button green', 'sigil' => 'create-condition', 'mustcapture' => true, ), pht('New Condition'))) ->setDescription( pht('When %s these conditions are met:', $must_match_selector)) ->setContent(javelin_tag( 'table', array( 'sigil' => 'rule-conditions', 'class' => 'herald-condition-table', ), ''))) ->appendChild( id(new PHUIFormInsetView()) ->setTitle(pht('Action')) ->setRightButton(javelin_tag( 'a', array( 'href' => '#', 'class' => 'button green', 'sigil' => 'create-action', 'mustcapture' => true, ), pht('New Action'))) ->setDescription(pht( 'Take these actions %s this rule matches:', $repetition_selector)) ->setContent(javelin_tag( 'table', array( 'sigil' => 'rule-actions', 'class' => 'herald-action-table', ), ''))) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Save Rule')) ->addCancelButton($cancel_uri)); $this->setupEditorBehavior($rule, $handles, $adapter); $title = $rule->getID() ? pht('Edit Herald Rule') : pht('Create Herald Rule'); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($title) ->setFormErrors($errors) ->setForm($form); $crumbs = $this ->buildApplicationCrumbs() ->addTextCrumb($title); - return $this->buildApplicationPage( - array( - $crumbs, - $form_box, - ), - array( - 'title' => pht('Edit Rule'), + $title = pht('Edit Rule'); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $form_box, )); } private function saveRule(HeraldAdapter $adapter, $rule, $request) { $rule->setName($request->getStr('name')); $match_all = ($request->getStr('must_match') == 'all'); $rule->setMustMatchAll((int)$match_all); $repetition_policy_param = $request->getStr('repetition_policy'); $rule->setRepetitionPolicy( HeraldRepetitionPolicyConfig::toInt($repetition_policy_param)); $e_name = true; $errors = array(); if (!strlen($rule->getName())) { $e_name = pht('Required'); $errors[] = pht('Rule must have a name.'); } $data = null; try { $data = phutil_json_decode($request->getStr('rule')); } catch (PhutilJSONParserException $ex) { throw new PhutilProxyException( pht('Failed to decode rule data.'), $ex); } if (!is_array($data) || !$data['conditions'] || !$data['actions']) { throw new Exception(pht('Failed to decode rule data.')); } $conditions = array(); foreach ($data['conditions'] as $condition) { if ($condition === null) { // We manage this as a sparse array on the client, so may receive // NULL if conditions have been removed. continue; } $obj = new HeraldCondition(); $obj->setFieldName($condition[0]); $obj->setFieldCondition($condition[1]); if (is_array($condition[2])) { $obj->setValue(array_keys($condition[2])); } else { $obj->setValue($condition[2]); } try { $adapter->willSaveCondition($obj); } catch (HeraldInvalidConditionException $ex) { $errors[] = $ex->getMessage(); } $conditions[] = $obj; } $actions = array(); foreach ($data['actions'] as $action) { if ($action === null) { // Sparse on the client; removals can give us NULLs. continue; } if (!isset($action[1])) { // Legitimate for any action which doesn't need a target, like // "Do nothing". $action[1] = null; } $obj = new HeraldActionRecord(); $obj->setAction($action[0]); $obj->setTarget($action[1]); try { $adapter->willSaveAction($rule, $obj); } catch (HeraldInvalidActionException $ex) { $errors[] = $ex->getMessage(); } $actions[] = $obj; } $rule->attachConditions($conditions); $rule->attachActions($actions); if (!$errors) { $edit_action = $rule->getID() ? 'edit' : 'create'; $rule->openTransaction(); $rule->save(); $rule->saveConditions($conditions); $rule->saveActions($actions); $rule->saveTransaction(); } return array($e_name, $errors); } private function setupEditorBehavior( HeraldRule $rule, array $handles, HeraldAdapter $adapter) { $serial_conditions = array( array('default', 'default', ''), ); if ($rule->getConditions()) { $serial_conditions = array(); foreach ($rule->getConditions() as $condition) { $value = $adapter->getEditorValueForCondition( $this->getViewer(), $condition); $serial_conditions[] = array( $condition->getFieldName(), $condition->getFieldCondition(), $value, ); } } $serial_actions = array( array('default', ''), ); if ($rule->getActions()) { $serial_actions = array(); foreach ($rule->getActions() as $action) { $value = $adapter->getEditorValueForAction( $this->getViewer(), $action); $serial_actions[] = array( $action->getAction(), $value, ); } } $all_rules = $this->loadRulesThisRuleMayDependUpon($rule); $all_rules = mpull($all_rules, 'getName', 'getPHID'); asort($all_rules); $all_fields = $adapter->getFieldNameMap(); $all_conditions = $adapter->getConditionNameMap(); $all_actions = $adapter->getActionNameMap($rule->getRuleType()); $fields = $adapter->getFields(); $field_map = array_select_keys($all_fields, $fields); // Populate any fields which exist in the rule but which we don't know the // names of, so that saving a rule without touching anything doesn't change // it. foreach ($rule->getConditions() as $condition) { $field_name = $condition->getFieldName(); if (empty($field_map[$field_name])) { $field_map[$field_name] = pht('', $field_name); } } $actions = $adapter->getActions($rule->getRuleType()); $action_map = array_select_keys($all_actions, $actions); // Populate any actions which exist in the rule but which we don't know the // names of, so that saving a rule without touching anything doesn't change // it. foreach ($rule->getActions() as $action) { $action_name = $action->getAction(); if (empty($action_map[$action_name])) { $action_map[$action_name] = pht('', $action_name); } } $config_info = array(); $config_info['fields'] = $this->getFieldGroups($adapter, $field_map); $config_info['conditions'] = $all_conditions; $config_info['actions'] = $this->getActionGroups($adapter, $action_map); $config_info['valueMap'] = array(); foreach ($field_map as $field => $name) { try { $field_conditions = $adapter->getConditionsForField($field); } catch (Exception $ex) { $field_conditions = array(HeraldAdapter::CONDITION_UNCONDITIONALLY); } $config_info['conditionMap'][$field] = $field_conditions; } foreach ($field_map as $field => $fname) { foreach ($config_info['conditionMap'][$field] as $condition) { $value_key = $adapter->getValueTypeForFieldAndCondition( $field, $condition); if ($value_key instanceof HeraldFieldValue) { $value_key->setViewer($this->getViewer()); $spec = $value_key->getControlSpecificationDictionary(); $value_key = $value_key->getFieldValueKey(); $config_info['valueMap'][$value_key] = $spec; } $config_info['values'][$field][$condition] = $value_key; } } $config_info['rule_type'] = $rule->getRuleType(); foreach ($action_map as $action => $name) { try { $value_key = $adapter->getValueTypeForAction( $action, $rule->getRuleType()); } catch (Exception $ex) { $value_key = new HeraldEmptyFieldValue(); } if ($value_key instanceof HeraldFieldValue) { $value_key->setViewer($this->getViewer()); $spec = $value_key->getControlSpecificationDictionary(); $value_key = $value_key->getFieldValueKey(); $config_info['valueMap'][$value_key] = $spec; } $config_info['targets'][$action] = $value_key; } Javelin::initBehavior( 'herald-rule-editor', array( 'root' => 'herald-rule-edit-form', 'conditions' => (object)$serial_conditions, 'actions' => (object)$serial_actions, 'template' => $this->buildTokenizerTemplates() + array( 'rules' => $all_rules, ), 'info' => $config_info, )); } private function loadHandlesForRule($rule) { $phids = array(); foreach ($rule->getActions() as $action) { if (!is_array($action->getTarget())) { continue; } foreach ($action->getTarget() as $target) { $target = (array)$target; foreach ($target as $phid) { $phids[] = $phid; } } } foreach ($rule->getConditions() as $condition) { $value = $condition->getValue(); if (is_array($value)) { foreach ($value as $phid) { $phids[] = $phid; } } } $phids[] = $rule->getAuthorPHID(); if ($rule->isObjectRule()) { $phids[] = $rule->getTriggerObjectPHID(); } return $this->loadViewerHandles($phids); } /** * Render the selector for the "When (all of | any of) these conditions are * met:" element. */ private function renderMustMatchSelector($rule) { return AphrontFormSelectControl::renderSelectTag( $rule->getMustMatchAll() ? 'all' : 'any', array( 'all' => pht('all of'), 'any' => pht('any of'), ), array( 'name' => 'must_match', )); } /** * Render the selector for "Take these actions (every time | only the first * time) this rule matches..." element. */ private function renderRepetitionSelector($rule, HeraldAdapter $adapter) { $repetition_policy = HeraldRepetitionPolicyConfig::toString( $rule->getRepetitionPolicy()); $repetition_options = $adapter->getRepetitionOptions(); $repetition_names = HeraldRepetitionPolicyConfig::getMap(); $repetition_map = array_select_keys($repetition_names, $repetition_options); if (count($repetition_map) < 2) { return head($repetition_names); } else { return AphrontFormSelectControl::renderSelectTag( $repetition_policy, $repetition_map, array( 'name' => 'repetition_policy', )); } } protected function buildTokenizerTemplates() { $template = new AphrontTokenizerTemplateView(); $template = $template->render(); return array( 'markup' => $template, ); } /** * Load rules for the "Another Herald rule..." condition dropdown, which * allows one rule to depend upon the success or failure of another rule. */ private function loadRulesThisRuleMayDependUpon(HeraldRule $rule) { $viewer = $this->getRequest()->getUser(); // Any rule can depend on a global rule. $all_rules = id(new HeraldRuleQuery()) ->setViewer($viewer) ->withRuleTypes(array(HeraldRuleTypeConfig::RULE_TYPE_GLOBAL)) ->withContentTypes(array($rule->getContentType())) ->execute(); if ($rule->isObjectRule()) { // Object rules may depend on other rules for the same object. $all_rules += id(new HeraldRuleQuery()) ->setViewer($viewer) ->withRuleTypes(array(HeraldRuleTypeConfig::RULE_TYPE_OBJECT)) ->withContentTypes(array($rule->getContentType())) ->withTriggerObjectPHIDs(array($rule->getTriggerObjectPHID())) ->execute(); } if ($rule->isPersonalRule()) { // Personal rules may depend upon your other personal rules. $all_rules += id(new HeraldRuleQuery()) ->setViewer($viewer) ->withRuleTypes(array(HeraldRuleTypeConfig::RULE_TYPE_PERSONAL)) ->withContentTypes(array($rule->getContentType())) ->withAuthorPHIDs(array($rule->getAuthorPHID())) ->execute(); } // mark disabled rules as disabled since they are not useful as such; // don't filter though to keep edit cases sane / expected foreach ($all_rules as $current_rule) { if ($current_rule->getIsDisabled()) { $current_rule->makeEphemeral(); $current_rule->setName($rule->getName().' '.pht('(Disabled)')); } } // A rule can not depend upon itself. unset($all_rules[$rule->getID()]); return $all_rules; } private function getFieldGroups(HeraldAdapter $adapter, array $field_map) { $group_map = array(); foreach ($field_map as $field_key => $field_name) { $group_key = $adapter->getFieldGroupKey($field_key); $group_map[$group_key][$field_key] = $field_name; } return $this->getGroups( $group_map, HeraldFieldGroup::getAllFieldGroups()); } private function getActionGroups(HeraldAdapter $adapter, array $action_map) { $group_map = array(); foreach ($action_map as $action_key => $action_name) { $group_key = $adapter->getActionGroupKey($action_key); $group_map[$group_key][$action_key] = $action_name; } return $this->getGroups( $group_map, HeraldActionGroup::getAllActionGroups()); } private function getGroups(array $item_map, array $group_list) { assert_instances_of($group_list, 'HeraldGroup'); $groups = array(); foreach ($item_map as $group_key => $options) { asort($options); $group_object = idx($group_list, $group_key); if ($group_object) { $group_label = $group_object->getGroupLabel(); $group_order = $group_object->getSortKey(); } else { $group_label = nonempty($group_key, pht('Other')); $group_order = 'Z'; } $groups[] = array( 'label' => $group_label, 'options' => $options, 'order' => $group_order, ); } return array_values(isort($groups, 'order')); } } diff --git a/src/applications/herald/controller/HeraldRuleViewController.php b/src/applications/herald/controller/HeraldRuleViewController.php index 2063ebcc2f..c8e0442dc5 100644 --- a/src/applications/herald/controller/HeraldRuleViewController.php +++ b/src/applications/herald/controller/HeraldRuleViewController.php @@ -1,156 +1,157 @@ getViewer(); $id = $request->getURIData('id'); $rule = id(new HeraldRuleQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->needConditionsAndActions(true) ->executeOne(); if (!$rule) { return new Aphront404Response(); } $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($rule->getName()) ->setPolicyObject($rule); if ($rule->getIsDisabled()) { $header->setStatus( 'fa-ban', 'red', pht('Archived')); } else { $header->setStatus( 'fa-check', 'bluegrey', pht('Active')); } $actions = $this->buildActionView($rule); $properties = $this->buildPropertyView($rule, $actions); $id = $rule->getID(); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb("H{$id}"); $object_box = id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($properties); $timeline = $this->buildTransactionTimeline( $rule, new HeraldTransactionQuery()); $timeline->setShouldTerminate(true); - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - $timeline, - ), - array( - 'title' => $rule->getName(), + $title = $rule->getName(); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $object_box, + $timeline, )); } private function buildActionView(HeraldRule $rule) { $viewer = $this->getRequest()->getUser(); $id = $rule->getID(); $view = id(new PhabricatorActionListView()) ->setUser($viewer) ->setObject($rule) ->setObjectURI('/'.$rule->getMonogram()); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $rule, PhabricatorPolicyCapability::CAN_EDIT); $view->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'); } else { $disable_uri = "disable/{$id}/disable/"; $disable_icon = 'fa-ban'; $disable_name = pht('Archive Rule'); } $view->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 $view; } private function buildPropertyView( HeraldRule $rule, PhabricatorActionListView $actions) { $viewer = $this->getRequest()->getUser(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) ->setObject($rule) ->setActionList($actions); $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())); } $view->invokeWillRenderEvent(); $view->addSectionHeader( pht('Rule Description'), PHUIPropertyListView::ICON_SUMMARY); $handles = $viewer->loadHandles(HeraldAdapter::getHandlePHIDs($rule)); $rule_text = $adapter->renderRuleAsText($rule, $handles, $viewer); $view->addTextContent($rule_text); } return $view; } } diff --git a/src/applications/herald/controller/HeraldTestConsoleController.php b/src/applications/herald/controller/HeraldTestConsoleController.php index 64b7c5867b..1e12825291 100644 --- a/src/applications/herald/controller/HeraldTestConsoleController.php +++ b/src/applications/herald/controller/HeraldTestConsoleController.php @@ -1,120 +1,120 @@ getViewer(); $object_name = trim($request->getStr('object_name')); $e_name = true; $errors = array(); if ($request->isFormPost()) { if (!$object_name) { $e_name = pht('Required'); $errors[] = pht('An object name is required.'); } if (!$errors) { $object = id(new PhabricatorObjectQuery()) ->setViewer($viewer) ->withNames(array($object_name)) ->executeOne(); if (!$object) { $e_name = pht('Invalid'); $errors[] = pht('No object exists with that name.'); } if (!$errors) { // TODO: Let the adapters claim objects instead. if ($object instanceof DifferentialRevision) { $adapter = HeraldDifferentialRevisionAdapter::newLegacyAdapter( $object, $object->loadActiveDiff()); } else if ($object instanceof PhabricatorRepositoryCommit) { $adapter = id(new HeraldCommitAdapter()) ->setCommit($object); } else if ($object instanceof ManiphestTask) { $adapter = id(new HeraldManiphestTaskAdapter()) ->setTask($object); } else if ($object instanceof PholioMock) { $adapter = id(new HeraldPholioMockAdapter()) ->setMock($object); } else if ($object instanceof PhrictionDocument) { $adapter = id(new PhrictionDocumentHeraldAdapter()) ->setDocument($object); } else if ($object instanceof PonderQuestion) { $adapter = id(new HeraldPonderQuestionAdapter()) ->setQuestion($object); } else if ($object instanceof PhabricatorMetaMTAMail) { $adapter = id(new PhabricatorMailOutboundMailHeraldAdapter()) ->setObject($object); } else { throw new Exception(pht('Can not build adapter for object!')); } $adapter->setIsNewObject(false); $rules = id(new HeraldRuleQuery()) ->setViewer($viewer) ->withContentTypes(array($adapter->getAdapterContentType())) ->withDisabled(false) ->needConditionsAndActions(true) ->needAppliedToPHIDs(array($object->getPHID())) ->needValidateAuthors(true) ->execute(); $engine = id(new HeraldEngine()) ->setDryRun(true); $effects = $engine->applyRules($rules, $adapter); $engine->applyEffects($effects, $adapter, $rules); $xscript = $engine->getTranscript(); return id(new AphrontRedirectResponse()) ->setURI('/herald/transcript/'.$xscript->getID().'/'); } } } $form = id(new AphrontFormView()) ->setUser($viewer) ->appendRemarkupInstructions( pht( 'Enter an object to test rules for, like a Diffusion commit (e.g., '. '`rX123`) or a Differential revision (e.g., `D123`). You will be '. 'shown the results of a dry run on the object.')) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Object Name')) ->setName('object_name') ->setError($e_name) ->setValue($object_name)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Test Rules'))); $box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Herald Test Console')) ->setFormErrors($errors) ->setForm($form); - $nav = $this->buildSideNavView(); - $nav->selectFilter('test'); - $nav->appendChild($box); - $crumbs = id($this->buildApplicationCrumbs()) ->addTextCrumb(pht('Test Console')); - $nav->setCrumbs($crumbs); - return $this->buildApplicationPage( - $nav, - array( - 'title' => pht('Test Console'), + $title = pht('Test Console'); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $box, )); + } } diff --git a/src/applications/herald/controller/HeraldTranscriptController.php b/src/applications/herald/controller/HeraldTranscriptController.php index 3e4b0b074c..afb7b36558 100644 --- a/src/applications/herald/controller/HeraldTranscriptController.php +++ b/src/applications/herald/controller/HeraldTranscriptController.php @@ -1,477 +1,478 @@ adapter; } public function handleRequest(AphrontRequest $request) { $viewer = $this->getViewer(); $xscript = id(new HeraldTranscriptQuery()) ->setViewer($viewer) ->withIDs(array($request->getURIData('id'))) ->executeOne(); if (!$xscript) { return new Aphront404Response(); } require_celerity_resource('herald-test-css'); $content = array(); $object_xscript = $xscript->getObjectTranscript(); if (!$object_xscript) { $notice = id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) ->setTitle(pht('Old Transcript')) ->appendChild(phutil_tag( 'p', array(), pht('Details of this transcript have been garbage collected.'))); $content[] = $notice; } else { $map = HeraldAdapter::getEnabledAdapterMap($viewer); $object_type = $object_xscript->getType(); if (empty($map[$object_type])) { // TODO: We should filter these out in the Query, but we have to load // the objectTranscript right now, which is potentially enormous. We // should denormalize the object type, or move the data into a separate // table, and then filter this earlier (and thus raise a better error). // For now, just block access so we don't violate policies. throw new Exception( pht('This transcript has an invalid or inaccessible adapter.')); } $this->adapter = HeraldAdapter::getAdapterForContentType($object_type); $phids = $this->getTranscriptPHIDs($xscript); $phids = array_unique($phids); $phids = array_filter($phids); $handles = $this->loadViewerHandles($phids); $this->handles = $handles; if ($xscript->getDryRun()) { $notice = new PHUIInfoView(); $notice->setSeverity(PHUIInfoView::SEVERITY_NOTICE); $notice->setTitle(pht('Dry Run')); $notice->appendChild( pht( 'This was a dry run to test Herald rules, '. 'no actions were executed.')); $content[] = $notice; } $warning_panel = $this->buildWarningPanel($xscript); $content[] = $warning_panel; $content[] = array( $this->buildActionTranscriptPanel($xscript), $this->buildObjectTranscriptPanel($xscript), ); } $crumbs = id($this->buildApplicationCrumbs()) ->addTextCrumb( pht('Transcripts'), $this->getApplicationURI('/transcript/')) ->addTextCrumb($xscript->getID()); - return $this->buildApplicationPage( - array( - $crumbs, - $content, - ), - array( - 'title' => pht('Transcript'), + $title = pht('Transcript'); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $content, )); } protected function renderConditionTestValue($condition, $handles) { // TODO: This is all a hacky mess and should be driven through FieldValue // eventually. switch ($condition->getFieldName()) { case HeraldAnotherRuleField::FIELDCONST: $value = array($condition->getTestValue()); break; default: $value = $condition->getTestValue(); break; } if (!is_scalar($value) && $value !== null) { foreach ($value as $key => $phid) { $handle = idx($handles, $phid); if ($handle && $handle->isComplete()) { $value[$key] = $handle->getName(); } else { // This happens for things like task priorities, statuses, and // custom fields. $value[$key] = $phid; } } sort($value); $value = implode(', ', $value); } return phutil_tag('span', array('class' => 'condition-test-value'), $value); } protected function getTranscriptPHIDs($xscript) { $phids = array(); $object_xscript = $xscript->getObjectTranscript(); if (!$object_xscript) { return array(); } $phids[] = $object_xscript->getPHID(); foreach ($xscript->getApplyTranscripts() as $apply_xscript) { // TODO: This is total hacks. Add another amazing layer of abstraction. $target = (array)$apply_xscript->getTarget(); foreach ($target as $phid) { if ($phid) { $phids[] = $phid; } } } foreach ($xscript->getRuleTranscripts() as $rule_xscript) { $phids[] = $rule_xscript->getRuleOwner(); } $condition_xscripts = $xscript->getConditionTranscripts(); if ($condition_xscripts) { $condition_xscripts = call_user_func_array( 'array_merge', $condition_xscripts); } foreach ($condition_xscripts as $condition_xscript) { switch ($condition_xscript->getFieldName()) { case HeraldAnotherRuleField::FIELDCONST: $phids[] = $condition_xscript->getTestValue(); break; default: $value = $condition_xscript->getTestValue(); // TODO: Also total hacks. if (is_array($value)) { foreach ($value as $phid) { if ($phid) { // TODO: Probably need to make sure this // "looks like" a PHID or decrease the level of hacks here; // this used to be an is_numeric() check in Facebook land. $phids[] = $phid; } } } break; } } return $phids; } private function buildWarningPanel(HeraldTranscript $xscript) { $request = $this->getRequest(); $panel = null; if ($xscript->getObjectTranscript()) { $handles = $this->handles; $object_xscript = $xscript->getObjectTranscript(); $handle = $handles[$object_xscript->getPHID()]; if ($handle->getType() == PhabricatorRepositoryCommitPHIDType::TYPECONST) { $commit = id(new DiffusionCommitQuery()) ->setViewer($request->getUser()) ->withPHIDs(array($handle->getPHID())) ->executeOne(); if ($commit) { $repository = $commit->getRepository(); if ($repository->isImporting()) { $title = pht( 'The %s repository is still importing.', $repository->getMonogram()); $body = pht( 'Herald rules will not trigger until import completes.'); } else if (!$repository->isTracked()) { $title = pht( 'The %s repository is not tracked.', $repository->getMonogram()); $body = pht( 'Herald rules will not trigger until tracking is enabled.'); } else { return $panel; } $panel = id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_WARNING) ->setTitle($title) ->appendChild($body); } } } return $panel; } 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; $action_map = $xscript->getApplyTranscripts(); $action_map = mgroup($action_map, 'getRuleID'); $rule_list = id(new PHUIObjectItemListView()) ->setNoDataString(pht('No Herald rules applied to this object.')); $rule_xscripts = $xscript->getRuleTranscripts(); $rule_xscripts = msort($rule_xscripts, 'getRuleID'); foreach ($rule_xscripts 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; } // 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 { $name = pht('Unknown Action ("%s")', $action_key); } $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 ($xscript->getDryRun()) { $action_list->addItem( id(new PHUIStatusItemView()) ->setIcon('fa-ban', 'grey') ->setTarget(pht('Dry Run')) ->setNote( pht( 'This was a dry run, so no actions were taken.'))); continue; } else 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->renderEffectDescription($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); } } } $box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Rule Transcript')) ->appendChild($rule_list); return $box; } private function buildObjectTranscriptPanel(HeraldTranscript $xscript) { $adapter = $this->getAdapter(); $field_names = $adapter->getFieldNameMap(); $object_xscript = $xscript->getObjectTranscript(); $data = array(); if ($object_xscript) { $phid = $object_xscript->getPHID(); $handles = $this->handles; $data += array( pht('Object Name') => $object_xscript->getName(), pht('Object Type') => $object_xscript->getType(), pht('Object PHID') => $phid, pht('Object Link') => $handles[$phid]->renderLink(), ); } $data += $xscript->getMetadataMap(); if ($object_xscript) { foreach ($object_xscript->getFields() as $field => $value) { $field = idx($field_names, $field, '['.$field.'?]'); $data['Field: '.$field] = $value; } } $rows = array(); foreach ($data as $name => $value) { if (!($value instanceof PhutilSafeHTML)) { if (!is_scalar($value) && !is_null($value)) { $value = implode("\n", $value); } if (strlen($value) > 256) { $value = phutil_tag( 'textarea', array( 'class' => 'herald-field-value-transcript', ), $value); } } $rows[] = array($name, $value); } $property_list = new PHUIPropertyListView(); $property_list->setStacked(true); foreach ($rows as $row) { $property_list->addProperty($row[0], $row[1]); } $box = new PHUIObjectBoxView(); $box->setHeaderText(pht('Object Transcript')); $box->appendChild($property_list); return $box; } } diff --git a/src/applications/herald/controller/HeraldTranscriptListController.php b/src/applications/herald/controller/HeraldTranscriptListController.php index 81865eee4a..c1f2b3ea5d 100644 --- a/src/applications/herald/controller/HeraldTranscriptListController.php +++ b/src/applications/herald/controller/HeraldTranscriptListController.php @@ -1,44 +1,35 @@ getRequest()->getUser(); $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - if ($for_app) { - $nav->addFilter('new', pht('Create Rule')); - } - id(new HeraldTranscriptSearchEngine()) ->setViewer($user) ->addNavigationItems($nav->getMenu()); $nav->selectFilter(null); return $nav; } protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); $crumbs->addTextCrumb( pht('Transcripts'), $this->getApplicationURI('transcript/')); return $crumbs; } public function handleRequest(AphrontRequest $request) { - $querykey = $request->getURIData('queryKey'); - - $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($querykey) - ->setSearchEngine(new HeraldTranscriptSearchEngine()) - ->setNavigation($this->buildSideNavView()); - - return $this->delegateToController($controller); + return id(new HeraldTranscriptSearchEngine()) + ->setController($this) + ->buildResponse(); } } diff --git a/src/applications/herald/query/HeraldTranscriptSearchEngine.php b/src/applications/herald/query/HeraldTranscriptSearchEngine.php index c3e4b8d8e3..cc0d620394 100644 --- a/src/applications/herald/query/HeraldTranscriptSearchEngine.php +++ b/src/applications/herald/query/HeraldTranscriptSearchEngine.php @@ -1,143 +1,143 @@ getStrList('objectMonograms'); $saved->setParameter('objectMonograms', $object_monograms); $ids = $request->getStrList('ids'); foreach ($ids as $key => $id) { if (!$id || !is_numeric($id)) { unset($ids[$key]); } else { $ids[$key] = $id; } } $saved->setParameter('ids', $ids); return $saved; } public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { $query = id(new HeraldTranscriptQuery()); $object_monograms = $saved->getParameter('objectMonograms'); if ($object_monograms) { $objects = id(new PhabricatorObjectQuery()) ->setViewer($this->requireViewer()) ->withNames($object_monograms) ->execute(); $query->withObjectPHIDs(mpull($objects, 'getPHID')); } $ids = $saved->getParameter('ids'); if ($ids) { $query->withIDs($ids); } return $query; } public function buildSearchForm( AphrontFormView $form, PhabricatorSavedQuery $saved) { $object_monograms = $saved->getParameter('objectMonograms', array()); $ids = $saved->getParameter('ids', array()); $form ->appendChild( id(new AphrontFormTextControl()) ->setName('objectMonograms') ->setLabel(pht('Object Monograms')) ->setValue(implode(', ', $object_monograms))) ->appendChild( id(new AphrontFormTextControl()) ->setName('ids') ->setLabel(pht('Transcript IDs')) ->setValue(implode(', ', $ids))); } protected function getURI($path) { return '/herald/transcript/'.$path; } protected function getBuiltinQueryNames() { return array( - 'all' => pht('All'), + 'all' => pht('All Transcripts'), ); } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); $viewer_phid = $this->requireViewer()->getPHID(); switch ($query_key) { case 'all': return $query; } return parent::buildSavedQueryFromBuiltin($query_key); } protected function getRequiredHandlePHIDsForResultList( array $transcripts, PhabricatorSavedQuery $query) { return mpull($transcripts, 'getObjectPHID'); } protected function renderResultList( array $transcripts, PhabricatorSavedQuery $query, array $handles) { assert_instances_of($transcripts, 'HeraldTranscript'); $viewer = $this->requireViewer(); $list = new PHUIObjectItemListView(); foreach ($transcripts as $xscript) { $view_href = phutil_tag( 'a', array( 'href' => '/herald/transcript/'.$xscript->getID().'/', ), pht('View Full Transcript')); $item = new PHUIObjectItemView(); $item->setObjectName($xscript->getID()); $item->setHeader($view_href); if ($xscript->getDryRun()) { $item->addAttribute(pht('Dry Run')); } $item->addAttribute($handles[$xscript->getObjectPHID()]->renderLink()); $item->addAttribute( pht('%s ms', new PhutilNumber((int)(1000 * $xscript->getDuration())))); $item->addIcon( 'none', phabricator_datetime($xscript->getTime(), $viewer)); $list->addItem($item); } $result = new PhabricatorApplicationSearchResultView(); $result->setObjectList($list); $result->setNoDataString(pht('No transcripts found.')); return $result; } }