mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-26 00:32:42 +01:00
Build an event dispatch mechanism into Phabricator
Summary: This is an attempt to satisfy a lot of the one-off requests a little more generally, by providing a relatively generic piece of event architecture. Allow the registation of event listeners which can react to various application events (currently, task editing). I'll doc this a bit better but I wanted to see if anyone had massive objections to doing this or the broad approach. The specific problem I want to address is that one client wants to do a bunch of routing for tasks via email, so it's either build a hook, or have them override most of ManiphestReplyHandler, or something slightly more general like this. Test Plan: Wrote a silly listener that adds "Quack!" to a task every time it is edited and edited some tasks. I was justly rewarded. Reviewers: nh, jungejason, tuomaspelkonen, aran Reviewed By: aran CC: aran, epriestley Differential Revision: 881
This commit is contained in:
parent
8e8d91a1ff
commit
522e5b4779
23 changed files with 405 additions and 2 deletions
|
@ -602,7 +602,6 @@ return array(
|
|||
// projects that want to expose an activity feed on the project homepage.
|
||||
'feed.public' => false,
|
||||
|
||||
|
||||
// -- Customization --------------------------------------------------------- //
|
||||
|
||||
// Paths to additional phutil libraries to load.
|
||||
|
@ -636,7 +635,13 @@ return array(
|
|||
// settings are the defaults.)
|
||||
'celerity.force-disk-reads' => false,
|
||||
|
||||
// -- Pygments ------------------------------------------------------------ //
|
||||
// You can respond to various application events by installing listeners,
|
||||
// which will receive callbacks when interesting things occur. Specify a list
|
||||
// of classes which extend PhabricatorEventListener here.
|
||||
'events.listeners' => array(),
|
||||
|
||||
// -- Pygments -------------------------------------------------------------- //
|
||||
|
||||
// Phabricator can highlight PHP by default, but if you want syntax
|
||||
// highlighting for other languages you should install the python package
|
||||
// 'Pygments', make sure the 'pygmentize' script is available in the
|
||||
|
|
|
@ -33,3 +33,4 @@ phutil_load_library(dirname(__FILE__).'/../src/');
|
|||
// NOTE: This is dangerous in general, but we know we're in a script context and
|
||||
// are not vulnerable to CSRF.
|
||||
AphrontWriteGuard::allowDangerousUnguardedWrites(true);
|
||||
PhabricatorEventEngine::initialize();
|
||||
|
|
|
@ -412,6 +412,11 @@ phutil_register_library_map(array(
|
|||
'PhabricatorEmailLoginController' => 'applications/auth/controller/email',
|
||||
'PhabricatorEmailTokenController' => 'applications/auth/controller/emailtoken',
|
||||
'PhabricatorEnv' => 'infrastructure/env',
|
||||
'PhabricatorEvent' => 'infrastructure/events/event',
|
||||
'PhabricatorEventConstants' => 'infrastructure/events/constant/base',
|
||||
'PhabricatorEventEngine' => 'infrastructure/events/engine',
|
||||
'PhabricatorEventListener' => 'infrastructure/events/listener',
|
||||
'PhabricatorEventType' => 'infrastructure/events/constant/type',
|
||||
'PhabricatorFeedConstants' => 'applications/feed/constants/base',
|
||||
'PhabricatorFeedController' => 'applications/feed/controller/base',
|
||||
'PhabricatorFeedDAO' => 'applications/feed/storage/base',
|
||||
|
@ -1050,6 +1055,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorDraftDAO' => 'PhabricatorLiskDAO',
|
||||
'PhabricatorEmailLoginController' => 'PhabricatorAuthController',
|
||||
'PhabricatorEmailTokenController' => 'PhabricatorAuthController',
|
||||
'PhabricatorEventType' => 'PhabricatorEventConstants',
|
||||
'PhabricatorFeedController' => 'PhabricatorController',
|
||||
'PhabricatorFeedDAO' => 'PhabricatorLiskDAO',
|
||||
'PhabricatorFeedPublicStreamController' => 'PhabricatorFeedController',
|
||||
|
|
|
@ -103,6 +103,20 @@ final class ConduitAPI_maniphest_createtask_Method
|
|||
$transactions[] = $transaction;
|
||||
}
|
||||
|
||||
$event = new PhabricatorEvent(
|
||||
PhabricatorEventType::TYPE_MANIPHEST_WILLEDITTASK,
|
||||
array(
|
||||
'task' => $task,
|
||||
'new' => true,
|
||||
'transactions' => $transactions,
|
||||
));
|
||||
$event->setUser($request->getUser());
|
||||
$event->setConduitRequest($request);
|
||||
PhabricatorEventEngine::dispatchEvent($event);
|
||||
|
||||
$task = $event->getValue('task');
|
||||
$transactions = $event->getValue('transactions');
|
||||
|
||||
$editor = new ManiphestTransactionEditor();
|
||||
$editor->applyTransactions($task, $transactions);
|
||||
|
||||
|
|
|
@ -15,6 +15,9 @@ phutil_require_module('phabricator', 'applications/maniphest/storage/task');
|
|||
phutil_require_module('phabricator', 'applications/maniphest/storage/transaction');
|
||||
phutil_require_module('phabricator', 'applications/metamta/contentsource/source');
|
||||
phutil_require_module('phabricator', 'applications/phid/constants');
|
||||
phutil_require_module('phabricator', 'infrastructure/events/constant/type');
|
||||
phutil_require_module('phabricator', 'infrastructure/events/engine');
|
||||
phutil_require_module('phabricator', 'infrastructure/events/event');
|
||||
|
||||
|
||||
phutil_require_source('ConduitAPI_maniphest_createtask_Method.php');
|
||||
|
|
|
@ -201,6 +201,21 @@ class ManiphestTaskEditController extends ManiphestController {
|
|||
}
|
||||
|
||||
if ($transactions) {
|
||||
|
||||
$event = new PhabricatorEvent(
|
||||
PhabricatorEventType::TYPE_MANIPHEST_WILLEDITTASK,
|
||||
array(
|
||||
'task' => $task,
|
||||
'new' => !$task->getID(),
|
||||
'transactions' => $transactions,
|
||||
));
|
||||
$event->setUser($user);
|
||||
$event->setAphrontRequest($request);
|
||||
PhabricatorEventEngine::dispatchEvent($event);
|
||||
|
||||
$task = $event->getValue('task');
|
||||
$transactions = $event->getValue('transactions');
|
||||
|
||||
$editor = new ManiphestTransactionEditor();
|
||||
$editor->applyTransactions($task, $transactions);
|
||||
}
|
||||
|
|
|
@ -22,6 +22,9 @@ phutil_require_module('phabricator', 'applications/phid/constants');
|
|||
phutil_require_module('phabricator', 'applications/phid/handle/data');
|
||||
phutil_require_module('phabricator', 'infrastructure/celerity/api');
|
||||
phutil_require_module('phabricator', 'infrastructure/env');
|
||||
phutil_require_module('phabricator', 'infrastructure/events/constant/type');
|
||||
phutil_require_module('phabricator', 'infrastructure/events/engine');
|
||||
phutil_require_module('phabricator', 'infrastructure/events/event');
|
||||
phutil_require_module('phabricator', 'infrastructure/javelin/api');
|
||||
phutil_require_module('phabricator', 'infrastructure/javelin/markup');
|
||||
phutil_require_module('phabricator', 'view/form/base');
|
||||
|
|
|
@ -229,6 +229,20 @@ class ManiphestTransactionSaveController extends ManiphestController {
|
|||
$transaction->setContentSource($content_source);
|
||||
}
|
||||
|
||||
$event = new PhabricatorEvent(
|
||||
PhabricatorEventType::TYPE_MANIPHEST_WILLEDITTASK,
|
||||
array(
|
||||
'task' => $task,
|
||||
'new' => false,
|
||||
'transactions' => $transactions,
|
||||
));
|
||||
$event->setUser($user);
|
||||
$event->setAphrontRequest($request);
|
||||
PhabricatorEventEngine::dispatchEvent($event);
|
||||
|
||||
$task = $event->getValue('task');
|
||||
$transactions = $event->getValue('transactions');
|
||||
|
||||
$editor = new ManiphestTransactionEditor();
|
||||
$editor->applyTransactions($task, $transactions);
|
||||
|
||||
|
|
|
@ -19,6 +19,9 @@ phutil_require_module('phabricator', 'applications/maniphest/storage/transaction
|
|||
phutil_require_module('phabricator', 'applications/markup/engine');
|
||||
phutil_require_module('phabricator', 'applications/metamta/contentsource/source');
|
||||
phutil_require_module('phabricator', 'applications/phid/constants');
|
||||
phutil_require_module('phabricator', 'infrastructure/events/constant/type');
|
||||
phutil_require_module('phabricator', 'infrastructure/events/engine');
|
||||
phutil_require_module('phabricator', 'infrastructure/events/event');
|
||||
|
||||
phutil_require_module('phutil', 'utils');
|
||||
|
||||
|
|
|
@ -150,6 +150,21 @@ class ManiphestReplyHandler extends PhabricatorMailReplyHandler {
|
|||
$xactions[] = $file_xaction;
|
||||
}
|
||||
|
||||
$event = new PhabricatorEvent(
|
||||
PhabricatorEventType::TYPE_MANIPHEST_WILLEDITTASK,
|
||||
array(
|
||||
'task' => $task,
|
||||
'mail' => $mail,
|
||||
'new' => $is_new_task,
|
||||
'transactions' => $xactions,
|
||||
));
|
||||
$event->setUser($user);
|
||||
PhabricatorEventEngine::dispatchEvent($event);
|
||||
|
||||
$task = $event->getValue('task');
|
||||
$xactions = $event->getValue('transactions');
|
||||
|
||||
|
||||
$editor = new ManiphestTransactionEditor();
|
||||
$editor->setParentMessageID($mail->getMessageID());
|
||||
$editor->applyTransactions($task, $xactions);
|
||||
|
|
|
@ -14,6 +14,9 @@ phutil_require_module('phabricator', 'applications/metamta/contentsource/source'
|
|||
phutil_require_module('phabricator', 'applications/metamta/replyhandler/base');
|
||||
phutil_require_module('phabricator', 'applications/phid/constants');
|
||||
phutil_require_module('phabricator', 'infrastructure/env');
|
||||
phutil_require_module('phabricator', 'infrastructure/events/constant/type');
|
||||
phutil_require_module('phabricator', 'infrastructure/events/engine');
|
||||
phutil_require_module('phabricator', 'infrastructure/events/event');
|
||||
|
||||
phutil_require_module('phutil', 'utils');
|
||||
|
||||
|
|
27
src/docs/userguide/events.diviner
Normal file
27
src/docs/userguide/events.diviner
Normal file
|
@ -0,0 +1,27 @@
|
|||
@title Events User Guide: Installing Event Listeners
|
||||
@group userguide
|
||||
|
||||
Using Phabricator event listeners to customize behavior.
|
||||
|
||||
= Overview =
|
||||
|
||||
Phabricator allows you to install custom runtime event listeners which can react
|
||||
to certain things happening (like a Maniphest Task being edited) and run custom
|
||||
code to perform logging, synchronize with other systems, or modify workflows.
|
||||
|
||||
NOTE: This feature is new and experimental, so few events are available and
|
||||
things might not be completely stable.
|
||||
|
||||
= Available Events =
|
||||
|
||||
== PhabricatorEventType::TYPE_MANIPHEST_WILLEDITTASK ==
|
||||
|
||||
This event is dispatched before a task is edited, and allows you to respond to
|
||||
or alter the edit. Data available on this event:
|
||||
|
||||
- ##task## The {@class:ManiphestTask} being edited.
|
||||
- ##transactions## The list of edits (objects of class
|
||||
@{class:ManiphestTransaction}) being applied.
|
||||
- ##new## A boolean indicating if this task is being created.
|
||||
- ##mail## If this edit originates from email, the
|
||||
@{class:PhabricatorMetaMTAReceivedMail} object.
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2011 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.
|
||||
*/
|
||||
|
||||
abstract class PhabricatorEventConstants {
|
||||
|
||||
}
|
10
src/infrastructure/events/constant/base/__init__.php
Normal file
10
src/infrastructure/events/constant/base/__init__.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
phutil_require_source('PhabricatorEventConstants.php');
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2011 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 PhabricatorEventType extends PhabricatorEventConstants {
|
||||
|
||||
const TYPE_MANIPHEST_WILLEDITTASK = 'maniphest.willEditTask';
|
||||
|
||||
}
|
12
src/infrastructure/events/constant/type/__init__.php
Normal file
12
src/infrastructure/events/constant/type/__init__.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
phutil_require_module('phabricator', 'infrastructure/events/constant/base');
|
||||
|
||||
|
||||
phutil_require_source('PhabricatorEventType.php');
|
68
src/infrastructure/events/engine/PhabricatorEventEngine.php
Normal file
68
src/infrastructure/events/engine/PhabricatorEventEngine.php
Normal file
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2011 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.
|
||||
*/
|
||||
|
||||
class PhabricatorEventEngine {
|
||||
|
||||
private static $instance;
|
||||
|
||||
private $listeners = array();
|
||||
|
||||
private function __construct() {
|
||||
// <empty>
|
||||
}
|
||||
|
||||
public static function initialize() {
|
||||
self::$instance = new PhabricatorEventEngine();
|
||||
|
||||
// Instantiate and register custom event listeners so they can react to
|
||||
// events.
|
||||
$listeners = PhabricatorEnv::getEnvConfig('events.listeners');
|
||||
foreach ($listeners as $listener) {
|
||||
id(new $listener())->register();
|
||||
}
|
||||
}
|
||||
|
||||
public static function getInstance() {
|
||||
if (!self::$instance) {
|
||||
throw new Exception("Event engine has not been initialized!");
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function addListener(
|
||||
PhabricatorEventListener $listener,
|
||||
$type) {
|
||||
$this->listeners[$type][] = $listener;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public static function dispatchEvent(PhabricatorEvent $event) {
|
||||
$instance = self::getInstance();
|
||||
|
||||
$listeners = idx($instance->listeners, $event->getType(), array());
|
||||
foreach ($listeners as $listener) {
|
||||
if ($event->isStopped()) {
|
||||
// Do this first so if someone tries to dispatch a stopped event it
|
||||
// doesn't go anywhere. Silly but less surprising.
|
||||
break;
|
||||
}
|
||||
$listener->handleEvent($event);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
14
src/infrastructure/events/engine/__init__.php
Normal file
14
src/infrastructure/events/engine/__init__.php
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
phutil_require_module('phabricator', 'infrastructure/env');
|
||||
|
||||
phutil_require_module('phutil', 'utils');
|
||||
|
||||
|
||||
phutil_require_source('PhabricatorEventEngine.php');
|
88
src/infrastructure/events/event/PhabricatorEvent.php
Normal file
88
src/infrastructure/events/event/PhabricatorEvent.php
Normal file
|
@ -0,0 +1,88 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2011 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 PhabricatorEvent {
|
||||
|
||||
private $user;
|
||||
private $aphrontRequest;
|
||||
private $conduitRequest;
|
||||
|
||||
private $type;
|
||||
private $data;
|
||||
private $stop = false;
|
||||
|
||||
public function __construct($type, array $data = array()) {
|
||||
$this->type = $type;
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public function setUser(PhabricatorUser $user) {
|
||||
$this->user = $user;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUser() {
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
public function setAphrontRequest(AphrontRequest $aphront_request) {
|
||||
$this->aphrontRequest = $aphront_request;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAphrontRequest() {
|
||||
return $this->aphrontRequest;
|
||||
}
|
||||
|
||||
public function setConduitRequest(ConduitRequest $conduit_request) {
|
||||
$this->conduitRequest = $conduit_request;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getConduitRequest() {
|
||||
return $this->conduitRequest;
|
||||
}
|
||||
|
||||
public function getType() {
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function getValue($key, $default = null) {
|
||||
return idx($this->data, $key, $default);
|
||||
}
|
||||
|
||||
public function setValue($key, $value) {
|
||||
$this->data[$key] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function stop() {
|
||||
$this->stop = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isStopped() {
|
||||
return $this->stop;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
12
src/infrastructure/events/event/__init__.php
Normal file
12
src/infrastructure/events/event/__init__.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
phutil_require_module('phutil', 'utils');
|
||||
|
||||
|
||||
phutil_require_source('PhabricatorEvent.php');
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2011 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.
|
||||
*/
|
||||
|
||||
abstract class PhabricatorEventListener {
|
||||
|
||||
final public function __construct() {
|
||||
// <empty>
|
||||
}
|
||||
|
||||
abstract public function register();
|
||||
abstract public function handleEvent(PhabricatorEvent $event);
|
||||
|
||||
final public function listen($type) {
|
||||
$engine = PhabricatorEventEngine::getInstance();
|
||||
$engine->addListener($this, $type);
|
||||
}
|
||||
|
||||
}
|
12
src/infrastructure/events/listener/__init__.php
Normal file
12
src/infrastructure/events/listener/__init__.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
phutil_require_module('phabricator', 'infrastructure/events/engine');
|
||||
|
||||
|
||||
phutil_require_source('PhabricatorEventListener.php');
|
|
@ -118,6 +118,7 @@ $application->willBuildRequest();
|
|||
$request = $application->buildRequest();
|
||||
|
||||
$write_guard = new AphrontWriteGuard($request);
|
||||
PhabricatorEventEngine::initialize();
|
||||
|
||||
$application->setRequest($request);
|
||||
list($controller, $uri_data) = $application->buildController();
|
||||
|
|
Loading…
Reference in a new issue