1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-22 21:40:55 +01:00

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
This commit is contained in:
epriestley 2012-03-30 10:49:55 -07:00
parent 3c19e5b133
commit 698ec68327
34 changed files with 634 additions and 640 deletions

View file

@ -0,0 +1,5 @@
ALTER TABLE phabricator_herald.herald_ruleedit
ADD ruleName VARCHAR(255) NOT NULL COLLATE utf8_general_ci;
ALTER TABLE phabricator_herald.herald_ruleedit
ADD action VARCHAR(32) NOT NULL COLLATE utf8_general_ci;

View file

@ -304,7 +304,7 @@ celerity_register_resource_map(array(
), ),
'herald-css' => 'herald-css' =>
array( array(
'uri' => '/res/ed5556e6/rsrc/css/application/herald/herald.css', 'uri' => '/res/5051f3ab/rsrc/css/application/herald/herald.css',
'type' => 'css', 'type' => 'css',
'requires' => 'requires' =>
array( array(
@ -313,7 +313,7 @@ celerity_register_resource_map(array(
), ),
'herald-rule-editor' => 'herald-rule-editor' =>
array( array(
'uri' => '/res/745c9d43/rsrc/js/application/herald/HeraldRuleEditor.js', 'uri' => '/res/209ed8c4/rsrc/js/application/herald/HeraldRuleEditor.js',
'type' => 'js', 'type' => 'js',
'requires' => 'requires' =>
array( array(

View file

@ -395,6 +395,7 @@ phutil_register_library_map(array(
'HeraldDeleteController' => 'applications/herald/controller/delete', 'HeraldDeleteController' => 'applications/herald/controller/delete',
'HeraldDifferentialRevisionAdapter' => 'applications/herald/adapter/differential', 'HeraldDifferentialRevisionAdapter' => 'applications/herald/adapter/differential',
'HeraldDryRunAdapter' => 'applications/herald/adapter/dryrun', 'HeraldDryRunAdapter' => 'applications/herald/adapter/dryrun',
'HeraldEditLogQuery' => 'applications/herald/query/log',
'HeraldEffect' => 'applications/herald/engine/effect', 'HeraldEffect' => 'applications/herald/engine/effect',
'HeraldEngine' => 'applications/herald/engine/engine', 'HeraldEngine' => 'applications/herald/engine/engine',
'HeraldFieldConfig' => 'applications/herald/config/field', 'HeraldFieldConfig' => 'applications/herald/config/field',
@ -412,6 +413,7 @@ phutil_register_library_map(array(
'HeraldRuleEditHistoryController' => 'applications/herald/controller/edithistory', 'HeraldRuleEditHistoryController' => 'applications/herald/controller/edithistory',
'HeraldRuleEditHistoryView' => 'applications/herald/view/edithistory', 'HeraldRuleEditHistoryView' => 'applications/herald/view/edithistory',
'HeraldRuleListView' => 'applications/herald/view/rulelist', 'HeraldRuleListView' => 'applications/herald/view/rulelist',
'HeraldRuleQuery' => 'applications/herald/query/rule',
'HeraldRuleTranscript' => 'applications/herald/storage/transcript/rule', 'HeraldRuleTranscript' => 'applications/herald/storage/transcript/rule',
'HeraldRuleTypeConfig' => 'applications/herald/config/ruletype', 'HeraldRuleTypeConfig' => 'applications/herald/config/ruletype',
'HeraldTestConsoleController' => 'applications/herald/controller/test', 'HeraldTestConsoleController' => 'applications/herald/controller/test',
@ -1188,6 +1190,7 @@ phutil_register_library_map(array(
'DiffusionContainsQuery' => 'DiffusionQuery', 'DiffusionContainsQuery' => 'DiffusionQuery',
'DiffusionController' => 'PhabricatorController', 'DiffusionController' => 'PhabricatorController',
'DiffusionDiffController' => 'DiffusionController', 'DiffusionDiffController' => 'DiffusionController',
'DiffusionDiffQuery' => 'DiffusionQuery',
'DiffusionEmptyResultView' => 'DiffusionView', 'DiffusionEmptyResultView' => 'DiffusionView',
'DiffusionExternalController' => 'DiffusionController', 'DiffusionExternalController' => 'DiffusionController',
'DiffusionFileContentQuery' => 'DiffusionQuery', 'DiffusionFileContentQuery' => 'DiffusionQuery',
@ -1208,6 +1211,7 @@ phutil_register_library_map(array(
'DiffusionHomeController' => 'DiffusionController', 'DiffusionHomeController' => 'DiffusionController',
'DiffusionInlineCommentController' => 'PhabricatorInlineCommentController', 'DiffusionInlineCommentController' => 'PhabricatorInlineCommentController',
'DiffusionLastModifiedController' => 'DiffusionController', 'DiffusionLastModifiedController' => 'DiffusionController',
'DiffusionLastModifiedQuery' => 'DiffusionQuery',
'DiffusionMercurialBranchQuery' => 'DiffusionBranchQuery', 'DiffusionMercurialBranchQuery' => 'DiffusionBranchQuery',
'DiffusionMercurialBrowseQuery' => 'DiffusionBrowseQuery', 'DiffusionMercurialBrowseQuery' => 'DiffusionBrowseQuery',
'DiffusionMercurialCommitParentsQuery' => 'DiffusionCommitParentsQuery', 'DiffusionMercurialCommitParentsQuery' => 'DiffusionCommitParentsQuery',
@ -1268,6 +1272,7 @@ phutil_register_library_map(array(
'HeraldDeleteController' => 'HeraldController', 'HeraldDeleteController' => 'HeraldController',
'HeraldDifferentialRevisionAdapter' => 'HeraldObjectAdapter', 'HeraldDifferentialRevisionAdapter' => 'HeraldObjectAdapter',
'HeraldDryRunAdapter' => 'HeraldObjectAdapter', 'HeraldDryRunAdapter' => 'HeraldObjectAdapter',
'HeraldEditLogQuery' => 'PhabricatorOffsetPagedQuery',
'HeraldHomeController' => 'HeraldController', 'HeraldHomeController' => 'HeraldController',
'HeraldNewController' => 'HeraldController', 'HeraldNewController' => 'HeraldController',
'HeraldRule' => 'HeraldDAO', 'HeraldRule' => 'HeraldDAO',
@ -1276,6 +1281,7 @@ phutil_register_library_map(array(
'HeraldRuleEditHistoryController' => 'HeraldController', 'HeraldRuleEditHistoryController' => 'HeraldController',
'HeraldRuleEditHistoryView' => 'AphrontView', 'HeraldRuleEditHistoryView' => 'AphrontView',
'HeraldRuleListView' => 'AphrontView', 'HeraldRuleListView' => 'AphrontView',
'HeraldRuleQuery' => 'PhabricatorOffsetPagedQuery',
'HeraldTestConsoleController' => 'HeraldController', 'HeraldTestConsoleController' => 'HeraldController',
'HeraldTranscript' => 'HeraldDAO', 'HeraldTranscript' => 'HeraldDAO',
'HeraldTranscriptController' => 'HeraldController', 'HeraldTranscriptController' => 'HeraldController',

View file

@ -282,19 +282,14 @@ class AphrontDefaultApplicationConfiguration
'/herald/' => array( '/herald/' => array(
'' => 'HeraldHomeController', '' => 'HeraldHomeController',
'view/(?P<view>[^/]+)/' => array( 'view/(?P<content_type>[^/]+)/(?:(?P<rule_type>[^/]+)/)?'
'' => 'HeraldHomeController', => 'HeraldHomeController',
'(?P<global>global)/' => 'HeraldHomeController' 'new/(?:(?P<type>[^/]+)/(?:(?P<rule_type>[^/]+)/)?)?'
), => 'HeraldNewController',
'new/(?:(?P<type>[^/]+)/)?' => 'HeraldNewController',
'rule/(?:(?P<id>\d+)/)?' => 'HeraldRuleController', 'rule/(?:(?P<id>\d+)/)?' => 'HeraldRuleController',
'history/(?P<id>\d+)/' => 'HeraldRuleEditHistoryController', 'history/(?:(?P<id>\d+)/)?' => 'HeraldRuleEditHistoryController',
'delete/(?P<id>\d+)/' => 'HeraldDeleteController', 'delete/(?P<id>\d+)/' => 'HeraldDeleteController',
'test/' => 'HeraldTestConsoleController', 'test/' => 'HeraldTestConsoleController',
'all/' => array(
'' => 'HeraldAllRulesController',
'view/(?P<view>[^/]+)/' => 'HeraldAllRulesController',
),
'transcript/' => 'HeraldTranscriptListController', 'transcript/' => 'HeraldTranscriptListController',
'transcript/(?P<id>\d+)/(?:(?P<filter>\w+)/)?' 'transcript/(?P<id>\d+)/(?:(?P<filter>\w+)/)?'
=> 'HeraldTranscriptController', => 'HeraldTranscriptController',

View file

@ -1,173 +0,0 @@
<?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 HeraldAllRulesController extends HeraldController {
private $view;
private $viewPHID;
private $filter;
public function getFilter() {
return $this->filter;
}
public function setFilter($filter) {
$this->filter = 'all/view/'.$filter;
return $this;
}
public function shouldRequireAdmin() {
return true;
}
public function willProcessRequest(array $data) {
$this->view = idx($data, 'view');
$this->setFilter($this->view);
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$this->viewPHID = nonempty($request->getStr('phid'), null);
if ($request->isFormPost()) {
$phid_arr = $request->getArr('view_user');
$view_target = head($phid_arr);
return id(new AphrontRedirectResponse())
->setURI($request->getRequestURI()->alter('phid', $view_target));
}
$map = HeraldContentTypeConfig::getContentTypeMap();
if (empty($map[$this->view])) {
reset($map);
$this->view = key($map);
}
$offset = $request->getInt('offset', 0);
$pager = new AphrontPagerView();
$pager->setPageSize(50);
$pager->setOffset($offset);
$pager->setURI($request->getRequestURI(), 'offset');
list($rules, $handles) = $this->queryRules($pager);
if (!$this->viewPHID) {
$view_users = array();
} else {
$view_users = array(
$this->viewPHID => $handles[$this->viewPHID]->getFullName(),
);
}
$filter_form = id(new AphrontFormView())
->setUser($user)
->appendChild(
id(new AphrontFormTokenizerControl())
->setDatasource('/typeahead/common/users/')
->setLabel('View User')
->setName('view_user')
->setValue($view_users)
->setLimit(1));
$filter_view = new AphrontListFilterView();
$filter_view->appendChild($filter_form);
$list_view = id(new HeraldRuleListView())
->setRules($rules)
->setHandles($handles)
->setMap($map)
->setShowType(true)
->setAllowCreation(false)
->setUser($user)
->setView($this->view);
$panel = $list_view->render();
$panel->appendChild($pager);
$sidenav = new AphrontSideNavView();
$sidenav->appendChild($filter_view);
$sidenav->appendChild($panel);
$query = '';
if ($this->viewPHID) {
$query = '?phid='.$this->viewPHID;
}
foreach ($map as $key => $value) {
$sidenav->addNavItem(
phutil_render_tag(
'a',
array(
'href' => '/herald/all/view/'.$key.'/'.$query,
'class' => ($key == $this->view)
? 'aphront-side-nav-selected'
: null,
),
phutil_escape_html($value)));
}
return $this->buildStandardPageResponse(
array(
$filter_view,
$panel
),
array(
'title' => 'Herald',
'tab' => 'all',
));
}
private function queryRules(AphrontPagerView $pager) {
$rule = new HeraldRule();
$conn_r = $rule->establishConnection('r');
$where_clause = qsprintf(
$conn_r,
'WHERE contentType = %s',
$this->view);
if ($this->viewPHID) {
$where_clause .= qsprintf(
$conn_r,
' AND authorPHID = %s',
$this->viewPHID);
}
$data = queryfx_all(
$conn_r,
'SELECT * FROM %T
%Q
ORDER BY id DESC
LIMIT %d, %d',
$rule->getTableName(),
$where_clause,
$pager->getOffset(),
$pager->getPageSize() + 1);
$data = $pager->sliceResults($data);
$rules = $rule->loadAllFromArray($data);
$need_phids = mpull($rules, 'getAuthorPHID');
if ($this->viewPHID) {
$need_phids[] = $this->viewPHID;
}
$handles = id(new PhabricatorObjectHandleData($need_phids))
->loadHandles();
return array($rules, $handles);
}
}

View file

@ -1,27 +0,0 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'aphront/response/redirect');
phutil_require_module('phabricator', 'applications/herald/config/contenttype');
phutil_require_module('phabricator', 'applications/herald/controller/base');
phutil_require_module('phabricator', 'applications/herald/storage/rule');
phutil_require_module('phabricator', 'applications/herald/view/rulelist');
phutil_require_module('phabricator', 'applications/phid/handle/data');
phutil_require_module('phabricator', 'storage/qsprintf');
phutil_require_module('phabricator', 'storage/queryfx');
phutil_require_module('phabricator', 'view/control/pager');
phutil_require_module('phabricator', 'view/form/base');
phutil_require_module('phabricator', 'view/form/control/tokenizer');
phutil_require_module('phabricator', 'view/layout/listfilter');
phutil_require_module('phabricator', 'view/layout/sidenav');
phutil_require_module('phutil', 'markup');
phutil_require_module('phutil', 'utils');
phutil_require_source('HeraldAllRulesController.php');

View file

@ -28,48 +28,6 @@ abstract class HeraldController extends PhabricatorController {
$doclink = PhabricatorEnv::getDoclink('article/Herald_User_Guide.html'); $doclink = PhabricatorEnv::getDoclink('article/Herald_User_Guide.html');
$nav = new AphrontSideNavFilterView();
$nav
->setBaseURI(new PhutilURI('/herald/'))
->addLabel('My Rules')
->addFilter('new', 'Create Rule');
$rules_map = HeraldContentTypeConfig::getContentTypeMap();
$first_filter = null;
foreach ($rules_map as $key => $value) {
$nav->addFilter('view/'.$key, $value);
if (!$first_filter) {
$first_filter = 'view/'.$key;
}
}
$nav
->addSpacer()
->addLabel('Global Rules');
foreach ($rules_map as $key => $value) {
$nav->addFilter("view/${key}/global", $value);
}
$nav
->addSpacer()
->addLabel('Utilities')
->addFilter('test', 'Test Console')
->addFilter('transcript', 'Transcripts');
$user = $this->getRequest()->getUser();
if ($user->getIsAdmin()) {
$nav
->addSpacer()
->addLabel('Admin');
$view_PHID = nonempty($this->getRequest()->getStr('phid'), null);
foreach ($rules_map as $key => $value) {
$nav
->addFilter('all/view/'.$key, $value);
}
}
$nav->selectFilter($this->getFilter(), $first_filter);
$nav->appendChild($view);
$page->appendChild($nav);
$tabs = array( $tabs = array(
'help' => array( 'help' => array(
'href' => $doclink, 'href' => $doclink,
@ -78,10 +36,49 @@ abstract class HeraldController extends PhabricatorController {
); );
$page->setTabs($tabs, null); $page->setTabs($tabs, null);
$page->appendChild($view);
$page->setIsAdminInterface(idx($data, 'admin'));
$response = new AphrontWebpageResponse(); $response = new AphrontWebpageResponse();
return $response->setContent($page->render()); return $response->setContent($page->render());
} }
abstract function getFilter(); protected function renderNav() {
$nav = id(new AphrontSideNavFilterView())
->setBaseURI(new PhutilURI('/herald/'))
->addLabel('My Rules')
->addFilter('new', 'Create Rule');
$rules_map = HeraldContentTypeConfig::getContentTypeMap();
foreach ($rules_map as $key => $value) {
$nav->addFilter("view/{$key}/personal", $value);
}
$nav
->addSpacer()
->addLabel('Global Rules');
foreach ($rules_map as $key => $value) {
$nav->addFilter("view/{$key}/global", $value);
}
$nav
->addSpacer()
->addLabel('Utilities')
->addFilter('test', 'Test Console')
->addFilter('transcript', 'Transcripts')
->addFilter('history', 'Edit Log');
if ($this->getRequest()->getUser()->getIsAdmin()) {
$nav
->addSpacer()
->addLabel('Admin');
foreach ($rules_map as $key => $value) {
$nav->addFilter("view/{$key}/all", $value);
}
}
return $nav;
}
} }

View file

@ -40,17 +40,18 @@ final class HeraldDeleteController extends HeraldController {
$request = $this->getRequest(); $request = $this->getRequest();
$user = $request->getUser(); $user = $request->getUser();
if ($user->getPHID() != $rule->getAuthorPHID() && !$user->getIsAdmin()) { // Anyone can delete a global rule, but only the rule owner can delete a
// personal one.
if ($rule->getRuleType() == HeraldRuleTypeConfig::RULE_TYPE_PERSONAL) {
if ($user->getPHID() != $rule->getAuthorPHID()) {
return new Aphront400Response(); return new Aphront400Response();
} }
}
if ($request->isFormPost()) { if ($request->isFormPost()) {
$rule->logEdit($user->getPHID(), 'delete');
$rule->delete(); $rule->delete();
if ($request->isAjax()) { return id(new AphrontReloadResponse())->setURI('/herald/');
return new AphrontRedirectResponse();
} else {
return id(new AphrontRedirectResponse())->setURI('/herald/');
}
} }
$dialog = new AphrontDialogView(); $dialog = new AphrontDialogView();

View file

@ -9,7 +9,8 @@
phutil_require_module('phabricator', 'aphront/response/400'); phutil_require_module('phabricator', 'aphront/response/400');
phutil_require_module('phabricator', 'aphront/response/404'); phutil_require_module('phabricator', 'aphront/response/404');
phutil_require_module('phabricator', 'aphront/response/dialog'); phutil_require_module('phabricator', 'aphront/response/dialog');
phutil_require_module('phabricator', 'aphront/response/redirect'); phutil_require_module('phabricator', 'aphront/response/reload');
phutil_require_module('phabricator', 'applications/herald/config/ruletype');
phutil_require_module('phabricator', 'applications/herald/controller/base'); phutil_require_module('phabricator', 'applications/herald/controller/base');
phutil_require_module('phabricator', 'applications/herald/storage/rule'); phutil_require_module('phabricator', 'applications/herald/storage/rule');
phutil_require_module('phabricator', 'view/dialog'); phutil_require_module('phabricator', 'view/dialog');

View file

@ -21,35 +21,45 @@ final class HeraldRuleEditHistoryController extends HeraldController {
private $id; private $id;
public function willProcessRequest(array $data) { public function willProcessRequest(array $data) {
$this->id = $data['id']; $this->id = idx($data, 'id');
} }
public function processRequest() { public function processRequest() {
$rule = id(new HeraldRule())->load($this->id); $request = $this->getRequest();
if ($rule === null) {
return new Aphront404Response(); $edit_query = new HeraldEditLogQuery();
if ($this->id) {
$edit_query->withRuleIDs(array($this->id));
} }
$edits = $rule->loadEdits(); $pager = new AphrontPagerView();
$rule->attachEdits($edits); $pager->setURI($request->getRequestURI(), 'offset');
$pager->setOffset($request->getStr('offset'));
$edits = $edit_query->executeWithPager($pager);
$need_phids = mpull($edits, 'getEditorPHID'); $need_phids = mpull($edits, 'getEditorPHID');
$handles = id(new PhabricatorObjectHandleData($need_phids)) $handles = id(new PhabricatorObjectHandleData($need_phids))
->loadHandles(); ->loadHandles();
$list_view = id(new HeraldRuleEditHistoryView()) $list_view = id(new HeraldRuleEditHistoryView())
->setRule($rule) ->setEdits($edits)
->setHandles($handles) ->setHandles($handles)
->setUser($this->getRequest()->getUser()); ->setUser($this->getRequest()->getUser());
$panel = new AphrontPanelView();
$panel->setHeader('Edit History');
$panel->appendChild($list_view);
$nav = $this->renderNav();
$nav->selectFilter('history');
$nav->appendChild($panel);
return $this->buildStandardPageResponse( return $this->buildStandardPageResponse(
$list_view->render(), $nav,
array( array(
'title' => 'Rule Edit History', 'title' => 'Rule Edit History',
)); ));
} }
public function getFilter() {
return;
}
} }

View file

@ -6,11 +6,12 @@
phutil_require_module('phabricator', 'aphront/response/404');
phutil_require_module('phabricator', 'applications/herald/controller/base'); phutil_require_module('phabricator', 'applications/herald/controller/base');
phutil_require_module('phabricator', 'applications/herald/storage/rule'); phutil_require_module('phabricator', 'applications/herald/query/log');
phutil_require_module('phabricator', 'applications/herald/view/edithistory'); phutil_require_module('phabricator', 'applications/herald/view/edithistory');
phutil_require_module('phabricator', 'applications/phid/handle/data'); phutil_require_module('phabricator', 'applications/phid/handle/data');
phutil_require_module('phabricator', 'view/control/pager');
phutil_require_module('phabricator', 'view/layout/panel');
phutil_require_module('phutil', 'utils'); phutil_require_module('phutil', 'utils');

View file

@ -18,56 +18,82 @@
final class HeraldHomeController extends HeraldController { final class HeraldHomeController extends HeraldController {
private $view; private $contentType;
private $filter; private $ruleType;
private $global;
public function willProcessRequest(array $data) { public function willProcessRequest(array $data) {
$this->view = idx($data, 'view'); $this->contentType = idx($data, 'content_type');
$this->global = idx($data, 'global'); $this->ruleType = idx($data, 'rule_type');
if ($this->global) {
$this->setFilter($this->view.'/global');
} else {
$this->setFilter($this->view);
}
}
public function getFilter() {
return $this->filter;
}
public function setFilter($filter) {
$this->filter = 'view/'.$filter;
return $this;
} }
public function processRequest() { public function processRequest() {
$request = $this->getRequest(); $request = $this->getRequest();
$user = $request->getUser(); $user = $request->getUser();
$map = HeraldContentTypeConfig::getContentTypeMap(); if ($request->isFormPost()) {
if (empty($map[$this->view])) { $phids = $request->getArr('set_phid');
reset($map); $phid = head($phids);
$this->view = key($map);
$uri = $request->getRequestURI();
if ($phid) {
$uri = $uri->alter('phid', nonempty($phid, null));
} }
if ($this->global) { return id(new AphrontRedirectResponse())->setURI($uri);
$rules = id(new HeraldRule())->loadAllWhere(
'contentType = %s AND ruleType = %s',
$this->view,
HeraldRuleTypeConfig::RULE_TYPE_GLOBAL);
} else {
$rules = id(new HeraldRule())->loadAllWhere(
'contentType = %s AND authorPHID = %s AND ruleType = %s',
$this->view,
$user->getPHID(),
HeraldRuleTypeConfig::RULE_TYPE_PERSONAL);
} }
foreach ($rules as $rule) { $query = new HeraldRuleQuery();
$edits = $rule->loadEdits();
$rule->attachEdits($edits); $content_type_map = HeraldContentTypeConfig::getContentTypeMap();
if (empty($content_type_map[$this->contentType])) {
$this->contentType = key($content_type_map);
} }
$content_desc = $content_type_map[$this->contentType];
$query->withContentTypes(array($this->contentType));
$is_admin_page = false;
$show_author = false;
$show_rule_type = false;
$can_create = false;
$has_author_filter = false;
$author_filter_phid = null;
switch ($this->ruleType) {
case 'all':
if (!$user->getIsAdmin()) {
return new Aphront400Response();
}
$is_admin_page = true;
$show_rule_type = true;
$show_author = true;
$has_author_filter = true;
$author_filter_phid = $request->getStr('phid');
if ($author_filter_phid) {
$query->withAuthorPHIDs(array($author_filter_phid));
}
$rule_desc = 'All';
break;
case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL:
$query->withRuleTypes(array(HeraldRuleTypeConfig::RULE_TYPE_GLOBAL));
$can_create = true;
$rule_desc = 'Global';
break;
case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL:
default:
$this->ruleType = HeraldRuleTypeConfig::RULE_TYPE_PERSONAL;
$query->withRuleTypes(array(HeraldRuleTypeConfig::RULE_TYPE_PERSONAL));
$query->withAuthorPHIDs(array($user->getPHID()));
$can_create = true;
$rule_desc = 'Personal';
break;
}
$pager = new AphrontPagerView();
$pager->setURI($request->getRequestURI(), 'offset');
$pager->setOffset($request->getStr('offset'));
$rules = $query->executeWithPager($pager);
$need_phids = mpull($rules, 'getAuthorPHID'); $need_phids = mpull($rules, 'getAuthorPHID');
$handles = id(new PhabricatorObjectHandleData($need_phids)) $handles = id(new PhabricatorObjectHandleData($need_phids))
@ -75,19 +101,73 @@ final class HeraldHomeController extends HeraldController {
$list_view = id(new HeraldRuleListView()) $list_view = id(new HeraldRuleListView())
->setRules($rules) ->setRules($rules)
->setShowOwner(!$this->global) ->setShowAuthor($show_author)
->setShowRuleType($show_rule_type)
->setHandles($handles) ->setHandles($handles)
->setMap($map)
->setAllowCreation(true)
->setView($this->view)
->setUser($user); ->setUser($user);
$panel = $list_view->render();
$panel = new AphrontPanelView();
$panel->appendChild($list_view);
$panel->appendChild($pager);
$panel->setHeader("Herald: {$rule_desc} Rules for {$content_desc}");
if ($can_create) {
$panel->addButton(
phutil_render_tag(
'a',
array(
'href' => '/herald/new/'.$this->contentType.'/'.$this->ruleType.'/',
'class' => 'green button',
),
'Create New Herald Rule'));
}
$nav = $this->renderNav();
$nav->selectFilter('view/'.$this->contentType.'/'.$this->ruleType);
if ($has_author_filter) {
$nav->appendChild($this->renderAuthorFilter($author_filter_phid));
}
$nav->appendChild($panel);
return $this->buildStandardPageResponse( return $this->buildStandardPageResponse(
$panel, $nav,
array( array(
'title' => 'Herald', 'title' => 'Herald',
'admin' => $is_admin_page,
)); ));
} }
private function renderAuthorFilter($phid) {
if ($phid) {
$handle = PhabricatorObjectHandleData::loadOneHandle($phid);
$tokens = array(
$phid => $handle->getFullName(),
);
} else {
$tokens = array();
}
$form = id(new AphrontFormView())
->setUser($this->getRequest()->getUser())
->appendChild(
id(new AphrontFormTokenizerControl())
->setName('set_phid')
->setValue($tokens)
->setLimit(1)
->setLabel('Filter Author')
->setDataSource('/typeahead/common/accounts/'))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Apply Filter'));
$filter = new AphrontListFilterView();
$filter->appendChild($form);
return $filter;
}
} }

View file

@ -6,13 +6,22 @@
phutil_require_module('phabricator', 'aphront/response/400');
phutil_require_module('phabricator', 'aphront/response/redirect');
phutil_require_module('phabricator', 'applications/herald/config/contenttype'); phutil_require_module('phabricator', 'applications/herald/config/contenttype');
phutil_require_module('phabricator', 'applications/herald/config/ruletype'); phutil_require_module('phabricator', 'applications/herald/config/ruletype');
phutil_require_module('phabricator', 'applications/herald/controller/base'); phutil_require_module('phabricator', 'applications/herald/controller/base');
phutil_require_module('phabricator', 'applications/herald/storage/rule'); phutil_require_module('phabricator', 'applications/herald/query/rule');
phutil_require_module('phabricator', 'applications/herald/view/rulelist'); phutil_require_module('phabricator', 'applications/herald/view/rulelist');
phutil_require_module('phabricator', 'applications/phid/handle/data'); phutil_require_module('phabricator', 'applications/phid/handle/data');
phutil_require_module('phabricator', 'view/control/pager');
phutil_require_module('phabricator', 'view/form/base');
phutil_require_module('phabricator', 'view/form/control/submit');
phutil_require_module('phabricator', 'view/form/control/tokenizer');
phutil_require_module('phabricator', 'view/layout/listfilter');
phutil_require_module('phabricator', 'view/layout/panel');
phutil_require_module('phutil', 'markup');
phutil_require_module('phutil', 'utils'); phutil_require_module('phutil', 'utils');

View file

@ -19,13 +19,11 @@
final class HeraldNewController extends HeraldController { final class HeraldNewController extends HeraldController {
private $contentType; private $contentType;
private $ruleType;
public function getFilter() {
return 'new';
}
public function willProcessRequest(array $data) { public function willProcessRequest(array $data) {
$this->contentType = idx($data, 'type'); $this->contentType = idx($data, 'type');
$this->ruleType = idx($data, 'rule_type');
} }
public function processRequest() { public function processRequest() {
@ -40,6 +38,9 @@ final class HeraldNewController extends HeraldController {
} }
$rule_type_map = HeraldRuleTypeConfig::getRuleTypeMap(); $rule_type_map = HeraldRuleTypeConfig::getRuleTypeMap();
if (empty($rule_type_map[$this->ruleType])) {
$this->ruleType = HeraldRuleTypeConfig::RULE_TYPE_PERSONAL;
}
// Reorder array to put "personal" first. // Reorder array to put "personal" first.
$rule_type_map = array_select_keys( $rule_type_map = array_select_keys(
@ -61,7 +62,7 @@ final class HeraldNewController extends HeraldController {
$radio = id(new AphrontFormRadioButtonControl()) $radio = id(new AphrontFormRadioButtonControl())
->setLabel('Type') ->setLabel('Type')
->setName('rule_type') ->setName('rule_type')
->setValue(HeraldRuleTypeConfig::RULE_TYPE_PERSONAL); ->setValue($this->ruleType);
foreach ($rule_type_map as $value => $name) { foreach ($rule_type_map as $value => $name) {
$radio->addButton( $radio->addButton(
@ -90,8 +91,12 @@ final class HeraldNewController extends HeraldController {
$panel->setWidth(AphrontPanelView::WIDTH_FULL); $panel->setWidth(AphrontPanelView::WIDTH_FULL);
$panel->appendChild($form); $panel->appendChild($form);
$nav = $this->renderNav();
$nav->selectFilter('new');
$nav->appendChild($panel);
return $this->buildStandardPageResponse( return $this->buildStandardPageResponse(
$panel, $nav,
array( array(
'title' => 'Create Herald Rule', 'title' => 'Create Herald Rule',
)); ));

View file

@ -21,13 +21,6 @@ final class HeraldRuleController extends HeraldController {
private $id; private $id;
private $filter; private $filter;
public function getFilter() {
return $this->filter;
}
public function setFilter($filter) {
$this->filter = 'view/'.$filter;
}
public function willProcessRequest(array $data) { public function willProcessRequest(array $data) {
$this->id = (int)idx($data, 'id'); $this->id = (int)idx($data, 'id');
} }
@ -65,13 +58,13 @@ final class HeraldRuleController extends HeraldController {
} }
$rule->setRuleType($rule_type); $rule->setRuleType($rule_type);
} }
$this->setFilter($rule->getContentType());
$local_version = id(new HeraldRule())->getConfigVersion(); $local_version = id(new HeraldRule())->getConfigVersion();
if ($rule->getConfigVersion() > $local_version) { if ($rule->getConfigVersion() > $local_version) {
throw new Exception( throw new Exception(
"This rule was created with a newer version of Herald. You can not ". "This rule was created with a newer version of Herald. You can not ".
"view or edit it in this older version. Try dev or wait for a push."); "view or edit it in this older version. Upgrade your Phabricator ".
"deployment.");
} }
// Upgrade rule version to our version, since we might add newly-defined // Upgrade rule version to our version, since we might add newly-defined
@ -88,16 +81,11 @@ final class HeraldRuleController extends HeraldController {
$errors = array(); $errors = array();
if ($request->isFormPost() && $request->getStr('save')) { if ($request->isFormPost() && $request->getStr('save')) {
list($e_name, $errors) = $this->saveRule($rule, $request); list($e_name, $errors) = $this->saveRule($rule, $request);
if (!$errors) { if (!$errors) {
$uri = '/herald/view/'.$rule->getContentType().'/'; $uri = '/herald/view/'.
$rule->getContentType().'/'.
if ($rule->getRuleType() == HeraldRuleTypeConfig::RULE_TYPE_GLOBAL) { $rule->getRuleType().'/';
$uri .= 'global/'; return id(new AphrontRedirectResponse())->setURI($uri);
}
return id(new AphrontRedirectResponse())
->setURI($uri);
} }
} }
@ -109,8 +97,8 @@ final class HeraldRuleController extends HeraldController {
$error_view = null; $error_view = null;
} }
$must_match_selector = $this->getMustMatchSelector($rule); $must_match_selector = $this->renderMustMatchSelector($rule);
$repetition_selector = $this->getRepetitionSelector($rule); $repetition_selector = $this->renderRepetitionSelector($rule);
$handles = $this->loadHandles($rule); $handles = $this->loadHandles($rule);
@ -126,7 +114,8 @@ final class HeraldRuleController extends HeraldController {
->addHiddenInput('rule_type', $rule->getRuleType()) ->addHiddenInput('rule_type', $rule->getRuleType())
->addHiddenInput('save', 1) ->addHiddenInput('save', 1)
->appendChild( ->appendChild(
// Build this explicitly so we can add a sigil to it. // Build this explicitly (instead of using addHiddenInput())
// so we can add a sigil to it.
javelin_render_tag( javelin_render_tag(
'input', 'input',
array( array(
@ -141,23 +130,6 @@ final class HeraldRuleController extends HeraldController {
->setError($e_name) ->setError($e_name)
->setValue($rule->getName())); ->setValue($rule->getName()));
if ($rule->getRuleType() == HeraldRuleTypeConfig::RULE_TYPE_PERSONAL) {
$form
->appendChild(
id(new AphrontFormMarkupControl())
->setLabel('Owner')
->setValue('<div id="author-input"/>'))
->appendChild(
// Build this explicitly so we can add a sigil to it.
javelin_render_tag(
'input',
array(
'type' => 'hidden',
'name' => 'author',
'sigil' => 'author',
)));
}
$form $form
->appendChild( ->appendChild(
id(new AphrontFormMarkupControl()) id(new AphrontFormMarkupControl())
@ -215,15 +187,24 @@ final class HeraldRuleController extends HeraldController {
$this->setupEditorBehavior($rule, $handles); $this->setupEditorBehavior($rule, $handles);
$panel = new AphrontPanelView(); $panel = new AphrontPanelView();
$panel->setHeader('Edit Herald Rule'); $panel->setHeader(
$rule->getID()
? 'Edit Herald Rule'
: 'Create Herald Rule');
$panel->setWidth(AphrontPanelView::WIDTH_WIDE); $panel->setWidth(AphrontPanelView::WIDTH_WIDE);
$panel->appendChild($form); $panel->appendChild($form);
return $this->buildStandardPageResponse( $nav = $this->renderNav();
$nav->selectFilter(
'view/'.$rule->getContentType().'/'.$rule->getRuleType());
$nav->appendChild(
array( array(
$error_view, $error_view,
$panel, $panel,
), ));
return $this->buildStandardPageResponse(
$nav,
array( array(
'title' => 'Edit Rule', 'title' => 'Edit Rule',
)); ));
@ -231,9 +212,9 @@ final class HeraldRuleController extends HeraldController {
private function canEditRule($rule, $user) { private function canEditRule($rule, $user) {
return return
$user->getIsAdmin() || ($user->getIsAdmin()) ||
$rule->getRuleType() == HeraldRuleTypeConfig::RULE_TYPE_GLOBAL || ($rule->getRuleType() == HeraldRuleTypeConfig::RULE_TYPE_GLOBAL) ||
$rule->getAuthorPHID() == $user->getPHID(); ($rule->getAuthorPHID() == $user->getPHID());
} }
private function saveRule($rule, $request) { private function saveRule($rule, $request) {
@ -322,11 +303,6 @@ final class HeraldRuleController extends HeraldController {
$conditions[] = $obj; $conditions[] = $obj;
} }
$author = $request->getStr('author');
if ($author) {
$rule->setAuthorPHID($author);
}
$actions = array(); $actions = array();
foreach ($data['actions'] as $action) { foreach ($data['actions'] as $action) {
if ($action === null) { if ($action === null) {
@ -351,13 +327,14 @@ final class HeraldRuleController extends HeraldController {
if (!$errors) { if (!$errors) {
try { try {
// TODO $edit_action = $rule->getID() ? 'edit' : 'create';
// $rule->openTransaction();
$rule->openTransaction();
$rule->save(); $rule->save();
$rule->saveConditions($conditions); $rule->saveConditions($conditions);
$rule->saveActions($actions); $rule->saveActions($actions);
$rule->saveEdit($request->getUser()->getPHID()); $rule->logEdit($request->getUser()->getPHID(), $edit_action);
// $rule->saveTransaction(); $rule->saveTransaction();
} catch (AphrontQueryDuplicateKeyException $ex) { } catch (AphrontQueryDuplicateKeyException $ex) {
$e_name = "Not Unique"; $e_name = "Not Unique";
@ -413,14 +390,9 @@ final class HeraldRuleController extends HeraldController {
} }
} }
$all_rules = id(new HeraldRule())->loadAllWhere( $all_rules = $this->loadRulesThisRuleMayDependUpon($rule);
'authorPHID = %s AND contentType = %s',
$rule->getAuthorPHID(),
$rule->getContentType());
$all_rules = mpull($all_rules, 'getName', 'getID'); $all_rules = mpull($all_rules, 'getName', 'getID');
asort($all_rules); asort($all_rules);
unset($all_rules[$rule->getID()]);
$config_info = array(); $config_info = array();
$config_info['fields'] $config_info['fields']
@ -468,7 +440,6 @@ final class HeraldRuleController extends HeraldController {
private function loadHandles($rule) { private function loadHandles($rule) {
$phids = array(); $phids = array();
$phids[] = $rule->getAuthorPHID();
foreach ($rule->getActions() as $action) { foreach ($rule->getActions() as $action) {
if (!is_array($action->getTarget())) { if (!is_array($action->getTarget())) {
@ -491,79 +462,60 @@ final class HeraldRuleController extends HeraldController {
} }
} }
$phids += array($rule->getAuthorPHID()); $phids[] = $rule->getAuthorPHID();
$handles = id(new PhabricatorObjectHandleData($phids))
->loadHandles(); return id(new PhabricatorObjectHandleData($phids))->loadHandles();
return $handles;
} }
private function getMustMatchSelector($rule) {
$options = array( /**
* 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' => 'all of', 'all' => 'all of',
'any' => 'any of', 'any' => 'any of',
);
$selected = $rule->getMustMatchAll() ? 'all' : 'any';
$must_match = array();
foreach ($options as $key => $option) {
$must_match[] = phutil_render_tag(
'option',
array(
'selected' => ($selected == $key) ? 'selected' : null,
'value' => $key,
), ),
phutil_escape_html($option)); array(
} 'name' => 'must_match',
$must_match = ));
'<select name="must_match">' .
implode("\n", $must_match) .
'</select>';
return $must_match;
} }
private function getRepetitionSelector($rule) {
/**
* Render the selector for "Take these actions (every time | only the first
* time) this rule matches..." element.
*/
private function renderRepetitionSelector($rule) {
// Make the selector for choosing how often this rule should be repeated // Make the selector for choosing how often this rule should be repeated
$repetition_policy = HeraldRepetitionPolicyConfig::toString( $repetition_policy = HeraldRepetitionPolicyConfig::toString(
$rule->getRepetitionPolicy() $rule->getRepetitionPolicy());
);
$repetition_options = HeraldRepetitionPolicyConfig::getMapForContentType( $repetition_options = HeraldRepetitionPolicyConfig::getMapForContentType(
$rule->getContentType() $rule->getContentType());
);
if (empty($repetition_options)) { if (empty($repetition_options)) {
// default option is 'every time' // default option is 'every time'
$repetition_selector = idx( $repetition_selector = idx(
HeraldRepetitionPolicyConfig::getMap(), HeraldRepetitionPolicyConfig::getMap(),
HeraldRepetitionPolicyConfig::EVERY HeraldRepetitionPolicyConfig::EVERY);
);
return $repetition_selector; return $repetition_selector;
} else if (count($repetition_options) == 1) { } else if (count($repetition_options) == 1) {
// if there's only 1 option, just pick it for the user // if there's only 1 option, just pick it for the user
$repetition_selector = reset($repetition_options); $repetition_selector = reset($repetition_options);
return $repetition_selector; return $repetition_selector;
} else { } else {
// give the user all the options for this rule type return AphrontFormSelectControl::renderSelectTag(
$tags = array(); $repetition_policy,
$repetition_options,
foreach ($repetition_options as $name => $option) {
$tags[] = phutil_render_tag(
'option',
array( array(
'selected' => ($repetition_policy == $name) ? 'selected' : null, 'name' => 'repetition_policy',
'value' => $name, ));
), }
phutil_escape_html($option)
);
} }
$repetition_selector =
'<select name="repetition_policy">' .
implode("\n", $tags) .
'</select>';
return $repetition_selector;
}
}
protected function buildTokenizerTemplates() { protected function buildTokenizerTemplates() {
$template = new AphrontTokenizerTemplateView(); $template = new AphrontTokenizerTemplateView();
@ -580,4 +532,33 @@ final class HeraldRuleController extends HeraldController {
'markup' => $template, '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) {
// Any rule can depend on a global rule.
$all_rules = id(new HeraldRuleQuery())
->withRuleTypes(array(HeraldRuleTypeConfig::RULE_TYPE_GLOBAL))
->withContentTypes(array($rule->getContentType()))
->execute();
if ($rule->getRuleType() == HeraldRuleTypeConfig::RULE_TYPE_PERSONAL) {
// Personal rules may depend upon your other personal rules.
$all_rules += id(new HeraldRuleQuery())
->withRuleTypes(array(HeraldRuleTypeConfig::RULE_TYPE_PERSONAL))
->withContentTypes(array($rule->getContentType()))
->withAuthorPHIDs(array($rule->getAuthorPHID()))
->execute();
}
// A rule can not depend upon itself.
unset($all_rules[$rule->getID()]);
return $all_rules;
}
} }

View file

@ -16,6 +16,7 @@ phutil_require_module('phabricator', 'applications/herald/config/repetitionpolic
phutil_require_module('phabricator', 'applications/herald/config/ruletype'); phutil_require_module('phabricator', 'applications/herald/config/ruletype');
phutil_require_module('phabricator', 'applications/herald/config/valuetype'); phutil_require_module('phabricator', 'applications/herald/config/valuetype');
phutil_require_module('phabricator', 'applications/herald/controller/base'); phutil_require_module('phabricator', 'applications/herald/controller/base');
phutil_require_module('phabricator', 'applications/herald/query/rule');
phutil_require_module('phabricator', 'applications/herald/storage/condition'); phutil_require_module('phabricator', 'applications/herald/storage/condition');
phutil_require_module('phabricator', 'applications/herald/storage/rule'); phutil_require_module('phabricator', 'applications/herald/storage/rule');
phutil_require_module('phabricator', 'applications/phid/handle/data'); phutil_require_module('phabricator', 'applications/phid/handle/data');
@ -25,13 +26,13 @@ phutil_require_module('phabricator', 'infrastructure/javelin/markup');
phutil_require_module('phabricator', 'view/control/tokenizer'); phutil_require_module('phabricator', 'view/control/tokenizer');
phutil_require_module('phabricator', 'view/form/base'); phutil_require_module('phabricator', 'view/form/base');
phutil_require_module('phabricator', 'view/form/control/markup'); phutil_require_module('phabricator', 'view/form/control/markup');
phutil_require_module('phabricator', 'view/form/control/select');
phutil_require_module('phabricator', 'view/form/control/submit'); phutil_require_module('phabricator', 'view/form/control/submit');
phutil_require_module('phabricator', 'view/form/control/text'); phutil_require_module('phabricator', 'view/form/control/text');
phutil_require_module('phabricator', 'view/form/error'); phutil_require_module('phabricator', 'view/form/error');
phutil_require_module('phabricator', 'view/form/inset'); phutil_require_module('phabricator', 'view/form/inset');
phutil_require_module('phabricator', 'view/layout/panel'); phutil_require_module('phabricator', 'view/layout/panel');
phutil_require_module('phutil', 'markup');
phutil_require_module('phutil', 'utils'); phutil_require_module('phutil', 'utils');

View file

@ -18,10 +18,6 @@
final class HeraldTestConsoleController extends HeraldController { final class HeraldTestConsoleController extends HeraldController {
public function getFilter() {
return 'test';
}
public function processRequest() { public function processRequest() {
$request = $this->getRequest(); $request = $this->getRequest();
@ -136,11 +132,16 @@ final class HeraldTestConsoleController extends HeraldController {
$panel->setWidth(AphrontPanelView::WIDTH_FULL); $panel->setWidth(AphrontPanelView::WIDTH_FULL);
$panel->appendChild($form); $panel->appendChild($form);
return $this->buildStandardPageResponse( $nav = $this->renderNav();
$nav->selectFilter('test');
$nav->appendChild(
array( array(
$error_view, $error_view,
$panel, $panel,
), ));
return $this->buildStandardPageResponse(
$nav,
array( array(
'title' => 'Test Console', 'title' => 'Test Console',
'tab' => 'test', 'tab' => 'test',

View file

@ -26,10 +26,6 @@ final class HeraldTranscriptController extends HeraldController {
private $filter; private $filter;
private $handles; private $handles;
public function getFilter() {
return 'transcript';
}
public function willProcessRequest(array $data) { public function willProcessRequest(array $data) {
$this->id = $data['id']; $this->id = $data['id'];
$map = $this->getFilterMap(); $map = $this->getFilterMap();
@ -69,6 +65,15 @@ final class HeraldTranscriptController extends HeraldController {
->loadHandles(); ->loadHandles();
$this->handles = $handles; $this->handles = $handles;
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( $apply_xscript_panel = $this->buildApplyTranscriptPanel(
$xscript); $xscript);
$nav->appendChild($apply_xscript_panel); $nav->appendChild($apply_xscript_panel);
@ -82,21 +87,12 @@ final class HeraldTranscriptController extends HeraldController {
$nav->appendChild($object_xscript_panel); $nav->appendChild($object_xscript_panel);
} }
/* $main_nav = $this->renderNav();
$main_nav->selectFilter('transcript');
TODO $main_nav->appendChild($nav);
$notice = null;
if ($xscript->getDryRun()) {
$notice =
<tools:notice title="Dry Run">
This was a dry run to test Herald rules, no actions were executed.
</tools:notice>;
}
*/
return $this->buildStandardPageResponse( return $this->buildStandardPageResponse(
$nav, $main_nav,
array( array(
'title' => 'Transcript', 'title' => 'Transcript',
)); ));

View file

@ -18,10 +18,6 @@
final class HeraldTranscriptListController extends HeraldController { final class HeraldTranscriptListController extends HeraldController {
public function getFilter() {
return 'transcript';
}
public function processRequest() { public function processRequest() {
$request = $this->getRequest(); $request = $this->getRequest();
@ -41,12 +37,15 @@ final class HeraldTranscriptListController extends HeraldController {
$phid); $phid);
} }
$offset = $request->getInt('offset', 0); $pager = new AphrontPagerView();
$page_size = 100; $pager->setOffset($request->getInt('offset'));
$pager->setURI($request->getRequestURI(), 'offset');
$limit_clause = qsprintf( $limit_clause = qsprintf(
$conn_r, $conn_r,
'LIMIT %d, %d', 'LIMIT %d, %d',
$offset, $page_size + 1); $pager->getOffset(),
$pager->getPageSize() + 1);
$data = queryfx_all( $data = queryfx_all(
$conn_r, $conn_r,
@ -58,39 +57,7 @@ final class HeraldTranscriptListController extends HeraldController {
$where_clause, $where_clause,
$limit_clause); $limit_clause);
$pager = new AphrontPagerView(); $data = $pager->sliceResults($data);
$pager->getPageSize($page_size);
$pager->setHasMorePages(count($data) == $page_size + 1);
$pager->setOffset($offset);
$pager->setURI($request->getRequestURI(), 'offset');
/*
$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);
*/
// Render the table. // Render the table.
$handles = array(); $handles = array();
@ -103,8 +70,8 @@ final class HeraldTranscriptListController extends HeraldController {
$rows = array(); $rows = array();
foreach ($data as $xscript) { foreach ($data as $xscript) {
$rows[] = array( $rows[] = array(
phabricator_date($xscript['time'],$user), phabricator_date($xscript['time'], $user),
phabricator_time($xscript['time'],$user), phabricator_time($xscript['time'], $user),
$handles[$xscript['objectPHID']]->renderLink(), $handles[$xscript['objectPHID']]->renderLink(),
$xscript['dryRun'] ? 'Yes' : '', $xscript['dryRun'] ? 'Yes' : '',
number_format((int)(1000 * $xscript['duration'])).' ms', number_format((int)(1000 * $xscript['duration'])).' ms',
@ -144,8 +111,12 @@ final class HeraldTranscriptListController extends HeraldController {
$panel->appendChild($table); $panel->appendChild($table);
$panel->appendChild($pager); $panel->appendChild($pager);
$nav = $this->renderNav();
$nav->selectFilter('transcript');
$nav->appendChild($panel);
return $this->buildStandardPageResponse( return $this->buildStandardPageResponse(
$panel, $nav,
array( array(
'title' => 'Herald Transcripts', 'title' => 'Herald Transcripts',
'tab' => 'transcripts', 'tab' => 'transcripts',

View file

@ -0,0 +1,64 @@
<?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 HeraldEditLogQuery extends PhabricatorOffsetPagedQuery {
private $ruleIDs;
public function withRuleIDs(array $rule_ids) {
$this->ruleIDs = $rule_ids;
return $this;
}
public function execute() {
$table = new HeraldRuleEdit();
$conn_r = $table->establishConnection('r');
$where = $this->buildWhereClause($conn_r);
$order = $this->buildOrderClause($conn_r);
$limit = $this->buildLimitClause($conn_r);
$data = queryfx_all(
$conn_r,
'SELECT log.* FROM %T log %Q %Q %Q',
$table->getTableName(),
$where,
$order,
$limit);
return $table->loadAllFromArray($data);
}
private function buildWhereClause($conn_r) {
$where = array();
if ($this->ruleIDs) {
$where[] = qsprintf(
$conn_r,
'ruleID IN (%Ld)',
$this->ruleIDs);
}
return $this->formatWhereClause($where);
}
private function buildOrderClause($conn_r) {
return 'ORDER BY id DESC';
}
}

View file

@ -0,0 +1,15 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/herald/storage/edithistory');
phutil_require_module('phabricator', 'infrastructure/query/offsetpaged');
phutil_require_module('phabricator', 'storage/qsprintf');
phutil_require_module('phabricator', 'storage/queryfx');
phutil_require_source('HeraldEditLogQuery.php');

View file

@ -0,0 +1,90 @@
<?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 HeraldRuleQuery extends PhabricatorOffsetPagedQuery {
private $authorPHIDs;
private $ruleTypes;
private $contentTypes;
public function withAuthorPHIDs(array $author_phids) {
$this->authorPHIDs = $author_phids;
return $this;
}
public function withRuleTypes(array $types) {
$this->ruleTypes = $types;
return $this;
}
public function withContentTypes(array $types) {
$this->contentTypes = $types;
return $this;
}
public function execute() {
$table = new HeraldRule();
$conn_r = $table->establishConnection('r');
$where = $this->buildWhereClause($conn_r);
$order = $this->buildOrderClause($conn_r);
$limit = $this->buildLimitClause($conn_r);
$data = queryfx_all(
$conn_r,
'SELECT rule.* FROM %T rule %Q %Q %Q',
$table->getTableName(),
$where,
$order,
$limit);
return $table->loadAllFromArray($data);
}
private function buildWhereClause($conn_r) {
$where = array();
if ($this->authorPHIDs) {
$where[] = qsprintf(
$conn_r,
'rule.authorPHID IN (%Ls)',
$this->authorPHIDs);
}
if ($this->ruleTypes) {
$where[] = qsprintf(
$conn_r,
'rule.ruleType IN (%Ls)',
$this->ruleTypes);
}
if ($this->contentTypes) {
$where[] = qsprintf(
$conn_r,
'rule.contentType IN (%Ls)',
$this->contentTypes);
}
return $this->formatWhereClause($where);
}
private function buildOrderClause($conn_r) {
return 'ORDER BY id DESC';
}
}

View file

@ -0,0 +1,15 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/herald/storage/rule');
phutil_require_module('phabricator', 'infrastructure/query/offsetpaged');
phutil_require_module('phabricator', 'storage/qsprintf');
phutil_require_module('phabricator', 'storage/queryfx');
phutil_require_source('HeraldRuleQuery.php');

View file

@ -20,5 +20,7 @@ final class HeraldRuleEdit extends HeraldDAO {
protected $editorPHID; protected $editorPHID;
protected $ruleID; protected $ruleID;
protected $ruleName;
protected $action;
} }

View file

@ -163,23 +163,13 @@ final class HeraldRule extends HeraldDAO {
return $edits; return $edits;
} }
public function attachEdits(array $edits) { public function logEdit($editor_phid, $action) {
$this->edits = $edits; id(new HeraldRuleEdit())
return $this; ->setRuleID($this->getID())
} ->setRuleName($this->getName())
->setEditorPHID($editor_phid)
public function getEdits() { ->setAction($action)
if ($this->edits === null) { ->save();
throw new Exception("Attach edits before accessing them!");
}
return $this->edits;
}
public function saveEdit($editor_phid) {
$edit = new HeraldRuleEdit();
$edit->setRuleID($this->getID());
$edit->setEditorPHID($editor_phid);
$edit->save();
} }
public function saveConditions(array $conditions) { public function saveConditions(array $conditions) {

View file

@ -17,14 +17,19 @@
*/ */
final class HeraldRuleEditHistoryView extends AphrontView { final class HeraldRuleEditHistoryView extends AphrontView {
private $rule;
private $edits;
private $handles; private $handles;
public function setRule($rule) { public function setEdits(array $edits) {
$this->rule = $rule; $this->edits = $edits;
return $this; return $this;
} }
public function getEdits() {
return $this->edits;
}
public function setHandles(array $handles) { public function setHandles(array $handles) {
$this->handles = $handles; $this->handles = $handles;
return $this; return $this;
@ -38,36 +43,51 @@ final class HeraldRuleEditHistoryView extends AphrontView {
public function render() { public function render() {
$rows = array(); $rows = array();
foreach ($this->rule->getEdits() as $edit) { foreach ($this->edits as $edit) {
$editor = $this->handles[$edit->getEditorPHID()]->renderLink(); $name = nonempty($edit->getRuleName(), 'Unknown Rule');
$rule_name = phutil_render_tag(
'strong',
array(),
phutil_escape_html($name));
$edit_date = phabricator_datetime($edit->getDateCreated(), $this->user); switch ($edit->getAction()) {
case 'create':
// Something useful could totally go here $details = "Created rule '{$rule_name}'.";
$details = ""; break;
case 'delete':
$details = "Deleted rule '{$rule_name}'.";
break;
case 'edit':
default:
$details = "Edited rule '{$rule_name}'.";
break;
}
$rows[] = array( $rows[] = array(
$editor, $edit->getRuleID(),
$edit_date, $this->handles[$edit->getEditorPHID()]->renderLink(),
$details, $details,
phabricator_datetime($edit->getDateCreated(), $this->user),
); );
} }
$rule_name = phutil_escape_html($this->rule->getName());
$table = new AphrontTableView($rows); $table = new AphrontTableView($rows);
$table->setNoDataString( $table->setNoDataString("No edits for rule.");
"No edits for \"${rule_name}\"");
$table->setHeaders( $table->setHeaders(
array( array(
'Rule ID',
'Editor', 'Editor',
'Edit Date',
'Details', 'Details',
'Edit Date',
));
$table->setColumnClasses(
array(
'',
'',
'wide',
'',
)); ));
$panel = new AphrontPanelView(); return $table->render();
$panel->setHeader("Edit History for \"${rule_name}\"");
$panel->appendChild($table);
return $panel;
} }
} }

View file

@ -8,10 +8,10 @@
phutil_require_module('phabricator', 'view/base'); phutil_require_module('phabricator', 'view/base');
phutil_require_module('phabricator', 'view/control/table'); phutil_require_module('phabricator', 'view/control/table');
phutil_require_module('phabricator', 'view/layout/panel');
phutil_require_module('phabricator', 'view/utils'); phutil_require_module('phabricator', 'view/utils');
phutil_require_module('phutil', 'markup'); phutil_require_module('phutil', 'markup');
phutil_require_module('phutil', 'utils');
phutil_require_source('HeraldRuleEditHistoryView.php'); phutil_require_source('HeraldRuleEditHistoryView.php');

View file

@ -17,13 +17,12 @@
*/ */
final class HeraldRuleListView extends AphrontView { final class HeraldRuleListView extends AphrontView {
private $rules; private $rules;
private $handles; private $handles;
private $map;
private $view; private $showAuthor;
private $allowCreation; private $showRuleType;
private $showOwner = true;
private $showType = false;
private $user; private $user;
public function setRules(array $rules) { public function setRules(array $rules) {
@ -36,28 +35,13 @@ final class HeraldRuleListView extends AphrontView {
return $this; return $this;
} }
public function setMap($map) { public function setShowAuthor($show_author) {
$this->map = $map; $this->showAuthor = $show_author;
return $this; return $this;
} }
public function setView($view) { public function setShowRuleType($show_rule_type) {
$this->view = $view; $this->showRuleType = $show_rule_type;
return $this;
}
public function setAllowCreation($allow_creation) {
$this->allowCreation = $allow_creation;
return $this;
}
public function setShowOwner($show_owner) {
$this->showOwner = $show_owner;
return $this;
}
public function setShowType($show_type) {
$this->showType = $show_type;
return $this; return $this;
} }
@ -73,7 +57,12 @@ final class HeraldRuleListView extends AphrontView {
$rows = array(); $rows = array();
foreach ($this->rules as $rule) { foreach ($this->rules as $rule) {
$owner = $this->handles[$rule->getAuthorPHID()]->renderLink();
if ($rule->getRuleType() == HeraldRuleTypeConfig::RULE_TYPE_GLOBAL) {
$author = null;
} else {
$author = $this->handles[$rule->getAuthorPHID()]->renderLink();
}
$name = phutil_render_tag( $name = phutil_render_tag(
'a', 'a',
@ -82,21 +71,12 @@ final class HeraldRuleListView extends AphrontView {
), ),
phutil_escape_html($rule->getName())); phutil_escape_html($rule->getName()));
$last_edit_date = phabricator_datetime($rule->getDateModified(), $edit_log = phutil_render_tag(
$this->user);
$view_edits = phutil_render_tag(
'a', 'a',
array( array(
'href' => '/herald/history/' . $rule->getID() . '/', 'href' => '/herald/history/'.$rule->getID().'/',
), ),
'(View Edits)'); 'View Edit Log');
$last_edited = phutil_render_tag(
'span',
array(),
"Last edited on $last_edit_date ${view_edits}");
$delete = javelin_render_tag( $delete = javelin_render_tag(
'a', 'a',
@ -108,60 +88,42 @@ final class HeraldRuleListView extends AphrontView {
'Delete'); 'Delete');
$rows[] = array( $rows[] = array(
$this->map[$rule->getContentType()],
$type_map[$rule->getRuleType()], $type_map[$rule->getRuleType()],
$owner, $author,
$name, $name,
$last_edited, $edit_log,
$delete, $delete,
); );
} }
$rules_for = phutil_escape_html($this->map[$this->view]);
$table = new AphrontTableView($rows); $table = new AphrontTableView($rows);
$table->setNoDataString( $table->setNoDataString("No matching rules.");
"No matching subscription rules for {$rules_for}.");
$table->setHeaders( $table->setHeaders(
array( array(
'Content Type',
'Rule Type', 'Rule Type',
'Owner', 'Author',
'Rule Name', 'Rule Name',
'Last Edited', 'Edit Log',
'', '',
)); ));
$table->setColumnClasses( $table->setColumnClasses(
array( array(
'', '',
'', '',
'', 'wide pri',
'wide wrap pri',
'', '',
'action' 'action'
)); ));
$table->setColumnVisibility( $table->setColumnVisibility(
array( array(
$this->showRuleType,
$this->showAuthor,
true, true,
$this->showType,
$this->showOwner,
true, true,
true, true,
)); ));
$panel = new AphrontPanelView(); return $table->render();
$panel->setHeader("Herald Rules for {$rules_for}");
if ($this->allowCreation) {
$panel->setCreateButton(
'Create New Herald Rule',
'/herald/new/'.$this->view.'/');
}
$panel->appendChild($table);
return $panel;
} }
} }

View file

@ -10,8 +10,6 @@ phutil_require_module('phabricator', 'applications/herald/config/ruletype');
phutil_require_module('phabricator', 'infrastructure/javelin/markup'); phutil_require_module('phabricator', 'infrastructure/javelin/markup');
phutil_require_module('phabricator', 'view/base'); phutil_require_module('phabricator', 'view/base');
phutil_require_module('phabricator', 'view/control/table'); phutil_require_module('phabricator', 'view/control/table');
phutil_require_module('phabricator', 'view/layout/panel');
phutil_require_module('phabricator', 'view/utils');
phutil_require_module('phutil', 'markup'); phutil_require_module('phutil', 'markup');

View file

@ -24,6 +24,11 @@ final class PhabricatorObjectHandleData {
$this->phids = array_unique($phids); $this->phids = array_unique($phids);
} }
public static function loadOneHandle($phid) {
$handles = id(new PhabricatorObjectHandleData(array($phid)))->loadHandles();
return $handles[$phid];
}
public function loadObjects() { public function loadObjects() {
$types = array(); $types = array();
foreach ($this->phids as $phid) { foreach ($this->phids as $phid) {

View file

@ -237,7 +237,7 @@ final class AphrontTableView extends AphrontView {
++$row_num; ++$row_num;
} }
} else { } else {
$colspan = max(count($headers), 1); $colspan = max(count(array_filter($visibility)), 1);
$table[] = $table[] =
'<tr class="no-data"><td colspan="'.$colspan.'">'. '<tr class="no-data"><td colspan="'.$colspan.'">'.
coalesce($this->noDataString, 'No data available.'). coalesce($this->noDataString, 'No data available.').

View file

@ -78,7 +78,7 @@ final class AphrontSideNavFilterView extends AphrontView {
return $this->baseURI; return $this->baseURI;
} }
public function selectFilter($key, $default) { public function selectFilter($key, $default = null) {
$this->selectedFilter = $default; $this->selectedFilter = $default;
if ($key !== null) { if ($key !== null) {
foreach ($this->items as $item) { foreach ($this->items as $item) {

View file

@ -29,7 +29,3 @@
.herald-action-table td.target { .herald-action-table td.target {
width: 100%; width: 100%;
} }
#herald-rule-edit-form #author-input {
width: 300px;
}

View file

@ -65,7 +65,6 @@ JX.install('HeraldRuleEditor', {
this._renderConditions(conditions); this._renderConditions(conditions);
this._renderActions(actions); this._renderActions(actions);
this._renderAuthorInput();
}, },
members : { members : {
@ -120,12 +119,6 @@ JX.install('HeraldRuleEditor', {
conditions: this._config.conditions, conditions: this._config.conditions,
actions: this._config.actions actions: this._config.actions
}); });
var authorInput = JX.DOM.find(this._root, 'input', 'author');
var authorID = JX.keys(this._config.authorGetter())[0];
if (authorID) {
authorInput.value = authorID;
}
}, },
_getConditionValue : function(id) { _getConditionValue : function(id) {
@ -243,22 +236,6 @@ JX.install('HeraldRuleEditor', {
return [input, get_fn, set_fn]; return [input, get_fn, set_fn];
}, },
_renderAuthorInput : function() {
var tokenizer = this._newTokenizer('email', 1);
input = tokenizer[0];
set_fn = tokenizer[2];
set_fn(this._config.author);
this._config.authorGetter = tokenizer[1];
try {
var author_cell = JX.$('author-input');
JX.DOM.setContent(author_cell, input);
} catch (ex) {
// TODO: Get rid of the ownership changing stuff or something?
// On global rules, there's no "author" input. Not fixing this
// properly because I think I'm going to nuke it soon.
}
},
_renderValueInputForRow : function(row_id) { _renderValueInputForRow : function(row_id) {
var cond = this._config.conditions[row_id]; var cond = this._config.conditions[row_id];
var type = this._config.info.values[cond[0]][cond[1]]; var type = this._config.info.values[cond[0]][cond[1]];