Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F17928185
D9316.id.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
27 KB
Referenced Files
None
Subscribers
None
D9316.id.diff
View Options
diff --git a/resources/sql/autopatches/20140528.chronicle.1.sql b/resources/sql/autopatches/20140528.chronicle.1.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20140528.chronicle.1.sql
@@ -0,0 +1,14 @@
+CREATE TABLE {$NAMESPACE}_chronicle.chronicle_trigger (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
+ name VARCHAR(254) NOT NULL COLLATE utf8_bin,
+ epochNext INT UNSIGNED NOT NULL,
+ epochConfig VARCHAR(4000) NOT NULL COLLATE utf8_bin,
+ actionClass VARCHAR(254) NOT NULL COLLATE utf8_bin,
+ actionData VARCHAR(4000) NOT NULL COLLATE utf8_bin,
+ viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
+ editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
+ dateCreated INT UNSIGNED NOT NULL,
+ dateModified INT UNSIGNED NOT NULL,
+ UNIQUE KEY `key_phid` (phid)
+) ENGINE=InnoDB, COLLATE utf8_general_ci;
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -112,6 +112,19 @@
'CelerityResourcesOnDisk' => 'infrastructure/celerity/resources/CelerityResourcesOnDisk.php',
'CeleritySpriteGenerator' => 'infrastructure/celerity/CeleritySpriteGenerator.php',
'CelerityStaticResourceResponse' => 'infrastructure/celerity/CelerityStaticResourceResponse.php',
+ 'ChronicleAction' => 'applications/chronicle/action/ChronicleAction.php',
+ 'ChronicleCapabilityManageTriggers' => 'applications/chronicle/capability/ChronicleCapabilityManageTriggers.php',
+ 'ChronicleController' => 'applications/chronicle/controller/ChronicleController.php',
+ 'ChronicleDAO' => 'applications/chronicle/storage/ChronicleDAO.php',
+ 'ChroniclePHIDTypeTrigger' => 'applications/chronicle/phid/ChroniclePHIDTypeTrigger.php',
+ 'ChronicleStartHarbormasterBuildAction' => 'applications/chronicle/action/ChronicleStartHarbormasterBuildAction.php',
+ 'ChronicleTrigger' => 'applications/chronicle/storage/ChronicleTrigger.php',
+ 'ChronicleTriggerAddController' => 'applications/chronicle/controller/ChronicleTriggerAddController.php',
+ 'ChronicleTriggerController' => 'applications/chronicle/controller/ChronicleTriggerController.php',
+ 'ChronicleTriggerListController' => 'applications/chronicle/controller/ChronicleTriggerListController.php',
+ 'ChronicleTriggerNextEpochTestCase' => 'applications/chronicle/storage/__tests__/ChronicleTriggerNextEpochTestCase.php',
+ 'ChronicleTriggerQuery' => 'applications/chronicle/query/ChronicleTriggerQuery.php',
+ 'ChronicleTriggerSearchEngine' => 'applications/chronicle/query/ChronicleTriggerSearchEngine.php',
'ConduitAPIMethod' => 'applications/conduit/method/ConduitAPIMethod.php',
'ConduitAPIRequest' => 'applications/conduit/protocol/ConduitAPIRequest.php',
'ConduitAPIResponse' => 'applications/conduit/protocol/ConduitAPIResponse.php',
@@ -1111,6 +1124,7 @@
'PhabricatorApplicationAuth' => 'applications/auth/application/PhabricatorApplicationAuth.php',
'PhabricatorApplicationCalendar' => 'applications/calendar/application/PhabricatorApplicationCalendar.php',
'PhabricatorApplicationChatLog' => 'applications/chatlog/applications/PhabricatorApplicationChatLog.php',
+ 'PhabricatorApplicationChronicle' => 'applications/chronicle/application/PhabricatorApplicationChronicle.php',
'PhabricatorApplicationConduit' => 'applications/conduit/application/PhabricatorApplicationConduit.php',
'PhabricatorApplicationConfig' => 'applications/config/application/PhabricatorApplicationConfig.php',
'PhabricatorApplicationConfigOptions' => 'applications/config/option/PhabricatorApplicationConfigOptions.php',
@@ -2786,6 +2800,22 @@
'CelerityResourceGraph' => 'AbstractDirectedGraph',
'CelerityResourceTransformerTestCase' => 'PhabricatorTestCase',
'CelerityResourcesOnDisk' => 'CelerityPhysicalResources',
+ 'ChronicleCapabilityManageTriggers' => 'PhabricatorPolicyCapability',
+ 'ChronicleController' => 'PhabricatorController',
+ 'ChronicleDAO' => 'PhabricatorLiskDAO',
+ 'ChroniclePHIDTypeTrigger' => 'PhabricatorPHIDType',
+ 'ChronicleStartHarbormasterBuildAction' => 'ChronicleAction',
+ 'ChronicleTrigger' =>
+ array(
+ 0 => 'ChronicleDAO',
+ 1 => 'PhabricatorPolicyInterface',
+ ),
+ 'ChronicleTriggerAddController' => 'ChronicleTriggerController',
+ 'ChronicleTriggerController' => 'ChronicleController',
+ 'ChronicleTriggerListController' => 'ChronicleTriggerController',
+ 'ChronicleTriggerNextEpochTestCase' => 'PhabricatorTestCase',
+ 'ChronicleTriggerQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
+ 'ChronicleTriggerSearchEngine' => 'PhabricatorApplicationSearchEngine',
'ConduitAPIMethod' =>
array(
0 => 'Phobject',
@@ -3866,6 +3896,7 @@
'PhabricatorApplicationAuth' => 'PhabricatorApplication',
'PhabricatorApplicationCalendar' => 'PhabricatorApplication',
'PhabricatorApplicationChatLog' => 'PhabricatorApplication',
+ 'PhabricatorApplicationChronicle' => 'PhabricatorApplication',
'PhabricatorApplicationConduit' => 'PhabricatorApplication',
'PhabricatorApplicationConfig' => 'PhabricatorApplication',
'PhabricatorApplicationConfigOptions' => 'Phobject',
diff --git a/src/applications/chronicle/action/ChronicleAction.php b/src/applications/chronicle/action/ChronicleAction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/chronicle/action/ChronicleAction.php
@@ -0,0 +1,75 @@
+<?php
+
+abstract class ChronicleAction {
+
+ public static function getImplementations() {
+ return id(new PhutilSymbolLoader())
+ ->setAncestorClass('ChronicleAction')
+ ->loadObjects();
+ }
+
+ public static function getImplementation($class) {
+ $base = idx(self::getImplementations(), $class);
+
+ if ($base) {
+ return (clone $base);
+ }
+
+ return null;
+ }
+
+ public static function requireImplementation($class) {
+ if (!$class) {
+ throw new Exception(pht('No implementation is specified!'));
+ }
+
+ $implementation = self::getImplementation($class);
+ if (!$implementation) {
+ throw new Exception(pht('No such implementation "%s" exists!', $class));
+ }
+
+ return $implementation;
+ }
+
+ /**
+ * The name of the implementation.
+ */
+ abstract public function getName();
+
+ /**
+ * The description of the implementation.
+ */
+ public function getDescription() {
+ return '';
+ }
+
+ /**
+ * Run the build target against the specified build.
+ */
+ abstract public function execute();
+
+ /**
+ * Gets the settings for this build step.
+ */
+ public function getSettings() {
+ return $this->settings;
+ }
+
+ public function getSetting($key, $default = null) {
+ return idx($this->settings, $key, $default);
+ }
+
+ /**
+ * Loads the settings for this build step implementation from a build
+ * step or target.
+ */
+ public final function loadSettings($action_data) {
+ $this->settings = $action_data;
+ return $this;
+ }
+
+ public function getFieldSpecifications() {
+ return array();
+ }
+
+}
diff --git a/src/applications/chronicle/action/ChronicleStartHarbormasterBuildAction.php b/src/applications/chronicle/action/ChronicleStartHarbormasterBuildAction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/chronicle/action/ChronicleStartHarbormasterBuildAction.php
@@ -0,0 +1,38 @@
+<?php
+
+final class ChronicleStartHarbormasterBuildAction
+ extends ChronicleAction {
+
+ public function getName() {
+ return pht('Start Harbormaster Build');
+ }
+
+ public function getDescription() {
+ return pht('Starts a Harbormaster build against a branch in a repository');
+ }
+
+ public function execute() {
+ return;
+ }
+
+ public function getFieldSpecifications() {
+ return array(
+ 'callsign' => array(
+ 'name' => pht('Repository Callsign'),
+ 'type' => 'text',
+ 'required' => true,
+ ),
+ 'branch' => array(
+ 'name' => pht('Branch or Reference'),
+ 'type' => 'text',
+ 'required' => true
+ ),
+ 'planid' => array(
+ 'name' => pht('Build Plan ID'),
+ 'type' => 'text',
+ 'required' => true,
+ ),
+ );
+ }
+
+}
diff --git a/src/applications/chronicle/application/PhabricatorApplicationChronicle.php b/src/applications/chronicle/application/PhabricatorApplicationChronicle.php
new file mode 100644
--- /dev/null
+++ b/src/applications/chronicle/application/PhabricatorApplicationChronicle.php
@@ -0,0 +1,59 @@
+<?php
+
+final class PhabricatorApplicationChronicle extends PhabricatorApplication {
+
+ public function getBaseURI() {
+ return '/chronicle/';
+ }
+
+ public function getShortDescription() {
+ return pht('Timed Events');
+ }
+
+ public function getIconName() {
+ return 'chronicle';
+ }
+
+ public function getTitleGlyph() {
+ return "\xE2\x99\xBB";
+ }
+
+ public function getApplicationGroup() {
+ return self::GROUP_UTILITIES;
+ }
+
+ public function isBeta() {
+ return true;
+ }
+
+ public function getRoutes() {
+ return array(
+ '/chronicle/' => array(
+ '(?:query/(?P<queryKey>[^/]+)/)?'
+ => 'ChronicleTriggerListController',
+ 'trigger/' => array(
+ 'add/(?:(?P<id>\d+)/)?' => 'ChronicleTriggerAddController',
+ // 'new/(?P<plan>\d+)/(?P<class>[^/]+)/'
+ // => 'ChronicleTriggerEditController',
+ // 'edit/(?:(?P<id>\d+)/)?' => 'ChronicleTriggerEditController',
+ // 'view/(?:(?P<id>\d+)/)?' => 'ChronicleTriggerViewController',
+ // 'delete/(?:(?P<id>\d+)/)?' => 'ChronicleTriggerDeleteController',
+ ),
+ ),
+ );
+ }
+
+ public function getCustomCapabilities() {
+ return array(
+ ChronicleCapabilityManageTriggers::CAPABILITY => array(
+ 'caption' => pht('Can create and manage triggers.'),
+ 'default' => PhabricatorPolicies::POLICY_ADMIN,
+ ),
+ );
+ }
+
+ public function shouldAppearInLaunchView() {
+ return false;
+ }
+
+}
diff --git a/src/applications/chronicle/capability/ChronicleCapabilityManageTriggers.php b/src/applications/chronicle/capability/ChronicleCapabilityManageTriggers.php
new file mode 100644
--- /dev/null
+++ b/src/applications/chronicle/capability/ChronicleCapabilityManageTriggers.php
@@ -0,0 +1,21 @@
+<?php
+
+final class ChronicleCapabilityManageTriggers
+ extends PhabricatorPolicyCapability {
+
+ const CAPABILITY = 'chronicle.triggers';
+
+ public function getCapabilityKey() {
+ return self::CAPABILITY;
+ }
+
+ public function getCapabilityName() {
+ return pht('Can Manage Triggers');
+ }
+
+ public function describeCapabilityRejection() {
+ return pht(
+ 'You do not have permission to manage Chronicle triggers.');
+ }
+
+}
diff --git a/src/applications/chronicle/controller/ChronicleController.php b/src/applications/chronicle/controller/ChronicleController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/chronicle/controller/ChronicleController.php
@@ -0,0 +1,18 @@
+<?php
+
+abstract class ChronicleController extends PhabricatorController {
+
+ public function buildApplicationCrumbs() {
+ $crumbs = parent::buildApplicationCrumbs();
+
+ $crumbs->addAction(
+ id(new PHUIListItemView())
+ ->setName(pht('New Trigger'))
+ ->setHref($this->getApplicationURI('trigger/add/'))
+ ->setIcon('fa-plus-square')
+ ->setWorkflow(true));
+
+ return $crumbs;
+ }
+
+}
diff --git a/src/applications/chronicle/controller/ChronicleTriggerAddController.php b/src/applications/chronicle/controller/ChronicleTriggerAddController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/chronicle/controller/ChronicleTriggerAddController.php
@@ -0,0 +1,57 @@
+<?php
+
+final class ChronicleTriggerAddController
+ extends ChronicleTriggerController {
+
+ public function processRequest() {
+ $request = $this->getRequest();
+ $viewer = $request->getUser();
+
+ $this->requireApplicationCapability(
+ ChronicleCapabilityManageTriggers::CAPABILITY);
+
+ $cancel_uri = $this->getApplicationURI('/');
+
+ $errors = array();
+ if ($request->isFormPost()) {
+
+ $errors[] = pht(
+ 'Not yet implemented.');
+
+ $class = $request->getStr('class');
+ if (!ChronicleAction::getImplementation($class)) {
+ $errors[] = pht(
+ 'Choose the type of build step you want to add.');
+ }
+ if (!$errors) {
+ $new_uri = $this->getApplicationURI("trigger/new/{$class}/");
+ return id(new AphrontRedirectResponse())->setURI($new_uri);
+ }
+ }
+
+ $control = id(new AphrontFormRadioButtonControl())
+ ->setName('class');
+
+ $all = ChronicleAction::getImplementations();
+ foreach ($all as $class => $implementation) {
+ $control->addButton(
+ $class,
+ $implementation->getName(),
+ $implementation->getDescription());
+ }
+
+ if ($errors) {
+ $errors = id(new AphrontErrorView())
+ ->setErrors($errors);
+ }
+
+ return $this->newDialog()
+ ->setTitle(pht('Create New Trigger'))
+ ->addSubmitButton(pht('Create New Trigger'))
+ ->addCancelButton($cancel_uri)
+ ->appendChild($errors)
+ ->appendParagraph(pht('Choose a type of build step to add:'))
+ ->appendChild($control);
+ }
+
+}
diff --git a/src/applications/chronicle/controller/ChronicleTriggerController.php b/src/applications/chronicle/controller/ChronicleTriggerController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/chronicle/controller/ChronicleTriggerController.php
@@ -0,0 +1,5 @@
+<?php
+
+abstract class ChronicleTriggerController extends ChronicleController {
+
+}
diff --git a/src/applications/chronicle/controller/ChronicleTriggerListController.php b/src/applications/chronicle/controller/ChronicleTriggerListController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/chronicle/controller/ChronicleTriggerListController.php
@@ -0,0 +1,44 @@
+<?php
+
+final class ChronicleTriggerListController extends ChronicleTriggerController {
+
+ private $queryKey;
+
+ public function shouldAllowPublic() {
+ return true;
+ }
+
+ public function willProcessRequest(array $data) {
+ $this->queryKey = idx($data, 'queryKey');
+ }
+
+ public function processRequest() {
+ $request = $this->getRequest();
+ $controller = id(new PhabricatorApplicationSearchController($request))
+ ->setQueryKey($this->queryKey)
+ ->setSearchEngine(new ChronicleTriggerSearchEngine())
+ ->setNavigation($this->buildSideNavView());
+
+ return $this->delegateToController($controller);
+ }
+
+ public function buildSideNavView($for_app = false) {
+ $user = $this->getRequest()->getUser();
+
+ $nav = new AphrontSideNavFilterView();
+ $nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
+
+ id(new ChronicleTriggerSearchEngine())
+ ->setViewer($user)
+ ->addNavigationItems($nav->getMenu());
+
+ $nav->selectFilter(null);
+
+ return $nav;
+ }
+
+ public function buildApplicationMenu() {
+ return $this->buildSideNavView(true)->getMenu();
+ }
+
+}
diff --git a/src/applications/chronicle/phid/ChroniclePHIDTypeTrigger.php b/src/applications/chronicle/phid/ChroniclePHIDTypeTrigger.php
new file mode 100644
--- /dev/null
+++ b/src/applications/chronicle/phid/ChroniclePHIDTypeTrigger.php
@@ -0,0 +1,45 @@
+<?php
+
+final class ChroniclePHIDTypeTrigger extends PhabricatorPHIDType {
+
+ const TYPECONST = 'CHRT';
+
+ public function getTypeConstant() {
+ return self::TYPECONST;
+ }
+
+ public function getTypeName() {
+ return pht('Chronicle Trigger');
+ }
+
+ public function newObject() {
+ return new ChronicleTrigger();
+ }
+
+ protected function buildQueryForObjects(
+ PhabricatorObjectQuery $query,
+ array $phids) {
+
+ return id(new ChronicleTriggerQuery())
+ ->withPHIDs($phids)
+ ->needBuildableHandles(true);
+ }
+
+ public function loadHandles(
+ PhabricatorHandleQuery $query,
+ array $handles,
+ array $objects) {
+
+ foreach ($handles as $phid => $handle) {
+ $trigger = $objects[$phid];
+
+ $id = $trigger->getID();
+ $name = $trigger->getName();
+
+ $handle->setURI("/chronicle/trigger/view/{$id}");
+ $handle->setName("Chronicle Trigger {$id}");
+ $handle->setFullName("Chronicle Trigger {$id}: ".$name);
+ }
+ }
+
+}
diff --git a/src/applications/chronicle/query/ChronicleTriggerQuery.php b/src/applications/chronicle/query/ChronicleTriggerQuery.php
new file mode 100644
--- /dev/null
+++ b/src/applications/chronicle/query/ChronicleTriggerQuery.php
@@ -0,0 +1,60 @@
+<?php
+
+final class ChronicleTriggerQuery
+ extends PhabricatorCursorPagedPolicyAwareQuery {
+
+ private $ids;
+ private $phids;
+
+ public function withIDs(array $ids) {
+ $this->ids = $ids;
+ return $this;
+ }
+
+ public function withPHIDs(array $phids) {
+ $this->phids = $phids;
+ return $this;
+ }
+
+ protected function loadPage() {
+ $table = new ChronicleTrigger();
+ $conn_r = $table->establishConnection('r');
+
+ $data = queryfx_all(
+ $conn_r,
+ 'SELECT * FROM %T %Q %Q %Q',
+ $table->getTableName(),
+ $this->buildWhereClause($conn_r),
+ $this->buildOrderClause($conn_r),
+ $this->buildLimitClause($conn_r));
+
+ return $table->loadAllFromArray($data);
+ }
+
+ private function buildWhereClause(AphrontDatabaseConnection $conn_r) {
+ $where = array();
+
+ if ($this->ids !== null) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'id IN (%Ld)',
+ $this->ids);
+ }
+
+ if ($this->phids !== null) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'phid IN (%Ls)',
+ $this->phids);
+ }
+
+ $where[] = $this->buildPagingClause($conn_r);
+
+ return $this->formatWhereClause($where);
+ }
+
+ public function getQueryApplicationClass() {
+ return 'PhabricatorApplicationChronicle';
+ }
+
+}
diff --git a/src/applications/chronicle/query/ChronicleTriggerSearchEngine.php b/src/applications/chronicle/query/ChronicleTriggerSearchEngine.php
new file mode 100644
--- /dev/null
+++ b/src/applications/chronicle/query/ChronicleTriggerSearchEngine.php
@@ -0,0 +1,80 @@
+<?php
+
+final class ChronicleTriggerSearchEngine
+ extends PhabricatorApplicationSearchEngine {
+
+ public function getApplicationClassName() {
+ return 'PhabricatorApplicationChronicle';
+ }
+
+ public function buildSavedQueryFromRequest(AphrontRequest $request) {
+ $saved = new PhabricatorSavedQuery();
+
+ return $saved;
+ }
+
+ public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
+ $query = id(new ChronicleTriggerQuery());
+
+ return $query;
+ }
+
+ public function buildSearchForm(
+ AphrontFormView $form,
+ PhabricatorSavedQuery $saved_query) {
+
+ // TODO
+ }
+
+ protected function getURI($path) {
+ return '/chronicle/'.$path;
+ }
+
+ public function getBuiltinQueryNames() {
+ $names = array(
+ 'all' => pht('All Triggers'),
+ );
+
+ return $names;
+ }
+
+ public function buildSavedQueryFromBuiltin($query_key) {
+
+ $query = $this->newSavedQuery();
+ $query->setQueryKey($query_key);
+
+ switch ($query_key) {
+ case 'all':
+ return $query;
+ }
+
+ return parent::buildSavedQueryFromBuiltin($query_key);
+ }
+
+ protected function renderResultList(
+ array $triggers,
+ PhabricatorSavedQuery $query,
+ array $handles) {
+ assert_instances_of($triggers, 'ChronicleTrigger');
+
+ $viewer = $this->requireViewer();
+
+ $list = new PHUIObjectItemListView();
+ $list->setCards(true);
+ foreach ($triggers as $trigger) {
+ $id = $trigger->getID();
+
+ $item = id(new PHUIObjectItemView())
+ ->setHeader(pht('Chronicle Trigger %d', $id));
+
+ if ($id) {
+ $item->setHref("/chronicle/trigger/view/{$id}");
+ }
+
+ $list->addItem($item);
+
+ }
+
+ return $list;
+ }
+}
diff --git a/src/applications/chronicle/storage/ChronicleDAO.php b/src/applications/chronicle/storage/ChronicleDAO.php
new file mode 100644
--- /dev/null
+++ b/src/applications/chronicle/storage/ChronicleDAO.php
@@ -0,0 +1,9 @@
+<?php
+
+abstract class ChronicleDAO extends PhabricatorLiskDAO {
+
+ public function getApplicationName() {
+ return 'chronicle';
+ }
+
+}
diff --git a/src/applications/chronicle/storage/ChronicleTrigger.php b/src/applications/chronicle/storage/ChronicleTrigger.php
new file mode 100644
--- /dev/null
+++ b/src/applications/chronicle/storage/ChronicleTrigger.php
@@ -0,0 +1,143 @@
+<?php
+
+final class ChronicleTrigger extends ChronicleDAO
+ implements PhabricatorPolicyInterface {
+
+ protected $name;
+ protected $epochNext;
+ protected $epochConfig = array();
+ protected $actionClass;
+ protected $actionData = array();
+
+ const EPOCH_NEVER = 0;
+
+ const TYPE_NEVER = 'never';
+ const TYPE_ONCE = 'once';
+ const TYPE_DAILY = 'daily';
+
+ const DAY_SUNDAY = 0;
+ const DAY_MONDAY = 1;
+ const DAY_TUESDAY = 2;
+ const DAY_WEDNESDAY = 3;
+ const DAY_THURSDAY = 4;
+ const DAY_FRIDAY = 5;
+ const DAY_SATURDAY = 6;
+
+ public function getConfiguration() {
+ return array(
+ self::CONFIG_AUX_PHID => true,
+ self::CONFIG_SERIALIZATION => array(
+ 'actionData' => self::SERIALIZATION_JSON,
+ 'epochConfig' => self::SERIALIZATION_JSON
+ )
+ ) + parent::getConfiguration();
+ }
+
+ public function generatePHID() {
+ return PhabricatorPHID::generateNewPHID(
+ ChroniclePHIDTypeTrigger::TYPECONST);
+ }
+
+ public function getEpochType() {
+ return $this->getEpochConfigSetting('type', self::TYPE_NEVER);
+ }
+
+ public function setEpochType($type) {
+ return $this->setEpochConfigSetting('type', $type);
+ }
+
+ public function getEpochConfigSetting($key, $default = null) {
+ return idx($this->epochConfig, $key, $default);
+ }
+
+ public function setEpochConfigSetting($key, $value) {
+ $this->epochConfig[$key] = $value;
+ return $this;
+ }
+
+ public function calculateNextEpoch($now = null) {
+ if ($now === null) {
+ $now = time();
+ }
+
+ switch ($this->getEpochType()) {
+ case self::TYPE_NEVER:
+ return self::EPOCH_NEVER;
+ case self::TYPE_ONCE:
+ $epoch = $this->getEpochConfigSetting('epoch');
+ if ($epoch >= $now) {
+ return $epoch;
+ } else {
+ return self::EPOCH_NEVER;
+ }
+ case self::TYPE_DAILY:
+ $days = array(
+ self::DAY_SUNDAY => 'Sunday',
+ self::DAY_MONDAY => 'Monday',
+ self::DAY_TUESDAY => 'Tuesday',
+ self::DAY_WEDNESDAY => 'Wednesday',
+ self::DAY_THURSDAY => 'Thursday',
+ self::DAY_FRIDAY => 'Friday',
+ self::DAY_SATURDAY => 'Saturday');
+
+ $days_active = $this->getEpochConfigSetting('days');
+ $times_active = $this->getEpochConfigSetting('times');
+
+ $current_next = null;
+ foreach ($days as $key => $value) {
+ if (in_array($key, $days_active)) {
+ $midnight = strtotime('midnight '.$value, $now);
+
+ foreach ($times_active as $time) {
+ $time_of_day = $midnight + $time;
+
+ if ($time_of_day == $now) {
+ // This is as close as we'll get!
+ return $now;
+ }
+
+ if ($time_of_day < $now) {
+ $midnight = strtotime('midnight next '.$value, $now);
+ $time_of_day = $midnight + $time;
+ }
+
+ if ($current_next === null || $time_of_day < $current_next) {
+ $current_next = $time_of_day;
+ }
+ }
+ }
+ }
+
+ return $current_next;
+ }
+ }
+
+
+/* -( 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;
+ }
+
+ public function describeAutomaticCapability($capability) {
+ return null;
+ }
+
+}
diff --git a/src/applications/chronicle/storage/__tests__/ChronicleTriggerNextEpochTestCase.php b/src/applications/chronicle/storage/__tests__/ChronicleTriggerNextEpochTestCase.php
new file mode 100644
--- /dev/null
+++ b/src/applications/chronicle/storage/__tests__/ChronicleTriggerNextEpochTestCase.php
@@ -0,0 +1,103 @@
+<?php
+
+final class ChronicleTriggerNextEpochTestCase extends PhabricatorTestCase {
+
+ public function testUnsetEpoch() {
+ $trigger = id(new ChronicleTrigger())
+ ->makeEphemeral();
+
+ $this->assertEqual(
+ ChronicleTrigger::EPOCH_NEVER,
+ $trigger->calculateNextEpoch());
+ }
+
+ public function testNeverEpoch() {
+ $trigger = id(new ChronicleTrigger())
+ ->setEpochType(ChronicleTrigger::TYPE_NEVER)
+ ->makeEphemeral();
+
+ $this->assertEqual(
+ ChronicleTrigger::EPOCH_NEVER,
+ $trigger->calculateNextEpoch());
+ }
+
+ public function testOnceEpoch() {
+ $now = time();
+
+ $trigger = id(new ChronicleTrigger())
+ ->setEpochType(ChronicleTrigger::TYPE_ONCE)
+ ->setEpochConfigSetting('epoch', $now)
+ ->makeEphemeral();
+
+ $this->assertEqual(
+ $now,
+ $trigger->calculateNextEpoch($now - 1));
+ $this->assertEqual(
+ $now,
+ $trigger->calculateNextEpoch($now));
+ $this->assertEqual(
+ ChronicleTrigger::EPOCH_NEVER,
+ $trigger->calculateNextEpoch($now + 1));
+ }
+
+ public function testDailyEpoch() {
+ $now = 1401271590;
+
+ $next_monday = 1401638400;
+ $next_wednesday = 1401811200;
+ $next_thursday = 1401292800;
+ $next_sunday = 1401552000;
+
+ $trigger = id(new ChronicleTrigger())
+ ->setEpochType(ChronicleTrigger::TYPE_DAILY)
+ ->setEpochConfigSetting('days', array(
+ ChronicleTrigger::DAY_MONDAY,
+ ChronicleTrigger::DAY_WEDNESDAY,
+ ChronicleTrigger::DAY_THURSDAY,
+ ChronicleTrigger::DAY_SUNDAY))
+ ->setEpochConfigSetting('times', array(3600 * 2)) // 2am
+ ->makeEphemeral();
+
+ $this->assertEqual(
+ $next_thursday,
+ $trigger->calculateNextEpoch($now));
+
+ $this->assertEqual(
+ $next_thursday,
+ $trigger->calculateNextEpoch($next_thursday - 1));
+ $this->assertEqual(
+ $next_thursday,
+ $trigger->calculateNextEpoch($next_thursday));
+ $this->assertEqual(
+ $next_sunday,
+ $trigger->calculateNextEpoch($next_thursday + 1));
+
+ $this->assertEqual(
+ $next_sunday,
+ $trigger->calculateNextEpoch($next_sunday - 1));
+ $this->assertEqual(
+ $next_sunday,
+ $trigger->calculateNextEpoch($next_sunday));
+ $this->assertEqual(
+ $next_monday,
+ $trigger->calculateNextEpoch($next_sunday + 1));
+
+ $this->assertEqual(
+ $next_monday,
+ $trigger->calculateNextEpoch($next_monday - 1));
+ $this->assertEqual(
+ $next_monday,
+ $trigger->calculateNextEpoch($next_monday));
+ $this->assertEqual(
+ $next_wednesday,
+ $trigger->calculateNextEpoch($next_monday + 1));
+
+ $this->assertEqual(
+ $next_wednesday,
+ $trigger->calculateNextEpoch($next_wednesday - 1));
+ $this->assertEqual(
+ $next_wednesday,
+ $trigger->calculateNextEpoch($next_wednesday));
+ }
+
+}
diff --git a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
--- a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
+++ b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
@@ -119,6 +119,7 @@
'db.phragment' => array(),
'db.dashboard' => array(),
'db.system' => array(),
+ 'db.chronicle' => array(),
'0000.legacy.sql' => array(
'legacy' => 0,
),
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Thu, Jul 31, 3:52 PM (5 d, 22 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
8733406
Default Alt Text
D9316.id.diff (27 KB)
Attached To
Mode
D9316: Chronicle v0
Attached
Detach File
Event Timeline
Log In to Comment