1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-23 05:50:55 +01:00
phorge-phorge/src/applications/herald/controller/HeraldTranscriptController.php

537 lines
15 KiB
PHP
Raw Normal View History

<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class HeraldTranscriptController extends HeraldController {
const FILTER_AFFECTED = 'affected';
const FILTER_OWNED = 'owned';
const FILTER_ALL = 'all';
private $id;
private $filter;
private $handles;
public function willProcessRequest(array $data) {
$this->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!');
}
require_celerity_resource('herald-test-css');
$nav = $this->buildSideNav();
$object_xscript = $xscript->getObjectTranscript();
if (!$object_xscript) {
$notice = id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
->setTitle('Old Transcript')
->appendChild(
'<p>Details of this transcript have been garbage collected.</p>');
$nav->appendChild($notice);
} else {
$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;
General Herald refactoring pass Summary: **Who can delete global rules?**: I discussed this with @jungejason. The current behavior is that the rule author or any administrator can delete a global rule, but this isn't consistent with who can edit a rule (anyone) and doesn't really make much sense (it's an artifact of the global/personal split). I proposed that anyone can delete a rule but we don't actually delete them, and log the deletion. However, when it came time to actually write the code for this I backed off a bit and continued actually deleting the rules -- I think this does a reasonable job of balancing accountability with complexity. So the new impelmentation is: - Personal rules can be deleted only by their owners. - Global rules can be deleted by any user. - All deletes are logged. - Logs are more detailed. - All logged actions can be viewed in aggregate. **Minor Cleanup** - Merged `HomeController` and `AllController`. - Moved most queries to Query classes. - Use AphrontFormSelectControl::renderSelectTag() where appropriate (this is a fairly recent addition). - Use an AphrontErrorView to render the dry run notice (this didn't exist when I ported). - Reenable some transaction code (this works again now). - Removed the ability for admins to change rule authors (this was a little buggy, messy, and doesn't make tons of sense after the personal/global rule split). - Rules which depend on other rules now display the right options (all global rules, all your personal rules for personal rules). - Fix a bug in AphrontTableView where the "no data" cell would be rendered too wide if some columns are not visible. - Allow selectFilter() in AphrontNavFilterView to be called without a 'default' argument. Test Plan: - Browsed, created, edited, deleted personal and gules. - Verified generated logs. - Did some dry runs. - Verified transcript list and transcript details. - Created/edited all/any rules; created/edited once/every time rules. - Filtered admin views by users. Reviewers: jungejason, btrahan Reviewed By: btrahan CC: aran, epriestley Differential Revision: https://secure.phabricator.com/D2040
2012-03-30 19:49:55 +02:00
if ($xscript->getDryRun()) {
$notice = new AphrontErrorView();
$notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
$notice->setTitle('Dry Run');
$notice->appendChild(
'This was a dry run to test Herald rules, no actions were executed.');
$nav->appendChild($notice);
}
$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);
}
General Herald refactoring pass Summary: **Who can delete global rules?**: I discussed this with @jungejason. The current behavior is that the rule author or any administrator can delete a global rule, but this isn't consistent with who can edit a rule (anyone) and doesn't really make much sense (it's an artifact of the global/personal split). I proposed that anyone can delete a rule but we don't actually delete them, and log the deletion. However, when it came time to actually write the code for this I backed off a bit and continued actually deleting the rules -- I think this does a reasonable job of balancing accountability with complexity. So the new impelmentation is: - Personal rules can be deleted only by their owners. - Global rules can be deleted by any user. - All deletes are logged. - Logs are more detailed. - All logged actions can be viewed in aggregate. **Minor Cleanup** - Merged `HomeController` and `AllController`. - Moved most queries to Query classes. - Use AphrontFormSelectControl::renderSelectTag() where appropriate (this is a fairly recent addition). - Use an AphrontErrorView to render the dry run notice (this didn't exist when I ported). - Reenable some transaction code (this works again now). - Removed the ability for admins to change rule authors (this was a little buggy, messy, and doesn't make tons of sense after the personal/global rule split). - Rules which depend on other rules now display the right options (all global rules, all your personal rules for personal rules). - Fix a bug in AphrontTableView where the "no data" cell would be rendered too wide if some columns are not visible. - Allow selectFilter() in AphrontNavFilterView to be called without a 'default' argument. Test Plan: - Browsed, created, edited, deleted personal and gules. - Verified generated logs. - Did some dry runs. - Verified transcript list and transcript details. - Created/edited all/any rules; created/edited once/every time rules. - Filtered admin views by users. Reviewers: jungejason, btrahan Reviewed By: btrahan CC: aran, epriestley Differential Revision: https://secure.phabricator.com/D2040
2012-03-30 19:49:55 +02:00
$main_nav = $this->renderNav();
$main_nav->selectFilter('transcript');
$main_nav->appendChild($nav);
return $this->buildStandardPageResponse(
General Herald refactoring pass Summary: **Who can delete global rules?**: I discussed this with @jungejason. The current behavior is that the rule author or any administrator can delete a global rule, but this isn't consistent with who can edit a rule (anyone) and doesn't really make much sense (it's an artifact of the global/personal split). I proposed that anyone can delete a rule but we don't actually delete them, and log the deletion. However, when it came time to actually write the code for this I backed off a bit and continued actually deleting the rules -- I think this does a reasonable job of balancing accountability with complexity. So the new impelmentation is: - Personal rules can be deleted only by their owners. - Global rules can be deleted by any user. - All deletes are logged. - Logs are more detailed. - All logged actions can be viewed in aggregate. **Minor Cleanup** - Merged `HomeController` and `AllController`. - Moved most queries to Query classes. - Use AphrontFormSelectControl::renderSelectTag() where appropriate (this is a fairly recent addition). - Use an AphrontErrorView to render the dry run notice (this didn't exist when I ported). - Reenable some transaction code (this works again now). - Removed the ability for admins to change rule authors (this was a little buggy, messy, and doesn't make tons of sense after the personal/global rule split). - Rules which depend on other rules now display the right options (all global rules, all your personal rules for personal rules). - Fix a bug in AphrontTableView where the "no data" cell would be rendered too wide if some columns are not visible. - Allow selectFilter() in AphrontNavFilterView to be called without a 'default' argument. Test Plan: - Browsed, created, edited, deleted personal and gules. - Verified generated logs. - Did some dry runs. - Verified transcript list and transcript details. - Created/edited all/any rules; created/edited once/every time rules. - Filtered admin views by users. Reviewers: jungejason, btrahan Reviewed By: btrahan CC: aran, epriestley Differential Revision: https://secure.phabricator.com/D2040
2012-03-30 19:49:55 +02:00
$main_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
'<span class="condition-test-value">'.
phutil_escape_html($value).
'</span>';
}
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 (empty($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::getActionMessageMapForRuleType(null);
$rows = array();
foreach ($xscript->getApplyTranscripts() as $apply_xscript) {
$target = $apply_xscript->getTarget();
switch ($apply_xscript->getAction()) {
case HeraldActionConfig::ACTION_NOTHING:
$target = '';
break;
case HeraldActionConfig::ACTION_FLAG:
$target = PhabricatorFlagColor::getColorName($target);
break;
default:
if ($target) {
foreach ($target as $k => $phid) {
$target[$k] = $handles[$phid]->getName();
}
$target = implode("\n", $target);
} else {
$target = '<empty>';
}
break;
}
$target = phutil_escape_html($target);
if ($apply_xscript->getApplied()) {
$outcome = '<span class="outcome-success">SUCCESS</span>';
} else {
$outcome = '<span class="outcome-failure">FAILURE</span>';
}
$outcome .= ' '.phutil_escape_html($apply_xscript->getAppliedReason());
$rows[] = array(
phutil_escape_html($action_names[$apply_xscript->getAction()]),
$target,
'<strong>Taken because:</strong> '.
phutil_escape_html($apply_xscript->getReason()).
'<br />'.
'<strong>Outcome:</strong> '.$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();
$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 =
'<div class="herald-condition-note">'.
phutil_escape_html($cond->getNote()).
'</div>';
} else {
$note = null;
}
if ($cond->getResult()) {
$result =
'<span class="herald-outcome condition-pass">'.
"\xE2\x9C\x93".
'</span>';
} else {
$result =
'<span class="herald-outcome condition-fail">'.
"\xE2\x9C\x98".
'</span>';
}
$cond_markup[] =
'<li>'.
$result.' Condition: '.
phutil_escape_html($field_names[$cond->getFieldName()]).
' '.
phutil_escape_html($condition_names[$cond->getCondition()]).
' '.
$this->renderConditionTestValue($cond, $handles).
$note.
'</li>';
}
if ($rule->getResult()) {
$result = '<span class="herald-outcome rule-pass">PASS</span>';
$class = 'herald-rule-pass';
} else {
$result = '<span class="herald-outcome rule-fail">FAIL</span>';
$class = 'herald-rule-fail';
}
$cond_markup[] =
'<li>'.$result.' '.phutil_escape_html($rule->getReason()).'</li>';
/*
if ($rule->getResult()) {
$actions = idx($action_xscript, $rule_id, array());
if ($actions) {
$cond_markup[] = <li><div class="action-header">Actions</div></li>;
foreach ($actions as $action) {
$target = $action->getTarget();
if ($target) {
foreach ((array)$target as $k => $phid) {
$target[$k] = $handles[$phid]->getName();
}
$target = <strong>: {implode(', ', $target)}</strong>;
}
$cond_markup[] =
<li>
{$action_names[$action->getAction()]}
{$target}
</li>;
}
}
}
*/
$user_phid = $this->getRequest()->getUser()->getPHID();
$name = $rule->getRuleName();
if ($rule->getRuleOwner() == $user_phid) {
// $name = <a href={"/herald/rule/".$rule->getRuleID()."/"}>{$name}</a>;
}
$rule_markup[] =
phutil_render_tag(
'li',
array(
'class' => $class,
),
'<div class="rule-name">'.
'<strong>'.phutil_escape_html($name).'</strong> '.
phutil_escape_html($handles[$rule->getRuleOwner()]->getName()).
'</div>'.
'<ul>'.implode("\n", $cond_markup).'</ul>');
}
$panel = new AphrontPanelView();
$panel->setHeader('Rule Details');
$panel->appendChild(
'<ul class="herald-explain-list">'.
implode("\n", $rule_markup).
'</ul>');
return $panel;
}
private function buildObjectTranscriptPanel($xscript) {
$field_names = HeraldFieldConfig::getFieldMap();
$object_xscript = $xscript->getObjectTranscript();
$data = array();
if ($object_xscript) {
$phid = $object_xscript->getPHID();
$handles = id(new PhabricatorObjectHandleData(array($phid)))
->loadHandles();
$data += array(
'Object Name' => $object_xscript->getName(),
'Object Type' => $object_xscript->getType(),
'Object PHID' => $phid,
'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 (!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 if ($name === 'Object Link') {
// The link cannot be escaped
} 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;
}
}