diff --git a/conf/default.conf.php b/conf/default.conf.php index 95f99eb5f9..ed6cc98bb0 100644 --- a/conf/default.conf.php +++ b/conf/default.conf.php @@ -267,6 +267,7 @@ return array( 'image/jpeg' => 'image/jpeg', 'image/jpg' => 'image/jpg', 'image/png' => 'image/png', + 'image/gif' => 'image/gif', 'text/plain' => 'text/plain; charset=utf-8', ), diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index d455550255..782c95c853 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -188,6 +188,7 @@ phutil_register_library_map(array( 'HeraldController' => 'applications/herald/controller/base', 'HeraldDAO' => 'applications/herald/storage/base', 'HeraldDeleteController' => 'applications/herald/controller/delete', + 'HeraldDifferentialRevisionAdapter' => 'applications/herald/adapter/differential', 'HeraldDryRunAdapter' => 'applications/herald/adapter/dryrun', 'HeraldEffect' => 'applications/herald/engine/effect', 'HeraldEngine' => 'applications/herald/engine/engine', @@ -204,6 +205,8 @@ phutil_register_library_map(array( 'HeraldRuleTranscript' => 'applications/herald/storage/transcript/rule', 'HeraldTestConsoleController' => 'applications/herald/controller/test', 'HeraldTranscript' => 'applications/herald/storage/transcript/base', + 'HeraldTranscriptController' => 'applications/herald/controller/transcript', + 'HeraldTranscriptListController' => 'applications/herald/controller/transcriptlist', 'HeraldValueTypeConfig' => 'applications/herald/config/valuetype', 'Javelin' => 'infrastructure/javelin/api', 'LiskDAO' => 'storage/lisk/dao', @@ -539,6 +542,7 @@ phutil_register_library_map(array( 'HeraldController' => 'PhabricatorController', 'HeraldDAO' => 'PhabricatorLiskDAO', 'HeraldDeleteController' => 'HeraldController', + 'HeraldDifferentialRevisionAdapter' => 'HeraldObjectAdapter', 'HeraldDryRunAdapter' => 'HeraldObjectAdapter', 'HeraldHomeController' => 'HeraldController', 'HeraldNewController' => 'HeraldController', @@ -546,6 +550,8 @@ phutil_register_library_map(array( 'HeraldRuleController' => 'HeraldController', 'HeraldTestConsoleController' => 'HeraldController', 'HeraldTranscript' => 'HeraldDAO', + 'HeraldTranscriptController' => 'HeraldController', + 'HeraldTranscriptListController' => 'HeraldController', 'ManiphestController' => 'PhabricatorController', 'ManiphestDAO' => 'PhabricatorLiskDAO', 'ManiphestTask' => 'ManiphestDAO', diff --git a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php index 363f089b99..85a47facc6 100644 --- a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php +++ b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php @@ -225,7 +225,8 @@ class AphrontDefaultApplicationConfiguration 'delete/(?P\d+)/$' => 'HeraldDeleteController', 'test/$' => 'HeraldTestConsoleController', 'transcript/$' => 'HeraldTranscriptListController', - 'transcript/(?P\d+)/$' => 'HeraldTranscriptController', + 'transcript/(?P\d+)/(?:(?P\w+)/)?$' + => 'HeraldTranscriptController', ), ); diff --git a/src/applications/differential/controller/revisionview/DifferentialRevisionViewController.php b/src/applications/differential/controller/revisionview/DifferentialRevisionViewController.php index dcc6d4ea2a..b14f66fedd 100644 --- a/src/applications/differential/controller/revisionview/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/revisionview/DifferentialRevisionViewController.php @@ -38,6 +38,11 @@ class DifferentialRevisionViewController extends DifferentialController { $diffs = $revision->loadDiffs(); + if (!$diffs) { + throw new Exception( + "This revision has no diffs. Something has gone quite wrong."); + } + $diff_vs = $request->getInt('vs'); $target = end($diffs); diff --git a/src/applications/herald/adapter/differential/HeraldDifferentialRevisionAdapter.php b/src/applications/herald/adapter/differential/HeraldDifferentialRevisionAdapter.php new file mode 100644 index 0000000000..484b0ec2dd --- /dev/null +++ b/src/applications/herald/adapter/differential/HeraldDifferentialRevisionAdapter.php @@ -0,0 +1,257 @@ +loadRelationships(); + $this->revision = $revision; + } + + public function setDiff(Diff $diff) { + $this->diff = $diff; + return $this; + } + + public function setExplicitCCs($explicit_ccs) { + $this->explicitCCs = $explicit_ccs; + return $this; + } + + public function setExplicitReviewers($explicit_reviewers) { + $this->explicitReviewers = $explicit_reviewers; + return $this; + } + + public function setForbiddenCCs($forbidden_ccs) { + $this->forbiddenCCs = $forbidden_ccs; + return $this; + } + + public function setForbiddenReviewers($forbidden_reviewers) { + $this->forbiddenReviewers = $forbidden_reviewers; + return $this; + } + + public function getCCsAddedByHerald() { + return array_diff_key($this->newCCs, $this->remCCs); + } + + public function getCCsRemovedByHerald() { + return $this->remCCs; + } + + public function getPHID() { + return $this->revision->getPHID(); + } + + public function getHeraldName() { + return $this->revision->getTitle(); + } + + public function getHeraldTypeName() { + return HeraldContentTypeConfig::CONTENT_TYPE_DIFFERENTIAL; + } + + protected function loadChangesets() { + if ($this->changesets) { + return $this->changesets; + } + $diff = $this->loadDiff(); + $changes = $diff->getChangesets(); + return ($this->changesets = $changes); + } + + protected function loadDiff() { + if ($this->diff === null) { + $this->diff = $this->revision->getActiveDiff(); + } + return $this->diff; + } + + protected function getContentDictionary() { + $changes = $this->loadChangesets(); + + $hunks = array(); + if ($changes) { + $hunks = id(new DifferentialHunk())->loadAllwhere( + 'changesetID in (%Ld)', + mpull($changes, 'getID')); + } + + $dict = array(); + $hunks = mgroup($hunks, 'getChangesetID'); + $changes = mpull($changes, null, 'getID'); + foreach ($changes as $id => $change) { + $filename = $change->getFilename(); + $content = array(); + foreach (idx($hunks, $id, array()) as $hunk) { + $content[] = $hunk->makeChanges(); + } + $dict[$filename] = implode("\n", $content); + } + + return $dict; + } + + public function getHeraldField($field) { + switch ($field) { + case HeraldFieldConfig::FIELD_TITLE: + return $this->revision->getTitle(); + break; + case HeraldFieldConfig::FIELD_BODY: + return $this->revision->getSummary()."\n". + $this->revision->getTestPlan(); + break; + case HeraldFieldConfig::FIELD_AUTHOR: + return $this->revision->getAuthorPHID(); + break; + case HeraldFieldConfig::FIELD_DIFF_FILE: + $changes = $this->loadChangesets(); + return array_values(mpull($changes, 'getFilename')); + case HeraldFieldConfig::FIELD_CC: + if (isset($this->explicitCCs)) { + return array_keys($this->explicitCCs); + } else { + return $this->revision->getCCPHIDs(); + } + case HeraldFieldConfig::FIELD_REVIEWERS: + if (isset($this->explicitReviewers)) { + return array_keys($this->explicitReviewers); + } else { + return $this->revision->getReviewers(); + } +/* TODO + case HeraldFieldConfig::FIELD_REPOSITORY: + $id = $this->revision->getRepositoryID(); + if (!$id) { + return null; + } + require_module_lazy('intern/repository'); + $repository = RepositoryRef::getByID($id); + if (!$repository) { + return null; + } + return $repository->getFBID(); +*/ + case HeraldFieldConfig::FIELD_DIFF_CONTENT: + return $this->getContentDictionary(); +/* TODO + case HeraldFieldConfig::FIELD_AFFECTED_PACKAGE: + return mpull( + DiffOwners::getPackages($this->loadDiff()), + 'getFBID'); +*/ +/* TODO + case HeraldFieldConfig::FIELD_AFFECTED_PACKAGE_OWNER: + return DiffOwners::getOwners($this->loadDiff()); +*/ + default: + throw new Exception("Invalid field '{$field}'."); + } + } + + public function applyHeraldEffects(array $effects) { + $result = array(); + if ($this->explicitCCs) { + $effect = new HeraldEffect(); + $effect->setAction(HeraldActionConfig::ACTION_ADD_CC); + $effect->setTarget(array_keys($this->explicitCCs)); + $effect->setReason( + 'CCs provided explicitly by revision author or carried over from a '. + 'previous version of the revision.'); + $result[] = new HeraldApplyTranscript( + $effect, + true, + 'Added addresses to CC list.'); + } + + $forbidden_ccs = array_fill_keys( + nonempty($this->forbiddenCCs, array()), + true); + + foreach ($effects as $effect) { + $action = $effect->getAction(); + switch ($action) { + case HeraldActionConfig::ACTION_NOTHING: + $result[] = new HeraldApplyTranscript( + $effect, + true, + 'OK, did nothing.'); + break; + case HeraldActionConfig::ACTION_ADD_CC: + $base_target = $effect->getTarget(); + $forbidden = array(); + foreach ($base_target as $key => $fbid) { + if (isset($forbidden_ccs[$fbid])) { + $forbidden[] = $fbid; + unset($base_target[$key]); + } else { + $this->newCCs[$fbid] = true; + } + } + + if ($forbidden) { + $failed = clone $effect; + $failed->setTarget($forbidden); + if ($base_target) { + $effect->setTarget($base_target); + $result[] = new HeraldApplyTranscript( + $effect, + true, + 'Added these addresses to CC list. Others could not be added.'); + } + $result[] = new HeraldApplyTranscript( + $failed, + false, + 'CC forbidden, these addresses have unsubscribed.'); + } else { + $result[] = new HeraldApplyTranscript( + $effect, + true, + 'Added addresses to CC list.'); + } + break; + case HeraldActionConfig::ACTION_REMOVE_CC: + foreach ($effect->getTarget() as $fbid) { + $this->remCCs[$fbid] = true; + } + $result[] = new HeraldApplyTranscript( + $effect, + true, + 'Removed addresses from CC list.'); + break; + default: + throw new Exception("No rules to handle action '{$action}'."); + } + } + return $result; + } +} diff --git a/src/applications/herald/adapter/differential/__init__.php b/src/applications/herald/adapter/differential/__init__.php new file mode 100644 index 0000000000..432d8502fd --- /dev/null +++ b/src/applications/herald/adapter/differential/__init__.php @@ -0,0 +1,20 @@ + array( 'email' => '/typeahead/common/mailable/', - 'user' => '/typeahead/common/user/', + 'user' => '/typeahead/common/users/', 'repository' => '/typeahead/common/repository/', /* 'tag' => '/datasource/tag/', diff --git a/src/applications/herald/controller/test/__init__.php b/src/applications/herald/controller/test/__init__.php index 79ae6a414b..72d995cbaf 100644 --- a/src/applications/herald/controller/test/__init__.php +++ b/src/applications/herald/controller/test/__init__.php @@ -8,6 +8,7 @@ phutil_require_module('phabricator', 'aphront/response/redirect'); phutil_require_module('phabricator', 'applications/differential/storage/revision'); +phutil_require_module('phabricator', 'applications/herald/adapter/differential'); phutil_require_module('phabricator', 'applications/herald/adapter/dryrun'); phutil_require_module('phabricator', 'applications/herald/controller/base'); phutil_require_module('phabricator', 'applications/herald/engine/engine'); diff --git a/src/applications/herald/controller/transcript/HeraldTranscriptController.php b/src/applications/herald/controller/transcript/HeraldTranscriptController.php new file mode 100644 index 0000000000..aac094d05f --- /dev/null +++ b/src/applications/herald/controller/transcript/HeraldTranscriptController.php @@ -0,0 +1,543 @@ +id = $data['id']; + $map = $this->getFilterMap(); + $this->filter = idx($data, 'filter'); + if (empty($map[$this->filter])) { + $this->filter = self::FILTER_AFFECTED; + } + } + + public function processRequest() { + + $xscript = id(new HeraldTranscript())->load($this->id); + if (!$xscript) { + throw new Exception('Uknown transcript!'); + } + + $field_names = HeraldFieldConfig::getFieldMap(); + $condition_names = HeraldConditionConfig::getConditionMap(); + $action_names = HeraldActionConfig::getActionMap(); + + require_celerity_resource('herald-test-css'); + + $filter = $this->getFilterPHIDs(); + $this->filterTranscript($xscript, $filter); + $phids = array_merge($filter, $this->getTranscriptPHIDs($xscript)); + $phids = array_unique($phids); + $phids = array_filter($phids); + + $handles = id(new PhabricatorObjectHandleData($phids)) + ->loadHandles(); + $this->handles = $handles; + + $object_xscript = $xscript->getObjectTranscript(); + + $nav = $this->buildSideNav(); + + $apply_xscript_panel = $this->buildApplyTranscriptPanel( + $xscript); + $nav->appendChild($apply_xscript_panel); + + $action_xscript_panel = $this->buildActionTranscriptPanel( + $xscript); + $nav->appendChild($action_xscript_panel); + + $object_xscript_panel = $this->buildObjectTranscriptPanel( + $xscript); + $nav->appendChild($object_xscript_panel); + +/* + + + $notice = null; + if ($xscript->getDryRun()) { + $notice = + + This was a dry run to test Herald rules, no actions were executed. + ; + } + + if (!$object_xscript) { + $notice = + + + Details of this transcript have been discarded. Full transcripts + are retained for 30 days. + + {$notice} + ; + } + + + return + +
+ renderNavItems()}> + {$notice} + {$apply_xscript_markup} + {$rule_table} + {$object_xscript_table} + +
+
; +*/ + + return $this->buildStandardPageResponse( + $nav, + array( + 'title' => 'Transcript', + )); + } + + protected function renderConditionTestValue($condition, $handles) { + $value = $condition->getTestValue(); + if (!is_scalar($value) && $value !== null) { + foreach ($value as $key => $phid) { + $handle = idx($handles, $phid); + if ($handle) { + $value[$key] = $handle->getName(); + } else { + // This shouldn't ever really happen as we are supposed to have + // grabbed handles for everything, but be super liberal in what + // we accept here since we expect all sorts of weird issues as we + // version the system. + $value[$key] = 'Unknown Object #'.$phid; + } + } + sort($value); + $value = implode(', ', $value); + } + + return + ''. + phutil_escape_html($value). + ''; + } + + private function buildSideNav() { + $nav = new AphrontSideNavView(); + + $items = array(); + $filters = $this->getFilterMap(); + foreach ($filters as $key => $name) { + $nav->addNavItem( + phutil_render_tag( + 'a', + array( + 'href' => '/herald/transcript/'.$this->id.'/'.$key.'/', + 'class' => + ($key == $this->filter) + ? 'aphront-side-nav-selected' + : null, + ), + phutil_escape_html($name))); + } + + return $nav; + } + + protected function getFilterMap() { + return array( + self::FILTER_AFFECTED => 'Rules that Affected Me', + self::FILTER_OWNED => 'Rules I Own', + self::FILTER_ALL => 'All Rules', + ); + } + + + protected function getFilterPHIDs() { + return array($this->getRequest()->getUser()->getPHID()); + +/* TODO + $viewer_id = $this->getRequest()->getUser()->getPHID(); + + $fbids = array(); + if ($this->filter == self::FILTER_AFFECTED) { + $fbids[] = $viewer_id; + require_module_lazy('intern/subscriptions'); + $datastore = new SubscriberDatabaseStore(); + $lists = $datastore->getUserMailmanLists($viewer_id); + foreach ($lists as $list) { + $fbids[] = $list; + } + } + return $fbids; +*/ + } + + 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) { + $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; + } + } + } + } + + return $phids; + } + + protected function filterTranscript($xscript, $filter_phids) { + $filter_owned = ($this->filter == self::FILTER_OWNED); + $filter_affected = ($this->filter == self::FILTER_AFFECTED); + + if (!$filter_owned && !$filter_affected) { + // No filtering to be done. + return; + } + + if (!$xscript->getObjectTranscript()) { + return; + } + + $user_phid = $this->getRequest()->getUser()->getPHID(); + + $keep_apply_xscripts = array(); + $keep_rule_xscripts = array(); + + $filter_phids = array_fill_keys($filter_phids, true); + + $rule_xscripts = $xscript->getRuleTranscripts(); + foreach ($xscript->getApplyTranscripts() as $id => $apply_xscript) { + $rule_id = $apply_xscript->getRuleID(); + if ($filter_owned) { + if (!$rule_xscripts[$rule_id]) { + // No associated rule so you can't own this effect. + continue; + } + if ($rule_xscripts[$rule_id]->getRuleOwner() != $user_phid) { + continue; + } + } else if ($filter_affected) { + $targets = (array)$apply_xscript->getTarget(); + if (!array_select_keys($filter_phids, $targets)) { + continue; + } + } + $keep_apply_xscripts[$id] = true; + if ($rule_id) { + $keep_rule_xscripts[$rule_id] = true; + } + } + + foreach ($rule_xscripts as $rule_id => $rule_xscript) { + if ($filter_owned && $rule_xscript->getRuleOwner() == $user_phid) { + $keep_rule_xscripts[$rule_id] = true; + } + } + + $xscript->setRuleTranscripts( + array_intersect_key( + $xscript->getRuleTranscripts(), + $keep_rule_xscripts)); + + $xscript->setApplyTranscripts( + array_intersect_key( + $xscript->getApplyTranscripts(), + $keep_apply_xscripts)); + + $xscript->setConditionTranscripts( + array_intersect_key( + $xscript->getConditionTranscripts(), + $keep_rule_xscripts)); + } + + private function buildApplyTranscriptPanel($xscript) { + $handles = $this->handles; + + $action_names = HeraldActionConfig::getActionMap(); + + $rows = array(); + foreach ($xscript->getApplyTranscripts() as $apply_xscript) { + // TODO: Hacks, this is an approximate guess at the target type. + $target = (array)$apply_xscript->getTarget(); + if (!$target) { + if ($apply_xscript->getAction() == HeraldActionConfig::ACTION_NOTHING) { + $target = ''; + } else { + $target = ''; + } + } else { + foreach ($target as $k => $phid) { + $target[$k] = $handles[$phid]->getName(); + } + $target = implode("\n", $target); + } + $target = phutil_escape_html($target); + + if ($apply_xscript->getApplied()) { + $outcome = 'SUCCESS'; + } else { + $outcome = 'FAILURE'; + } + $outcome .= ' '.phutil_escape_html($apply_xscript->getAppliedReason()); + + $rows[] = array( + phutil_escape_html($action_names[$apply_xscript->getAction()]), + $target, + 'Taken because: '. + phutil_escape_html($apply_xscript->getReason()). + '
'. + 'Outcome: '.$outcome, + ); + } + + $table = new AphrontTableView($rows); + $table->setNoDataString('No actions were taken.'); + $table->setHeaders( + array( + 'Action', + 'Target', + 'Details', + )); + $table->setColumnClasses( + array( + '', + '', + 'wide', + )); + + $panel = new AphrontPanelView(); + $panel->setHeader('Actions Taken'); + $panel->appendChild($table); + + return $panel; + } + + private function buildActionTranscriptPanel($xscript) { + $action_xscript = mgroup($xscript->getApplyTranscripts(), 'getRuleID'); + + $field_names = HeraldFieldConfig::getFieldMap(); + $condition_names = HeraldConditionConfig::getConditionMap(); + $action_names = HeraldActionConfig::getActionMap(); + + $handles = $this->handles; + + $rule_markup = array(); + foreach ($xscript->getRuleTranscripts() as $rule_id => $rule) { + $cond_markup = array(); + foreach ($xscript->getConditionTranscriptsForRule($rule_id) as $cond) { + if ($cond->getNote()) { + $note = + '
'. + phutil_escape_html($cond->getNote()). + '
'; + } else { + $note = null; + } + + if ($cond->getResult()) { + $result = + ''. + "\xE2\x9C\x93". + ''; + } else { + $result = + ''. + "\xE2\x9C\x98". + ''; + } + + $cond_markup[] = + '
  • '. + $result.' Condition: '. + phutil_escape_html($field_names[$cond->getFieldName()]). + ' '. + phutil_escape_html($condition_names[$cond->getCondition()]). + ' '. + $this->renderConditionTestValue($cond, $handles). + $note. + '
  • '; + } + + if ($rule->getResult()) { + $result = 'PASS'; + $class = 'herald-rule-pass'; + } else { + $result = 'FAIL'; + $class = 'herald-rule-fail'; + } + + $cond_markup[] = + '
  • '.$result.' '.phutil_escape_html($rule->getReason()).'
  • '; + +/* + if ($rule->getResult()) { + $actions = idx($action_xscript, $rule_id, array()); + if ($actions) { + $cond_markup[] =
  • Actions
  • ; + foreach ($actions as $action) { + + $target = $action->getTarget(); + if ($target) { + foreach ((array)$target as $k => $phid) { + $target[$k] = $handles[$phid]->getName(); + } + $target = : {implode(', ', $target)}; + } + + $cond_markup[] = +
  • + {$action_names[$action->getAction()]} + {$target} +
  • ; + } + } + } +*/ + $user_phid = $this->getRequest()->getUser()->getPHID(); + + $name = $rule->getRuleName(); + if ($rule->getRuleOwner() == $user_phid) { +// $name = getRuleID()."/"}>{$name}; + } + + $rule_markup[] = + phutil_render_tag( + 'li', + array( + 'class' => $class, + ), + '
    '. + ''.phutil_escape_html($name).' '. + phutil_escape_html($handles[$rule->getRuleOwner()]->getName()). + '
    '. + '
      '.implode("\n", $cond_markup).'
    '); + } + + $panel = new AphrontPanelView(); + $panel->setHeader('Rule Details'); + $panel->appendChild( + '
      '. + implode("\n", $rule_markup). + '
    '); + + return $panel; + } + + private function buildObjectTranscriptPanel($xscript) { + + $field_names = HeraldFieldConfig::getFieldMap(); + + $object_xscript = $xscript->getObjectTranscript(); + + $data = array(); + if ($object_xscript) { + $data += array( + 'Object Name' => $object_xscript->getName(), + 'Object Type' => $object_xscript->getType(), + 'Object PHID' => $object_xscript->getPHID(), + ); + } + + $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 (!is_scalar($value) && !is_null($value)) { + $value = implode("\n", $value); + } + + if (strlen($value) > 256) { + $value = phutil_render_tag( + 'textarea', + array( + 'class' => 'herald-field-value-transcript', + ), + phutil_escape_html($value)); + } else { + $value = phutil_escape_html($value); + } + + $rows[] = array( + phutil_escape_html($name), + $value, + ); + } + + $table = new AphrontTableView($rows); + $table->setColumnClasses( + array( + 'header', + 'wide', + )); + + $panel = new AphrontPanelView(); + $panel->setHeader('Object Transcript'); + $panel->appendChild($table); + + return $panel; + } + + +} diff --git a/src/applications/herald/controller/transcript/__init__.php b/src/applications/herald/controller/transcript/__init__.php new file mode 100644 index 0000000000..81bfe3385f --- /dev/null +++ b/src/applications/herald/controller/transcript/__init__.php @@ -0,0 +1,24 @@ +getRequest(); + + // Pull these objects manually since the serialized fields are gigantic. + $transcript = new HeraldTranscript(); + $data = queryfx_all( + $transcript->establishConnection('r'), + 'SELECT id, objectPHID, time, duration, dryRun FROM %T + ORDER BY id DESC + LIMIT 100', + $transcript->getTableName()); + + /* + + $conn_r = smc_get_db('cdb.herald', 'r'); + + $page_size = 100; + + $pager = new SimplePager(); + $pager->setPageSize($page_size); + $pager->setOffset((((int)$request->getInt('page')) - 1) * $page_size); + $pager->order('id', array('id')); + + + $fbid = $request->getInt('fbid'); + if ($fbid) { + $filter = qsprintf( + $conn_r, + 'WHERE objectID = %d', + $fbid); + } else { + $filter = ''; + } + + $data = $pager->select( + $conn_r, + 'id, objectID, time, duration, dryRun FROM transcript %Q', + $filter); +*/ + + $handles = array(); + if ($data) { + $phids = ipull($data, 'objectPHID', 'objectPHID'); + $handles = id(new PhabricatorObjectHandleData($phids)) + ->loadHandles(); + } + + $rows = array(); + foreach ($data as $xscript) { + $rows[] = array( + date('F jS', $xscript['time']), + date('g:i:s A', $xscript['time']), + $handles[$xscript['objectPHID']]->renderLink(), + $xscript['dryRun'] ? 'Yes' : '', + number_format((int)(1000 * $xscript['duration'])).' ms', + phutil_render_tag( + 'a', + array( + 'href' => '/herald/transcript/'.$xscript['id'].'/', + 'class' => 'button small grey', + ), + 'View Transcript'), + ); + } + + $table = new AphrontTableView($rows); + $table->setHeaders( + array( + 'Date', + 'Time', + 'Object', + 'Dry Run', + 'Duration', + 'View', + )); + $table->setColumnClasses( + array( + '', + 'right', + 'wide wrap', + '', + '', + 'action', + )); + + + $panel = new AphrontPanelView(); + $panel->setHeader('Herald Transcripts'); + $panel->appendChild($table); + + return $this->buildStandardPageResponse( + $panel, + array( + 'title' => 'Herald Transcripts', + 'tab' => 'transcripts', + )); + } + +} diff --git a/src/applications/herald/controller/transcriptlist/__init__.php b/src/applications/herald/controller/transcriptlist/__init__.php new file mode 100644 index 0000000000..5a799e96f2 --- /dev/null +++ b/src/applications/herald/controller/transcriptlist/__init__.php @@ -0,0 +1,20 @@ +objectID = $object_id; + public function setObjectPHID($object_phid) { + $this->objectPHID = $object_phid; return $this; } - public function getObjectID() { - return $this->objectID; + public function getObjectPHID() { + return $this->objectPHID; } public function setAction($action) { diff --git a/src/applications/herald/engine/engine/HeraldEngine.php b/src/applications/herald/engine/engine/HeraldEngine.php index 96f283e977..5cc8f8338c 100644 --- a/src/applications/herald/engine/engine/HeraldEngine.php +++ b/src/applications/herald/engine/engine/HeraldEngine.php @@ -26,7 +26,7 @@ class HeraldEngine { protected $fieldCache = array(); protected $object = null; - public static function loadAndApplyRules(IHeraldable $object) { + public static function loadAndApplyRules(HeraldObjectAdapter $object) { $content_type = $object->getHeraldTypeName(); $rules = HeraldRule::loadAllByContentTypeWithFullData($content_type); @@ -37,13 +37,13 @@ class HeraldEngine { return $engine->getTranscript(); } - public function applyRules(array $rules, IHeraldable $object) { + public function applyRules(array $rules, HeraldObjectAdapter $object) { $t_start = microtime(true); $rules = mpull($rules, null, 'getID'); $this->transcript = new HeraldTranscript(); - $this->transcript->setObjectID((string)$object->getFBID()); + $this->transcript->setObjectPHID((string)$object->getPHID()); $this->fieldCache = array(); $this->results = array(); $this->rules = $rules; @@ -71,7 +71,7 @@ class HeraldEngine { "Don't do this! You have formed an unresolvable cycle in the ". "dependency graph!"); $xscript->setRuleName($rules[$rule_id]->getName()); - $xscript->setRuleOwner($rules[$rule_id]->getOwnerID()); + $xscript->setRuleOwner($rules[$rule_id]->getAuthorPHID()); $this->transcript->addRuleTranscript($xscript); } $rule_matches = false; @@ -86,7 +86,7 @@ class HeraldEngine { } $object_transcript = new HeraldObjectTranscript(); - $object_transcript->setFBID($object->getFBID()); + $object_transcript->setPHID($object->getPHID()); $object_transcript->setName($object->getHeraldName()); $object_transcript->setType($object->getHeraldTypeName()); $object_transcript->setFields($this->fieldCache); @@ -100,7 +100,7 @@ class HeraldEngine { return $effects; } - public function applyEffects(array $effects, IHeraldable $object) { + public function applyEffects(array $effects, HeraldObjectAdapter $object) { if ($object instanceof DryRunHeraldable) { $this->transcript->setDryRun(true); } else { @@ -121,7 +121,10 @@ class HeraldEngine { return $this->transcript; } - protected function doesRuleMatch(HeraldRule $rule, IHeraldable $object) { + protected function doesRuleMatch( + HeraldRule $rule, + HeraldObjectAdapter $object) { + $id = $rule->getID(); if (isset($this->results[$id])) { @@ -157,7 +160,7 @@ class HeraldEngine { $reason = "Rule failed automatically because it has no conditions."; $result = false; /* TOOD: Restore this in some form? - } else if (!is_fb_employee($rule->getOwnerID())) { + } else if (!is_fb_employee($rule->getAuthorPHID())) { $reason = "Rule failed automatically because its owner is not an ". "active employee."; $result = false; @@ -195,7 +198,7 @@ class HeraldEngine { $rule_transcript->setResult($result); $rule_transcript->setReason($reason); $rule_transcript->setRuleName($rule->getName()); - $rule_transcript->setRuleOwner($rule->getOwnerID()); + $rule_transcript->setRuleOwner($rule->getAuthorPHID()); $this->transcript->addRuleTranscript($rule_transcript); @@ -205,12 +208,12 @@ class HeraldEngine { protected function doesConditionMatch( HeraldRule $rule, HeraldCondition $condition, - IHeraldable $object) { + HeraldObjectAdapter $object) { $object_value = $this->getConditionObjectValue($condition, $object); $test_value = $condition->getValue(); - $cond = $condition->getCondition(); + $cond = $condition->getFieldCondition(); $transcript = new HeraldConditionTranscript(); $transcript->setRuleID($rule->getID()); @@ -241,10 +244,10 @@ class HeraldEngine { $result = ($object_value != $test_value); break; case HeraldConditionConfig::CONDITION_IS_ME: - $result = ($object_value == $rule->getOwnerID()); + $result = ($object_value == $rule->getAuthorPHID()); break; case HeraldConditionConfig::CONDITION_IS_NOT_ME: - $result = ($object_value != $rule->getOwnerID()); + $result = ($object_value != $rule->getAuthorPHID()); break; case HeraldConditionConfig::CONDITION_IS_ANY: $test_value = array_flip($test_value); @@ -364,7 +367,7 @@ class HeraldEngine { protected function getConditionObjectValue( HeraldCondition $condition, - IHeraldable $object) { + HeraldObjectAdapter $object) { $field = $condition->getFieldName(); @@ -391,7 +394,7 @@ class HeraldEngine { case HeraldFieldConfig::FIELD_AUTHOR: case HeraldFieldConfig::FIELD_REPOSITORY: case HeraldFieldConfig::FIELD_MERGE_REQUESTER: - // TODO: Type should be fbid. + // TODO: Type should be PHID. $result = $this->object->getHeraldField($field); break; case HeraldFieldConfig::FIELD_TAGS: @@ -424,11 +427,14 @@ class HeraldEngine { return $result; } - protected function getRuleEffects(HeraldRule $rule, IHeraldable $object) { + protected function getRuleEffects( + HeraldRule $rule, + HeraldObjectAdapter $object) { + $effects = array(); foreach ($rule->getActions() as $action) { $effect = new HeraldEffect(); - $effect->setObjectID($object->getFBID()); + $effect->setObjectPHID($object->getPHID()); $effect->setAction($action->getAction()); $effect->setTarget($action->getTarget()); diff --git a/src/applications/herald/storage/transcript/base/HeraldTranscript.php b/src/applications/herald/storage/transcript/base/HeraldTranscript.php index 48df40ebec..7928ff0726 100644 --- a/src/applications/herald/storage/transcript/base/HeraldTranscript.php +++ b/src/applications/herald/storage/transcript/base/HeraldTranscript.php @@ -19,7 +19,7 @@ class HeraldTranscript extends HeraldDAO { protected $id; - protected $fbid; + protected $phid; protected $objectTranscript; protected $ruleTranscripts = array(); @@ -28,10 +28,9 @@ class HeraldTranscript extends HeraldDAO { protected $time; protected $host; - protected $path; protected $duration; - protected $objectID; + protected $objectPHID; protected $dryRun; public function getXHeraldRulesHeader() { @@ -61,7 +60,8 @@ class HeraldTranscript extends HeraldDAO { protected function getConfiguration() { // Ugh. Too much of a mess to deal with. return array( - self::CONFIG_AUX_FBID => 'HERALD_TRANSCRIPT', + self::CONFIG_AUX_PHID => true, + self::CONFIG_TIMESTAMPS => false, self::CONFIG_SERIALIZATION => array( 'objectTranscript' => self::SERIALIZATION_PHP, 'ruleTranscripts' => self::SERIALIZATION_PHP, @@ -74,7 +74,6 @@ class HeraldTranscript extends HeraldDAO { public function __construct() { $this->time = time(); $this->host = php_uname('n'); - $this->path = realpath($_SERVER['PHP_ROOT']); } public function addApplyTranscript(HeraldApplyTranscript $transcript) { @@ -131,14 +130,14 @@ class HeraldTranscript extends HeraldDAO { public function getMetadataMap() { return array( - 'Run At Epoch' => date('F jS, g:i A', $this->time), - 'Run On Host' => $this->host.':'.$this->path, + 'Run At Epoch' => date('F jS, g:i:s A', $this->time), + 'Run On Host' => $this->host, 'Run Duration' => (int)(1000 * $this->duration).' ms', ); } - public function getURI() { - return 'http://tools.facebook.com/herald/transcript/'.$this->getID().'/'; + public function generatePHID() { + return PhabricatorPHID::generateNewPHID('HLXS'); } } diff --git a/src/applications/herald/storage/transcript/base/__init__.php b/src/applications/herald/storage/transcript/base/__init__.php index dd3d4f4993..3b9b170090 100644 --- a/src/applications/herald/storage/transcript/base/__init__.php +++ b/src/applications/herald/storage/transcript/base/__init__.php @@ -7,6 +7,7 @@ phutil_require_module('phabricator', 'applications/herald/storage/base'); +phutil_require_module('phabricator', 'applications/phid/storage/phid'); phutil_require_module('phutil', 'utils'); diff --git a/src/applications/herald/storage/transcript/object/HeraldObjectTranscript.php b/src/applications/herald/storage/transcript/object/HeraldObjectTranscript.php index 4e9fc1e712..432921eefb 100644 --- a/src/applications/herald/storage/transcript/object/HeraldObjectTranscript.php +++ b/src/applications/herald/storage/transcript/object/HeraldObjectTranscript.php @@ -18,18 +18,18 @@ class HeraldObjectTranscript { - protected $fbid; + protected $phid; protected $type; protected $name; protected $fields; - public function setFBID($fbid) { - $this->fbid = $fbid; + public function setPHID($phid) { + $this->phid = $phid; return $this; } - public function getFBID() { - return $this->fbid; + public function getPHID() { + return $this->phid; } public function setType($type) {