mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-28 16:30:59 +01:00
Merge branch 'master' into redesign-2015
This commit is contained in:
commit
075f041bec
51 changed files with 2624 additions and 425 deletions
15
resources/sql/autopatches/20150622.bulk.1.job.sql
Normal file
15
resources/sql/autopatches/20150622.bulk.1.job.sql
Normal file
|
@ -0,0 +1,15 @@
|
|||
CREATE TABLE {$NAMESPACE}_worker.worker_bulkjob (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
phid VARBINARY(64) NOT NULL,
|
||||
authorPHID VARBINARY(64) NOT NULL,
|
||||
jobTypeKey VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||
status VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||
parameters LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||
size INT UNSIGNED NOT NULL,
|
||||
dateCreated INT UNSIGNED NOT NULL,
|
||||
dateModified INT UNSIGNED NOT NULL,
|
||||
UNIQUE KEY `key_phid` (phid),
|
||||
KEY `key_type` (jobTypeKey),
|
||||
KEY `key_author` (authorPHID),
|
||||
KEY `key_status` (status)
|
||||
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
|
9
resources/sql/autopatches/20150622.bulk.2.task.sql
Normal file
9
resources/sql/autopatches/20150622.bulk.2.task.sql
Normal file
|
@ -0,0 +1,9 @@
|
|||
CREATE TABLE {$NAMESPACE}_worker.worker_bulktask (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
bulkJobPHID VARBINARY(64) NOT NULL,
|
||||
objectPHID VARBINARY(64) NOT NULL,
|
||||
status VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||
data LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||
KEY `key_job` (bulkJobPHID, status),
|
||||
KEY `key_object` (objectPHID)
|
||||
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
|
19
resources/sql/autopatches/20150622.bulk.3.xaction.sql
Normal file
19
resources/sql/autopatches/20150622.bulk.3.xaction.sql
Normal file
|
@ -0,0 +1,19 @@
|
|||
CREATE TABLE {$NAMESPACE}_worker.worker_bulkjobtransaction (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
phid VARBINARY(64) NOT NULL,
|
||||
authorPHID VARBINARY(64) NOT NULL,
|
||||
objectPHID VARBINARY(64) NOT NULL,
|
||||
viewPolicy VARBINARY(64) NOT NULL,
|
||||
editPolicy VARBINARY(64) NOT NULL,
|
||||
commentPHID VARBINARY(64) DEFAULT NULL,
|
||||
commentVersion INT UNSIGNED NOT NULL,
|
||||
transactionType VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||
oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||
newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||
contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||
metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||
dateCreated INT UNSIGNED NOT NULL,
|
||||
dateModified INT UNSIGNED NOT NULL,
|
||||
UNIQUE KEY `key_phid` (`phid`),
|
||||
KEY `key_object` (`objectPHID`)
|
||||
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
|
16
resources/sql/autopatches/20150622.bulk.4.edge.sql
Normal file
16
resources/sql/autopatches/20150622.bulk.4.edge.sql
Normal file
|
@ -0,0 +1,16 @@
|
|||
CREATE TABLE {$NAMESPACE}_worker.edge (
|
||||
src VARBINARY(64) NOT NULL,
|
||||
type INT UNSIGNED NOT NULL,
|
||||
dst VARBINARY(64) NOT NULL,
|
||||
dateCreated INT UNSIGNED NOT NULL,
|
||||
seq INT UNSIGNED NOT NULL,
|
||||
dataID INT UNSIGNED,
|
||||
PRIMARY KEY (src, type, dst),
|
||||
KEY `src` (src, type, dateCreated, seq),
|
||||
UNIQUE KEY `key_dst` (dst, type, src)
|
||||
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
|
||||
|
||||
CREATE TABLE {$NAMESPACE}_worker.edgedata (
|
||||
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
data LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}
|
||||
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
|
|
@ -379,6 +379,7 @@ phutil_register_library_map(array(
|
|||
'DifferentialGetWorkingCopy' => 'applications/differential/DifferentialGetWorkingCopy.php',
|
||||
'DifferentialGitHubLandingStrategy' => 'applications/differential/landing/DifferentialGitHubLandingStrategy.php',
|
||||
'DifferentialGitSVNIDField' => 'applications/differential/customfield/DifferentialGitSVNIDField.php',
|
||||
'DifferentialHarbormasterField' => 'applications/differential/customfield/DifferentialHarbormasterField.php',
|
||||
'DifferentialHiddenComment' => 'applications/differential/storage/DifferentialHiddenComment.php',
|
||||
'DifferentialHostField' => 'applications/differential/customfield/DifferentialHostField.php',
|
||||
'DifferentialHostedGitLandingStrategy' => 'applications/differential/landing/DifferentialHostedGitLandingStrategy.php',
|
||||
|
@ -893,6 +894,7 @@ phutil_register_library_map(array(
|
|||
'HarbormasterDAO' => 'applications/harbormaster/storage/HarbormasterDAO.php',
|
||||
'HarbormasterHTTPRequestBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php',
|
||||
'HarbormasterLeaseHostBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterLeaseHostBuildStepImplementation.php',
|
||||
'HarbormasterLintMessagesController' => 'applications/harbormaster/controller/HarbormasterLintMessagesController.php',
|
||||
'HarbormasterLintPropertyView' => 'applications/harbormaster/view/HarbormasterLintPropertyView.php',
|
||||
'HarbormasterManagePlansCapability' => 'applications/harbormaster/capability/HarbormasterManagePlansCapability.php',
|
||||
'HarbormasterManagementBuildWorkflow' => 'applications/harbormaster/management/HarbormasterManagementBuildWorkflow.php',
|
||||
|
@ -921,6 +923,7 @@ phutil_register_library_map(array(
|
|||
'HarbormasterTargetWorker' => 'applications/harbormaster/worker/HarbormasterTargetWorker.php',
|
||||
'HarbormasterThrowExceptionBuildStep' => 'applications/harbormaster/step/HarbormasterThrowExceptionBuildStep.php',
|
||||
'HarbormasterUIEventListener' => 'applications/harbormaster/event/HarbormasterUIEventListener.php',
|
||||
'HarbormasterUnitMessagesController' => 'applications/harbormaster/controller/HarbormasterUnitMessagesController.php',
|
||||
'HarbormasterUnitPropertyView' => 'applications/harbormaster/view/HarbormasterUnitPropertyView.php',
|
||||
'HarbormasterUploadArtifactBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php',
|
||||
'HarbormasterWaitForPreviousBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php',
|
||||
|
@ -1082,6 +1085,7 @@ phutil_register_library_map(array(
|
|||
'ManiphestTaskDependedOnByTaskEdgeType' => 'applications/maniphest/edge/ManiphestTaskDependedOnByTaskEdgeType.php',
|
||||
'ManiphestTaskDependsOnTaskEdgeType' => 'applications/maniphest/edge/ManiphestTaskDependsOnTaskEdgeType.php',
|
||||
'ManiphestTaskDetailController' => 'applications/maniphest/controller/ManiphestTaskDetailController.php',
|
||||
'ManiphestTaskEditBulkJobType' => 'applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php',
|
||||
'ManiphestTaskEditController' => 'applications/maniphest/controller/ManiphestTaskEditController.php',
|
||||
'ManiphestTaskHasCommitEdgeType' => 'applications/maniphest/edge/ManiphestTaskHasCommitEdgeType.php',
|
||||
'ManiphestTaskHasMockEdgeType' => 'applications/maniphest/edge/ManiphestTaskHasMockEdgeType.php',
|
||||
|
@ -1708,6 +1712,9 @@ phutil_register_library_map(array(
|
|||
'PhabricatorCustomFieldStringIndexStorage' => 'infrastructure/customfield/storage/PhabricatorCustomFieldStringIndexStorage.php',
|
||||
'PhabricatorCustomHeaderConfigType' => 'applications/config/custom/PhabricatorCustomHeaderConfigType.php',
|
||||
'PhabricatorDaemon' => 'infrastructure/daemon/PhabricatorDaemon.php',
|
||||
'PhabricatorDaemonBulkJobListController' => 'applications/daemon/controller/PhabricatorDaemonBulkJobListController.php',
|
||||
'PhabricatorDaemonBulkJobMonitorController' => 'applications/daemon/controller/PhabricatorDaemonBulkJobMonitorController.php',
|
||||
'PhabricatorDaemonBulkJobViewController' => 'applications/daemon/controller/PhabricatorDaemonBulkJobViewController.php',
|
||||
'PhabricatorDaemonConsoleController' => 'applications/daemon/controller/PhabricatorDaemonConsoleController.php',
|
||||
'PhabricatorDaemonController' => 'applications/daemon/controller/PhabricatorDaemonController.php',
|
||||
'PhabricatorDaemonDAO' => 'applications/daemon/storage/PhabricatorDaemonDAO.php',
|
||||
|
@ -2111,10 +2118,14 @@ phutil_register_library_map(array(
|
|||
'PhabricatorMetaMTAMail' => 'applications/metamta/storage/PhabricatorMetaMTAMail.php',
|
||||
'PhabricatorMetaMTAMailBody' => 'applications/metamta/view/PhabricatorMetaMTAMailBody.php',
|
||||
'PhabricatorMetaMTAMailBodyTestCase' => 'applications/metamta/view/__tests__/PhabricatorMetaMTAMailBodyTestCase.php',
|
||||
'PhabricatorMetaMTAMailHasRecipientEdgeType' => 'applications/metamta/edge/PhabricatorMetaMTAMailHasRecipientEdgeType.php',
|
||||
'PhabricatorMetaMTAMailListController' => 'applications/metamta/controller/PhabricatorMetaMTAMailListController.php',
|
||||
'PhabricatorMetaMTAMailPHIDType' => 'applications/metamta/phid/PhabricatorMetaMTAMailPHIDType.php',
|
||||
'PhabricatorMetaMTAMailQuery' => 'applications/metamta/query/PhabricatorMetaMTAMailQuery.php',
|
||||
'PhabricatorMetaMTAMailSearchEngine' => 'applications/metamta/query/PhabricatorMetaMTAMailSearchEngine.php',
|
||||
'PhabricatorMetaMTAMailSection' => 'applications/metamta/view/PhabricatorMetaMTAMailSection.php',
|
||||
'PhabricatorMetaMTAMailTestCase' => 'applications/metamta/storage/__tests__/PhabricatorMetaMTAMailTestCase.php',
|
||||
'PhabricatorMetaMTAMailViewController' => 'applications/metamta/controller/PhabricatorMetaMTAMailViewController.php',
|
||||
'PhabricatorMetaMTAMailableDatasource' => 'applications/metamta/typeahead/PhabricatorMetaMTAMailableDatasource.php',
|
||||
'PhabricatorMetaMTAMailableFunctionDatasource' => 'applications/metamta/typeahead/PhabricatorMetaMTAMailableFunctionDatasource.php',
|
||||
'PhabricatorMetaMTAMailgunReceiveController' => 'applications/metamta/controller/PhabricatorMetaMTAMailgunReceiveController.php',
|
||||
|
@ -2815,6 +2826,19 @@ phutil_register_library_map(array(
|
|||
'PhabricatorWorkerActiveTask' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php',
|
||||
'PhabricatorWorkerArchiveTask' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerArchiveTask.php',
|
||||
'PhabricatorWorkerArchiveTaskQuery' => 'infrastructure/daemon/workers/query/PhabricatorWorkerArchiveTaskQuery.php',
|
||||
'PhabricatorWorkerBulkJob' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerBulkJob.php',
|
||||
'PhabricatorWorkerBulkJobCreateWorker' => 'infrastructure/daemon/workers/bulk/PhabricatorWorkerBulkJobCreateWorker.php',
|
||||
'PhabricatorWorkerBulkJobEditor' => 'infrastructure/daemon/workers/editor/PhabricatorWorkerBulkJobEditor.php',
|
||||
'PhabricatorWorkerBulkJobPHIDType' => 'infrastructure/daemon/workers/phid/PhabricatorWorkerBulkJobPHIDType.php',
|
||||
'PhabricatorWorkerBulkJobQuery' => 'infrastructure/daemon/workers/query/PhabricatorWorkerBulkJobQuery.php',
|
||||
'PhabricatorWorkerBulkJobSearchEngine' => 'infrastructure/daemon/workers/query/PhabricatorWorkerBulkJobSearchEngine.php',
|
||||
'PhabricatorWorkerBulkJobTaskWorker' => 'infrastructure/daemon/workers/bulk/PhabricatorWorkerBulkJobTaskWorker.php',
|
||||
'PhabricatorWorkerBulkJobTestCase' => 'infrastructure/daemon/workers/__tests__/PhabricatorWorkerBulkJobTestCase.php',
|
||||
'PhabricatorWorkerBulkJobTransaction' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerBulkJobTransaction.php',
|
||||
'PhabricatorWorkerBulkJobTransactionQuery' => 'infrastructure/daemon/workers/query/PhabricatorWorkerBulkJobTransactionQuery.php',
|
||||
'PhabricatorWorkerBulkJobType' => 'infrastructure/daemon/workers/bulk/PhabricatorWorkerBulkJobType.php',
|
||||
'PhabricatorWorkerBulkJobWorker' => 'infrastructure/daemon/workers/bulk/PhabricatorWorkerBulkJobWorker.php',
|
||||
'PhabricatorWorkerBulkTask' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerBulkTask.php',
|
||||
'PhabricatorWorkerDAO' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerDAO.php',
|
||||
'PhabricatorWorkerLeaseQuery' => 'infrastructure/daemon/workers/query/PhabricatorWorkerLeaseQuery.php',
|
||||
'PhabricatorWorkerManagementCancelWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementCancelWorkflow.php',
|
||||
|
@ -2824,6 +2848,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorWorkerManagementRetryWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementRetryWorkflow.php',
|
||||
'PhabricatorWorkerManagementWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementWorkflow.php',
|
||||
'PhabricatorWorkerPermanentFailureException' => 'infrastructure/daemon/workers/exception/PhabricatorWorkerPermanentFailureException.php',
|
||||
'PhabricatorWorkerSchemaSpec' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerSchemaSpec.php',
|
||||
'PhabricatorWorkerTask' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerTask.php',
|
||||
'PhabricatorWorkerTaskData' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerTaskData.php',
|
||||
'PhabricatorWorkerTaskDetailController' => 'applications/daemon/controller/PhabricatorWorkerTaskDetailController.php',
|
||||
|
@ -3744,6 +3769,7 @@ phutil_register_library_map(array(
|
|||
'DifferentialGetWorkingCopy' => 'Phobject',
|
||||
'DifferentialGitHubLandingStrategy' => 'DifferentialLandingStrategy',
|
||||
'DifferentialGitSVNIDField' => 'DifferentialCustomField',
|
||||
'DifferentialHarbormasterField' => 'DifferentialCustomField',
|
||||
'DifferentialHiddenComment' => 'DifferentialDAO',
|
||||
'DifferentialHostField' => 'DifferentialCustomField',
|
||||
'DifferentialHostedGitLandingStrategy' => 'DifferentialLandingStrategy',
|
||||
|
@ -3769,7 +3795,7 @@ phutil_register_library_map(array(
|
|||
'DifferentialLandingStrategy' => 'Phobject',
|
||||
'DifferentialLegacyHunk' => 'DifferentialHunk',
|
||||
'DifferentialLineAdjustmentMap' => 'Phobject',
|
||||
'DifferentialLintField' => 'DifferentialCustomField',
|
||||
'DifferentialLintField' => 'DifferentialHarbormasterField',
|
||||
'DifferentialLintStatus' => 'Phobject',
|
||||
'DifferentialLocalCommitsView' => 'AphrontView',
|
||||
'DifferentialManiphestTasksField' => 'DifferentialCoreCustomField',
|
||||
|
@ -3845,7 +3871,7 @@ phutil_register_library_map(array(
|
|||
'DifferentialTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||
'DifferentialTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||
'DifferentialTransactionView' => 'PhabricatorApplicationTransactionView',
|
||||
'DifferentialUnitField' => 'DifferentialCustomField',
|
||||
'DifferentialUnitField' => 'DifferentialHarbormasterField',
|
||||
'DifferentialUnitStatus' => 'Phobject',
|
||||
'DifferentialUnitTestResult' => 'Phobject',
|
||||
'DifferentialUpdateRevisionConduitAPIMethod' => 'DifferentialConduitAPIMethod',
|
||||
|
@ -4356,6 +4382,7 @@ phutil_register_library_map(array(
|
|||
'HarbormasterDAO' => 'PhabricatorLiskDAO',
|
||||
'HarbormasterHTTPRequestBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
|
||||
'HarbormasterLeaseHostBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
|
||||
'HarbormasterLintMessagesController' => 'HarbormasterController',
|
||||
'HarbormasterLintPropertyView' => 'AphrontView',
|
||||
'HarbormasterManagePlansCapability' => 'PhabricatorPolicyCapability',
|
||||
'HarbormasterManagementBuildWorkflow' => 'HarbormasterManagementWorkflow',
|
||||
|
@ -4384,6 +4411,7 @@ phutil_register_library_map(array(
|
|||
'HarbormasterTargetWorker' => 'HarbormasterWorker',
|
||||
'HarbormasterThrowExceptionBuildStep' => 'HarbormasterBuildStepImplementation',
|
||||
'HarbormasterUIEventListener' => 'PhabricatorEventListener',
|
||||
'HarbormasterUnitMessagesController' => 'HarbormasterController',
|
||||
'HarbormasterUnitPropertyView' => 'AphrontView',
|
||||
'HarbormasterUploadArtifactBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
|
||||
'HarbormasterWaitForPreviousBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
|
||||
|
@ -4584,6 +4612,7 @@ phutil_register_library_map(array(
|
|||
'ManiphestTaskDependedOnByTaskEdgeType' => 'PhabricatorEdgeType',
|
||||
'ManiphestTaskDependsOnTaskEdgeType' => 'PhabricatorEdgeType',
|
||||
'ManiphestTaskDetailController' => 'ManiphestController',
|
||||
'ManiphestTaskEditBulkJobType' => 'PhabricatorWorkerBulkJobType',
|
||||
'ManiphestTaskEditController' => 'ManiphestController',
|
||||
'ManiphestTaskHasCommitEdgeType' => 'PhabricatorEdgeType',
|
||||
'ManiphestTaskHasMockEdgeType' => 'PhabricatorEdgeType',
|
||||
|
@ -5295,6 +5324,9 @@ phutil_register_library_map(array(
|
|||
'PhabricatorCustomFieldStringIndexStorage' => 'PhabricatorCustomFieldIndexStorage',
|
||||
'PhabricatorCustomHeaderConfigType' => 'PhabricatorConfigOptionType',
|
||||
'PhabricatorDaemon' => 'PhutilDaemon',
|
||||
'PhabricatorDaemonBulkJobListController' => 'PhabricatorDaemonController',
|
||||
'PhabricatorDaemonBulkJobMonitorController' => 'PhabricatorDaemonController',
|
||||
'PhabricatorDaemonBulkJobViewController' => 'PhabricatorDaemonController',
|
||||
'PhabricatorDaemonConsoleController' => 'PhabricatorDaemonController',
|
||||
'PhabricatorDaemonController' => 'PhabricatorController',
|
||||
'PhabricatorDaemonDAO' => 'PhabricatorLiskDAO',
|
||||
|
@ -5759,10 +5791,14 @@ phutil_register_library_map(array(
|
|||
),
|
||||
'PhabricatorMetaMTAMailBody' => 'Phobject',
|
||||
'PhabricatorMetaMTAMailBodyTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorMetaMTAMailHasRecipientEdgeType' => 'PhabricatorEdgeType',
|
||||
'PhabricatorMetaMTAMailListController' => 'PhabricatorMetaMTAController',
|
||||
'PhabricatorMetaMTAMailPHIDType' => 'PhabricatorPHIDType',
|
||||
'PhabricatorMetaMTAMailQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'PhabricatorMetaMTAMailSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||
'PhabricatorMetaMTAMailSection' => 'Phobject',
|
||||
'PhabricatorMetaMTAMailTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorMetaMTAMailViewController' => 'PhabricatorMetaMTAController',
|
||||
'PhabricatorMetaMTAMailableDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
||||
'PhabricatorMetaMTAMailableFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
||||
'PhabricatorMetaMTAMailgunReceiveController' => 'PhabricatorMetaMTAController',
|
||||
|
@ -6593,6 +6629,25 @@ phutil_register_library_map(array(
|
|||
'PhabricatorWorkerActiveTask' => 'PhabricatorWorkerTask',
|
||||
'PhabricatorWorkerArchiveTask' => 'PhabricatorWorkerTask',
|
||||
'PhabricatorWorkerArchiveTaskQuery' => 'PhabricatorQuery',
|
||||
'PhabricatorWorkerBulkJob' => array(
|
||||
'PhabricatorWorkerDAO',
|
||||
'PhabricatorPolicyInterface',
|
||||
'PhabricatorSubscribableInterface',
|
||||
'PhabricatorApplicationTransactionInterface',
|
||||
'PhabricatorDestructibleInterface',
|
||||
),
|
||||
'PhabricatorWorkerBulkJobCreateWorker' => 'PhabricatorWorkerBulkJobWorker',
|
||||
'PhabricatorWorkerBulkJobEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||
'PhabricatorWorkerBulkJobPHIDType' => 'PhabricatorPHIDType',
|
||||
'PhabricatorWorkerBulkJobQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'PhabricatorWorkerBulkJobSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||
'PhabricatorWorkerBulkJobTaskWorker' => 'PhabricatorWorkerBulkJobWorker',
|
||||
'PhabricatorWorkerBulkJobTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorWorkerBulkJobTransaction' => 'PhabricatorApplicationTransaction',
|
||||
'PhabricatorWorkerBulkJobTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||
'PhabricatorWorkerBulkJobType' => 'Phobject',
|
||||
'PhabricatorWorkerBulkJobWorker' => 'PhabricatorWorker',
|
||||
'PhabricatorWorkerBulkTask' => 'PhabricatorWorkerDAO',
|
||||
'PhabricatorWorkerDAO' => 'PhabricatorLiskDAO',
|
||||
'PhabricatorWorkerLeaseQuery' => 'PhabricatorQuery',
|
||||
'PhabricatorWorkerManagementCancelWorkflow' => 'PhabricatorWorkerManagementWorkflow',
|
||||
|
@ -6602,6 +6657,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorWorkerManagementRetryWorkflow' => 'PhabricatorWorkerManagementWorkflow',
|
||||
'PhabricatorWorkerManagementWorkflow' => 'PhabricatorManagementWorkflow',
|
||||
'PhabricatorWorkerPermanentFailureException' => 'Exception',
|
||||
'PhabricatorWorkerSchemaSpec' => 'PhabricatorConfigSchemaSpec',
|
||||
'PhabricatorWorkerTask' => 'PhabricatorWorkerDAO',
|
||||
'PhabricatorWorkerTaskData' => 'PhabricatorWorkerDAO',
|
||||
'PhabricatorWorkerTaskDetailController' => 'PhabricatorDaemonController',
|
||||
|
|
|
@ -46,6 +46,15 @@ final class PhabricatorDaemonsApplication extends PhabricatorApplication {
|
|||
'(?P<id>[1-9]\d*)/' => 'PhabricatorDaemonLogViewController',
|
||||
),
|
||||
'event/(?P<id>[1-9]\d*)/' => 'PhabricatorDaemonLogEventViewController',
|
||||
'bulk/' => array(
|
||||
'(?:query/(?P<queryKey>[^/]+)/)?' =>
|
||||
'PhabricatorDaemonBulkJobListController',
|
||||
'monitor/(?P<id>\d+)/' =>
|
||||
'PhabricatorDaemonBulkJobMonitorController',
|
||||
'view/(?P<id>\d+)/' =>
|
||||
'PhabricatorDaemonBulkJobViewController',
|
||||
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorDaemonBulkJobListController
|
||||
extends PhabricatorDaemonController {
|
||||
|
||||
public function shouldAllowPublic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$controller = id(new PhabricatorApplicationSearchController())
|
||||
->setQueryKey($request->getURIData('queryKey'))
|
||||
->setSearchEngine(new PhabricatorWorkerBulkJobSearchEngine())
|
||||
->setNavigation($this->buildSideNavView());
|
||||
return $this->delegateToController($controller);
|
||||
}
|
||||
|
||||
protected function buildSideNavView($for_app = false) {
|
||||
$user = $this->getRequest()->getUser();
|
||||
|
||||
$nav = new AphrontSideNavFilterView();
|
||||
$nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
|
||||
|
||||
id(new PhabricatorWorkerBulkJobSearchEngine())
|
||||
->setViewer($user)
|
||||
->addNavigationItems($nav->getMenu());
|
||||
$nav->selectFilter(null);
|
||||
|
||||
return $nav;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorDaemonBulkJobMonitorController
|
||||
extends PhabricatorDaemonController {
|
||||
|
||||
public function shouldAllowPublic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$job = id(new PhabricatorWorkerBulkJobQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($request->getURIData('id')))
|
||||
->executeOne();
|
||||
if (!$job) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
// If the user clicks "Continue" on a completed job, take them back to
|
||||
// whatever application sent them here.
|
||||
if ($request->getStr('done')) {
|
||||
if ($request->isFormPost()) {
|
||||
$done_uri = $job->getDoneURI();
|
||||
return id(new AphrontRedirectResponse())->setURI($done_uri);
|
||||
}
|
||||
}
|
||||
|
||||
$title = pht('Bulk Job %d', $job->getID());
|
||||
|
||||
if ($job->getStatus() == PhabricatorWorkerBulkJob::STATUS_CONFIRM) {
|
||||
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||
$viewer,
|
||||
$job,
|
||||
PhabricatorPolicyCapability::CAN_EDIT);
|
||||
|
||||
if ($can_edit) {
|
||||
if ($request->isFormPost()) {
|
||||
$type_status = PhabricatorWorkerBulkJobTransaction::TYPE_STATUS;
|
||||
|
||||
$xactions = array();
|
||||
$xactions[] = id(new PhabricatorWorkerBulkJobTransaction())
|
||||
->setTransactionType($type_status)
|
||||
->setNewValue(PhabricatorWorkerBulkJob::STATUS_WAITING);
|
||||
|
||||
$editor = id(new PhabricatorWorkerBulkJobEditor())
|
||||
->setActor($viewer)
|
||||
->setContentSourceFromRequest($request)
|
||||
->setContinueOnMissingFields(true)
|
||||
->applyTransactions($job, $xactions);
|
||||
|
||||
return id(new AphrontRedirectResponse())
|
||||
->setURI($job->getMonitorURI());
|
||||
} else {
|
||||
return $this->newDialog()
|
||||
->setTitle(pht('Confirm Bulk Job'))
|
||||
->appendParagraph($job->getDescriptionForConfirm())
|
||||
->appendParagraph(
|
||||
pht('Start work on this bulk job?'))
|
||||
->addCancelButton($job->getManageURI(), pht('Details'))
|
||||
->addSubmitButton(pht('Start Work'));
|
||||
}
|
||||
} else {
|
||||
return $this->newDialog()
|
||||
->setTitle(pht('Waiting For Confirmation'))
|
||||
->appendParagraph(
|
||||
pht(
|
||||
'This job is waiting for confirmation before work begins.'))
|
||||
->addCancelButotn($job->getManageURI(), pht('Details'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$dialog = $this->newDialog()
|
||||
->setTitle(pht('%s: %s', $title, $job->getStatusName()))
|
||||
->addCancelButton($job->getManageURI(), pht('Details'));
|
||||
|
||||
switch ($job->getStatus()) {
|
||||
case PhabricatorWorkerBulkJob::STATUS_WAITING:
|
||||
$dialog->appendParagraph(
|
||||
pht('This job is waiting for tasks to be queued.'));
|
||||
break;
|
||||
case PhabricatorWorkerBulkJob::STATUS_RUNNING:
|
||||
$dialog->appendParagraph(
|
||||
pht('This job is running.'));
|
||||
break;
|
||||
case PhabricatorWorkerBulkJob::STATUS_COMPLETE:
|
||||
$dialog->appendParagraph(
|
||||
pht('This job is complete.'));
|
||||
break;
|
||||
}
|
||||
|
||||
$counts = $job->loadTaskStatusCounts();
|
||||
if ($counts) {
|
||||
$dialog->appendParagraph($this->renderProgress($counts));
|
||||
}
|
||||
|
||||
switch ($job->getStatus()) {
|
||||
case PhabricatorWorkerBulkJob::STATUS_COMPLETE:
|
||||
$dialog->addHiddenInput('done', true);
|
||||
$dialog->addSubmitButton(pht('Continue'));
|
||||
break;
|
||||
default:
|
||||
Javelin::initBehavior('bulk-job-reload');
|
||||
break;
|
||||
}
|
||||
|
||||
return $dialog;
|
||||
}
|
||||
|
||||
private function renderProgress(array $counts) {
|
||||
$this->requireResource('bulk-job-css');
|
||||
|
||||
$states = array(
|
||||
PhabricatorWorkerBulkTask::STATUS_DONE => array(
|
||||
'class' => 'bulk-job-progress-slice-green',
|
||||
),
|
||||
PhabricatorWorkerBulkTask::STATUS_RUNNING => array(
|
||||
'class' => 'bulk-job-progress-slice-blue',
|
||||
),
|
||||
PhabricatorWorkerBulkTask::STATUS_WAITING => array(
|
||||
'class' => 'bulk-job-progress-slice-empty',
|
||||
),
|
||||
PhabricatorWorkerBulkTask::STATUS_FAIL => array(
|
||||
'class' => 'bulk-job-progress-slice-red',
|
||||
),
|
||||
);
|
||||
|
||||
$total = array_sum($counts);
|
||||
$offset = 0;
|
||||
$bars = array();
|
||||
foreach ($states as $state => $spec) {
|
||||
$size = idx($counts, $state, 0);
|
||||
if (!$size) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$classes = array();
|
||||
$classes[] = 'bulk-job-progress-slice';
|
||||
$classes[] = $spec['class'];
|
||||
|
||||
$width = ($size / $total);
|
||||
$bars[] = phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => implode(' ', $classes),
|
||||
'style' =>
|
||||
'left: '.sprintf('%.2f%%', 100 * $offset).'; '.
|
||||
'width: '.sprintf('%.2f%%', 100 * $width).';',
|
||||
),
|
||||
'');
|
||||
|
||||
$offset += $width;
|
||||
}
|
||||
|
||||
return phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'bulk-job-progress-bar',
|
||||
),
|
||||
$bars);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorDaemonBulkJobViewController
|
||||
extends PhabricatorDaemonController {
|
||||
|
||||
public function shouldAllowPublic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$job = id(new PhabricatorWorkerBulkJobQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($request->getURIData('id')))
|
||||
->executeOne();
|
||||
if (!$job) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$title = pht('Bulk Job %d', $job->getID());
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->addTextCrumb(pht('Bulk Jobs'), '/daemon/bulk/');
|
||||
$crumbs->addTextCrumb($title);
|
||||
|
||||
$properties = $this->renderProperties($job);
|
||||
$actions = $this->renderActions($job);
|
||||
$properties->setActionList($actions);
|
||||
|
||||
$box = id(new PHUIObjectBoxView())
|
||||
->setHeaderText($title)
|
||||
->addPropertyList($properties);
|
||||
|
||||
$timeline = $this->buildTransactionTimeline(
|
||||
$job,
|
||||
new PhabricatorWorkerBulkJobTransactionQuery());
|
||||
$timeline->setShouldTerminate(true);
|
||||
|
||||
return $this->buildApplicationPage(
|
||||
array(
|
||||
$crumbs,
|
||||
$box,
|
||||
$timeline,
|
||||
),
|
||||
array(
|
||||
'title' => $title,
|
||||
));
|
||||
}
|
||||
|
||||
private function renderProperties(PhabricatorWorkerBulkJob $job) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$view = id(new PHUIPropertyListView())
|
||||
->setUser($viewer)
|
||||
->setObject($job);
|
||||
|
||||
$view->addProperty(
|
||||
pht('Author'),
|
||||
$viewer->renderHandle($job->getAuthorPHID()));
|
||||
|
||||
$view->addProperty(pht('Status'), $job->getStatusName());
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
private function renderActions(PhabricatorWorkerBulkJob $job) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$actions = id(new PhabricatorActionListView())
|
||||
->setUser($viewer)
|
||||
->setObject($job);
|
||||
|
||||
$actions->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setHref($job->getDoneURI())
|
||||
->setIcon('fa-arrow-circle-o-right')
|
||||
->setName(pht('Continue')));
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
}
|
|
@ -10,6 +10,9 @@ abstract class PhabricatorDaemonController extends PhabricatorController {
|
|||
$nav->addFilter('/', pht('Console'));
|
||||
$nav->addFilter('log', pht('All Daemons'));
|
||||
|
||||
$nav->addLabel(pht('Bulk Jobs'));
|
||||
$nav->addFilter('bulk', pht('Manage Bulk Jobs'));
|
||||
|
||||
return $nav;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
|
||||
abstract class DifferentialHarbormasterField
|
||||
extends DifferentialCustomField {
|
||||
|
||||
abstract protected function getDiffPropertyKeys();
|
||||
abstract protected function loadHarbormasterTargetMessages(
|
||||
array $target_phids);
|
||||
abstract protected function getLegacyProperty();
|
||||
abstract protected function newModernMessage(array $message);
|
||||
abstract protected function renderHarbormasterStatus(
|
||||
DifferentialDiff $diff,
|
||||
array $messages);
|
||||
abstract protected function newHarbormasterMessageView(array $messages);
|
||||
|
||||
public function renderDiffPropertyViewValue(DifferentialDiff $diff) {
|
||||
// TODO: This load is slightly inefficient, but most of this is moving
|
||||
// to Harbormaster and this simplifies the transition. Eat 1-2 extra
|
||||
// queries for now.
|
||||
$keys = $this->getDiffPropertyKeys();
|
||||
|
||||
$properties = id(new DifferentialDiffProperty())->loadAllWhere(
|
||||
'diffID = %d AND name IN (%Ls)',
|
||||
$diff->getID(),
|
||||
$keys);
|
||||
$properties = mpull($properties, 'getData', 'getName');
|
||||
|
||||
foreach ($keys as $key) {
|
||||
$diff->attachProperty($key, idx($properties, $key));
|
||||
}
|
||||
|
||||
$messages = array();
|
||||
|
||||
$buildable = $diff->getBuildable();
|
||||
if ($buildable) {
|
||||
$target_phids = array();
|
||||
foreach ($buildable->getBuilds() as $build) {
|
||||
foreach ($build->getBuildTargets() as $target) {
|
||||
$target_phids[] = $target->getPHID();
|
||||
}
|
||||
}
|
||||
|
||||
if ($target_phids) {
|
||||
$messages = $this->loadHarbormasterTargetMessages($target_phids);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$messages) {
|
||||
// No Harbormaster messages, so look for legacy messages and make them
|
||||
// look like modern messages.
|
||||
$legacy_messages = $diff->getProperty($this->getLegacyProperty());
|
||||
if ($legacy_messages) {
|
||||
// Show the top 100 legacy lint messages. Previously, we showed some
|
||||
// by default and let the user toggle the rest. With modern messages,
|
||||
// we can send the user to the Harbormaster detail page. Just show
|
||||
// "a lot" of messages in legacy cases to try to strike a balance
|
||||
// between implementation simplicitly and compatibility.
|
||||
$legacy_messages = array_slice($legacy_messages, 0, 100);
|
||||
|
||||
foreach ($legacy_messages as $message) {
|
||||
try {
|
||||
$modern = $this->newModernMessage($message);
|
||||
$messages[] = $modern;
|
||||
} catch (Exception $ex) {
|
||||
// Ignore any poorly formatted messages.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$status = $this->renderHarbormasterStatus($diff, $messages);
|
||||
|
||||
if ($messages) {
|
||||
$path_map = mpull($diff->loadChangesets(), 'getID', 'getFilename');
|
||||
foreach ($path_map as $path => $id) {
|
||||
$href = '#C'.$id.'NL';
|
||||
|
||||
// TODO: When the diff is not the right-hand-size diff, we should
|
||||
// ideally adjust this URI to be absolute.
|
||||
|
||||
$path_map[$path] = $href;
|
||||
}
|
||||
|
||||
$view = $this->newHarbormasterMessageView($messages);
|
||||
if ($view) {
|
||||
$view->setPathURIMap($path_map);
|
||||
}
|
||||
} else {
|
||||
$view = null;
|
||||
}
|
||||
|
||||
if ($view) {
|
||||
$view = phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'differential-harbormaster-table-view',
|
||||
),
|
||||
$view);
|
||||
}
|
||||
|
||||
return array(
|
||||
$status,
|
||||
$view,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
|
||||
final class DifferentialLintField
|
||||
extends DifferentialCustomField {
|
||||
extends DifferentialHarbormasterField {
|
||||
|
||||
public function getFieldKey() {
|
||||
return 'differential:lint';
|
||||
|
@ -31,91 +31,33 @@ final class DifferentialLintField
|
|||
return $this->getFieldName();
|
||||
}
|
||||
|
||||
public function renderDiffPropertyViewValue(DifferentialDiff $diff) {
|
||||
// TODO: This load is slightly inefficient, but most of this is moving
|
||||
// to Harbormaster and this simplifies the transition. Eat 1-2 extra
|
||||
// queries for now.
|
||||
$keys = array(
|
||||
protected function getLegacyProperty() {
|
||||
return 'arc:lint';
|
||||
}
|
||||
|
||||
protected function getDiffPropertyKeys() {
|
||||
return array(
|
||||
'arc:lint',
|
||||
'arc:lint-excuse',
|
||||
);
|
||||
}
|
||||
|
||||
$properties = id(new DifferentialDiffProperty())->loadAllWhere(
|
||||
'diffID = %d AND name IN (%Ls)',
|
||||
$diff->getID(),
|
||||
$keys);
|
||||
$properties = mpull($properties, 'getData', 'getName');
|
||||
protected function loadHarbormasterTargetMessages(array $target_phids) {
|
||||
return id(new HarbormasterBuildLintMessage())->loadAllWhere(
|
||||
'buildTargetPHID IN (%Ls) LIMIT 25',
|
||||
$target_phids);
|
||||
}
|
||||
|
||||
foreach ($keys as $key) {
|
||||
$diff->attachProperty($key, idx($properties, $key));
|
||||
}
|
||||
protected function newHarbormasterMessageView(array $messages) {
|
||||
return id(new HarbormasterLintPropertyView())
|
||||
->setLimit(25)
|
||||
->setLintMessages($messages);
|
||||
}
|
||||
|
||||
$status = $this->renderLintStatus($diff);
|
||||
|
||||
$lint = array();
|
||||
|
||||
$buildable = $diff->getBuildable();
|
||||
if ($buildable) {
|
||||
$target_phids = array();
|
||||
foreach ($buildable->getBuilds() as $build) {
|
||||
foreach ($build->getBuildTargets() as $target) {
|
||||
$target_phids[] = $target->getPHID();
|
||||
}
|
||||
}
|
||||
|
||||
$lint = id(new HarbormasterBuildLintMessage())->loadAllWhere(
|
||||
'buildTargetPHID IN (%Ls) LIMIT 25',
|
||||
$target_phids);
|
||||
}
|
||||
|
||||
if (!$lint) {
|
||||
// No Harbormaster messages, so look for legacy messages and make them
|
||||
// look like modern messages.
|
||||
$legacy_lint = $diff->getProperty('arc:lint');
|
||||
if ($legacy_lint) {
|
||||
// Show the top 100 legacy lint messages. Previously, we showed some
|
||||
// by default and let the user toggle the rest. With modern messages,
|
||||
// we can send the user to the Harbormaster detail page. Just show
|
||||
// "a lot" of messages in legacy cases to try to strike a balance
|
||||
// between implementation simplicitly and compatibility.
|
||||
$legacy_lint = array_slice($legacy_lint, 0, 100);
|
||||
|
||||
$target = new HarbormasterBuildTarget();
|
||||
foreach ($legacy_lint as $message) {
|
||||
try {
|
||||
$modern = HarbormasterBuildLintMessage::newFromDictionary(
|
||||
$target,
|
||||
$this->getModernLintMessageDictionary($message));
|
||||
$lint[] = $modern;
|
||||
} catch (Exception $ex) {
|
||||
// Ignore any poorly formatted messages.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($lint) {
|
||||
$path_map = mpull($diff->loadChangesets(), 'getID', 'getFilename');
|
||||
foreach ($path_map as $path => $id) {
|
||||
$href = '#C'.$id.'NL';
|
||||
|
||||
// TODO: When the diff is not the right-hand-size diff, we should
|
||||
// ideally adjust this URI to be absolute.
|
||||
|
||||
$path_map[$path] = $href;
|
||||
}
|
||||
|
||||
$view = id(new HarbormasterLintPropertyView())
|
||||
->setPathURIMap($path_map)
|
||||
->setLintMessages($lint);
|
||||
} else {
|
||||
$view = null;
|
||||
}
|
||||
|
||||
return array(
|
||||
$status,
|
||||
$view,
|
||||
);
|
||||
protected function newModernMessage(array $message) {
|
||||
return HarbormasterBuildLintMessage::newFromDictionary(
|
||||
new HarbormasterBuildTarget(),
|
||||
$this->getModernLintMessageDictionary($message));
|
||||
}
|
||||
|
||||
public function getWarningsForDetailView() {
|
||||
|
@ -141,7 +83,10 @@ final class DifferentialLintField
|
|||
return $warnings;
|
||||
}
|
||||
|
||||
private function renderLintStatus(DifferentialDiff $diff) {
|
||||
protected function renderHarbormasterStatus(
|
||||
DifferentialDiff $diff,
|
||||
array $messages) {
|
||||
|
||||
$colors = array(
|
||||
DifferentialLintStatus::LINT_NONE => 'grey',
|
||||
DifferentialLintStatus::LINT_OKAY => 'green',
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
|
||||
final class DifferentialUnitField
|
||||
extends DifferentialCustomField {
|
||||
extends DifferentialHarbormasterField {
|
||||
|
||||
public function getFieldKey() {
|
||||
return 'differential:unit';
|
||||
|
@ -31,84 +31,44 @@ final class DifferentialUnitField
|
|||
return $this->getFieldName();
|
||||
}
|
||||
|
||||
public function renderDiffPropertyViewValue(DifferentialDiff $diff) {
|
||||
// TODO: See DifferentialLintField.
|
||||
$keys = array(
|
||||
protected function getLegacyProperty() {
|
||||
return 'arc:unit';
|
||||
}
|
||||
|
||||
protected function getDiffPropertyKeys() {
|
||||
return array(
|
||||
'arc:unit',
|
||||
'arc:unit-excuse',
|
||||
);
|
||||
}
|
||||
|
||||
$properties = id(new DifferentialDiffProperty())->loadAllWhere(
|
||||
'diffID = %d AND name IN (%Ls)',
|
||||
$diff->getID(),
|
||||
$keys);
|
||||
$properties = mpull($properties, 'getData', 'getName');
|
||||
protected function loadHarbormasterTargetMessages(array $target_phids) {
|
||||
return id(new HarbormasterBuildUnitMessage())->loadAllWhere(
|
||||
'buildTargetPHID IN (%Ls)',
|
||||
$target_phids);
|
||||
}
|
||||
|
||||
foreach ($keys as $key) {
|
||||
$diff->attachProperty($key, idx($properties, $key));
|
||||
}
|
||||
protected function newModernMessage(array $message) {
|
||||
return HarbormasterBuildUnitMessage::newFromDictionary(
|
||||
new HarbormasterBuildTarget(),
|
||||
$this->getModernUnitMessageDictionary($message));
|
||||
}
|
||||
|
||||
$status = $this->renderUnitStatus($diff);
|
||||
|
||||
$unit = array();
|
||||
|
||||
$buildable = $diff->getBuildable();
|
||||
if ($buildable) {
|
||||
$target_phids = array();
|
||||
foreach ($buildable->getBuilds() as $build) {
|
||||
foreach ($build->getBuildTargets() as $target) {
|
||||
$target_phids[] = $target->getPHID();
|
||||
}
|
||||
}
|
||||
|
||||
$unit = id(new HarbormasterBuildUnitMessage())->loadAllWhere(
|
||||
'buildTargetPHID IN (%Ls) LIMIT 25',
|
||||
$target_phids);
|
||||
}
|
||||
|
||||
if (!$unit) {
|
||||
$legacy_unit = $diff->getProperty('arc:unit');
|
||||
if ($legacy_unit) {
|
||||
// Show the top 100 legacy unit messages.
|
||||
$legacy_unit = array_slice($legacy_unit, 0, 100);
|
||||
|
||||
$target = new HarbormasterBuildTarget();
|
||||
foreach ($legacy_unit as $message) {
|
||||
try {
|
||||
$modern = HarbormasterBuildUnitMessage::newFromDictionary(
|
||||
$target,
|
||||
$this->getModernUnitMessageDictionary($message));
|
||||
$unit[] = $modern;
|
||||
} catch (Exception $ex) {
|
||||
// Just ignore it if legacy messages aren't formatted like
|
||||
// we expect.
|
||||
}
|
||||
}
|
||||
protected function newHarbormasterMessageView(array $messages) {
|
||||
foreach ($messages as $key => $message) {
|
||||
if ($message->getResult() == ArcanistUnitTestResult::RESULT_PASS) {
|
||||
unset($messages[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($unit) {
|
||||
$path_map = mpull($diff->loadChangesets(), 'getID', 'getFilename');
|
||||
foreach ($path_map as $path => $id) {
|
||||
$href = '#C'.$id.'NL';
|
||||
|
||||
// TODO: When the diff is not the right-hand-size diff, we should
|
||||
// ideally adjust this URI to be absolute.
|
||||
|
||||
$path_map[$path] = $href;
|
||||
}
|
||||
|
||||
$view = id(new HarbormasterUnitPropertyView())
|
||||
->setPathURIMap($path_map)
|
||||
->setUnitMessages($unit);
|
||||
} else {
|
||||
$view = null;
|
||||
if (!$messages) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return array(
|
||||
$status,
|
||||
$view,
|
||||
);
|
||||
return id(new HarbormasterUnitPropertyView())
|
||||
->setLimit(10)
|
||||
->setHidePassingTests(true)
|
||||
->setUnitMessages($messages);
|
||||
}
|
||||
|
||||
public function getWarningsForDetailView() {
|
||||
|
@ -132,8 +92,10 @@ final class DifferentialUnitField
|
|||
return $warnings;
|
||||
}
|
||||
|
||||
protected function renderHarbormasterStatus(
|
||||
DifferentialDiff $diff,
|
||||
array $messages) {
|
||||
|
||||
private function renderUnitStatus(DifferentialDiff $diff) {
|
||||
$colors = array(
|
||||
DifferentialUnitStatus::UNIT_NONE => 'grey',
|
||||
DifferentialUnitStatus::UNIT_OKAY => 'green',
|
||||
|
@ -147,6 +109,55 @@ final class DifferentialUnitField
|
|||
|
||||
$message = DifferentialRevisionUpdateHistoryView::getDiffUnitMessage($diff);
|
||||
|
||||
$note = array();
|
||||
|
||||
$groups = mgroup($messages, 'getResult');
|
||||
|
||||
$groups = array_select_keys(
|
||||
$groups,
|
||||
array(
|
||||
ArcanistUnitTestResult::RESULT_FAIL,
|
||||
ArcanistUnitTestResult::RESULT_BROKEN,
|
||||
ArcanistUnitTestResult::RESULT_UNSOUND,
|
||||
ArcanistUnitTestResult::RESULT_SKIP,
|
||||
ArcanistUnitTestResult::RESULT_PASS,
|
||||
)) + $groups;
|
||||
|
||||
foreach ($groups as $result => $group) {
|
||||
$count = new PhutilNumber(count($group));
|
||||
switch ($result) {
|
||||
case ArcanistUnitTestResult::RESULT_PASS:
|
||||
$note[] = pht('%s Passed Test(s)', $count);
|
||||
break;
|
||||
case ArcanistUnitTestResult::RESULT_FAIL:
|
||||
$note[] = pht('%s Failed Test(s)', $count);
|
||||
break;
|
||||
case ArcanistUnitTestResult::RESULT_SKIP:
|
||||
$note[] = pht('%s Skipped Test(s)', $count);
|
||||
break;
|
||||
case ArcanistUnitTestResult::RESULT_BROKEN:
|
||||
$note[] = pht('%s Broken Test(s)', $count);
|
||||
break;
|
||||
case ArcanistUnitTestResult::RESULT_UNSOUND:
|
||||
$note[] = pht('%s Unsound Test(s)', $count);
|
||||
break;
|
||||
default:
|
||||
$note[] = pht('%s Other Test(s)', $count);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$buildable = $diff->getBuildable();
|
||||
if ($buildable) {
|
||||
$full_results = '/harbormaster/unit/'.$buildable->getID().'/';
|
||||
$note[] = phutil_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => $full_results,
|
||||
),
|
||||
pht('View Full Results'));
|
||||
}
|
||||
|
||||
$excuse = $diff->getProperty('arc:unit-excuse');
|
||||
if (strlen($excuse)) {
|
||||
$excuse = array(
|
||||
|
@ -154,14 +165,17 @@ final class DifferentialUnitField
|
|||
' ',
|
||||
phutil_escape_html_newlines($excuse),
|
||||
);
|
||||
$note[] = $excuse;
|
||||
}
|
||||
|
||||
$note = phutil_implode_html(" \xC2\xB7 ", $note);
|
||||
|
||||
$status = id(new PHUIStatusListView())
|
||||
->addItem(
|
||||
id(new PHUIStatusItemView())
|
||||
->setIcon(PHUIStatusItemView::ICON_STAR, $icon_color)
|
||||
->setTarget($message)
|
||||
->setNote($excuse));
|
||||
->setNote($note));
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
|
|
@ -77,6 +77,12 @@ final class PhabricatorHarbormasterApplication extends PhabricatorApplication {
|
|||
'run/(?P<id>\d+)/' => 'HarbormasterPlanRunController',
|
||||
'(?P<id>\d+)/' => 'HarbormasterPlanViewController',
|
||||
),
|
||||
'unit/' => array(
|
||||
'(?P<id>\d+)/' => 'HarbormasterUnitMessagesController',
|
||||
),
|
||||
'lint/' => array(
|
||||
'(?P<id>\d+)/' => 'HarbormasterLintMessagesController',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ final class HarbormasterBuildableViewController
|
|||
->needBuildTargets(true)
|
||||
->execute();
|
||||
|
||||
list($lint, $unit) = $this->renderLintAndUnit($builds);
|
||||
list($lint, $unit) = $this->renderLintAndUnit($buildable, $builds);
|
||||
|
||||
$buildable->attachBuilds($builds);
|
||||
$object = $buildable->getBuildableObject();
|
||||
|
@ -257,7 +257,10 @@ final class HarbormasterBuildableViewController
|
|||
return $box;
|
||||
}
|
||||
|
||||
private function renderLintAndUnit(array $builds) {
|
||||
private function renderLintAndUnit(
|
||||
HarbormasterBuildable $buildable,
|
||||
array $builds) {
|
||||
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$targets = array();
|
||||
|
@ -274,20 +277,32 @@ final class HarbormasterBuildableViewController
|
|||
$target_phids = mpull($targets, 'getPHID');
|
||||
|
||||
$lint_data = id(new HarbormasterBuildLintMessage())->loadAllWhere(
|
||||
'buildTargetPHID IN (%Ls) LIMIT 25',
|
||||
'buildTargetPHID IN (%Ls)',
|
||||
$target_phids);
|
||||
|
||||
$unit_data = id(new HarbormasterBuildUnitMessage())->loadAllWhere(
|
||||
'buildTargetPHID IN (%Ls) LIMIT 25',
|
||||
'buildTargetPHID IN (%Ls)',
|
||||
$target_phids);
|
||||
|
||||
if ($lint_data) {
|
||||
$lint_table = id(new HarbormasterLintPropertyView())
|
||||
->setUser($viewer)
|
||||
->setLimit(10)
|
||||
->setLintMessages($lint_data);
|
||||
|
||||
$lint_href = $this->getApplicationURI('lint/'.$buildable->getID().'/');
|
||||
|
||||
$lint_header = id(new PHUIHeaderView())
|
||||
->setHeader(pht('Lint Messages'))
|
||||
->addActionLink(
|
||||
id(new PHUIButtonView())
|
||||
->setTag('a')
|
||||
->setHref($lint_href)
|
||||
->setIconFont('fa-list-ul')
|
||||
->setText('View All'));
|
||||
|
||||
$lint = id(new PHUIObjectBoxView())
|
||||
->setHeaderText(pht('Lint Messages'))
|
||||
->setHeader($lint_header)
|
||||
->appendChild($lint_table);
|
||||
} else {
|
||||
$lint = null;
|
||||
|
@ -296,10 +311,22 @@ final class HarbormasterBuildableViewController
|
|||
if ($unit_data) {
|
||||
$unit_table = id(new HarbormasterUnitPropertyView())
|
||||
->setUser($viewer)
|
||||
->setLimit(25)
|
||||
->setUnitMessages($unit_data);
|
||||
|
||||
$unit_href = $this->getApplicationURI('unit/'.$buildable->getID().'/');
|
||||
|
||||
$unit_header = id(new PHUIHeaderView())
|
||||
->setHeader(pht('Unit Tests'))
|
||||
->addActionLink(
|
||||
id(new PHUIButtonView())
|
||||
->setTag('a')
|
||||
->setHref($unit_href)
|
||||
->setIconFont('fa-list-ul')
|
||||
->setText('View All'));
|
||||
|
||||
$unit = id(new PHUIObjectBoxView())
|
||||
->setHeaderText(pht('Unit Tests'))
|
||||
->setHeader($unit_header)
|
||||
->appendChild($unit_table);
|
||||
} else {
|
||||
$unit = null;
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
final class HarbormasterLintMessagesController
|
||||
extends HarbormasterController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$buildable = id(new HarbormasterBuildableQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($request->getURIData('id')))
|
||||
->needBuilds(true)
|
||||
->needTargets(true)
|
||||
->executeOne();
|
||||
if (!$buildable) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$id = $buildable->getID();
|
||||
|
||||
$target_phids = array();
|
||||
foreach ($buildable->getBuilds() as $build) {
|
||||
foreach ($build->getBuildTargets() as $target) {
|
||||
$target_phids[] = $target->getPHID();
|
||||
}
|
||||
}
|
||||
|
||||
$lint_data = array();
|
||||
if ($target_phids) {
|
||||
$lint_data = id(new HarbormasterBuildLintMessage())->loadAllWhere(
|
||||
'buildTargetPHID IN (%Ls)',
|
||||
$target_phids);
|
||||
} else {
|
||||
$lint_data = array();
|
||||
}
|
||||
|
||||
$lint_table = id(new HarbormasterLintPropertyView())
|
||||
->setUser($viewer)
|
||||
->setLintMessages($lint_data);
|
||||
|
||||
$lint = id(new PHUIObjectBoxView())
|
||||
->setHeaderText(pht('Lint Messages'))
|
||||
->appendChild($lint_table);
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$this->addBuildableCrumb($crumbs, $buildable);
|
||||
$crumbs->addTextCrumb(pht('Lint'));
|
||||
|
||||
$title = array(
|
||||
$buildable->getMonogram(),
|
||||
pht('Lint'),
|
||||
);
|
||||
|
||||
return $this->buildApplicationPage(
|
||||
array(
|
||||
$crumbs,
|
||||
$lint,
|
||||
),
|
||||
array(
|
||||
'title' => $title,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
final class HarbormasterUnitMessagesController
|
||||
extends HarbormasterController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$buildable = id(new HarbormasterBuildableQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($request->getURIData('id')))
|
||||
->needBuilds(true)
|
||||
->needTargets(true)
|
||||
->executeOne();
|
||||
if (!$buildable) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$id = $buildable->getID();
|
||||
|
||||
$target_phids = array();
|
||||
foreach ($buildable->getBuilds() as $build) {
|
||||
foreach ($build->getBuildTargets() as $target) {
|
||||
$target_phids[] = $target->getPHID();
|
||||
}
|
||||
}
|
||||
|
||||
$unit_data = array();
|
||||
if ($target_phids) {
|
||||
$unit_data = id(new HarbormasterBuildUnitMessage())->loadAllWhere(
|
||||
'buildTargetPHID IN (%Ls)',
|
||||
$target_phids);
|
||||
} else {
|
||||
$unit_data = array();
|
||||
}
|
||||
|
||||
$unit_table = id(new HarbormasterUnitPropertyView())
|
||||
->setUser($viewer)
|
||||
->setUnitMessages($unit_data);
|
||||
|
||||
$unit = id(new PHUIObjectBoxView())
|
||||
->setHeaderText(pht('Unit Tests'))
|
||||
->appendChild($unit_table);
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$this->addBuildableCrumb($crumbs, $buildable);
|
||||
$crumbs->addTextCrumb(pht('Unit Tests'));
|
||||
|
||||
$title = array(
|
||||
$buildable->getMonogram(),
|
||||
pht('Unit Tests'),
|
||||
);
|
||||
|
||||
return $this->buildApplicationPage(
|
||||
array(
|
||||
$crumbs,
|
||||
$unit,
|
||||
),
|
||||
array(
|
||||
'title' => $title,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
|
@ -51,22 +51,17 @@ final class HarbormasterUIEventListener
|
|||
return;
|
||||
}
|
||||
|
||||
$buildables = id(new HarbormasterBuildableQuery())
|
||||
$buildable = id(new HarbormasterBuildableQuery())
|
||||
->setViewer($user)
|
||||
->withManualBuildables(false)
|
||||
->withBuildablePHIDs(array($buildable_phid))
|
||||
->execute();
|
||||
if (!$buildables) {
|
||||
->needBuilds(true)
|
||||
->executeOne();
|
||||
if (!$buildable) {
|
||||
return;
|
||||
}
|
||||
|
||||
$builds = id(new HarbormasterBuildQuery())
|
||||
->setViewer($user)
|
||||
->withBuildablePHIDs(mpull($buildables, 'getPHID'))
|
||||
->execute();
|
||||
if (!$builds) {
|
||||
return;
|
||||
}
|
||||
$builds = $buildable->getBuilds();
|
||||
|
||||
$build_handles = id(new PhabricatorHandleQuery())
|
||||
->setViewer($user)
|
||||
|
@ -75,6 +70,29 @@ final class HarbormasterUIEventListener
|
|||
|
||||
$status_view = new PHUIStatusListView();
|
||||
|
||||
$buildable_status = $buildable->getBuildableStatus();
|
||||
$buildable_icon = HarbormasterBuildable::getBuildableStatusIcon(
|
||||
$buildable_status);
|
||||
$buildable_color = HarbormasterBuildable::getBuildableStatusColor(
|
||||
$buildable_status);
|
||||
$buildable_name = HarbormasterBuildable::getBuildableStatusName(
|
||||
$buildable_status);
|
||||
|
||||
$target = phutil_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => '/'.$buildable->getMonogram(),
|
||||
),
|
||||
pht('Buildable %d', $buildable->getID()));
|
||||
|
||||
$target = phutil_tag('strong', array(), $target);
|
||||
|
||||
$status_view
|
||||
->addItem(
|
||||
id(new PHUIStatusItemView())
|
||||
->setIcon($buildable_icon, $buildable_color, $buildable_name)
|
||||
->setTarget($target));
|
||||
|
||||
foreach ($builds as $build) {
|
||||
$item = new PHUIStatusItemView();
|
||||
$item->setTarget($build_handles[$build->getPHID()]->renderLink());
|
||||
|
|
|
@ -95,4 +95,26 @@ final class HarbormasterBuildLintMessage
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function getSortKey() {
|
||||
// TODO: Maybe use more numeric values after T6861.
|
||||
$map = array(
|
||||
ArcanistLintSeverity::SEVERITY_ERROR => 'A',
|
||||
ArcanistLintSeverity::SEVERITY_WARNING => 'B',
|
||||
ArcanistLintSeverity::SEVERITY_AUTOFIX => 'C',
|
||||
ArcanistLintSeverity::SEVERITY_ADVICE => 'Y',
|
||||
ArcanistLintSeverity::SEVERITY_DISABLED => 'Z',
|
||||
);
|
||||
|
||||
$severity = idx($map, $this->getSeverity(), 'N');
|
||||
|
||||
$parts = array(
|
||||
$severity,
|
||||
$this->getPath(),
|
||||
sprintf('%08d', $this->getLine()),
|
||||
$this->getCode(),
|
||||
);
|
||||
|
||||
return implode("\0", $parts);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -97,4 +97,26 @@ final class HarbormasterBuildUnitMessage
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function getSortKey() {
|
||||
// TODO: Maybe use more numeric values after T6861.
|
||||
$map = array(
|
||||
ArcanistUnitTestResult::RESULT_FAIL => 'A',
|
||||
ArcanistUnitTestResult::RESULT_BROKEN => 'B',
|
||||
ArcanistUnitTestResult::RESULT_UNSOUND => 'C',
|
||||
ArcanistUnitTestResult::RESULT_PASS => 'Z',
|
||||
);
|
||||
|
||||
$result = idx($map, $this->getResult(), 'N');
|
||||
|
||||
$parts = array(
|
||||
$result,
|
||||
$this->getEngine(),
|
||||
$this->getNamespace(),
|
||||
$this->getName(),
|
||||
$this->getID(),
|
||||
);
|
||||
|
||||
return implode("\0", $parts);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ final class HarbormasterLintPropertyView extends AphrontView {
|
|||
|
||||
private $pathURIMap = array();
|
||||
private $lintMessages = array();
|
||||
private $limit;
|
||||
|
||||
public function setPathURIMap(array $map) {
|
||||
$this->pathURIMap = $map;
|
||||
|
@ -16,9 +17,21 @@ final class HarbormasterLintPropertyView extends AphrontView {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function setLimit($limit) {
|
||||
$this->limit = $limit;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$messages = $this->lintMessages;
|
||||
$messages = msort($messages, 'getSortKey');
|
||||
|
||||
if ($this->limit) {
|
||||
$messages = array_slice($messages, 0, $this->limit);
|
||||
}
|
||||
|
||||
$rows = array();
|
||||
foreach ($this->lintMessages as $message) {
|
||||
foreach ($messages as $message) {
|
||||
$path = $message->getPath();
|
||||
$line = $message->getLine();
|
||||
|
||||
|
@ -40,8 +53,8 @@ final class HarbormasterLintPropertyView extends AphrontView {
|
|||
}
|
||||
|
||||
$rows[] = array(
|
||||
$location,
|
||||
$severity,
|
||||
$location,
|
||||
$message->getCode(),
|
||||
$message->getName(),
|
||||
);
|
||||
|
@ -50,15 +63,15 @@ final class HarbormasterLintPropertyView extends AphrontView {
|
|||
$table = id(new AphrontTableView($rows))
|
||||
->setHeaders(
|
||||
array(
|
||||
pht('Location'),
|
||||
pht('Severity'),
|
||||
pht('Location'),
|
||||
pht('Code'),
|
||||
pht('Message'),
|
||||
))
|
||||
->setColumnClasses(
|
||||
array(
|
||||
'pri',
|
||||
null,
|
||||
'pri',
|
||||
null,
|
||||
'wide',
|
||||
));
|
||||
|
|
|
@ -4,6 +4,7 @@ final class HarbormasterUnitPropertyView extends AphrontView {
|
|||
|
||||
private $pathURIMap = array();
|
||||
private $unitMessages = array();
|
||||
private $limit;
|
||||
|
||||
public function setPathURIMap(array $map) {
|
||||
$this->pathURIMap = $map;
|
||||
|
@ -16,11 +17,22 @@ final class HarbormasterUnitPropertyView extends AphrontView {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function setLimit($limit) {
|
||||
$this->limit = $limit;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$messages = $this->unitMessages;
|
||||
$messages = msort($messages, 'getSortKey');
|
||||
|
||||
if ($this->limit) {
|
||||
$messages = array_slice($messages, 0, $this->limit);
|
||||
}
|
||||
|
||||
$rows = array();
|
||||
$any_duration = false;
|
||||
foreach ($this->unitMessages as $message) {
|
||||
foreach ($messages as $message) {
|
||||
$result = $this->renderResult($message->getResult());
|
||||
|
||||
$duration = $message->getDuration();
|
||||
|
@ -48,7 +60,6 @@ final class HarbormasterUnitPropertyView extends AphrontView {
|
|||
);
|
||||
}
|
||||
|
||||
|
||||
$table = id(new AphrontTableView($rows))
|
||||
->setHeaders(
|
||||
array(
|
||||
|
|
296
src/applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php
Normal file
296
src/applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php
Normal file
|
@ -0,0 +1,296 @@
|
|||
<?php
|
||||
|
||||
final class ManiphestTaskEditBulkJobType
|
||||
extends PhabricatorWorkerBulkJobType {
|
||||
|
||||
public function getBulkJobTypeKey() {
|
||||
return 'maniphest.task.edit';
|
||||
}
|
||||
|
||||
public function getJobName(PhabricatorWorkerBulkJob $job) {
|
||||
return pht('Maniphest Bulk Edit');
|
||||
}
|
||||
|
||||
public function getDescriptionForConfirm(PhabricatorWorkerBulkJob $job) {
|
||||
return pht(
|
||||
'You are about to apply a bulk edit to Maniphest which will affect '.
|
||||
'%s task(s).',
|
||||
new PhutilNumber($job->getSize()));
|
||||
}
|
||||
|
||||
public function getJobSize(PhabricatorWorkerBulkJob $job) {
|
||||
return count($job->getParameter('taskPHIDs', array()));
|
||||
}
|
||||
|
||||
public function getDoneURI(PhabricatorWorkerBulkJob $job) {
|
||||
return $job->getParameter('doneURI');
|
||||
}
|
||||
|
||||
public function createTasks(PhabricatorWorkerBulkJob $job) {
|
||||
$tasks = array();
|
||||
|
||||
foreach ($job->getParameter('taskPHIDs', array()) as $phid) {
|
||||
$tasks[] = PhabricatorWorkerBulkTask::initializeNewTask($job, $phid);
|
||||
}
|
||||
|
||||
return $tasks;
|
||||
}
|
||||
|
||||
public function runTask(
|
||||
PhabricatorUser $actor,
|
||||
PhabricatorWorkerBulkJob $job,
|
||||
PhabricatorWorkerBulkTask $task) {
|
||||
|
||||
$object = id(new ManiphestTaskQuery())
|
||||
->setViewer($actor)
|
||||
->requireCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
))
|
||||
->withPHIDs(array($task->getObjectPHID()))
|
||||
->executeOne();
|
||||
if (!$object) {
|
||||
return;
|
||||
}
|
||||
|
||||
$field_list = PhabricatorCustomField::getObjectFields(
|
||||
$object,
|
||||
PhabricatorCustomField::ROLE_EDIT);
|
||||
$field_list->readFieldsFromStorage($object);
|
||||
|
||||
$actions = $job->getParameter('actions');
|
||||
$xactions = $this->buildTransactions($actions, $object);
|
||||
|
||||
$editor = id(new ManiphestTransactionEditor())
|
||||
->setActor($actor)
|
||||
->setContentSource($job->newContentSource())
|
||||
->setContinueOnNoEffect(true)
|
||||
->setContinueOnMissingFields(true)
|
||||
->applyTransactions($object, $xactions);
|
||||
}
|
||||
|
||||
private function buildTransactions($actions, ManiphestTask $task) {
|
||||
$value_map = array();
|
||||
$type_map = array(
|
||||
'add_comment' => PhabricatorTransactions::TYPE_COMMENT,
|
||||
'assign' => ManiphestTransaction::TYPE_OWNER,
|
||||
'status' => ManiphestTransaction::TYPE_STATUS,
|
||||
'priority' => ManiphestTransaction::TYPE_PRIORITY,
|
||||
'add_project' => PhabricatorTransactions::TYPE_EDGE,
|
||||
'remove_project' => PhabricatorTransactions::TYPE_EDGE,
|
||||
'add_ccs' => PhabricatorTransactions::TYPE_SUBSCRIBERS,
|
||||
'remove_ccs' => PhabricatorTransactions::TYPE_SUBSCRIBERS,
|
||||
'space' => PhabricatorTransactions::TYPE_SPACE,
|
||||
);
|
||||
|
||||
$edge_edit_types = array(
|
||||
'add_project' => true,
|
||||
'remove_project' => true,
|
||||
'add_ccs' => true,
|
||||
'remove_ccs' => true,
|
||||
);
|
||||
|
||||
$xactions = array();
|
||||
foreach ($actions as $action) {
|
||||
if (empty($type_map[$action['action']])) {
|
||||
throw new Exception(pht("Unknown batch edit action '%s'!", $action));
|
||||
}
|
||||
|
||||
$type = $type_map[$action['action']];
|
||||
|
||||
// Figure out the current value, possibly after modifications by other
|
||||
// batch actions of the same type. For example, if the user chooses to
|
||||
// "Add Comment" twice, we should add both comments. More notably, if the
|
||||
// user chooses "Remove Project..." and also "Add Project...", we should
|
||||
// avoid restoring the removed project in the second transaction.
|
||||
|
||||
if (array_key_exists($type, $value_map)) {
|
||||
$current = $value_map[$type];
|
||||
} else {
|
||||
switch ($type) {
|
||||
case PhabricatorTransactions::TYPE_COMMENT:
|
||||
$current = null;
|
||||
break;
|
||||
case ManiphestTransaction::TYPE_OWNER:
|
||||
$current = $task->getOwnerPHID();
|
||||
break;
|
||||
case ManiphestTransaction::TYPE_STATUS:
|
||||
$current = $task->getStatus();
|
||||
break;
|
||||
case ManiphestTransaction::TYPE_PRIORITY:
|
||||
$current = $task->getPriority();
|
||||
break;
|
||||
case PhabricatorTransactions::TYPE_EDGE:
|
||||
$current = $task->getProjectPHIDs();
|
||||
break;
|
||||
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
|
||||
$current = $task->getSubscriberPHIDs();
|
||||
break;
|
||||
case PhabricatorTransactions::TYPE_SPACE:
|
||||
$current = PhabricatorSpacesNamespaceQuery::getObjectSpacePHID(
|
||||
$task);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the value is meaningful / provided, and normalize it if
|
||||
// necessary. This discards, e.g., empty comments and empty owner
|
||||
// changes.
|
||||
|
||||
$value = $action['value'];
|
||||
switch ($type) {
|
||||
case PhabricatorTransactions::TYPE_COMMENT:
|
||||
if (!strlen($value)) {
|
||||
continue 2;
|
||||
}
|
||||
break;
|
||||
case PhabricatorTransactions::TYPE_SPACE:
|
||||
if (empty($value)) {
|
||||
continue 2;
|
||||
}
|
||||
$value = head($value);
|
||||
break;
|
||||
case ManiphestTransaction::TYPE_OWNER:
|
||||
if (empty($value)) {
|
||||
continue 2;
|
||||
}
|
||||
$value = head($value);
|
||||
$no_owner = PhabricatorPeopleNoOwnerDatasource::FUNCTION_TOKEN;
|
||||
if ($value === $no_owner) {
|
||||
$value = null;
|
||||
}
|
||||
break;
|
||||
case PhabricatorTransactions::TYPE_EDGE:
|
||||
if (empty($value)) {
|
||||
continue 2;
|
||||
}
|
||||
break;
|
||||
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
|
||||
if (empty($value)) {
|
||||
continue 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// If the edit doesn't change anything, go to the next action. This
|
||||
// check is only valid for changes like "owner", "status", etc, not
|
||||
// for edge edits, because we should still apply an edit like
|
||||
// "Remove Projects: A, B" to a task with projects "A, B".
|
||||
|
||||
if (empty($edge_edit_types[$action['action']])) {
|
||||
if ($value == $current) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the value change; for most edits this is just replacement, but
|
||||
// some need to merge the current and edited values (add/remove project).
|
||||
|
||||
switch ($type) {
|
||||
case PhabricatorTransactions::TYPE_COMMENT:
|
||||
if (strlen($current)) {
|
||||
$value = $current."\n\n".$value;
|
||||
}
|
||||
break;
|
||||
case PhabricatorTransactions::TYPE_EDGE:
|
||||
$is_remove = $action['action'] == 'remove_project';
|
||||
|
||||
$current = array_fill_keys($current, true);
|
||||
$value = array_fill_keys($value, true);
|
||||
|
||||
$new = $current;
|
||||
$did_something = false;
|
||||
|
||||
if ($is_remove) {
|
||||
foreach ($value as $phid => $ignored) {
|
||||
if (isset($new[$phid])) {
|
||||
unset($new[$phid]);
|
||||
$did_something = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach ($value as $phid => $ignored) {
|
||||
if (empty($new[$phid])) {
|
||||
$new[$phid] = true;
|
||||
$did_something = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$did_something) {
|
||||
continue 2;
|
||||
}
|
||||
|
||||
$value = array_keys($new);
|
||||
break;
|
||||
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
|
||||
$is_remove = $action['action'] == 'remove_ccs';
|
||||
|
||||
$current = array_fill_keys($current, true);
|
||||
|
||||
$new = array();
|
||||
$did_something = false;
|
||||
|
||||
if ($is_remove) {
|
||||
foreach ($value as $phid) {
|
||||
if (isset($current[$phid])) {
|
||||
$new[$phid] = true;
|
||||
$did_something = true;
|
||||
}
|
||||
}
|
||||
if ($new) {
|
||||
$value = array('-' => array_keys($new));
|
||||
}
|
||||
} else {
|
||||
$new = array();
|
||||
foreach ($value as $phid) {
|
||||
$new[$phid] = true;
|
||||
$did_something = true;
|
||||
}
|
||||
if ($new) {
|
||||
$value = array('+' => array_keys($new));
|
||||
}
|
||||
}
|
||||
if (!$did_something) {
|
||||
continue 2;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
$value_map[$type] = $value;
|
||||
}
|
||||
|
||||
$template = new ManiphestTransaction();
|
||||
|
||||
foreach ($value_map as $type => $value) {
|
||||
$xaction = clone $template;
|
||||
$xaction->setTransactionType($type);
|
||||
|
||||
switch ($type) {
|
||||
case PhabricatorTransactions::TYPE_COMMENT:
|
||||
$xaction->attachComment(
|
||||
id(new ManiphestTransactionComment())
|
||||
->setContent($value));
|
||||
break;
|
||||
case PhabricatorTransactions::TYPE_EDGE:
|
||||
$project_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
|
||||
$xaction
|
||||
->setMetadataValue('edge:type', $project_type)
|
||||
->setNewValue(
|
||||
array(
|
||||
'=' => array_fuse($value),
|
||||
));
|
||||
break;
|
||||
default:
|
||||
$xaction->setNewValue($value);
|
||||
break;
|
||||
}
|
||||
|
||||
$xactions[] = $xaction;
|
||||
}
|
||||
|
||||
return $xactions;
|
||||
}
|
||||
}
|
|
@ -45,8 +45,7 @@ final class ManiphestBatchEditController extends ManiphestController {
|
|||
|
||||
if (!$tasks) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
"You don't have permission to edit any of the selected tasks."));
|
||||
pht("You don't have permission to edit any of the selected tasks."));
|
||||
}
|
||||
|
||||
if ($project) {
|
||||
|
@ -62,27 +61,32 @@ final class ManiphestBatchEditController extends ManiphestController {
|
|||
$actions = phutil_json_decode($actions);
|
||||
}
|
||||
|
||||
if ($request->isFormPost() && is_array($actions)) {
|
||||
foreach ($tasks as $task) {
|
||||
$field_list = PhabricatorCustomField::getObjectFields(
|
||||
$task,
|
||||
PhabricatorCustomField::ROLE_EDIT);
|
||||
$field_list->readFieldsFromStorage($task);
|
||||
if ($request->isFormPost() && $actions) {
|
||||
$job = PhabricatorWorkerBulkJob::initializeNewJob(
|
||||
$viewer,
|
||||
new ManiphestTaskEditBulkJobType(),
|
||||
array(
|
||||
'taskPHIDs' => mpull($tasks, 'getPHID'),
|
||||
'actions' => $actions,
|
||||
'cancelURI' => $cancel_uri,
|
||||
'doneURI' => $redirect_uri,
|
||||
));
|
||||
|
||||
$xactions = $this->buildTransactions($actions, $task);
|
||||
if ($xactions) {
|
||||
// TODO: Set content source to "batch edit".
|
||||
$type_status = PhabricatorWorkerBulkJobTransaction::TYPE_STATUS;
|
||||
|
||||
$editor = id(new ManiphestTransactionEditor())
|
||||
->setActor($viewer)
|
||||
->setContentSourceFromRequest($request)
|
||||
->setContinueOnNoEffect(true)
|
||||
->setContinueOnMissingFields(true)
|
||||
->applyTransactions($task, $xactions);
|
||||
}
|
||||
}
|
||||
$xactions = array();
|
||||
$xactions[] = id(new PhabricatorWorkerBulkJobTransaction())
|
||||
->setTransactionType($type_status)
|
||||
->setNewValue(PhabricatorWorkerBulkJob::STATUS_CONFIRM);
|
||||
|
||||
return id(new AphrontRedirectResponse())->setURI($redirect_uri);
|
||||
$editor = id(new PhabricatorWorkerBulkJobEditor())
|
||||
->setActor($viewer)
|
||||
->setContentSourceFromRequest($request)
|
||||
->setContinueOnMissingFields(true)
|
||||
->applyTransactions($job, $xactions);
|
||||
|
||||
return id(new AphrontRedirectResponse())
|
||||
->setURI($job->getMonitorURI());
|
||||
}
|
||||
|
||||
$handles = ManiphestTaskListView::loadTaskHandles($viewer, $tasks);
|
||||
|
@ -210,228 +214,4 @@ final class ManiphestBatchEditController extends ManiphestController {
|
|||
));
|
||||
}
|
||||
|
||||
private function buildTransactions($actions, ManiphestTask $task) {
|
||||
$value_map = array();
|
||||
$type_map = array(
|
||||
'add_comment' => PhabricatorTransactions::TYPE_COMMENT,
|
||||
'assign' => ManiphestTransaction::TYPE_OWNER,
|
||||
'status' => ManiphestTransaction::TYPE_STATUS,
|
||||
'priority' => ManiphestTransaction::TYPE_PRIORITY,
|
||||
'add_project' => PhabricatorTransactions::TYPE_EDGE,
|
||||
'remove_project' => PhabricatorTransactions::TYPE_EDGE,
|
||||
'add_ccs' => PhabricatorTransactions::TYPE_SUBSCRIBERS,
|
||||
'remove_ccs' => PhabricatorTransactions::TYPE_SUBSCRIBERS,
|
||||
'space' => PhabricatorTransactions::TYPE_SPACE,
|
||||
);
|
||||
|
||||
$edge_edit_types = array(
|
||||
'add_project' => true,
|
||||
'remove_project' => true,
|
||||
'add_ccs' => true,
|
||||
'remove_ccs' => true,
|
||||
);
|
||||
|
||||
$xactions = array();
|
||||
foreach ($actions as $action) {
|
||||
if (empty($type_map[$action['action']])) {
|
||||
throw new Exception(pht("Unknown batch edit action '%s'!", $action));
|
||||
}
|
||||
|
||||
$type = $type_map[$action['action']];
|
||||
|
||||
// Figure out the current value, possibly after modifications by other
|
||||
// batch actions of the same type. For example, if the user chooses to
|
||||
// "Add Comment" twice, we should add both comments. More notably, if the
|
||||
// user chooses "Remove Project..." and also "Add Project...", we should
|
||||
// avoid restoring the removed project in the second transaction.
|
||||
|
||||
if (array_key_exists($type, $value_map)) {
|
||||
$current = $value_map[$type];
|
||||
} else {
|
||||
switch ($type) {
|
||||
case PhabricatorTransactions::TYPE_COMMENT:
|
||||
$current = null;
|
||||
break;
|
||||
case ManiphestTransaction::TYPE_OWNER:
|
||||
$current = $task->getOwnerPHID();
|
||||
break;
|
||||
case ManiphestTransaction::TYPE_STATUS:
|
||||
$current = $task->getStatus();
|
||||
break;
|
||||
case ManiphestTransaction::TYPE_PRIORITY:
|
||||
$current = $task->getPriority();
|
||||
break;
|
||||
case PhabricatorTransactions::TYPE_EDGE:
|
||||
$current = $task->getProjectPHIDs();
|
||||
break;
|
||||
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
|
||||
$current = $task->getSubscriberPHIDs();
|
||||
break;
|
||||
case PhabricatorTransactions::TYPE_SPACE:
|
||||
$current = PhabricatorSpacesNamespaceQuery::getObjectSpacePHID(
|
||||
$task);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the value is meaningful / provided, and normalize it if
|
||||
// necessary. This discards, e.g., empty comments and empty owner
|
||||
// changes.
|
||||
|
||||
$value = $action['value'];
|
||||
switch ($type) {
|
||||
case PhabricatorTransactions::TYPE_COMMENT:
|
||||
if (!strlen($value)) {
|
||||
continue 2;
|
||||
}
|
||||
break;
|
||||
case PhabricatorTransactions::TYPE_SPACE:
|
||||
if (empty($value)) {
|
||||
continue 2;
|
||||
}
|
||||
$value = head($value);
|
||||
break;
|
||||
case ManiphestTransaction::TYPE_OWNER:
|
||||
if (empty($value)) {
|
||||
continue 2;
|
||||
}
|
||||
$value = head($value);
|
||||
$no_owner = PhabricatorPeopleNoOwnerDatasource::FUNCTION_TOKEN;
|
||||
if ($value === $no_owner) {
|
||||
$value = null;
|
||||
}
|
||||
break;
|
||||
case PhabricatorTransactions::TYPE_EDGE:
|
||||
if (empty($value)) {
|
||||
continue 2;
|
||||
}
|
||||
break;
|
||||
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
|
||||
if (empty($value)) {
|
||||
continue 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// If the edit doesn't change anything, go to the next action. This
|
||||
// check is only valid for changes like "owner", "status", etc, not
|
||||
// for edge edits, because we should still apply an edit like
|
||||
// "Remove Projects: A, B" to a task with projects "A, B".
|
||||
|
||||
if (empty($edge_edit_types[$action['action']])) {
|
||||
if ($value == $current) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the value change; for most edits this is just replacement, but
|
||||
// some need to merge the current and edited values (add/remove project).
|
||||
|
||||
switch ($type) {
|
||||
case PhabricatorTransactions::TYPE_COMMENT:
|
||||
if (strlen($current)) {
|
||||
$value = $current."\n\n".$value;
|
||||
}
|
||||
break;
|
||||
case PhabricatorTransactions::TYPE_EDGE:
|
||||
$is_remove = $action['action'] == 'remove_project';
|
||||
|
||||
$current = array_fill_keys($current, true);
|
||||
$value = array_fill_keys($value, true);
|
||||
|
||||
$new = $current;
|
||||
$did_something = false;
|
||||
|
||||
if ($is_remove) {
|
||||
foreach ($value as $phid => $ignored) {
|
||||
if (isset($new[$phid])) {
|
||||
unset($new[$phid]);
|
||||
$did_something = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach ($value as $phid => $ignored) {
|
||||
if (empty($new[$phid])) {
|
||||
$new[$phid] = true;
|
||||
$did_something = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$did_something) {
|
||||
continue 2;
|
||||
}
|
||||
|
||||
$value = array_keys($new);
|
||||
break;
|
||||
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
|
||||
$is_remove = $action['action'] == 'remove_ccs';
|
||||
|
||||
$current = array_fill_keys($current, true);
|
||||
|
||||
$new = array();
|
||||
$did_something = false;
|
||||
|
||||
if ($is_remove) {
|
||||
foreach ($value as $phid) {
|
||||
if (isset($current[$phid])) {
|
||||
$new[$phid] = true;
|
||||
$did_something = true;
|
||||
}
|
||||
}
|
||||
if ($new) {
|
||||
$value = array('-' => array_keys($new));
|
||||
}
|
||||
} else {
|
||||
$new = array();
|
||||
foreach ($value as $phid) {
|
||||
$new[$phid] = true;
|
||||
$did_something = true;
|
||||
}
|
||||
if ($new) {
|
||||
$value = array('+' => array_keys($new));
|
||||
}
|
||||
}
|
||||
if (!$did_something) {
|
||||
continue 2;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
$value_map[$type] = $value;
|
||||
}
|
||||
|
||||
$template = new ManiphestTransaction();
|
||||
|
||||
foreach ($value_map as $type => $value) {
|
||||
$xaction = clone $template;
|
||||
$xaction->setTransactionType($type);
|
||||
|
||||
switch ($type) {
|
||||
case PhabricatorTransactions::TYPE_COMMENT:
|
||||
$xaction->attachComment(
|
||||
id(new ManiphestTransactionComment())
|
||||
->setContent($value));
|
||||
break;
|
||||
case PhabricatorTransactions::TYPE_EDGE:
|
||||
$project_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
|
||||
$xaction
|
||||
->setMetadataValue('edge:type', $project_type)
|
||||
->setNewValue(
|
||||
array(
|
||||
'=' => array_fuse($value),
|
||||
));
|
||||
break;
|
||||
default:
|
||||
$xaction->setNewValue($value);
|
||||
break;
|
||||
}
|
||||
|
||||
$xactions[] = $xaction;
|
||||
}
|
||||
|
||||
return $xactions;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -6,6 +6,10 @@ final class PhabricatorMetaMTAApplication extends PhabricatorApplication {
|
|||
return pht('MetaMTA');
|
||||
}
|
||||
|
||||
public function getBaseURI() {
|
||||
return '/mail/';
|
||||
}
|
||||
|
||||
public function getFontIcon() {
|
||||
return 'fa-send';
|
||||
}
|
||||
|
@ -37,6 +41,9 @@ final class PhabricatorMetaMTAApplication extends PhabricatorApplication {
|
|||
public function getRoutes() {
|
||||
return array(
|
||||
'/mail/' => array(
|
||||
'(query/(?P<queryKey>[^/]+)/)?' =>
|
||||
'PhabricatorMetaMTAMailListController',
|
||||
'detail/(?P<id>[1-9]\d*)/' => 'PhabricatorMetaMTAMailViewController',
|
||||
'sendgrid/' => 'PhabricatorMetaMTASendGridReceiveController',
|
||||
'mailgun/' => 'PhabricatorMetaMTAMailgunReceiveController',
|
||||
),
|
||||
|
|
|
@ -15,6 +15,7 @@ final class PhabricatorContentSource extends Phobject {
|
|||
const SOURCE_DAEMON = 'daemon';
|
||||
const SOURCE_LIPSUM = 'lipsum';
|
||||
const SOURCE_PHORTUNE = 'phortune';
|
||||
const SOURCE_BULK = 'bulk';
|
||||
|
||||
private $source;
|
||||
private $params = array();
|
||||
|
@ -79,6 +80,7 @@ final class PhabricatorContentSource extends Phobject {
|
|||
self::SOURCE_LIPSUM => pht('Lipsum'),
|
||||
self::SOURCE_UNKNOWN => pht('Old World'),
|
||||
self::SOURCE_PHORTUNE => pht('Phortune'),
|
||||
self::SOURCE_BULK => pht('Bulk Edit'),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorMetaMTAMailListController
|
||||
extends PhabricatorMetaMTAController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$controller = id(new PhabricatorApplicationSearchController())
|
||||
->setQueryKey($request->getURIData('queryKey'))
|
||||
->setSearchEngine(new PhabricatorMetaMTAMailSearchEngine())
|
||||
->setNavigation($this->buildSideNav());
|
||||
|
||||
return $this->delegateToController($controller);
|
||||
}
|
||||
|
||||
public function buildSideNav() {
|
||||
$user = $this->getRequest()->getUser();
|
||||
|
||||
$nav = new AphrontSideNavFilterView();
|
||||
$nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
|
||||
|
||||
id(new PhabricatorMetaMTAMailSearchEngine())
|
||||
->setViewer($user)
|
||||
->addNavigationItems($nav->getMenu());
|
||||
|
||||
$nav->selectFilter(null);
|
||||
|
||||
return $nav;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorMetaMTAMailViewController
|
||||
extends PhabricatorMetaMTAController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getUser();
|
||||
|
||||
$mail = id(new PhabricatorMetaMTAMailQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($request->getURIData('id')))
|
||||
->executeOne();
|
||||
if (!$mail) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
if ($mail->hasSensitiveContent()) {
|
||||
$title = pht('Content Redacted');
|
||||
} else {
|
||||
$title = $mail->getSubject();
|
||||
}
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader($title)
|
||||
->setUser($this->getRequest()->getUser())
|
||||
->setPolicyObject($mail);
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs()
|
||||
->addTextCrumb(
|
||||
'Mail '.$mail->getID());
|
||||
$object_box = id(new PHUIObjectBoxView())
|
||||
->setHeader($header)
|
||||
->addPropertyList($this->buildPropertyView($mail));
|
||||
|
||||
return $this->buildApplicationPage(
|
||||
array(
|
||||
$crumbs,
|
||||
$object_box,
|
||||
),
|
||||
array(
|
||||
'title' => $title,
|
||||
'pageObjects' => array($mail->getPHID()),
|
||||
));
|
||||
}
|
||||
|
||||
private function buildPropertyView(PhabricatorMetaMTAMail $mail) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$properties = id(new PHUIPropertyListView())
|
||||
->setUser($viewer)
|
||||
->setObject($mail);
|
||||
|
||||
if ($mail->getActorPHID()) {
|
||||
$actor_str = $viewer->renderHandle($mail->getActorPHID());
|
||||
} else {
|
||||
$actor_str = pht('Generated by Phabricator');
|
||||
}
|
||||
$properties->addProperty(
|
||||
pht('Actor'),
|
||||
$actor_str);
|
||||
|
||||
if ($mail->getFrom()) {
|
||||
$from_str = $viewer->renderHandle($mail->getFrom());
|
||||
} else {
|
||||
$from_str = pht('Sent by Phabricator');
|
||||
}
|
||||
$properties->addProperty(
|
||||
pht('From'),
|
||||
$from_str);
|
||||
|
||||
if ($mail->getToPHIDs()) {
|
||||
$to_list = $viewer->renderHandleList($mail->getToPHIDs());
|
||||
} else {
|
||||
$to_list = pht('None');
|
||||
}
|
||||
$properties->addProperty(
|
||||
pht('To'),
|
||||
$to_list);
|
||||
|
||||
if ($mail->getCcPHIDs()) {
|
||||
$cc_list = $viewer->renderHandleList($mail->getCcPHIDs());
|
||||
} else {
|
||||
$cc_list = pht('None');
|
||||
}
|
||||
$properties->addProperty(
|
||||
pht('Cc'),
|
||||
$cc_list);
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorMetaMTAMailHasRecipientEdgeType
|
||||
extends PhabricatorEdgeType {
|
||||
|
||||
const EDGECONST = 57;
|
||||
|
||||
}
|
|
@ -5,6 +5,8 @@ final class PhabricatorMetaMTAMailQuery
|
|||
|
||||
private $ids;
|
||||
private $phids;
|
||||
private $actorPHIDs;
|
||||
private $recipientPHIDs;
|
||||
|
||||
public function withIDs(array $ids) {
|
||||
$this->ids = $ids;
|
||||
|
@ -16,6 +18,16 @@ final class PhabricatorMetaMTAMailQuery
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function withActorPHIDs(array $phids) {
|
||||
$this->actorPHIDs = $phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withRecipientPHIDs(array $phids) {
|
||||
$this->recipientPHIDs = $phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function loadPage() {
|
||||
return $this->loadStandardPage($this->newResultObject());
|
||||
}
|
||||
|
@ -37,11 +49,57 @@ final class PhabricatorMetaMTAMailQuery
|
|||
$this->phids);
|
||||
}
|
||||
|
||||
if ($this->actorPHIDs !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
'mail.actorPHID IN (%Ls)',
|
||||
$this->actorPHIDs);
|
||||
}
|
||||
|
||||
if ($this->recipientPHIDs !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
'recipient.dst IN (%Ls)',
|
||||
$this->recipientPHIDs);
|
||||
}
|
||||
|
||||
if ($this->actorPHIDs === null && $this->recipientPHIDs === null) {
|
||||
$viewer = $this->getViewer();
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
'edge.dst = %s OR actorPHID = %s',
|
||||
$viewer->getPHID(),
|
||||
$viewer->getPHID());
|
||||
}
|
||||
|
||||
$where[] = $this->buildPagingClause($conn_r);
|
||||
|
||||
return $this->formatWhereClause($where);
|
||||
}
|
||||
|
||||
protected function buildJoinClause(AphrontDatabaseConnection $conn) {
|
||||
$joins = array();
|
||||
|
||||
if ($this->actorPHIDs === null && $this->recipientPHIDs === null) {
|
||||
$joins[] = qsprintf(
|
||||
$conn,
|
||||
'LEFT JOIN %T edge ON mail.phid = edge.src AND edge.type = %d',
|
||||
PhabricatorEdgeConfig::TABLE_NAME_EDGE,
|
||||
PhabricatorMetaMTAMailHasRecipientEdgeType::EDGECONST);
|
||||
}
|
||||
|
||||
if ($this->recipientPHIDs !== null) {
|
||||
$joins[] = qsprintf(
|
||||
$conn,
|
||||
'LEFT JOIN %T recipient '.
|
||||
'ON mail.phid = recipient.src AND recipient.type = %d',
|
||||
PhabricatorEdgeConfig::TABLE_NAME_EDGE,
|
||||
PhabricatorMetaMTAMailHasRecipientEdgeType::EDGECONST);
|
||||
}
|
||||
|
||||
return implode(' ', $joins);
|
||||
}
|
||||
|
||||
protected function getPrimaryTableAlias() {
|
||||
return 'mail';
|
||||
}
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorMetaMTAMailSearchEngine
|
||||
extends PhabricatorApplicationSearchEngine {
|
||||
|
||||
public function getResultTypeDescription() {
|
||||
return pht('MetaMTA Mails');
|
||||
}
|
||||
|
||||
public function getApplicationClassName() {
|
||||
return 'PhabricatorMetaMTAApplication';
|
||||
}
|
||||
|
||||
public function newQuery() {
|
||||
return new PhabricatorMetaMTAMailQuery();
|
||||
}
|
||||
|
||||
protected function shouldShowOrderField() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function buildCustomSearchFields() {
|
||||
return array(
|
||||
id(new PhabricatorSearchUsersField())
|
||||
->setLabel(pht('Actors'))
|
||||
->setKey('actorPHIDs')
|
||||
->setAliases(array('actor', 'actors')),
|
||||
id(new PhabricatorSearchUsersField())
|
||||
->setLabel(pht('Recipients'))
|
||||
->setKey('recipientPHIDs')
|
||||
->setAliases(array('recipient', 'recipients')),
|
||||
);
|
||||
}
|
||||
|
||||
protected function buildQueryFromParameters(array $map) {
|
||||
$query = $this->newQuery();
|
||||
|
||||
if ($map['actorPHIDs']) {
|
||||
$query->withActorPHIDs($map['actorPHIDs']);
|
||||
}
|
||||
|
||||
if ($map['recipientPHIDs']) {
|
||||
$query->withRecipientPHIDs($map['recipientPHIDs']);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function getURI($path) {
|
||||
return '/mail/'.$path;
|
||||
}
|
||||
|
||||
protected function getBuiltinQueryNames() {
|
||||
$names = array(
|
||||
'inbox' => pht('Inbox'),
|
||||
'outbox' => pht('Outbox'),
|
||||
);
|
||||
|
||||
return $names;
|
||||
}
|
||||
|
||||
public function buildSavedQueryFromBuiltin($query_key) {
|
||||
$viewer = $this->requireViewer();
|
||||
|
||||
$query = $this->newSavedQuery();
|
||||
$query->setQueryKey($query_key);
|
||||
|
||||
switch ($query_key) {
|
||||
case 'inbox':
|
||||
return $query->setParameter(
|
||||
'recipientPHIDs',
|
||||
array($viewer->getPHID()));
|
||||
case 'outbox':
|
||||
return $query->setParameter(
|
||||
'actorPHIDs',
|
||||
array($viewer->getPHID()));
|
||||
}
|
||||
|
||||
return parent::buildSavedQueryFromBuiltin($query_key);
|
||||
}
|
||||
|
||||
protected function getRequiredHandlePHIDsForResultList(
|
||||
array $objects,
|
||||
PhabricatorSavedQuery $query) {
|
||||
|
||||
$phids = array();
|
||||
foreach ($objects as $mail) {
|
||||
$phids[] = $mail->getExpandedRecipientPHIDs();
|
||||
}
|
||||
return array_mergev($phids);
|
||||
}
|
||||
|
||||
protected function renderResultList(
|
||||
array $mails,
|
||||
PhabricatorSavedQuery $query,
|
||||
array $handles) {
|
||||
|
||||
assert_instances_of($mails, 'PhabricatorMetaMTAMail');
|
||||
$viewer = $this->requireViewer();
|
||||
$list = new PHUIObjectItemListView();
|
||||
|
||||
foreach ($mails as $mail) {
|
||||
if ($mail->hasSensitiveContent()) {
|
||||
$header = pht(
|
||||
'Mail %d: < content redacted >',
|
||||
$mail->getID());
|
||||
} else {
|
||||
$header = pht(
|
||||
'Mail %d: %s',
|
||||
$mail->getID(),
|
||||
$mail->getSubject());
|
||||
}
|
||||
|
||||
$item = id(new PHUIObjectItemView())
|
||||
->setObject($mail)
|
||||
->setHeader($header)
|
||||
->setHref($this->getURI('detail/'.$mail->getID()));
|
||||
$list->addItem($item);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
}
|
|
@ -25,7 +25,7 @@ final class PhabricatorMetaMTAMail
|
|||
public function __construct() {
|
||||
|
||||
$this->status = self::STATUS_QUEUE;
|
||||
$this->parameters = array();
|
||||
$this->parameters = array('sensitive' => true);
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
@ -262,6 +262,15 @@ final class PhabricatorMetaMTAMail
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function setSensitiveContent($bool) {
|
||||
$this->setParam('sensitive', $bool);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function hasSensitiveContent() {
|
||||
return $this->getParam('sensitive', true);
|
||||
}
|
||||
|
||||
public function setHTMLBody($html) {
|
||||
$this->setParam('html-body', $html);
|
||||
return $this;
|
||||
|
@ -366,9 +375,24 @@ final class PhabricatorMetaMTAMail
|
|||
// method.
|
||||
|
||||
$this->openTransaction();
|
||||
// Save to generate a task ID.
|
||||
// Save to generate a mail ID and PHID.
|
||||
$result = parent::save();
|
||||
|
||||
// Write the recipient edges.
|
||||
$editor = new PhabricatorEdgeEditor();
|
||||
$edge_type = PhabricatorMetaMTAMailHasRecipientEdgeType::EDGECONST;
|
||||
$recipient_phids = array_merge(
|
||||
$this->getToPHIDs(),
|
||||
$this->getCcPHIDs());
|
||||
$expanded_phids = $this->expandRecipients($recipient_phids);
|
||||
$all_phids = array_unique(array_merge(
|
||||
$recipient_phids,
|
||||
$expanded_phids));
|
||||
foreach ($all_phids as $curr_phid) {
|
||||
$editor->addEdge($this->getPHID(), $edge_type, $curr_phid);
|
||||
}
|
||||
$editor->save();
|
||||
|
||||
// Queue a task to send this mail.
|
||||
$mailer_task = PhabricatorWorker::scheduleTask(
|
||||
'PhabricatorMetaMTAWorker',
|
||||
|
@ -813,11 +837,15 @@ final class PhabricatorMetaMTAMail
|
|||
}
|
||||
|
||||
public function loadAllActors() {
|
||||
$actor_phids = $this->getAllActorPHIDs();
|
||||
$actor_phids = $this->expandRecipients($actor_phids);
|
||||
$actor_phids = $this->getExpandedRecipientPHIDs();
|
||||
return $this->loadActors($actor_phids);
|
||||
}
|
||||
|
||||
public function getExpandedRecipientPHIDs() {
|
||||
$actor_phids = $this->getAllActorPHIDs();
|
||||
return $this->expandRecipients($actor_phids);
|
||||
}
|
||||
|
||||
private function getAllActorPHIDs() {
|
||||
return array_merge(
|
||||
array($this->getParam('from')),
|
||||
|
@ -1025,8 +1053,7 @@ final class PhabricatorMetaMTAMail
|
|||
}
|
||||
|
||||
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
|
||||
$actor_phids = $this->getAllActorPHIDs();
|
||||
$actor_phids = $this->expandRecipients($actor_phids);
|
||||
$actor_phids = $this->getExpandedRecipientPHIDs();
|
||||
return in_array($viewer->getPHID(), $actor_phids);
|
||||
}
|
||||
|
||||
|
|
|
@ -2334,6 +2334,7 @@ abstract class PhabricatorApplicationTransactionEditor
|
|||
}
|
||||
|
||||
$mail
|
||||
->setSensitiveContent(false)
|
||||
->setFrom($this->getActingAsPHID())
|
||||
->setSubjectPrefix($this->getMailSubjectPrefix())
|
||||
->setVarySubjectPrefix('['.$action.']')
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorWorkerBulkJobTestCase extends PhabricatorTestCase {
|
||||
|
||||
public function testGetAllBulkJobTypes() {
|
||||
PhabricatorWorkerBulkJobType::getAllJobTypes();
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorWorkerBulkJobCreateWorker
|
||||
extends PhabricatorWorkerBulkJobWorker {
|
||||
|
||||
protected function doWork() {
|
||||
$lock = $this->acquireJobLock();
|
||||
|
||||
$job = $this->loadJob();
|
||||
$actor = $this->loadActor($job);
|
||||
|
||||
$status = $job->getStatus();
|
||||
switch ($status) {
|
||||
case PhabricatorWorkerBulkJob::STATUS_WAITING:
|
||||
// This is what we expect. Other statuses indicate some kind of race
|
||||
// is afoot.
|
||||
break;
|
||||
default:
|
||||
throw new PhabricatorWorkerPermanentFailureException(
|
||||
pht(
|
||||
'Found unexpected job status ("%s").',
|
||||
$status));
|
||||
}
|
||||
|
||||
$tasks = $job->createTasks();
|
||||
foreach ($tasks as $task) {
|
||||
$task->save();
|
||||
}
|
||||
|
||||
$this->updateJobStatus(
|
||||
$job,
|
||||
PhabricatorWorkerBulkJob::STATUS_RUNNING);
|
||||
|
||||
$lock->unlock();
|
||||
|
||||
foreach ($tasks as $task) {
|
||||
PhabricatorWorker::scheduleTask(
|
||||
'PhabricatorWorkerBulkJobTaskWorker',
|
||||
array(
|
||||
'jobID' => $job->getID(),
|
||||
'taskID' => $task->getID(),
|
||||
),
|
||||
array(
|
||||
'priority' => PhabricatorWorker::PRIORITY_BULK,
|
||||
));
|
||||
}
|
||||
|
||||
$this->updateJob($job);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorWorkerBulkJobTaskWorker
|
||||
extends PhabricatorWorkerBulkJobWorker {
|
||||
|
||||
protected function doWork() {
|
||||
$lock = $this->acquireTaskLock();
|
||||
|
||||
$task = $this->loadTask();
|
||||
$status = $task->getStatus();
|
||||
switch ($task->getStatus()) {
|
||||
case PhabricatorWorkerBulkTask::STATUS_WAITING:
|
||||
// This is what we expect.
|
||||
break;
|
||||
default:
|
||||
throw new PhabricatorWorkerPermanentFailureException(
|
||||
pht(
|
||||
'Found unexpected task status ("%s").',
|
||||
$status));
|
||||
}
|
||||
|
||||
$task
|
||||
->setStatus(PhabricatorWorkerBulkTask::STATUS_RUNNING)
|
||||
->save();
|
||||
|
||||
$lock->unlock();
|
||||
|
||||
$job = $this->loadJob();
|
||||
$actor = $this->loadActor($job);
|
||||
|
||||
try {
|
||||
$job->runTask($actor, $task);
|
||||
$status = PhabricatorWorkerBulkTask::STATUS_DONE;
|
||||
} catch (Exception $ex) {
|
||||
phlog($ex);
|
||||
$status = PhabricatorWorkerBulkTask::STATUS_FAIL;
|
||||
}
|
||||
|
||||
$task
|
||||
->setStatus($status)
|
||||
->save();
|
||||
|
||||
$this->updateJob($job);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
abstract class PhabricatorWorkerBulkJobType extends Phobject {
|
||||
|
||||
abstract public function getJobName(PhabricatorWorkerBulkJob $job);
|
||||
abstract public function getBulkJobTypeKey();
|
||||
abstract public function getJobSize(PhabricatorWorkerBulkJob $job);
|
||||
abstract public function getDescriptionForConfirm(
|
||||
PhabricatorWorkerBulkJob $job);
|
||||
|
||||
abstract public function createTasks(PhabricatorWorkerBulkJob $job);
|
||||
abstract public function runTask(
|
||||
PhabricatorUser $actor,
|
||||
PhabricatorWorkerBulkJob $job,
|
||||
PhabricatorWorkerBulkTask $task);
|
||||
|
||||
public function getDoneURI(PhabricatorWorkerBulkJob $job) {
|
||||
return $job->getManageURI();
|
||||
}
|
||||
|
||||
final public static function getAllJobTypes() {
|
||||
return id(new PhutilClassMapQuery())
|
||||
->setAncestorClass(__CLASS__)
|
||||
->setUniqueMethod('getBulkJobTypeKey')
|
||||
->execute();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
<?php
|
||||
|
||||
abstract class PhabricatorWorkerBulkJobWorker
|
||||
extends PhabricatorWorker {
|
||||
|
||||
final protected function acquireJobLock() {
|
||||
return PhabricatorGlobalLock::newLock('bulkjob.'.$this->getJobID())
|
||||
->lock(15);
|
||||
}
|
||||
|
||||
final protected function acquireTaskLock() {
|
||||
return PhabricatorGlobalLock::newLock('bulktask.'.$this->getTaskID())
|
||||
->lock(15);
|
||||
}
|
||||
|
||||
final protected function getJobID() {
|
||||
$data = $this->getTaskData();
|
||||
$id = idx($data, 'jobID');
|
||||
if (!$id) {
|
||||
throw new PhabricatorWorkerPermanentFailureException(
|
||||
pht('Worker has no job ID.'));
|
||||
}
|
||||
return $id;
|
||||
}
|
||||
|
||||
final protected function getTaskID() {
|
||||
$data = $this->getTaskData();
|
||||
$id = idx($data, 'taskID');
|
||||
if (!$id) {
|
||||
throw new PhabricatorWorkerPermanentFailureException(
|
||||
pht('Worker has no task ID.'));
|
||||
}
|
||||
return $id;
|
||||
}
|
||||
|
||||
final protected function loadJob() {
|
||||
$id = $this->getJobID();
|
||||
$job = id(new PhabricatorWorkerBulkJobQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->withIDs(array($id))
|
||||
->executeOne();
|
||||
if (!$job) {
|
||||
throw new PhabricatorWorkerPermanentFailureException(
|
||||
pht('Worker has invalid job ID ("%s").', $id));
|
||||
}
|
||||
return $job;
|
||||
}
|
||||
|
||||
final protected function loadTask() {
|
||||
$id = $this->getTaskID();
|
||||
$task = id(new PhabricatorWorkerBulkTask())->load($id);
|
||||
if (!$task) {
|
||||
throw new PhabricatorWorkerPermanentFailureException(
|
||||
pht('Worker has invalid task ID ("%s").', $id));
|
||||
}
|
||||
return $task;
|
||||
}
|
||||
|
||||
final protected function loadActor(PhabricatorWorkerBulkJob $job) {
|
||||
$actor_phid = $job->getAuthorPHID();
|
||||
$actor = id(new PhabricatorPeopleQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->withPHIDs(array($actor_phid))
|
||||
->executeOne();
|
||||
if (!$actor) {
|
||||
throw new PhabricatorWorkerPermanentFailureException(
|
||||
pht('Worker has invalid actor PHID ("%s").', $actor_phid));
|
||||
}
|
||||
|
||||
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||
$actor,
|
||||
$job,
|
||||
PhabricatorPolicyCapability::CAN_EDIT);
|
||||
|
||||
if (!$can_edit) {
|
||||
throw new PhabricatorWorkerPermanentFailureException(
|
||||
pht('Job actor does not have permission to edit job.'));
|
||||
}
|
||||
|
||||
return $actor;
|
||||
}
|
||||
|
||||
final protected function updateJob(PhabricatorWorkerBulkJob $job) {
|
||||
$has_work = $this->hasRemainingWork($job);
|
||||
if ($has_work) {
|
||||
return;
|
||||
}
|
||||
|
||||
$lock = $this->acquireJobLock();
|
||||
|
||||
$job = $this->loadJob();
|
||||
if ($job->getStatus() == PhabricatorWorkerBulkJob::STATUS_RUNNING) {
|
||||
if (!$this->hasRemainingWork($job)) {
|
||||
$this->updateJobStatus(
|
||||
$job,
|
||||
PhabricatorWorkerBulkJob::STATUS_COMPLETE);
|
||||
}
|
||||
}
|
||||
|
||||
$lock->unlock();
|
||||
}
|
||||
|
||||
private function hasRemainingWork(PhabricatorWorkerBulkJob $job) {
|
||||
return (bool)queryfx_one(
|
||||
$job->establishConnection('r'),
|
||||
'SELECT * FROM %T WHERE bulkJobPHID = %s
|
||||
AND status NOT IN (%Ls) LIMIT 1',
|
||||
id(new PhabricatorWorkerBulkTask())->getTableName(),
|
||||
$job->getPHID(),
|
||||
array(
|
||||
PhabricatorWorkerBulkTask::STATUS_DONE,
|
||||
PhabricatorWorkerBulkTask::STATUS_FAIL,
|
||||
));
|
||||
}
|
||||
|
||||
protected function updateJobStatus(PhabricatorWorkerBulkJob $job, $status) {
|
||||
$type_status = PhabricatorWorkerBulkJobTransaction::TYPE_STATUS;
|
||||
|
||||
$xactions = array();
|
||||
$xactions[] = id(new PhabricatorWorkerBulkJobTransaction())
|
||||
->setTransactionType($type_status)
|
||||
->setNewValue($status);
|
||||
|
||||
$daemon_source = PhabricatorContentSource::newForSource(
|
||||
PhabricatorContentSource::SOURCE_DAEMON,
|
||||
array());
|
||||
|
||||
$app_phid = id(new PhabricatorDaemonsApplication())->getPHID();
|
||||
|
||||
$editor = id(new PhabricatorWorkerBulkJobEditor())
|
||||
->setActor(PhabricatorUser::getOmnipotentUser())
|
||||
->setActingAsPHID($app_phid)
|
||||
->setContentSource($daemon_source)
|
||||
->setContinueOnMissingFields(true)
|
||||
->applyTransactions($job, $xactions);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorWorkerBulkJobEditor
|
||||
extends PhabricatorApplicationTransactionEditor {
|
||||
|
||||
public function getEditorApplicationClass() {
|
||||
return 'PhabricatorDaemonsApplication';
|
||||
}
|
||||
|
||||
public function getEditorObjectsDescription() {
|
||||
return pht('Bulk Jobs');
|
||||
}
|
||||
|
||||
public function getTransactionTypes() {
|
||||
$types = parent::getTransactionTypes();
|
||||
|
||||
$types[] = PhabricatorWorkerBulkJobTransaction::TYPE_STATUS;
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
protected function getCustomTransactionOldValue(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case PhabricatorWorkerBulkJobTransaction::TYPE_STATUS:
|
||||
return $object->getStatus();
|
||||
}
|
||||
}
|
||||
|
||||
protected function getCustomTransactionNewValue(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case PhabricatorWorkerBulkJobTransaction::TYPE_STATUS:
|
||||
return $xaction->getNewValue();
|
||||
}
|
||||
}
|
||||
|
||||
protected function applyCustomInternalTransaction(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
|
||||
$type = $xaction->getTransactionType();
|
||||
$new = $xaction->getNewValue();
|
||||
|
||||
switch ($type) {
|
||||
case PhabricatorWorkerBulkJobTransaction::TYPE_STATUS:
|
||||
$object->setStatus($xaction->getNewValue());
|
||||
return;
|
||||
}
|
||||
|
||||
return parent::applyCustomInternalTransaction($object, $xaction);
|
||||
}
|
||||
|
||||
protected function applyCustomExternalTransaction(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
|
||||
$type = $xaction->getTransactionType();
|
||||
$new = $xaction->getNewValue();
|
||||
|
||||
switch ($type) {
|
||||
case PhabricatorWorkerBulkJobTransaction::TYPE_STATUS:
|
||||
switch ($new) {
|
||||
case PhabricatorWorkerBulkJob::STATUS_WAITING:
|
||||
PhabricatorWorker::scheduleTask(
|
||||
'PhabricatorWorkerBulkJobCreateWorker',
|
||||
array(
|
||||
'jobID' => $object->getID(),
|
||||
),
|
||||
array(
|
||||
'priority' => PhabricatorWorker::PRIORITY_BULK,
|
||||
));
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
return parent::applyCustomExternalTransaction($object, $xaction);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorWorkerBulkJobPHIDType extends PhabricatorPHIDType {
|
||||
|
||||
const TYPECONST = 'BULK';
|
||||
|
||||
public function getTypeName() {
|
||||
return pht('Bulk Job');
|
||||
}
|
||||
|
||||
public function newObject() {
|
||||
return new PhabricatorWorkerBulkJob();
|
||||
}
|
||||
|
||||
protected function buildQueryForObjects(
|
||||
PhabricatorObjectQuery $query,
|
||||
array $phids) {
|
||||
|
||||
return id(new PhabricatorWorkerBulkJobQuery())
|
||||
->withPHIDs($phids);
|
||||
}
|
||||
|
||||
public function loadHandles(
|
||||
PhabricatorHandleQuery $query,
|
||||
array $handles,
|
||||
array $objects) {
|
||||
|
||||
foreach ($handles as $phid => $handle) {
|
||||
$job = $objects[$phid];
|
||||
|
||||
$id = $job->getID();
|
||||
|
||||
$handle->setName(pht('Bulk Job %d', $id));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorWorkerBulkJobQuery
|
||||
extends PhabricatorCursorPagedPolicyAwareQuery {
|
||||
|
||||
private $ids;
|
||||
private $phids;
|
||||
private $authorPHIDs;
|
||||
private $bulkJobTypes;
|
||||
private $statuses;
|
||||
|
||||
public function withIDs(array $ids) {
|
||||
$this->ids = $ids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withPHIDs(array $phids) {
|
||||
$this->phids = $phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withAuthorPHIDs(array $author_phids) {
|
||||
$this->authorPHIDs = $author_phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withBulkJobTypes(array $job_types) {
|
||||
$this->bulkJobTypes = $job_types;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withStatuses(array $statuses) {
|
||||
$this->statuses = $statuses;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function newResultObject() {
|
||||
return new PhabricatorWorkerBulkJob();
|
||||
}
|
||||
|
||||
protected function loadPage() {
|
||||
return $this->loadStandardPage($this->newResultObject());
|
||||
}
|
||||
|
||||
protected function willFilterPage(array $page) {
|
||||
$map = PhabricatorWorkerBulkJobType::getAllJobTypes();
|
||||
|
||||
foreach ($page as $key => $job) {
|
||||
$implementation = idx($map, $job->getJobTypeKey());
|
||||
if (!$implementation) {
|
||||
$this->didRejectResult($job);
|
||||
unset($page[$key]);
|
||||
continue;
|
||||
}
|
||||
$job->attachJobImplementation($implementation);
|
||||
}
|
||||
|
||||
return $page;
|
||||
}
|
||||
|
||||
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
|
||||
$where = parent::buildWhereClauseParts($conn);
|
||||
|
||||
if ($this->ids !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'id IN (%Ld)',
|
||||
$this->ids);
|
||||
}
|
||||
|
||||
if ($this->phids !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'phid IN (%Ls)',
|
||||
$this->phids);
|
||||
}
|
||||
|
||||
if ($this->authorPHIDs !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'authorPHID IN (%Ls)',
|
||||
$this->authorPHIDs);
|
||||
}
|
||||
|
||||
if ($this->bulkJobTypes !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'bulkJobType IN (%Ls)',
|
||||
$this->bulkJobTypes);
|
||||
}
|
||||
|
||||
if ($this->statuses !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'status IN (%Ls)',
|
||||
$this->statuses);
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
public function getQueryApplicationClass() {
|
||||
return 'PhabricatorDaemonsApplication';
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorWorkerBulkJobSearchEngine
|
||||
extends PhabricatorApplicationSearchEngine {
|
||||
|
||||
public function getResultTypeDescription() {
|
||||
return pht('Bulk Jobs');
|
||||
}
|
||||
|
||||
public function getApplicationClassName() {
|
||||
return 'PhabricatorDaemonsApplication';
|
||||
}
|
||||
|
||||
public function newQuery() {
|
||||
return id(new PhabricatorWorkerBulkJobQuery());
|
||||
}
|
||||
|
||||
protected function buildQueryFromParameters(array $map) {
|
||||
$query = $this->newQuery();
|
||||
|
||||
if ($map['authorPHIDs']) {
|
||||
$query->withAuthorPHIDs($map['authorPHIDs']);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function buildCustomSearchFields() {
|
||||
return array(
|
||||
id(new PhabricatorSearchUsersField())
|
||||
->setLabel(pht('Authors'))
|
||||
->setKey('authorPHIDs')
|
||||
->setAliases(array('author', 'authors')),
|
||||
);
|
||||
}
|
||||
|
||||
protected function getURI($path) {
|
||||
return '/daemon/bulk/'.$path;
|
||||
}
|
||||
|
||||
protected function getBuiltinQueryNames() {
|
||||
$names = array();
|
||||
|
||||
if ($this->requireViewer()->isLoggedIn()) {
|
||||
$names['authored'] = pht('Authored Jobs');
|
||||
}
|
||||
|
||||
$names['all'] = pht('All Jobs');
|
||||
|
||||
return $names;
|
||||
}
|
||||
|
||||
public function buildSavedQueryFromBuiltin($query_key) {
|
||||
|
||||
$query = $this->newSavedQuery();
|
||||
$query->setQueryKey($query_key);
|
||||
|
||||
switch ($query_key) {
|
||||
case 'all':
|
||||
return $query;
|
||||
case 'authored':
|
||||
return $query->setParameter(
|
||||
'authorPHIDs',
|
||||
array($this->requireViewer()->getPHID()));
|
||||
}
|
||||
|
||||
return parent::buildSavedQueryFromBuiltin($query_key);
|
||||
}
|
||||
|
||||
protected function renderResultList(
|
||||
array $jobs,
|
||||
PhabricatorSavedQuery $query,
|
||||
array $handles) {
|
||||
assert_instances_of($jobs, 'PhabricatorWorkerBulkJob');
|
||||
|
||||
$viewer = $this->requireViewer();
|
||||
|
||||
$list = id(new PHUIObjectItemListView())
|
||||
->setUser($viewer);
|
||||
foreach ($jobs as $job) {
|
||||
$size = pht('%s Bulk Task(s)', new PhutilNumber($job->getSize()));
|
||||
|
||||
$item = id(new PHUIObjectItemView())
|
||||
->setObjectName(pht('Bulk Job %d', $job->getID()))
|
||||
->setHeader($job->getJobName())
|
||||
->addAttribute(phabricator_datetime($job->getDateCreated(), $viewer))
|
||||
->setHref($job->getManageURI())
|
||||
->addIcon($job->getStatusIcon(), $job->getStatusName())
|
||||
->addIcon('none', $size);
|
||||
|
||||
$list->addItem($item);
|
||||
}
|
||||
|
||||
// TODO: Needs new wrapper when merging to redesign.
|
||||
|
||||
return $list;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorWorkerBulkJobTransactionQuery
|
||||
extends PhabricatorApplicationTransactionQuery {
|
||||
|
||||
public function getTemplateApplicationTransaction() {
|
||||
return new PhabricatorWorkerBulkJobTransaction();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,272 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @task implementation Job Implementation
|
||||
*/
|
||||
final class PhabricatorWorkerBulkJob
|
||||
extends PhabricatorWorkerDAO
|
||||
implements
|
||||
PhabricatorPolicyInterface,
|
||||
PhabricatorSubscribableInterface,
|
||||
PhabricatorApplicationTransactionInterface,
|
||||
PhabricatorDestructibleInterface {
|
||||
|
||||
const STATUS_CONFIRM = 'confirm';
|
||||
const STATUS_WAITING = 'waiting';
|
||||
const STATUS_RUNNING = 'running';
|
||||
const STATUS_COMPLETE = 'complete';
|
||||
|
||||
protected $authorPHID;
|
||||
protected $jobTypeKey;
|
||||
protected $status;
|
||||
protected $parameters = array();
|
||||
protected $size;
|
||||
|
||||
private $jobImplementation = self::ATTACHABLE;
|
||||
|
||||
protected function getConfiguration() {
|
||||
return array(
|
||||
self::CONFIG_AUX_PHID => true,
|
||||
self::CONFIG_SERIALIZATION => array(
|
||||
'parameters' => self::SERIALIZATION_JSON,
|
||||
),
|
||||
self::CONFIG_COLUMN_SCHEMA => array(
|
||||
'jobTypeKey' => 'text32',
|
||||
'status' => 'text32',
|
||||
'size' => 'uint32',
|
||||
),
|
||||
self::CONFIG_KEY_SCHEMA => array(
|
||||
'key_type' => array(
|
||||
'columns' => array('jobTypeKey'),
|
||||
),
|
||||
'key_author' => array(
|
||||
'columns' => array('authorPHID'),
|
||||
),
|
||||
'key_status' => array(
|
||||
'columns' => array('status'),
|
||||
),
|
||||
),
|
||||
) + parent::getConfiguration();
|
||||
}
|
||||
|
||||
public static function initializeNewJob(
|
||||
PhabricatorUser $actor,
|
||||
PhabricatorWorkerBulkJobType $type,
|
||||
array $parameters) {
|
||||
|
||||
$job = id(new PhabricatorWorkerBulkJob())
|
||||
->setAuthorPHID($actor->getPHID())
|
||||
->setJobTypeKey($type->getBulkJobTypeKey())
|
||||
->setParameters($parameters)
|
||||
->attachJobImplementation($type);
|
||||
|
||||
$job->setSize($job->computeSize());
|
||||
|
||||
return $job;
|
||||
}
|
||||
|
||||
public function generatePHID() {
|
||||
return PhabricatorPHID::generateNewPHID(
|
||||
PhabricatorWorkerBulkJobPHIDType::TYPECONST);
|
||||
}
|
||||
|
||||
public function getMonitorURI() {
|
||||
return '/daemon/bulk/monitor/'.$this->getID().'/';
|
||||
}
|
||||
|
||||
public function getManageURI() {
|
||||
return '/daemon/bulk/view/'.$this->getID().'/';
|
||||
}
|
||||
|
||||
public function getParameter($key, $default = null) {
|
||||
return idx($this->parameters, $key, $default);
|
||||
}
|
||||
|
||||
public function setParameter($key, $value) {
|
||||
$this->parameters[$key] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function loadTaskStatusCounts() {
|
||||
$table = new PhabricatorWorkerBulkTask();
|
||||
$conn_r = $table->establishConnection('r');
|
||||
$rows = queryfx_all(
|
||||
$conn_r,
|
||||
'SELECT status, COUNT(*) N FROM %T WHERE bulkJobPHID = %s
|
||||
GROUP BY status',
|
||||
$table->getTableName(),
|
||||
$this->getPHID());
|
||||
|
||||
return ipull($rows, 'N', 'status');
|
||||
}
|
||||
|
||||
public function newContentSource() {
|
||||
return PhabricatorContentSource::newForSource(
|
||||
PhabricatorContentSource::SOURCE_BULK,
|
||||
array(
|
||||
'jobID' => $this->getID(),
|
||||
));
|
||||
}
|
||||
|
||||
public function getStatusIcon() {
|
||||
$map = array(
|
||||
self::STATUS_CONFIRM => 'fa-question',
|
||||
self::STATUS_WAITING => 'fa-clock-o',
|
||||
self::STATUS_RUNNING => 'fa-clock-o',
|
||||
self::STATUS_COMPLETE => 'fa-check grey',
|
||||
);
|
||||
|
||||
return idx($map, $this->getStatus(), 'none');
|
||||
}
|
||||
|
||||
public function getStatusName() {
|
||||
$map = array(
|
||||
self::STATUS_CONFIRM => pht('Confirming'),
|
||||
self::STATUS_WAITING => pht('Waiting'),
|
||||
self::STATUS_RUNNING => pht('Running'),
|
||||
self::STATUS_COMPLETE => pht('Complete'),
|
||||
);
|
||||
|
||||
return idx($map, $this->getStatus(), $this->getStatus());
|
||||
}
|
||||
|
||||
|
||||
/* -( Job Implementation )------------------------------------------------- */
|
||||
|
||||
|
||||
protected function getJobImplementation() {
|
||||
return $this->assertAttached($this->jobImplementation);
|
||||
}
|
||||
|
||||
public function attachJobImplementation(PhabricatorWorkerBulkJobType $type) {
|
||||
$this->jobImplementation = $type;
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function computeSize() {
|
||||
return $this->getJobImplementation()->getJobSize($this);
|
||||
}
|
||||
|
||||
public function getCancelURI() {
|
||||
return $this->getJobImplementation()->getCancelURI($this);
|
||||
}
|
||||
|
||||
public function getDoneURI() {
|
||||
return $this->getJobImplementation()->getDoneURI($this);
|
||||
}
|
||||
|
||||
public function getDescriptionForConfirm() {
|
||||
return $this->getJobImplementation()->getDescriptionForConfirm($this);
|
||||
}
|
||||
|
||||
public function createTasks() {
|
||||
return $this->getJobImplementation()->createTasks($this);
|
||||
}
|
||||
|
||||
public function runTask(
|
||||
PhabricatorUser $actor,
|
||||
PhabricatorWorkerBulkTask $task) {
|
||||
return $this->getJobImplementation()->runTask($actor, $this, $task);
|
||||
}
|
||||
|
||||
public function getJobName() {
|
||||
return $this->getJobImplementation()->getJobName($this);
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||
|
||||
|
||||
public function getCapabilities() {
|
||||
return array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
);
|
||||
}
|
||||
|
||||
public function getPolicy($capability) {
|
||||
switch ($capability) {
|
||||
case PhabricatorPolicyCapability::CAN_VIEW:
|
||||
return PhabricatorPolicies::getMostOpenPolicy();
|
||||
case PhabricatorPolicyCapability::CAN_EDIT:
|
||||
return $this->getAuthorPHID();
|
||||
}
|
||||
}
|
||||
|
||||
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function describeAutomaticCapability($capability) {
|
||||
switch ($capability) {
|
||||
case PhabricatorPolicyCapability::CAN_EDIT:
|
||||
return pht('Only the owner of a bulk job can edit it.');
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorSubscribableInterface )----------------------------------- */
|
||||
|
||||
|
||||
public function isAutomaticallySubscribed($phid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function shouldShowSubscribersProperty() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function shouldAllowSubscription($phid) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
|
||||
|
||||
|
||||
public function getApplicationTransactionEditor() {
|
||||
return new PhabricatorWorkerBulkJobEditor();
|
||||
}
|
||||
|
||||
public function getApplicationTransactionObject() {
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getApplicationTransactionTemplate() {
|
||||
return new PhabricatorWorkerBulkJobTransaction();
|
||||
}
|
||||
|
||||
public function willRenderTimeline(
|
||||
PhabricatorApplicationTransactionView $timeline,
|
||||
AphrontRequest $request) {
|
||||
return $timeline;
|
||||
}
|
||||
|
||||
/* -( PhabricatorDestructibleInterface )----------------------------------- */
|
||||
|
||||
|
||||
public function destroyObjectPermanently(
|
||||
PhabricatorDestructionEngine $engine) {
|
||||
|
||||
$this->openTransaction();
|
||||
|
||||
// We're only removing the actual task objects. This may leave stranded
|
||||
// workers in the queue itself, but they'll just flush out automatically
|
||||
// when they can't load bulk job data.
|
||||
|
||||
$task_table = new PhabricatorWorkerBulkTask();
|
||||
$conn_w = $task_table->establishConnection('w');
|
||||
queryfx(
|
||||
$conn_w,
|
||||
'DELETE FROM %T WHERE bulkJobPHID = %s',
|
||||
$task_table->getPHID(),
|
||||
$this->getPHID());
|
||||
|
||||
$this->delete();
|
||||
$this->saveTransaction();
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorWorkerBulkJobTransaction
|
||||
extends PhabricatorApplicationTransaction {
|
||||
|
||||
const TYPE_STATUS = 'bulkjob.status';
|
||||
|
||||
public function getApplicationName() {
|
||||
return 'worker';
|
||||
}
|
||||
|
||||
public function getApplicationTransactionType() {
|
||||
return PhabricatorWorkerBulkJobPHIDType::TYPECONST;
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
$author_phid = $this->getAuthorPHID();
|
||||
|
||||
$old = $this->getOldValue();
|
||||
$new = $this->getNewValue();
|
||||
|
||||
$type = $this->getTransactionType();
|
||||
switch ($type) {
|
||||
case self::TYPE_STATUS:
|
||||
if ($old === null) {
|
||||
return pht(
|
||||
'%s created this bulk job.',
|
||||
$this->renderHandleLink($author_phid));
|
||||
} else {
|
||||
switch ($new) {
|
||||
case PhabricatorWorkerBulkJob::STATUS_WAITING:
|
||||
return pht(
|
||||
'%s confirmed this job.',
|
||||
$this->renderHandleLink($author_phid));
|
||||
case PhabricatorWorkerBulkJob::STATUS_RUNNING:
|
||||
return pht(
|
||||
'%s marked this job as running.',
|
||||
$this->renderHandleLink($author_phid));
|
||||
case PhabricatorWorkerBulkJob::STATUS_COMPLETE:
|
||||
return pht(
|
||||
'%s marked this job complete.',
|
||||
$this->renderHandleLink($author_phid));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return parent::getTitle();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorWorkerBulkTask
|
||||
extends PhabricatorWorkerDAO {
|
||||
|
||||
const STATUS_WAITING = 'waiting';
|
||||
const STATUS_RUNNING = 'running';
|
||||
const STATUS_DONE = 'done';
|
||||
const STATUS_FAIL = 'fail';
|
||||
|
||||
protected $bulkJobPHID;
|
||||
protected $objectPHID;
|
||||
protected $status;
|
||||
protected $data = array();
|
||||
|
||||
protected function getConfiguration() {
|
||||
return array(
|
||||
self::CONFIG_TIMESTAMPS => false,
|
||||
self::CONFIG_SERIALIZATION => array(
|
||||
'data' => self::SERIALIZATION_JSON,
|
||||
),
|
||||
self::CONFIG_COLUMN_SCHEMA => array(
|
||||
'status' => 'text32',
|
||||
),
|
||||
self::CONFIG_KEY_SCHEMA => array(
|
||||
'key_job' => array(
|
||||
'columns' => array('bulkJobPHID', 'status'),
|
||||
),
|
||||
'key_object' => array(
|
||||
'columns' => array('objectPHID'),
|
||||
),
|
||||
),
|
||||
) + parent::getConfiguration();
|
||||
}
|
||||
|
||||
public static function initializeNewTask(
|
||||
PhabricatorWorkerBulkJob $job,
|
||||
$object_phid) {
|
||||
|
||||
return id(new PhabricatorWorkerBulkTask())
|
||||
->setBulkJobPHID($job->getPHID())
|
||||
->setStatus(self::STATUS_WAITING)
|
||||
->setObjectPHID($object_phid);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorWorkerSchemaSpec
|
||||
extends PhabricatorConfigSchemaSpec {
|
||||
|
||||
public function buildSchemata() {
|
||||
$this->buildEdgeSchemata(new PhabricatorWorkerBulkJob());
|
||||
}
|
||||
|
||||
}
|
|
@ -1170,6 +1170,18 @@ final class PhabricatorUSEnglishTranslation
|
|||
'This call takes %s parameters, but only %s are documented.',
|
||||
),
|
||||
),
|
||||
|
||||
'%s Passed Test(s)' => '%s Passed',
|
||||
'%s Failed Test(s)' => '%s Failed',
|
||||
'%s Skipped Test(s)' => '%s Skipped',
|
||||
'%s Broken Test(s)' => '%s Broken',
|
||||
'%s Unsound Test(s)' => '%s Unsound',
|
||||
'%s Other Test(s)' => '%s Other',
|
||||
|
||||
'%s Bulk Task(s)' => array(
|
||||
'%s Task',
|
||||
'%s Tasks',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
32
webroot/rsrc/css/application/daemon/bulk-job.css
Normal file
32
webroot/rsrc/css/application/daemon/bulk-job.css
Normal file
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* @provides bulk-job-css
|
||||
*/
|
||||
|
||||
.bulk-job-progress-bar {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
border: 1px solid {$lightgreyborder};
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.bulk-job-progress-slice {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.bulk-job-progress-slice-green {
|
||||
background-color: {$green};
|
||||
}
|
||||
|
||||
.bulk-job-progress-slice-blue {
|
||||
background-color: {$blue};
|
||||
}
|
||||
|
||||
.bulk-job-progress-slice-red {
|
||||
background-color: {$red};
|
||||
}
|
||||
|
||||
.bulk-job-progress-slice-empty {
|
||||
background-color: {$lightbluebackground};
|
||||
}
|
|
@ -83,3 +83,8 @@ table.aphront-table-view td.differential-toc-ftype {
|
|||
border-top: 1px solid {$thinblueborder};
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.differential-harbormaster-table-view {
|
||||
margin: 4px 0;
|
||||
border: 1px solid {$thinblueborder};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* @provides javelin-behavior-bulk-job-reload
|
||||
* @requires javelin-behavior
|
||||
* javelin-uri
|
||||
*/
|
||||
|
||||
JX.behavior('bulk-job-reload', function() {
|
||||
|
||||
// TODO: It would be nice to have a pretty Ajax progress bar here, but just
|
||||
// reload the page for now.
|
||||
|
||||
function reload() {
|
||||
JX.$U().go();
|
||||
}
|
||||
|
||||
setTimeout(reload, 1000);
|
||||
|
||||
});
|
Loading…
Reference in a new issue