mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-09 16:32:39 +01:00
Add skeleton code for webhooks
Summary: Ref T11330. Adds general support for webhooks. This is still rough and missing a lot of pieces -- and not yet useful for anything -- but can make HTTP requests. Test Plan: Used `bin/webhook call ...` to complete requests to a test endpoint. Maniphest Tasks: T11330 Differential Revision: https://secure.phabricator.com/D19045
This commit is contained in:
parent
9386e436fe
commit
0470125d9e
34 changed files with 1896 additions and 14 deletions
1
bin/webhook
Symbolic link
1
bin/webhook
Symbolic link
|
@ -0,0 +1 @@
|
|||
../scripts/setup/manage_webhook.php
|
12
resources/sql/autopatches/20180209.hook.01.hook.sql
Normal file
12
resources/sql/autopatches/20180209.hook.01.hook.sql
Normal file
|
@ -0,0 +1,12 @@
|
|||
CREATE TABLE {$NAMESPACE}_herald.herald_webhook (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
phid VARBINARY(64) NOT NULL,
|
||||
name VARCHAR(128) NOT NULL COLLATE {$COLLATE_TEXT},
|
||||
webhookURI VARCHAR(255) NOT NULL COLLATE {$COLLATE_TEXT},
|
||||
viewPolicy VARBINARY(64) NOT NULL,
|
||||
editPolicy VARBINARY(64) NOT NULL,
|
||||
status VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT},
|
||||
hmacKey VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT},
|
||||
dateCreated INT UNSIGNED NOT NULL,
|
||||
dateModified INT UNSIGNED NOT NULL
|
||||
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
|
19
resources/sql/autopatches/20180209.hook.02.hookxaction.sql
Normal file
19
resources/sql/autopatches/20180209.hook.02.hookxaction.sql
Normal file
|
@ -0,0 +1,19 @@
|
|||
CREATE TABLE {$NAMESPACE}_herald.herald_webhooktransaction (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
phid VARBINARY(64) NOT NULL,
|
||||
authorPHID VARBINARY(64) NOT NULL,
|
||||
objectPHID VARBINARY(64) NOT NULL,
|
||||
viewPolicy VARBINARY(64) NOT NULL,
|
||||
editPolicy VARBINARY(64) NOT NULL,
|
||||
commentPHID VARBINARY(64) DEFAULT NULL,
|
||||
commentVersion INT UNSIGNED NOT NULL,
|
||||
transactionType VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||
oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||
newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||
contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||
metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||
dateCreated INT UNSIGNED NOT NULL,
|
||||
dateModified INT UNSIGNED NOT NULL,
|
||||
UNIQUE KEY `key_phid` (`phid`),
|
||||
KEY `key_object` (`objectPHID`)
|
||||
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
|
12
resources/sql/autopatches/20180209.hook.03.hookrequest.sql
Normal file
12
resources/sql/autopatches/20180209.hook.03.hookrequest.sql
Normal file
|
@ -0,0 +1,12 @@
|
|||
CREATE TABLE {$NAMESPACE}_herald.herald_webhookrequest (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
phid VARBINARY(64) NOT NULL,
|
||||
webhookPHID VARBINARY(64) NOT NULL,
|
||||
objectPHID VARBINARY(64) NOT NULL,
|
||||
status VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT},
|
||||
properties LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT},
|
||||
lastRequestResult VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT},
|
||||
lastRequestEpoch INT UNSIGNED NOT NULL,
|
||||
dateCreated INT UNSIGNED NOT NULL,
|
||||
dateModified INT UNSIGNED NOT NULL
|
||||
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
|
21
scripts/setup/manage_webhook.php
Executable file
21
scripts/setup/manage_webhook.php
Executable file
|
@ -0,0 +1,21 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
$root = dirname(dirname(dirname(__FILE__)));
|
||||
require_once $root.'/scripts/init/init-script.php';
|
||||
|
||||
$args = new PhutilArgumentParser($argv);
|
||||
$args->setTagline(pht('manage webhooks'));
|
||||
$args->setSynopsis(<<<EOSYNOPSIS
|
||||
**webhook** __command__ [__options__]
|
||||
Manage webhooks.
|
||||
|
||||
EOSYNOPSIS
|
||||
);
|
||||
$args->parseStandardArguments();
|
||||
|
||||
$workflows = id(new PhutilClassMapQuery())
|
||||
->setAncestorClass('HeraldWebhookManagementWorkflow')
|
||||
->execute();
|
||||
$workflows[] = new PhutilHelpArgumentWorkflow();
|
||||
$args->parseWorkflows($workflows);
|
|
@ -1364,6 +1364,7 @@ phutil_register_library_map(array(
|
|||
'HeraldContentSourceField' => 'applications/herald/field/HeraldContentSourceField.php',
|
||||
'HeraldController' => 'applications/herald/controller/HeraldController.php',
|
||||
'HeraldCoreStateReasons' => 'applications/herald/state/HeraldCoreStateReasons.php',
|
||||
'HeraldCreateWebhooksCapability' => 'applications/herald/capability/HeraldCreateWebhooksCapability.php',
|
||||
'HeraldDAO' => 'applications/herald/storage/HeraldDAO.php',
|
||||
'HeraldDeprecatedFieldGroup' => 'applications/herald/field/HeraldDeprecatedFieldGroup.php',
|
||||
'HeraldDifferentialAdapter' => 'applications/differential/herald/HeraldDifferentialAdapter.php',
|
||||
|
@ -1440,6 +1441,30 @@ phutil_register_library_map(array(
|
|||
'HeraldTranscriptSearchEngine' => 'applications/herald/query/HeraldTranscriptSearchEngine.php',
|
||||
'HeraldTranscriptTestCase' => 'applications/herald/storage/__tests__/HeraldTranscriptTestCase.php',
|
||||
'HeraldUtilityActionGroup' => 'applications/herald/action/HeraldUtilityActionGroup.php',
|
||||
'HeraldWebhook' => 'applications/herald/storage/HeraldWebhook.php',
|
||||
'HeraldWebhookCallManagementWorkflow' => 'applications/herald/management/HeraldWebhookCallManagementWorkflow.php',
|
||||
'HeraldWebhookController' => 'applications/herald/controller/HeraldWebhookController.php',
|
||||
'HeraldWebhookEditController' => 'applications/herald/controller/HeraldWebhookEditController.php',
|
||||
'HeraldWebhookEditEngine' => 'applications/herald/editor/HeraldWebhookEditEngine.php',
|
||||
'HeraldWebhookEditor' => 'applications/herald/editor/HeraldWebhookEditor.php',
|
||||
'HeraldWebhookListController' => 'applications/herald/controller/HeraldWebhookListController.php',
|
||||
'HeraldWebhookManagementWorkflow' => 'applications/herald/management/HeraldWebhookManagementWorkflow.php',
|
||||
'HeraldWebhookNameTransaction' => 'applications/herald/xaction/HeraldWebhookNameTransaction.php',
|
||||
'HeraldWebhookPHIDType' => 'applications/herald/phid/HeraldWebhookPHIDType.php',
|
||||
'HeraldWebhookQuery' => 'applications/herald/query/HeraldWebhookQuery.php',
|
||||
'HeraldWebhookRequest' => 'applications/herald/storage/HeraldWebhookRequest.php',
|
||||
'HeraldWebhookRequestListView' => 'applications/herald/view/HeraldWebhookRequestListView.php',
|
||||
'HeraldWebhookRequestPHIDType' => 'applications/herald/phid/HeraldWebhookRequestPHIDType.php',
|
||||
'HeraldWebhookRequestQuery' => 'applications/herald/query/HeraldWebhookRequestQuery.php',
|
||||
'HeraldWebhookSearchEngine' => 'applications/herald/query/HeraldWebhookSearchEngine.php',
|
||||
'HeraldWebhookStatusTransaction' => 'applications/herald/xaction/HeraldWebhookStatusTransaction.php',
|
||||
'HeraldWebhookTestController' => 'applications/herald/controller/HeraldWebhookTestController.php',
|
||||
'HeraldWebhookTransaction' => 'applications/herald/storage/HeraldWebhookTransaction.php',
|
||||
'HeraldWebhookTransactionQuery' => 'applications/herald/query/HeraldWebhookTransactionQuery.php',
|
||||
'HeraldWebhookTransactionType' => 'applications/herald/xaction/HeraldWebhookTransactionType.php',
|
||||
'HeraldWebhookURITransaction' => 'applications/herald/xaction/HeraldWebhookURITransaction.php',
|
||||
'HeraldWebhookViewController' => 'applications/herald/controller/HeraldWebhookViewController.php',
|
||||
'HeraldWebhookWorker' => 'applications/herald/worker/HeraldWebhookWorker.php',
|
||||
'Javelin' => 'infrastructure/javelin/Javelin.php',
|
||||
'LegalpadController' => 'applications/legalpad/controller/LegalpadController.php',
|
||||
'LegalpadCreateDocumentsCapability' => 'applications/legalpad/capability/LegalpadCreateDocumentsCapability.php',
|
||||
|
@ -6614,6 +6639,7 @@ phutil_register_library_map(array(
|
|||
'HeraldContentSourceField' => 'HeraldField',
|
||||
'HeraldController' => 'PhabricatorController',
|
||||
'HeraldCoreStateReasons' => 'HeraldStateReasons',
|
||||
'HeraldCreateWebhooksCapability' => 'PhabricatorPolicyCapability',
|
||||
'HeraldDAO' => 'PhabricatorLiskDAO',
|
||||
'HeraldDeprecatedFieldGroup' => 'HeraldFieldGroup',
|
||||
'HeraldDifferentialAdapter' => 'HeraldAdapter',
|
||||
|
@ -6704,6 +6730,39 @@ phutil_register_library_map(array(
|
|||
'HeraldTranscriptSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||
'HeraldTranscriptTestCase' => 'PhabricatorTestCase',
|
||||
'HeraldUtilityActionGroup' => 'HeraldActionGroup',
|
||||
'HeraldWebhook' => array(
|
||||
'HeraldDAO',
|
||||
'PhabricatorPolicyInterface',
|
||||
'PhabricatorApplicationTransactionInterface',
|
||||
'PhabricatorDestructibleInterface',
|
||||
),
|
||||
'HeraldWebhookCallManagementWorkflow' => 'HeraldWebhookManagementWorkflow',
|
||||
'HeraldWebhookController' => 'HeraldController',
|
||||
'HeraldWebhookEditController' => 'HeraldWebhookController',
|
||||
'HeraldWebhookEditEngine' => 'PhabricatorEditEngine',
|
||||
'HeraldWebhookEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||
'HeraldWebhookListController' => 'HeraldWebhookController',
|
||||
'HeraldWebhookManagementWorkflow' => 'PhabricatorManagementWorkflow',
|
||||
'HeraldWebhookNameTransaction' => 'HeraldWebhookTransactionType',
|
||||
'HeraldWebhookPHIDType' => 'PhabricatorPHIDType',
|
||||
'HeraldWebhookQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'HeraldWebhookRequest' => array(
|
||||
'HeraldDAO',
|
||||
'PhabricatorPolicyInterface',
|
||||
'PhabricatorExtendedPolicyInterface',
|
||||
),
|
||||
'HeraldWebhookRequestListView' => 'AphrontView',
|
||||
'HeraldWebhookRequestPHIDType' => 'PhabricatorPHIDType',
|
||||
'HeraldWebhookRequestQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'HeraldWebhookSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||
'HeraldWebhookStatusTransaction' => 'HeraldWebhookTransactionType',
|
||||
'HeraldWebhookTestController' => 'HeraldWebhookController',
|
||||
'HeraldWebhookTransaction' => 'PhabricatorModularTransaction',
|
||||
'HeraldWebhookTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||
'HeraldWebhookTransactionType' => 'PhabricatorModularTransactionType',
|
||||
'HeraldWebhookURITransaction' => 'HeraldWebhookTransactionType',
|
||||
'HeraldWebhookViewController' => 'HeraldWebhookController',
|
||||
'HeraldWebhookWorker' => 'PhabricatorWorker',
|
||||
'Javelin' => 'Phobject',
|
||||
'LegalpadController' => 'PhabricatorController',
|
||||
'LegalpadCreateDocumentsCapability' => 'PhabricatorPolicyCapability',
|
||||
|
|
|
@ -62,6 +62,17 @@ final class PhabricatorHeraldApplication extends PhabricatorApplication {
|
|||
'(?P<id>[1-9]\d*)/'
|
||||
=> 'HeraldTranscriptController',
|
||||
),
|
||||
'webhook/' => array(
|
||||
$this->getQueryRoutePattern() => 'HeraldWebhookListController',
|
||||
'view/(?P<id>\d+)/(?:request/(?P<requestID>[^/]+)/)?' =>
|
||||
'HeraldWebhookViewController',
|
||||
$this->getEditRoutePattern('edit/') => 'HeraldWebhookEditController',
|
||||
'test/(?P<id>\d+)/' => 'HeraldWebhookTestController',
|
||||
'key/' => array(
|
||||
'view/(?P<id>\d+)/' => 'HeraldWebhookViewKeyController',
|
||||
'cycle/(?P<id>\d+)/' => 'HeraldWebhookCycleKeyController',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -72,6 +83,9 @@ final class PhabricatorHeraldApplication extends PhabricatorApplication {
|
|||
'caption' => pht('Global rules can bypass access controls.'),
|
||||
'default' => PhabricatorPolicies::POLICY_ADMIN,
|
||||
),
|
||||
HeraldCreateWebhooksCapability::CAPABILITY => array(
|
||||
'default' => PhabricatorPolicies::POLICY_ADMIN,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
final class HeraldCreateWebhooksCapability
|
||||
extends PhabricatorPolicyCapability {
|
||||
|
||||
const CAPABILITY = 'herald.webhooks';
|
||||
|
||||
public function getCapabilityName() {
|
||||
return pht('Can Create Webhooks');
|
||||
}
|
||||
|
||||
public function describeCapabilityRejection() {
|
||||
return pht('You do not have permission to create webhooks.');
|
||||
}
|
||||
|
||||
}
|
|
@ -6,18 +6,6 @@ abstract class HeraldController extends PhabricatorController {
|
|||
return $this->buildSideNavView()->getMenu();
|
||||
}
|
||||
|
||||
protected function buildApplicationCrumbs() {
|
||||
$crumbs = parent::buildApplicationCrumbs();
|
||||
|
||||
$crumbs->addAction(
|
||||
id(new PHUIListItemView())
|
||||
->setName(pht('Create Herald Rule'))
|
||||
->setHref($this->getApplicationURI('create/'))
|
||||
->setIcon('fa-plus-square'));
|
||||
|
||||
return $crumbs;
|
||||
}
|
||||
|
||||
public function buildSideNavView() {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
|
@ -32,6 +20,9 @@ abstract class HeraldController extends PhabricatorController {
|
|||
->addFilter('test', pht('Test Console'))
|
||||
->addFilter('transcript', pht('Transcripts'));
|
||||
|
||||
$nav->addLabel(pht('Webhooks'))
|
||||
->addFilter('webhook', pht('Webhooks'));
|
||||
|
||||
$nav->selectFilter(null);
|
||||
|
||||
return $nav;
|
||||
|
|
|
@ -17,5 +17,16 @@ final class HeraldRuleListController extends HeraldController {
|
|||
return $this->delegateToController($controller);
|
||||
}
|
||||
|
||||
protected function buildApplicationCrumbs() {
|
||||
$crumbs = parent::buildApplicationCrumbs();
|
||||
|
||||
$crumbs->addAction(
|
||||
id(new PHUIListItemView())
|
||||
->setName(pht('Create Herald Rule'))
|
||||
->setHref($this->getApplicationURI('create/'))
|
||||
->setIcon('fa-plus-square'));
|
||||
|
||||
return $crumbs;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
abstract class HeraldWebhookController extends HeraldController {
|
||||
|
||||
protected function buildApplicationCrumbs() {
|
||||
$crumbs = parent::buildApplicationCrumbs();
|
||||
|
||||
$crumbs->addTextCrumb(
|
||||
pht('Webhooks'),
|
||||
$this->getApplicationURI('webhook/'));
|
||||
|
||||
return $crumbs;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
final class HeraldWebhookEditController
|
||||
extends HeraldWebhookController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
return id(new HeraldWebhookEditEngine())
|
||||
->setController($this)
|
||||
->buildResponse();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
final class HeraldWebhookListController
|
||||
extends HeraldWebhookController {
|
||||
|
||||
public function shouldAllowPublic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
return id(new HeraldWebhookSearchEngine())
|
||||
->setController($this)
|
||||
->buildResponse();
|
||||
}
|
||||
|
||||
protected function buildApplicationCrumbs() {
|
||||
$crumbs = parent::buildApplicationCrumbs();
|
||||
|
||||
id(new HeraldWebhookEditEngine())
|
||||
->setViewer($this->getViewer())
|
||||
->addActionToCrumbs($crumbs);
|
||||
|
||||
return $crumbs;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
final class HeraldWebhookTestController
|
||||
extends HeraldWebhookController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$hook = id(new HeraldWebhookQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($request->getURIData('id')))
|
||||
->requireCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
))
|
||||
->executeOne();
|
||||
if (!$hook) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
$object = $hook;
|
||||
|
||||
$request = HeraldWebhookRequest::initializeNewWebhookRequest($hook)
|
||||
->setObjectPHID($object->getPHID())
|
||||
->save();
|
||||
|
||||
$request->queueCall();
|
||||
|
||||
$next_uri = $hook->getURI().'request/'.$request->getID().'/';
|
||||
|
||||
return id(new AphrontRedirectResponse())->setURI($next_uri);
|
||||
}
|
||||
|
||||
return $this->newDialog()
|
||||
->setTitle(pht('New Test Request'))
|
||||
->appendParagraph(
|
||||
pht('This will make a new test request to the configured URI.'))
|
||||
->addCancelButton($hook->getURI())
|
||||
->addSubmitButton(pht('Make Request'));
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,160 @@
|
|||
<?php
|
||||
|
||||
final class HeraldWebhookViewController
|
||||
extends HeraldWebhookController {
|
||||
|
||||
public function shouldAllowPublic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$hook = id(new HeraldWebhookQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($request->getURIData('id')))
|
||||
->executeOne();
|
||||
if (!$hook) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$header = $this->buildHeaderView($hook);
|
||||
|
||||
$warnings = null;
|
||||
if ($hook->isInErrorBackoff($viewer)) {
|
||||
$message = pht(
|
||||
'Many requests to this webhook have failed recently (at least %s '.
|
||||
'errors in the last %s seconds). New requests are temporarily paused.',
|
||||
$hook->getErrorBackoffThreshold(),
|
||||
$hook->getErrorBackoffWindow());
|
||||
|
||||
$warnings = id(new PHUIInfoView())
|
||||
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
|
||||
->setErrors(
|
||||
array(
|
||||
$message,
|
||||
));
|
||||
}
|
||||
|
||||
$curtain = $this->buildCurtain($hook);
|
||||
$properties_view = $this->buildPropertiesView($hook);
|
||||
|
||||
$timeline = $this->buildTransactionTimeline(
|
||||
$hook,
|
||||
new HeraldWebhookTransactionQuery());
|
||||
$timeline->setShouldTerminate(true);
|
||||
|
||||
$requests = id(new HeraldWebhookRequestQuery())
|
||||
->setViewer($viewer)
|
||||
->withWebhookPHIDs(array($hook->getPHID()))
|
||||
->setLimit(20)
|
||||
->execute();
|
||||
|
||||
$requests_table = id(new HeraldWebhookRequestListView())
|
||||
->setViewer($viewer)
|
||||
->setRequests($requests)
|
||||
->setHighlightID($request->getURIData('requestID'));
|
||||
|
||||
$requests_view = id(new PHUIObjectBoxView())
|
||||
->setHeaderText(pht('Recent Requests'))
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->setTable($requests_table);
|
||||
|
||||
$hook_view = id(new PHUITwoColumnView())
|
||||
->setHeader($header)
|
||||
->setMainColumn(
|
||||
array(
|
||||
$warnings,
|
||||
$properties_view,
|
||||
$requests_view,
|
||||
$timeline,
|
||||
))
|
||||
->setCurtain($curtain);
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs()
|
||||
->addTextCrumb(pht('Webhook %d', $hook->getID()))
|
||||
->setBorder(true);
|
||||
|
||||
return $this->newPage()
|
||||
->setTitle(
|
||||
array(
|
||||
pht('Webhook %d', $hook->getID()),
|
||||
$hook->getName(),
|
||||
))
|
||||
->setCrumbs($crumbs)
|
||||
->setPageObjectPHIDs(
|
||||
array(
|
||||
$hook->getPHID(),
|
||||
))
|
||||
->appendChild($hook_view);
|
||||
}
|
||||
|
||||
private function buildHeaderView(HeraldWebhook $hook) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$title = $hook->getName();
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader($title)
|
||||
->setViewer($viewer)
|
||||
->setPolicyObject($hook)
|
||||
->setHeaderIcon('fa-cloud-upload');
|
||||
|
||||
return $header;
|
||||
}
|
||||
|
||||
|
||||
private function buildCurtain(HeraldWebhook $hook) {
|
||||
$viewer = $this->getViewer();
|
||||
$curtain = $this->newCurtainView($hook);
|
||||
|
||||
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||
$viewer,
|
||||
$hook,
|
||||
PhabricatorPolicyCapability::CAN_EDIT);
|
||||
|
||||
$id = $hook->getID();
|
||||
$edit_uri = $this->getApplicationURI("webhook/edit/{$id}/");
|
||||
$test_uri = $this->getApplicationURI("webhook/test/{$id}/");
|
||||
|
||||
$curtain->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setName(pht('Edit Webhook'))
|
||||
->setIcon('fa-pencil')
|
||||
->setDisabled(!$can_edit)
|
||||
->setWorkflow(!$can_edit)
|
||||
->setHref($edit_uri));
|
||||
|
||||
$curtain->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setName(pht('New Test Request'))
|
||||
->setIcon('fa-cloud-upload')
|
||||
->setDisabled(!$can_edit)
|
||||
->setWorkflow(true)
|
||||
->setHref($test_uri));
|
||||
|
||||
return $curtain;
|
||||
}
|
||||
|
||||
|
||||
private function buildPropertiesView(HeraldWebhook $hook) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$properties = id(new PHUIPropertyListView())
|
||||
->setViewer($viewer);
|
||||
|
||||
$properties->addProperty(
|
||||
pht('URI'),
|
||||
$hook->getWebhookURI());
|
||||
|
||||
$properties->addProperty(
|
||||
pht('Status'),
|
||||
$hook->getStatusDisplayName());
|
||||
|
||||
return id(new PHUIObjectBoxView())
|
||||
->setHeaderText(pht('Details'))
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->appendChild($properties);
|
||||
}
|
||||
|
||||
}
|
105
src/applications/herald/editor/HeraldWebhookEditEngine.php
Normal file
105
src/applications/herald/editor/HeraldWebhookEditEngine.php
Normal file
|
@ -0,0 +1,105 @@
|
|||
<?php
|
||||
|
||||
final class HeraldWebhookEditEngine
|
||||
extends PhabricatorEditEngine {
|
||||
|
||||
const ENGINECONST = 'herald.webhook';
|
||||
|
||||
public function isEngineConfigurable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getEngineName() {
|
||||
return pht('Webhooks');
|
||||
}
|
||||
|
||||
public function getSummaryHeader() {
|
||||
return pht('Edit Webhook Configurations');
|
||||
}
|
||||
|
||||
public function getSummaryText() {
|
||||
return pht('This engine is used to edit webhooks.');
|
||||
}
|
||||
|
||||
public function getEngineApplicationClass() {
|
||||
return 'PhabricatorHeraldApplication';
|
||||
}
|
||||
|
||||
protected function newEditableObject() {
|
||||
$viewer = $this->getViewer();
|
||||
return HeraldWebhook::initializeNewWebhook($viewer);
|
||||
}
|
||||
|
||||
protected function newObjectQuery() {
|
||||
return new HeraldWebhookQuery();
|
||||
}
|
||||
|
||||
protected function getObjectCreateTitleText($object) {
|
||||
return pht('Create Webhook');
|
||||
}
|
||||
|
||||
protected function getObjectCreateButtonText($object) {
|
||||
return pht('Create Webhook');
|
||||
}
|
||||
|
||||
protected function getObjectEditTitleText($object) {
|
||||
return pht('Edit Webhook: %s', $object->getName());
|
||||
}
|
||||
|
||||
protected function getObjectEditShortText($object) {
|
||||
return pht('Edit Webhook');
|
||||
}
|
||||
|
||||
protected function getObjectCreateShortText() {
|
||||
return pht('Create Webhook');
|
||||
}
|
||||
|
||||
protected function getObjectName() {
|
||||
return pht('Webhook');
|
||||
}
|
||||
|
||||
protected function getEditorURI() {
|
||||
return '/herald/webhook/edit/';
|
||||
}
|
||||
|
||||
protected function getObjectCreateCancelURI($object) {
|
||||
return '/herald/webhook/';
|
||||
}
|
||||
|
||||
protected function getObjectViewURI($object) {
|
||||
return $object->getURI();
|
||||
}
|
||||
|
||||
protected function getCreateNewObjectPolicy() {
|
||||
return $this->getApplication()->getPolicy(
|
||||
HeraldCreateWebhooksCapability::CAPABILITY);
|
||||
}
|
||||
|
||||
protected function buildCustomEditFields($object) {
|
||||
return array(
|
||||
id(new PhabricatorTextEditField())
|
||||
->setKey('name')
|
||||
->setLabel(pht('Name'))
|
||||
->setDescription(pht('Name of the webhook.'))
|
||||
->setTransactionType(HeraldWebhookNameTransaction::TRANSACTIONTYPE)
|
||||
->setIsRequired(true)
|
||||
->setValue($object->getName()),
|
||||
id(new PhabricatorTextEditField())
|
||||
->setKey('uri')
|
||||
->setLabel(pht('URI'))
|
||||
->setDescription(pht('URI for the webhook.'))
|
||||
->setTransactionType(HeraldWebhookURITransaction::TRANSACTIONTYPE)
|
||||
->setIsRequired(true)
|
||||
->setValue($object->getWebhookURI()),
|
||||
id(new PhabricatorSelectEditField())
|
||||
->setKey('status')
|
||||
->setLabel(pht('Status'))
|
||||
->setDescription(pht('Status mode for the webhook.'))
|
||||
->setTransactionType(HeraldWebhookStatusTransaction::TRANSACTIONTYPE)
|
||||
->setOptions(HeraldWebhook::getStatusDisplayNameMap())
|
||||
->setValue($object->getStatus()),
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
}
|
22
src/applications/herald/editor/HeraldWebhookEditor.php
Normal file
22
src/applications/herald/editor/HeraldWebhookEditor.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
final class HeraldWebhookEditor
|
||||
extends PhabricatorApplicationTransactionEditor {
|
||||
|
||||
public function getEditorApplicationClass() {
|
||||
return 'PhabricatorHeraldApplication';
|
||||
}
|
||||
|
||||
public function getEditorObjectsDescription() {
|
||||
return pht('Webhooks');
|
||||
}
|
||||
|
||||
public function getCreateObjectTitle($author, $object) {
|
||||
return pht('%s created this webhook.', $author);
|
||||
}
|
||||
|
||||
public function getCreateObjectTitleForFeed($author, $object) {
|
||||
return pht('%s created %s.', $author, $object);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
final class HeraldWebhookCallManagementWorkflow
|
||||
extends HeraldWebhookManagementWorkflow {
|
||||
|
||||
protected function didConstruct() {
|
||||
$this
|
||||
->setName('call')
|
||||
->setExamples('**call** --id __id__')
|
||||
->setSynopsis(pht('Call a webhook.'))
|
||||
->setArguments(
|
||||
array(
|
||||
array(
|
||||
'name' => 'id',
|
||||
'param' => 'id',
|
||||
'help' => pht('Webhook ID to call'),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
public function execute(PhutilArgumentParser $args) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$id = $args->getArg('id');
|
||||
if (!$id) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Specify a webhook to call with "--id".'));
|
||||
}
|
||||
|
||||
$hook = id(new HeraldWebhookQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($id))
|
||||
->executeOne();
|
||||
if (!$hook) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Unable to load specified webhook ("%s").',
|
||||
$id));
|
||||
}
|
||||
|
||||
$object = $hook;
|
||||
|
||||
$application_phid = id(new PhabricatorHeraldApplication())->getPHID();
|
||||
|
||||
$request = HeraldWebhookRequest::initializeNewWebhookRequest($hook)
|
||||
->setObjectPHID($object->getPHID())
|
||||
->save();
|
||||
|
||||
PhabricatorWorker::setRunAllTasksInProcess(true);
|
||||
$request->queueCall();
|
||||
|
||||
$request->reload();
|
||||
|
||||
echo tsprintf(
|
||||
"%s\n",
|
||||
pht(
|
||||
'Success, got HTTP %s from webhook.',
|
||||
$request->getErrorCode()));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
<?php
|
||||
|
||||
abstract class HeraldWebhookManagementWorkflow
|
||||
extends PhabricatorManagementWorkflow {}
|
49
src/applications/herald/phid/HeraldWebhookPHIDType.php
Normal file
49
src/applications/herald/phid/HeraldWebhookPHIDType.php
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
final class HeraldWebhookPHIDType extends PhabricatorPHIDType {
|
||||
|
||||
const TYPECONST = 'HWBH';
|
||||
|
||||
public function getTypeName() {
|
||||
return pht('Webhook');
|
||||
}
|
||||
|
||||
public function newObject() {
|
||||
return new HeraldWebhook();
|
||||
}
|
||||
|
||||
public function getPHIDTypeApplicationClass() {
|
||||
return 'PhabricatorHeraldApplication';
|
||||
}
|
||||
|
||||
protected function buildQueryForObjects(
|
||||
PhabricatorObjectQuery $query,
|
||||
array $phids) {
|
||||
|
||||
return id(new HeraldWebhookQuery())
|
||||
->withPHIDs($phids);
|
||||
}
|
||||
|
||||
public function loadHandles(
|
||||
PhabricatorHandleQuery $query,
|
||||
array $handles,
|
||||
array $objects) {
|
||||
|
||||
foreach ($handles as $phid => $handle) {
|
||||
$hook = $objects[$phid];
|
||||
|
||||
$name = $hook->getName();
|
||||
$id = $hook->getID();
|
||||
|
||||
$handle
|
||||
->setName($name)
|
||||
->setURI($hook->getURI())
|
||||
->setFullName(pht('Webhook %d %s', $id, $name));
|
||||
|
||||
if ($hook->isDisabled()) {
|
||||
$handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
final class HeraldWebhookRequestPHIDType extends PhabricatorPHIDType {
|
||||
|
||||
const TYPECONST = 'HWBR';
|
||||
|
||||
public function getTypeName() {
|
||||
return pht('Webhook Request');
|
||||
}
|
||||
|
||||
public function newObject() {
|
||||
return new HeraldWebhook();
|
||||
}
|
||||
|
||||
public function getPHIDTypeApplicationClass() {
|
||||
return 'PhabricatorHeraldApplication';
|
||||
}
|
||||
|
||||
protected function buildQueryForObjects(
|
||||
PhabricatorObjectQuery $query,
|
||||
array $phids) {
|
||||
|
||||
return id(new HeraldWebhookRequestQuery())
|
||||
->withPHIDs($phids);
|
||||
}
|
||||
|
||||
public function loadHandles(
|
||||
PhabricatorHandleQuery $query,
|
||||
array $handles,
|
||||
array $objects) {
|
||||
|
||||
foreach ($handles as $phid => $handle) {
|
||||
$request = $objects[$phid];
|
||||
|
||||
// TODO: Fill this in.
|
||||
}
|
||||
}
|
||||
|
||||
}
|
64
src/applications/herald/query/HeraldWebhookQuery.php
Normal file
64
src/applications/herald/query/HeraldWebhookQuery.php
Normal file
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
final class HeraldWebhookQuery
|
||||
extends PhabricatorCursorPagedPolicyAwareQuery {
|
||||
|
||||
private $ids;
|
||||
private $phids;
|
||||
private $statuses;
|
||||
|
||||
public function withIDs(array $ids) {
|
||||
$this->ids = $ids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withPHIDs(array $phids) {
|
||||
$this->phids = $phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withStatuses(array $statuses) {
|
||||
$this->statuses = $statuses;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function newResultObject() {
|
||||
return new HeraldWebhook();
|
||||
}
|
||||
|
||||
protected function loadPage() {
|
||||
return $this->loadStandardPage($this->newResultObject());
|
||||
}
|
||||
|
||||
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
|
||||
$where = parent::buildWhereClauseParts($conn);
|
||||
|
||||
if ($this->ids !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'id IN (%Ld)',
|
||||
$this->ids);
|
||||
}
|
||||
|
||||
if ($this->phids !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'phid IN (%Ls)',
|
||||
$this->phids);
|
||||
}
|
||||
|
||||
if ($this->statuses !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'status IN (%Ls)',
|
||||
$this->statuses);
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
public function getQueryApplicationClass() {
|
||||
return 'PhabricatorHeraldApplication';
|
||||
}
|
||||
|
||||
}
|
126
src/applications/herald/query/HeraldWebhookRequestQuery.php
Normal file
126
src/applications/herald/query/HeraldWebhookRequestQuery.php
Normal file
|
@ -0,0 +1,126 @@
|
|||
<?php
|
||||
|
||||
final class HeraldWebhookRequestQuery
|
||||
extends PhabricatorCursorPagedPolicyAwareQuery {
|
||||
|
||||
private $ids;
|
||||
private $phids;
|
||||
private $webhookPHIDs;
|
||||
private $lastRequestEpochMin;
|
||||
private $lastRequestEpochMax;
|
||||
private $lastRequestResults;
|
||||
|
||||
public function withIDs(array $ids) {
|
||||
$this->ids = $ids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withPHIDs(array $phids) {
|
||||
$this->phids = $phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withWebhookPHIDs(array $phids) {
|
||||
$this->webhookPHIDs = $phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function newResultObject() {
|
||||
return new HeraldWebhookRequest();
|
||||
}
|
||||
|
||||
protected function loadPage() {
|
||||
return $this->loadStandardPage($this->newResultObject());
|
||||
}
|
||||
|
||||
public function withLastRequestEpochBetween($epoch_min, $epoch_max) {
|
||||
$this->lastRequestEpochMin = $epoch_min;
|
||||
$this->lastRequestEpochMax = $epoch_max;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withLastRequestResults(array $results) {
|
||||
$this->lastRequestResults = $results;
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
|
||||
$where = parent::buildWhereClauseParts($conn);
|
||||
|
||||
if ($this->ids !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'id IN (%Ld)',
|
||||
$this->ids);
|
||||
}
|
||||
|
||||
if ($this->phids !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'phid IN (%Ls)',
|
||||
$this->phids);
|
||||
}
|
||||
|
||||
if ($this->webhookPHIDs !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'webhookPHID IN (%Ls)',
|
||||
$this->webhookPHIDs);
|
||||
}
|
||||
|
||||
if ($this->lastRequestEpochMin !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'lastRequestEpoch >= %d',
|
||||
$this->lastRequestEpochMin);
|
||||
}
|
||||
|
||||
if ($this->lastRequestEpochMax !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'lastRequestEpoch <= %d',
|
||||
$this->lastRequestEpochMax);
|
||||
}
|
||||
|
||||
if ($this->lastRequestResults !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'lastRequestResult IN (%Ls)',
|
||||
$this->lastRequestResults);
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
protected function willFilterPage(array $requests) {
|
||||
$hook_phids = mpull($requests, 'getWebhookPHID');
|
||||
|
||||
$hooks = id(new HeraldWebhookQuery())
|
||||
->setViewer($this->getViewer())
|
||||
->setParentQuery($this)
|
||||
->withPHIDs($hook_phids)
|
||||
->execute();
|
||||
$hooks = mpull($hooks, null, 'getPHID');
|
||||
|
||||
foreach ($requests as $key => $request) {
|
||||
$hook_phid = $request->getWebhookPHID();
|
||||
$hook = idx($hooks, $hook_phid);
|
||||
|
||||
if (!$hook) {
|
||||
unset($requests[$key]);
|
||||
$this->didRejectResult($request);
|
||||
continue;
|
||||
}
|
||||
|
||||
$request->attachWebhook($hook);
|
||||
}
|
||||
|
||||
return $requests;
|
||||
}
|
||||
|
||||
|
||||
public function getQueryApplicationClass() {
|
||||
return 'PhabricatorHeraldApplication';
|
||||
}
|
||||
|
||||
}
|
95
src/applications/herald/query/HeraldWebhookSearchEngine.php
Normal file
95
src/applications/herald/query/HeraldWebhookSearchEngine.php
Normal file
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
|
||||
final class HeraldWebhookSearchEngine
|
||||
extends PhabricatorApplicationSearchEngine {
|
||||
|
||||
public function getResultTypeDescription() {
|
||||
return pht('Webhooks');
|
||||
}
|
||||
|
||||
public function getApplicationClassName() {
|
||||
return 'PhabricatorHeraldApplication';
|
||||
}
|
||||
|
||||
public function newQuery() {
|
||||
return new HeraldWebhookQuery();
|
||||
}
|
||||
|
||||
protected function buildQueryFromParameters(array $map) {
|
||||
$query = $this->newQuery();
|
||||
|
||||
if ($map['statuses']) {
|
||||
$query->withStatuses($map['statuses']);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function buildCustomSearchFields() {
|
||||
return array(
|
||||
id(new PhabricatorSearchCheckboxesField())
|
||||
->setKey('statuses')
|
||||
->setLabel(pht('Status'))
|
||||
->setDescription(
|
||||
pht('Search for archived or active pastes.'))
|
||||
->setOptions(HeraldWebhook::getStatusDisplayNameMap()),
|
||||
);
|
||||
}
|
||||
|
||||
protected function getURI($path) {
|
||||
return '/herald/webhook/'.$path;
|
||||
}
|
||||
|
||||
protected function getBuiltinQueryNames() {
|
||||
$names = array();
|
||||
|
||||
$names['active'] = pht('Active');
|
||||
$names['all'] = pht('All');
|
||||
|
||||
return $names;
|
||||
}
|
||||
|
||||
public function buildSavedQueryFromBuiltin($query_key) {
|
||||
$query = $this->newSavedQuery();
|
||||
$query->setQueryKey($query_key);
|
||||
|
||||
switch ($query_key) {
|
||||
case 'all':
|
||||
return $query;
|
||||
case 'active':
|
||||
return $query->setParameter(
|
||||
'statuses',
|
||||
array(
|
||||
HeraldWebhook::HOOKSTATUS_FIREHOSE,
|
||||
HeraldWebhook::HOOKSTATUS_ENABLED,
|
||||
));
|
||||
}
|
||||
|
||||
return parent::buildSavedQueryFromBuiltin($query_key);
|
||||
}
|
||||
|
||||
protected function renderResultList(
|
||||
array $hooks,
|
||||
PhabricatorSavedQuery $query,
|
||||
array $handles) {
|
||||
assert_instances_of($hooks, 'HeraldWebhook');
|
||||
|
||||
$viewer = $this->requireViewer();
|
||||
|
||||
$list = id(new PHUIObjectItemListView())
|
||||
->setViewer($viewer);
|
||||
foreach ($hooks as $hook) {
|
||||
$item = id(new PHUIObjectItemView())
|
||||
->setObjectName(pht('Hook %d', $hook->getID()))
|
||||
->setHeader($hook->getName())
|
||||
->setHref($hook->getURI());
|
||||
|
||||
$list->addItem($item);
|
||||
}
|
||||
|
||||
return id(new PhabricatorApplicationSearchResultView())
|
||||
->setObjectList($list)
|
||||
->setNoDataString(pht('No webhooks found.'));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
final class HeraldWebhookTransactionQuery
|
||||
extends PhabricatorApplicationTransactionQuery {
|
||||
|
||||
public function getTemplateApplicationTransaction() {
|
||||
return new HeraldWebhookTransaction();
|
||||
}
|
||||
|
||||
}
|
177
src/applications/herald/storage/HeraldWebhook.php
Normal file
177
src/applications/herald/storage/HeraldWebhook.php
Normal file
|
@ -0,0 +1,177 @@
|
|||
<?php
|
||||
|
||||
final class HeraldWebhook
|
||||
extends HeraldDAO
|
||||
implements
|
||||
PhabricatorPolicyInterface,
|
||||
PhabricatorApplicationTransactionInterface,
|
||||
PhabricatorDestructibleInterface {
|
||||
|
||||
protected $name;
|
||||
protected $webhookURI;
|
||||
protected $viewPolicy;
|
||||
protected $editPolicy;
|
||||
protected $status;
|
||||
protected $hmacKey;
|
||||
|
||||
const HOOKSTATUS_FIREHOSE = 'firehose';
|
||||
const HOOKSTATUS_ENABLED = 'enabled';
|
||||
const HOOKSTATUS_DISABLED = 'disabled';
|
||||
|
||||
protected function getConfiguration() {
|
||||
return array(
|
||||
self::CONFIG_AUX_PHID => true,
|
||||
self::CONFIG_COLUMN_SCHEMA => array(
|
||||
'name' => 'text128',
|
||||
'webhookURI' => 'text255',
|
||||
'status' => 'text32',
|
||||
'hmacKey' => 'text32',
|
||||
),
|
||||
self::CONFIG_KEY_SCHEMA => array(
|
||||
'key_status' => array(
|
||||
'columns' => array('status'),
|
||||
),
|
||||
),
|
||||
) + parent::getConfiguration();
|
||||
}
|
||||
|
||||
public function getPHIDType() {
|
||||
return HeraldWebhookPHIDType::TYPECONST;
|
||||
}
|
||||
|
||||
public static function initializeNewWebhook(PhabricatorUser $viewer) {
|
||||
return id(new self())
|
||||
->setStatus(self::HOOKSTATUS_ENABLED)
|
||||
->setViewPolicy(PhabricatorPolicies::getMostOpenPolicy())
|
||||
->setEditPolicy($viewer->getPHID())
|
||||
->setHmacKey(Filesystem::readRandomCharacters(32));
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
return '/herald/webhook/view/'.$this->getID().'/';
|
||||
}
|
||||
|
||||
public function isDisabled() {
|
||||
return ($this->getStatus() === self::HOOKSTATUS_DISABLED);
|
||||
}
|
||||
|
||||
public static function getStatusDisplayNameMap() {
|
||||
return array(
|
||||
self::HOOKSTATUS_FIREHOSE => pht('Firehose'),
|
||||
self::HOOKSTATUS_ENABLED => pht('Enabled'),
|
||||
self::HOOKSTATUS_DISABLED => pht('Disabled'),
|
||||
);
|
||||
}
|
||||
|
||||
public function getStatusDisplayName() {
|
||||
$status = $this->getStatus();
|
||||
return idx($this->getStatusDisplayNameMap(), $status);
|
||||
}
|
||||
|
||||
public function getErrorBackoffWindow() {
|
||||
return phutil_units('5 minutes in seconds');
|
||||
}
|
||||
|
||||
public function getErrorBackoffThreshold() {
|
||||
return 10;
|
||||
}
|
||||
|
||||
public function isInErrorBackoff(PhabricatorUser $viewer) {
|
||||
$backoff_window = $this->getErrorBackoffWindow();
|
||||
$backoff_threshold = $this->getErrorBackoffThreshold();
|
||||
|
||||
$now = PhabricatorTime::getNow();
|
||||
|
||||
$window_start = ($now - $backoff_window);
|
||||
|
||||
$requests = id(new HeraldWebhookRequestQuery())
|
||||
->setViewer($viewer)
|
||||
->withWebhookPHIDs(array($this->getPHID()))
|
||||
->withLastRequestEpochBetween($window_start, null)
|
||||
->withLastRequestResults(
|
||||
array(
|
||||
HeraldWebhookRequest::RESULT_FAIL,
|
||||
))
|
||||
->execute();
|
||||
|
||||
if (count($requests) >= $backoff_threshold) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||
|
||||
|
||||
public function getCapabilities() {
|
||||
return array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
);
|
||||
}
|
||||
|
||||
public function getPolicy($capability) {
|
||||
switch ($capability) {
|
||||
case PhabricatorPolicyCapability::CAN_VIEW:
|
||||
return $this->getViewPolicy();
|
||||
case PhabricatorPolicyCapability::CAN_EDIT:
|
||||
return $this->getEditPolicy();
|
||||
}
|
||||
}
|
||||
|
||||
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
|
||||
|
||||
|
||||
public function getApplicationTransactionEditor() {
|
||||
return new HeraldWebhookEditor();
|
||||
}
|
||||
|
||||
public function getApplicationTransactionObject() {
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getApplicationTransactionTemplate() {
|
||||
return new HeraldWebhookTransaction();
|
||||
}
|
||||
|
||||
public function willRenderTimeline(
|
||||
PhabricatorApplicationTransactionView $timeline,
|
||||
AphrontRequest $request) {
|
||||
return $timeline;
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorDestructibleInterface )----------------------------------- */
|
||||
|
||||
|
||||
public function destroyObjectPermanently(
|
||||
PhabricatorDestructionEngine $engine) {
|
||||
|
||||
while (true) {
|
||||
$requests = id(new HeraldWebhookRequestQuery())
|
||||
->setViewer($engine->getViewer())
|
||||
->withWebhookPHIDs(array($this->getPHID()))
|
||||
->setLimit(100)
|
||||
->execute();
|
||||
|
||||
if (!$requests) {
|
||||
break;
|
||||
}
|
||||
|
||||
foreach ($requests as $request) {
|
||||
$request->delete();
|
||||
}
|
||||
}
|
||||
|
||||
$this->delete();
|
||||
}
|
||||
|
||||
|
||||
}
|
191
src/applications/herald/storage/HeraldWebhookRequest.php
Normal file
191
src/applications/herald/storage/HeraldWebhookRequest.php
Normal file
|
@ -0,0 +1,191 @@
|
|||
<?php
|
||||
|
||||
final class HeraldWebhookRequest
|
||||
extends HeraldDAO
|
||||
implements
|
||||
PhabricatorPolicyInterface,
|
||||
PhabricatorExtendedPolicyInterface {
|
||||
|
||||
protected $webhookPHID;
|
||||
protected $objectPHID;
|
||||
protected $status;
|
||||
protected $properties = array();
|
||||
protected $lastRequestResult;
|
||||
protected $lastRequestEpoch;
|
||||
|
||||
private $webhook = self::ATTACHABLE;
|
||||
|
||||
const RETRY_NEVER = 'never';
|
||||
const RETRY_FOREVER = 'forever';
|
||||
|
||||
const STATUS_QUEUED = 'queued';
|
||||
const STATUS_FAILED = 'failed';
|
||||
const STATUS_SENT = 'sent';
|
||||
|
||||
const RESULT_NONE = 'none';
|
||||
const RESULT_OKAY = 'okay';
|
||||
const RESULT_FAIL = 'fail';
|
||||
|
||||
protected function getConfiguration() {
|
||||
return array(
|
||||
self::CONFIG_AUX_PHID => true,
|
||||
self::CONFIG_SERIALIZATION => array(
|
||||
'properties' => self::SERIALIZATION_JSON,
|
||||
),
|
||||
self::CONFIG_COLUMN_SCHEMA => array(
|
||||
'status' => 'text32',
|
||||
'lastRequestResult' => 'text32',
|
||||
'lastRequestEpoch' => 'epoch',
|
||||
),
|
||||
self::CONFIG_KEY_SCHEMA => array(
|
||||
'key_ratelimit' => array(
|
||||
'columns' => array(
|
||||
'webhookPHID',
|
||||
'lastRequestResult',
|
||||
'lastRequestEpoch',
|
||||
),
|
||||
),
|
||||
'key_collect' => array(
|
||||
'columns' => array('dateCreated'),
|
||||
),
|
||||
),
|
||||
) + parent::getConfiguration();
|
||||
}
|
||||
|
||||
public function getPHIDType() {
|
||||
return HeraldWebhookRequestPHIDType::TYPECONST;
|
||||
}
|
||||
|
||||
public static function initializeNewWebhookRequest(HeraldWebhook $hook) {
|
||||
return id(new self())
|
||||
->setWebhookPHID($hook->getPHID())
|
||||
->attachWebhook($hook)
|
||||
->setStatus(self::STATUS_QUEUED)
|
||||
->setRetryMode(self::RETRY_NEVER)
|
||||
->setLastRequestResult(self::RESULT_NONE)
|
||||
->setLastRequestEpoch(0);
|
||||
}
|
||||
|
||||
public function getWebhook() {
|
||||
return $this->assertAttached($this->webhook);
|
||||
}
|
||||
|
||||
public function attachWebhook(HeraldWebhook $hook) {
|
||||
$this->webhook = $hook;
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function setProperty($key, $value) {
|
||||
$this->properties[$key] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function getProperty($key, $default = null) {
|
||||
return idx($this->properties, $key, $default);
|
||||
}
|
||||
|
||||
public function setRetryMode($mode) {
|
||||
return $this->setProperty('retry', $mode);
|
||||
}
|
||||
|
||||
public function getRetryMode() {
|
||||
return $this->getProperty('retry');
|
||||
}
|
||||
|
||||
public function setErrorType($error_type) {
|
||||
return $this->setProperty('errorType', $error_type);
|
||||
}
|
||||
|
||||
public function getErrorType() {
|
||||
return $this->getProperty('errorType');
|
||||
}
|
||||
|
||||
public function setErrorCode($error_code) {
|
||||
return $this->setProperty('errorCode', $error_code);
|
||||
}
|
||||
|
||||
public function getErrorCode() {
|
||||
return $this->getProperty('errorCode');
|
||||
}
|
||||
|
||||
public function setTransactionPHIDs(array $phids) {
|
||||
return $this->setProperty('transactionPHIDs', $phids);
|
||||
}
|
||||
|
||||
public function getTransactionPHIDs() {
|
||||
return $this->getProperty('transactionPHIDs', array());
|
||||
}
|
||||
|
||||
public function queueCall() {
|
||||
PhabricatorWorker::scheduleTask(
|
||||
'HeraldWebhookWorker',
|
||||
array(
|
||||
'webhookRequestPHID' => $this->getPHID(),
|
||||
),
|
||||
array(
|
||||
'objectPHID' => $this->getPHID(),
|
||||
));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function newStatusIcon() {
|
||||
switch ($this->getStatus()) {
|
||||
case self::STATUS_QUEUED:
|
||||
$icon = 'fa-refresh';
|
||||
$color = 'blue';
|
||||
$tooltip = pht('Queued');
|
||||
break;
|
||||
case self::STATUS_SENT:
|
||||
$icon = 'fa-check';
|
||||
$color = 'green';
|
||||
$tooltip = pht('Sent');
|
||||
break;
|
||||
case self::STATUS_FAILED:
|
||||
default:
|
||||
$icon = 'fa-times';
|
||||
$color = 'red';
|
||||
$tooltip = pht('Failed');
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return id(new PHUIIconView())
|
||||
->setIcon($icon, $color)
|
||||
->setTooltip($tooltip);
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||
|
||||
|
||||
public function getCapabilities() {
|
||||
return array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
);
|
||||
}
|
||||
|
||||
public function getPolicy($capability) {
|
||||
switch ($capability) {
|
||||
case PhabricatorPolicyCapability::CAN_VIEW:
|
||||
return PhabricatorPolicies::getMostOpenPolicy();
|
||||
}
|
||||
}
|
||||
|
||||
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorExtendedPolicyInterface )--------------------------------- */
|
||||
|
||||
|
||||
public function getExtendedPolicy($capability, PhabricatorUser $viewer) {
|
||||
return array(
|
||||
array($this->getWebhook(), PhabricatorPolicyCapability::CAN_VIEW),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
22
src/applications/herald/storage/HeraldWebhookTransaction.php
Normal file
22
src/applications/herald/storage/HeraldWebhookTransaction.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
final class HeraldWebhookTransaction
|
||||
extends PhabricatorModularTransaction {
|
||||
|
||||
public function getApplicationName() {
|
||||
return 'herald';
|
||||
}
|
||||
|
||||
public function getApplicationTransactionType() {
|
||||
return HeraldWebhookPHIDType::TYPECONST;
|
||||
}
|
||||
|
||||
public function getApplicationTransactionCommentObject() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getBaseTransactionClass() {
|
||||
return 'HeraldWebhookTransactionType';
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
|
||||
final class HeraldWebhookRequestListView
|
||||
extends AphrontView {
|
||||
|
||||
private $requests;
|
||||
private $highlightID;
|
||||
|
||||
public function setRequests(array $requests) {
|
||||
assert_instances_of($requests, 'HeraldWebhookRequest');
|
||||
$this->requests = $requests;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setHighlightID($highlight_id) {
|
||||
$this->highlightID = $highlight_id;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getHighlightID() {
|
||||
return $this->highlightID;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$viewer = $this->getViewer();
|
||||
$requests = $this->requests;
|
||||
|
||||
$handle_phids = array();
|
||||
foreach ($requests as $request) {
|
||||
$handle_phids[] = $request->getObjectPHID();
|
||||
}
|
||||
$handles = $viewer->loadHandles($handle_phids);
|
||||
|
||||
$highlight_id = $this->getHighlightID();
|
||||
|
||||
$rows = array();
|
||||
$rowc = array();
|
||||
foreach ($requests as $request) {
|
||||
$icon = $request->newStatusIcon();
|
||||
|
||||
if ($highlight_id == $request->getID()) {
|
||||
$rowc[] = 'highlighted';
|
||||
} else {
|
||||
$rowc[] = null;
|
||||
}
|
||||
|
||||
$rows[] = array(
|
||||
$request->getID(),
|
||||
$icon,
|
||||
$handles[$request->getObjectPHID()]->renderLink(),
|
||||
$request->getErrorType(),
|
||||
$request->getErrorCode(),
|
||||
);
|
||||
}
|
||||
|
||||
$table = id(new AphrontTableView($rows))
|
||||
->setRowClasses($rowc)
|
||||
->setHeaders(
|
||||
array(
|
||||
pht('ID'),
|
||||
'',
|
||||
pht('Object'),
|
||||
pht('Type'),
|
||||
pht('Code'),
|
||||
))
|
||||
->setColumnClasses(
|
||||
array(
|
||||
'n',
|
||||
'',
|
||||
'wide',
|
||||
'',
|
||||
'',
|
||||
));
|
||||
|
||||
return $table;
|
||||
}
|
||||
|
||||
}
|
229
src/applications/herald/worker/HeraldWebhookWorker.php
Normal file
229
src/applications/herald/worker/HeraldWebhookWorker.php
Normal file
|
@ -0,0 +1,229 @@
|
|||
<?php
|
||||
|
||||
final class HeraldWebhookWorker
|
||||
extends PhabricatorWorker {
|
||||
|
||||
protected function doWork() {
|
||||
$viewer = PhabricatorUser::getOmnipotentUser();
|
||||
|
||||
$data = $this->getTaskData();
|
||||
$request_phid = idx($data, 'webhookRequestPHID');
|
||||
|
||||
$request = id(new HeraldWebhookRequestQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($request_phid))
|
||||
->executeOne();
|
||||
if (!$request) {
|
||||
throw new PhabricatorWorkerPermanentFailureException(
|
||||
pht(
|
||||
'Unable to load webhook request ("%s"). It may have been '.
|
||||
'garbage collected.',
|
||||
$request_phid));
|
||||
}
|
||||
|
||||
$status = $request->getStatus();
|
||||
if ($status !== HeraldWebhookRequest::STATUS_QUEUED) {
|
||||
throw new PhabricatorWorkerPermanentFailureException(
|
||||
pht(
|
||||
'Webhook request ("%s") is not in "%s" status (actual '.
|
||||
'status is "%s"). Declining call to hook.',
|
||||
$request_phid,
|
||||
HeraldWebhookRequest::STATUS_QUEUED,
|
||||
$status));
|
||||
}
|
||||
|
||||
$hook = $request->getWebhook();
|
||||
|
||||
if ($hook->isDisabled()) {
|
||||
$this->failRequest($request, 'hook', 'disabled');
|
||||
throw new PhabricatorWorkerPermanentFailureException(
|
||||
pht(
|
||||
'Associated hook ("%s") for webhook request ("%s") is disabled.',
|
||||
$hook->getPHID(),
|
||||
$request_phid));
|
||||
}
|
||||
|
||||
$uri = $hook->getWebhookURI();
|
||||
try {
|
||||
PhabricatorEnv::requireValidRemoteURIForFetch(
|
||||
$uri,
|
||||
array(
|
||||
'http',
|
||||
'https',
|
||||
));
|
||||
} catch (Exception $ex) {
|
||||
$this->failRequest($request, 'hook', 'uri');
|
||||
throw new PhabricatorWorkerPermanentFailureException(
|
||||
pht(
|
||||
'Associated hook ("%s") for webhook request ("%s") has invalid '.
|
||||
'fetch URI: %s',
|
||||
$hook->getPHID(),
|
||||
$request_phid,
|
||||
$ex->getMessage()));
|
||||
}
|
||||
|
||||
$object_phid = $request->getObjectPHID();
|
||||
|
||||
$object = id(new PhabricatorObjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($object_phid))
|
||||
->executeOne();
|
||||
if (!$object) {
|
||||
$this->failRequest($request, 'hook', 'object');
|
||||
throw new PhabricatorWorkerPermanentFailureException(
|
||||
pht(
|
||||
'Unable to load object ("%s") for webhook request ("%s").',
|
||||
$object_phid,
|
||||
$request_phid));
|
||||
}
|
||||
|
||||
$xaction_query = PhabricatorApplicationTransactionQuery::newQueryForObject(
|
||||
$object);
|
||||
$xaction_phids = $request->getTransactionPHIDs();
|
||||
if ($xaction_phids) {
|
||||
$xactions = $xaction_query
|
||||
->setViewer($viewer)
|
||||
->withObjectPHIDs(array($object_phid))
|
||||
->withPHIDs($xaction_phids)
|
||||
->execute();
|
||||
$xactions = mpull($xactions, null, 'getPHID');
|
||||
} else {
|
||||
$xactions = array();
|
||||
}
|
||||
|
||||
// To prevent thundering herd issues for high volume webhooks (where
|
||||
// a large number of workers might try to work through a request backlog
|
||||
// simultaneously, before the error backoff can catch up), we never
|
||||
// parallelize requests to a particular webhook.
|
||||
|
||||
$lock_key = 'webhook('.$hook->getPHID().')';
|
||||
$lock = PhabricatorGlobalLock::newLock($lock_key);
|
||||
|
||||
try {
|
||||
$lock->lock();
|
||||
} catch (Exception $ex) {
|
||||
phlog($ex);
|
||||
throw new PhabricatorWorkerYieldException(15);
|
||||
}
|
||||
|
||||
$caught = null;
|
||||
try {
|
||||
$this->callWebhookWithLock($hook, $request, $object, $xactions);
|
||||
} catch (Exception $ex) {
|
||||
$caught = $ex;
|
||||
}
|
||||
|
||||
$lock->unlock();
|
||||
|
||||
if ($caught) {
|
||||
throw $caught;
|
||||
}
|
||||
}
|
||||
|
||||
private function callWebhookWithLock(
|
||||
HeraldWebhook $hook,
|
||||
HeraldWebhookRequest $request,
|
||||
$object,
|
||||
array $xactions) {
|
||||
$viewer = PhabricatorUser::getOmnipotentUser();
|
||||
|
||||
if ($hook->isInErrorBackoff($viewer)) {
|
||||
throw new PhabricatorWorkerYieldException($hook->getErrorBackoffWindow());
|
||||
}
|
||||
|
||||
$xaction_data = array();
|
||||
foreach ($xactions as $xaction) {
|
||||
$xaction_data[] = array(
|
||||
'phid' => $xaction->getPHID(),
|
||||
);
|
||||
}
|
||||
|
||||
$payload = array(
|
||||
'triggers' => array(),
|
||||
'object' => array(
|
||||
'phid' => $object->getPHID(),
|
||||
),
|
||||
'transactions' => $xaction_data,
|
||||
);
|
||||
|
||||
$payload = phutil_json_encode($payload);
|
||||
$key = $hook->getHmacKey();
|
||||
$signature = PhabricatorHash::digestHMACSHA256($payload, $key);
|
||||
$uri = $hook->getWebhookURI();
|
||||
|
||||
$future = id(new HTTPSFuture($uri))
|
||||
->setMethod('POST')
|
||||
->addHeader('Content-Type', 'application/json')
|
||||
->addHeader('X-Phabricator-Webhook-Signature', $signature)
|
||||
->setTimeout(15)
|
||||
->setData($payload);
|
||||
|
||||
list($status) = $future->resolve();
|
||||
|
||||
if ($status->isTimeout()) {
|
||||
$error_type = 'timeout';
|
||||
} else {
|
||||
$error_type = 'http';
|
||||
}
|
||||
$error_code = $status->getStatusCode();
|
||||
|
||||
$request
|
||||
->setErrorType($error_type)
|
||||
->setErrorCode($error_code)
|
||||
->setLastRequestEpoch(PhabricatorTime::getNow());
|
||||
|
||||
$retry_forever = HeraldWebhookRequest::RETRY_FOREVER;
|
||||
if ($status->isTimeout() || $status->isError()) {
|
||||
$should_retry = ($request->getRetryMode() === $retry_forever);
|
||||
|
||||
$request
|
||||
->setLastRequestResult(HeraldWebhookRequest::RESULT_FAIL);
|
||||
|
||||
if ($should_retry) {
|
||||
$request->save();
|
||||
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Webhook request ("%s", to "%s") failed (%s / %s). The request '.
|
||||
'will be retried.',
|
||||
$request->getPHID(),
|
||||
$uri,
|
||||
$error_type,
|
||||
$error_code));
|
||||
} else {
|
||||
$request
|
||||
->setStatus(HeraldWebhookRequest::STATUS_FAILED)
|
||||
->save();
|
||||
|
||||
throw new PhabricatorWorkerPermanentFailureException(
|
||||
pht(
|
||||
'Webhook request ("%s", to "%s") failed (%s / %s). The request '.
|
||||
'will not be retried.',
|
||||
$request->getPHID(),
|
||||
$uri,
|
||||
$error_type,
|
||||
$error_code));
|
||||
}
|
||||
} else {
|
||||
$request
|
||||
->setLastRequestResult(HeraldWebhookRequest::RESULT_OKAY)
|
||||
->setStatus(HeraldWebhookRequest::STATUS_SENT)
|
||||
->save();
|
||||
}
|
||||
}
|
||||
|
||||
private function failRequest(
|
||||
HeraldWebhookRequest $request,
|
||||
$error_type,
|
||||
$error_code) {
|
||||
|
||||
$request
|
||||
->setStatus(HeraldWebhookRequest::STATUS_FAILED)
|
||||
->setErrorType($error_type)
|
||||
->setErrorCode($error_code)
|
||||
->setLastRequestResult(HeraldWebhookRequest::RESULT_NONE)
|
||||
->setLastRequestEpoch(0)
|
||||
->save();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
final class HeraldWebhookNameTransaction
|
||||
extends HeraldWebhookTransactionType {
|
||||
|
||||
const TRANSACTIONTYPE = 'name';
|
||||
|
||||
public function generateOldValue($object) {
|
||||
return $object->getName();
|
||||
}
|
||||
|
||||
public function applyInternalEffects($object, $value) {
|
||||
$object->setName($value);
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
return pht(
|
||||
'%s renamed this webhook from %s to %s.',
|
||||
$this->renderAuthor(),
|
||||
$this->renderOldValue(),
|
||||
$this->renderNewValue());
|
||||
}
|
||||
|
||||
public function getTitleForFeed() {
|
||||
return pht(
|
||||
'%s renamed %s from %s to %s.',
|
||||
$this->renderAuthor(),
|
||||
$this->renderObject(),
|
||||
$this->renderOldValue(),
|
||||
$this->renderNewValue());
|
||||
}
|
||||
|
||||
public function validateTransactions($object, array $xactions) {
|
||||
$errors = array();
|
||||
$viewer = $this->getActor();
|
||||
|
||||
if ($this->isEmptyTextTransaction($object->getName(), $xactions)) {
|
||||
$errors[] = $this->newRequiredError(
|
||||
pht('Webhooks must have a name.'));
|
||||
return $errors;
|
||||
}
|
||||
|
||||
$max_length = $object->getColumnMaximumByteLength('name');
|
||||
foreach ($xactions as $xaction) {
|
||||
$old_value = $this->generateOldValue($object);
|
||||
$new_value = $xaction->getNewValue();
|
||||
|
||||
$new_length = strlen($new_value);
|
||||
if ($new_length > $max_length) {
|
||||
$errors[] = $this->newInvalidError(
|
||||
pht(
|
||||
'Webhook names can be no longer than %s characters.',
|
||||
new PhutilNumber($max_length)));
|
||||
}
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
final class HeraldWebhookStatusTransaction
|
||||
extends HeraldWebhookTransactionType {
|
||||
|
||||
const TRANSACTIONTYPE = 'status';
|
||||
|
||||
public function generateOldValue($object) {
|
||||
return $object->getStatus();
|
||||
}
|
||||
|
||||
public function applyInternalEffects($object, $value) {
|
||||
$object->setStatus($value);
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
return pht(
|
||||
'%s changed hook status from %s to %s.',
|
||||
$this->renderAuthor(),
|
||||
$this->renderOldValue(),
|
||||
$this->renderNewValue());
|
||||
}
|
||||
|
||||
public function getTitleForFeed() {
|
||||
return pht(
|
||||
'%s changed %s from %s to %s.',
|
||||
$this->renderAuthor(),
|
||||
$this->renderObject(),
|
||||
$this->renderOldValue(),
|
||||
$this->renderNewValue());
|
||||
}
|
||||
|
||||
public function validateTransactions($object, array $xactions) {
|
||||
$errors = array();
|
||||
$viewer = $this->getActor();
|
||||
|
||||
$options = HeraldWebhook::getStatusDisplayNameMap();
|
||||
|
||||
foreach ($xactions as $xaction) {
|
||||
$new_value = $xaction->getNewValue();
|
||||
|
||||
if (!isset($options[$new_value])) {
|
||||
$errors[] = $this->newInvalidError(
|
||||
pht(
|
||||
'Webhook status "%s" is not valid. Valid statuses are: %s.',
|
||||
$new_value,
|
||||
implode(', ', array_keys($options))),
|
||||
$xaction);
|
||||
}
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
<?php
|
||||
|
||||
abstract class HeraldWebhookTransactionType
|
||||
extends PhabricatorModularTransactionType {}
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
final class HeraldWebhookURITransaction
|
||||
extends HeraldWebhookTransactionType {
|
||||
|
||||
const TRANSACTIONTYPE = 'uri';
|
||||
|
||||
public function generateOldValue($object) {
|
||||
return $object->getWebhookURI();
|
||||
}
|
||||
|
||||
public function applyInternalEffects($object, $value) {
|
||||
$object->setWebhookURI($value);
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
return pht(
|
||||
'%s changed the URI for this webhook from %s to %s.',
|
||||
$this->renderAuthor(),
|
||||
$this->renderOldValue(),
|
||||
$this->renderNewValue());
|
||||
}
|
||||
|
||||
public function getTitleForFeed() {
|
||||
return pht(
|
||||
'%s changed the URI for %s from %s to %s.',
|
||||
$this->renderAuthor(),
|
||||
$this->renderObject(),
|
||||
$this->renderOldValue(),
|
||||
$this->renderNewValue());
|
||||
}
|
||||
|
||||
public function validateTransactions($object, array $xactions) {
|
||||
$errors = array();
|
||||
$viewer = $this->getActor();
|
||||
|
||||
if ($this->isEmptyTextTransaction($object->getName(), $xactions)) {
|
||||
$errors[] = $this->newRequiredError(
|
||||
pht('Webhooks must have a URI.'));
|
||||
return $errors;
|
||||
}
|
||||
|
||||
$max_length = $object->getColumnMaximumByteLength('webhookURI');
|
||||
foreach ($xactions as $xaction) {
|
||||
$old_value = $this->generateOldValue($object);
|
||||
$new_value = $xaction->getNewValue();
|
||||
|
||||
$new_length = strlen($new_value);
|
||||
if ($new_length > $max_length) {
|
||||
$errors[] = $this->newInvalidError(
|
||||
pht(
|
||||
'Webhook URIs can be no longer than %s characters.',
|
||||
new PhutilNumber($max_length)),
|
||||
$xaction);
|
||||
}
|
||||
|
||||
try {
|
||||
PhabricatorEnv::requireValidRemoteURIForFetch(
|
||||
$new_value,
|
||||
array(
|
||||
'http',
|
||||
'https',
|
||||
));
|
||||
} catch (Exception $ex) {
|
||||
$errors[] = $this->newInvalidError(
|
||||
$ex->getMessage(),
|
||||
$xaction);
|
||||
}
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue