@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
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge branch 'master' into redesign-2015

+2624 -425
+15
resources/sql/autopatches/20150622.bulk.1.job.sql
··· 1 + CREATE TABLE {$NAMESPACE}_worker.worker_bulkjob ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + phid VARBINARY(64) NOT NULL, 4 + authorPHID VARBINARY(64) NOT NULL, 5 + jobTypeKey VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL, 6 + status VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL, 7 + parameters LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, 8 + size INT UNSIGNED NOT NULL, 9 + dateCreated INT UNSIGNED NOT NULL, 10 + dateModified INT UNSIGNED NOT NULL, 11 + UNIQUE KEY `key_phid` (phid), 12 + KEY `key_type` (jobTypeKey), 13 + KEY `key_author` (authorPHID), 14 + KEY `key_status` (status) 15 + ) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
+9
resources/sql/autopatches/20150622.bulk.2.task.sql
··· 1 + CREATE TABLE {$NAMESPACE}_worker.worker_bulktask ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + bulkJobPHID VARBINARY(64) NOT NULL, 4 + objectPHID VARBINARY(64) NOT NULL, 5 + status VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL, 6 + data LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, 7 + KEY `key_job` (bulkJobPHID, status), 8 + KEY `key_object` (objectPHID) 9 + ) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
+19
resources/sql/autopatches/20150622.bulk.3.xaction.sql
··· 1 + CREATE TABLE {$NAMESPACE}_worker.worker_bulkjobtransaction ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + phid VARBINARY(64) NOT NULL, 4 + authorPHID VARBINARY(64) NOT NULL, 5 + objectPHID VARBINARY(64) NOT NULL, 6 + viewPolicy VARBINARY(64) NOT NULL, 7 + editPolicy VARBINARY(64) NOT NULL, 8 + commentPHID VARBINARY(64) DEFAULT NULL, 9 + commentVersion INT UNSIGNED NOT NULL, 10 + transactionType VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL, 11 + oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, 12 + newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, 13 + contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, 14 + metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, 15 + dateCreated INT UNSIGNED NOT NULL, 16 + dateModified INT UNSIGNED NOT NULL, 17 + UNIQUE KEY `key_phid` (`phid`), 18 + KEY `key_object` (`objectPHID`) 19 + ) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
+16
resources/sql/autopatches/20150622.bulk.4.edge.sql
··· 1 + CREATE TABLE {$NAMESPACE}_worker.edge ( 2 + src VARBINARY(64) NOT NULL, 3 + type INT UNSIGNED NOT NULL, 4 + dst VARBINARY(64) NOT NULL, 5 + dateCreated INT UNSIGNED NOT NULL, 6 + seq INT UNSIGNED NOT NULL, 7 + dataID INT UNSIGNED, 8 + PRIMARY KEY (src, type, dst), 9 + KEY `src` (src, type, dateCreated, seq), 10 + UNIQUE KEY `key_dst` (dst, type, src) 11 + ) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; 12 + 13 + CREATE TABLE {$NAMESPACE}_worker.edgedata ( 14 + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, 15 + data LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT} 16 + ) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
+58 -2
src/__phutil_library_map__.php
··· 379 379 'DifferentialGetWorkingCopy' => 'applications/differential/DifferentialGetWorkingCopy.php', 380 380 'DifferentialGitHubLandingStrategy' => 'applications/differential/landing/DifferentialGitHubLandingStrategy.php', 381 381 'DifferentialGitSVNIDField' => 'applications/differential/customfield/DifferentialGitSVNIDField.php', 382 + 'DifferentialHarbormasterField' => 'applications/differential/customfield/DifferentialHarbormasterField.php', 382 383 'DifferentialHiddenComment' => 'applications/differential/storage/DifferentialHiddenComment.php', 383 384 'DifferentialHostField' => 'applications/differential/customfield/DifferentialHostField.php', 384 385 'DifferentialHostedGitLandingStrategy' => 'applications/differential/landing/DifferentialHostedGitLandingStrategy.php', ··· 893 894 'HarbormasterDAO' => 'applications/harbormaster/storage/HarbormasterDAO.php', 894 895 'HarbormasterHTTPRequestBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php', 895 896 'HarbormasterLeaseHostBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterLeaseHostBuildStepImplementation.php', 897 + 'HarbormasterLintMessagesController' => 'applications/harbormaster/controller/HarbormasterLintMessagesController.php', 896 898 'HarbormasterLintPropertyView' => 'applications/harbormaster/view/HarbormasterLintPropertyView.php', 897 899 'HarbormasterManagePlansCapability' => 'applications/harbormaster/capability/HarbormasterManagePlansCapability.php', 898 900 'HarbormasterManagementBuildWorkflow' => 'applications/harbormaster/management/HarbormasterManagementBuildWorkflow.php', ··· 921 923 'HarbormasterTargetWorker' => 'applications/harbormaster/worker/HarbormasterTargetWorker.php', 922 924 'HarbormasterThrowExceptionBuildStep' => 'applications/harbormaster/step/HarbormasterThrowExceptionBuildStep.php', 923 925 'HarbormasterUIEventListener' => 'applications/harbormaster/event/HarbormasterUIEventListener.php', 926 + 'HarbormasterUnitMessagesController' => 'applications/harbormaster/controller/HarbormasterUnitMessagesController.php', 924 927 'HarbormasterUnitPropertyView' => 'applications/harbormaster/view/HarbormasterUnitPropertyView.php', 925 928 'HarbormasterUploadArtifactBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php', 926 929 'HarbormasterWaitForPreviousBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php', ··· 1082 1085 'ManiphestTaskDependedOnByTaskEdgeType' => 'applications/maniphest/edge/ManiphestTaskDependedOnByTaskEdgeType.php', 1083 1086 'ManiphestTaskDependsOnTaskEdgeType' => 'applications/maniphest/edge/ManiphestTaskDependsOnTaskEdgeType.php', 1084 1087 'ManiphestTaskDetailController' => 'applications/maniphest/controller/ManiphestTaskDetailController.php', 1088 + 'ManiphestTaskEditBulkJobType' => 'applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php', 1085 1089 'ManiphestTaskEditController' => 'applications/maniphest/controller/ManiphestTaskEditController.php', 1086 1090 'ManiphestTaskHasCommitEdgeType' => 'applications/maniphest/edge/ManiphestTaskHasCommitEdgeType.php', 1087 1091 'ManiphestTaskHasMockEdgeType' => 'applications/maniphest/edge/ManiphestTaskHasMockEdgeType.php', ··· 1708 1712 'PhabricatorCustomFieldStringIndexStorage' => 'infrastructure/customfield/storage/PhabricatorCustomFieldStringIndexStorage.php', 1709 1713 'PhabricatorCustomHeaderConfigType' => 'applications/config/custom/PhabricatorCustomHeaderConfigType.php', 1710 1714 'PhabricatorDaemon' => 'infrastructure/daemon/PhabricatorDaemon.php', 1715 + 'PhabricatorDaemonBulkJobListController' => 'applications/daemon/controller/PhabricatorDaemonBulkJobListController.php', 1716 + 'PhabricatorDaemonBulkJobMonitorController' => 'applications/daemon/controller/PhabricatorDaemonBulkJobMonitorController.php', 1717 + 'PhabricatorDaemonBulkJobViewController' => 'applications/daemon/controller/PhabricatorDaemonBulkJobViewController.php', 1711 1718 'PhabricatorDaemonConsoleController' => 'applications/daemon/controller/PhabricatorDaemonConsoleController.php', 1712 1719 'PhabricatorDaemonController' => 'applications/daemon/controller/PhabricatorDaemonController.php', 1713 1720 'PhabricatorDaemonDAO' => 'applications/daemon/storage/PhabricatorDaemonDAO.php', ··· 2111 2118 'PhabricatorMetaMTAMail' => 'applications/metamta/storage/PhabricatorMetaMTAMail.php', 2112 2119 'PhabricatorMetaMTAMailBody' => 'applications/metamta/view/PhabricatorMetaMTAMailBody.php', 2113 2120 'PhabricatorMetaMTAMailBodyTestCase' => 'applications/metamta/view/__tests__/PhabricatorMetaMTAMailBodyTestCase.php', 2121 + 'PhabricatorMetaMTAMailHasRecipientEdgeType' => 'applications/metamta/edge/PhabricatorMetaMTAMailHasRecipientEdgeType.php', 2122 + 'PhabricatorMetaMTAMailListController' => 'applications/metamta/controller/PhabricatorMetaMTAMailListController.php', 2114 2123 'PhabricatorMetaMTAMailPHIDType' => 'applications/metamta/phid/PhabricatorMetaMTAMailPHIDType.php', 2115 2124 'PhabricatorMetaMTAMailQuery' => 'applications/metamta/query/PhabricatorMetaMTAMailQuery.php', 2125 + 'PhabricatorMetaMTAMailSearchEngine' => 'applications/metamta/query/PhabricatorMetaMTAMailSearchEngine.php', 2116 2126 'PhabricatorMetaMTAMailSection' => 'applications/metamta/view/PhabricatorMetaMTAMailSection.php', 2117 2127 'PhabricatorMetaMTAMailTestCase' => 'applications/metamta/storage/__tests__/PhabricatorMetaMTAMailTestCase.php', 2128 + 'PhabricatorMetaMTAMailViewController' => 'applications/metamta/controller/PhabricatorMetaMTAMailViewController.php', 2118 2129 'PhabricatorMetaMTAMailableDatasource' => 'applications/metamta/typeahead/PhabricatorMetaMTAMailableDatasource.php', 2119 2130 'PhabricatorMetaMTAMailableFunctionDatasource' => 'applications/metamta/typeahead/PhabricatorMetaMTAMailableFunctionDatasource.php', 2120 2131 'PhabricatorMetaMTAMailgunReceiveController' => 'applications/metamta/controller/PhabricatorMetaMTAMailgunReceiveController.php', ··· 2815 2826 'PhabricatorWorkerActiveTask' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php', 2816 2827 'PhabricatorWorkerArchiveTask' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerArchiveTask.php', 2817 2828 'PhabricatorWorkerArchiveTaskQuery' => 'infrastructure/daemon/workers/query/PhabricatorWorkerArchiveTaskQuery.php', 2829 + 'PhabricatorWorkerBulkJob' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerBulkJob.php', 2830 + 'PhabricatorWorkerBulkJobCreateWorker' => 'infrastructure/daemon/workers/bulk/PhabricatorWorkerBulkJobCreateWorker.php', 2831 + 'PhabricatorWorkerBulkJobEditor' => 'infrastructure/daemon/workers/editor/PhabricatorWorkerBulkJobEditor.php', 2832 + 'PhabricatorWorkerBulkJobPHIDType' => 'infrastructure/daemon/workers/phid/PhabricatorWorkerBulkJobPHIDType.php', 2833 + 'PhabricatorWorkerBulkJobQuery' => 'infrastructure/daemon/workers/query/PhabricatorWorkerBulkJobQuery.php', 2834 + 'PhabricatorWorkerBulkJobSearchEngine' => 'infrastructure/daemon/workers/query/PhabricatorWorkerBulkJobSearchEngine.php', 2835 + 'PhabricatorWorkerBulkJobTaskWorker' => 'infrastructure/daemon/workers/bulk/PhabricatorWorkerBulkJobTaskWorker.php', 2836 + 'PhabricatorWorkerBulkJobTestCase' => 'infrastructure/daemon/workers/__tests__/PhabricatorWorkerBulkJobTestCase.php', 2837 + 'PhabricatorWorkerBulkJobTransaction' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerBulkJobTransaction.php', 2838 + 'PhabricatorWorkerBulkJobTransactionQuery' => 'infrastructure/daemon/workers/query/PhabricatorWorkerBulkJobTransactionQuery.php', 2839 + 'PhabricatorWorkerBulkJobType' => 'infrastructure/daemon/workers/bulk/PhabricatorWorkerBulkJobType.php', 2840 + 'PhabricatorWorkerBulkJobWorker' => 'infrastructure/daemon/workers/bulk/PhabricatorWorkerBulkJobWorker.php', 2841 + 'PhabricatorWorkerBulkTask' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerBulkTask.php', 2818 2842 'PhabricatorWorkerDAO' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerDAO.php', 2819 2843 'PhabricatorWorkerLeaseQuery' => 'infrastructure/daemon/workers/query/PhabricatorWorkerLeaseQuery.php', 2820 2844 'PhabricatorWorkerManagementCancelWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementCancelWorkflow.php', ··· 2824 2848 'PhabricatorWorkerManagementRetryWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementRetryWorkflow.php', 2825 2849 'PhabricatorWorkerManagementWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementWorkflow.php', 2826 2850 'PhabricatorWorkerPermanentFailureException' => 'infrastructure/daemon/workers/exception/PhabricatorWorkerPermanentFailureException.php', 2851 + 'PhabricatorWorkerSchemaSpec' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerSchemaSpec.php', 2827 2852 'PhabricatorWorkerTask' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerTask.php', 2828 2853 'PhabricatorWorkerTaskData' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerTaskData.php', 2829 2854 'PhabricatorWorkerTaskDetailController' => 'applications/daemon/controller/PhabricatorWorkerTaskDetailController.php', ··· 3744 3769 'DifferentialGetWorkingCopy' => 'Phobject', 3745 3770 'DifferentialGitHubLandingStrategy' => 'DifferentialLandingStrategy', 3746 3771 'DifferentialGitSVNIDField' => 'DifferentialCustomField', 3772 + 'DifferentialHarbormasterField' => 'DifferentialCustomField', 3747 3773 'DifferentialHiddenComment' => 'DifferentialDAO', 3748 3774 'DifferentialHostField' => 'DifferentialCustomField', 3749 3775 'DifferentialHostedGitLandingStrategy' => 'DifferentialLandingStrategy', ··· 3769 3795 'DifferentialLandingStrategy' => 'Phobject', 3770 3796 'DifferentialLegacyHunk' => 'DifferentialHunk', 3771 3797 'DifferentialLineAdjustmentMap' => 'Phobject', 3772 - 'DifferentialLintField' => 'DifferentialCustomField', 3798 + 'DifferentialLintField' => 'DifferentialHarbormasterField', 3773 3799 'DifferentialLintStatus' => 'Phobject', 3774 3800 'DifferentialLocalCommitsView' => 'AphrontView', 3775 3801 'DifferentialManiphestTasksField' => 'DifferentialCoreCustomField', ··· 3845 3871 'DifferentialTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 3846 3872 'DifferentialTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 3847 3873 'DifferentialTransactionView' => 'PhabricatorApplicationTransactionView', 3848 - 'DifferentialUnitField' => 'DifferentialCustomField', 3874 + 'DifferentialUnitField' => 'DifferentialHarbormasterField', 3849 3875 'DifferentialUnitStatus' => 'Phobject', 3850 3876 'DifferentialUnitTestResult' => 'Phobject', 3851 3877 'DifferentialUpdateRevisionConduitAPIMethod' => 'DifferentialConduitAPIMethod', ··· 4356 4382 'HarbormasterDAO' => 'PhabricatorLiskDAO', 4357 4383 'HarbormasterHTTPRequestBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 4358 4384 'HarbormasterLeaseHostBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 4385 + 'HarbormasterLintMessagesController' => 'HarbormasterController', 4359 4386 'HarbormasterLintPropertyView' => 'AphrontView', 4360 4387 'HarbormasterManagePlansCapability' => 'PhabricatorPolicyCapability', 4361 4388 'HarbormasterManagementBuildWorkflow' => 'HarbormasterManagementWorkflow', ··· 4384 4411 'HarbormasterTargetWorker' => 'HarbormasterWorker', 4385 4412 'HarbormasterThrowExceptionBuildStep' => 'HarbormasterBuildStepImplementation', 4386 4413 'HarbormasterUIEventListener' => 'PhabricatorEventListener', 4414 + 'HarbormasterUnitMessagesController' => 'HarbormasterController', 4387 4415 'HarbormasterUnitPropertyView' => 'AphrontView', 4388 4416 'HarbormasterUploadArtifactBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 4389 4417 'HarbormasterWaitForPreviousBuildStepImplementation' => 'HarbormasterBuildStepImplementation', ··· 4584 4612 'ManiphestTaskDependedOnByTaskEdgeType' => 'PhabricatorEdgeType', 4585 4613 'ManiphestTaskDependsOnTaskEdgeType' => 'PhabricatorEdgeType', 4586 4614 'ManiphestTaskDetailController' => 'ManiphestController', 4615 + 'ManiphestTaskEditBulkJobType' => 'PhabricatorWorkerBulkJobType', 4587 4616 'ManiphestTaskEditController' => 'ManiphestController', 4588 4617 'ManiphestTaskHasCommitEdgeType' => 'PhabricatorEdgeType', 4589 4618 'ManiphestTaskHasMockEdgeType' => 'PhabricatorEdgeType', ··· 5295 5324 'PhabricatorCustomFieldStringIndexStorage' => 'PhabricatorCustomFieldIndexStorage', 5296 5325 'PhabricatorCustomHeaderConfigType' => 'PhabricatorConfigOptionType', 5297 5326 'PhabricatorDaemon' => 'PhutilDaemon', 5327 + 'PhabricatorDaemonBulkJobListController' => 'PhabricatorDaemonController', 5328 + 'PhabricatorDaemonBulkJobMonitorController' => 'PhabricatorDaemonController', 5329 + 'PhabricatorDaemonBulkJobViewController' => 'PhabricatorDaemonController', 5298 5330 'PhabricatorDaemonConsoleController' => 'PhabricatorDaemonController', 5299 5331 'PhabricatorDaemonController' => 'PhabricatorController', 5300 5332 'PhabricatorDaemonDAO' => 'PhabricatorLiskDAO', ··· 5759 5791 ), 5760 5792 'PhabricatorMetaMTAMailBody' => 'Phobject', 5761 5793 'PhabricatorMetaMTAMailBodyTestCase' => 'PhabricatorTestCase', 5794 + 'PhabricatorMetaMTAMailHasRecipientEdgeType' => 'PhabricatorEdgeType', 5795 + 'PhabricatorMetaMTAMailListController' => 'PhabricatorMetaMTAController', 5762 5796 'PhabricatorMetaMTAMailPHIDType' => 'PhabricatorPHIDType', 5763 5797 'PhabricatorMetaMTAMailQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 5798 + 'PhabricatorMetaMTAMailSearchEngine' => 'PhabricatorApplicationSearchEngine', 5764 5799 'PhabricatorMetaMTAMailSection' => 'Phobject', 5765 5800 'PhabricatorMetaMTAMailTestCase' => 'PhabricatorTestCase', 5801 + 'PhabricatorMetaMTAMailViewController' => 'PhabricatorMetaMTAController', 5766 5802 'PhabricatorMetaMTAMailableDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 5767 5803 'PhabricatorMetaMTAMailableFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 5768 5804 'PhabricatorMetaMTAMailgunReceiveController' => 'PhabricatorMetaMTAController', ··· 6593 6629 'PhabricatorWorkerActiveTask' => 'PhabricatorWorkerTask', 6594 6630 'PhabricatorWorkerArchiveTask' => 'PhabricatorWorkerTask', 6595 6631 'PhabricatorWorkerArchiveTaskQuery' => 'PhabricatorQuery', 6632 + 'PhabricatorWorkerBulkJob' => array( 6633 + 'PhabricatorWorkerDAO', 6634 + 'PhabricatorPolicyInterface', 6635 + 'PhabricatorSubscribableInterface', 6636 + 'PhabricatorApplicationTransactionInterface', 6637 + 'PhabricatorDestructibleInterface', 6638 + ), 6639 + 'PhabricatorWorkerBulkJobCreateWorker' => 'PhabricatorWorkerBulkJobWorker', 6640 + 'PhabricatorWorkerBulkJobEditor' => 'PhabricatorApplicationTransactionEditor', 6641 + 'PhabricatorWorkerBulkJobPHIDType' => 'PhabricatorPHIDType', 6642 + 'PhabricatorWorkerBulkJobQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 6643 + 'PhabricatorWorkerBulkJobSearchEngine' => 'PhabricatorApplicationSearchEngine', 6644 + 'PhabricatorWorkerBulkJobTaskWorker' => 'PhabricatorWorkerBulkJobWorker', 6645 + 'PhabricatorWorkerBulkJobTestCase' => 'PhabricatorTestCase', 6646 + 'PhabricatorWorkerBulkJobTransaction' => 'PhabricatorApplicationTransaction', 6647 + 'PhabricatorWorkerBulkJobTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 6648 + 'PhabricatorWorkerBulkJobType' => 'Phobject', 6649 + 'PhabricatorWorkerBulkJobWorker' => 'PhabricatorWorker', 6650 + 'PhabricatorWorkerBulkTask' => 'PhabricatorWorkerDAO', 6596 6651 'PhabricatorWorkerDAO' => 'PhabricatorLiskDAO', 6597 6652 'PhabricatorWorkerLeaseQuery' => 'PhabricatorQuery', 6598 6653 'PhabricatorWorkerManagementCancelWorkflow' => 'PhabricatorWorkerManagementWorkflow', ··· 6602 6657 'PhabricatorWorkerManagementRetryWorkflow' => 'PhabricatorWorkerManagementWorkflow', 6603 6658 'PhabricatorWorkerManagementWorkflow' => 'PhabricatorManagementWorkflow', 6604 6659 'PhabricatorWorkerPermanentFailureException' => 'Exception', 6660 + 'PhabricatorWorkerSchemaSpec' => 'PhabricatorConfigSchemaSpec', 6605 6661 'PhabricatorWorkerTask' => 'PhabricatorWorkerDAO', 6606 6662 'PhabricatorWorkerTaskData' => 'PhabricatorWorkerDAO', 6607 6663 'PhabricatorWorkerTaskDetailController' => 'PhabricatorDaemonController',
+9
src/applications/daemon/application/PhabricatorDaemonsApplication.php
··· 46 46 '(?P<id>[1-9]\d*)/' => 'PhabricatorDaemonLogViewController', 47 47 ), 48 48 'event/(?P<id>[1-9]\d*)/' => 'PhabricatorDaemonLogEventViewController', 49 + 'bulk/' => array( 50 + '(?:query/(?P<queryKey>[^/]+)/)?' => 51 + 'PhabricatorDaemonBulkJobListController', 52 + 'monitor/(?P<id>\d+)/' => 53 + 'PhabricatorDaemonBulkJobMonitorController', 54 + 'view/(?P<id>\d+)/' => 55 + 'PhabricatorDaemonBulkJobViewController', 56 + 57 + ), 49 58 ), 50 59 ); 51 60 }
+31
src/applications/daemon/controller/PhabricatorDaemonBulkJobListController.php
··· 1 + <?php 2 + 3 + final class PhabricatorDaemonBulkJobListController 4 + extends PhabricatorDaemonController { 5 + 6 + public function shouldAllowPublic() { 7 + return true; 8 + } 9 + 10 + public function handleRequest(AphrontRequest $request) { 11 + $controller = id(new PhabricatorApplicationSearchController()) 12 + ->setQueryKey($request->getURIData('queryKey')) 13 + ->setSearchEngine(new PhabricatorWorkerBulkJobSearchEngine()) 14 + ->setNavigation($this->buildSideNavView()); 15 + return $this->delegateToController($controller); 16 + } 17 + 18 + protected function buildSideNavView($for_app = false) { 19 + $user = $this->getRequest()->getUser(); 20 + 21 + $nav = new AphrontSideNavFilterView(); 22 + $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); 23 + 24 + id(new PhabricatorWorkerBulkJobSearchEngine()) 25 + ->setViewer($user) 26 + ->addNavigationItems($nav->getMenu()); 27 + $nav->selectFilter(null); 28 + 29 + return $nav; 30 + } 31 + }
+165
src/applications/daemon/controller/PhabricatorDaemonBulkJobMonitorController.php
··· 1 + <?php 2 + 3 + final class PhabricatorDaemonBulkJobMonitorController 4 + extends PhabricatorDaemonController { 5 + 6 + public function shouldAllowPublic() { 7 + return true; 8 + } 9 + 10 + public function handleRequest(AphrontRequest $request) { 11 + $viewer = $this->getViewer(); 12 + 13 + $job = id(new PhabricatorWorkerBulkJobQuery()) 14 + ->setViewer($viewer) 15 + ->withIDs(array($request->getURIData('id'))) 16 + ->executeOne(); 17 + if (!$job) { 18 + return new Aphront404Response(); 19 + } 20 + 21 + // If the user clicks "Continue" on a completed job, take them back to 22 + // whatever application sent them here. 23 + if ($request->getStr('done')) { 24 + if ($request->isFormPost()) { 25 + $done_uri = $job->getDoneURI(); 26 + return id(new AphrontRedirectResponse())->setURI($done_uri); 27 + } 28 + } 29 + 30 + $title = pht('Bulk Job %d', $job->getID()); 31 + 32 + if ($job->getStatus() == PhabricatorWorkerBulkJob::STATUS_CONFIRM) { 33 + $can_edit = PhabricatorPolicyFilter::hasCapability( 34 + $viewer, 35 + $job, 36 + PhabricatorPolicyCapability::CAN_EDIT); 37 + 38 + if ($can_edit) { 39 + if ($request->isFormPost()) { 40 + $type_status = PhabricatorWorkerBulkJobTransaction::TYPE_STATUS; 41 + 42 + $xactions = array(); 43 + $xactions[] = id(new PhabricatorWorkerBulkJobTransaction()) 44 + ->setTransactionType($type_status) 45 + ->setNewValue(PhabricatorWorkerBulkJob::STATUS_WAITING); 46 + 47 + $editor = id(new PhabricatorWorkerBulkJobEditor()) 48 + ->setActor($viewer) 49 + ->setContentSourceFromRequest($request) 50 + ->setContinueOnMissingFields(true) 51 + ->applyTransactions($job, $xactions); 52 + 53 + return id(new AphrontRedirectResponse()) 54 + ->setURI($job->getMonitorURI()); 55 + } else { 56 + return $this->newDialog() 57 + ->setTitle(pht('Confirm Bulk Job')) 58 + ->appendParagraph($job->getDescriptionForConfirm()) 59 + ->appendParagraph( 60 + pht('Start work on this bulk job?')) 61 + ->addCancelButton($job->getManageURI(), pht('Details')) 62 + ->addSubmitButton(pht('Start Work')); 63 + } 64 + } else { 65 + return $this->newDialog() 66 + ->setTitle(pht('Waiting For Confirmation')) 67 + ->appendParagraph( 68 + pht( 69 + 'This job is waiting for confirmation before work begins.')) 70 + ->addCancelButotn($job->getManageURI(), pht('Details')); 71 + } 72 + } 73 + 74 + 75 + $dialog = $this->newDialog() 76 + ->setTitle(pht('%s: %s', $title, $job->getStatusName())) 77 + ->addCancelButton($job->getManageURI(), pht('Details')); 78 + 79 + switch ($job->getStatus()) { 80 + case PhabricatorWorkerBulkJob::STATUS_WAITING: 81 + $dialog->appendParagraph( 82 + pht('This job is waiting for tasks to be queued.')); 83 + break; 84 + case PhabricatorWorkerBulkJob::STATUS_RUNNING: 85 + $dialog->appendParagraph( 86 + pht('This job is running.')); 87 + break; 88 + case PhabricatorWorkerBulkJob::STATUS_COMPLETE: 89 + $dialog->appendParagraph( 90 + pht('This job is complete.')); 91 + break; 92 + } 93 + 94 + $counts = $job->loadTaskStatusCounts(); 95 + if ($counts) { 96 + $dialog->appendParagraph($this->renderProgress($counts)); 97 + } 98 + 99 + switch ($job->getStatus()) { 100 + case PhabricatorWorkerBulkJob::STATUS_COMPLETE: 101 + $dialog->addHiddenInput('done', true); 102 + $dialog->addSubmitButton(pht('Continue')); 103 + break; 104 + default: 105 + Javelin::initBehavior('bulk-job-reload'); 106 + break; 107 + } 108 + 109 + return $dialog; 110 + } 111 + 112 + private function renderProgress(array $counts) { 113 + $this->requireResource('bulk-job-css'); 114 + 115 + $states = array( 116 + PhabricatorWorkerBulkTask::STATUS_DONE => array( 117 + 'class' => 'bulk-job-progress-slice-green', 118 + ), 119 + PhabricatorWorkerBulkTask::STATUS_RUNNING => array( 120 + 'class' => 'bulk-job-progress-slice-blue', 121 + ), 122 + PhabricatorWorkerBulkTask::STATUS_WAITING => array( 123 + 'class' => 'bulk-job-progress-slice-empty', 124 + ), 125 + PhabricatorWorkerBulkTask::STATUS_FAIL => array( 126 + 'class' => 'bulk-job-progress-slice-red', 127 + ), 128 + ); 129 + 130 + $total = array_sum($counts); 131 + $offset = 0; 132 + $bars = array(); 133 + foreach ($states as $state => $spec) { 134 + $size = idx($counts, $state, 0); 135 + if (!$size) { 136 + continue; 137 + } 138 + 139 + $classes = array(); 140 + $classes[] = 'bulk-job-progress-slice'; 141 + $classes[] = $spec['class']; 142 + 143 + $width = ($size / $total); 144 + $bars[] = phutil_tag( 145 + 'div', 146 + array( 147 + 'class' => implode(' ', $classes), 148 + 'style' => 149 + 'left: '.sprintf('%.2f%%', 100 * $offset).'; '. 150 + 'width: '.sprintf('%.2f%%', 100 * $width).';', 151 + ), 152 + ''); 153 + 154 + $offset += $width; 155 + } 156 + 157 + return phutil_tag( 158 + 'div', 159 + array( 160 + 'class' => 'bulk-job-progress-bar', 161 + ), 162 + $bars); 163 + } 164 + 165 + }
+83
src/applications/daemon/controller/PhabricatorDaemonBulkJobViewController.php
··· 1 + <?php 2 + 3 + final class PhabricatorDaemonBulkJobViewController 4 + extends PhabricatorDaemonController { 5 + 6 + public function shouldAllowPublic() { 7 + return true; 8 + } 9 + 10 + public function handleRequest(AphrontRequest $request) { 11 + $viewer = $this->getViewer(); 12 + 13 + $job = id(new PhabricatorWorkerBulkJobQuery()) 14 + ->setViewer($viewer) 15 + ->withIDs(array($request->getURIData('id'))) 16 + ->executeOne(); 17 + if (!$job) { 18 + return new Aphront404Response(); 19 + } 20 + 21 + $title = pht('Bulk Job %d', $job->getID()); 22 + 23 + $crumbs = $this->buildApplicationCrumbs(); 24 + $crumbs->addTextCrumb(pht('Bulk Jobs'), '/daemon/bulk/'); 25 + $crumbs->addTextCrumb($title); 26 + 27 + $properties = $this->renderProperties($job); 28 + $actions = $this->renderActions($job); 29 + $properties->setActionList($actions); 30 + 31 + $box = id(new PHUIObjectBoxView()) 32 + ->setHeaderText($title) 33 + ->addPropertyList($properties); 34 + 35 + $timeline = $this->buildTransactionTimeline( 36 + $job, 37 + new PhabricatorWorkerBulkJobTransactionQuery()); 38 + $timeline->setShouldTerminate(true); 39 + 40 + return $this->buildApplicationPage( 41 + array( 42 + $crumbs, 43 + $box, 44 + $timeline, 45 + ), 46 + array( 47 + 'title' => $title, 48 + )); 49 + } 50 + 51 + private function renderProperties(PhabricatorWorkerBulkJob $job) { 52 + $viewer = $this->getViewer(); 53 + 54 + $view = id(new PHUIPropertyListView()) 55 + ->setUser($viewer) 56 + ->setObject($job); 57 + 58 + $view->addProperty( 59 + pht('Author'), 60 + $viewer->renderHandle($job->getAuthorPHID())); 61 + 62 + $view->addProperty(pht('Status'), $job->getStatusName()); 63 + 64 + return $view; 65 + } 66 + 67 + private function renderActions(PhabricatorWorkerBulkJob $job) { 68 + $viewer = $this->getViewer(); 69 + 70 + $actions = id(new PhabricatorActionListView()) 71 + ->setUser($viewer) 72 + ->setObject($job); 73 + 74 + $actions->addAction( 75 + id(new PhabricatorActionView()) 76 + ->setHref($job->getDoneURI()) 77 + ->setIcon('fa-arrow-circle-o-right') 78 + ->setName(pht('Continue'))); 79 + 80 + return $actions; 81 + } 82 + 83 + }
+3
src/applications/daemon/controller/PhabricatorDaemonController.php
··· 10 10 $nav->addFilter('/', pht('Console')); 11 11 $nav->addFilter('log', pht('All Daemons')); 12 12 13 + $nav->addLabel(pht('Bulk Jobs')); 14 + $nav->addFilter('bulk', pht('Manage Bulk Jobs')); 15 + 13 16 return $nav; 14 17 } 15 18
+107
src/applications/differential/customfield/DifferentialHarbormasterField.php
··· 1 + <?php 2 + 3 + abstract class DifferentialHarbormasterField 4 + extends DifferentialCustomField { 5 + 6 + abstract protected function getDiffPropertyKeys(); 7 + abstract protected function loadHarbormasterTargetMessages( 8 + array $target_phids); 9 + abstract protected function getLegacyProperty(); 10 + abstract protected function newModernMessage(array $message); 11 + abstract protected function renderHarbormasterStatus( 12 + DifferentialDiff $diff, 13 + array $messages); 14 + abstract protected function newHarbormasterMessageView(array $messages); 15 + 16 + public function renderDiffPropertyViewValue(DifferentialDiff $diff) { 17 + // TODO: This load is slightly inefficient, but most of this is moving 18 + // to Harbormaster and this simplifies the transition. Eat 1-2 extra 19 + // queries for now. 20 + $keys = $this->getDiffPropertyKeys(); 21 + 22 + $properties = id(new DifferentialDiffProperty())->loadAllWhere( 23 + 'diffID = %d AND name IN (%Ls)', 24 + $diff->getID(), 25 + $keys); 26 + $properties = mpull($properties, 'getData', 'getName'); 27 + 28 + foreach ($keys as $key) { 29 + $diff->attachProperty($key, idx($properties, $key)); 30 + } 31 + 32 + $messages = array(); 33 + 34 + $buildable = $diff->getBuildable(); 35 + if ($buildable) { 36 + $target_phids = array(); 37 + foreach ($buildable->getBuilds() as $build) { 38 + foreach ($build->getBuildTargets() as $target) { 39 + $target_phids[] = $target->getPHID(); 40 + } 41 + } 42 + 43 + if ($target_phids) { 44 + $messages = $this->loadHarbormasterTargetMessages($target_phids); 45 + } 46 + } 47 + 48 + if (!$messages) { 49 + // No Harbormaster messages, so look for legacy messages and make them 50 + // look like modern messages. 51 + $legacy_messages = $diff->getProperty($this->getLegacyProperty()); 52 + if ($legacy_messages) { 53 + // Show the top 100 legacy lint messages. Previously, we showed some 54 + // by default and let the user toggle the rest. With modern messages, 55 + // we can send the user to the Harbormaster detail page. Just show 56 + // "a lot" of messages in legacy cases to try to strike a balance 57 + // between implementation simplicitly and compatibility. 58 + $legacy_messages = array_slice($legacy_messages, 0, 100); 59 + 60 + foreach ($legacy_messages as $message) { 61 + try { 62 + $modern = $this->newModernMessage($message); 63 + $messages[] = $modern; 64 + } catch (Exception $ex) { 65 + // Ignore any poorly formatted messages. 66 + } 67 + } 68 + } 69 + } 70 + 71 + $status = $this->renderHarbormasterStatus($diff, $messages); 72 + 73 + if ($messages) { 74 + $path_map = mpull($diff->loadChangesets(), 'getID', 'getFilename'); 75 + foreach ($path_map as $path => $id) { 76 + $href = '#C'.$id.'NL'; 77 + 78 + // TODO: When the diff is not the right-hand-size diff, we should 79 + // ideally adjust this URI to be absolute. 80 + 81 + $path_map[$path] = $href; 82 + } 83 + 84 + $view = $this->newHarbormasterMessageView($messages); 85 + if ($view) { 86 + $view->setPathURIMap($path_map); 87 + } 88 + } else { 89 + $view = null; 90 + } 91 + 92 + if ($view) { 93 + $view = phutil_tag( 94 + 'div', 95 + array( 96 + 'class' => 'differential-harbormaster-table-view', 97 + ), 98 + $view); 99 + } 100 + 101 + return array( 102 + $status, 103 + $view, 104 + ); 105 + } 106 + 107 + }
+26 -81
src/applications/differential/customfield/DifferentialLintField.php
··· 1 1 <?php 2 2 3 3 final class DifferentialLintField 4 - extends DifferentialCustomField { 4 + extends DifferentialHarbormasterField { 5 5 6 6 public function getFieldKey() { 7 7 return 'differential:lint'; ··· 31 31 return $this->getFieldName(); 32 32 } 33 33 34 - public function renderDiffPropertyViewValue(DifferentialDiff $diff) { 35 - // TODO: This load is slightly inefficient, but most of this is moving 36 - // to Harbormaster and this simplifies the transition. Eat 1-2 extra 37 - // queries for now. 38 - $keys = array( 34 + protected function getLegacyProperty() { 35 + return 'arc:lint'; 36 + } 37 + 38 + protected function getDiffPropertyKeys() { 39 + return array( 39 40 'arc:lint', 40 41 'arc:lint-excuse', 41 42 ); 42 - 43 - $properties = id(new DifferentialDiffProperty())->loadAllWhere( 44 - 'diffID = %d AND name IN (%Ls)', 45 - $diff->getID(), 46 - $keys); 47 - $properties = mpull($properties, 'getData', 'getName'); 48 - 49 - foreach ($keys as $key) { 50 - $diff->attachProperty($key, idx($properties, $key)); 51 - } 52 - 53 - $status = $this->renderLintStatus($diff); 54 - 55 - $lint = array(); 43 + } 56 44 57 - $buildable = $diff->getBuildable(); 58 - if ($buildable) { 59 - $target_phids = array(); 60 - foreach ($buildable->getBuilds() as $build) { 61 - foreach ($build->getBuildTargets() as $target) { 62 - $target_phids[] = $target->getPHID(); 63 - } 64 - } 45 + protected function loadHarbormasterTargetMessages(array $target_phids) { 46 + return id(new HarbormasterBuildLintMessage())->loadAllWhere( 47 + 'buildTargetPHID IN (%Ls) LIMIT 25', 48 + $target_phids); 49 + } 65 50 66 - $lint = id(new HarbormasterBuildLintMessage())->loadAllWhere( 67 - 'buildTargetPHID IN (%Ls) LIMIT 25', 68 - $target_phids); 69 - } 51 + protected function newHarbormasterMessageView(array $messages) { 52 + return id(new HarbormasterLintPropertyView()) 53 + ->setLimit(25) 54 + ->setLintMessages($messages); 55 + } 70 56 71 - if (!$lint) { 72 - // No Harbormaster messages, so look for legacy messages and make them 73 - // look like modern messages. 74 - $legacy_lint = $diff->getProperty('arc:lint'); 75 - if ($legacy_lint) { 76 - // Show the top 100 legacy lint messages. Previously, we showed some 77 - // by default and let the user toggle the rest. With modern messages, 78 - // we can send the user to the Harbormaster detail page. Just show 79 - // "a lot" of messages in legacy cases to try to strike a balance 80 - // between implementation simplicitly and compatibility. 81 - $legacy_lint = array_slice($legacy_lint, 0, 100); 82 - 83 - $target = new HarbormasterBuildTarget(); 84 - foreach ($legacy_lint as $message) { 85 - try { 86 - $modern = HarbormasterBuildLintMessage::newFromDictionary( 87 - $target, 88 - $this->getModernLintMessageDictionary($message)); 89 - $lint[] = $modern; 90 - } catch (Exception $ex) { 91 - // Ignore any poorly formatted messages. 92 - } 93 - } 94 - } 95 - } 96 - 97 - if ($lint) { 98 - $path_map = mpull($diff->loadChangesets(), 'getID', 'getFilename'); 99 - foreach ($path_map as $path => $id) { 100 - $href = '#C'.$id.'NL'; 101 - 102 - // TODO: When the diff is not the right-hand-size diff, we should 103 - // ideally adjust this URI to be absolute. 104 - 105 - $path_map[$path] = $href; 106 - } 107 - 108 - $view = id(new HarbormasterLintPropertyView()) 109 - ->setPathURIMap($path_map) 110 - ->setLintMessages($lint); 111 - } else { 112 - $view = null; 113 - } 114 - 115 - return array( 116 - $status, 117 - $view, 118 - ); 57 + protected function newModernMessage(array $message) { 58 + return HarbormasterBuildLintMessage::newFromDictionary( 59 + new HarbormasterBuildTarget(), 60 + $this->getModernLintMessageDictionary($message)); 119 61 } 120 62 121 63 public function getWarningsForDetailView() { ··· 141 83 return $warnings; 142 84 } 143 85 144 - private function renderLintStatus(DifferentialDiff $diff) { 86 + protected function renderHarbormasterStatus( 87 + DifferentialDiff $diff, 88 + array $messages) { 89 + 145 90 $colors = array( 146 91 DifferentialLintStatus::LINT_NONE => 'grey', 147 92 DifferentialLintStatus::LINT_OKAY => 'green',
+84 -70
src/applications/differential/customfield/DifferentialUnitField.php
··· 1 1 <?php 2 2 3 3 final class DifferentialUnitField 4 - extends DifferentialCustomField { 4 + extends DifferentialHarbormasterField { 5 5 6 6 public function getFieldKey() { 7 7 return 'differential:unit'; ··· 31 31 return $this->getFieldName(); 32 32 } 33 33 34 - public function renderDiffPropertyViewValue(DifferentialDiff $diff) { 35 - // TODO: See DifferentialLintField. 36 - $keys = array( 34 + protected function getLegacyProperty() { 35 + return 'arc:unit'; 36 + } 37 + 38 + protected function getDiffPropertyKeys() { 39 + return array( 37 40 'arc:unit', 38 41 'arc:unit-excuse', 39 42 ); 43 + } 40 44 41 - $properties = id(new DifferentialDiffProperty())->loadAllWhere( 42 - 'diffID = %d AND name IN (%Ls)', 43 - $diff->getID(), 44 - $keys); 45 - $properties = mpull($properties, 'getData', 'getName'); 45 + protected function loadHarbormasterTargetMessages(array $target_phids) { 46 + return id(new HarbormasterBuildUnitMessage())->loadAllWhere( 47 + 'buildTargetPHID IN (%Ls)', 48 + $target_phids); 49 + } 46 50 47 - foreach ($keys as $key) { 48 - $diff->attachProperty($key, idx($properties, $key)); 49 - } 50 - 51 - $status = $this->renderUnitStatus($diff); 52 - 53 - $unit = array(); 51 + protected function newModernMessage(array $message) { 52 + return HarbormasterBuildUnitMessage::newFromDictionary( 53 + new HarbormasterBuildTarget(), 54 + $this->getModernUnitMessageDictionary($message)); 55 + } 54 56 55 - $buildable = $diff->getBuildable(); 56 - if ($buildable) { 57 - $target_phids = array(); 58 - foreach ($buildable->getBuilds() as $build) { 59 - foreach ($build->getBuildTargets() as $target) { 60 - $target_phids[] = $target->getPHID(); 61 - } 62 - } 63 - 64 - $unit = id(new HarbormasterBuildUnitMessage())->loadAllWhere( 65 - 'buildTargetPHID IN (%Ls) LIMIT 25', 66 - $target_phids); 67 - } 68 - 69 - if (!$unit) { 70 - $legacy_unit = $diff->getProperty('arc:unit'); 71 - if ($legacy_unit) { 72 - // Show the top 100 legacy unit messages. 73 - $legacy_unit = array_slice($legacy_unit, 0, 100); 74 - 75 - $target = new HarbormasterBuildTarget(); 76 - foreach ($legacy_unit as $message) { 77 - try { 78 - $modern = HarbormasterBuildUnitMessage::newFromDictionary( 79 - $target, 80 - $this->getModernUnitMessageDictionary($message)); 81 - $unit[] = $modern; 82 - } catch (Exception $ex) { 83 - // Just ignore it if legacy messages aren't formatted like 84 - // we expect. 85 - } 86 - } 57 + protected function newHarbormasterMessageView(array $messages) { 58 + foreach ($messages as $key => $message) { 59 + if ($message->getResult() == ArcanistUnitTestResult::RESULT_PASS) { 60 + unset($messages[$key]); 87 61 } 88 62 } 89 63 90 - if ($unit) { 91 - $path_map = mpull($diff->loadChangesets(), 'getID', 'getFilename'); 92 - foreach ($path_map as $path => $id) { 93 - $href = '#C'.$id.'NL'; 94 - 95 - // TODO: When the diff is not the right-hand-size diff, we should 96 - // ideally adjust this URI to be absolute. 97 - 98 - $path_map[$path] = $href; 99 - } 100 - 101 - $view = id(new HarbormasterUnitPropertyView()) 102 - ->setPathURIMap($path_map) 103 - ->setUnitMessages($unit); 104 - } else { 105 - $view = null; 64 + if (!$messages) { 65 + return null; 106 66 } 107 67 108 - return array( 109 - $status, 110 - $view, 111 - ); 68 + return id(new HarbormasterUnitPropertyView()) 69 + ->setLimit(10) 70 + ->setHidePassingTests(true) 71 + ->setUnitMessages($messages); 112 72 } 113 73 114 74 public function getWarningsForDetailView() { ··· 132 92 return $warnings; 133 93 } 134 94 95 + protected function renderHarbormasterStatus( 96 + DifferentialDiff $diff, 97 + array $messages) { 135 98 136 - private function renderUnitStatus(DifferentialDiff $diff) { 137 99 $colors = array( 138 100 DifferentialUnitStatus::UNIT_NONE => 'grey', 139 101 DifferentialUnitStatus::UNIT_OKAY => 'green', ··· 147 109 148 110 $message = DifferentialRevisionUpdateHistoryView::getDiffUnitMessage($diff); 149 111 112 + $note = array(); 113 + 114 + $groups = mgroup($messages, 'getResult'); 115 + 116 + $groups = array_select_keys( 117 + $groups, 118 + array( 119 + ArcanistUnitTestResult::RESULT_FAIL, 120 + ArcanistUnitTestResult::RESULT_BROKEN, 121 + ArcanistUnitTestResult::RESULT_UNSOUND, 122 + ArcanistUnitTestResult::RESULT_SKIP, 123 + ArcanistUnitTestResult::RESULT_PASS, 124 + )) + $groups; 125 + 126 + foreach ($groups as $result => $group) { 127 + $count = new PhutilNumber(count($group)); 128 + switch ($result) { 129 + case ArcanistUnitTestResult::RESULT_PASS: 130 + $note[] = pht('%s Passed Test(s)', $count); 131 + break; 132 + case ArcanistUnitTestResult::RESULT_FAIL: 133 + $note[] = pht('%s Failed Test(s)', $count); 134 + break; 135 + case ArcanistUnitTestResult::RESULT_SKIP: 136 + $note[] = pht('%s Skipped Test(s)', $count); 137 + break; 138 + case ArcanistUnitTestResult::RESULT_BROKEN: 139 + $note[] = pht('%s Broken Test(s)', $count); 140 + break; 141 + case ArcanistUnitTestResult::RESULT_UNSOUND: 142 + $note[] = pht('%s Unsound Test(s)', $count); 143 + break; 144 + default: 145 + $note[] = pht('%s Other Test(s)', $count); 146 + break; 147 + } 148 + } 149 + 150 + $buildable = $diff->getBuildable(); 151 + if ($buildable) { 152 + $full_results = '/harbormaster/unit/'.$buildable->getID().'/'; 153 + $note[] = phutil_tag( 154 + 'a', 155 + array( 156 + 'href' => $full_results, 157 + ), 158 + pht('View Full Results')); 159 + } 160 + 150 161 $excuse = $diff->getProperty('arc:unit-excuse'); 151 162 if (strlen($excuse)) { 152 163 $excuse = array( ··· 154 165 ' ', 155 166 phutil_escape_html_newlines($excuse), 156 167 ); 168 + $note[] = $excuse; 157 169 } 158 170 171 + $note = phutil_implode_html(" \xC2\xB7 ", $note); 172 + 159 173 $status = id(new PHUIStatusListView()) 160 174 ->addItem( 161 175 id(new PHUIStatusItemView()) 162 176 ->setIcon(PHUIStatusItemView::ICON_STAR, $icon_color) 163 177 ->setTarget($message) 164 - ->setNote($excuse)); 178 + ->setNote($note)); 165 179 166 180 return $status; 167 181 }
+6
src/applications/harbormaster/application/PhabricatorHarbormasterApplication.php
··· 77 77 'run/(?P<id>\d+)/' => 'HarbormasterPlanRunController', 78 78 '(?P<id>\d+)/' => 'HarbormasterPlanViewController', 79 79 ), 80 + 'unit/' => array( 81 + '(?P<id>\d+)/' => 'HarbormasterUnitMessagesController', 82 + ), 83 + 'lint/' => array( 84 + '(?P<id>\d+)/' => 'HarbormasterLintMessagesController', 85 + ), 80 86 ), 81 87 ); 82 88 }
+33 -6
src/applications/harbormaster/controller/HarbormasterBuildableViewController.php
··· 25 25 ->needBuildTargets(true) 26 26 ->execute(); 27 27 28 - list($lint, $unit) = $this->renderLintAndUnit($builds); 28 + list($lint, $unit) = $this->renderLintAndUnit($buildable, $builds); 29 29 30 30 $buildable->attachBuilds($builds); 31 31 $object = $buildable->getBuildableObject(); ··· 257 257 return $box; 258 258 } 259 259 260 - private function renderLintAndUnit(array $builds) { 260 + private function renderLintAndUnit( 261 + HarbormasterBuildable $buildable, 262 + array $builds) { 263 + 261 264 $viewer = $this->getViewer(); 262 265 263 266 $targets = array(); ··· 274 277 $target_phids = mpull($targets, 'getPHID'); 275 278 276 279 $lint_data = id(new HarbormasterBuildLintMessage())->loadAllWhere( 277 - 'buildTargetPHID IN (%Ls) LIMIT 25', 280 + 'buildTargetPHID IN (%Ls)', 278 281 $target_phids); 279 282 280 283 $unit_data = id(new HarbormasterBuildUnitMessage())->loadAllWhere( 281 - 'buildTargetPHID IN (%Ls) LIMIT 25', 284 + 'buildTargetPHID IN (%Ls)', 282 285 $target_phids); 283 286 284 287 if ($lint_data) { 285 288 $lint_table = id(new HarbormasterLintPropertyView()) 286 289 ->setUser($viewer) 290 + ->setLimit(10) 287 291 ->setLintMessages($lint_data); 288 292 293 + $lint_href = $this->getApplicationURI('lint/'.$buildable->getID().'/'); 294 + 295 + $lint_header = id(new PHUIHeaderView()) 296 + ->setHeader(pht('Lint Messages')) 297 + ->addActionLink( 298 + id(new PHUIButtonView()) 299 + ->setTag('a') 300 + ->setHref($lint_href) 301 + ->setIconFont('fa-list-ul') 302 + ->setText('View All')); 303 + 289 304 $lint = id(new PHUIObjectBoxView()) 290 - ->setHeaderText(pht('Lint Messages')) 305 + ->setHeader($lint_header) 291 306 ->appendChild($lint_table); 292 307 } else { 293 308 $lint = null; ··· 296 311 if ($unit_data) { 297 312 $unit_table = id(new HarbormasterUnitPropertyView()) 298 313 ->setUser($viewer) 314 + ->setLimit(25) 299 315 ->setUnitMessages($unit_data); 300 316 317 + $unit_href = $this->getApplicationURI('unit/'.$buildable->getID().'/'); 318 + 319 + $unit_header = id(new PHUIHeaderView()) 320 + ->setHeader(pht('Unit Tests')) 321 + ->addActionLink( 322 + id(new PHUIButtonView()) 323 + ->setTag('a') 324 + ->setHref($unit_href) 325 + ->setIconFont('fa-list-ul') 326 + ->setText('View All')); 327 + 301 328 $unit = id(new PHUIObjectBoxView()) 302 - ->setHeaderText(pht('Unit Tests')) 329 + ->setHeader($unit_header) 303 330 ->appendChild($unit_table); 304 331 } else { 305 332 $unit = null;
+64
src/applications/harbormaster/controller/HarbormasterLintMessagesController.php
··· 1 + <?php 2 + 3 + final class HarbormasterLintMessagesController 4 + extends HarbormasterController { 5 + 6 + public function handleRequest(AphrontRequest $request) { 7 + $viewer = $this->getViewer(); 8 + 9 + $buildable = id(new HarbormasterBuildableQuery()) 10 + ->setViewer($viewer) 11 + ->withIDs(array($request->getURIData('id'))) 12 + ->needBuilds(true) 13 + ->needTargets(true) 14 + ->executeOne(); 15 + if (!$buildable) { 16 + return new Aphront404Response(); 17 + } 18 + 19 + $id = $buildable->getID(); 20 + 21 + $target_phids = array(); 22 + foreach ($buildable->getBuilds() as $build) { 23 + foreach ($build->getBuildTargets() as $target) { 24 + $target_phids[] = $target->getPHID(); 25 + } 26 + } 27 + 28 + $lint_data = array(); 29 + if ($target_phids) { 30 + $lint_data = id(new HarbormasterBuildLintMessage())->loadAllWhere( 31 + 'buildTargetPHID IN (%Ls)', 32 + $target_phids); 33 + } else { 34 + $lint_data = array(); 35 + } 36 + 37 + $lint_table = id(new HarbormasterLintPropertyView()) 38 + ->setUser($viewer) 39 + ->setLintMessages($lint_data); 40 + 41 + $lint = id(new PHUIObjectBoxView()) 42 + ->setHeaderText(pht('Lint Messages')) 43 + ->appendChild($lint_table); 44 + 45 + $crumbs = $this->buildApplicationCrumbs(); 46 + $this->addBuildableCrumb($crumbs, $buildable); 47 + $crumbs->addTextCrumb(pht('Lint')); 48 + 49 + $title = array( 50 + $buildable->getMonogram(), 51 + pht('Lint'), 52 + ); 53 + 54 + return $this->buildApplicationPage( 55 + array( 56 + $crumbs, 57 + $lint, 58 + ), 59 + array( 60 + 'title' => $title, 61 + )); 62 + } 63 + 64 + }
+64
src/applications/harbormaster/controller/HarbormasterUnitMessagesController.php
··· 1 + <?php 2 + 3 + final class HarbormasterUnitMessagesController 4 + extends HarbormasterController { 5 + 6 + public function handleRequest(AphrontRequest $request) { 7 + $viewer = $this->getViewer(); 8 + 9 + $buildable = id(new HarbormasterBuildableQuery()) 10 + ->setViewer($viewer) 11 + ->withIDs(array($request->getURIData('id'))) 12 + ->needBuilds(true) 13 + ->needTargets(true) 14 + ->executeOne(); 15 + if (!$buildable) { 16 + return new Aphront404Response(); 17 + } 18 + 19 + $id = $buildable->getID(); 20 + 21 + $target_phids = array(); 22 + foreach ($buildable->getBuilds() as $build) { 23 + foreach ($build->getBuildTargets() as $target) { 24 + $target_phids[] = $target->getPHID(); 25 + } 26 + } 27 + 28 + $unit_data = array(); 29 + if ($target_phids) { 30 + $unit_data = id(new HarbormasterBuildUnitMessage())->loadAllWhere( 31 + 'buildTargetPHID IN (%Ls)', 32 + $target_phids); 33 + } else { 34 + $unit_data = array(); 35 + } 36 + 37 + $unit_table = id(new HarbormasterUnitPropertyView()) 38 + ->setUser($viewer) 39 + ->setUnitMessages($unit_data); 40 + 41 + $unit = id(new PHUIObjectBoxView()) 42 + ->setHeaderText(pht('Unit Tests')) 43 + ->appendChild($unit_table); 44 + 45 + $crumbs = $this->buildApplicationCrumbs(); 46 + $this->addBuildableCrumb($crumbs, $buildable); 47 + $crumbs->addTextCrumb(pht('Unit Tests')); 48 + 49 + $title = array( 50 + $buildable->getMonogram(), 51 + pht('Unit Tests'), 52 + ); 53 + 54 + return $this->buildApplicationPage( 55 + array( 56 + $crumbs, 57 + $unit, 58 + ), 59 + array( 60 + 'title' => $title, 61 + )); 62 + } 63 + 64 + }
+28 -10
src/applications/harbormaster/event/HarbormasterUIEventListener.php
··· 51 51 return; 52 52 } 53 53 54 - $buildables = id(new HarbormasterBuildableQuery()) 54 + $buildable = id(new HarbormasterBuildableQuery()) 55 55 ->setViewer($user) 56 56 ->withManualBuildables(false) 57 57 ->withBuildablePHIDs(array($buildable_phid)) 58 - ->execute(); 59 - if (!$buildables) { 58 + ->needBuilds(true) 59 + ->executeOne(); 60 + if (!$buildable) { 60 61 return; 61 62 } 62 63 63 - $builds = id(new HarbormasterBuildQuery()) 64 - ->setViewer($user) 65 - ->withBuildablePHIDs(mpull($buildables, 'getPHID')) 66 - ->execute(); 67 - if (!$builds) { 68 - return; 69 - } 64 + $builds = $buildable->getBuilds(); 70 65 71 66 $build_handles = id(new PhabricatorHandleQuery()) 72 67 ->setViewer($user) ··· 74 69 ->execute(); 75 70 76 71 $status_view = new PHUIStatusListView(); 72 + 73 + $buildable_status = $buildable->getBuildableStatus(); 74 + $buildable_icon = HarbormasterBuildable::getBuildableStatusIcon( 75 + $buildable_status); 76 + $buildable_color = HarbormasterBuildable::getBuildableStatusColor( 77 + $buildable_status); 78 + $buildable_name = HarbormasterBuildable::getBuildableStatusName( 79 + $buildable_status); 80 + 81 + $target = phutil_tag( 82 + 'a', 83 + array( 84 + 'href' => '/'.$buildable->getMonogram(), 85 + ), 86 + pht('Buildable %d', $buildable->getID())); 87 + 88 + $target = phutil_tag('strong', array(), $target); 89 + 90 + $status_view 91 + ->addItem( 92 + id(new PHUIStatusItemView()) 93 + ->setIcon($buildable_icon, $buildable_color, $buildable_name) 94 + ->setTarget($target)); 77 95 78 96 foreach ($builds as $build) { 79 97 $item = new PHUIStatusItemView();
+22
src/applications/harbormaster/storage/build/HarbormasterBuildLintMessage.php
··· 95 95 return $this; 96 96 } 97 97 98 + public function getSortKey() { 99 + // TODO: Maybe use more numeric values after T6861. 100 + $map = array( 101 + ArcanistLintSeverity::SEVERITY_ERROR => 'A', 102 + ArcanistLintSeverity::SEVERITY_WARNING => 'B', 103 + ArcanistLintSeverity::SEVERITY_AUTOFIX => 'C', 104 + ArcanistLintSeverity::SEVERITY_ADVICE => 'Y', 105 + ArcanistLintSeverity::SEVERITY_DISABLED => 'Z', 106 + ); 107 + 108 + $severity = idx($map, $this->getSeverity(), 'N'); 109 + 110 + $parts = array( 111 + $severity, 112 + $this->getPath(), 113 + sprintf('%08d', $this->getLine()), 114 + $this->getCode(), 115 + ); 116 + 117 + return implode("\0", $parts); 118 + } 119 + 98 120 }
+22
src/applications/harbormaster/storage/build/HarbormasterBuildUnitMessage.php
··· 97 97 return $this; 98 98 } 99 99 100 + public function getSortKey() { 101 + // TODO: Maybe use more numeric values after T6861. 102 + $map = array( 103 + ArcanistUnitTestResult::RESULT_FAIL => 'A', 104 + ArcanistUnitTestResult::RESULT_BROKEN => 'B', 105 + ArcanistUnitTestResult::RESULT_UNSOUND => 'C', 106 + ArcanistUnitTestResult::RESULT_PASS => 'Z', 107 + ); 108 + 109 + $result = idx($map, $this->getResult(), 'N'); 110 + 111 + $parts = array( 112 + $result, 113 + $this->getEngine(), 114 + $this->getNamespace(), 115 + $this->getName(), 116 + $this->getID(), 117 + ); 118 + 119 + return implode("\0", $parts); 120 + } 121 + 100 122 }
+17 -4
src/applications/harbormaster/view/HarbormasterLintPropertyView.php
··· 4 4 5 5 private $pathURIMap = array(); 6 6 private $lintMessages = array(); 7 + private $limit; 7 8 8 9 public function setPathURIMap(array $map) { 9 10 $this->pathURIMap = $map; ··· 16 17 return $this; 17 18 } 18 19 20 + public function setLimit($limit) { 21 + $this->limit = $limit; 22 + return $this; 23 + } 24 + 19 25 public function render() { 26 + $messages = $this->lintMessages; 27 + $messages = msort($messages, 'getSortKey'); 28 + 29 + if ($this->limit) { 30 + $messages = array_slice($messages, 0, $this->limit); 31 + } 32 + 20 33 $rows = array(); 21 - foreach ($this->lintMessages as $message) { 34 + foreach ($messages as $message) { 22 35 $path = $message->getPath(); 23 36 $line = $message->getLine(); 24 37 ··· 40 53 } 41 54 42 55 $rows[] = array( 43 - $location, 44 56 $severity, 57 + $location, 45 58 $message->getCode(), 46 59 $message->getName(), 47 60 ); ··· 50 63 $table = id(new AphrontTableView($rows)) 51 64 ->setHeaders( 52 65 array( 53 - pht('Location'), 54 66 pht('Severity'), 67 + pht('Location'), 55 68 pht('Code'), 56 69 pht('Message'), 57 70 )) 58 71 ->setColumnClasses( 59 72 array( 60 - 'pri', 61 73 null, 74 + 'pri', 62 75 null, 63 76 'wide', 64 77 ));
+13 -2
src/applications/harbormaster/view/HarbormasterUnitPropertyView.php
··· 4 4 5 5 private $pathURIMap = array(); 6 6 private $unitMessages = array(); 7 + private $limit; 7 8 8 9 public function setPathURIMap(array $map) { 9 10 $this->pathURIMap = $map; ··· 16 17 return $this; 17 18 } 18 19 20 + public function setLimit($limit) { 21 + $this->limit = $limit; 22 + return $this; 23 + } 24 + 19 25 public function render() { 26 + $messages = $this->unitMessages; 27 + $messages = msort($messages, 'getSortKey'); 28 + 29 + if ($this->limit) { 30 + $messages = array_slice($messages, 0, $this->limit); 31 + } 20 32 21 33 $rows = array(); 22 34 $any_duration = false; 23 - foreach ($this->unitMessages as $message) { 35 + foreach ($messages as $message) { 24 36 $result = $this->renderResult($message->getResult()); 25 37 26 38 $duration = $message->getDuration(); ··· 47 59 $name, 48 60 ); 49 61 } 50 - 51 62 52 63 $table = id(new AphrontTableView($rows)) 53 64 ->setHeaders(
+296
src/applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php
··· 1 + <?php 2 + 3 + final class ManiphestTaskEditBulkJobType 4 + extends PhabricatorWorkerBulkJobType { 5 + 6 + public function getBulkJobTypeKey() { 7 + return 'maniphest.task.edit'; 8 + } 9 + 10 + public function getJobName(PhabricatorWorkerBulkJob $job) { 11 + return pht('Maniphest Bulk Edit'); 12 + } 13 + 14 + public function getDescriptionForConfirm(PhabricatorWorkerBulkJob $job) { 15 + return pht( 16 + 'You are about to apply a bulk edit to Maniphest which will affect '. 17 + '%s task(s).', 18 + new PhutilNumber($job->getSize())); 19 + } 20 + 21 + public function getJobSize(PhabricatorWorkerBulkJob $job) { 22 + return count($job->getParameter('taskPHIDs', array())); 23 + } 24 + 25 + public function getDoneURI(PhabricatorWorkerBulkJob $job) { 26 + return $job->getParameter('doneURI'); 27 + } 28 + 29 + public function createTasks(PhabricatorWorkerBulkJob $job) { 30 + $tasks = array(); 31 + 32 + foreach ($job->getParameter('taskPHIDs', array()) as $phid) { 33 + $tasks[] = PhabricatorWorkerBulkTask::initializeNewTask($job, $phid); 34 + } 35 + 36 + return $tasks; 37 + } 38 + 39 + public function runTask( 40 + PhabricatorUser $actor, 41 + PhabricatorWorkerBulkJob $job, 42 + PhabricatorWorkerBulkTask $task) { 43 + 44 + $object = id(new ManiphestTaskQuery()) 45 + ->setViewer($actor) 46 + ->requireCapabilities( 47 + array( 48 + PhabricatorPolicyCapability::CAN_VIEW, 49 + PhabricatorPolicyCapability::CAN_EDIT, 50 + )) 51 + ->withPHIDs(array($task->getObjectPHID())) 52 + ->executeOne(); 53 + if (!$object) { 54 + return; 55 + } 56 + 57 + $field_list = PhabricatorCustomField::getObjectFields( 58 + $object, 59 + PhabricatorCustomField::ROLE_EDIT); 60 + $field_list->readFieldsFromStorage($object); 61 + 62 + $actions = $job->getParameter('actions'); 63 + $xactions = $this->buildTransactions($actions, $object); 64 + 65 + $editor = id(new ManiphestTransactionEditor()) 66 + ->setActor($actor) 67 + ->setContentSource($job->newContentSource()) 68 + ->setContinueOnNoEffect(true) 69 + ->setContinueOnMissingFields(true) 70 + ->applyTransactions($object, $xactions); 71 + } 72 + 73 + private function buildTransactions($actions, ManiphestTask $task) { 74 + $value_map = array(); 75 + $type_map = array( 76 + 'add_comment' => PhabricatorTransactions::TYPE_COMMENT, 77 + 'assign' => ManiphestTransaction::TYPE_OWNER, 78 + 'status' => ManiphestTransaction::TYPE_STATUS, 79 + 'priority' => ManiphestTransaction::TYPE_PRIORITY, 80 + 'add_project' => PhabricatorTransactions::TYPE_EDGE, 81 + 'remove_project' => PhabricatorTransactions::TYPE_EDGE, 82 + 'add_ccs' => PhabricatorTransactions::TYPE_SUBSCRIBERS, 83 + 'remove_ccs' => PhabricatorTransactions::TYPE_SUBSCRIBERS, 84 + 'space' => PhabricatorTransactions::TYPE_SPACE, 85 + ); 86 + 87 + $edge_edit_types = array( 88 + 'add_project' => true, 89 + 'remove_project' => true, 90 + 'add_ccs' => true, 91 + 'remove_ccs' => true, 92 + ); 93 + 94 + $xactions = array(); 95 + foreach ($actions as $action) { 96 + if (empty($type_map[$action['action']])) { 97 + throw new Exception(pht("Unknown batch edit action '%s'!", $action)); 98 + } 99 + 100 + $type = $type_map[$action['action']]; 101 + 102 + // Figure out the current value, possibly after modifications by other 103 + // batch actions of the same type. For example, if the user chooses to 104 + // "Add Comment" twice, we should add both comments. More notably, if the 105 + // user chooses "Remove Project..." and also "Add Project...", we should 106 + // avoid restoring the removed project in the second transaction. 107 + 108 + if (array_key_exists($type, $value_map)) { 109 + $current = $value_map[$type]; 110 + } else { 111 + switch ($type) { 112 + case PhabricatorTransactions::TYPE_COMMENT: 113 + $current = null; 114 + break; 115 + case ManiphestTransaction::TYPE_OWNER: 116 + $current = $task->getOwnerPHID(); 117 + break; 118 + case ManiphestTransaction::TYPE_STATUS: 119 + $current = $task->getStatus(); 120 + break; 121 + case ManiphestTransaction::TYPE_PRIORITY: 122 + $current = $task->getPriority(); 123 + break; 124 + case PhabricatorTransactions::TYPE_EDGE: 125 + $current = $task->getProjectPHIDs(); 126 + break; 127 + case PhabricatorTransactions::TYPE_SUBSCRIBERS: 128 + $current = $task->getSubscriberPHIDs(); 129 + break; 130 + case PhabricatorTransactions::TYPE_SPACE: 131 + $current = PhabricatorSpacesNamespaceQuery::getObjectSpacePHID( 132 + $task); 133 + break; 134 + } 135 + } 136 + 137 + // Check if the value is meaningful / provided, and normalize it if 138 + // necessary. This discards, e.g., empty comments and empty owner 139 + // changes. 140 + 141 + $value = $action['value']; 142 + switch ($type) { 143 + case PhabricatorTransactions::TYPE_COMMENT: 144 + if (!strlen($value)) { 145 + continue 2; 146 + } 147 + break; 148 + case PhabricatorTransactions::TYPE_SPACE: 149 + if (empty($value)) { 150 + continue 2; 151 + } 152 + $value = head($value); 153 + break; 154 + case ManiphestTransaction::TYPE_OWNER: 155 + if (empty($value)) { 156 + continue 2; 157 + } 158 + $value = head($value); 159 + $no_owner = PhabricatorPeopleNoOwnerDatasource::FUNCTION_TOKEN; 160 + if ($value === $no_owner) { 161 + $value = null; 162 + } 163 + break; 164 + case PhabricatorTransactions::TYPE_EDGE: 165 + if (empty($value)) { 166 + continue 2; 167 + } 168 + break; 169 + case PhabricatorTransactions::TYPE_SUBSCRIBERS: 170 + if (empty($value)) { 171 + continue 2; 172 + } 173 + break; 174 + } 175 + 176 + // If the edit doesn't change anything, go to the next action. This 177 + // check is only valid for changes like "owner", "status", etc, not 178 + // for edge edits, because we should still apply an edit like 179 + // "Remove Projects: A, B" to a task with projects "A, B". 180 + 181 + if (empty($edge_edit_types[$action['action']])) { 182 + if ($value == $current) { 183 + continue; 184 + } 185 + } 186 + 187 + // Apply the value change; for most edits this is just replacement, but 188 + // some need to merge the current and edited values (add/remove project). 189 + 190 + switch ($type) { 191 + case PhabricatorTransactions::TYPE_COMMENT: 192 + if (strlen($current)) { 193 + $value = $current."\n\n".$value; 194 + } 195 + break; 196 + case PhabricatorTransactions::TYPE_EDGE: 197 + $is_remove = $action['action'] == 'remove_project'; 198 + 199 + $current = array_fill_keys($current, true); 200 + $value = array_fill_keys($value, true); 201 + 202 + $new = $current; 203 + $did_something = false; 204 + 205 + if ($is_remove) { 206 + foreach ($value as $phid => $ignored) { 207 + if (isset($new[$phid])) { 208 + unset($new[$phid]); 209 + $did_something = true; 210 + } 211 + } 212 + } else { 213 + foreach ($value as $phid => $ignored) { 214 + if (empty($new[$phid])) { 215 + $new[$phid] = true; 216 + $did_something = true; 217 + } 218 + } 219 + } 220 + 221 + if (!$did_something) { 222 + continue 2; 223 + } 224 + 225 + $value = array_keys($new); 226 + break; 227 + case PhabricatorTransactions::TYPE_SUBSCRIBERS: 228 + $is_remove = $action['action'] == 'remove_ccs'; 229 + 230 + $current = array_fill_keys($current, true); 231 + 232 + $new = array(); 233 + $did_something = false; 234 + 235 + if ($is_remove) { 236 + foreach ($value as $phid) { 237 + if (isset($current[$phid])) { 238 + $new[$phid] = true; 239 + $did_something = true; 240 + } 241 + } 242 + if ($new) { 243 + $value = array('-' => array_keys($new)); 244 + } 245 + } else { 246 + $new = array(); 247 + foreach ($value as $phid) { 248 + $new[$phid] = true; 249 + $did_something = true; 250 + } 251 + if ($new) { 252 + $value = array('+' => array_keys($new)); 253 + } 254 + } 255 + if (!$did_something) { 256 + continue 2; 257 + } 258 + 259 + break; 260 + } 261 + 262 + $value_map[$type] = $value; 263 + } 264 + 265 + $template = new ManiphestTransaction(); 266 + 267 + foreach ($value_map as $type => $value) { 268 + $xaction = clone $template; 269 + $xaction->setTransactionType($type); 270 + 271 + switch ($type) { 272 + case PhabricatorTransactions::TYPE_COMMENT: 273 + $xaction->attachComment( 274 + id(new ManiphestTransactionComment()) 275 + ->setContent($value)); 276 + break; 277 + case PhabricatorTransactions::TYPE_EDGE: 278 + $project_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; 279 + $xaction 280 + ->setMetadataValue('edge:type', $project_type) 281 + ->setNewValue( 282 + array( 283 + '=' => array_fuse($value), 284 + )); 285 + break; 286 + default: 287 + $xaction->setNewValue($value); 288 + break; 289 + } 290 + 291 + $xactions[] = $xaction; 292 + } 293 + 294 + return $xactions; 295 + } 296 + }
+24 -244
src/applications/maniphest/controller/ManiphestBatchEditController.php
··· 45 45 46 46 if (!$tasks) { 47 47 throw new Exception( 48 - pht( 49 - "You don't have permission to edit any of the selected tasks.")); 48 + pht("You don't have permission to edit any of the selected tasks.")); 50 49 } 51 50 52 51 if ($project) { ··· 62 61 $actions = phutil_json_decode($actions); 63 62 } 64 63 65 - if ($request->isFormPost() && is_array($actions)) { 66 - foreach ($tasks as $task) { 67 - $field_list = PhabricatorCustomField::getObjectFields( 68 - $task, 69 - PhabricatorCustomField::ROLE_EDIT); 70 - $field_list->readFieldsFromStorage($task); 64 + if ($request->isFormPost() && $actions) { 65 + $job = PhabricatorWorkerBulkJob::initializeNewJob( 66 + $viewer, 67 + new ManiphestTaskEditBulkJobType(), 68 + array( 69 + 'taskPHIDs' => mpull($tasks, 'getPHID'), 70 + 'actions' => $actions, 71 + 'cancelURI' => $cancel_uri, 72 + 'doneURI' => $redirect_uri, 73 + )); 71 74 72 - $xactions = $this->buildTransactions($actions, $task); 73 - if ($xactions) { 74 - // TODO: Set content source to "batch edit". 75 + $type_status = PhabricatorWorkerBulkJobTransaction::TYPE_STATUS; 75 76 76 - $editor = id(new ManiphestTransactionEditor()) 77 - ->setActor($viewer) 78 - ->setContentSourceFromRequest($request) 79 - ->setContinueOnNoEffect(true) 80 - ->setContinueOnMissingFields(true) 81 - ->applyTransactions($task, $xactions); 82 - } 83 - } 77 + $xactions = array(); 78 + $xactions[] = id(new PhabricatorWorkerBulkJobTransaction()) 79 + ->setTransactionType($type_status) 80 + ->setNewValue(PhabricatorWorkerBulkJob::STATUS_CONFIRM); 84 81 85 - return id(new AphrontRedirectResponse())->setURI($redirect_uri); 82 + $editor = id(new PhabricatorWorkerBulkJobEditor()) 83 + ->setActor($viewer) 84 + ->setContentSourceFromRequest($request) 85 + ->setContinueOnMissingFields(true) 86 + ->applyTransactions($job, $xactions); 87 + 88 + return id(new AphrontRedirectResponse()) 89 + ->setURI($job->getMonitorURI()); 86 90 } 87 91 88 92 $handles = ManiphestTaskListView::loadTaskHandles($viewer, $tasks); ··· 208 212 array( 209 213 'title' => $title, 210 214 )); 211 - } 212 - 213 - private function buildTransactions($actions, ManiphestTask $task) { 214 - $value_map = array(); 215 - $type_map = array( 216 - 'add_comment' => PhabricatorTransactions::TYPE_COMMENT, 217 - 'assign' => ManiphestTransaction::TYPE_OWNER, 218 - 'status' => ManiphestTransaction::TYPE_STATUS, 219 - 'priority' => ManiphestTransaction::TYPE_PRIORITY, 220 - 'add_project' => PhabricatorTransactions::TYPE_EDGE, 221 - 'remove_project' => PhabricatorTransactions::TYPE_EDGE, 222 - 'add_ccs' => PhabricatorTransactions::TYPE_SUBSCRIBERS, 223 - 'remove_ccs' => PhabricatorTransactions::TYPE_SUBSCRIBERS, 224 - 'space' => PhabricatorTransactions::TYPE_SPACE, 225 - ); 226 - 227 - $edge_edit_types = array( 228 - 'add_project' => true, 229 - 'remove_project' => true, 230 - 'add_ccs' => true, 231 - 'remove_ccs' => true, 232 - ); 233 - 234 - $xactions = array(); 235 - foreach ($actions as $action) { 236 - if (empty($type_map[$action['action']])) { 237 - throw new Exception(pht("Unknown batch edit action '%s'!", $action)); 238 - } 239 - 240 - $type = $type_map[$action['action']]; 241 - 242 - // Figure out the current value, possibly after modifications by other 243 - // batch actions of the same type. For example, if the user chooses to 244 - // "Add Comment" twice, we should add both comments. More notably, if the 245 - // user chooses "Remove Project..." and also "Add Project...", we should 246 - // avoid restoring the removed project in the second transaction. 247 - 248 - if (array_key_exists($type, $value_map)) { 249 - $current = $value_map[$type]; 250 - } else { 251 - switch ($type) { 252 - case PhabricatorTransactions::TYPE_COMMENT: 253 - $current = null; 254 - break; 255 - case ManiphestTransaction::TYPE_OWNER: 256 - $current = $task->getOwnerPHID(); 257 - break; 258 - case ManiphestTransaction::TYPE_STATUS: 259 - $current = $task->getStatus(); 260 - break; 261 - case ManiphestTransaction::TYPE_PRIORITY: 262 - $current = $task->getPriority(); 263 - break; 264 - case PhabricatorTransactions::TYPE_EDGE: 265 - $current = $task->getProjectPHIDs(); 266 - break; 267 - case PhabricatorTransactions::TYPE_SUBSCRIBERS: 268 - $current = $task->getSubscriberPHIDs(); 269 - break; 270 - case PhabricatorTransactions::TYPE_SPACE: 271 - $current = PhabricatorSpacesNamespaceQuery::getObjectSpacePHID( 272 - $task); 273 - break; 274 - } 275 - } 276 - 277 - // Check if the value is meaningful / provided, and normalize it if 278 - // necessary. This discards, e.g., empty comments and empty owner 279 - // changes. 280 - 281 - $value = $action['value']; 282 - switch ($type) { 283 - case PhabricatorTransactions::TYPE_COMMENT: 284 - if (!strlen($value)) { 285 - continue 2; 286 - } 287 - break; 288 - case PhabricatorTransactions::TYPE_SPACE: 289 - if (empty($value)) { 290 - continue 2; 291 - } 292 - $value = head($value); 293 - break; 294 - case ManiphestTransaction::TYPE_OWNER: 295 - if (empty($value)) { 296 - continue 2; 297 - } 298 - $value = head($value); 299 - $no_owner = PhabricatorPeopleNoOwnerDatasource::FUNCTION_TOKEN; 300 - if ($value === $no_owner) { 301 - $value = null; 302 - } 303 - break; 304 - case PhabricatorTransactions::TYPE_EDGE: 305 - if (empty($value)) { 306 - continue 2; 307 - } 308 - break; 309 - case PhabricatorTransactions::TYPE_SUBSCRIBERS: 310 - if (empty($value)) { 311 - continue 2; 312 - } 313 - break; 314 - } 315 - 316 - // If the edit doesn't change anything, go to the next action. This 317 - // check is only valid for changes like "owner", "status", etc, not 318 - // for edge edits, because we should still apply an edit like 319 - // "Remove Projects: A, B" to a task with projects "A, B". 320 - 321 - if (empty($edge_edit_types[$action['action']])) { 322 - if ($value == $current) { 323 - continue; 324 - } 325 - } 326 - 327 - // Apply the value change; for most edits this is just replacement, but 328 - // some need to merge the current and edited values (add/remove project). 329 - 330 - switch ($type) { 331 - case PhabricatorTransactions::TYPE_COMMENT: 332 - if (strlen($current)) { 333 - $value = $current."\n\n".$value; 334 - } 335 - break; 336 - case PhabricatorTransactions::TYPE_EDGE: 337 - $is_remove = $action['action'] == 'remove_project'; 338 - 339 - $current = array_fill_keys($current, true); 340 - $value = array_fill_keys($value, true); 341 - 342 - $new = $current; 343 - $did_something = false; 344 - 345 - if ($is_remove) { 346 - foreach ($value as $phid => $ignored) { 347 - if (isset($new[$phid])) { 348 - unset($new[$phid]); 349 - $did_something = true; 350 - } 351 - } 352 - } else { 353 - foreach ($value as $phid => $ignored) { 354 - if (empty($new[$phid])) { 355 - $new[$phid] = true; 356 - $did_something = true; 357 - } 358 - } 359 - } 360 - 361 - if (!$did_something) { 362 - continue 2; 363 - } 364 - 365 - $value = array_keys($new); 366 - break; 367 - case PhabricatorTransactions::TYPE_SUBSCRIBERS: 368 - $is_remove = $action['action'] == 'remove_ccs'; 369 - 370 - $current = array_fill_keys($current, true); 371 - 372 - $new = array(); 373 - $did_something = false; 374 - 375 - if ($is_remove) { 376 - foreach ($value as $phid) { 377 - if (isset($current[$phid])) { 378 - $new[$phid] = true; 379 - $did_something = true; 380 - } 381 - } 382 - if ($new) { 383 - $value = array('-' => array_keys($new)); 384 - } 385 - } else { 386 - $new = array(); 387 - foreach ($value as $phid) { 388 - $new[$phid] = true; 389 - $did_something = true; 390 - } 391 - if ($new) { 392 - $value = array('+' => array_keys($new)); 393 - } 394 - } 395 - if (!$did_something) { 396 - continue 2; 397 - } 398 - 399 - break; 400 - } 401 - 402 - $value_map[$type] = $value; 403 - } 404 - 405 - $template = new ManiphestTransaction(); 406 - 407 - foreach ($value_map as $type => $value) { 408 - $xaction = clone $template; 409 - $xaction->setTransactionType($type); 410 - 411 - switch ($type) { 412 - case PhabricatorTransactions::TYPE_COMMENT: 413 - $xaction->attachComment( 414 - id(new ManiphestTransactionComment()) 415 - ->setContent($value)); 416 - break; 417 - case PhabricatorTransactions::TYPE_EDGE: 418 - $project_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; 419 - $xaction 420 - ->setMetadataValue('edge:type', $project_type) 421 - ->setNewValue( 422 - array( 423 - '=' => array_fuse($value), 424 - )); 425 - break; 426 - default: 427 - $xaction->setNewValue($value); 428 - break; 429 - } 430 - 431 - $xactions[] = $xaction; 432 - } 433 - 434 - return $xactions; 435 215 } 436 216 437 217 }
+7
src/applications/metamta/application/PhabricatorMetaMTAApplication.php
··· 6 6 return pht('MetaMTA'); 7 7 } 8 8 9 + public function getBaseURI() { 10 + return '/mail/'; 11 + } 12 + 9 13 public function getFontIcon() { 10 14 return 'fa-send'; 11 15 } ··· 37 41 public function getRoutes() { 38 42 return array( 39 43 '/mail/' => array( 44 + '(query/(?P<queryKey>[^/]+)/)?' => 45 + 'PhabricatorMetaMTAMailListController', 46 + 'detail/(?P<id>[1-9]\d*)/' => 'PhabricatorMetaMTAMailViewController', 40 47 'sendgrid/' => 'PhabricatorMetaMTASendGridReceiveController', 41 48 'mailgun/' => 'PhabricatorMetaMTAMailgunReceiveController', 42 49 ),
+2
src/applications/metamta/contentsource/PhabricatorContentSource.php
··· 15 15 const SOURCE_DAEMON = 'daemon'; 16 16 const SOURCE_LIPSUM = 'lipsum'; 17 17 const SOURCE_PHORTUNE = 'phortune'; 18 + const SOURCE_BULK = 'bulk'; 18 19 19 20 private $source; 20 21 private $params = array(); ··· 79 80 self::SOURCE_LIPSUM => pht('Lipsum'), 80 81 self::SOURCE_UNKNOWN => pht('Old World'), 81 82 self::SOURCE_PHORTUNE => pht('Phortune'), 83 + self::SOURCE_BULK => pht('Bulk Edit'), 82 84 ); 83 85 } 84 86
+30
src/applications/metamta/controller/PhabricatorMetaMTAMailListController.php
··· 1 + <?php 2 + 3 + final class PhabricatorMetaMTAMailListController 4 + extends PhabricatorMetaMTAController { 5 + 6 + public function handleRequest(AphrontRequest $request) { 7 + $controller = id(new PhabricatorApplicationSearchController()) 8 + ->setQueryKey($request->getURIData('queryKey')) 9 + ->setSearchEngine(new PhabricatorMetaMTAMailSearchEngine()) 10 + ->setNavigation($this->buildSideNav()); 11 + 12 + return $this->delegateToController($controller); 13 + } 14 + 15 + public function buildSideNav() { 16 + $user = $this->getRequest()->getUser(); 17 + 18 + $nav = new AphrontSideNavFilterView(); 19 + $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); 20 + 21 + id(new PhabricatorMetaMTAMailSearchEngine()) 22 + ->setViewer($user) 23 + ->addNavigationItems($nav->getMenu()); 24 + 25 + $nav->selectFilter(null); 26 + 27 + return $nav; 28 + } 29 + 30 + }
+91
src/applications/metamta/controller/PhabricatorMetaMTAMailViewController.php
··· 1 + <?php 2 + 3 + final class PhabricatorMetaMTAMailViewController 4 + extends PhabricatorMetaMTAController { 5 + 6 + public function handleRequest(AphrontRequest $request) { 7 + $viewer = $request->getUser(); 8 + 9 + $mail = id(new PhabricatorMetaMTAMailQuery()) 10 + ->setViewer($viewer) 11 + ->withIDs(array($request->getURIData('id'))) 12 + ->executeOne(); 13 + if (!$mail) { 14 + return new Aphront404Response(); 15 + } 16 + 17 + if ($mail->hasSensitiveContent()) { 18 + $title = pht('Content Redacted'); 19 + } else { 20 + $title = $mail->getSubject(); 21 + } 22 + $header = id(new PHUIHeaderView()) 23 + ->setHeader($title) 24 + ->setUser($this->getRequest()->getUser()) 25 + ->setPolicyObject($mail); 26 + 27 + $crumbs = $this->buildApplicationCrumbs() 28 + ->addTextCrumb( 29 + 'Mail '.$mail->getID()); 30 + $object_box = id(new PHUIObjectBoxView()) 31 + ->setHeader($header) 32 + ->addPropertyList($this->buildPropertyView($mail)); 33 + 34 + return $this->buildApplicationPage( 35 + array( 36 + $crumbs, 37 + $object_box, 38 + ), 39 + array( 40 + 'title' => $title, 41 + 'pageObjects' => array($mail->getPHID()), 42 + )); 43 + } 44 + 45 + private function buildPropertyView(PhabricatorMetaMTAMail $mail) { 46 + $viewer = $this->getViewer(); 47 + 48 + $properties = id(new PHUIPropertyListView()) 49 + ->setUser($viewer) 50 + ->setObject($mail); 51 + 52 + if ($mail->getActorPHID()) { 53 + $actor_str = $viewer->renderHandle($mail->getActorPHID()); 54 + } else { 55 + $actor_str = pht('Generated by Phabricator'); 56 + } 57 + $properties->addProperty( 58 + pht('Actor'), 59 + $actor_str); 60 + 61 + if ($mail->getFrom()) { 62 + $from_str = $viewer->renderHandle($mail->getFrom()); 63 + } else { 64 + $from_str = pht('Sent by Phabricator'); 65 + } 66 + $properties->addProperty( 67 + pht('From'), 68 + $from_str); 69 + 70 + if ($mail->getToPHIDs()) { 71 + $to_list = $viewer->renderHandleList($mail->getToPHIDs()); 72 + } else { 73 + $to_list = pht('None'); 74 + } 75 + $properties->addProperty( 76 + pht('To'), 77 + $to_list); 78 + 79 + if ($mail->getCcPHIDs()) { 80 + $cc_list = $viewer->renderHandleList($mail->getCcPHIDs()); 81 + } else { 82 + $cc_list = pht('None'); 83 + } 84 + $properties->addProperty( 85 + pht('Cc'), 86 + $cc_list); 87 + 88 + return $properties; 89 + } 90 + 91 + }
+8
src/applications/metamta/edge/PhabricatorMetaMTAMailHasRecipientEdgeType.php
··· 1 + <?php 2 + 3 + final class PhabricatorMetaMTAMailHasRecipientEdgeType 4 + extends PhabricatorEdgeType { 5 + 6 + const EDGECONST = 57; 7 + 8 + }
+58
src/applications/metamta/query/PhabricatorMetaMTAMailQuery.php
··· 5 5 6 6 private $ids; 7 7 private $phids; 8 + private $actorPHIDs; 9 + private $recipientPHIDs; 8 10 9 11 public function withIDs(array $ids) { 10 12 $this->ids = $ids; ··· 13 15 14 16 public function withPHIDs(array $phids) { 15 17 $this->phids = $phids; 18 + return $this; 19 + } 20 + 21 + public function withActorPHIDs(array $phids) { 22 + $this->actorPHIDs = $phids; 23 + return $this; 24 + } 25 + 26 + public function withRecipientPHIDs(array $phids) { 27 + $this->recipientPHIDs = $phids; 16 28 return $this; 17 29 } 18 30 ··· 37 49 $this->phids); 38 50 } 39 51 52 + if ($this->actorPHIDs !== null) { 53 + $where[] = qsprintf( 54 + $conn_r, 55 + 'mail.actorPHID IN (%Ls)', 56 + $this->actorPHIDs); 57 + } 58 + 59 + if ($this->recipientPHIDs !== null) { 60 + $where[] = qsprintf( 61 + $conn_r, 62 + 'recipient.dst IN (%Ls)', 63 + $this->recipientPHIDs); 64 + } 65 + 66 + if ($this->actorPHIDs === null && $this->recipientPHIDs === null) { 67 + $viewer = $this->getViewer(); 68 + $where[] = qsprintf( 69 + $conn_r, 70 + 'edge.dst = %s OR actorPHID = %s', 71 + $viewer->getPHID(), 72 + $viewer->getPHID()); 73 + } 74 + 40 75 $where[] = $this->buildPagingClause($conn_r); 41 76 42 77 return $this->formatWhereClause($where); 78 + } 79 + 80 + protected function buildJoinClause(AphrontDatabaseConnection $conn) { 81 + $joins = array(); 82 + 83 + if ($this->actorPHIDs === null && $this->recipientPHIDs === null) { 84 + $joins[] = qsprintf( 85 + $conn, 86 + 'LEFT JOIN %T edge ON mail.phid = edge.src AND edge.type = %d', 87 + PhabricatorEdgeConfig::TABLE_NAME_EDGE, 88 + PhabricatorMetaMTAMailHasRecipientEdgeType::EDGECONST); 89 + } 90 + 91 + if ($this->recipientPHIDs !== null) { 92 + $joins[] = qsprintf( 93 + $conn, 94 + 'LEFT JOIN %T recipient '. 95 + 'ON mail.phid = recipient.src AND recipient.type = %d', 96 + PhabricatorEdgeConfig::TABLE_NAME_EDGE, 97 + PhabricatorMetaMTAMailHasRecipientEdgeType::EDGECONST); 98 + } 99 + 100 + return implode(' ', $joins); 43 101 } 44 102 45 103 protected function getPrimaryTableAlias() {
+123
src/applications/metamta/query/PhabricatorMetaMTAMailSearchEngine.php
··· 1 + <?php 2 + 3 + final class PhabricatorMetaMTAMailSearchEngine 4 + extends PhabricatorApplicationSearchEngine { 5 + 6 + public function getResultTypeDescription() { 7 + return pht('MetaMTA Mails'); 8 + } 9 + 10 + public function getApplicationClassName() { 11 + return 'PhabricatorMetaMTAApplication'; 12 + } 13 + 14 + public function newQuery() { 15 + return new PhabricatorMetaMTAMailQuery(); 16 + } 17 + 18 + protected function shouldShowOrderField() { 19 + return false; 20 + } 21 + 22 + protected function buildCustomSearchFields() { 23 + return array( 24 + id(new PhabricatorSearchUsersField()) 25 + ->setLabel(pht('Actors')) 26 + ->setKey('actorPHIDs') 27 + ->setAliases(array('actor', 'actors')), 28 + id(new PhabricatorSearchUsersField()) 29 + ->setLabel(pht('Recipients')) 30 + ->setKey('recipientPHIDs') 31 + ->setAliases(array('recipient', 'recipients')), 32 + ); 33 + } 34 + 35 + protected function buildQueryFromParameters(array $map) { 36 + $query = $this->newQuery(); 37 + 38 + if ($map['actorPHIDs']) { 39 + $query->withActorPHIDs($map['actorPHIDs']); 40 + } 41 + 42 + if ($map['recipientPHIDs']) { 43 + $query->withRecipientPHIDs($map['recipientPHIDs']); 44 + } 45 + 46 + return $query; 47 + } 48 + 49 + protected function getURI($path) { 50 + return '/mail/'.$path; 51 + } 52 + 53 + protected function getBuiltinQueryNames() { 54 + $names = array( 55 + 'inbox' => pht('Inbox'), 56 + 'outbox' => pht('Outbox'), 57 + ); 58 + 59 + return $names; 60 + } 61 + 62 + public function buildSavedQueryFromBuiltin($query_key) { 63 + $viewer = $this->requireViewer(); 64 + 65 + $query = $this->newSavedQuery(); 66 + $query->setQueryKey($query_key); 67 + 68 + switch ($query_key) { 69 + case 'inbox': 70 + return $query->setParameter( 71 + 'recipientPHIDs', 72 + array($viewer->getPHID())); 73 + case 'outbox': 74 + return $query->setParameter( 75 + 'actorPHIDs', 76 + array($viewer->getPHID())); 77 + } 78 + 79 + return parent::buildSavedQueryFromBuiltin($query_key); 80 + } 81 + 82 + protected function getRequiredHandlePHIDsForResultList( 83 + array $objects, 84 + PhabricatorSavedQuery $query) { 85 + 86 + $phids = array(); 87 + foreach ($objects as $mail) { 88 + $phids[] = $mail->getExpandedRecipientPHIDs(); 89 + } 90 + return array_mergev($phids); 91 + } 92 + 93 + protected function renderResultList( 94 + array $mails, 95 + PhabricatorSavedQuery $query, 96 + array $handles) { 97 + 98 + assert_instances_of($mails, 'PhabricatorMetaMTAMail'); 99 + $viewer = $this->requireViewer(); 100 + $list = new PHUIObjectItemListView(); 101 + 102 + foreach ($mails as $mail) { 103 + if ($mail->hasSensitiveContent()) { 104 + $header = pht( 105 + 'Mail %d: < content redacted >', 106 + $mail->getID()); 107 + } else { 108 + $header = pht( 109 + 'Mail %d: %s', 110 + $mail->getID(), 111 + $mail->getSubject()); 112 + } 113 + 114 + $item = id(new PHUIObjectItemView()) 115 + ->setObject($mail) 116 + ->setHeader($header) 117 + ->setHref($this->getURI('detail/'.$mail->getID())); 118 + $list->addItem($item); 119 + } 120 + 121 + return $list; 122 + } 123 + }
+33 -6
src/applications/metamta/storage/PhabricatorMetaMTAMail.php
··· 25 25 public function __construct() { 26 26 27 27 $this->status = self::STATUS_QUEUE; 28 - $this->parameters = array(); 28 + $this->parameters = array('sensitive' => true); 29 29 30 30 parent::__construct(); 31 31 } ··· 262 262 return $this; 263 263 } 264 264 265 + public function setSensitiveContent($bool) { 266 + $this->setParam('sensitive', $bool); 267 + return $this; 268 + } 269 + 270 + public function hasSensitiveContent() { 271 + return $this->getParam('sensitive', true); 272 + } 273 + 265 274 public function setHTMLBody($html) { 266 275 $this->setParam('html-body', $html); 267 276 return $this; ··· 366 375 // method. 367 376 368 377 $this->openTransaction(); 369 - // Save to generate a task ID. 378 + // Save to generate a mail ID and PHID. 370 379 $result = parent::save(); 371 380 381 + // Write the recipient edges. 382 + $editor = new PhabricatorEdgeEditor(); 383 + $edge_type = PhabricatorMetaMTAMailHasRecipientEdgeType::EDGECONST; 384 + $recipient_phids = array_merge( 385 + $this->getToPHIDs(), 386 + $this->getCcPHIDs()); 387 + $expanded_phids = $this->expandRecipients($recipient_phids); 388 + $all_phids = array_unique(array_merge( 389 + $recipient_phids, 390 + $expanded_phids)); 391 + foreach ($all_phids as $curr_phid) { 392 + $editor->addEdge($this->getPHID(), $edge_type, $curr_phid); 393 + } 394 + $editor->save(); 395 + 372 396 // Queue a task to send this mail. 373 397 $mailer_task = PhabricatorWorker::scheduleTask( 374 398 'PhabricatorMetaMTAWorker', ··· 813 837 } 814 838 815 839 public function loadAllActors() { 816 - $actor_phids = $this->getAllActorPHIDs(); 817 - $actor_phids = $this->expandRecipients($actor_phids); 840 + $actor_phids = $this->getExpandedRecipientPHIDs(); 818 841 return $this->loadActors($actor_phids); 842 + } 843 + 844 + public function getExpandedRecipientPHIDs() { 845 + $actor_phids = $this->getAllActorPHIDs(); 846 + return $this->expandRecipients($actor_phids); 819 847 } 820 848 821 849 private function getAllActorPHIDs() { ··· 1025 1053 } 1026 1054 1027 1055 public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 1028 - $actor_phids = $this->getAllActorPHIDs(); 1029 - $actor_phids = $this->expandRecipients($actor_phids); 1056 + $actor_phids = $this->getExpandedRecipientPHIDs(); 1030 1057 return in_array($viewer->getPHID(), $actor_phids); 1031 1058 } 1032 1059
+1
src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
··· 2334 2334 } 2335 2335 2336 2336 $mail 2337 + ->setSensitiveContent(false) 2337 2338 ->setFrom($this->getActingAsPHID()) 2338 2339 ->setSubjectPrefix($this->getMailSubjectPrefix()) 2339 2340 ->setVarySubjectPrefix('['.$action.']')
+10
src/infrastructure/daemon/workers/__tests__/PhabricatorWorkerBulkJobTestCase.php
··· 1 + <?php 2 + 3 + final class PhabricatorWorkerBulkJobTestCase extends PhabricatorTestCase { 4 + 5 + public function testGetAllBulkJobTypes() { 6 + PhabricatorWorkerBulkJobType::getAllJobTypes(); 7 + $this->assertTrue(true); 8 + } 9 + 10 + }
+51
src/infrastructure/daemon/workers/bulk/PhabricatorWorkerBulkJobCreateWorker.php
··· 1 + <?php 2 + 3 + final class PhabricatorWorkerBulkJobCreateWorker 4 + extends PhabricatorWorkerBulkJobWorker { 5 + 6 + protected function doWork() { 7 + $lock = $this->acquireJobLock(); 8 + 9 + $job = $this->loadJob(); 10 + $actor = $this->loadActor($job); 11 + 12 + $status = $job->getStatus(); 13 + switch ($status) { 14 + case PhabricatorWorkerBulkJob::STATUS_WAITING: 15 + // This is what we expect. Other statuses indicate some kind of race 16 + // is afoot. 17 + break; 18 + default: 19 + throw new PhabricatorWorkerPermanentFailureException( 20 + pht( 21 + 'Found unexpected job status ("%s").', 22 + $status)); 23 + } 24 + 25 + $tasks = $job->createTasks(); 26 + foreach ($tasks as $task) { 27 + $task->save(); 28 + } 29 + 30 + $this->updateJobStatus( 31 + $job, 32 + PhabricatorWorkerBulkJob::STATUS_RUNNING); 33 + 34 + $lock->unlock(); 35 + 36 + foreach ($tasks as $task) { 37 + PhabricatorWorker::scheduleTask( 38 + 'PhabricatorWorkerBulkJobTaskWorker', 39 + array( 40 + 'jobID' => $job->getID(), 41 + 'taskID' => $task->getID(), 42 + ), 43 + array( 44 + 'priority' => PhabricatorWorker::PRIORITY_BULK, 45 + )); 46 + } 47 + 48 + $this->updateJob($job); 49 + } 50 + 51 + }
+46
src/infrastructure/daemon/workers/bulk/PhabricatorWorkerBulkJobTaskWorker.php
··· 1 + <?php 2 + 3 + final class PhabricatorWorkerBulkJobTaskWorker 4 + extends PhabricatorWorkerBulkJobWorker { 5 + 6 + protected function doWork() { 7 + $lock = $this->acquireTaskLock(); 8 + 9 + $task = $this->loadTask(); 10 + $status = $task->getStatus(); 11 + switch ($task->getStatus()) { 12 + case PhabricatorWorkerBulkTask::STATUS_WAITING: 13 + // This is what we expect. 14 + break; 15 + default: 16 + throw new PhabricatorWorkerPermanentFailureException( 17 + pht( 18 + 'Found unexpected task status ("%s").', 19 + $status)); 20 + } 21 + 22 + $task 23 + ->setStatus(PhabricatorWorkerBulkTask::STATUS_RUNNING) 24 + ->save(); 25 + 26 + $lock->unlock(); 27 + 28 + $job = $this->loadJob(); 29 + $actor = $this->loadActor($job); 30 + 31 + try { 32 + $job->runTask($actor, $task); 33 + $status = PhabricatorWorkerBulkTask::STATUS_DONE; 34 + } catch (Exception $ex) { 35 + phlog($ex); 36 + $status = PhabricatorWorkerBulkTask::STATUS_FAIL; 37 + } 38 + 39 + $task 40 + ->setStatus($status) 41 + ->save(); 42 + 43 + $this->updateJob($job); 44 + } 45 + 46 + }
+28
src/infrastructure/daemon/workers/bulk/PhabricatorWorkerBulkJobType.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorWorkerBulkJobType extends Phobject { 4 + 5 + abstract public function getJobName(PhabricatorWorkerBulkJob $job); 6 + abstract public function getBulkJobTypeKey(); 7 + abstract public function getJobSize(PhabricatorWorkerBulkJob $job); 8 + abstract public function getDescriptionForConfirm( 9 + PhabricatorWorkerBulkJob $job); 10 + 11 + abstract public function createTasks(PhabricatorWorkerBulkJob $job); 12 + abstract public function runTask( 13 + PhabricatorUser $actor, 14 + PhabricatorWorkerBulkJob $job, 15 + PhabricatorWorkerBulkTask $task); 16 + 17 + public function getDoneURI(PhabricatorWorkerBulkJob $job) { 18 + return $job->getManageURI(); 19 + } 20 + 21 + final public static function getAllJobTypes() { 22 + return id(new PhutilClassMapQuery()) 23 + ->setAncestorClass(__CLASS__) 24 + ->setUniqueMethod('getBulkJobTypeKey') 25 + ->execute(); 26 + } 27 + 28 + }
+138
src/infrastructure/daemon/workers/bulk/PhabricatorWorkerBulkJobWorker.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorWorkerBulkJobWorker 4 + extends PhabricatorWorker { 5 + 6 + final protected function acquireJobLock() { 7 + return PhabricatorGlobalLock::newLock('bulkjob.'.$this->getJobID()) 8 + ->lock(15); 9 + } 10 + 11 + final protected function acquireTaskLock() { 12 + return PhabricatorGlobalLock::newLock('bulktask.'.$this->getTaskID()) 13 + ->lock(15); 14 + } 15 + 16 + final protected function getJobID() { 17 + $data = $this->getTaskData(); 18 + $id = idx($data, 'jobID'); 19 + if (!$id) { 20 + throw new PhabricatorWorkerPermanentFailureException( 21 + pht('Worker has no job ID.')); 22 + } 23 + return $id; 24 + } 25 + 26 + final protected function getTaskID() { 27 + $data = $this->getTaskData(); 28 + $id = idx($data, 'taskID'); 29 + if (!$id) { 30 + throw new PhabricatorWorkerPermanentFailureException( 31 + pht('Worker has no task ID.')); 32 + } 33 + return $id; 34 + } 35 + 36 + final protected function loadJob() { 37 + $id = $this->getJobID(); 38 + $job = id(new PhabricatorWorkerBulkJobQuery()) 39 + ->setViewer(PhabricatorUser::getOmnipotentUser()) 40 + ->withIDs(array($id)) 41 + ->executeOne(); 42 + if (!$job) { 43 + throw new PhabricatorWorkerPermanentFailureException( 44 + pht('Worker has invalid job ID ("%s").', $id)); 45 + } 46 + return $job; 47 + } 48 + 49 + final protected function loadTask() { 50 + $id = $this->getTaskID(); 51 + $task = id(new PhabricatorWorkerBulkTask())->load($id); 52 + if (!$task) { 53 + throw new PhabricatorWorkerPermanentFailureException( 54 + pht('Worker has invalid task ID ("%s").', $id)); 55 + } 56 + return $task; 57 + } 58 + 59 + final protected function loadActor(PhabricatorWorkerBulkJob $job) { 60 + $actor_phid = $job->getAuthorPHID(); 61 + $actor = id(new PhabricatorPeopleQuery()) 62 + ->setViewer(PhabricatorUser::getOmnipotentUser()) 63 + ->withPHIDs(array($actor_phid)) 64 + ->executeOne(); 65 + if (!$actor) { 66 + throw new PhabricatorWorkerPermanentFailureException( 67 + pht('Worker has invalid actor PHID ("%s").', $actor_phid)); 68 + } 69 + 70 + $can_edit = PhabricatorPolicyFilter::hasCapability( 71 + $actor, 72 + $job, 73 + PhabricatorPolicyCapability::CAN_EDIT); 74 + 75 + if (!$can_edit) { 76 + throw new PhabricatorWorkerPermanentFailureException( 77 + pht('Job actor does not have permission to edit job.')); 78 + } 79 + 80 + return $actor; 81 + } 82 + 83 + final protected function updateJob(PhabricatorWorkerBulkJob $job) { 84 + $has_work = $this->hasRemainingWork($job); 85 + if ($has_work) { 86 + return; 87 + } 88 + 89 + $lock = $this->acquireJobLock(); 90 + 91 + $job = $this->loadJob(); 92 + if ($job->getStatus() == PhabricatorWorkerBulkJob::STATUS_RUNNING) { 93 + if (!$this->hasRemainingWork($job)) { 94 + $this->updateJobStatus( 95 + $job, 96 + PhabricatorWorkerBulkJob::STATUS_COMPLETE); 97 + } 98 + } 99 + 100 + $lock->unlock(); 101 + } 102 + 103 + private function hasRemainingWork(PhabricatorWorkerBulkJob $job) { 104 + return (bool)queryfx_one( 105 + $job->establishConnection('r'), 106 + 'SELECT * FROM %T WHERE bulkJobPHID = %s 107 + AND status NOT IN (%Ls) LIMIT 1', 108 + id(new PhabricatorWorkerBulkTask())->getTableName(), 109 + $job->getPHID(), 110 + array( 111 + PhabricatorWorkerBulkTask::STATUS_DONE, 112 + PhabricatorWorkerBulkTask::STATUS_FAIL, 113 + )); 114 + } 115 + 116 + protected function updateJobStatus(PhabricatorWorkerBulkJob $job, $status) { 117 + $type_status = PhabricatorWorkerBulkJobTransaction::TYPE_STATUS; 118 + 119 + $xactions = array(); 120 + $xactions[] = id(new PhabricatorWorkerBulkJobTransaction()) 121 + ->setTransactionType($type_status) 122 + ->setNewValue($status); 123 + 124 + $daemon_source = PhabricatorContentSource::newForSource( 125 + PhabricatorContentSource::SOURCE_DAEMON, 126 + array()); 127 + 128 + $app_phid = id(new PhabricatorDaemonsApplication())->getPHID(); 129 + 130 + $editor = id(new PhabricatorWorkerBulkJobEditor()) 131 + ->setActor(PhabricatorUser::getOmnipotentUser()) 132 + ->setActingAsPHID($app_phid) 133 + ->setContentSource($daemon_source) 134 + ->setContinueOnMissingFields(true) 135 + ->applyTransactions($job, $xactions); 136 + } 137 + 138 + }
+87
src/infrastructure/daemon/workers/editor/PhabricatorWorkerBulkJobEditor.php
··· 1 + <?php 2 + 3 + final class PhabricatorWorkerBulkJobEditor 4 + extends PhabricatorApplicationTransactionEditor { 5 + 6 + public function getEditorApplicationClass() { 7 + return 'PhabricatorDaemonsApplication'; 8 + } 9 + 10 + public function getEditorObjectsDescription() { 11 + return pht('Bulk Jobs'); 12 + } 13 + 14 + public function getTransactionTypes() { 15 + $types = parent::getTransactionTypes(); 16 + 17 + $types[] = PhabricatorWorkerBulkJobTransaction::TYPE_STATUS; 18 + 19 + return $types; 20 + } 21 + 22 + protected function getCustomTransactionOldValue( 23 + PhabricatorLiskDAO $object, 24 + PhabricatorApplicationTransaction $xaction) { 25 + 26 + switch ($xaction->getTransactionType()) { 27 + case PhabricatorWorkerBulkJobTransaction::TYPE_STATUS: 28 + return $object->getStatus(); 29 + } 30 + } 31 + 32 + protected function getCustomTransactionNewValue( 33 + PhabricatorLiskDAO $object, 34 + PhabricatorApplicationTransaction $xaction) { 35 + 36 + switch ($xaction->getTransactionType()) { 37 + case PhabricatorWorkerBulkJobTransaction::TYPE_STATUS: 38 + return $xaction->getNewValue(); 39 + } 40 + } 41 + 42 + protected function applyCustomInternalTransaction( 43 + PhabricatorLiskDAO $object, 44 + PhabricatorApplicationTransaction $xaction) { 45 + 46 + $type = $xaction->getTransactionType(); 47 + $new = $xaction->getNewValue(); 48 + 49 + switch ($type) { 50 + case PhabricatorWorkerBulkJobTransaction::TYPE_STATUS: 51 + $object->setStatus($xaction->getNewValue()); 52 + return; 53 + } 54 + 55 + return parent::applyCustomInternalTransaction($object, $xaction); 56 + } 57 + 58 + protected function applyCustomExternalTransaction( 59 + PhabricatorLiskDAO $object, 60 + PhabricatorApplicationTransaction $xaction) { 61 + 62 + $type = $xaction->getTransactionType(); 63 + $new = $xaction->getNewValue(); 64 + 65 + switch ($type) { 66 + case PhabricatorWorkerBulkJobTransaction::TYPE_STATUS: 67 + switch ($new) { 68 + case PhabricatorWorkerBulkJob::STATUS_WAITING: 69 + PhabricatorWorker::scheduleTask( 70 + 'PhabricatorWorkerBulkJobCreateWorker', 71 + array( 72 + 'jobID' => $object->getID(), 73 + ), 74 + array( 75 + 'priority' => PhabricatorWorker::PRIORITY_BULK, 76 + )); 77 + break; 78 + } 79 + return; 80 + } 81 + 82 + return parent::applyCustomExternalTransaction($object, $xaction); 83 + } 84 + 85 + 86 + 87 + }
+37
src/infrastructure/daemon/workers/phid/PhabricatorWorkerBulkJobPHIDType.php
··· 1 + <?php 2 + 3 + final class PhabricatorWorkerBulkJobPHIDType extends PhabricatorPHIDType { 4 + 5 + const TYPECONST = 'BULK'; 6 + 7 + public function getTypeName() { 8 + return pht('Bulk Job'); 9 + } 10 + 11 + public function newObject() { 12 + return new PhabricatorWorkerBulkJob(); 13 + } 14 + 15 + protected function buildQueryForObjects( 16 + PhabricatorObjectQuery $query, 17 + array $phids) { 18 + 19 + return id(new PhabricatorWorkerBulkJobQuery()) 20 + ->withPHIDs($phids); 21 + } 22 + 23 + public function loadHandles( 24 + PhabricatorHandleQuery $query, 25 + array $handles, 26 + array $objects) { 27 + 28 + foreach ($handles as $phid => $handle) { 29 + $job = $objects[$phid]; 30 + 31 + $id = $job->getID(); 32 + 33 + $handle->setName(pht('Bulk Job %d', $id)); 34 + } 35 + } 36 + 37 + }
+106
src/infrastructure/daemon/workers/query/PhabricatorWorkerBulkJobQuery.php
··· 1 + <?php 2 + 3 + final class PhabricatorWorkerBulkJobQuery 4 + extends PhabricatorCursorPagedPolicyAwareQuery { 5 + 6 + private $ids; 7 + private $phids; 8 + private $authorPHIDs; 9 + private $bulkJobTypes; 10 + private $statuses; 11 + 12 + public function withIDs(array $ids) { 13 + $this->ids = $ids; 14 + return $this; 15 + } 16 + 17 + public function withPHIDs(array $phids) { 18 + $this->phids = $phids; 19 + return $this; 20 + } 21 + 22 + public function withAuthorPHIDs(array $author_phids) { 23 + $this->authorPHIDs = $author_phids; 24 + return $this; 25 + } 26 + 27 + public function withBulkJobTypes(array $job_types) { 28 + $this->bulkJobTypes = $job_types; 29 + return $this; 30 + } 31 + 32 + public function withStatuses(array $statuses) { 33 + $this->statuses = $statuses; 34 + return $this; 35 + } 36 + 37 + public function newResultObject() { 38 + return new PhabricatorWorkerBulkJob(); 39 + } 40 + 41 + protected function loadPage() { 42 + return $this->loadStandardPage($this->newResultObject()); 43 + } 44 + 45 + protected function willFilterPage(array $page) { 46 + $map = PhabricatorWorkerBulkJobType::getAllJobTypes(); 47 + 48 + foreach ($page as $key => $job) { 49 + $implementation = idx($map, $job->getJobTypeKey()); 50 + if (!$implementation) { 51 + $this->didRejectResult($job); 52 + unset($page[$key]); 53 + continue; 54 + } 55 + $job->attachJobImplementation($implementation); 56 + } 57 + 58 + return $page; 59 + } 60 + 61 + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { 62 + $where = parent::buildWhereClauseParts($conn); 63 + 64 + if ($this->ids !== null) { 65 + $where[] = qsprintf( 66 + $conn, 67 + 'id IN (%Ld)', 68 + $this->ids); 69 + } 70 + 71 + if ($this->phids !== null) { 72 + $where[] = qsprintf( 73 + $conn, 74 + 'phid IN (%Ls)', 75 + $this->phids); 76 + } 77 + 78 + if ($this->authorPHIDs !== null) { 79 + $where[] = qsprintf( 80 + $conn, 81 + 'authorPHID IN (%Ls)', 82 + $this->authorPHIDs); 83 + } 84 + 85 + if ($this->bulkJobTypes !== null) { 86 + $where[] = qsprintf( 87 + $conn, 88 + 'bulkJobType IN (%Ls)', 89 + $this->bulkJobTypes); 90 + } 91 + 92 + if ($this->statuses !== null) { 93 + $where[] = qsprintf( 94 + $conn, 95 + 'status IN (%Ls)', 96 + $this->statuses); 97 + } 98 + 99 + return $where; 100 + } 101 + 102 + public function getQueryApplicationClass() { 103 + return 'PhabricatorDaemonsApplication'; 104 + } 105 + 106 + }
+98
src/infrastructure/daemon/workers/query/PhabricatorWorkerBulkJobSearchEngine.php
··· 1 + <?php 2 + 3 + final class PhabricatorWorkerBulkJobSearchEngine 4 + extends PhabricatorApplicationSearchEngine { 5 + 6 + public function getResultTypeDescription() { 7 + return pht('Bulk Jobs'); 8 + } 9 + 10 + public function getApplicationClassName() { 11 + return 'PhabricatorDaemonsApplication'; 12 + } 13 + 14 + public function newQuery() { 15 + return id(new PhabricatorWorkerBulkJobQuery()); 16 + } 17 + 18 + protected function buildQueryFromParameters(array $map) { 19 + $query = $this->newQuery(); 20 + 21 + if ($map['authorPHIDs']) { 22 + $query->withAuthorPHIDs($map['authorPHIDs']); 23 + } 24 + 25 + return $query; 26 + } 27 + 28 + protected function buildCustomSearchFields() { 29 + return array( 30 + id(new PhabricatorSearchUsersField()) 31 + ->setLabel(pht('Authors')) 32 + ->setKey('authorPHIDs') 33 + ->setAliases(array('author', 'authors')), 34 + ); 35 + } 36 + 37 + protected function getURI($path) { 38 + return '/daemon/bulk/'.$path; 39 + } 40 + 41 + protected function getBuiltinQueryNames() { 42 + $names = array(); 43 + 44 + if ($this->requireViewer()->isLoggedIn()) { 45 + $names['authored'] = pht('Authored Jobs'); 46 + } 47 + 48 + $names['all'] = pht('All Jobs'); 49 + 50 + return $names; 51 + } 52 + 53 + public function buildSavedQueryFromBuiltin($query_key) { 54 + 55 + $query = $this->newSavedQuery(); 56 + $query->setQueryKey($query_key); 57 + 58 + switch ($query_key) { 59 + case 'all': 60 + return $query; 61 + case 'authored': 62 + return $query->setParameter( 63 + 'authorPHIDs', 64 + array($this->requireViewer()->getPHID())); 65 + } 66 + 67 + return parent::buildSavedQueryFromBuiltin($query_key); 68 + } 69 + 70 + protected function renderResultList( 71 + array $jobs, 72 + PhabricatorSavedQuery $query, 73 + array $handles) { 74 + assert_instances_of($jobs, 'PhabricatorWorkerBulkJob'); 75 + 76 + $viewer = $this->requireViewer(); 77 + 78 + $list = id(new PHUIObjectItemListView()) 79 + ->setUser($viewer); 80 + foreach ($jobs as $job) { 81 + $size = pht('%s Bulk Task(s)', new PhutilNumber($job->getSize())); 82 + 83 + $item = id(new PHUIObjectItemView()) 84 + ->setObjectName(pht('Bulk Job %d', $job->getID())) 85 + ->setHeader($job->getJobName()) 86 + ->addAttribute(phabricator_datetime($job->getDateCreated(), $viewer)) 87 + ->setHref($job->getManageURI()) 88 + ->addIcon($job->getStatusIcon(), $job->getStatusName()) 89 + ->addIcon('none', $size); 90 + 91 + $list->addItem($item); 92 + } 93 + 94 + // TODO: Needs new wrapper when merging to redesign. 95 + 96 + return $list; 97 + } 98 + }
+10
src/infrastructure/daemon/workers/query/PhabricatorWorkerBulkJobTransactionQuery.php
··· 1 + <?php 2 + 3 + final class PhabricatorWorkerBulkJobTransactionQuery 4 + extends PhabricatorApplicationTransactionQuery { 5 + 6 + public function getTemplateApplicationTransaction() { 7 + return new PhabricatorWorkerBulkJobTransaction(); 8 + } 9 + 10 + }
+272
src/infrastructure/daemon/workers/storage/PhabricatorWorkerBulkJob.php
··· 1 + <?php 2 + 3 + /** 4 + * @task implementation Job Implementation 5 + */ 6 + final class PhabricatorWorkerBulkJob 7 + extends PhabricatorWorkerDAO 8 + implements 9 + PhabricatorPolicyInterface, 10 + PhabricatorSubscribableInterface, 11 + PhabricatorApplicationTransactionInterface, 12 + PhabricatorDestructibleInterface { 13 + 14 + const STATUS_CONFIRM = 'confirm'; 15 + const STATUS_WAITING = 'waiting'; 16 + const STATUS_RUNNING = 'running'; 17 + const STATUS_COMPLETE = 'complete'; 18 + 19 + protected $authorPHID; 20 + protected $jobTypeKey; 21 + protected $status; 22 + protected $parameters = array(); 23 + protected $size; 24 + 25 + private $jobImplementation = self::ATTACHABLE; 26 + 27 + protected function getConfiguration() { 28 + return array( 29 + self::CONFIG_AUX_PHID => true, 30 + self::CONFIG_SERIALIZATION => array( 31 + 'parameters' => self::SERIALIZATION_JSON, 32 + ), 33 + self::CONFIG_COLUMN_SCHEMA => array( 34 + 'jobTypeKey' => 'text32', 35 + 'status' => 'text32', 36 + 'size' => 'uint32', 37 + ), 38 + self::CONFIG_KEY_SCHEMA => array( 39 + 'key_type' => array( 40 + 'columns' => array('jobTypeKey'), 41 + ), 42 + 'key_author' => array( 43 + 'columns' => array('authorPHID'), 44 + ), 45 + 'key_status' => array( 46 + 'columns' => array('status'), 47 + ), 48 + ), 49 + ) + parent::getConfiguration(); 50 + } 51 + 52 + public static function initializeNewJob( 53 + PhabricatorUser $actor, 54 + PhabricatorWorkerBulkJobType $type, 55 + array $parameters) { 56 + 57 + $job = id(new PhabricatorWorkerBulkJob()) 58 + ->setAuthorPHID($actor->getPHID()) 59 + ->setJobTypeKey($type->getBulkJobTypeKey()) 60 + ->setParameters($parameters) 61 + ->attachJobImplementation($type); 62 + 63 + $job->setSize($job->computeSize()); 64 + 65 + return $job; 66 + } 67 + 68 + public function generatePHID() { 69 + return PhabricatorPHID::generateNewPHID( 70 + PhabricatorWorkerBulkJobPHIDType::TYPECONST); 71 + } 72 + 73 + public function getMonitorURI() { 74 + return '/daemon/bulk/monitor/'.$this->getID().'/'; 75 + } 76 + 77 + public function getManageURI() { 78 + return '/daemon/bulk/view/'.$this->getID().'/'; 79 + } 80 + 81 + public function getParameter($key, $default = null) { 82 + return idx($this->parameters, $key, $default); 83 + } 84 + 85 + public function setParameter($key, $value) { 86 + $this->parameters[$key] = $value; 87 + return $this; 88 + } 89 + 90 + public function loadTaskStatusCounts() { 91 + $table = new PhabricatorWorkerBulkTask(); 92 + $conn_r = $table->establishConnection('r'); 93 + $rows = queryfx_all( 94 + $conn_r, 95 + 'SELECT status, COUNT(*) N FROM %T WHERE bulkJobPHID = %s 96 + GROUP BY status', 97 + $table->getTableName(), 98 + $this->getPHID()); 99 + 100 + return ipull($rows, 'N', 'status'); 101 + } 102 + 103 + public function newContentSource() { 104 + return PhabricatorContentSource::newForSource( 105 + PhabricatorContentSource::SOURCE_BULK, 106 + array( 107 + 'jobID' => $this->getID(), 108 + )); 109 + } 110 + 111 + public function getStatusIcon() { 112 + $map = array( 113 + self::STATUS_CONFIRM => 'fa-question', 114 + self::STATUS_WAITING => 'fa-clock-o', 115 + self::STATUS_RUNNING => 'fa-clock-o', 116 + self::STATUS_COMPLETE => 'fa-check grey', 117 + ); 118 + 119 + return idx($map, $this->getStatus(), 'none'); 120 + } 121 + 122 + public function getStatusName() { 123 + $map = array( 124 + self::STATUS_CONFIRM => pht('Confirming'), 125 + self::STATUS_WAITING => pht('Waiting'), 126 + self::STATUS_RUNNING => pht('Running'), 127 + self::STATUS_COMPLETE => pht('Complete'), 128 + ); 129 + 130 + return idx($map, $this->getStatus(), $this->getStatus()); 131 + } 132 + 133 + 134 + /* -( Job Implementation )------------------------------------------------- */ 135 + 136 + 137 + protected function getJobImplementation() { 138 + return $this->assertAttached($this->jobImplementation); 139 + } 140 + 141 + public function attachJobImplementation(PhabricatorWorkerBulkJobType $type) { 142 + $this->jobImplementation = $type; 143 + return $this; 144 + } 145 + 146 + private function computeSize() { 147 + return $this->getJobImplementation()->getJobSize($this); 148 + } 149 + 150 + public function getCancelURI() { 151 + return $this->getJobImplementation()->getCancelURI($this); 152 + } 153 + 154 + public function getDoneURI() { 155 + return $this->getJobImplementation()->getDoneURI($this); 156 + } 157 + 158 + public function getDescriptionForConfirm() { 159 + return $this->getJobImplementation()->getDescriptionForConfirm($this); 160 + } 161 + 162 + public function createTasks() { 163 + return $this->getJobImplementation()->createTasks($this); 164 + } 165 + 166 + public function runTask( 167 + PhabricatorUser $actor, 168 + PhabricatorWorkerBulkTask $task) { 169 + return $this->getJobImplementation()->runTask($actor, $this, $task); 170 + } 171 + 172 + public function getJobName() { 173 + return $this->getJobImplementation()->getJobName($this); 174 + } 175 + 176 + 177 + /* -( PhabricatorPolicyInterface )----------------------------------------- */ 178 + 179 + 180 + public function getCapabilities() { 181 + return array( 182 + PhabricatorPolicyCapability::CAN_VIEW, 183 + PhabricatorPolicyCapability::CAN_EDIT, 184 + ); 185 + } 186 + 187 + public function getPolicy($capability) { 188 + switch ($capability) { 189 + case PhabricatorPolicyCapability::CAN_VIEW: 190 + return PhabricatorPolicies::getMostOpenPolicy(); 191 + case PhabricatorPolicyCapability::CAN_EDIT: 192 + return $this->getAuthorPHID(); 193 + } 194 + } 195 + 196 + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 197 + return false; 198 + } 199 + 200 + public function describeAutomaticCapability($capability) { 201 + switch ($capability) { 202 + case PhabricatorPolicyCapability::CAN_EDIT: 203 + return pht('Only the owner of a bulk job can edit it.'); 204 + default: 205 + return null; 206 + } 207 + } 208 + 209 + 210 + /* -( PhabricatorSubscribableInterface )----------------------------------- */ 211 + 212 + 213 + public function isAutomaticallySubscribed($phid) { 214 + return false; 215 + } 216 + 217 + public function shouldShowSubscribersProperty() { 218 + return true; 219 + } 220 + 221 + public function shouldAllowSubscription($phid) { 222 + return true; 223 + } 224 + 225 + 226 + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ 227 + 228 + 229 + public function getApplicationTransactionEditor() { 230 + return new PhabricatorWorkerBulkJobEditor(); 231 + } 232 + 233 + public function getApplicationTransactionObject() { 234 + return $this; 235 + } 236 + 237 + public function getApplicationTransactionTemplate() { 238 + return new PhabricatorWorkerBulkJobTransaction(); 239 + } 240 + 241 + public function willRenderTimeline( 242 + PhabricatorApplicationTransactionView $timeline, 243 + AphrontRequest $request) { 244 + return $timeline; 245 + } 246 + 247 + /* -( PhabricatorDestructibleInterface )----------------------------------- */ 248 + 249 + 250 + public function destroyObjectPermanently( 251 + PhabricatorDestructionEngine $engine) { 252 + 253 + $this->openTransaction(); 254 + 255 + // We're only removing the actual task objects. This may leave stranded 256 + // workers in the queue itself, but they'll just flush out automatically 257 + // when they can't load bulk job data. 258 + 259 + $task_table = new PhabricatorWorkerBulkTask(); 260 + $conn_w = $task_table->establishConnection('w'); 261 + queryfx( 262 + $conn_w, 263 + 'DELETE FROM %T WHERE bulkJobPHID = %s', 264 + $task_table->getPHID(), 265 + $this->getPHID()); 266 + 267 + $this->delete(); 268 + $this->saveTransaction(); 269 + } 270 + 271 + 272 + }
+51
src/infrastructure/daemon/workers/storage/PhabricatorWorkerBulkJobTransaction.php
··· 1 + <?php 2 + 3 + final class PhabricatorWorkerBulkJobTransaction 4 + extends PhabricatorApplicationTransaction { 5 + 6 + const TYPE_STATUS = 'bulkjob.status'; 7 + 8 + public function getApplicationName() { 9 + return 'worker'; 10 + } 11 + 12 + public function getApplicationTransactionType() { 13 + return PhabricatorWorkerBulkJobPHIDType::TYPECONST; 14 + } 15 + 16 + public function getTitle() { 17 + $author_phid = $this->getAuthorPHID(); 18 + 19 + $old = $this->getOldValue(); 20 + $new = $this->getNewValue(); 21 + 22 + $type = $this->getTransactionType(); 23 + switch ($type) { 24 + case self::TYPE_STATUS: 25 + if ($old === null) { 26 + return pht( 27 + '%s created this bulk job.', 28 + $this->renderHandleLink($author_phid)); 29 + } else { 30 + switch ($new) { 31 + case PhabricatorWorkerBulkJob::STATUS_WAITING: 32 + return pht( 33 + '%s confirmed this job.', 34 + $this->renderHandleLink($author_phid)); 35 + case PhabricatorWorkerBulkJob::STATUS_RUNNING: 36 + return pht( 37 + '%s marked this job as running.', 38 + $this->renderHandleLink($author_phid)); 39 + case PhabricatorWorkerBulkJob::STATUS_COMPLETE: 40 + return pht( 41 + '%s marked this job complete.', 42 + $this->renderHandleLink($author_phid)); 43 + } 44 + } 45 + break; 46 + } 47 + 48 + return parent::getTitle(); 49 + } 50 + 51 + }
+46
src/infrastructure/daemon/workers/storage/PhabricatorWorkerBulkTask.php
··· 1 + <?php 2 + 3 + final class PhabricatorWorkerBulkTask 4 + extends PhabricatorWorkerDAO { 5 + 6 + const STATUS_WAITING = 'waiting'; 7 + const STATUS_RUNNING = 'running'; 8 + const STATUS_DONE = 'done'; 9 + const STATUS_FAIL = 'fail'; 10 + 11 + protected $bulkJobPHID; 12 + protected $objectPHID; 13 + protected $status; 14 + protected $data = array(); 15 + 16 + protected function getConfiguration() { 17 + return array( 18 + self::CONFIG_TIMESTAMPS => false, 19 + self::CONFIG_SERIALIZATION => array( 20 + 'data' => self::SERIALIZATION_JSON, 21 + ), 22 + self::CONFIG_COLUMN_SCHEMA => array( 23 + 'status' => 'text32', 24 + ), 25 + self::CONFIG_KEY_SCHEMA => array( 26 + 'key_job' => array( 27 + 'columns' => array('bulkJobPHID', 'status'), 28 + ), 29 + 'key_object' => array( 30 + 'columns' => array('objectPHID'), 31 + ), 32 + ), 33 + ) + parent::getConfiguration(); 34 + } 35 + 36 + public static function initializeNewTask( 37 + PhabricatorWorkerBulkJob $job, 38 + $object_phid) { 39 + 40 + return id(new PhabricatorWorkerBulkTask()) 41 + ->setBulkJobPHID($job->getPHID()) 42 + ->setStatus(self::STATUS_WAITING) 43 + ->setObjectPHID($object_phid); 44 + } 45 + 46 + }
+10
src/infrastructure/daemon/workers/storage/PhabricatorWorkerSchemaSpec.php
··· 1 + <?php 2 + 3 + final class PhabricatorWorkerSchemaSpec 4 + extends PhabricatorConfigSchemaSpec { 5 + 6 + public function buildSchemata() { 7 + $this->buildEdgeSchemata(new PhabricatorWorkerBulkJob()); 8 + } 9 + 10 + }
+12
src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php
··· 1170 1170 'This call takes %s parameters, but only %s are documented.', 1171 1171 ), 1172 1172 ), 1173 + 1174 + '%s Passed Test(s)' => '%s Passed', 1175 + '%s Failed Test(s)' => '%s Failed', 1176 + '%s Skipped Test(s)' => '%s Skipped', 1177 + '%s Broken Test(s)' => '%s Broken', 1178 + '%s Unsound Test(s)' => '%s Unsound', 1179 + '%s Other Test(s)' => '%s Other', 1180 + 1181 + '%s Bulk Task(s)' => array( 1182 + '%s Task', 1183 + '%s Tasks', 1184 + ), 1173 1185 ); 1174 1186 } 1175 1187
+32
webroot/rsrc/css/application/daemon/bulk-job.css
··· 1 + /** 2 + * @provides bulk-job-css 3 + */ 4 + 5 + .bulk-job-progress-bar { 6 + position: relative; 7 + width: 100%; 8 + border: 1px solid {$lightgreyborder}; 9 + height: 32px; 10 + } 11 + 12 + .bulk-job-progress-slice { 13 + position: absolute; 14 + top: 0; 15 + bottom: 0; 16 + } 17 + 18 + .bulk-job-progress-slice-green { 19 + background-color: {$green}; 20 + } 21 + 22 + .bulk-job-progress-slice-blue { 23 + background-color: {$blue}; 24 + } 25 + 26 + .bulk-job-progress-slice-red { 27 + background-color: {$red}; 28 + } 29 + 30 + .bulk-job-progress-slice-empty { 31 + background-color: {$lightbluebackground}; 32 + }
+5
webroot/rsrc/css/application/differential/table-of-contents.css
··· 83 83 border-top: 1px solid {$thinblueborder}; 84 84 padding: 8px; 85 85 } 86 + 87 + .differential-harbormaster-table-view { 88 + margin: 4px 0; 89 + border: 1px solid {$thinblueborder}; 90 + }
+18
webroot/rsrc/js/application/daemon/behavior-bulk-job-reload.js
··· 1 + /** 2 + * @provides javelin-behavior-bulk-job-reload 3 + * @requires javelin-behavior 4 + * javelin-uri 5 + */ 6 + 7 + JX.behavior('bulk-job-reload', function() { 8 + 9 + // TODO: It would be nice to have a pretty Ajax progress bar here, but just 10 + // reload the page for now. 11 + 12 + function reload() { 13 + JX.$U().go(); 14 + } 15 + 16 + setTimeout(reload, 1000); 17 + 18 + });