diff --git a/bin/trigger b/bin/trigger new file mode 120000 index 0000000000..37d4b4a7cb --- /dev/null +++ b/bin/trigger @@ -0,0 +1 @@ +../scripts/setup/manage_trigger.php \ No newline at end of file diff --git a/scripts/setup/manage_trigger.php b/scripts/setup/manage_trigger.php new file mode 100755 index 0000000000..7624c1f1de --- /dev/null +++ b/scripts/setup/manage_trigger.php @@ -0,0 +1,21 @@ +#!/usr/bin/env php +setTagline('manage triggers'); +$args->setSynopsis(<<parseStandardArguments(); + +$workflows = id(new PhutilSymbolLoader()) + ->setAncestorClass('PhabricatorWorkerTriggerManagementWorkflow') + ->loadObjects(); +$workflows[] = new PhutilHelpArgumentWorkflow(); +$args->parseWorkflows($workflows); diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index f31547cd97..4a6610d31d 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2603,6 +2603,8 @@ phutil_register_library_map(array( 'PhabricatorWorkerTestCase' => 'infrastructure/daemon/workers/__tests__/PhabricatorWorkerTestCase.php', 'PhabricatorWorkerTrigger' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerTrigger.php', 'PhabricatorWorkerTriggerEvent' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerTriggerEvent.php', + 'PhabricatorWorkerTriggerManagementFireWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerTriggerManagementFireWorkflow.php', + 'PhabricatorWorkerTriggerManagementWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerTriggerManagementWorkflow.php', 'PhabricatorWorkerTriggerPHIDType' => 'infrastructure/daemon/workers/phid/PhabricatorWorkerTriggerPHIDType.php', 'PhabricatorWorkerTriggerQuery' => 'infrastructure/daemon/workers/query/PhabricatorWorkerTriggerQuery.php', 'PhabricatorWorkerYieldException' => 'infrastructure/daemon/workers/exception/PhabricatorWorkerYieldException.php', @@ -5889,6 +5891,8 @@ phutil_register_library_map(array( 'PhabricatorDestructibleInterface', ), 'PhabricatorWorkerTriggerEvent' => 'PhabricatorWorkerDAO', + 'PhabricatorWorkerTriggerManagementFireWorkflow' => 'PhabricatorWorkerTriggerManagementWorkflow', + 'PhabricatorWorkerTriggerManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorWorkerTriggerPHIDType' => 'PhabricatorPHIDType', 'PhabricatorWorkerTriggerQuery' => 'PhabricatorOffsetPagedQuery', 'PhabricatorWorkerYieldException' => 'Exception', diff --git a/src/infrastructure/daemon/workers/management/PhabricatorWorkerTriggerManagementFireWorkflow.php b/src/infrastructure/daemon/workers/management/PhabricatorWorkerTriggerManagementFireWorkflow.php new file mode 100644 index 0000000000..5fe5a8bf29 --- /dev/null +++ b/src/infrastructure/daemon/workers/management/PhabricatorWorkerTriggerManagementFireWorkflow.php @@ -0,0 +1,136 @@ +setName('fire') + ->setExamples('**fire** --id __id__') + ->setSynopsis( + pht( + 'Activates selected triggers, firing them immediately.')) + ->setArguments( + array_merge( + array( + array( + 'name' => 'now', + 'param' => 'time', + 'help' => pht( + 'Fire the trigger as though the current time is a given '. + 'time. This allows you to test how a trigger would behave '. + 'if activated in the past or future. Defaults to the actual '. + 'current time.'), + ), + array( + 'name' => 'last', + 'param' => 'time', + 'help' => pht( + 'Fire the trigger as though the last event occurred at a '. + 'given time. Defaults to the actual last event time.'), + ), + array( + 'name' => 'next', + 'param' => 'time', + 'help' => pht( + 'Fire the trigger as though the next event was scheduled '. + 'at a given time. Defaults to the actual time when the '. + 'event is next scheduled to fire.'), + ), + ), + $this->getTriggerSelectionArguments())); + } + + public function execute(PhutilArgumentParser $args) { + $console = PhutilConsole::getConsole(); + $viewer = $this->getViewer(); + $triggers = $this->loadTriggers($args); + + $now = $args->getArg('now'); + $now = $this->parseTime($now); + if (!$now) { + $now = PhabricatorTime::getNow(); + } + + PhabricatorTime::pushTime($now, date_default_timezone_get()); + + $console->writeOut( + "%s\n", + pht( + 'Set current time to %s.', + phabricator_datetime(PhabricatorTime::getNow(), $viewer))); + + $last_time = $this->parseTime($args->getArg('last')); + $next_time = $this->parseTime($args->getArg('next')); + + PhabricatorWorker::setRunAllTasksInProcess(true); + + foreach ($triggers as $trigger) { + $console->writeOut( + "%s\n", + pht('Executing trigger %s.', $this->describeTrigger($trigger))); + + $event = $trigger->getEvent(); + if ($event) { + if (!$last_time) { + $last_time = $event->getLastEventEpoch(); + } + if (!$next_time) { + $next_time = $event->getNextEventEpoch(); + } + } + + if (!$next_time) { + $console->writeOut( + "%s\n", + pht( + 'Trigger is not scheduled to execute. Use --at to simluate '. + 'a scheduled event.')); + continue; + } else { + $console->writeOut( + "%s\n", + pht( + 'Executing event as though it was scheduled to execute at %s.', + phabricator_datetime($next_time, $viewer))); + } + + if (!$last_time) { + $console->writeOut( + "%s\n", + pht( + 'Executing event as though it never previously executed.')); + } else { + $console->writeOut( + "%s\n", + pht( + 'Executing event as though it previously executed at %s.', + phabricator_datetime($last_time, $viewer))); + } + + $trigger->executeTrigger($last_time, $next_time); + + $reschedule_time = $trigger->getNextEventEpoch( + $next_time, + $is_reschedule = true); + + if (!$reschedule_time) { + $console->writeOut( + "%s\n", + pht( + 'After executing under these conditions, this event would never '. + 'execute again.')); + } else { + $console->writeOut( + "%s\n", + pht( + 'After executing under these conditions, this event would '. + 'next execute at %s.', + phabricator_datetime($reschedule_time, $viewer))); + } + } + + return 0; + } + +} diff --git a/src/infrastructure/daemon/workers/management/PhabricatorWorkerTriggerManagementWorkflow.php b/src/infrastructure/daemon/workers/management/PhabricatorWorkerTriggerManagementWorkflow.php new file mode 100644 index 0000000000..40ee3edb84 --- /dev/null +++ b/src/infrastructure/daemon/workers/management/PhabricatorWorkerTriggerManagementWorkflow.php @@ -0,0 +1,57 @@ + 'id', + 'param' => 'id', + 'repeat' => true, + 'help' => pht('Select one or more triggers by ID.'), + ), + ); + } + + protected function loadTriggers(PhutilArgumentParser $args) { + $ids = $args->getArg('id'); + if (!$ids) { + throw new PhutilArgumentUsageException( + pht('Use --id to select triggers by ID.')); + } + + $triggers = id(new PhabricatorWorkerTriggerQuery()) + ->withIDs($ids) + ->needEvents(true) + ->execute(); + $triggers = mpull($triggers, null, 'getID'); + + foreach ($ids as $id) { + if (empty($triggers[$id])) { + throw new PhutilArgumentUsageException( + pht('No trigger exists with id "%s"!', $id)); + } + } + + return $triggers; + } + + protected function describeTrigger(PhabricatorWorkerTrigger $trigger) { + return pht('Trigger %d', $trigger->getID()); + } + + protected function parseTime($time) { + if (!strlen($time)) { + return null; + } + + $epoch = strtotime($time); + if ($epoch <= 0) { + throw new PhutilArgumentUsageException( + pht('Unable to parse time "%s".', $time)); + } + return $epoch; + } + +}