Page MenuHomePhabricator

D7723.id17438.diff
No OneTemporary

D7723.id17438.diff

Index: src/__celerity_resource_map__.php
===================================================================
--- src/__celerity_resource_map__.php
+++ src/__celerity_resource_map__.php
@@ -1969,6 +1969,20 @@
),
'disk' => '/rsrc/js/application/maniphest/behavior-transaction-preview.js',
),
+ 'javelin-behavior-nuance-source-editor' =>
+ array(
+ 'uri' => '/res/b201c675/rsrc/js/application/nuance/nuance-source-editor.js',
+ 'type' => 'js',
+ 'requires' =>
+ array(
+ 0 => 'javelin-dom',
+ 1 => 'javelin-workflow',
+ 2 => 'javelin-behavior',
+ 3 => 'javelin-stratcom',
+ 4 => 'javelin-uri',
+ ),
+ 'disk' => '/rsrc/js/application/nuance/nuance-source-editor.js',
+ ),
'javelin-behavior-owners-path-editor' =>
array(
'uri' => '/res/9cf78ffc/rsrc/js/application/owners/owners-path-editor.js',
Index: src/__phutil_library_map__.php
===================================================================
--- src/__phutil_library_map__.php
+++ src/__phutil_library_map__.php
@@ -886,7 +886,7 @@
'NuancePHIDTypeQueue' => 'applications/nuance/phid/NuancePHIDTypeQueue.php',
'NuancePHIDTypeRequestor' => 'applications/nuance/phid/NuancePHIDTypeRequestor.php',
'NuancePHIDTypeSource' => 'applications/nuance/phid/NuancePHIDTypeSource.php',
- 'NuancePhabricatorFormSourceDefinition' => 'applications/nuance/source/NuancePhabricatorFormSourceDefinition.php',
+ 'NuancePhabricatorFormSourceDefinition' => 'applications/nuance/source/definition/NuancePhabricatorFormSourceDefinition.php',
'NuanceQuery' => 'applications/nuance/query/NuanceQuery.php',
'NuanceQueue' => 'applications/nuance/storage/NuanceQueue.php',
'NuanceQueueEditController' => 'applications/nuance/controller/NuanceQueueEditController.php',
@@ -907,7 +907,7 @@
'NuanceRequestorTransactionQuery' => 'applications/nuance/query/NuanceRequestorTransactionQuery.php',
'NuanceRequestorViewController' => 'applications/nuance/controller/NuanceRequestorViewController.php',
'NuanceSource' => 'applications/nuance/storage/NuanceSource.php',
- 'NuanceSourceDefinition' => 'applications/nuance/source/NuanceSourceDefinition.php',
+ 'NuanceSourceDefinition' => 'applications/nuance/source/definition/NuanceSourceDefinition.php',
'NuanceSourceEditController' => 'applications/nuance/controller/NuanceSourceEditController.php',
'NuanceSourceEditor' => 'applications/nuance/editor/NuanceSourceEditor.php',
'NuanceSourceQuery' => 'applications/nuance/query/NuanceSourceQuery.php',
@@ -916,6 +916,10 @@
'NuanceSourceTransactionQuery' => 'applications/nuance/query/NuanceSourceTransactionQuery.php',
'NuanceSourceViewController' => 'applications/nuance/controller/NuanceSourceViewController.php',
'NuanceTransaction' => 'applications/nuance/storage/NuanceTransaction.php',
+ 'NuanceTwitterPublicStreamSourceDefinition' => 'applications/nuance/source/definition/NuanceTwitterPublicStreamSourceDefinition.php',
+ 'NuanceTwitterSourceBot' => 'applications/nuance/source/bot/NuanceTwitterSourceBot.php',
+ 'NuanceTwitterSourceDefinition' => 'applications/nuance/source/definition/NuanceTwitterSourceDefinition.php',
+ 'NuanceTwitterUserStreamSourceDefinition' => 'applications/nuance/source/definition/NuanceTwitterUserStreamSourceDefinition.php',
'OwnersPackageReplyHandler' => 'applications/owners/mail/OwnersPackageReplyHandler.php',
'PHUI' => 'view/phui/PHUI.php',
'PHUIBoxExample' => 'applications/uiexample/examples/PHUIBoxExample.php',
@@ -3340,6 +3344,10 @@
'NuanceSourceTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'NuanceSourceViewController' => 'NuanceController',
'NuanceTransaction' => 'PhabricatorApplicationTransaction',
+ 'NuanceTwitterPublicStreamSourceDefinition' => 'NuanceTwitterSourceDefinition',
+ 'NuanceTwitterSourceBot' => 'PhabricatorDaemon',
+ 'NuanceTwitterSourceDefinition' => 'NuanceSourceDefinition',
+ 'NuanceTwitterUserStreamSourceDefinition' => 'NuanceTwitterSourceDefinition',
'OwnersPackageReplyHandler' => 'PhabricatorMailReplyHandler',
'PHUIBoxExample' => 'PhabricatorUIExample',
'PHUIBoxView' => 'AphrontTagView',
Index: src/applications/nuance/controller/NuanceSourceEditController.php
===================================================================
--- src/applications/nuance/controller/NuanceSourceEditController.php
+++ src/applications/nuance/controller/NuanceSourceEditController.php
@@ -19,32 +19,67 @@
public function processRequest() {
$can_edit = $this->requireApplicationCapability(
NuanceCapabilitySourceManage::CAPABILITY);
-
$request = $this->getRequest();
$user = $request->getUser();
+ $source = $this->loadOrCreateSourceObject($user);
+ if (!$source) {
+ return new Aphront404Response();
+ }
- $source_id = $this->getSourceID();
- $is_new = !$source_id;
+ if ($request->isAjax()) {
+ return $this->processAjaxRequest($source);
+ }
- if ($is_new) {
- $source = NuanceSource::initializeNewSource($user);
- } else {
- $source = id(new NuanceSourceQuery())
- ->setViewer($user)
- ->withIDs(array($source_id))
- ->requireCapabilities(
- array(
- PhabricatorPolicyCapability::CAN_VIEW,
- PhabricatorPolicyCapability::CAN_EDIT,
- ))
- ->executeOne();
+ return $this->processEditRequest($source);
+ }
+
+ /**
+ * This is just to dynamically update the edit form.
+ */
+ private function processAjaxRequest(NuanceSource $source) {
+ $request = $this->getRequest();
+ $user = $request->getUser();
+
+ if ($request->getBool('warn', false)) {
+ $dialog = id(new AphrontDialogView())
+ ->setUser($user)
+ ->setTitle(pht('Warning - form data could be lost.'))
+ ->appendParagraph(pht(
+ 'Edits to this form other than the name will not be preserved if '.
+ 'you switch source type. '.
+ 'However, changes will not be saved until you click "save". '.
+ 'Are you sure you want to change source type?'))
+ ->addSubmitButton('I am sure.')
+ ->addCancelButton($source->getEditURI());
+ return id(new AphrontDialogResponse())
+ ->setDialog($dialog);
}
- if (!$source) {
- return new Aphront404Response();
+ return id(new AphrontAjaxResponse())
+ ->setContent(pht('You are cleared for submission ghost rider.'));
+ }
+
+ private function processEditRequest(NuanceSource $source) {
+ $request = $this->getRequest();
+ $user = $request->getUser();
+
+ // this handles if the user just changes the source type, which does a
+ // quick re-direct and preserves the new type and name
+ if (!$request->isFormPost() && $request->getExists('redraw')) {
+ $source->setType($request->getStr('type'));
+ $source->setName($request->getStr('name'));
}
- $definition = NuanceSourceDefinition::getDefinitionForSource($source);
+ // this handles if the user just changed the source type *AND* is trying
+ // to save the update. we need to use the new definition immediately, but
+ // need to let the editor handle actually update the type on the source.
+ if ($request->isFormPost()) {
+ $definition = NuanceSourceDefinition::getDefinitionForSourceType(
+ $request->getStr('type'));
+ $definition->setSourceObject($source);
+ } else {
+ $definition = NuanceSourceDefinition::getDefinitionForSource($source);
+ }
$definition->setActor($user);
$response = $definition->buildEditLayout($request);
@@ -63,4 +98,26 @@
'title' => $definition->getEditTitle(),
'device' => true));
}
+
+ private function loadOrCreateSourceObject(PhabricatorUser $user) {
+ $source_id = $this->getSourceID();
+ $is_new = !$source_id;
+
+ if (!$source_id) {
+ $source = NuanceSource::initializeNewSource($user);
+ } else {
+ $source = id(new NuanceSourceQuery())
+ ->setViewer($user)
+ ->withIDs(array($source_id))
+ ->requireCapabilities(
+ array(
+ PhabricatorPolicyCapability::CAN_VIEW,
+ PhabricatorPolicyCapability::CAN_EDIT,
+ ))
+ ->executeOne();
+ }
+
+ return $source;
+ }
+
}
Index: src/applications/nuance/editor/NuanceSourceEditor.php
===================================================================
--- src/applications/nuance/editor/NuanceSourceEditor.php
+++ src/applications/nuance/editor/NuanceSourceEditor.php
@@ -7,6 +7,8 @@
$types = parent::getTransactionTypes();
$types[] = NuanceSourceTransaction::TYPE_NAME;
+ $types[] = NuanceSourceTransaction::TYPE_SOURCE_TYPE;
+ $types[] = NuanceSourceTransaction::TYPE_METADATA;
$types[] = PhabricatorTransactions::TYPE_EDGE;
$types[] = PhabricatorTransactions::TYPE_COMMENT;
@@ -23,6 +25,16 @@
switch ($xaction->getTransactionType()) {
case NuanceSourceTransaction::TYPE_NAME:
return $object->getName();
+ case NuanceSourceTransaction::TYPE_SOURCE_TYPE:
+ if ($this->getIsNewObject()) {
+ return null;
+ }
+ return $object->getType();
+ case NuanceSourceTransaction::TYPE_METADATA:
+ if ($this->getIsNewObject()) {
+ return null;
+ }
+ return $object->getData();
}
return parent::getCustomTransactionOldValue($object, $xaction);
@@ -34,6 +46,8 @@
switch ($xaction->getTransactionType()) {
case NuanceSourceTransaction::TYPE_NAME:
+ case NuanceSourceTransaction::TYPE_SOURCE_TYPE:
+ case NuanceSourceTransaction::TYPE_METADATA:
return $xaction->getNewValue();
}
@@ -48,6 +62,12 @@
case NuanceSourceTransaction::TYPE_NAME:
$object->setName($xaction->getNewValue());
break;
+ case NuanceSourceTransaction::TYPE_SOURCE_TYPE:
+ $object->setType($xaction->getNewValue());
+ break;
+ case NuanceSourceTransaction::TYPE_METADATA:
+ $object->setData($xaction->getNewValue());
+ break;
}
}
@@ -57,6 +77,9 @@
switch ($xaction->getTransactionType()) {
case NuanceSourceTransaction::TYPE_NAME:
+ case NuanceSourceTransaction::TYPE_SOURCE_TYPE:
+ return;
+ case NuanceSourceTransaction::TYPE_METADATA:
return;
}
@@ -87,6 +110,13 @@
$errors[] = $error;
}
break;
+ case NuanceSourceTransaction::TYPE_METADATA:
+ $definition = NuanceSourceDefinition::getDefinitionForSource($object);
+ $error = $definition->validateTransaction($type, $xactions);
+ if ($error) {
+ $errors[] = $error;
+ }
+ break;
}
return $errors;
Index: src/applications/nuance/query/NuanceSourceQuery.php
===================================================================
--- src/applications/nuance/query/NuanceSourceQuery.php
+++ src/applications/nuance/query/NuanceSourceQuery.php
@@ -28,7 +28,6 @@
return $this;
}
-
public function loadPage() {
$table = new NuanceSource();
$conn_r = $table->establishConnection('r');
@@ -59,7 +58,7 @@
if ($this->types) {
$where[] = qsprintf(
$conn_r,
- 'type IN (%Ld)',
+ 'type IN (%Ls)',
$this->types);
}
Index: src/applications/nuance/source/bot/NuanceTwitterSourceBot.php
===================================================================
--- /dev/null
+++ src/applications/nuance/source/bot/NuanceTwitterSourceBot.php
@@ -0,0 +1,107 @@
+<?php
+
+/**
+ * Continuously loads all known @{class:NuanceSource} objects of
+ * @{class:NuanceTwitterSourceDefinition} type, calling each objects
+ * @{method:updateItems} method.
+ */
+final class NuanceTwitterSourceBot extends PhabricatorDaemon {
+
+ const PUBLIC_STREAM_URI =
+ 'https://stream.twitter.com/1.1/statuses/filter.json';
+ const USER_STREAM_URI = 'https://userstream.twitter.com/1.1/user.json';
+
+ public function run() {
+ $argv = $this->getArgv();
+ if (count($argv) !== 0) {
+ throw new Exception("usage: NuanceTwitterSourceBot");
+ }
+
+ // $this->runLoop();
+ $future = $this->buildTestTwitterFuture();
+
+ var_dump(array(
+ 'test_result' => $future->resolveJSON()));
+
+ }
+
+ private function runLoop() {
+ do {
+ $this->stillWorking();
+
+ $sources = $this->reloadSources();
+ $streams = $this->reconnectStreams($sources);
+ $this->readStreams($streams);
+
+ } while (true);
+ }
+
+ private function reloadSources() {
+ return id(new NuanceSourceQuery())
+ ->withTypes(array(
+ NuanceTwitterPublicStreamSourceDefinition::getSourceTypeConstant(),
+ NuanceTwitterUserStreamSourceDefinition::getSourceTypeConstant()))
+ ->setViewer($this->getViewer())
+ ->execute();
+ }
+
+ private function reconnectStreams(array $sources) {
+
+ // TODO - foreach source, build a future
+
+ return array();
+ }
+
+ private function readStreams(array $futures) {
+ // TODO - foreach future, read that bad boy
+ }
+
+ private function getHackedViewer() {
+ return id(new PhabricatorUser())
+ ->loadOneWhere('phid = "PHID-USER-xee4ju2teq7mflitwfcs"');
+ }
+
+ private function buildTestTwitterFuture(array $sources) {
+ assert_instances_of($sources, 'NuanceSource');
+
+ $viewer = $this->getHackedViewer();
+ $twitter = null;
+ $providers = PhabricatorAuthProvider::getAllEnabledProviders();
+ foreach ($providers as $provider) {
+ if ($provider instanceof PhabricatorAuthProviderOAuth1Twitter) {
+ $twitter = $provider;
+ break;
+ }
+ }
+
+ if (!$twitter) {
+ throw new Exception("No Twitter OAuth on this install!");
+ }
+
+ $external = id(new PhabricatorExternalAccountQuery())
+ ->setViewer($viewer)
+ ->withAccountTypes(array($provider->getProviderType()))
+ ->withAccountDomains(array($provider->getProviderDomain()))
+ ->withUserPHIDs(array($viewer->getPHID()))
+ ->executeOne();
+
+ if (!$external) {
+ throw new Exception("No external Twitter account on this account!");
+ }
+
+ $token = $external->getProperty('oauth1.token');
+ $secret = $external->getProperty('oauth1.token.secret');
+
+ $adapter = $twitter->getAdapter();
+ $adapter->setToken($token);
+ $adapter->setTokenSecret($secret);
+
+ $uri = new PhutilURI(self::PUBLIC_STREAM_URI);
+ $uri->setQueryParam('track', 'twitter');
+
+ $future = $adapter->newOAuth1Future($uri);
+
+ return $future;
+ }
+
+}
Index: src/applications/nuance/source/definition/NuancePhabricatorFormSourceDefinition.php
===================================================================
--- src/applications/nuance/source/definition/NuancePhabricatorFormSourceDefinition.php
+++ src/applications/nuance/source/definition/NuancePhabricatorFormSourceDefinition.php
@@ -15,7 +15,14 @@
return null;
}
+ protected function getEditFormDescription() {
+ return pht(
+ 'This source type creates a form, accessible to Phabricator users, that '.
+ 'creates Nuance issues.');
+ }
+
protected function augmentEditForm(
+ AphrontRequest $request,
AphrontFormView $form,
PhabricatorApplicationTransactionValidationException $ex = null) {
Index: src/applications/nuance/source/definition/NuanceSourceDefinition.php
===================================================================
--- src/applications/nuance/source/definition/NuanceSourceDefinition.php
+++ src/applications/nuance/source/definition/NuanceSourceDefinition.php
@@ -56,13 +56,25 @@
*/
public static function getDefinitionForSource(NuanceSource $source) {
$definitions = self::getAllDefinitions();
- $map = mpull($definitions, null, 'getSourceTypeConstant');
- $definition = $map[$source->getType()];
+ if (!$source->getType()) {
+ $definition = reset($definitions);
+ } else {
+ $map = mpull($definitions, null, 'getSourceTypeConstant');
+ $definition = $map[$source->getType()];
+ }
$definition->setSourceObject($source);
return $definition;
}
+ public static function getDefinitionForSourceType($type) {
+ $definitions = self::getAllDefinitions();
+ $map = mpull($definitions, null, 'getSourceTypeConstant');
+ $definition = $map[$type];
+
+ return $definition;
+ }
+
public static function getAllDefinitions() {
static $definitions;
@@ -128,7 +140,7 @@
if ($source->getPHID()) {
$title = pht('Edit "%s" source.', $source->getName());
} else {
- $title = pht('Create a new "%s" source.', $this->getName());
+ $title = pht('Create a new source.');
}
return $title;
@@ -157,10 +169,15 @@
} catch (PhabricatorApplicationTransactionValidationException $ex) {
$validation_exception = $ex;
}
-
}
- $form = $this->renderEditForm($validation_exception);
+ $form = $this->renderEditForm($request, $validation_exception);
+ Javelin::initBehavior('nuance-source-editor',
+ array(
+ 'redrawURI' => $source->getEditURI(),
+ 'isNew' => $source->getPHID() ? false : true,
+ 'baseValueKeys' => array('name', 'type'),
+ ));
$layout = id(new PHUIObjectBoxView())
->setHeaderText($this->getEditTitle())
->setValidationException($validation_exception)
@@ -172,12 +189,15 @@
return $layout;
}
+ abstract protected function getEditFormDescription();
+
/**
* Code to create a form to edit the @{class:NuanceItem} you are defining.
*
* return @{class:AphrontFormView}
*/
- private function renderEditForm(
+ public function renderEditForm(
+ AphrontRequest $request,
PhabricatorApplicationTransactionValidationException $ex = null) {
$user = $this->requireActor();
$source = $this->requireSourceObject();
@@ -187,22 +207,40 @@
$e_name = $ex->getShortMessage(NuanceSourceTransaction::TYPE_NAME);
}
+ $v_name = $request->getStr('name', $source->getName());
+ $v_type = $request->getStr('type', $source->getType());
+ if ($request->isFormPost()) {
+ $source->setViewPolicy($request->getStr('viewPolicy'));
+ $source->setEditPolicy($request->getStr('editPolicy'));
+ }
+
+ $form_id = $source->getPHID() ? $source->getPHID() : 'new-source-form';
$form = id(new AphrontFormView())
->setUser($user)
- ->appendChild(
+ ->setAction($source->getEditURI())
+ ->setID($form_id)
+ ->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Name'))
->setName('name')
->setError($e_name)
- ->setValue($source->getName()))
+ ->setValue($v_name))
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Type'))
->setName('type')
->setOptions(self::getSelectOptions())
- ->setValue($source->getType()));
+ ->setValue($v_type)
+ ->setMetadata(array(
+ 'form_id' => $form_id,
+ 'selected' => $v_type))
+ ->addSigil('source-type-select'))
+ ->appendChild(
+ id(new AphrontFormStaticControl())
+ ->setLabel(pht('Type Description'))
+ ->setValue($this->getEditFormDescription()));
- $form = $this->augmentEditForm($form, $ex);
+ $form = $this->augmentEditForm($request, $form, $ex);
$form
->appendChild(
@@ -231,6 +269,7 @@
* return @{class:AphrontFormView}
*/
protected function augmentEditForm(
+ AphrontRequest $request,
AphrontFormView $form,
PhabricatorApplicationTransactionValidationException $ex = null) {
@@ -263,10 +302,22 @@
$transactions[] = id(new NuanceSourceTransaction())
->setTransactionType(NuanceSourceTransaction::TYPE_NAME)
->setNewvalue($request->getStr('name'));
+ $transactions[] = id(new NuanceSourceTransaction())
+ ->setTransactionType(NuanceSourceTransaction::TYPE_SOURCE_TYPE)
+ ->setNewvalue($request->getStr('type'));
return $transactions;
}
+ /**
+ * Hook to validate @{class:PhabricatorTransactions} on a per-definition
+ * basis. Useful for validating metadata, which tends to vary from definition
+ * to definition.
+ */
+ public function validateTransaction($type, array $type_xactions) {
+ return null;
+ }
+
abstract public function renderView();
abstract public function renderListView();
Index: src/applications/nuance/source/definition/NuanceTwitterPublicStreamSourceDefinition.php
===================================================================
--- /dev/null
+++ src/applications/nuance/source/definition/NuanceTwitterPublicStreamSourceDefinition.php
@@ -0,0 +1,108 @@
+<?php
+
+final class NuanceTwitterPublicStreamSourceDefinition
+ extends NuanceTwitterSourceDefinition {
+
+ public function getName() {
+ return pht('Twitter Public Stream');
+ }
+
+ public function getSourceTypeConstant() {
+ return 'twitter-public-stream';
+ }
+
+ protected function getEditFormDescription() {
+ return pht(
+ 'This source type uses the Twitter Public Steam API to create items '.
+ 'from the stream of public tweets that match the specified keywords.');
+ }
+
+ protected function augmentEditForm(
+ AphrontRequest $request,
+ AphrontFormView $form,
+ PhabricatorApplicationTransactionValidationException $ex = null) {
+
+ $source = $this->requireSourceObject();
+ $data = $source->getData();
+ $v_keywords = null;
+ if ($request->isFormPost()) {
+ $v_keywords = $request->getStr('keywords');
+ } else if ($data) {
+ $v_keywords = implode(' ', idx($data, 'keywords', array()));
+ }
+ $e_keywords = null;
+ if ($ex) {
+ $e_keywords =
+ $ex->getShortMessage(NuanceSourceTransaction::TYPE_METADATA);
+ }
+ $form
+ ->appendChild(
+ id(new AphrontFormTextAreaControl())
+ ->setLabel(pht('Keywords'))
+ ->setName('keywords')
+ ->setValue($v_keywords)
+ ->setError($e_keywords)
+ ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT)
+ ->setCaption(
+ pht(
+ 'Space-delimited keywords this source should track. '.
+ 'Keywords are "OR\'d" and match regardless of capitalization.')));
+
+ // See https://dev.twitter.com/docs/streaming-apis/parameters#track for
+ // more details on keywords.
+
+ return $form;
+ }
+
+ protected function buildTransactions(AphrontRequest $request) {
+ $transactions = parent::buildTransactions($request);
+
+ $keyword_string = trim($request->getStr('keywords'));
+ if ($keyword_string) {
+ $keywords = explode(' ', $keyword_string);
+ } else {
+ $keywords = array();
+ }
+ $metadata = array(
+ 'keywords' => $keywords);
+
+ $transactions[] = id(new NuanceSourceTransaction())
+ ->setTransactionType(NuanceSourceTransaction::TYPE_METADATA)
+ ->setNewValue($metadata);
+
+ return $transactions;
+ }
+
+ public function validateTransaction($type, array $type_xactions) {
+ $error = null;
+ switch ($type) {
+ case NuanceSourceTransaction::TYPE_METADATA:
+ $xaction = last($type_xactions);
+ $data = $xaction->getNewValue();
+ $keywords = $data['keywords'];
+ if (empty($keywords)) {
+ $error = new PhabricatorApplicationTransactionValidationError(
+ $type,
+ pht('Required'),
+ pht('At least one keyword is required.'),
+ $xaction);
+ $error->setIsMissingFieldError(true);
+ } else {
+ foreach ($keywords as $keyword) {
+ if (phutil_utf8_strlen($keyword) > 60) {
+ $error = new PhabricatorApplicationTransactionValidationError(
+ $type,
+ pht('Too long'),
+ pht(
+ 'At least one keyword ("%s") is too long; each keyword must '.
+ 'be 60 characters or less.', $keyword));
+ break;
+ }
+ }
+ }
+ break;
+ }
+ return $error;
+ }
+
+}
Index: src/applications/nuance/source/definition/NuanceTwitterSourceDefinition.php
===================================================================
--- /dev/null
+++ src/applications/nuance/source/definition/NuanceTwitterSourceDefinition.php
@@ -0,0 +1,16 @@
+<?php
+
+abstract class NuanceTwitterSourceDefinition
+ extends NuanceSourceDefinition {
+
+ public function updateItems() {
+ return null;
+ }
+
+ public function renderView() {
+ }
+
+ public function renderListView() {
+ }
+
+}
Index: src/applications/nuance/source/definition/NuanceTwitterUserStreamSourceDefinition.php
===================================================================
--- /dev/null
+++ src/applications/nuance/source/definition/NuanceTwitterUserStreamSourceDefinition.php
@@ -0,0 +1,112 @@
+<?php
+
+final class NuanceTwitterUserStreamSourceDefinition
+ extends NuanceTwitterSourceDefinition {
+
+ public function getName() {
+ return pht('Twitter User Stream');
+ }
+
+ public function getSourceTypeConstant() {
+ return 'twitter-user-stream';
+ }
+
+ protected function getEditFormDescription() {
+ return pht(
+ 'This source type uses the Twitter User Stream API to create items from '.
+ 'the stream of twitter events specific to the authenticated user.');
+ }
+
+ protected function augmentEditForm(
+ AphrontRequest $request,
+ AphrontFormView $form,
+ PhabricatorApplicationTransactionValidationException $ex = null) {
+
+ $source = $this->requireSourceObject();
+ $data = $source->getData();
+ $v_tweeted = false;
+ $v_mentioned = true;
+ $v_direct_messaged = true;
+ $v_followed = true;
+ if ($request->isFormPost()) {
+ $v_tweeted = $request->getExists('tweeted');
+ $v_mentioned = $request->getExists('mentioned');
+ $v_direct_messaged = $request->getExists('direct_messaged');
+ $v_followed = $request->getExists('followed');
+ } else if ($data) {
+ $v_tweeted = idx($data, 'tweeted', false);
+ $v_mentioned = idx($data, 'mentioned', true);
+ $v_direct_messaged = idx($data, 'direct_messaged', true);
+ $v_followed = idx($data, 'followed', true);
+ }
+ $e_checkboxes = null;
+ if ($ex) {
+ $e_checkboxes =
+ $ex->getShortMessage(NuanceSourceTransaction::TYPE_METADATA);
+ }
+
+ $form
+ ->appendChild(
+ id(new AphrontFormCheckboxControl())
+ ->setError($e_checkboxes)
+ ->setLabel(pht('Events'))
+ ->setName('events')
+ ->addCheckbox('tweeted',
+ 'tweeted',
+ 'User tweeted',
+ $v_tweeted)
+ ->addCheckbox('mentioned',
+ 'mentioned',
+ 'User is mentioned',
+ $v_mentioned)
+ ->addCheckbox('direct_messaged',
+ 'direct_messaged',
+ 'User is direct messaged',
+ $v_direct_messaged)
+ ->addCheckbox('followed',
+ 'followed',
+ 'User is followed',
+ $v_followed));
+
+ return $form;
+ }
+
+ protected function buildTransactions(AphrontRequest $request) {
+
+ $transactions = parent::buildTransactions($request);
+
+ $metadata = array(
+ 'tweeted' => $request->getExists('tweeted'),
+ 'mentioned' => $request->getExists('mentioned'),
+ 'direct_messaged' => $request->getExists('direct_messaged'),
+ 'followed' => $request->getExists('followed'));
+
+ $transactions[] = id(new NuanceSourceTransaction())
+ ->setTransactionType(NuanceSourceTransaction::TYPE_METADATA)
+ ->setNewValue($metadata);
+
+ return $transactions;
+ }
+
+ public function validateTransaction($type, array $type_xactions) {
+ $error = null;
+ switch ($type) {
+ case NuanceSourceTransaction::TYPE_METADATA:
+ $xaction = last($type_xactions);
+ $data = $xaction->getNewValue();
+ if (!$data['tweeted'] &&
+ !$data['mentioned'] &&
+ !$data['direct_messaged'] &&
+ !$data['followed']) {
+ $error = new PhabricatorApplicationTransactionValidationError(
+ $type,
+ pht('Required'),
+ pht('At least one event is required.'),
+ $xaction);
+ $error->setIsMissingFieldError(true);
+ }
+ break;
+ }
+ return $error;
+ }
+}
Index: src/applications/nuance/storage/NuanceSource.php
===================================================================
--- src/applications/nuance/storage/NuanceSource.php
+++ src/applications/nuance/storage/NuanceSource.php
@@ -6,7 +6,7 @@
protected $name;
protected $type;
- protected $data;
+ protected $data = array();
protected $mailKey;
protected $viewPolicy;
protected $editPolicy;
@@ -36,6 +36,16 @@
return '/nuance/source/view/'.$this->getID().'/';
}
+ public function getEditURI() {
+ if ($this->getID()) {
+ $uri = '/nuance/source/edit/'.$this->getID().'/';
+ } else {
+ $uri = '/nuance/source/new/';
+ }
+
+ return $uri;
+ }
+
public static function initializeNewSource(PhabricatorUser $actor) {
$app = id(new PhabricatorApplicationQuery())
->setViewer($actor)
@@ -47,13 +57,9 @@
$edit_policy = $app->getPolicy(
NuanceCapabilitySourceDefaultEdit::CAPABILITY);
- $definitions = NuanceSourceDefinition::getAllDefinitions();
- $lucky_definition = head($definitions);
-
return id(new NuanceSource())
->setViewPolicy($view_policy)
- ->setEditPolicy($edit_policy)
- ->setType($lucky_definition->getSourceTypeConstant());
+ ->setEditPolicy($edit_policy);
}
Index: src/applications/nuance/storage/NuanceSourceTransaction.php
===================================================================
--- src/applications/nuance/storage/NuanceSourceTransaction.php
+++ src/applications/nuance/storage/NuanceSourceTransaction.php
@@ -3,7 +3,9 @@
final class NuanceSourceTransaction
extends NuanceTransaction {
- const TYPE_NAME = 'name-source';
+ const TYPE_NAME = 'name-source';
+ const TYPE_SOURCE_TYPE = 'source-type-source';
+ const TYPE_METADATA = 'source-metadata';
public function getApplicationTransactionType() {
return NuancePHIDTypeSource::TYPECONST;
@@ -32,8 +34,67 @@
$new);
}
break;
+ case self::TYPE_SOURCE_TYPE:
+ if ($old === null) {
+ return null;
+ } else {
+ $old_definition = NuanceSourceDefinition::getDefinitionForSourceType(
+ $old);
+ $new_definition = NuanceSourceDefinition::getDefinitionForSourceType(
+ $new);
+ return pht(
+ '%s changed the source type from "%s" to "%s".',
+ $this->renderHandleLink($author_phid),
+ $old_definition->getName(),
+ $new_definition->getName());
+ break;
+ }
+ case self::TYPE_METADATA:
+ if ($old === null) {
+ return null;
+ } else {
+ return pht(
+ '%s updated the metadata.',
+ $this->renderHandleLink($author_phid));
+ break;
+ }
}
}
+ public function shouldHide() {
+ switch ($this->getTransactionType()) {
+ case self::TYPE_SOURCE_TYPE:
+ case self::TYPE_METADATA:
+ if ($this->getOldValue() === null) {
+ return true;
+ } else {
+ return false;
+ }
+ break;
+ }
+
+ return false;
+ }
+
+ public function hasChangeDetails() {
+ switch ($this->getTransactionType()) {
+ case self::TYPE_METADATA:
+ return true;
+ }
+ return parent::hasChangeDetails();
+ }
+
+ public function renderChangeDetails(PhabricatorUser $viewer) {
+ $old = $this->getOldValue();
+ $new = $this->getNewValue();
+
+ $view = id(new PhabricatorApplicationTransactionTextDiffDetailView())
+ ->setUser($viewer)
+ ->setOldText(json_encode($old))
+ ->setNewText(json_encode($new));
+
+ return $view->render();
+ }
+
}
Index: src/view/form/control/AphrontFormSelectControl.php
===================================================================
--- src/view/form/control/AphrontFormSelectControl.php
+++ src/view/form/control/AphrontFormSelectControl.php
@@ -7,6 +7,18 @@
}
private $options;
+ private $sigils = array();
+ private $metadata;
+
+ public function addSigil($sigil) {
+ $this->sigils[] = $sigil;
+ return $this;
+ }
+
+ public function setMetadata(array $data) {
+ $this->metadata = $data;
+ return $this;
+ }
public function setOptions(array $options) {
$this->options = $options;
@@ -25,6 +37,8 @@
'name' => $this->getName(),
'disabled' => $this->getDisabled() ? 'disabled' : null,
'id' => $this->getID(),
+ 'sigil' => $this->sigils ? implode(' ', $this->sigils) : null,
+ 'meta' => $this->metadata,
));
}
Index: webroot/rsrc/js/application/nuance/nuance-source-editor.js
===================================================================
--- /dev/null
+++ webroot/rsrc/js/application/nuance/nuance-source-editor.js
@@ -0,0 +1,49 @@
+/**
+ * @requires javelin-dom
+ * javelin-workflow
+ * javelin-behavior
+ * javelin-stratcom
+ * javelin-uri
+ * @provides javelin-behavior-nuance-source-editor
+ * @javelin
+ */
+
+JX.behavior('nuance-source-editor', function(config) {
+
+ JX.Stratcom.listen(
+ 'change',
+ 'source-type-select',
+ redrawSourceEditForm);
+
+ /**
+ * Since different source types require different data, warn the user
+ * they are going to blow away their changes if they change the type.
+ */
+ function redrawSourceEditForm(e) {
+ var form_id = e.getNodeData('source-type-select').form_id;
+ var form = JX.$(form_id);
+ var form_data = JX.DOM.convertFormToDictionary(form);
+ var data = { warn : !config.isNew };
+ new JX.Workflow.newFromForm(form, data)
+ .setHandler(function (r) {
+ var uri = JX.$U(config.redrawURI);
+ var params = {};
+ var key = null;
+ for (var i = 0; i < config.baseValueKeys.length; i++) {
+ key = config.baseValueKeys[i];
+ if (form_data[key]) {
+ params[key] = form_data[key];
+ }
+ }
+ params.redraw = true;
+ uri.addQueryParams(params);
+ uri.go();
+ })
+ .setCloseHandler(function (r) {
+ var select = e.getNode('source-type-select');
+ select.value = e.getNodeData('source-type-select').selected;
+ })
+ .start();
+ }
+
+});

File Metadata

Mime Type
text/plain
Expires
Tue, Oct 15, 12:54 PM (3 w, 3 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6712903
Default Alt Text
D7723.id17438.diff (34 KB)

Event Timeline