@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator

Allow bulk edits to be made silently if you have CLI access

Summary:
Fixes T13042. This hooks up the new "silent" mode from D18882 and makes it actually work.

The UI (where we tell you to go run some command and then reload the page) is pretty clumsy, but should solve some problems for now and can be cleaned up eventually. The actual mechanics (timeline aggregation, Herald interaction, etc.) are on firmer ground.

Test Plan:
- Made a normal bulk edit, got mail and feed stories.
- Made a silent bulk edit, no mail and no feed.
- Saw "Silent Edit" marker in timeline for silent edits:

{F5386245}

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13042

Differential Revision: https://secure.phabricator.com/D18883

+165 -5
+1
bin/bulk
··· 1 + ../scripts/setup/manage_bulk.php
+2
resources/sql/autopatches/20180119.bulk.01.silent.sql
··· 1 + ALTER TABLE {$NAMESPACE}_worker.worker_bulkjob 2 + ADD isSilent BOOL NOT NULL;
+1
scripts/manage_bulk.php
··· 1 + ../scripts/setup/manage_bulk.php
+21
scripts/setup/manage_bulk.php
··· 1 + #!/usr/bin/env php 2 + <?php 3 + 4 + $root = dirname(dirname(dirname(__FILE__))); 5 + require_once $root.'/scripts/__init_script__.php'; 6 + 7 + $args = new PhutilArgumentParser($argv); 8 + $args->setTagline(pht('manage bulk jobs')); 9 + $args->setSynopsis(<<<EOSYNOPSIS 10 + **bulk** __command__ [__options__] 11 + Manage and debug bulk jobs. 12 + 13 + EOSYNOPSIS 14 + ); 15 + $args->parseStandardArguments(); 16 + 17 + $workflows = id(new PhutilClassMapQuery()) 18 + ->setAncestorClass('PhabricatorBulkManagementWorkflow') 19 + ->execute(); 20 + $workflows[] = new PhutilHelpArgumentWorkflow(); 21 + $args->parseWorkflows($workflows);
+4
src/__phutil_library_map__.php
··· 2206 2206 'PhabricatorBulkContentSource' => 'infrastructure/daemon/contentsource/PhabricatorBulkContentSource.php', 2207 2207 'PhabricatorBulkEditGroup' => 'applications/transactions/bulk/PhabricatorBulkEditGroup.php', 2208 2208 'PhabricatorBulkEngine' => 'applications/transactions/bulk/PhabricatorBulkEngine.php', 2209 + 'PhabricatorBulkManagementMakeSilentWorkflow' => 'applications/transactions/bulk/management/PhabricatorBulkManagementMakeSilentWorkflow.php', 2210 + 'PhabricatorBulkManagementWorkflow' => 'applications/transactions/bulk/management/PhabricatorBulkManagementWorkflow.php', 2209 2211 'PhabricatorCacheDAO' => 'applications/cache/storage/PhabricatorCacheDAO.php', 2210 2212 'PhabricatorCacheEngine' => 'applications/system/engine/PhabricatorCacheEngine.php', 2211 2213 'PhabricatorCacheEngineExtension' => 'applications/system/engine/PhabricatorCacheEngineExtension.php', ··· 7504 7506 'PhabricatorBulkContentSource' => 'PhabricatorContentSource', 7505 7507 'PhabricatorBulkEditGroup' => 'Phobject', 7506 7508 'PhabricatorBulkEngine' => 'Phobject', 7509 + 'PhabricatorBulkManagementMakeSilentWorkflow' => 'PhabricatorBulkManagementWorkflow', 7510 + 'PhabricatorBulkManagementWorkflow' => 'PhabricatorManagementWorkflow', 7507 7511 'PhabricatorCacheDAO' => 'PhabricatorLiskDAO', 7508 7512 'PhabricatorCacheEngine' => 'Phobject', 7509 7513 'PhabricatorCacheEngineExtension' => 'Phobject',
+12 -3
src/applications/daemon/controller/PhabricatorDaemonBulkJobMonitorController.php
··· 49 49 return id(new AphrontRedirectResponse()) 50 50 ->setURI($job->getMonitorURI()); 51 51 } else { 52 - return $this->newDialog() 53 - ->setTitle(pht('Confirm Bulk Job')) 54 - ->appendParagraph($job->getDescriptionForConfirm()) 52 + $dialog = $this->newDialog() 53 + ->setTitle(pht('Confirm Bulk Job')); 54 + 55 + $confirm = $job->getDescriptionForConfirm(); 56 + $confirm = (array)$confirm; 57 + foreach ($confirm as $paragraph) { 58 + $dialog->appendParagraph($paragraph); 59 + } 60 + 61 + $dialog 55 62 ->appendParagraph( 56 63 pht('Start work on this bulk job?')) 57 64 ->addCancelButton($job->getManageURI(), pht('Details')) 58 65 ->addSubmitButton(pht('Start Work')); 66 + 67 + return $dialog; 59 68 } 60 69 } else { 61 70 return $this->newDialog()
+29 -1
src/applications/transactions/bulk/PhabricatorEditEngineBulkJobType.php
··· 12 12 } 13 13 14 14 public function getDescriptionForConfirm(PhabricatorWorkerBulkJob $job) { 15 - return pht( 15 + $parts = array(); 16 + 17 + $parts[] = pht( 16 18 'You are about to apply a bulk edit which will affect '. 17 19 '%s object(s).', 18 20 new PhutilNumber($job->getSize())); 21 + 22 + if ($job->getIsSilent()) { 23 + $parts[] = pht( 24 + 'If you start work now, this edit will be applied silently: it will '. 25 + 'not send mail or publish notifications.'); 26 + } else { 27 + $parts[] = pht( 28 + 'If you start work now, this edit will send mail and publish '. 29 + 'notifications normally.'); 30 + 31 + $parts[] = pht('To silence this edit, run this command:'); 32 + 33 + $command = csprintf( 34 + 'phabricator/ $ ./bin/bulk make-silent --id %R', 35 + $job->getID()); 36 + $command = (string)$command; 37 + 38 + $parts[] = phutil_tag('tt', array(), $command); 39 + 40 + $parts[] = pht( 41 + 'After running this command, reload this page to see the new setting.'); 42 + } 43 + 44 + return $parts; 19 45 } 20 46 21 47 public function getJobSize(PhabricatorWorkerBulkJob $job) { ··· 56 82 57 83 $raw_xactions = $job->getParameter('xactions'); 58 84 $xactions = $this->buildTransactions($object, $raw_xactions); 85 + $is_silent = $job->getIsSilent(); 59 86 60 87 $editor = $object->getApplicationTransactionEditor() 61 88 ->setActor($actor) 62 89 ->setContentSource($job->newContentSource()) 63 90 ->setContinueOnNoEffect(true) 64 91 ->setContinueOnMissingFields(true) 92 + ->setIsSilent($is_silent) 65 93 ->applyTransactions($object, $xactions); 66 94 } 67 95
+72
src/applications/transactions/bulk/management/PhabricatorBulkManagementMakeSilentWorkflow.php
··· 1 + <?php 2 + 3 + final class PhabricatorBulkManagementMakeSilentWorkflow 4 + extends PhabricatorBulkManagementWorkflow { 5 + 6 + protected function didConstruct() { 7 + $this 8 + ->setName('make-silent') 9 + ->setExamples('**make-silent** [options]') 10 + ->setSynopsis( 11 + pht('Configure a bulk job to execute silently.')) 12 + ->setArguments( 13 + array( 14 + array( 15 + 'name' => 'id', 16 + 'param' => 'id', 17 + 'help' => pht( 18 + 'Configure bulk job __id__ to run silently (without sending '. 19 + 'mail or publishing notifications).'), 20 + ), 21 + )); 22 + } 23 + 24 + public function execute(PhutilArgumentParser $args) { 25 + $viewer = $this->getViewer(); 26 + 27 + $id = $args->getArg('id'); 28 + if (!$id) { 29 + throw new PhutilArgumentUsageException( 30 + pht('Use "--id" to choose a bulk job to make silent.')); 31 + } 32 + 33 + $job = id(new PhabricatorWorkerBulkJobQuery()) 34 + ->setViewer($viewer) 35 + ->withIDs(array($id)) 36 + ->executeOne(); 37 + if (!$job) { 38 + throw new PhutilArgumentUsageException( 39 + pht( 40 + 'Unable to load bulk job with ID "%s".', 41 + $id)); 42 + } 43 + 44 + if ($job->getIsSilent()) { 45 + echo tsprintf( 46 + "%s\n", 47 + pht('This job is already configured to run silently.')); 48 + return 0; 49 + } 50 + 51 + if ($job->getStatus() !== PhabricatorWorkerBulkJob::STATUS_CONFIRM) { 52 + throw new PhutilArgumentUsageException( 53 + pht( 54 + 'Work has already started on job "%s". Jobs can not be '. 55 + 'reconfigured after they have been started.', 56 + $id)); 57 + } 58 + 59 + $job 60 + ->setIsSilent(true) 61 + ->save(); 62 + 63 + echo tsprintf( 64 + "%s\n", 65 + pht( 66 + 'Configured job "%s" to run silently.', 67 + $id)); 68 + 69 + return 0; 70 + } 71 + 72 + }
+4
src/applications/transactions/bulk/management/PhabricatorBulkManagementWorkflow.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorBulkManagementWorkflow 4 + extends PhabricatorManagementWorkflow {}
+4 -1
src/infrastructure/daemon/workers/storage/PhabricatorWorkerBulkJob.php
··· 21 21 protected $status; 22 22 protected $parameters = array(); 23 23 protected $size; 24 + protected $isSilent; 24 25 25 26 private $jobImplementation = self::ATTACHABLE; 26 27 ··· 34 35 'jobTypeKey' => 'text32', 35 36 'status' => 'text32', 36 37 'size' => 'uint32', 38 + 'isSilent' => 'bool', 37 39 ), 38 40 self::CONFIG_KEY_SCHEMA => array( 39 41 'key_type' => array( ··· 58 60 ->setAuthorPHID($actor->getPHID()) 59 61 ->setJobTypeKey($type->getBulkJobTypeKey()) 60 62 ->setParameters($parameters) 61 - ->attachJobImplementation($type); 63 + ->attachJobImplementation($type) 64 + ->setIsSilent(0); 62 65 63 66 $job->setSize($job->computeSize()); 64 67
+7
src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php
··· 1639 1639 ), 1640 1640 ), 1641 1641 1642 + 'You are about to apply a bulk edit which will affect '. 1643 + '%s object(s).' => array( 1644 + 'You are about to apply a bulk edit to a single object.', 1645 + 'You are about to apply a bulk edit which will affect '. 1646 + '%s objects.', 1647 + ), 1648 + 1642 1649 ); 1643 1650 } 1644 1651
+8
src/view/phui/PHUITimelineEventView.php
··· 584 584 } 585 585 $extra[] = $date; 586 586 } 587 + 588 + // If this edit was applied silently, give user a hint that they should 589 + // not expect to have received any mail or notifications. 590 + if ($this->getIsSilent()) { 591 + $extra[] = id(new PHUIIconView()) 592 + ->setIcon('fa-bell-slash', 'red') 593 + ->setTooltip(pht('Silent Edit')); 594 + } 587 595 } 588 596 589 597 $extra = javelin_tag(