mirror of
https://we.phorge.it/source/phorge.git
synced 2025-01-25 14:08:19 +01:00
(stable) Promote 2019 Week 10
This commit is contained in:
commit
2387a99d71
104 changed files with 2717 additions and 896 deletions
|
@ -9,9 +9,9 @@ return array(
|
|||
'names' => array(
|
||||
'conpherence.pkg.css' => '3c8a0668',
|
||||
'conpherence.pkg.js' => '020aebcf',
|
||||
'core.pkg.css' => 'e3c1a8f2',
|
||||
'core.pkg.css' => '34ce1741',
|
||||
'core.pkg.js' => '2cda17a4',
|
||||
'differential.pkg.css' => 'ab23bd75',
|
||||
'differential.pkg.css' => '1755a478',
|
||||
'differential.pkg.js' => '67e02996',
|
||||
'diffusion.pkg.css' => '42c75c37',
|
||||
'diffusion.pkg.js' => '91192d85',
|
||||
|
@ -61,7 +61,7 @@ return array(
|
|||
'rsrc/css/application/dashboard/dashboard.css' => '4267d6c6',
|
||||
'rsrc/css/application/diff/inline-comment-summary.css' => '81eb368d',
|
||||
'rsrc/css/application/differential/add-comment.css' => '7e5900d9',
|
||||
'rsrc/css/application/differential/changeset-view.css' => 'd92bed0d',
|
||||
'rsrc/css/application/differential/changeset-view.css' => '4193eeff',
|
||||
'rsrc/css/application/differential/core.css' => '7300a73e',
|
||||
'rsrc/css/application/differential/phui-inline-comment.css' => '48acce5b',
|
||||
'rsrc/css/application/differential/revision-comment.css' => '7dbc8d1d',
|
||||
|
@ -127,7 +127,7 @@ return array(
|
|||
'rsrc/css/phui/calendar/phui-calendar-list.css' => 'ccd7e4e2',
|
||||
'rsrc/css/phui/calendar/phui-calendar-month.css' => 'cb758c42',
|
||||
'rsrc/css/phui/calendar/phui-calendar.css' => 'f11073aa',
|
||||
'rsrc/css/phui/object-item/phui-oi-big-ui.css' => '9e037c7a',
|
||||
'rsrc/css/phui/object-item/phui-oi-big-ui.css' => '534f1757',
|
||||
'rsrc/css/phui/object-item/phui-oi-color.css' => 'b517bfa0',
|
||||
'rsrc/css/phui/object-item/phui-oi-drag-ui.css' => 'da15d3dc',
|
||||
'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '490e2e2e',
|
||||
|
@ -540,7 +540,7 @@ return array(
|
|||
'conpherence-thread-manager' => 'aec8e38c',
|
||||
'conpherence-transaction-css' => '3a3f5e7e',
|
||||
'd3' => 'd67475f5',
|
||||
'differential-changeset-view-css' => 'd92bed0d',
|
||||
'differential-changeset-view-css' => '4193eeff',
|
||||
'differential-core-view-css' => '7300a73e',
|
||||
'differential-revision-add-comment-css' => '7e5900d9',
|
||||
'differential-revision-comment-css' => '7dbc8d1d',
|
||||
|
@ -834,7 +834,7 @@ return array(
|
|||
'phui-lightbox-css' => '4ebf22da',
|
||||
'phui-list-view-css' => '470b1adb',
|
||||
'phui-object-box-css' => 'f434b6be',
|
||||
'phui-oi-big-ui-css' => '9e037c7a',
|
||||
'phui-oi-big-ui-css' => '534f1757',
|
||||
'phui-oi-color-css' => 'b517bfa0',
|
||||
'phui-oi-drag-ui-css' => 'da15d3dc',
|
||||
'phui-oi-flush-ui-css' => '490e2e2e',
|
||||
|
@ -1220,6 +1220,9 @@ return array(
|
|||
'javelin-behavior',
|
||||
'javelin-uri',
|
||||
),
|
||||
'4193eeff' => array(
|
||||
'phui-inline-comment-view-css',
|
||||
),
|
||||
'4234f572' => array(
|
||||
'syntax-default-css',
|
||||
),
|
||||
|
@ -1345,6 +1348,9 @@ return array(
|
|||
'javelin-dom',
|
||||
'javelin-fx',
|
||||
),
|
||||
'534f1757' => array(
|
||||
'phui-oi-list-view-css',
|
||||
),
|
||||
'541f81c3' => array(
|
||||
'javelin-install',
|
||||
),
|
||||
|
@ -1721,9 +1727,6 @@ return array(
|
|||
'javelin-uri',
|
||||
'phabricator-textareautils',
|
||||
),
|
||||
'9e037c7a' => array(
|
||||
'phui-oi-list-view-css',
|
||||
),
|
||||
'9f081f05' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
|
@ -1997,9 +2000,6 @@ return array(
|
|||
'javelin-util',
|
||||
'phabricator-shaped-request',
|
||||
),
|
||||
'd92bed0d' => array(
|
||||
'phui-inline-comment-view-css',
|
||||
),
|
||||
'da15d3dc' => array(
|
||||
'phui-oi-list-view-css',
|
||||
),
|
||||
|
|
|
@ -1,11 +1,3 @@
|
|||
<?php
|
||||
|
||||
$table = new PhabricatorOwnersPackage();
|
||||
|
||||
foreach (new LiskMigrationIterator($table) as $package) {
|
||||
PhabricatorSearchWorker::queueDocumentForIndexing(
|
||||
$package->getPHID(),
|
||||
array(
|
||||
'force' => true,
|
||||
));
|
||||
}
|
||||
// This was an old reindexing migration that has been obsoleted. See T13253.
|
||||
|
|
|
@ -1,11 +1,3 @@
|
|||
<?php
|
||||
|
||||
$table = new AlmanacDevice();
|
||||
|
||||
foreach (new LiskMigrationIterator($table) as $device) {
|
||||
PhabricatorSearchWorker::queueDocumentForIndexing(
|
||||
$device->getPHID(),
|
||||
array(
|
||||
'force' => true,
|
||||
));
|
||||
}
|
||||
// This was an old reindexing migration that has been obsoleted. See T13253.
|
||||
|
|
|
@ -1,11 +1,3 @@
|
|||
<?php
|
||||
|
||||
$table = new AlmanacService();
|
||||
|
||||
foreach (new LiskMigrationIterator($table) as $service) {
|
||||
PhabricatorSearchWorker::queueDocumentForIndexing(
|
||||
$service->getPHID(),
|
||||
array(
|
||||
'force' => true,
|
||||
));
|
||||
}
|
||||
// This was an old reindexing migration that has been obsoleted. See T13253.
|
||||
|
|
|
@ -1,11 +1,3 @@
|
|||
<?php
|
||||
|
||||
$table = new AlmanacNetwork();
|
||||
|
||||
foreach (new LiskMigrationIterator($table) as $network) {
|
||||
PhabricatorSearchWorker::queueDocumentForIndexing(
|
||||
$network->getPHID(),
|
||||
array(
|
||||
'force' => true,
|
||||
));
|
||||
}
|
||||
// This was an old reindexing migration that has been obsoleted. See T13253.
|
||||
|
|
|
@ -1,11 +1,3 @@
|
|||
<?php
|
||||
|
||||
$table = new HarbormasterBuildPlan();
|
||||
|
||||
foreach (new LiskMigrationIterator($table) as $plan) {
|
||||
PhabricatorSearchWorker::queueDocumentForIndexing(
|
||||
$plan->getPHID(),
|
||||
array(
|
||||
'force' => true,
|
||||
));
|
||||
}
|
||||
// This was an old reindexing migration that has been obsoleted. See T13253.
|
||||
|
|
|
@ -1,11 +1,3 @@
|
|||
<?php
|
||||
|
||||
$table = new DrydockBlueprint();
|
||||
|
||||
foreach (new LiskMigrationIterator($table) as $blueprint) {
|
||||
PhabricatorSearchWorker::queueDocumentForIndexing(
|
||||
$blueprint->getPHID(),
|
||||
array(
|
||||
'force' => true,
|
||||
));
|
||||
}
|
||||
// This was an old reindexing migration that has been obsoleted. See T13253.
|
||||
|
|
|
@ -1,11 +1,3 @@
|
|||
<?php
|
||||
|
||||
$table = new NuanceSource();
|
||||
|
||||
foreach (new LiskMigrationIterator($table) as $source) {
|
||||
PhabricatorSearchWorker::queueDocumentForIndexing(
|
||||
$source->getPHID(),
|
||||
array(
|
||||
'force' => true,
|
||||
));
|
||||
}
|
||||
// This was an old reindexing migration that has been obsoleted. See T13253.
|
||||
|
|
|
@ -1,11 +1,3 @@
|
|||
<?php
|
||||
|
||||
$table = new PhabricatorBadgesBadge();
|
||||
|
||||
foreach (new LiskMigrationIterator($table) as $badge) {
|
||||
PhabricatorSearchWorker::queueDocumentForIndexing(
|
||||
$badge->getPHID(),
|
||||
array(
|
||||
'force' => true,
|
||||
));
|
||||
}
|
||||
// This was an old reindexing migration that has been obsoleted. See T13253.
|
||||
|
|
|
@ -1,11 +1,3 @@
|
|||
<?php
|
||||
|
||||
$table = new PhabricatorPhurlURL();
|
||||
|
||||
foreach (new LiskMigrationIterator($table) as $url) {
|
||||
PhabricatorSearchWorker::queueDocumentForIndexing(
|
||||
$url->getPHID(),
|
||||
array(
|
||||
'force' => true,
|
||||
));
|
||||
}
|
||||
// This was an old reindexing migration that has been obsoleted. See T13253.
|
||||
|
|
|
@ -1,11 +1,3 @@
|
|||
<?php
|
||||
|
||||
$table = new ConpherenceThread();
|
||||
|
||||
foreach (new LiskMigrationIterator($table) as $thread) {
|
||||
PhabricatorSearchWorker::queueDocumentForIndexing(
|
||||
$thread->getPHID(),
|
||||
array(
|
||||
'force' => true,
|
||||
));
|
||||
}
|
||||
// This was an old reindexing migration that has been obsoleted. See T13253.
|
||||
|
|
|
@ -1,21 +1,3 @@
|
|||
<?php
|
||||
|
||||
$table_db = new PhabricatorDashboard();
|
||||
|
||||
foreach (new LiskMigrationIterator($table_db) as $dashboard) {
|
||||
PhabricatorSearchWorker::queueDocumentForIndexing(
|
||||
$dashboard->getPHID(),
|
||||
array(
|
||||
'force' => true,
|
||||
));
|
||||
}
|
||||
|
||||
$table_dbp = new PhabricatorDashboardPanel();
|
||||
|
||||
foreach (new LiskMigrationIterator($table_dbp) as $panel) {
|
||||
PhabricatorSearchWorker::queueDocumentForIndexing(
|
||||
$panel->getPHID(),
|
||||
array(
|
||||
'force' => true,
|
||||
));
|
||||
}
|
||||
// This was an old reindexing migration that has been obsoleted. See T13253.
|
||||
|
|
|
@ -1,11 +1,3 @@
|
|||
<?php
|
||||
|
||||
$table = new PhabricatorProject();
|
||||
|
||||
foreach (new LiskMigrationIterator($table) as $project) {
|
||||
PhabricatorSearchWorker::queueDocumentForIndexing(
|
||||
$project->getPHID(),
|
||||
array(
|
||||
'force' => true,
|
||||
));
|
||||
}
|
||||
// This was an old reindexing migration that has been obsoleted. See T13253.
|
||||
|
|
|
@ -1,11 +1,3 @@
|
|||
<?php
|
||||
|
||||
$table = new PonderQuestion();
|
||||
|
||||
foreach (new LiskMigrationIterator($table) as $question) {
|
||||
PhabricatorSearchWorker::queueDocumentForIndexing(
|
||||
$question->getPHID(),
|
||||
array(
|
||||
'force' => true,
|
||||
));
|
||||
}
|
||||
// This was an old reindexing migration that has been obsoleted. See T13253.
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_harbormaster.harbormaster_buildplan
|
||||
ADD properties LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT};
|
|
@ -0,0 +1,2 @@
|
|||
UPDATE {$NAMESPACE}_harbormaster.harbormaster_buildplan
|
||||
SET properties = '{}' WHERE properties = '';
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE {$NAMESPACE}_herald.herald_ruletransaction_comment;
|
|
@ -653,6 +653,7 @@ phutil_register_library_map(array(
|
|||
'DifferentialRevisionUpdateTransaction' => 'applications/differential/xaction/DifferentialRevisionUpdateTransaction.php',
|
||||
'DifferentialRevisionViewController' => 'applications/differential/controller/DifferentialRevisionViewController.php',
|
||||
'DifferentialRevisionVoidTransaction' => 'applications/differential/xaction/DifferentialRevisionVoidTransaction.php',
|
||||
'DifferentialRevisionWrongBuildsTransaction' => 'applications/differential/xaction/DifferentialRevisionWrongBuildsTransaction.php',
|
||||
'DifferentialRevisionWrongStateTransaction' => 'applications/differential/xaction/DifferentialRevisionWrongStateTransaction.php',
|
||||
'DifferentialSchemaSpec' => 'applications/differential/storage/DifferentialSchemaSpec.php',
|
||||
'DifferentialSetDiffPropertyConduitAPIMethod' => 'applications/differential/conduit/DifferentialSetDiffPropertyConduitAPIMethod.php',
|
||||
|
@ -1328,18 +1329,26 @@ phutil_register_library_map(array(
|
|||
'HarbormasterBuildMessageQuery' => 'applications/harbormaster/query/HarbormasterBuildMessageQuery.php',
|
||||
'HarbormasterBuildPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildPHIDType.php',
|
||||
'HarbormasterBuildPlan' => 'applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php',
|
||||
'HarbormasterBuildPlanBehavior' => 'applications/harbormaster/plan/HarbormasterBuildPlanBehavior.php',
|
||||
'HarbormasterBuildPlanBehaviorOption' => 'applications/harbormaster/plan/HarbormasterBuildPlanBehaviorOption.php',
|
||||
'HarbormasterBuildPlanBehaviorTransaction' => 'applications/harbormaster/xaction/plan/HarbormasterBuildPlanBehaviorTransaction.php',
|
||||
'HarbormasterBuildPlanDatasource' => 'applications/harbormaster/typeahead/HarbormasterBuildPlanDatasource.php',
|
||||
'HarbormasterBuildPlanDefaultEditCapability' => 'applications/harbormaster/capability/HarbormasterBuildPlanDefaultEditCapability.php',
|
||||
'HarbormasterBuildPlanDefaultViewCapability' => 'applications/harbormaster/capability/HarbormasterBuildPlanDefaultViewCapability.php',
|
||||
'HarbormasterBuildPlanEditAPIMethod' => 'applications/harbormaster/conduit/HarbormasterBuildPlanEditAPIMethod.php',
|
||||
'HarbormasterBuildPlanEditEngine' => 'applications/harbormaster/editor/HarbormasterBuildPlanEditEngine.php',
|
||||
'HarbormasterBuildPlanEditor' => 'applications/harbormaster/editor/HarbormasterBuildPlanEditor.php',
|
||||
'HarbormasterBuildPlanNameNgrams' => 'applications/harbormaster/storage/configuration/HarbormasterBuildPlanNameNgrams.php',
|
||||
'HarbormasterBuildPlanNameTransaction' => 'applications/harbormaster/xaction/plan/HarbormasterBuildPlanNameTransaction.php',
|
||||
'HarbormasterBuildPlanPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildPlanPHIDType.php',
|
||||
'HarbormasterBuildPlanPolicyCodex' => 'applications/harbormaster/codex/HarbormasterBuildPlanPolicyCodex.php',
|
||||
'HarbormasterBuildPlanQuery' => 'applications/harbormaster/query/HarbormasterBuildPlanQuery.php',
|
||||
'HarbormasterBuildPlanSearchAPIMethod' => 'applications/harbormaster/conduit/HarbormasterBuildPlanSearchAPIMethod.php',
|
||||
'HarbormasterBuildPlanSearchEngine' => 'applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php',
|
||||
'HarbormasterBuildPlanStatusTransaction' => 'applications/harbormaster/xaction/plan/HarbormasterBuildPlanStatusTransaction.php',
|
||||
'HarbormasterBuildPlanTransaction' => 'applications/harbormaster/storage/configuration/HarbormasterBuildPlanTransaction.php',
|
||||
'HarbormasterBuildPlanTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildPlanTransactionQuery.php',
|
||||
'HarbormasterBuildPlanTransactionType' => 'applications/harbormaster/xaction/plan/HarbormasterBuildPlanTransactionType.php',
|
||||
'HarbormasterBuildQuery' => 'applications/harbormaster/query/HarbormasterBuildQuery.php',
|
||||
'HarbormasterBuildRequest' => 'applications/harbormaster/engine/HarbormasterBuildRequest.php',
|
||||
'HarbormasterBuildSearchConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterBuildSearchConduitAPIMethod.php',
|
||||
|
@ -1366,6 +1375,7 @@ phutil_register_library_map(array(
|
|||
'HarbormasterBuildTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildTransactionQuery.php',
|
||||
'HarbormasterBuildUnitMessage' => 'applications/harbormaster/storage/build/HarbormasterBuildUnitMessage.php',
|
||||
'HarbormasterBuildUnitMessageQuery' => 'applications/harbormaster/query/HarbormasterBuildUnitMessageQuery.php',
|
||||
'HarbormasterBuildView' => 'applications/harbormaster/view/HarbormasterBuildView.php',
|
||||
'HarbormasterBuildViewController' => 'applications/harbormaster/controller/HarbormasterBuildViewController.php',
|
||||
'HarbormasterBuildWorker' => 'applications/harbormaster/worker/HarbormasterBuildWorker.php',
|
||||
'HarbormasterBuildable' => 'applications/harbormaster/storage/HarbormasterBuildable.php',
|
||||
|
@ -1419,6 +1429,7 @@ phutil_register_library_map(array(
|
|||
'HarbormasterMessageType' => 'applications/harbormaster/engine/HarbormasterMessageType.php',
|
||||
'HarbormasterObject' => 'applications/harbormaster/storage/HarbormasterObject.php',
|
||||
'HarbormasterOtherBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterOtherBuildStepGroup.php',
|
||||
'HarbormasterPlanBehaviorController' => 'applications/harbormaster/controller/HarbormasterPlanBehaviorController.php',
|
||||
'HarbormasterPlanController' => 'applications/harbormaster/controller/HarbormasterPlanController.php',
|
||||
'HarbormasterPlanDisableController' => 'applications/harbormaster/controller/HarbormasterPlanDisableController.php',
|
||||
'HarbormasterPlanEditController' => 'applications/harbormaster/controller/HarbormasterPlanEditController.php',
|
||||
|
@ -1432,6 +1443,7 @@ phutil_register_library_map(array(
|
|||
'HarbormasterQueryBuildsConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterQueryBuildsConduitAPIMethod.php',
|
||||
'HarbormasterQueryBuildsSearchEngineAttachment' => 'applications/harbormaster/engineextension/HarbormasterQueryBuildsSearchEngineAttachment.php',
|
||||
'HarbormasterRemarkupRule' => 'applications/harbormaster/remarkup/HarbormasterRemarkupRule.php',
|
||||
'HarbormasterRestartException' => 'applications/harbormaster/exception/HarbormasterRestartException.php',
|
||||
'HarbormasterRunBuildPlansHeraldAction' => 'applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php',
|
||||
'HarbormasterSchemaSpec' => 'applications/harbormaster/storage/HarbormasterSchemaSpec.php',
|
||||
'HarbormasterScratchTable' => 'applications/harbormaster/storage/HarbormasterScratchTable.php',
|
||||
|
@ -1520,14 +1532,20 @@ phutil_register_library_map(array(
|
|||
'HeraldRemarkupFieldValue' => 'applications/herald/value/HeraldRemarkupFieldValue.php',
|
||||
'HeraldRemarkupRule' => 'applications/herald/remarkup/HeraldRemarkupRule.php',
|
||||
'HeraldRule' => 'applications/herald/storage/HeraldRule.php',
|
||||
'HeraldRuleActionAffectsObjectEdgeType' => 'applications/herald/edge/HeraldRuleActionAffectsObjectEdgeType.php',
|
||||
'HeraldRuleAdapter' => 'applications/herald/adapter/HeraldRuleAdapter.php',
|
||||
'HeraldRuleAdapterField' => 'applications/herald/field/rule/HeraldRuleAdapterField.php',
|
||||
'HeraldRuleController' => 'applications/herald/controller/HeraldRuleController.php',
|
||||
'HeraldRuleDatasource' => 'applications/herald/typeahead/HeraldRuleDatasource.php',
|
||||
'HeraldRuleDisableTransaction' => 'applications/herald/xaction/HeraldRuleDisableTransaction.php',
|
||||
'HeraldRuleEditTransaction' => 'applications/herald/xaction/HeraldRuleEditTransaction.php',
|
||||
'HeraldRuleEditor' => 'applications/herald/editor/HeraldRuleEditor.php',
|
||||
'HeraldRuleField' => 'applications/herald/field/rule/HeraldRuleField.php',
|
||||
'HeraldRuleFieldGroup' => 'applications/herald/field/rule/HeraldRuleFieldGroup.php',
|
||||
'HeraldRuleIndexEngineExtension' => 'applications/herald/engineextension/HeraldRuleIndexEngineExtension.php',
|
||||
'HeraldRuleListController' => 'applications/herald/controller/HeraldRuleListController.php',
|
||||
'HeraldRuleListView' => 'applications/herald/view/HeraldRuleListView.php',
|
||||
'HeraldRuleNameTransaction' => 'applications/herald/xaction/HeraldRuleNameTransaction.php',
|
||||
'HeraldRulePHIDType' => 'applications/herald/phid/HeraldRulePHIDType.php',
|
||||
'HeraldRuleQuery' => 'applications/herald/query/HeraldRuleQuery.php',
|
||||
'HeraldRuleReplyHandler' => 'applications/herald/mail/HeraldRuleReplyHandler.php',
|
||||
|
@ -1535,7 +1553,7 @@ phutil_register_library_map(array(
|
|||
'HeraldRuleSerializer' => 'applications/herald/editor/HeraldRuleSerializer.php',
|
||||
'HeraldRuleTestCase' => 'applications/herald/storage/__tests__/HeraldRuleTestCase.php',
|
||||
'HeraldRuleTransaction' => 'applications/herald/storage/HeraldRuleTransaction.php',
|
||||
'HeraldRuleTransactionComment' => 'applications/herald/storage/HeraldRuleTransactionComment.php',
|
||||
'HeraldRuleTransactionType' => 'applications/herald/xaction/HeraldRuleTransactionType.php',
|
||||
'HeraldRuleTranscript' => 'applications/herald/storage/transcript/HeraldRuleTranscript.php',
|
||||
'HeraldRuleTypeConfig' => 'applications/herald/config/HeraldRuleTypeConfig.php',
|
||||
'HeraldRuleTypeDatasource' => 'applications/herald/typeahead/HeraldRuleTypeDatasource.php',
|
||||
|
@ -1770,6 +1788,7 @@ phutil_register_library_map(array(
|
|||
'ManiphestTaskTitleTransaction' => 'applications/maniphest/xaction/ManiphestTaskTitleTransaction.php',
|
||||
'ManiphestTaskTransactionType' => 'applications/maniphest/xaction/ManiphestTaskTransactionType.php',
|
||||
'ManiphestTaskUnblockTransaction' => 'applications/maniphest/xaction/ManiphestTaskUnblockTransaction.php',
|
||||
'ManiphestTaskUnlockEngine' => 'applications/maniphest/engine/ManiphestTaskUnlockEngine.php',
|
||||
'ManiphestTransaction' => 'applications/maniphest/storage/ManiphestTransaction.php',
|
||||
'ManiphestTransactionComment' => 'applications/maniphest/storage/ManiphestTransactionComment.php',
|
||||
'ManiphestTransactionEditor' => 'applications/maniphest/editor/ManiphestTransactionEditor.php',
|
||||
|
@ -2960,6 +2979,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorDebugController' => 'applications/system/controller/PhabricatorDebugController.php',
|
||||
'PhabricatorDefaultRequestExceptionHandler' => 'aphront/handler/PhabricatorDefaultRequestExceptionHandler.php',
|
||||
'PhabricatorDefaultSyntaxStyle' => 'infrastructure/syntax/PhabricatorDefaultSyntaxStyle.php',
|
||||
'PhabricatorDefaultUnlockEngine' => 'applications/system/engine/PhabricatorDefaultUnlockEngine.php',
|
||||
'PhabricatorDestructibleCodex' => 'applications/system/codex/PhabricatorDestructibleCodex.php',
|
||||
'PhabricatorDestructibleCodexInterface' => 'applications/system/interface/PhabricatorDestructibleCodexInterface.php',
|
||||
'PhabricatorDestructibleInterface' => 'applications/system/interface/PhabricatorDestructibleInterface.php',
|
||||
|
@ -4687,6 +4707,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorUnitTestContentSource' => 'infrastructure/contentsource/PhabricatorUnitTestContentSource.php',
|
||||
'PhabricatorUnitsTestCase' => 'view/__tests__/PhabricatorUnitsTestCase.php',
|
||||
'PhabricatorUnknownContentSource' => 'infrastructure/contentsource/PhabricatorUnknownContentSource.php',
|
||||
'PhabricatorUnlockEngine' => 'applications/system/engine/PhabricatorUnlockEngine.php',
|
||||
'PhabricatorUnlockableInterface' => 'applications/system/interface/PhabricatorUnlockableInterface.php',
|
||||
'PhabricatorUnsubscribedFromObjectEdgeType' => 'applications/transactions/edges/PhabricatorUnsubscribedFromObjectEdgeType.php',
|
||||
'PhabricatorUser' => 'applications/people/storage/PhabricatorUser.php',
|
||||
'PhabricatorUserApproveTransaction' => 'applications/people/xaction/PhabricatorUserApproveTransaction.php',
|
||||
|
@ -6171,6 +6193,7 @@ phutil_register_library_map(array(
|
|||
'DifferentialRevisionUpdateTransaction' => 'DifferentialRevisionTransactionType',
|
||||
'DifferentialRevisionViewController' => 'DifferentialController',
|
||||
'DifferentialRevisionVoidTransaction' => 'DifferentialRevisionTransactionType',
|
||||
'DifferentialRevisionWrongBuildsTransaction' => 'DifferentialRevisionTransactionType',
|
||||
'DifferentialRevisionWrongStateTransaction' => 'DifferentialRevisionTransactionType',
|
||||
'DifferentialSchemaSpec' => 'PhabricatorConfigSchemaSpec',
|
||||
'DifferentialSetDiffPropertyConduitAPIMethod' => 'DifferentialConduitAPIMethod',
|
||||
|
@ -6936,19 +6959,28 @@ phutil_register_library_map(array(
|
|||
'PhabricatorNgramsInterface',
|
||||
'PhabricatorConduitResultInterface',
|
||||
'PhabricatorProjectInterface',
|
||||
'PhabricatorPolicyCodexInterface',
|
||||
),
|
||||
'HarbormasterBuildPlanBehavior' => 'Phobject',
|
||||
'HarbormasterBuildPlanBehaviorOption' => 'Phobject',
|
||||
'HarbormasterBuildPlanBehaviorTransaction' => 'HarbormasterBuildPlanTransactionType',
|
||||
'HarbormasterBuildPlanDatasource' => 'PhabricatorTypeaheadDatasource',
|
||||
'HarbormasterBuildPlanDefaultEditCapability' => 'PhabricatorPolicyCapability',
|
||||
'HarbormasterBuildPlanDefaultViewCapability' => 'PhabricatorPolicyCapability',
|
||||
'HarbormasterBuildPlanEditAPIMethod' => 'PhabricatorEditEngineAPIMethod',
|
||||
'HarbormasterBuildPlanEditEngine' => 'PhabricatorEditEngine',
|
||||
'HarbormasterBuildPlanEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||
'HarbormasterBuildPlanNameNgrams' => 'PhabricatorSearchNgrams',
|
||||
'HarbormasterBuildPlanNameTransaction' => 'HarbormasterBuildPlanTransactionType',
|
||||
'HarbormasterBuildPlanPHIDType' => 'PhabricatorPHIDType',
|
||||
'HarbormasterBuildPlanPolicyCodex' => 'PhabricatorPolicyCodex',
|
||||
'HarbormasterBuildPlanQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'HarbormasterBuildPlanSearchAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
|
||||
'HarbormasterBuildPlanSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||
'HarbormasterBuildPlanTransaction' => 'PhabricatorApplicationTransaction',
|
||||
'HarbormasterBuildPlanStatusTransaction' => 'HarbormasterBuildPlanTransactionType',
|
||||
'HarbormasterBuildPlanTransaction' => 'PhabricatorModularTransaction',
|
||||
'HarbormasterBuildPlanTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||
'HarbormasterBuildPlanTransactionType' => 'PhabricatorModularTransactionType',
|
||||
'HarbormasterBuildQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'HarbormasterBuildRequest' => 'Phobject',
|
||||
'HarbormasterBuildSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
|
||||
|
@ -6991,6 +7023,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorPolicyInterface',
|
||||
),
|
||||
'HarbormasterBuildUnitMessageQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'HarbormasterBuildView' => 'AphrontView',
|
||||
'HarbormasterBuildViewController' => 'HarbormasterController',
|
||||
'HarbormasterBuildWorker' => 'HarbormasterWorker',
|
||||
'HarbormasterBuildable' => array(
|
||||
|
@ -7047,6 +7080,7 @@ phutil_register_library_map(array(
|
|||
'HarbormasterMessageType' => 'Phobject',
|
||||
'HarbormasterObject' => 'HarbormasterDAO',
|
||||
'HarbormasterOtherBuildStepGroup' => 'HarbormasterBuildStepGroup',
|
||||
'HarbormasterPlanBehaviorController' => 'HarbormasterPlanController',
|
||||
'HarbormasterPlanController' => 'HarbormasterController',
|
||||
'HarbormasterPlanDisableController' => 'HarbormasterPlanController',
|
||||
'HarbormasterPlanEditController' => 'HarbormasterPlanController',
|
||||
|
@ -7060,6 +7094,7 @@ phutil_register_library_map(array(
|
|||
'HarbormasterQueryBuildsConduitAPIMethod' => 'HarbormasterConduitAPIMethod',
|
||||
'HarbormasterQueryBuildsSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
|
||||
'HarbormasterRemarkupRule' => 'PhabricatorObjectRemarkupRule',
|
||||
'HarbormasterRestartException' => 'Exception',
|
||||
'HarbormasterRunBuildPlansHeraldAction' => 'HeraldAction',
|
||||
'HarbormasterSchemaSpec' => 'PhabricatorConfigSchemaSpec',
|
||||
'HarbormasterScratchTable' => 'HarbormasterDAO',
|
||||
|
@ -7159,24 +7194,31 @@ phutil_register_library_map(array(
|
|||
'PhabricatorFlaggableInterface',
|
||||
'PhabricatorPolicyInterface',
|
||||
'PhabricatorDestructibleInterface',
|
||||
'PhabricatorIndexableInterface',
|
||||
'PhabricatorSubscribableInterface',
|
||||
),
|
||||
'HeraldRuleActionAffectsObjectEdgeType' => 'PhabricatorEdgeType',
|
||||
'HeraldRuleAdapter' => 'HeraldAdapter',
|
||||
'HeraldRuleAdapterField' => 'HeraldRuleField',
|
||||
'HeraldRuleController' => 'HeraldController',
|
||||
'HeraldRuleDatasource' => 'PhabricatorTypeaheadDatasource',
|
||||
'HeraldRuleDisableTransaction' => 'HeraldRuleTransactionType',
|
||||
'HeraldRuleEditTransaction' => 'HeraldRuleTransactionType',
|
||||
'HeraldRuleEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||
'HeraldRuleField' => 'HeraldField',
|
||||
'HeraldRuleFieldGroup' => 'HeraldFieldGroup',
|
||||
'HeraldRuleIndexEngineExtension' => 'PhabricatorIndexEngineExtension',
|
||||
'HeraldRuleListController' => 'HeraldController',
|
||||
'HeraldRuleListView' => 'AphrontView',
|
||||
'HeraldRuleNameTransaction' => 'HeraldRuleTransactionType',
|
||||
'HeraldRulePHIDType' => 'PhabricatorPHIDType',
|
||||
'HeraldRuleQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'HeraldRuleReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
|
||||
'HeraldRuleSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||
'HeraldRuleSerializer' => 'Phobject',
|
||||
'HeraldRuleTestCase' => 'PhabricatorTestCase',
|
||||
'HeraldRuleTransaction' => 'PhabricatorApplicationTransaction',
|
||||
'HeraldRuleTransactionComment' => 'PhabricatorApplicationTransactionComment',
|
||||
'HeraldRuleTransaction' => 'PhabricatorModularTransaction',
|
||||
'HeraldRuleTransactionType' => 'PhabricatorModularTransactionType',
|
||||
'HeraldRuleTranscript' => 'Phobject',
|
||||
'HeraldRuleTypeConfig' => 'Phobject',
|
||||
'HeraldRuleTypeDatasource' => 'PhabricatorTypeaheadDatasource',
|
||||
|
@ -7388,6 +7430,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorEditEngineLockableInterface',
|
||||
'PhabricatorEditEngineMFAInterface',
|
||||
'PhabricatorPolicyCodexInterface',
|
||||
'PhabricatorUnlockableInterface',
|
||||
),
|
||||
'ManiphestTaskAssignHeraldAction' => 'HeraldAction',
|
||||
'ManiphestTaskAssignOtherHeraldAction' => 'ManiphestTaskAssignHeraldAction',
|
||||
|
@ -7465,6 +7508,7 @@ phutil_register_library_map(array(
|
|||
'ManiphestTaskTitleTransaction' => 'ManiphestTaskTransactionType',
|
||||
'ManiphestTaskTransactionType' => 'PhabricatorModularTransactionType',
|
||||
'ManiphestTaskUnblockTransaction' => 'ManiphestTaskTransactionType',
|
||||
'ManiphestTaskUnlockEngine' => 'PhabricatorUnlockEngine',
|
||||
'ManiphestTransaction' => 'PhabricatorModularTransaction',
|
||||
'ManiphestTransactionComment' => 'PhabricatorApplicationTransactionComment',
|
||||
'ManiphestTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||
|
@ -8842,6 +8886,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorDebugController' => 'PhabricatorController',
|
||||
'PhabricatorDefaultRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
|
||||
'PhabricatorDefaultSyntaxStyle' => 'PhabricatorSyntaxStyle',
|
||||
'PhabricatorDefaultUnlockEngine' => 'PhabricatorUnlockEngine',
|
||||
'PhabricatorDestructibleCodex' => 'Phobject',
|
||||
'PhabricatorDestructionEngine' => 'Phobject',
|
||||
'PhabricatorDestructionEngineExtension' => 'Phobject',
|
||||
|
@ -10852,6 +10897,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorUnitTestContentSource' => 'PhabricatorContentSource',
|
||||
'PhabricatorUnitsTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorUnknownContentSource' => 'PhabricatorContentSource',
|
||||
'PhabricatorUnlockEngine' => 'Phobject',
|
||||
'PhabricatorUnsubscribedFromObjectEdgeType' => 'PhabricatorEdgeType',
|
||||
'PhabricatorUser' => array(
|
||||
'PhabricatorUserDAO',
|
||||
|
|
|
@ -591,15 +591,11 @@ final class AphrontRequest extends Phobject {
|
|||
}
|
||||
|
||||
public function getRequestURI() {
|
||||
$request_uri = idx($_SERVER, 'REQUEST_URI', '/');
|
||||
$uri_path = phutil_escape_uri($this->getPath());
|
||||
$uri_query = idx($_SERVER, 'QUERY_STRING', '');
|
||||
|
||||
$uri = new PhutilURI($request_uri);
|
||||
$uri->removeQueryParam('__path__');
|
||||
|
||||
$path = phutil_escape_uri($this->getPath());
|
||||
$uri->setPath($path);
|
||||
|
||||
return $uri;
|
||||
return id(new PhutilURI($uri_path.'?'.$uri_query))
|
||||
->removeQueryParam('__path__');
|
||||
}
|
||||
|
||||
public function getAbsoluteRequestURI() {
|
||||
|
|
|
@ -776,7 +776,6 @@ final class AphrontApplicationConfiguration
|
|||
'filler' => str_repeat('Q', 1024 * 16),
|
||||
);
|
||||
|
||||
|
||||
return id(new AphrontJSONResponse())
|
||||
->setAddJSONShield(false)
|
||||
->setContent($result);
|
||||
|
|
|
@ -147,7 +147,7 @@ final class PhabricatorAuthInviteEngine extends Phobject {
|
|||
// no address. Users can use password recovery to access the other
|
||||
// account if they really control the address.
|
||||
throw id(new PhabricatorAuthInviteAccountException(
|
||||
pht('Wrong Acount'),
|
||||
pht('Wrong Account'),
|
||||
pht(
|
||||
'You are logged in as %s, but the email address you just '.
|
||||
'clicked a link from is already the primary email address '.
|
||||
|
|
|
@ -714,7 +714,14 @@ final class PhabricatorAuthSessionEngine extends Phobject {
|
|||
if (isset($validation_results[$factor_phid])) {
|
||||
continue;
|
||||
}
|
||||
$validation_results[$factor_phid] = new PhabricatorAuthFactorResult();
|
||||
|
||||
$issued_challenges = idx($challenge_map, $factor_phid, array());
|
||||
|
||||
$validation_results[$factor_phid] = $impl->getResultForPrompt(
|
||||
$factor,
|
||||
$viewer,
|
||||
$request,
|
||||
$issued_challenges);
|
||||
}
|
||||
|
||||
throw id(new PhabricatorAuthHighSecurityRequiredException())
|
||||
|
|
|
@ -221,6 +221,40 @@ abstract class PhabricatorAuthFactor extends Phobject {
|
|||
return $result;
|
||||
}
|
||||
|
||||
final public function getResultForPrompt(
|
||||
PhabricatorAuthFactorConfig $config,
|
||||
PhabricatorUser $viewer,
|
||||
AphrontRequest $request,
|
||||
array $challenges) {
|
||||
assert_instances_of($challenges, 'PhabricatorAuthChallenge');
|
||||
|
||||
$result = $this->newResultForPrompt(
|
||||
$config,
|
||||
$viewer,
|
||||
$request,
|
||||
$challenges);
|
||||
|
||||
if (!$this->isAuthResult($result)) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Expected "newResultForPrompt()" to return an object of class "%s", '.
|
||||
'but it returned something else ("%s"; in "%s").',
|
||||
'PhabricatorAuthFactorResult',
|
||||
phutil_describe_type($result),
|
||||
get_class($this)));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function newResultForPrompt(
|
||||
PhabricatorAuthFactorConfig $config,
|
||||
PhabricatorUser $viewer,
|
||||
AphrontRequest $request,
|
||||
array $challenges) {
|
||||
return $this->newResult();
|
||||
}
|
||||
|
||||
abstract protected function newResultFromIssuedChallenges(
|
||||
PhabricatorAuthFactorConfig $config,
|
||||
PhabricatorUser $viewer,
|
||||
|
|
|
@ -681,6 +681,19 @@ final class PhabricatorDuoAuthFactor
|
|||
AphrontRequest $request,
|
||||
array $challenges) {
|
||||
|
||||
return $this->getResultForPrompt(
|
||||
$config,
|
||||
$viewer,
|
||||
$request,
|
||||
$challenges);
|
||||
}
|
||||
|
||||
protected function newResultForPrompt(
|
||||
PhabricatorAuthFactorConfig $config,
|
||||
PhabricatorUser $viewer,
|
||||
AphrontRequest $request,
|
||||
array $challenges) {
|
||||
|
||||
$result = $this->newResult()
|
||||
->setIsContinue(true)
|
||||
->setErrorMessage(
|
||||
|
|
|
@ -58,6 +58,10 @@ final class PhabricatorConduitCallManagementWorkflow
|
|||
'No such user "%s" exists.',
|
||||
$as));
|
||||
}
|
||||
|
||||
// Allow inline generation of user caches for the user we're acting
|
||||
// as, since some calls may read user preferences.
|
||||
$actor->setAllowInlineCacheGeneration(true);
|
||||
} else {
|
||||
$actor = $viewer;
|
||||
}
|
||||
|
|
|
@ -409,4 +409,19 @@ abstract class ConduitAPIMethod
|
|||
$capability);
|
||||
}
|
||||
|
||||
final protected function newRemarkupDocumentationView($remarkup) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$view = new PHUIRemarkupView($viewer, $remarkup);
|
||||
|
||||
$view->setRemarkupOptions(
|
||||
array(
|
||||
PHUIRemarkupView::OPTION_PRESERVE_LINEBREAKS => false,
|
||||
));
|
||||
|
||||
return id(new PHUIBoxView())
|
||||
->appendChild($view)
|
||||
->addPadding(PHUI::PADDING_LARGE);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -129,30 +129,16 @@ final class PhabricatorWebServerSetupCheck extends PhabricatorSetupCheck {
|
|||
}
|
||||
|
||||
$structure = null;
|
||||
$caught = null;
|
||||
$extra_whitespace = ($body !== trim($body));
|
||||
|
||||
if (!$extra_whitespace) {
|
||||
try {
|
||||
$structure = phutil_json_decode($body);
|
||||
} catch (Exception $ex) {
|
||||
$caught = $ex;
|
||||
}
|
||||
try {
|
||||
$structure = phutil_json_decode(trim($body));
|
||||
} catch (Exception $ex) {
|
||||
// Ignore the exception, we only care if the decode worked or not.
|
||||
}
|
||||
|
||||
if (!$structure) {
|
||||
if ($extra_whitespace) {
|
||||
$message = pht(
|
||||
'Phabricator sent itself a test request and expected to get a bare '.
|
||||
'JSON response back, but the response had extra whitespace at '.
|
||||
'the beginning or end.'.
|
||||
"\n\n".
|
||||
'This usually means you have edited a file and left whitespace '.
|
||||
'characters before the opening %s tag, or after a closing %s tag. '.
|
||||
'Remove any leading whitespace, and prefer to omit closing tags.',
|
||||
phutil_tag('tt', array(), '<?php'),
|
||||
phutil_tag('tt', array(), '?>'));
|
||||
} else {
|
||||
if (!$structure || $extra_whitespace) {
|
||||
if (!$structure) {
|
||||
$short = id(new PhutilUTF8StringTruncator())
|
||||
->setMaximumGlyphs(1024)
|
||||
->truncateString($body);
|
||||
|
@ -166,6 +152,17 @@ final class PhabricatorWebServerSetupCheck extends PhabricatorSetupCheck {
|
|||
"\n\n".
|
||||
'Something is misconfigured or otherwise mangling responses.',
|
||||
phutil_tag('pre', array(), $short));
|
||||
} else {
|
||||
$message = pht(
|
||||
'Phabricator sent itself a test request and expected to get a bare '.
|
||||
'JSON response back. It received a JSON response, but the response '.
|
||||
'had extra whitespace at the beginning or end.'.
|
||||
"\n\n".
|
||||
'This usually means you have edited a file and left whitespace '.
|
||||
'characters before the opening %s tag, or after a closing %s tag. '.
|
||||
'Remove any leading whitespace, and prefer to omit closing tags.',
|
||||
phutil_tag('tt', array(), '<?php'),
|
||||
phutil_tag('tt', array(), '?>'));
|
||||
}
|
||||
|
||||
$this->newIssue('webserver.mangle')
|
||||
|
@ -174,7 +171,9 @@ final class PhabricatorWebServerSetupCheck extends PhabricatorSetupCheck {
|
|||
->setMessage($message);
|
||||
|
||||
// We can't run the other checks if we could not decode the response.
|
||||
return;
|
||||
if (!$structure) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$actual_user = idx($structure, 'user');
|
||||
|
|
|
@ -43,7 +43,7 @@ final class PhabricatorSetConfigType
|
|||
}
|
||||
|
||||
if ($value) {
|
||||
if (array_keys($value) !== range(0, count($value) - 1)) {
|
||||
if (!phutil_is_natural_list($value)) {
|
||||
throw $this->newException(
|
||||
pht(
|
||||
'Option "%s" is of type "%s", and should be specified on the '.
|
||||
|
|
|
@ -204,9 +204,9 @@ final class DifferentialInlineCommentEditController
|
|||
|
||||
queryfx(
|
||||
$conn_w,
|
||||
'INSERT IGNORE INTO %T (userPHID, commentID) VALUES %Q',
|
||||
'INSERT IGNORE INTO %T (userPHID, commentID) VALUES %LQ',
|
||||
$table->getTableName(),
|
||||
implode(', ', $sql));
|
||||
$sql);
|
||||
}
|
||||
|
||||
protected function showComments(array $ids) {
|
||||
|
|
|
@ -208,15 +208,6 @@ final class DifferentialDiffEditor
|
|||
return $adapter;
|
||||
}
|
||||
|
||||
protected function didApplyHeraldRules(
|
||||
PhabricatorLiskDAO $object,
|
||||
HeraldAdapter $adapter,
|
||||
HeraldTranscript $transcript) {
|
||||
|
||||
$xactions = array();
|
||||
return $xactions;
|
||||
}
|
||||
|
||||
private function updateDiffFromDict(DifferentialDiff $diff, $dict) {
|
||||
$diff
|
||||
->setSourcePath(idx($dict, 'sourcePath'))
|
||||
|
|
|
@ -54,6 +54,12 @@ final class DifferentialChangesetEngine extends Phobject {
|
|||
if (strpos($new_data, '@'.'generated') !== false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// See PHI1112. This is the official pattern for marking Go code as
|
||||
// generated.
|
||||
if (preg_match('(^// Code generated .* DO NOT EDIT\.$)m', $new_data)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
|
@ -285,6 +285,24 @@ final class DifferentialDiffExtractionEngine extends Phobject {
|
|||
->setNewValue($revision->getModernRevisionStatus());
|
||||
}
|
||||
|
||||
$concerning_builds = $this->loadConcerningBuilds($revision);
|
||||
if ($concerning_builds) {
|
||||
$build_list = array();
|
||||
foreach ($concerning_builds as $build) {
|
||||
$build_list[] = array(
|
||||
'phid' => $build->getPHID(),
|
||||
'status' => $build->getBuildStatus(),
|
||||
);
|
||||
}
|
||||
|
||||
$wrong_builds =
|
||||
DifferentialRevisionWrongBuildsTransaction::TRANSACTIONTYPE;
|
||||
|
||||
$xactions[] = id(new DifferentialTransaction())
|
||||
->setTransactionType($wrong_builds)
|
||||
->setNewValue($build_list);
|
||||
}
|
||||
|
||||
$type_update = DifferentialRevisionUpdateTransaction::TRANSACTIONTYPE;
|
||||
|
||||
$xactions[] = id(new DifferentialTransaction())
|
||||
|
@ -322,4 +340,81 @@ final class DifferentialDiffExtractionEngine extends Phobject {
|
|||
return $result_data;
|
||||
}
|
||||
|
||||
private function loadConcerningBuilds(DifferentialRevision $revision) {
|
||||
$viewer = $this->getViewer();
|
||||
$diff = $revision->getActiveDiff();
|
||||
|
||||
$buildables = id(new HarbormasterBuildableQuery())
|
||||
->setViewer($viewer)
|
||||
->withBuildablePHIDs(array($diff->getPHID()))
|
||||
->needBuilds(true)
|
||||
->withManualBuildables(false)
|
||||
->execute();
|
||||
if (!$buildables) {
|
||||
return array();
|
||||
}
|
||||
|
||||
|
||||
$land_key = HarbormasterBuildPlanBehavior::BEHAVIOR_LANDWARNING;
|
||||
$behavior = HarbormasterBuildPlanBehavior::getBehavior($land_key);
|
||||
|
||||
$key_never = HarbormasterBuildPlanBehavior::LANDWARNING_NEVER;
|
||||
$key_building = HarbormasterBuildPlanBehavior::LANDWARNING_IF_BUILDING;
|
||||
$key_complete = HarbormasterBuildPlanBehavior::LANDWARNING_IF_COMPLETE;
|
||||
|
||||
$concerning_builds = array();
|
||||
foreach ($buildables as $buildable) {
|
||||
$builds = $buildable->getBuilds();
|
||||
foreach ($builds as $build) {
|
||||
$plan = $build->getBuildPlan();
|
||||
$option = $behavior->getPlanOption($plan);
|
||||
$behavior_value = $option->getKey();
|
||||
|
||||
$if_never = ($behavior_value === $key_never);
|
||||
if ($if_never) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$if_building = ($behavior_value === $key_building);
|
||||
if ($if_building && $build->isComplete()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$if_complete = ($behavior_value === $key_complete);
|
||||
if ($if_complete) {
|
||||
if (!$build->isComplete()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: If you "arc land" and a build with "Warn: If Complete"
|
||||
// is still running, you may not see a warning, and push the revision
|
||||
// in good faith. The build may then complete before we get here, so
|
||||
// we now see a completed, failed build.
|
||||
|
||||
// For now, just err on the side of caution and assume these builds
|
||||
// were in a good state when we prompted the user, even if they're in
|
||||
// a bad state now.
|
||||
|
||||
// We could refine this with a rule like "if the build finished
|
||||
// within a couple of minutes before the push happened, assume it was
|
||||
// in good faith", but we don't currently have an especially
|
||||
// convenient way to check when the build finished or when the commit
|
||||
// was pushed or discovered, and this would create some issues in
|
||||
// cases where the repository is observed and the fetch pipeline
|
||||
// stalls for a while.
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($build->isPassed()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$concerning_builds[] = $build;
|
||||
}
|
||||
}
|
||||
|
||||
return $concerning_builds;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -162,7 +162,20 @@ final class DifferentialChangesetTwoUpRenderer
|
|||
} else if (empty($new_lines[$ii])) {
|
||||
$o_class = 'old old-full';
|
||||
} else {
|
||||
$o_class = 'old';
|
||||
if (isset($depth_only[$ii])) {
|
||||
if ($depth_only[$ii] == '>') {
|
||||
// When a line has depth-only change, we only highlight the
|
||||
// left side of the diff if the depth is decreasing. When the
|
||||
// depth is increasing, the ">>" marker on the right hand side
|
||||
// of the diff generally provides enough visibility on its own.
|
||||
|
||||
$o_class = '';
|
||||
} else {
|
||||
$o_class = 'old';
|
||||
}
|
||||
} else {
|
||||
$o_class = 'old';
|
||||
}
|
||||
}
|
||||
$o_classes = $o_class;
|
||||
}
|
||||
|
@ -200,13 +213,10 @@ final class DifferentialChangesetTwoUpRenderer
|
|||
} else if (empty($old_lines[$ii])) {
|
||||
$n_class = 'new new-full';
|
||||
} else {
|
||||
|
||||
// NOTE: At least for the moment, I'm intentionally clearing the
|
||||
// line highlighting only on the right side of the diff when a
|
||||
// line has only depth changes. When a block depth is decreased,
|
||||
// this gives us a large color block on the left (to make it easy
|
||||
// to see the depth change) but a clean diff on the right (to make
|
||||
// it easy to pick out actual code changes).
|
||||
// When a line has a depth-only change, never highlight it on
|
||||
// the right side. The ">>" marker generally provides enough
|
||||
// visibility on its own for indent depth increases, and the left
|
||||
// side is still highlighted for indent depth decreases.
|
||||
|
||||
if (isset($depth_only[$ii])) {
|
||||
$n_class = '';
|
||||
|
|
|
@ -877,7 +877,7 @@ final class DifferentialRevision extends DifferentialDAO
|
|||
PhabricatorUser $viewer,
|
||||
array $phids) {
|
||||
|
||||
return id(new HarbormasterBuildQuery())
|
||||
$builds = id(new HarbormasterBuildQuery())
|
||||
->setViewer($viewer)
|
||||
->withBuildablePHIDs($phids)
|
||||
->withAutobuilds(false)
|
||||
|
@ -893,6 +893,41 @@ final class DifferentialRevision extends DifferentialDAO
|
|||
HarbormasterBuildStatus::STATUS_DEADLOCKED,
|
||||
))
|
||||
->execute();
|
||||
|
||||
// Filter builds based on the "Hold Drafts" behavior of their associated
|
||||
// build plans.
|
||||
|
||||
$hold_drafts = HarbormasterBuildPlanBehavior::BEHAVIOR_DRAFTS;
|
||||
$behavior = HarbormasterBuildPlanBehavior::getBehavior($hold_drafts);
|
||||
|
||||
$key_never = HarbormasterBuildPlanBehavior::DRAFTS_NEVER;
|
||||
$key_building = HarbormasterBuildPlanBehavior::DRAFTS_IF_BUILDING;
|
||||
|
||||
foreach ($builds as $key => $build) {
|
||||
$plan = $build->getBuildPlan();
|
||||
$hold_key = $behavior->getPlanOption($plan)->getKey();
|
||||
|
||||
$hold_never = ($hold_key === $key_never);
|
||||
$hold_building = ($hold_key === $key_building);
|
||||
|
||||
// If the build "Never" holds drafts from promoting, we don't care what
|
||||
// the status is.
|
||||
if ($hold_never) {
|
||||
unset($builds[$key]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the build holds drafts from promoting "While Building", we only
|
||||
// care about the status until it completes.
|
||||
if ($hold_building) {
|
||||
if ($build->isComplete()) {
|
||||
unset($builds[$key]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $builds;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
final class DifferentialRevisionWrongBuildsTransaction
|
||||
extends DifferentialRevisionTransactionType {
|
||||
|
||||
const TRANSACTIONTYPE = 'differential.builds.wrong';
|
||||
|
||||
public function generateOldValue($object) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public function generateNewValue($object, $value) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return 'fa-exclamation';
|
||||
}
|
||||
|
||||
public function getColor() {
|
||||
return 'pink';
|
||||
}
|
||||
|
||||
public function getActionStrength() {
|
||||
return 4;
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
return pht(
|
||||
'This revision was landed with ongoing or failed builds.');
|
||||
}
|
||||
|
||||
public function shouldHideForFeed() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -368,9 +368,9 @@ final class DiffusionBrowseQueryConduitAPIMethod
|
|||
}
|
||||
|
||||
if ($commit) {
|
||||
$slice_clause = 'AND svnCommit <= '.(int)$commit;
|
||||
$slice_clause = qsprintf($conn_r, 'AND svnCommit <= %d', $commit);
|
||||
} else {
|
||||
$slice_clause = '';
|
||||
$slice_clause = qsprintf($conn_r, '');
|
||||
}
|
||||
|
||||
$index = queryfx_all(
|
||||
|
@ -439,9 +439,11 @@ final class DiffusionBrowseQueryConduitAPIMethod
|
|||
|
||||
$sql = array();
|
||||
foreach ($index as $row) {
|
||||
$sql[] =
|
||||
'(pathID = '.(int)$row['pathID'].' AND '.
|
||||
'svnCommit = '.(int)$row['maxCommit'].')';
|
||||
$sql[] = qsprintf(
|
||||
$conn_r,
|
||||
'(pathID = %d AND svnCommit = %d)',
|
||||
$row['pathID'],
|
||||
$row['maxCommit']);
|
||||
}
|
||||
|
||||
$browse = queryfx_all(
|
||||
|
|
|
@ -215,13 +215,17 @@ final class DiffusionHistoryQueryConduitAPIMethod
|
|||
return array();
|
||||
}
|
||||
|
||||
$filter_query = '';
|
||||
$filter_query = qsprintf($conn_r, '');
|
||||
if ($need_direct_changes) {
|
||||
if ($need_child_changes) {
|
||||
$type = DifferentialChangeType::TYPE_CHILD;
|
||||
$filter_query = 'AND (isDirect = 1 OR changeType = '.$type.')';
|
||||
$filter_query = qsprintf(
|
||||
$conn_r,
|
||||
'AND (isDirect = 1 OR changeType = %s)',
|
||||
DifferentialChangeType::TYPE_CHILD);
|
||||
} else {
|
||||
$filter_query = 'AND (isDirect = 1)';
|
||||
$filter_query = qsprintf(
|
||||
$conn_r,
|
||||
'AND (isDirect = 1)');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -709,8 +709,17 @@ final class DiffusionBrowseController extends DiffusionController {
|
|||
'path' => $path,
|
||||
));
|
||||
|
||||
$before_uri = $before_uri->alter('renamed', $renamed);
|
||||
$before_uri = $before_uri->alter('follow', $follow);
|
||||
if ($renamed === null) {
|
||||
$before_uri->removeQueryParam('renamed');
|
||||
} else {
|
||||
$before_uri->replaceQueryParam('renamed', $renamed);
|
||||
}
|
||||
|
||||
if ($follow === null) {
|
||||
$before_uri->removeQueryParam('follow');
|
||||
} else {
|
||||
$before_uri->replaceQueryParam('follow', $follow);
|
||||
}
|
||||
|
||||
return id(new AphrontRedirectResponse())->setURI($before_uri);
|
||||
}
|
||||
|
|
|
@ -192,7 +192,10 @@ final class DiffusionServeController extends DiffusionController {
|
|||
// Try Git LFS auth first since we can usually reject it without doing
|
||||
// any queries, since the username won't match the one we expect or the
|
||||
// request won't be LFS.
|
||||
$viewer = $this->authenticateGitLFSUser($username, $password);
|
||||
$viewer = $this->authenticateGitLFSUser(
|
||||
$username,
|
||||
$password,
|
||||
$identifier);
|
||||
|
||||
// If that failed, try normal auth. Note that we can use normal auth on
|
||||
// LFS requests, so this isn't strictly an alternative to LFS auth.
|
||||
|
@ -655,7 +658,8 @@ final class DiffusionServeController extends DiffusionController {
|
|||
|
||||
private function authenticateGitLFSUser(
|
||||
$username,
|
||||
PhutilOpaqueEnvelope $password) {
|
||||
PhutilOpaqueEnvelope $password,
|
||||
$identifier) {
|
||||
|
||||
// Never accept these credentials for requests which aren't LFS requests.
|
||||
if (!$this->getIsGitLFSRequest()) {
|
||||
|
@ -668,11 +672,31 @@ final class DiffusionServeController extends DiffusionController {
|
|||
return null;
|
||||
}
|
||||
|
||||
// See PHI1123. We need to be able to constrain the token query with
|
||||
// "withTokenResources(...)" to take advantage of the key on the table.
|
||||
// In this case, the repository PHID is the "resource" we're after.
|
||||
|
||||
// In normal workflows, we figure out the viewer first, then use the
|
||||
// viewer to load the repository, but that won't work here. Load the
|
||||
// repository as the omnipotent viewer, then use the repository PHID to
|
||||
// look for a token.
|
||||
|
||||
$omnipotent_viewer = PhabricatorUser::getOmnipotentUser();
|
||||
|
||||
$repository = id(new PhabricatorRepositoryQuery())
|
||||
->setViewer($omnipotent_viewer)
|
||||
->withIdentifiers(array($identifier))
|
||||
->executeOne();
|
||||
if (!$repository) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$lfs_pass = $password->openEnvelope();
|
||||
$lfs_hash = PhabricatorHash::weakDigest($lfs_pass);
|
||||
|
||||
$token = id(new PhabricatorAuthTemporaryTokenQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->setViewer($omnipotent_viewer)
|
||||
->withTokenResources(array($repository->getPHID()))
|
||||
->withTokenTypes(array(DiffusionGitLFSTemporaryTokenType::TOKENTYPE))
|
||||
->withTokenCodes(array($lfs_hash))
|
||||
->withExpired(false)
|
||||
|
@ -682,7 +706,7 @@ final class DiffusionServeController extends DiffusionController {
|
|||
}
|
||||
|
||||
$user = id(new PhabricatorPeopleQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->setViewer($omnipotent_viewer)
|
||||
->withPHIDs(array($token->getUserPHID()))
|
||||
->executeOne();
|
||||
|
||||
|
|
|
@ -444,13 +444,15 @@ final class DiffusionRepositoryBasicsManagementPanel
|
|||
id(new PHUIStatusItemView())
|
||||
->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')
|
||||
->setTarget(
|
||||
pht('Missing Binary %s', phutil_tag('tt', array(), $binary)))
|
||||
->setNote(pht(
|
||||
'Unable to find this binary in `%s`. '.
|
||||
'You need to configure %s and include %s.',
|
||||
'environment.append-paths',
|
||||
$this->getEnvConfigLink(),
|
||||
$path)));
|
||||
pht('Commit Hooks: %s', phutil_tag('tt', array(), $binary)))
|
||||
->setNote(
|
||||
pht(
|
||||
'The directory containing the "svnlook" binary is not '.
|
||||
'listed in "environment.append-paths", so commit hooks '.
|
||||
'(which execute with an empty "PATH") will not be able to '.
|
||||
'find "svnlook". Add `%s` to %s.',
|
||||
$path,
|
||||
$this->getEnvConfigLink())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -127,9 +127,9 @@ abstract class DiffusionSSHWorkflow extends PhabricatorSSHWorkflow {
|
|||
// This is suppressing "added <address> to the list of known hosts"
|
||||
// messages, which are confusing and irrelevant when they arise from
|
||||
// proxied requests. It might also be suppressing lots of useful errors,
|
||||
// of course. Ideally, we would enforce host keys eventually.
|
||||
// of course. Ideally, we would enforce host keys eventually. See T13121.
|
||||
$options[] = '-o';
|
||||
$options[] = 'LogLevel=quiet';
|
||||
$options[] = 'LogLevel=ERROR';
|
||||
|
||||
// NOTE: We prefix the command with "@username", which the far end of the
|
||||
// connection will parse in order to act as the specified user. This
|
||||
|
|
|
@ -30,8 +30,11 @@ final class DrydockSSHCommandInterface extends DrydockCommandInterface {
|
|||
$full_command = call_user_func_array('csprintf', $argv);
|
||||
|
||||
$flags = array();
|
||||
|
||||
// See T13121. Attempt to suppress the "Permanently added X to list of
|
||||
// known hosts" message without suppressing anything important.
|
||||
$flags[] = '-o';
|
||||
$flags[] = 'LogLevel=quiet';
|
||||
$flags[] = 'LogLevel=ERROR';
|
||||
|
||||
$flags[] = '-o';
|
||||
$flags[] = 'StrictHostKeyChecking=no';
|
||||
|
|
|
@ -83,6 +83,8 @@ final class PhabricatorHarbormasterApplication extends PhabricatorApplication {
|
|||
=> 'HarbormasterPlanEditController',
|
||||
'order/(?:(?P<id>\d+)/)?' => 'HarbormasterPlanOrderController',
|
||||
'disable/(?P<id>\d+)/' => 'HarbormasterPlanDisableController',
|
||||
'behavior/(?P<id>\d+)/(?P<behaviorKey>[^/]+)/' =>
|
||||
'HarbormasterPlanBehaviorController',
|
||||
'run/(?P<id>\d+)/' => 'HarbormasterPlanRunController',
|
||||
'(?P<id>\d+)/' => 'HarbormasterPlanViewController',
|
||||
),
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
final class HarbormasterBuildPlanPolicyCodex
|
||||
extends PhabricatorPolicyCodex {
|
||||
|
||||
public function getPolicySpecialRuleDescriptions() {
|
||||
$object = $this->getObject();
|
||||
$run_with_view = $object->canRunWithoutEditCapability();
|
||||
|
||||
$rules = array();
|
||||
|
||||
$rules[] = $this->newRule()
|
||||
->setCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
))
|
||||
->setIsActive(!$run_with_view)
|
||||
->setDescription(
|
||||
pht(
|
||||
'You must have edit permission on this build plan to pause, '.
|
||||
'abort, resume, or restart it.'));
|
||||
|
||||
$rules[] = $this->newRule()
|
||||
->setCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
))
|
||||
->setIsActive(!$run_with_view)
|
||||
->setDescription(
|
||||
pht(
|
||||
'You must have edit permission on this build plan to run it '.
|
||||
'manually.'));
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
final class HarbormasterBuildPlanEditAPIMethod
|
||||
extends PhabricatorEditEngineAPIMethod {
|
||||
|
||||
public function getAPIMethodName() {
|
||||
return 'harbormaster.buildplan.edit';
|
||||
}
|
||||
|
||||
public function newEditEngine() {
|
||||
return new HarbormasterBuildPlanEditEngine();
|
||||
}
|
||||
|
||||
public function getMethodSummary() {
|
||||
return pht(
|
||||
'Apply transactions to create a new build plan or edit an existing '.
|
||||
'one.');
|
||||
}
|
||||
|
||||
}
|
|
@ -52,6 +52,10 @@ final class HarbormasterBuildStatus extends Phobject {
|
|||
return ($this->key === self::STATUS_PASSED);
|
||||
}
|
||||
|
||||
public function isFailed() {
|
||||
return ($this->key === self::STATUS_FAILED);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a human readable name for a build status constant.
|
||||
|
|
|
@ -65,13 +65,12 @@ final class HarbormasterBuildActionController
|
|||
'restart build?');
|
||||
$submit = pht('Restart Build');
|
||||
} else {
|
||||
$title = pht('Unable to Restart Build');
|
||||
if ($build->isRestarting()) {
|
||||
$body = pht(
|
||||
'This build is already restarting. You can not reissue a '.
|
||||
'restart command to a restarting build.');
|
||||
} else {
|
||||
$body = pht('You can not restart this build.');
|
||||
try {
|
||||
$build->assertCanRestartBuild();
|
||||
throw new Exception(pht('Expected to be unable to restart build.'));
|
||||
} catch (HarbormasterRestartException $ex) {
|
||||
$title = $ex->getTitle();
|
||||
$body = $ex->getBody();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -135,8 +134,7 @@ final class HarbormasterBuildActionController
|
|||
break;
|
||||
}
|
||||
|
||||
$dialog = id(new AphrontDialogView())
|
||||
->setUser($viewer)
|
||||
$dialog = $this->newDialog()
|
||||
->setTitle($title)
|
||||
->appendChild($body)
|
||||
->addCancelButton($return_uri);
|
||||
|
@ -145,7 +143,7 @@ final class HarbormasterBuildActionController
|
|||
$dialog->addSubmitButton($submit);
|
||||
}
|
||||
|
||||
return id(new AphrontDialogResponse())->setDialog($dialog);
|
||||
return $dialog;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
final class HarbormasterPlanBehaviorController
|
||||
extends HarbormasterPlanController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$plan = id(new HarbormasterBuildPlanQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($request->getURIData('id')))
|
||||
->requireCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
))
|
||||
->executeOne();
|
||||
if (!$plan) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$behavior_key = $request->getURIData('behaviorKey');
|
||||
$metadata_key = HarbormasterBuildPlanBehavior::getTransactionMetadataKey();
|
||||
|
||||
$behaviors = HarbormasterBuildPlanBehavior::newPlanBehaviors();
|
||||
$behavior = idx($behaviors, $behavior_key);
|
||||
if (!$behavior) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$plan_uri = $plan->getURI();
|
||||
|
||||
$v_option = $behavior->getPlanOption($plan)->getKey();
|
||||
if ($request->isFormPost()) {
|
||||
$v_option = $request->getStr('option');
|
||||
|
||||
$xactions = array();
|
||||
|
||||
$xactions[] = id(new HarbormasterBuildPlanTransaction())
|
||||
->setTransactionType(
|
||||
HarbormasterBuildPlanBehaviorTransaction::TRANSACTIONTYPE)
|
||||
->setMetadataValue($metadata_key, $behavior_key)
|
||||
->setNewValue($v_option);
|
||||
|
||||
$editor = id(new HarbormasterBuildPlanEditor())
|
||||
->setActor($viewer)
|
||||
->setContinueOnNoEffect(true)
|
||||
->setContinueOnMissingFields(true)
|
||||
->setContentSourceFromRequest($request);
|
||||
|
||||
$editor->applyTransactions($plan, $xactions);
|
||||
|
||||
return id(new AphrontRedirectResponse())->setURI($plan_uri);
|
||||
}
|
||||
|
||||
$select_control = id(new AphrontFormRadioButtonControl())
|
||||
->setName('option')
|
||||
->setValue($v_option)
|
||||
->setLabel(pht('Option'));
|
||||
|
||||
foreach ($behavior->getOptions() as $option) {
|
||||
$icon = id(new PHUIIconView())
|
||||
->setIcon($option->getIcon());
|
||||
|
||||
$select_control->addButton(
|
||||
$option->getKey(),
|
||||
array(
|
||||
$icon,
|
||||
' ',
|
||||
$option->getName(),
|
||||
),
|
||||
$option->getDescription());
|
||||
}
|
||||
|
||||
$form = id(new AphrontFormView())
|
||||
->setViewer($viewer)
|
||||
->appendInstructions(
|
||||
pht(
|
||||
'Choose a build plan behavior for "%s".',
|
||||
phutil_tag('strong', array(), $behavior->getName())))
|
||||
->appendRemarkupInstructions($behavior->getEditInstructions())
|
||||
->appendControl($select_control);
|
||||
|
||||
return $this->newDialog()
|
||||
->setTitle(pht('Edit Behavior: %s', $behavior->getName()))
|
||||
->appendForm($form)
|
||||
->setWidth(AphrontDialogView::WIDTH_FORM)
|
||||
->addSubmitButton(pht('Save Changes'))
|
||||
->addCancelButton($plan_uri);
|
||||
}
|
||||
|
||||
}
|
|
@ -19,11 +19,11 @@ final class HarbormasterPlanDisableController
|
|||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$plan_uri = $this->getApplicationURI('plan/'.$plan->getID().'/');
|
||||
$plan_uri = $plan->getURI();
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
|
||||
$type_status = HarbormasterBuildPlanTransaction::TYPE_STATUS;
|
||||
$type_status = HarbormasterBuildPlanStatusTransaction::TRANSACTIONTYPE;
|
||||
|
||||
$v_status = $plan->isDisabled()
|
||||
? HarbormasterBuildPlan::STATUS_ACTIVE
|
||||
|
|
|
@ -9,16 +9,13 @@ final class HarbormasterPlanRunController extends HarbormasterPlanController {
|
|||
$plan = id(new HarbormasterBuildPlanQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($plan_id))
|
||||
->requireCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
))
|
||||
->executeOne();
|
||||
if (!$plan) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$plan->assertHasRunCapability($viewer);
|
||||
|
||||
$cancel_uri = $this->getApplicationURI("plan/{$plan_id}/");
|
||||
|
||||
if (!$plan->canRunManually()) {
|
||||
|
|
|
@ -18,11 +18,6 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController {
|
|||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$timeline = $this->buildTransactionTimeline(
|
||||
$plan,
|
||||
new HarbormasterBuildPlanTransactionQuery());
|
||||
$timeline->setShouldTerminate(true);
|
||||
|
||||
$title = $plan->getName();
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
|
@ -33,24 +28,30 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController {
|
|||
|
||||
$curtain = $this->buildCurtainView($plan);
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->addTextCrumb(pht('Plan %d', $id));
|
||||
$crumbs->setBorder(true);
|
||||
$crumbs = $this->buildApplicationCrumbs()
|
||||
->addTextCrumb($plan->getObjectName())
|
||||
->setBorder(true);
|
||||
|
||||
list($step_list, $has_any_conflicts, $would_deadlock) =
|
||||
list($step_list, $has_any_conflicts, $would_deadlock, $steps) =
|
||||
$this->buildStepList($plan);
|
||||
|
||||
$error = null;
|
||||
if ($would_deadlock) {
|
||||
$error = pht('This build plan will deadlock when executed, due to '.
|
||||
'circular dependencies present in the build plan. '.
|
||||
'Examine the step list and resolve the deadlock.');
|
||||
if (!$steps) {
|
||||
$error = pht(
|
||||
'This build plan does not have any build steps yet, so it will '.
|
||||
'not do anything when run.');
|
||||
} else if ($would_deadlock) {
|
||||
$error = pht(
|
||||
'This build plan will deadlock when executed, due to circular '.
|
||||
'dependencies present in the build plan. Examine the step list '.
|
||||
'and resolve the deadlock.');
|
||||
} else if ($has_any_conflicts) {
|
||||
// A deadlocking build will also cause all the artifacts to be
|
||||
// invalid, so we just skip showing this message if that's the
|
||||
// case.
|
||||
$error = pht('This build plan has conflicts in one or more build steps. '.
|
||||
'Examine the step list and resolve the listed errors.');
|
||||
$error = pht(
|
||||
'This build plan has conflicts in one or more build steps. '.
|
||||
'Examine the step list and resolve the listed errors.');
|
||||
}
|
||||
|
||||
if ($error) {
|
||||
|
@ -59,18 +60,32 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController {
|
|||
->appendChild($error);
|
||||
}
|
||||
|
||||
$builds_view = $this->newBuildsView($plan);
|
||||
$options_view = $this->newOptionsView($plan);
|
||||
$rules_view = $this->newRulesView($plan);
|
||||
|
||||
$timeline = $this->buildTransactionTimeline(
|
||||
$plan,
|
||||
new HarbormasterBuildPlanTransactionQuery());
|
||||
$timeline->setShouldTerminate(true);
|
||||
|
||||
$view = id(new PHUITwoColumnView())
|
||||
->setHeader($header)
|
||||
->setCurtain($curtain)
|
||||
->setMainColumn(array(
|
||||
$error,
|
||||
$step_list,
|
||||
$timeline,
|
||||
));
|
||||
->setMainColumn(
|
||||
array(
|
||||
$error,
|
||||
$step_list,
|
||||
$options_view,
|
||||
$rules_view,
|
||||
$builds_view,
|
||||
$timeline,
|
||||
));
|
||||
|
||||
return $this->newPage()
|
||||
->setTitle($title)
|
||||
->setCrumbs($crumbs)
|
||||
->setPageObjectPHIDs(array($plan->getPHID()))
|
||||
->appendChild($view);
|
||||
}
|
||||
|
||||
|
@ -213,7 +228,7 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController {
|
|||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->appendChild($step_list);
|
||||
|
||||
return array($step_box, $has_any_conflicts, $is_deadlocking);
|
||||
return array($step_box, $has_any_conflicts, $is_deadlocking, $steps);
|
||||
}
|
||||
|
||||
private function buildCurtainView(HarbormasterBuildPlan $plan) {
|
||||
|
@ -253,7 +268,7 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController {
|
|||
->setIcon('fa-ban'));
|
||||
}
|
||||
|
||||
$can_run = ($can_edit && $plan->canRunManually());
|
||||
$can_run = ($plan->hasRunCapability($viewer) && $plan->canRunManually());
|
||||
|
||||
$curtain->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
|
@ -263,11 +278,6 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController {
|
|||
->setDisabled(!$can_run)
|
||||
->setIcon('fa-play-circle'));
|
||||
|
||||
$curtain->addPanel(
|
||||
id(new PHUICurtainPanelView())
|
||||
->setHeaderText(pht('Created'))
|
||||
->appendChild(phabricator_datetime($plan->getDateCreated(), $viewer)));
|
||||
|
||||
return $curtain;
|
||||
}
|
||||
|
||||
|
@ -381,7 +391,7 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController {
|
|||
array $steps) {
|
||||
$has_conflicts = false;
|
||||
|
||||
if (count($step_phids) === 0) {
|
||||
if (!$step_phids) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -441,4 +451,149 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController {
|
|||
|
||||
return array($ui, $has_conflicts);
|
||||
}
|
||||
|
||||
private function newBuildsView(HarbormasterBuildPlan $plan) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$builds = id(new HarbormasterBuildQuery())
|
||||
->setViewer($viewer)
|
||||
->withBuildPlanPHIDs(array($plan->getPHID()))
|
||||
->setLimit(10)
|
||||
->execute();
|
||||
|
||||
$list = id(new HarbormasterBuildView())
|
||||
->setViewer($viewer)
|
||||
->setBuilds($builds)
|
||||
->newObjectList();
|
||||
|
||||
$list->setNoDataString(pht('No recent builds.'));
|
||||
|
||||
$more_href = new PhutilURI(
|
||||
$this->getApplicationURI('/build/'),
|
||||
array('plan' => $plan->getPHID()));
|
||||
|
||||
$more_link = id(new PHUIButtonView())
|
||||
->setTag('a')
|
||||
->setIcon('fa-list-ul')
|
||||
->setText(pht('View All Builds'))
|
||||
->setHref($more_href);
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader(pht('Recent Builds'))
|
||||
->addActionLink($more_link);
|
||||
|
||||
return id(new PHUIObjectBoxView())
|
||||
->setHeader($header)
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->appendChild($list);
|
||||
}
|
||||
|
||||
private function newRulesView(HarbormasterBuildPlan $plan) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$rules = id(new HeraldRuleQuery())
|
||||
->setViewer($viewer)
|
||||
->withDisabled(false)
|
||||
->withAffectedObjectPHIDs(array($plan->getPHID()))
|
||||
->needValidateAuthors(true)
|
||||
->setLimit(10)
|
||||
->execute();
|
||||
|
||||
$list = id(new HeraldRuleListView())
|
||||
->setViewer($viewer)
|
||||
->setRules($rules)
|
||||
->newObjectList();
|
||||
|
||||
$list->setNoDataString(pht('No active Herald rules trigger this build.'));
|
||||
|
||||
$more_href = new PhutilURI(
|
||||
'/herald/',
|
||||
array('affectedPHID' => $plan->getPHID()));
|
||||
|
||||
$more_link = id(new PHUIButtonView())
|
||||
->setTag('a')
|
||||
->setIcon('fa-list-ul')
|
||||
->setText(pht('View All Rules'))
|
||||
->setHref($more_href);
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader(pht('Run By Herald Rules'))
|
||||
->addActionLink($more_link);
|
||||
|
||||
return id(new PHUIObjectBoxView())
|
||||
->setHeader($header)
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->appendChild($list);
|
||||
}
|
||||
|
||||
private function newOptionsView(HarbormasterBuildPlan $plan) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||
$viewer,
|
||||
$plan,
|
||||
PhabricatorPolicyCapability::CAN_EDIT);
|
||||
|
||||
$behaviors = HarbormasterBuildPlanBehavior::newPlanBehaviors();
|
||||
|
||||
$rows = array();
|
||||
foreach ($behaviors as $behavior) {
|
||||
$option = $behavior->getPlanOption($plan);
|
||||
|
||||
$icon = $option->getIcon();
|
||||
$icon = id(new PHUIIconView())->setIcon($icon);
|
||||
|
||||
$edit_uri = new PhutilURI(
|
||||
$this->getApplicationURI(
|
||||
urisprintf(
|
||||
'plan/behavior/%d/%s/',
|
||||
$plan->getID(),
|
||||
$behavior->getKey())));
|
||||
|
||||
$edit_button = id(new PHUIButtonView())
|
||||
->setTag('a')
|
||||
->setColor(PHUIButtonView::GREY)
|
||||
->setSize(PHUIButtonView::SMALL)
|
||||
->setDisabled(!$can_edit)
|
||||
->setWorkflow(true)
|
||||
->setText(pht('Edit'))
|
||||
->setHref($edit_uri);
|
||||
|
||||
$rows[] = array(
|
||||
$icon,
|
||||
$behavior->getName(),
|
||||
$option->getName(),
|
||||
$option->getDescription(),
|
||||
$edit_button,
|
||||
);
|
||||
}
|
||||
|
||||
$table = id(new AphrontTableView($rows))
|
||||
->setHeaders(
|
||||
array(
|
||||
null,
|
||||
pht('Name'),
|
||||
pht('Behavior'),
|
||||
pht('Details'),
|
||||
null,
|
||||
))
|
||||
->setColumnClasses(
|
||||
array(
|
||||
null,
|
||||
'pri',
|
||||
null,
|
||||
'wide',
|
||||
null,
|
||||
));
|
||||
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader(pht('Plan Behaviors'));
|
||||
|
||||
return id(new PHUIObjectBoxView())
|
||||
->setHeader($header)
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->setTable($table);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -77,17 +77,48 @@ final class HarbormasterBuildPlanEditEngine
|
|||
}
|
||||
|
||||
protected function buildCustomEditFields($object) {
|
||||
return array(
|
||||
$fields = array(
|
||||
id(new PhabricatorTextEditField())
|
||||
->setKey('name')
|
||||
->setLabel(pht('Name'))
|
||||
->setIsRequired(true)
|
||||
->setTransactionType(HarbormasterBuildPlanTransaction::TYPE_NAME)
|
||||
->setTransactionType(
|
||||
HarbormasterBuildPlanNameTransaction::TRANSACTIONTYPE)
|
||||
->setDescription(pht('The build plan name.'))
|
||||
->setConduitDescription(pht('Rename the plan.'))
|
||||
->setConduitTypeDescription(pht('New plan name.'))
|
||||
->setValue($object->getName()),
|
||||
);
|
||||
|
||||
|
||||
$metadata_key = HarbormasterBuildPlanBehavior::getTransactionMetadataKey();
|
||||
|
||||
$behaviors = HarbormasterBuildPlanBehavior::newPlanBehaviors();
|
||||
foreach ($behaviors as $behavior) {
|
||||
$key = $behavior->getKey();
|
||||
|
||||
// Get the raw key off the object so that we don't reset stuff to
|
||||
// default values by mistake if a behavior goes missing somehow.
|
||||
$storage_key = HarbormasterBuildPlanBehavior::getStorageKeyForBehaviorKey(
|
||||
$key);
|
||||
$behavior_option = $object->getPlanProperty($storage_key);
|
||||
|
||||
if (!strlen($behavior_option)) {
|
||||
$behavior_option = $behavior->getPlanOption($object)->getKey();
|
||||
}
|
||||
|
||||
$fields[] = id(new PhabricatorSelectEditField())
|
||||
->setIsFormField(false)
|
||||
->setKey(sprintf('behavior.%s', $behavior->getKey()))
|
||||
->setMetadataValue($metadata_key, $behavior->getKey())
|
||||
->setLabel(pht('Behavior: %s', $behavior->getName()))
|
||||
->setTransactionType(
|
||||
HarbormasterBuildPlanBehaviorTransaction::TRANSACTIONTYPE)
|
||||
->setValue($behavior_option)
|
||||
->setOptions($behavior->getOptionMap());
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,100 +11,23 @@ final class HarbormasterBuildPlanEditor
|
|||
return pht('Harbormaster Build Plans');
|
||||
}
|
||||
|
||||
public function getCreateObjectTitle($author, $object) {
|
||||
return pht('%s created this build plan.', $author);
|
||||
}
|
||||
|
||||
public function getCreateObjectTitleForFeed($author, $object) {
|
||||
return pht('%s created %s.', $author, $object);
|
||||
}
|
||||
|
||||
protected function supportsSearch() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getTransactionTypes() {
|
||||
$types = parent::getTransactionTypes();
|
||||
$types[] = HarbormasterBuildPlanTransaction::TYPE_NAME;
|
||||
$types[] = HarbormasterBuildPlanTransaction::TYPE_STATUS;
|
||||
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
|
||||
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
|
||||
return $types;
|
||||
}
|
||||
|
||||
protected function getCustomTransactionOldValue(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case HarbormasterBuildPlanTransaction::TYPE_NAME:
|
||||
if ($this->getIsNewObject()) {
|
||||
return null;
|
||||
}
|
||||
return $object->getName();
|
||||
case HarbormasterBuildPlanTransaction::TYPE_STATUS:
|
||||
return $object->getPlanStatus();
|
||||
}
|
||||
|
||||
return parent::getCustomTransactionOldValue($object, $xaction);
|
||||
}
|
||||
|
||||
protected function getCustomTransactionNewValue(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case HarbormasterBuildPlanTransaction::TYPE_NAME:
|
||||
return $xaction->getNewValue();
|
||||
case HarbormasterBuildPlanTransaction::TYPE_STATUS:
|
||||
return $xaction->getNewValue();
|
||||
}
|
||||
return parent::getCustomTransactionNewValue($object, $xaction);
|
||||
}
|
||||
|
||||
protected function applyCustomInternalTransaction(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case HarbormasterBuildPlanTransaction::TYPE_NAME:
|
||||
$object->setName($xaction->getNewValue());
|
||||
return;
|
||||
case HarbormasterBuildPlanTransaction::TYPE_STATUS:
|
||||
$object->setPlanStatus($xaction->getNewValue());
|
||||
return;
|
||||
}
|
||||
return parent::applyCustomInternalTransaction($object, $xaction);
|
||||
}
|
||||
|
||||
protected function applyCustomExternalTransaction(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case HarbormasterBuildPlanTransaction::TYPE_NAME:
|
||||
case HarbormasterBuildPlanTransaction::TYPE_STATUS:
|
||||
return;
|
||||
}
|
||||
return parent::applyCustomExternalTransaction($object, $xaction);
|
||||
}
|
||||
|
||||
protected function validateTransaction(
|
||||
PhabricatorLiskDAO $object,
|
||||
$type,
|
||||
array $xactions) {
|
||||
|
||||
$errors = parent::validateTransaction($object, $type, $xactions);
|
||||
|
||||
switch ($type) {
|
||||
case HarbormasterBuildPlanTransaction::TYPE_NAME:
|
||||
$missing = $this->validateIsEmptyTextField(
|
||||
$object->getName(),
|
||||
$xactions);
|
||||
|
||||
if ($missing) {
|
||||
$error = new PhabricatorApplicationTransactionValidationError(
|
||||
$type,
|
||||
pht('Required'),
|
||||
pht('You must choose a name for your build plan.'),
|
||||
last($xactions));
|
||||
|
||||
$error->setIsMissingFieldError(true);
|
||||
$errors[] = $error;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -497,9 +497,33 @@ final class HarbormasterBuildEngine extends Phobject {
|
|||
// passed everything it needs to.
|
||||
|
||||
if (!$buildable->isPreparing()) {
|
||||
$behavior_key = HarbormasterBuildPlanBehavior::BEHAVIOR_BUILDABLE;
|
||||
$behavior = HarbormasterBuildPlanBehavior::getBehavior($behavior_key);
|
||||
|
||||
$key_never = HarbormasterBuildPlanBehavior::BUILDABLE_NEVER;
|
||||
$key_building = HarbormasterBuildPlanBehavior::BUILDABLE_IF_BUILDING;
|
||||
|
||||
$all_pass = true;
|
||||
$any_fail = false;
|
||||
foreach ($buildable->getBuilds() as $build) {
|
||||
$plan = $build->getBuildPlan();
|
||||
$option = $behavior->getPlanOption($plan);
|
||||
$option_key = $option->getKey();
|
||||
|
||||
$is_never = ($option_key === $key_never);
|
||||
$is_building = ($option_key === $key_building);
|
||||
|
||||
// If this build "Never" affects the buildable, ignore it.
|
||||
if ($is_never) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If this build affects the buildable "If Building", but is already
|
||||
// complete, ignore it.
|
||||
if ($is_building && $build->isComplete()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$build->isPassed()) {
|
||||
$all_pass = false;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
final class HarbormasterRestartException extends Exception {
|
||||
|
||||
private $title;
|
||||
private $body = array();
|
||||
|
||||
public function __construct($title, $body = null) {
|
||||
$this->setTitle($title);
|
||||
$this->appendParagraph($body);
|
||||
|
||||
parent::__construct($title);
|
||||
}
|
||||
|
||||
public function setTitle($title) {
|
||||
$this->title = $title;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function appendParagraph($description) {
|
||||
$this->body[] = $description;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBody() {
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
}
|
|
@ -91,4 +91,9 @@ final class HarbormasterRunBuildPlansHeraldAction
|
|||
'Run build plans: %s.',
|
||||
$this->renderHandleList($value));
|
||||
}
|
||||
|
||||
public function getPHIDsAffectedByAction(HeraldActionRecord $record) {
|
||||
return $record->getTarget();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,394 @@
|
|||
<?php
|
||||
|
||||
final class HarbormasterBuildPlanBehavior
|
||||
extends Phobject {
|
||||
|
||||
private $key;
|
||||
private $name;
|
||||
private $options;
|
||||
private $defaultKey;
|
||||
private $editInstructions;
|
||||
|
||||
const BEHAVIOR_RUNNABLE = 'runnable';
|
||||
const RUNNABLE_IF_VIEWABLE = 'view';
|
||||
const RUNNABLE_IF_EDITABLE = 'edit';
|
||||
|
||||
const BEHAVIOR_RESTARTABLE = 'restartable';
|
||||
const RESTARTABLE_ALWAYS = 'always';
|
||||
const RESTARTABLE_IF_FAILED = 'failed';
|
||||
const RESTARTABLE_NEVER = 'never';
|
||||
|
||||
const BEHAVIOR_DRAFTS = 'hold-drafts';
|
||||
const DRAFTS_ALWAYS = 'always';
|
||||
const DRAFTS_IF_BUILDING = 'building';
|
||||
const DRAFTS_NEVER = 'never';
|
||||
|
||||
const BEHAVIOR_BUILDABLE = 'buildable';
|
||||
const BUILDABLE_ALWAYS = 'always';
|
||||
const BUILDABLE_IF_BUILDING = 'building';
|
||||
const BUILDABLE_NEVER = 'never';
|
||||
|
||||
const BEHAVIOR_LANDWARNING = 'arc-land';
|
||||
const LANDWARNING_ALWAYS = 'always';
|
||||
const LANDWARNING_IF_BUILDING = 'building';
|
||||
const LANDWARNING_IF_COMPLETE = 'complete';
|
||||
const LANDWARNING_NEVER = 'never';
|
||||
|
||||
public function setKey($key) {
|
||||
$this->key = $key;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getKey() {
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
public function setName($name) {
|
||||
$this->name = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setEditInstructions($edit_instructions) {
|
||||
$this->editInstructions = $edit_instructions;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getEditInstructions() {
|
||||
return $this->editInstructions;
|
||||
}
|
||||
|
||||
public function getOptionMap() {
|
||||
return mpull($this->options, 'getName', 'getKey');
|
||||
}
|
||||
|
||||
public function setOptions(array $options) {
|
||||
assert_instances_of($options, 'HarbormasterBuildPlanBehaviorOption');
|
||||
|
||||
$key_map = array();
|
||||
$default = null;
|
||||
|
||||
foreach ($options as $option) {
|
||||
$key = $option->getKey();
|
||||
|
||||
if (isset($key_map[$key])) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Multiple behavior options (for behavior "%s") have the same '.
|
||||
'key ("%s"). Each option must have a unique key.',
|
||||
$this->getKey(),
|
||||
$key));
|
||||
}
|
||||
$key_map[$key] = true;
|
||||
|
||||
if ($option->getIsDefault()) {
|
||||
if ($default === null) {
|
||||
$default = $key;
|
||||
} else {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Multiple behavior options (for behavior "%s") are marked as '.
|
||||
'default options ("%s" and "%s"). Exactly one option must be '.
|
||||
'marked as the default option.',
|
||||
$this->getKey(),
|
||||
$default,
|
||||
$key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($default === null) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'No behavior option is marked as the default option (for '.
|
||||
'behavior "%s"). Exactly one option must be marked as the '.
|
||||
'default option.',
|
||||
$this->getKey()));
|
||||
}
|
||||
|
||||
$this->options = mpull($options, null, 'getKey');
|
||||
$this->defaultKey = $default;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getOptions() {
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
public function getPlanOption(HarbormasterBuildPlan $plan) {
|
||||
$behavior_key = $this->getKey();
|
||||
$storage_key = self::getStorageKeyForBehaviorKey($behavior_key);
|
||||
|
||||
$plan_value = $plan->getPlanProperty($storage_key);
|
||||
if (isset($this->options[$plan_value])) {
|
||||
return $this->options[$plan_value];
|
||||
}
|
||||
|
||||
return idx($this->options, $this->defaultKey);
|
||||
}
|
||||
|
||||
public static function getTransactionMetadataKey() {
|
||||
return 'behavior-key';
|
||||
}
|
||||
|
||||
public static function getStorageKeyForBehaviorKey($behavior_key) {
|
||||
return sprintf('behavior.%s', $behavior_key);
|
||||
}
|
||||
|
||||
public static function getBehavior($key) {
|
||||
$behaviors = self::newPlanBehaviors();
|
||||
|
||||
if (!isset($behaviors[$key])) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'No build plan behavior with key "%s" exists.',
|
||||
$key));
|
||||
}
|
||||
|
||||
return $behaviors[$key];
|
||||
}
|
||||
|
||||
public static function newPlanBehaviors() {
|
||||
$draft_options = array(
|
||||
id(new HarbormasterBuildPlanBehaviorOption())
|
||||
->setKey(self::DRAFTS_ALWAYS)
|
||||
->setIcon('fa-check-circle-o green')
|
||||
->setName(pht('Always'))
|
||||
->setIsDefault(true)
|
||||
->setDescription(
|
||||
pht(
|
||||
'Revisions are not sent for review until the build completes, '.
|
||||
'and are returned to the author for updates if the build fails.')),
|
||||
id(new HarbormasterBuildPlanBehaviorOption())
|
||||
->setKey(self::DRAFTS_IF_BUILDING)
|
||||
->setIcon('fa-pause-circle-o yellow')
|
||||
->setName(pht('If Building'))
|
||||
->setDescription(
|
||||
pht(
|
||||
'Revisions are not sent for review until the build completes, '.
|
||||
'but they will be sent for review even if it fails.')),
|
||||
id(new HarbormasterBuildPlanBehaviorOption())
|
||||
->setKey(self::DRAFTS_NEVER)
|
||||
->setIcon('fa-circle-o red')
|
||||
->setName(pht('Never'))
|
||||
->setDescription(
|
||||
pht(
|
||||
'Revisions are sent for review regardless of the status of the '.
|
||||
'build.')),
|
||||
);
|
||||
|
||||
$land_options = array(
|
||||
id(new HarbormasterBuildPlanBehaviorOption())
|
||||
->setKey(self::LANDWARNING_ALWAYS)
|
||||
->setIcon('fa-check-circle-o green')
|
||||
->setName(pht('Always'))
|
||||
->setIsDefault(true)
|
||||
->setDescription(
|
||||
pht(
|
||||
'"arc land" warns if the build is still running or has '.
|
||||
'failed.')),
|
||||
id(new HarbormasterBuildPlanBehaviorOption())
|
||||
->setKey(self::LANDWARNING_IF_BUILDING)
|
||||
->setIcon('fa-pause-circle-o yellow')
|
||||
->setName(pht('If Building'))
|
||||
->setDescription(
|
||||
pht(
|
||||
'"arc land" warns if the build is still running, but ignores '.
|
||||
'the build if it has failed.')),
|
||||
id(new HarbormasterBuildPlanBehaviorOption())
|
||||
->setKey(self::LANDWARNING_IF_COMPLETE)
|
||||
->setIcon('fa-dot-circle-o yellow')
|
||||
->setName(pht('If Complete'))
|
||||
->setDescription(
|
||||
pht(
|
||||
'"arc land" warns if the build has failed, but ignores the '.
|
||||
'build if it is still running.')),
|
||||
id(new HarbormasterBuildPlanBehaviorOption())
|
||||
->setKey(self::LANDWARNING_NEVER)
|
||||
->setIcon('fa-circle-o red')
|
||||
->setName(pht('Never'))
|
||||
->setDescription(
|
||||
pht(
|
||||
'"arc land" never warns that the build is still running or '.
|
||||
'has failed.')),
|
||||
);
|
||||
|
||||
$aggregate_options = array(
|
||||
id(new HarbormasterBuildPlanBehaviorOption())
|
||||
->setKey(self::BUILDABLE_ALWAYS)
|
||||
->setIcon('fa-check-circle-o green')
|
||||
->setName(pht('Always'))
|
||||
->setIsDefault(true)
|
||||
->setDescription(
|
||||
pht(
|
||||
'The buildable waits for the build, and fails if the '.
|
||||
'build fails.')),
|
||||
id(new HarbormasterBuildPlanBehaviorOption())
|
||||
->setKey(self::BUILDABLE_IF_BUILDING)
|
||||
->setIcon('fa-pause-circle-o yellow')
|
||||
->setName(pht('If Building'))
|
||||
->setDescription(
|
||||
pht(
|
||||
'The buildable waits for the build, but does not fail '.
|
||||
'if the build fails.')),
|
||||
id(new HarbormasterBuildPlanBehaviorOption())
|
||||
->setKey(self::BUILDABLE_NEVER)
|
||||
->setIcon('fa-circle-o red')
|
||||
->setName(pht('Never'))
|
||||
->setDescription(
|
||||
pht(
|
||||
'The buildable does not wait for the build.')),
|
||||
);
|
||||
|
||||
$restart_options = array(
|
||||
id(new HarbormasterBuildPlanBehaviorOption())
|
||||
->setKey(self::RESTARTABLE_ALWAYS)
|
||||
->setIcon('fa-repeat green')
|
||||
->setName(pht('Always'))
|
||||
->setIsDefault(true)
|
||||
->setDescription(
|
||||
pht('The build may be restarted.')),
|
||||
id(new HarbormasterBuildPlanBehaviorOption())
|
||||
->setKey(self::RESTARTABLE_IF_FAILED)
|
||||
->setIcon('fa-times-circle-o yellow')
|
||||
->setName(pht('If Failed'))
|
||||
->setDescription(
|
||||
pht('The build may be restarted if it has failed.')),
|
||||
id(new HarbormasterBuildPlanBehaviorOption())
|
||||
->setKey(self::RESTARTABLE_NEVER)
|
||||
->setIcon('fa-times red')
|
||||
->setName(pht('Never'))
|
||||
->setDescription(
|
||||
pht('The build may not be restarted.')),
|
||||
);
|
||||
|
||||
$run_options = array(
|
||||
id(new HarbormasterBuildPlanBehaviorOption())
|
||||
->setKey(self::RUNNABLE_IF_EDITABLE)
|
||||
->setIcon('fa-pencil green')
|
||||
->setName(pht('If Editable'))
|
||||
->setIsDefault(true)
|
||||
->setDescription(
|
||||
pht('Only users who can edit the plan can run it manually.')),
|
||||
id(new HarbormasterBuildPlanBehaviorOption())
|
||||
->setKey(self::RUNNABLE_IF_VIEWABLE)
|
||||
->setIcon('fa-exclamation-triangle yellow')
|
||||
->setName(pht('If Viewable'))
|
||||
->setDescription(
|
||||
pht(
|
||||
'Any user who can view the plan can run it manually.')),
|
||||
);
|
||||
|
||||
$behaviors = array(
|
||||
id(new self())
|
||||
->setKey(self::BEHAVIOR_DRAFTS)
|
||||
->setName(pht('Hold Drafts'))
|
||||
->setEditInstructions(
|
||||
pht(
|
||||
'When users create revisions in Differential, the default '.
|
||||
'behavior is to hold them in the "Draft" state until all builds '.
|
||||
'pass. Once builds pass, the revisions promote and are sent for '.
|
||||
'review, which notifies reviewers.'.
|
||||
"\n\n".
|
||||
'The general intent of this workflow is to make sure reviewers '.
|
||||
'are only spending time on review once changes survive automated '.
|
||||
'tests. If a change does not pass tests, it usually is not '.
|
||||
'really ready for review.'.
|
||||
"\n\n".
|
||||
'If you want to promote revisions out of "Draft" before builds '.
|
||||
'pass, or promote revisions even when builds fail, you can '.
|
||||
'change the promotion behavior. This may be useful if you have '.
|
||||
'very long-running builds, or some builds which are not very '.
|
||||
'important.'.
|
||||
"\n\n".
|
||||
'Users may always use "Request Review" to promote a "Draft" '.
|
||||
'revision, even if builds have failed or are still in progress.'))
|
||||
->setOptions($draft_options),
|
||||
id(new self())
|
||||
->setKey(self::BEHAVIOR_LANDWARNING)
|
||||
->setName(pht('Warn When Landing'))
|
||||
->setEditInstructions(
|
||||
pht(
|
||||
'When a user attempts to `arc land` a revision and that revision '.
|
||||
'has ongoing or failed builds, the default behavior of `arc` is '.
|
||||
'to warn them about those builds and give them a chance to '.
|
||||
'reconsider: they may want to wait for ongoing builds to '.
|
||||
'complete, or fix failed builds before landing the change.'.
|
||||
"\n\n".
|
||||
'If you do not want to warn users about this build, you can '.
|
||||
'change the warning behavior. This may be useful if the build '.
|
||||
'takes a long time to run (so you do not expect users to wait '.
|
||||
'for it) or the outcome is not important.'.
|
||||
"\n\n".
|
||||
'This warning is only advisory. Users may always elect to ignore '.
|
||||
'this warning and continue, even if builds have failed.'.
|
||||
"\n\n".
|
||||
'This setting also affects the warning that is published to '.
|
||||
'revisions when commits land with ongoing or failed builds.'))
|
||||
->setOptions($land_options),
|
||||
id(new self())
|
||||
->setKey(self::BEHAVIOR_BUILDABLE)
|
||||
->setEditInstructions(
|
||||
pht(
|
||||
'The overall state of a buildable (like a commit or revision) is '.
|
||||
'normally the aggregation of the individual states of all builds '.
|
||||
'that have run against it.'.
|
||||
"\n\n".
|
||||
'Buildables are "building" until all builds pass (which changes '.
|
||||
'them to "pass"), or any build fails (which changes them to '.
|
||||
'"fail").'.
|
||||
"\n\n".
|
||||
'You can change this behavior if you do not want to wait for this '.
|
||||
'build, or do not care if it fails.'))
|
||||
->setName(pht('Affects Buildable'))
|
||||
->setOptions($aggregate_options),
|
||||
id(new self())
|
||||
->setKey(self::BEHAVIOR_RESTARTABLE)
|
||||
->setEditInstructions(
|
||||
pht(
|
||||
'Usually, builds may be restarted. This may be useful if you '.
|
||||
'suspect a build has failed for environmental or circumstantial '.
|
||||
'reasons unrelated to the actual code, and want to give it '.
|
||||
'another chance at glory.'.
|
||||
"\n\n".
|
||||
'If you want to prevent a build from being restarted, you can '.
|
||||
'change the behavior here. This may be useful to prevent '.
|
||||
'accidents where a build with a dangerous side effect (like '.
|
||||
'deployment) is restarted improperly.'))
|
||||
->setName(pht('Restartable'))
|
||||
->setOptions($restart_options),
|
||||
id(new self())
|
||||
->setKey(self::BEHAVIOR_RUNNABLE)
|
||||
->setEditInstructions(
|
||||
pht(
|
||||
'To run a build manually, you normally must have permission to '.
|
||||
'edit the related build plan. If you would prefer that anyone who '.
|
||||
'can see the build plan be able to run and restart the build, you '.
|
||||
'can change the behavior here.'.
|
||||
"\n\n".
|
||||
'Note that this controls access to all build management actions: '.
|
||||
'"Run Plan Manually", "Restart", "Abort", "Pause", and "Resume".'.
|
||||
"\n\n".
|
||||
'WARNING: This may be unsafe, particularly if the build has '.
|
||||
'side effects like deployment.'.
|
||||
"\n\n".
|
||||
'If you weaken this policy, an attacker with control of an '.
|
||||
'account that has "Can View" permission but not "Can Edit" '.
|
||||
'permission can manually run this build against any old version '.
|
||||
'of the code, including versions with known security issues.'.
|
||||
"\n\n".
|
||||
'If running the build has a side effect like deploying code, '.
|
||||
'they can force deployment of a vulnerable version and then '.
|
||||
'escalate into an attack against the deployed service.'))
|
||||
->setName(pht('Runnable'))
|
||||
->setOptions($run_options),
|
||||
);
|
||||
|
||||
return mpull($behaviors, null, 'getKey');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
final class HarbormasterBuildPlanBehaviorOption
|
||||
extends Phobject {
|
||||
|
||||
private $name;
|
||||
private $key;
|
||||
private $icon;
|
||||
private $description;
|
||||
private $isDefault;
|
||||
|
||||
public function setName($name) {
|
||||
$this->name = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setKey($key) {
|
||||
$this->key = $key;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getKey() {
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
public function setDescription($description) {
|
||||
$this->description = $description;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDescription() {
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
public function setIsDefault($is_default) {
|
||||
$this->isDefault = $is_default;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getIsDefault() {
|
||||
return $this->isDefault;
|
||||
}
|
||||
|
||||
public function setIcon($icon) {
|
||||
$this->icon = $icon;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return $this->icon;
|
||||
}
|
||||
|
||||
}
|
|
@ -128,49 +128,14 @@ final class HarbormasterBuildSearchEngine
|
|||
|
||||
$viewer = $this->requireViewer();
|
||||
|
||||
$buildables = mpull($builds, 'getBuildable');
|
||||
$object_phids = mpull($buildables, 'getBuildablePHID');
|
||||
$initiator_phids = mpull($builds, 'getInitiatorPHID');
|
||||
$phids = array_mergev(array($initiator_phids, $object_phids));
|
||||
$phids = array_unique(array_filter($phids));
|
||||
$list = id(new HarbormasterBuildView())
|
||||
->setViewer($viewer)
|
||||
->setBuilds($builds)
|
||||
->newObjectList();
|
||||
|
||||
$handles = $viewer->loadHandles($phids);
|
||||
|
||||
$list = new PHUIObjectItemListView();
|
||||
foreach ($builds as $build) {
|
||||
$id = $build->getID();
|
||||
$initiator = $handles[$build->getInitiatorPHID()];
|
||||
$buildable_object = $handles[$build->getBuildable()->getBuildablePHID()];
|
||||
|
||||
$item = id(new PHUIObjectItemView())
|
||||
->setViewer($viewer)
|
||||
->setObject($build)
|
||||
->setObjectName(pht('Build %d', $build->getID()))
|
||||
->setHeader($build->getName())
|
||||
->setHref($build->getURI())
|
||||
->setEpoch($build->getDateCreated())
|
||||
->addAttribute($buildable_object->getName());
|
||||
|
||||
if ($initiator) {
|
||||
$item->addHandleIcon($initiator, $initiator->getName());
|
||||
}
|
||||
|
||||
$status = $build->getBuildStatus();
|
||||
|
||||
$status_icon = HarbormasterBuildStatus::getBuildStatusIcon($status);
|
||||
$status_color = HarbormasterBuildStatus::getBuildStatusColor($status);
|
||||
$status_label = HarbormasterBuildStatus::getBuildStatusName($status);
|
||||
|
||||
$item->setStatusIcon("{$status_icon} {$status_color}", $status_label);
|
||||
|
||||
$list->addItem($item);
|
||||
}
|
||||
|
||||
$result = new PhabricatorApplicationSearchResultView();
|
||||
$result->setObjectList($list);
|
||||
$result->setNoDataString(pht('No builds found.'));
|
||||
|
||||
return $result;
|
||||
return id(new PhabricatorApplicationSearchResultView())
|
||||
->setObjectList($list)
|
||||
->setNoDataString(pht('No builds found.'));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -183,6 +183,10 @@ final class HarbormasterBuild extends HarbormasterDAO
|
|||
return $this->getBuildStatusObject()->isPassed();
|
||||
}
|
||||
|
||||
public function isFailed() {
|
||||
return $this->getBuildStatusObject()->isFailed();
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
$id = $this->getID();
|
||||
return "/harbormaster/build/{$id}/";
|
||||
|
@ -193,6 +197,10 @@ final class HarbormasterBuild extends HarbormasterDAO
|
|||
return HarbormasterBuildStatus::newBuildStatusObject($status_key);
|
||||
}
|
||||
|
||||
public function getObjectName() {
|
||||
return pht('Build %d', $this->getID());
|
||||
}
|
||||
|
||||
|
||||
/* -( Build Commands )----------------------------------------------------- */
|
||||
|
||||
|
@ -207,11 +215,60 @@ final class HarbormasterBuild extends HarbormasterDAO
|
|||
}
|
||||
|
||||
public function canRestartBuild() {
|
||||
if ($this->isAutobuild()) {
|
||||
try {
|
||||
$this->assertCanRestartBuild();
|
||||
return true;
|
||||
} catch (HarbormasterRestartException $ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return !$this->isRestarting();
|
||||
public function assertCanRestartBuild() {
|
||||
if ($this->isAutobuild()) {
|
||||
throw new HarbormasterRestartException(
|
||||
pht('Can Not Restart Autobuild'),
|
||||
pht(
|
||||
'This build can not be restarted because it is an automatic '.
|
||||
'build.'));
|
||||
}
|
||||
|
||||
$restartable = HarbormasterBuildPlanBehavior::BEHAVIOR_RESTARTABLE;
|
||||
$plan = $this->getBuildPlan();
|
||||
|
||||
$option = HarbormasterBuildPlanBehavior::getBehavior($restartable)
|
||||
->getPlanOption($plan);
|
||||
$option_key = $option->getKey();
|
||||
|
||||
$never_restartable = HarbormasterBuildPlanBehavior::RESTARTABLE_NEVER;
|
||||
$is_never = ($option_key === $never_restartable);
|
||||
if ($is_never) {
|
||||
throw new HarbormasterRestartException(
|
||||
pht('Build Plan Prevents Restart'),
|
||||
pht(
|
||||
'This build can not be restarted because the build plan is '.
|
||||
'configured to prevent the build from restarting.'));
|
||||
}
|
||||
|
||||
$failed_restartable = HarbormasterBuildPlanBehavior::RESTARTABLE_IF_FAILED;
|
||||
$is_failed = ($option_key === $failed_restartable);
|
||||
if ($is_failed) {
|
||||
if (!$this->isFailed()) {
|
||||
throw new HarbormasterRestartException(
|
||||
pht('Only Restartable if Failed'),
|
||||
pht(
|
||||
'This build can not be restarted because the build plan is '.
|
||||
'configured to prevent the build from restarting unless it '.
|
||||
'has failed, and it has not failed.'));
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->isRestarting()) {
|
||||
throw new HarbormasterRestartException(
|
||||
pht('Already Restarting'),
|
||||
pht(
|
||||
'This build is already restarting. You can not reissue a restart '.
|
||||
'command to a restarting build.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function canPauseBuild() {
|
||||
|
@ -330,14 +387,17 @@ final class HarbormasterBuild extends HarbormasterDAO
|
|||
}
|
||||
|
||||
public function assertCanIssueCommand(PhabricatorUser $viewer, $command) {
|
||||
$need_edit = false;
|
||||
$plan = $this->getBuildPlan();
|
||||
|
||||
$need_edit = true;
|
||||
switch ($command) {
|
||||
case HarbormasterBuildCommand::COMMAND_RESTART:
|
||||
break;
|
||||
case HarbormasterBuildCommand::COMMAND_PAUSE:
|
||||
case HarbormasterBuildCommand::COMMAND_RESUME:
|
||||
case HarbormasterBuildCommand::COMMAND_ABORT:
|
||||
$need_edit = true;
|
||||
if ($plan->canRunWithoutEditCapability()) {
|
||||
$need_edit = false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Exception(
|
||||
|
@ -351,7 +411,7 @@ final class HarbormasterBuild extends HarbormasterDAO
|
|||
if ($need_edit) {
|
||||
PhabricatorPolicyFilter::requireCapability(
|
||||
$viewer,
|
||||
$this->getBuildPlan(),
|
||||
$plan,
|
||||
PhabricatorPolicyCapability::CAN_EDIT);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,13 +10,15 @@ final class HarbormasterBuildPlan extends HarbormasterDAO
|
|||
PhabricatorSubscribableInterface,
|
||||
PhabricatorNgramsInterface,
|
||||
PhabricatorConduitResultInterface,
|
||||
PhabricatorProjectInterface {
|
||||
PhabricatorProjectInterface,
|
||||
PhabricatorPolicyCodexInterface {
|
||||
|
||||
protected $name;
|
||||
protected $planStatus;
|
||||
protected $planAutoKey;
|
||||
protected $viewPolicy;
|
||||
protected $editPolicy;
|
||||
protected $properties = array();
|
||||
|
||||
const STATUS_ACTIVE = 'active';
|
||||
const STATUS_DISABLED = 'disabled';
|
||||
|
@ -45,6 +47,9 @@ final class HarbormasterBuildPlan extends HarbormasterDAO
|
|||
protected function getConfiguration() {
|
||||
return array(
|
||||
self::CONFIG_AUX_PHID => true,
|
||||
self::CONFIG_SERIALIZATION => array(
|
||||
'properties' => self::SERIALIZATION_JSON,
|
||||
),
|
||||
self::CONFIG_COLUMN_SCHEMA => array(
|
||||
'name' => 'sort128',
|
||||
'planStatus' => 'text32',
|
||||
|
@ -84,6 +89,25 @@ final class HarbormasterBuildPlan extends HarbormasterDAO
|
|||
return ($this->getPlanStatus() == self::STATUS_DISABLED);
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
return urisprintf(
|
||||
'/harbormaster/plan/%s/',
|
||||
$this->getID());
|
||||
}
|
||||
|
||||
public function getObjectName() {
|
||||
return pht('Plan %d', $this->getID());
|
||||
}
|
||||
|
||||
public function getPlanProperty($key, $default = null) {
|
||||
return idx($this->properties, $key, $default);
|
||||
}
|
||||
|
||||
public function setPlanProperty($key, $value) {
|
||||
$this->properties[$key] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/* -( Autoplans )---------------------------------------------------------- */
|
||||
|
||||
|
@ -110,7 +134,6 @@ final class HarbormasterBuildPlan extends HarbormasterDAO
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
public function getName() {
|
||||
$autoplan = $this->getAutoplan();
|
||||
if ($autoplan) {
|
||||
|
@ -120,6 +143,38 @@ final class HarbormasterBuildPlan extends HarbormasterDAO
|
|||
return parent::getName();
|
||||
}
|
||||
|
||||
public function hasRunCapability(PhabricatorUser $viewer) {
|
||||
try {
|
||||
$this->assertHasRunCapability($viewer);
|
||||
return true;
|
||||
} catch (PhabricatorPolicyException $ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function canRunWithoutEditCapability() {
|
||||
$runnable = HarbormasterBuildPlanBehavior::BEHAVIOR_RUNNABLE;
|
||||
$if_viewable = HarbormasterBuildPlanBehavior::RUNNABLE_IF_VIEWABLE;
|
||||
|
||||
$option = HarbormasterBuildPlanBehavior::getBehavior($runnable)
|
||||
->getPlanOption($this);
|
||||
|
||||
return ($option->getKey() === $if_viewable);
|
||||
}
|
||||
|
||||
public function assertHasRunCapability(PhabricatorUser $viewer) {
|
||||
if ($this->canRunWithoutEditCapability()) {
|
||||
$capability = PhabricatorPolicyCapability::CAN_VIEW;
|
||||
} else {
|
||||
$capability = PhabricatorPolicyCapability::CAN_EDIT;
|
||||
}
|
||||
|
||||
PhabricatorPolicyFilter::requireCapability(
|
||||
$viewer,
|
||||
$this,
|
||||
$capability);
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorSubscribableInterface )----------------------------------- */
|
||||
|
||||
|
@ -210,15 +265,31 @@ final class HarbormasterBuildPlan extends HarbormasterDAO
|
|||
->setKey('status')
|
||||
->setType('map<string, wild>')
|
||||
->setDescription(pht('The current status of this build plan.')),
|
||||
id(new PhabricatorConduitSearchFieldSpecification())
|
||||
->setKey('behaviors')
|
||||
->setType('map<string, string>')
|
||||
->setDescription(pht('Behavior configuration for the build plan.')),
|
||||
);
|
||||
}
|
||||
|
||||
public function getFieldValuesForConduit() {
|
||||
$behavior_map = array();
|
||||
|
||||
$behaviors = HarbormasterBuildPlanBehavior::newPlanBehaviors();
|
||||
foreach ($behaviors as $behavior) {
|
||||
$option = $behavior->getPlanOption($this);
|
||||
|
||||
$behavior_map[$behavior->getKey()] = array(
|
||||
'value' => $option->getKey(),
|
||||
);
|
||||
}
|
||||
|
||||
return array(
|
||||
'name' => $this->getName(),
|
||||
'status' => array(
|
||||
'value' => $this->getPlanStatus(),
|
||||
),
|
||||
'behaviors' => $behavior_map,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -226,4 +297,12 @@ final class HarbormasterBuildPlan extends HarbormasterDAO
|
|||
return array();
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorPolicyCodexInterface )------------------------------------ */
|
||||
|
||||
|
||||
public function newPolicyCodex() {
|
||||
return new HarbormasterBuildPlanPolicyCodex();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
<?php
|
||||
|
||||
final class HarbormasterBuildPlanTransaction
|
||||
extends PhabricatorApplicationTransaction {
|
||||
|
||||
const TYPE_NAME = 'harbormaster:name';
|
||||
const TYPE_STATUS = 'harbormaster:status';
|
||||
extends PhabricatorModularTransaction {
|
||||
|
||||
public function getApplicationName() {
|
||||
return 'harbormaster';
|
||||
|
@ -14,67 +11,8 @@ final class HarbormasterBuildPlanTransaction
|
|||
return HarbormasterBuildPlanPHIDType::TYPECONST;
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
$old = $this->getOldValue();
|
||||
$new = $this->getNewValue();
|
||||
|
||||
switch ($this->getTransactionType()) {
|
||||
case self::TYPE_NAME:
|
||||
if ($old === null) {
|
||||
return 'fa-plus';
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return parent::getIcon();
|
||||
}
|
||||
|
||||
public function getColor() {
|
||||
$old = $this->getOldValue();
|
||||
$new = $this->getNewValue();
|
||||
|
||||
switch ($this->getTransactionType()) {
|
||||
case self::TYPE_NAME:
|
||||
if ($old === null) {
|
||||
return 'green';
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return parent::getIcon();
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
$old = $this->getOldValue();
|
||||
$new = $this->getNewValue();
|
||||
$author_handle = $this->renderHandleLink($this->getAuthorPHID());
|
||||
|
||||
switch ($this->getTransactionType()) {
|
||||
case self::TYPE_NAME:
|
||||
if ($old === null) {
|
||||
return pht(
|
||||
'%s created this build plan.',
|
||||
$author_handle);
|
||||
} else {
|
||||
return pht(
|
||||
'%s renamed this build plan from "%s" to "%s".',
|
||||
$author_handle,
|
||||
$old,
|
||||
$new);
|
||||
}
|
||||
case self::TYPE_STATUS:
|
||||
if ($new == HarbormasterBuildPlan::STATUS_DISABLED) {
|
||||
return pht(
|
||||
'%s disabled this build plan.',
|
||||
$author_handle);
|
||||
} else {
|
||||
return pht(
|
||||
'%s enabled this build plan.',
|
||||
$author_handle);
|
||||
}
|
||||
}
|
||||
|
||||
return parent::getTitle();
|
||||
public function getBaseTransactionClass() {
|
||||
return 'HarbormasterBuildPlanTransactionType';
|
||||
}
|
||||
|
||||
}
|
||||
|
|
67
src/applications/harbormaster/view/HarbormasterBuildView.php
Normal file
67
src/applications/harbormaster/view/HarbormasterBuildView.php
Normal file
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
final class HarbormasterBuildView
|
||||
extends AphrontView {
|
||||
|
||||
private $builds = array();
|
||||
|
||||
public function setBuilds(array $builds) {
|
||||
assert_instances_of($builds, 'HarbormasterBuild');
|
||||
$this->builds = $builds;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBuilds() {
|
||||
return $this->builds;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
return $this->newObjectList();
|
||||
}
|
||||
|
||||
public function newObjectList() {
|
||||
$viewer = $this->getViewer();
|
||||
$builds = $this->getBuilds();
|
||||
|
||||
$buildables = mpull($builds, 'getBuildable');
|
||||
$object_phids = mpull($buildables, 'getBuildablePHID');
|
||||
$initiator_phids = mpull($builds, 'getInitiatorPHID');
|
||||
$phids = array_mergev(array($initiator_phids, $object_phids));
|
||||
$phids = array_unique(array_filter($phids));
|
||||
|
||||
$handles = $viewer->loadHandles($phids);
|
||||
|
||||
$list = new PHUIObjectItemListView();
|
||||
foreach ($builds as $build) {
|
||||
$id = $build->getID();
|
||||
$initiator = $handles[$build->getInitiatorPHID()];
|
||||
$buildable_object = $handles[$build->getBuildable()->getBuildablePHID()];
|
||||
|
||||
$item = id(new PHUIObjectItemView())
|
||||
->setViewer($viewer)
|
||||
->setObject($build)
|
||||
->setObjectName($build->getObjectName())
|
||||
->setHeader($build->getName())
|
||||
->setHref($build->getURI())
|
||||
->setEpoch($build->getDateCreated())
|
||||
->addAttribute($buildable_object->getName());
|
||||
|
||||
if ($initiator) {
|
||||
$item->addByline($initiator->renderLink());
|
||||
}
|
||||
|
||||
$status = $build->getBuildStatus();
|
||||
|
||||
$status_icon = HarbormasterBuildStatus::getBuildStatusIcon($status);
|
||||
$status_color = HarbormasterBuildStatus::getBuildStatusColor($status);
|
||||
$status_label = HarbormasterBuildStatus::getBuildStatusName($status);
|
||||
|
||||
$item->setStatusIcon("{$status_icon} {$status_color}", $status_label);
|
||||
|
||||
$list->addItem($item);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
<?php
|
||||
|
||||
final class HarbormasterBuildPlanBehaviorTransaction
|
||||
extends HarbormasterBuildPlanTransactionType {
|
||||
|
||||
const TRANSACTIONTYPE = 'behavior';
|
||||
|
||||
public function generateOldValue($object) {
|
||||
$behavior = $this->getBehavior();
|
||||
return $behavior->getPlanOption($object)->getKey();
|
||||
}
|
||||
|
||||
public function applyInternalEffects($object, $value) {
|
||||
$key = $this->getStorageKey();
|
||||
return $object->setPlanProperty($key, $value);
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
$old_value = $this->getOldValue();
|
||||
$new_value = $this->getNewValue();
|
||||
|
||||
$behavior = $this->getBehavior();
|
||||
if ($behavior) {
|
||||
$behavior_name = $behavior->getName();
|
||||
|
||||
$options = $behavior->getOptions();
|
||||
if (isset($options[$old_value])) {
|
||||
$old_value = $options[$old_value]->getName();
|
||||
}
|
||||
|
||||
if (isset($options[$new_value])) {
|
||||
$new_value = $options[$new_value]->getName();
|
||||
}
|
||||
} else {
|
||||
$behavior_name = $this->getBehaviorKey();
|
||||
}
|
||||
|
||||
return pht(
|
||||
'%s changed the %s behavior for this plan from %s to %s.',
|
||||
$this->renderAuthor(),
|
||||
$this->renderValue($behavior_name),
|
||||
$this->renderValue($old_value),
|
||||
$this->renderValue($new_value));
|
||||
}
|
||||
|
||||
public function validateTransactions($object, array $xactions) {
|
||||
$errors = array();
|
||||
|
||||
$behaviors = HarbormasterBuildPlanBehavior::newPlanBehaviors();
|
||||
$behaviors = mpull($behaviors, null, 'getKey');
|
||||
|
||||
foreach ($xactions as $xaction) {
|
||||
$key = $this->getBehaviorKeyForTransaction($xaction);
|
||||
|
||||
if (!isset($behaviors[$key])) {
|
||||
$errors[] = $this->newInvalidError(
|
||||
pht(
|
||||
'No behavior with key "%s" exists. Valid keys are: %s.',
|
||||
$key,
|
||||
implode(', ', array_keys($behaviors))),
|
||||
$xaction);
|
||||
continue;
|
||||
}
|
||||
|
||||
$behavior = $behaviors[$key];
|
||||
$options = $behavior->getOptions();
|
||||
|
||||
$storage_key = HarbormasterBuildPlanBehavior::getStorageKeyForBehaviorKey(
|
||||
$key);
|
||||
$old = $object->getPlanProperty($storage_key);
|
||||
$new = $xaction->getNewValue();
|
||||
|
||||
if ($old === $new) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($options[$new])) {
|
||||
$errors[] = $this->newInvalidError(
|
||||
pht(
|
||||
'Value "%s" is not a valid option for behavior "%s". Valid '.
|
||||
'options are: %s.',
|
||||
$new,
|
||||
$key,
|
||||
implode(', ', array_keys($options))),
|
||||
$xaction);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
public function getTransactionTypeForConduit($xaction) {
|
||||
return 'behavior';
|
||||
}
|
||||
|
||||
public function getFieldValuesForConduit($xaction, $data) {
|
||||
return array(
|
||||
'key' => $this->getBehaviorKeyForTransaction($xaction),
|
||||
'old' => $xaction->getOldValue(),
|
||||
'new' => $xaction->getNewValue(),
|
||||
);
|
||||
}
|
||||
|
||||
private function getBehaviorKeyForTransaction(
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
$metadata_key = HarbormasterBuildPlanBehavior::getTransactionMetadataKey();
|
||||
return $xaction->getMetadataValue($metadata_key);
|
||||
}
|
||||
|
||||
private function getBehaviorKey() {
|
||||
$metadata_key = HarbormasterBuildPlanBehavior::getTransactionMetadataKey();
|
||||
return $this->getMetadataValue($metadata_key);
|
||||
}
|
||||
|
||||
private function getBehavior() {
|
||||
$behavior_key = $this->getBehaviorKey();
|
||||
$behaviors = HarbormasterBuildPlanBehavior::newPlanBehaviors();
|
||||
return idx($behaviors, $behavior_key);
|
||||
}
|
||||
|
||||
private function getStorageKey() {
|
||||
return HarbormasterBuildPlanBehavior::getStorageKeyForBehaviorKey(
|
||||
$this->getBehaviorKey());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
final class HarbormasterBuildPlanNameTransaction
|
||||
extends HarbormasterBuildPlanTransactionType {
|
||||
|
||||
const TRANSACTIONTYPE = 'harbormaster:name';
|
||||
|
||||
public function generateOldValue($object) {
|
||||
return $object->getName();
|
||||
}
|
||||
|
||||
public function applyInternalEffects($object, $value) {
|
||||
$object->setName($value);
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
return pht(
|
||||
'%s renamed this build plan from "%s" to "%s".',
|
||||
$this->renderAuthor(),
|
||||
$this->renderOldValue(),
|
||||
$this->renderNewValue());
|
||||
}
|
||||
|
||||
public function validateTransactions($object, array $xactions) {
|
||||
$errors = array();
|
||||
|
||||
if ($this->isEmptyTextTransaction($object->getName(), $xactions)) {
|
||||
$errors[] = $this->newRequiredError(
|
||||
pht('You must choose a name for your build plan.'));
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
public function getTransactionTypeForConduit($xaction) {
|
||||
return 'name';
|
||||
}
|
||||
|
||||
public function getFieldValuesForConduit($xaction, $data) {
|
||||
return array(
|
||||
'old' => $xaction->getOldValue(),
|
||||
'new' => $xaction->getNewValue(),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
final class HarbormasterBuildPlanStatusTransaction
|
||||
extends HarbormasterBuildPlanTransactionType {
|
||||
|
||||
const TRANSACTIONTYPE = 'harbormaster:status';
|
||||
|
||||
public function generateOldValue($object) {
|
||||
return $object->getPlanStatus();
|
||||
}
|
||||
|
||||
public function applyInternalEffects($object, $value) {
|
||||
$object->setPlanStatus($value);
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
$new = $this->getNewValue();
|
||||
if ($new === HarbormasterBuildPlan::STATUS_DISABLED) {
|
||||
return pht(
|
||||
'%s disabled this build plan.',
|
||||
$this->renderAuthor());
|
||||
} else {
|
||||
return pht(
|
||||
'%s enabled this build plan.',
|
||||
$this->renderAuthor());
|
||||
}
|
||||
}
|
||||
|
||||
public function validateTransactions($object, array $xactions) {
|
||||
$errors = array();
|
||||
|
||||
$options = array(
|
||||
HarbormasterBuildPlan::STATUS_DISABLED,
|
||||
HarbormasterBuildPlan::STATUS_ACTIVE,
|
||||
);
|
||||
$options = array_fuse($options);
|
||||
|
||||
foreach ($xactions as $xaction) {
|
||||
$new = $xaction->getNewValue();
|
||||
|
||||
if (!isset($options[$new])) {
|
||||
$errors[] = $this->newInvalidError(
|
||||
pht(
|
||||
'Status "%s" is not a valid build plan status. Valid '.
|
||||
'statuses are: %s.',
|
||||
$new,
|
||||
implode(', ', $options)));
|
||||
continue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
public function getTransactionTypeForConduit($xaction) {
|
||||
return 'status';
|
||||
}
|
||||
|
||||
public function getFieldValuesForConduit($xaction, $data) {
|
||||
return array(
|
||||
'old' => $xaction->getOldValue(),
|
||||
'new' => $xaction->getNewValue(),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
<?php
|
||||
|
||||
abstract class HarbormasterBuildPlanTransactionType
|
||||
extends PhabricatorModularTransactionType {}
|
|
@ -401,4 +401,8 @@ abstract class HeraldAction extends Phobject {
|
|||
return null;
|
||||
}
|
||||
|
||||
public function getPHIDsAffectedByAction(HeraldActionRecord $record) {
|
||||
return array();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -63,4 +63,8 @@ final class HeraldCallWebhookAction extends HeraldAction {
|
|||
return new HeraldWebhookDatasource();
|
||||
}
|
||||
|
||||
public function getPHIDsAffectedByAction(HeraldActionRecord $record) {
|
||||
return $record->getTarget();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -186,15 +186,16 @@ abstract class HeraldAdapter extends Phobject {
|
|||
return $this->appliedTransactions;
|
||||
}
|
||||
|
||||
public function queueTransaction($transaction) {
|
||||
final public function queueTransaction(
|
||||
PhabricatorApplicationTransaction $transaction) {
|
||||
$this->queuedTransactions[] = $transaction;
|
||||
}
|
||||
|
||||
public function getQueuedTransactions() {
|
||||
final public function getQueuedTransactions() {
|
||||
return $this->queuedTransactions;
|
||||
}
|
||||
|
||||
public function newTransaction() {
|
||||
final public function newTransaction() {
|
||||
$object = $this->newObject();
|
||||
|
||||
if (!($object instanceof PhabricatorApplicationTransactionInterface)) {
|
||||
|
@ -205,7 +206,19 @@ abstract class HeraldAdapter extends Phobject {
|
|||
'PhabricatorApplicationTransactionInterface'));
|
||||
}
|
||||
|
||||
return $object->getApplicationTransactionTemplate();
|
||||
$xaction = $object->getApplicationTransactionTemplate();
|
||||
|
||||
if (!($xaction instanceof PhabricatorApplicationTransaction)) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Expected object (of class "%s") to return a transaction template '.
|
||||
'(of class "%s"), but it returned something else ("%s").',
|
||||
get_class($object),
|
||||
'PhabricatorApplicationTransaction',
|
||||
phutil_describe_type($xaction)));
|
||||
}
|
||||
|
||||
return $xaction;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ final class HeraldDisableController extends HeraldController {
|
|||
|
||||
if ($request->isFormPost()) {
|
||||
$xaction = id(new HeraldRuleTransaction())
|
||||
->setTransactionType(HeraldRuleTransaction::TYPE_DISABLE)
|
||||
->setTransactionType(HeraldRuleDisableTransaction::TRANSACTIONTYPE)
|
||||
->setNewValue($is_disable);
|
||||
|
||||
id(new HeraldRuleEditor())
|
||||
|
|
|
@ -359,11 +359,21 @@ final class HeraldRuleController extends HeraldController {
|
|||
$repetition_policy);
|
||||
|
||||
$xactions = array();
|
||||
|
||||
// Until this moves to EditEngine, manually add a "CREATE" transaction
|
||||
// if we're creating a new rule. This improves rendering of the initial
|
||||
// group of transactions.
|
||||
$is_new = (bool)(!$rule->getID());
|
||||
if ($is_new) {
|
||||
$xactions[] = id(new HeraldRuleTransaction())
|
||||
->setTransactionType(PhabricatorTransactions::TYPE_CREATE);
|
||||
}
|
||||
|
||||
$xactions[] = id(new HeraldRuleTransaction())
|
||||
->setTransactionType(HeraldRuleTransaction::TYPE_EDIT)
|
||||
->setTransactionType(HeraldRuleEditTransaction::TRANSACTIONTYPE)
|
||||
->setNewValue($new_state);
|
||||
$xactions[] = id(new HeraldRuleTransaction())
|
||||
->setTransactionType(HeraldRuleTransaction::TYPE_NAME)
|
||||
->setTransactionType(HeraldRuleNameTransaction::TRANSACTIONTYPE)
|
||||
->setNewValue($new_name);
|
||||
|
||||
try {
|
||||
|
|
|
@ -73,12 +73,15 @@ final class HeraldWebhookViewController
|
|||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->setTable($requests_table);
|
||||
|
||||
$rules_view = $this->newRulesView($hook);
|
||||
|
||||
$hook_view = id(new PHUITwoColumnView())
|
||||
->setHeader($header)
|
||||
->setMainColumn(
|
||||
array(
|
||||
$warnings,
|
||||
$properties_view,
|
||||
$rules_view,
|
||||
$requests_view,
|
||||
$timeline,
|
||||
))
|
||||
|
@ -194,4 +197,42 @@ final class HeraldWebhookViewController
|
|||
->appendChild($properties);
|
||||
}
|
||||
|
||||
private function newRulesView(HeraldWebhook $hook) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$rules = id(new HeraldRuleQuery())
|
||||
->setViewer($viewer)
|
||||
->withDisabled(false)
|
||||
->withAffectedObjectPHIDs(array($hook->getPHID()))
|
||||
->needValidateAuthors(true)
|
||||
->setLimit(10)
|
||||
->execute();
|
||||
|
||||
$list = id(new HeraldRuleListView())
|
||||
->setViewer($viewer)
|
||||
->setRules($rules)
|
||||
->newObjectList();
|
||||
|
||||
$list->setNoDataString(pht('No active Herald rules call this webhook.'));
|
||||
|
||||
$more_href = new PhutilURI(
|
||||
'/herald/',
|
||||
array('affectedPHID' => $hook->getPHID()));
|
||||
|
||||
$more_link = id(new PHUIButtonView())
|
||||
->setTag('a')
|
||||
->setIcon('fa-list-ul')
|
||||
->setText(pht('View All Rules'))
|
||||
->setHref($more_href);
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader(pht('Called By Herald Rules'))
|
||||
->addActionLink($more_link);
|
||||
|
||||
return id(new PHUIObjectBoxView())
|
||||
->setHeader($header)
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->appendChild($list);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
final class HeraldRuleActionAffectsObjectEdgeType
|
||||
extends PhabricatorEdgeType {
|
||||
|
||||
const EDGECONST = 69;
|
||||
|
||||
}
|
|
@ -11,82 +11,6 @@ final class HeraldRuleEditor
|
|||
return pht('Herald Rules');
|
||||
}
|
||||
|
||||
public function getTransactionTypes() {
|
||||
$types = parent::getTransactionTypes();
|
||||
|
||||
$types[] = PhabricatorTransactions::TYPE_COMMENT;
|
||||
$types[] = HeraldRuleTransaction::TYPE_EDIT;
|
||||
$types[] = HeraldRuleTransaction::TYPE_NAME;
|
||||
$types[] = HeraldRuleTransaction::TYPE_DISABLE;
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
protected function getCustomTransactionOldValue(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case HeraldRuleTransaction::TYPE_DISABLE:
|
||||
return (int)$object->getIsDisabled();
|
||||
case HeraldRuleTransaction::TYPE_EDIT:
|
||||
return id(new HeraldRuleSerializer())
|
||||
->serializeRule($object);
|
||||
case HeraldRuleTransaction::TYPE_NAME:
|
||||
return $object->getName();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected function getCustomTransactionNewValue(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case HeraldRuleTransaction::TYPE_DISABLE:
|
||||
return (int)$xaction->getNewValue();
|
||||
case HeraldRuleTransaction::TYPE_EDIT:
|
||||
case HeraldRuleTransaction::TYPE_NAME:
|
||||
return $xaction->getNewValue();
|
||||
}
|
||||
}
|
||||
|
||||
protected function applyCustomInternalTransaction(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case HeraldRuleTransaction::TYPE_DISABLE:
|
||||
return $object->setIsDisabled($xaction->getNewValue());
|
||||
case HeraldRuleTransaction::TYPE_NAME:
|
||||
return $object->setName($xaction->getNewValue());
|
||||
case HeraldRuleTransaction::TYPE_EDIT:
|
||||
$new_state = id(new HeraldRuleSerializer())
|
||||
->deserializeRuleComponents($xaction->getNewValue());
|
||||
$object->setMustMatchAll((int)$new_state['match_all']);
|
||||
$object->attachConditions($new_state['conditions']);
|
||||
$object->attachActions($new_state['actions']);
|
||||
|
||||
$new_repetition = $new_state['repetition_policy'];
|
||||
$object->setRepetitionPolicyStringConstant($new_repetition);
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected function applyCustomExternalTransaction(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case HeraldRuleTransaction::TYPE_EDIT:
|
||||
$object->saveConditions($object->getConditions());
|
||||
$object->saveActions($object->getActions());
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
protected function shouldApplyHeraldRules(
|
||||
PhabricatorLiskDAO $object,
|
||||
array $xactions) {
|
||||
|
@ -137,7 +61,6 @@ final class HeraldRuleEditor
|
|||
return pht('[Herald]');
|
||||
}
|
||||
|
||||
|
||||
protected function buildMailBody(
|
||||
PhabricatorLiskDAO $object,
|
||||
array $xactions) {
|
||||
|
@ -151,4 +74,8 @@ final class HeraldRuleEditor
|
|||
return $body;
|
||||
}
|
||||
|
||||
protected function supportsSearch() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
final class HeraldRuleIndexEngineExtension
|
||||
extends PhabricatorIndexEngineExtension {
|
||||
|
||||
const EXTENSIONKEY = 'herald.actions';
|
||||
|
||||
public function getExtensionName() {
|
||||
return pht('Herald Actions');
|
||||
}
|
||||
|
||||
public function shouldIndexObject($object) {
|
||||
if (!($object instanceof HeraldRule)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function indexObject(
|
||||
PhabricatorIndexEngine $engine,
|
||||
$object) {
|
||||
|
||||
$edge_type = HeraldRuleActionAffectsObjectEdgeType::EDGECONST;
|
||||
|
||||
$old_edges = PhabricatorEdgeQuery::loadDestinationPHIDs(
|
||||
$object->getPHID(),
|
||||
$edge_type);
|
||||
$old_edges = array_fuse($old_edges);
|
||||
|
||||
$new_edges = $this->getPHIDsAffectedByActions($object);
|
||||
$new_edges = array_fuse($new_edges);
|
||||
|
||||
$add_edges = array_diff_key($new_edges, $old_edges);
|
||||
$rem_edges = array_diff_key($old_edges, $new_edges);
|
||||
|
||||
if (!$add_edges && !$rem_edges) {
|
||||
return;
|
||||
}
|
||||
|
||||
$editor = new PhabricatorEdgeEditor();
|
||||
|
||||
foreach ($add_edges as $phid) {
|
||||
$editor->addEdge($object->getPHID(), $edge_type, $phid);
|
||||
}
|
||||
|
||||
foreach ($rem_edges as $phid) {
|
||||
$editor->removeEdge($object->getPHID(), $edge_type, $phid);
|
||||
}
|
||||
|
||||
$editor->save();
|
||||
}
|
||||
|
||||
public function getIndexVersion($object) {
|
||||
$phids = $this->getPHIDsAffectedByActions($object);
|
||||
sort($phids);
|
||||
$phids = implode(':', $phids);
|
||||
return PhabricatorHash::digestForIndex($phids);
|
||||
}
|
||||
|
||||
private function getPHIDsAffectedByActions(HeraldRule $rule) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$rule = id(new HeraldRuleQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($rule->getID()))
|
||||
->needConditionsAndActions(true)
|
||||
->executeOne();
|
||||
if (!$rule) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$phids = array();
|
||||
|
||||
$actions = HeraldAction::getAllActions();
|
||||
foreach ($rule->getActions() as $action_record) {
|
||||
$action = idx($actions, $action_record->getAction());
|
||||
|
||||
if (!$action) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($action->getPHIDsAffectedByAction($action_record) as $phid) {
|
||||
$phids[] = $phid;
|
||||
}
|
||||
}
|
||||
|
||||
$phids = array_fuse($phids);
|
||||
return array_keys($phids);
|
||||
}
|
||||
|
||||
}
|
|
@ -11,6 +11,7 @@ final class HeraldRuleQuery extends PhabricatorCursorPagedPolicyAwareQuery {
|
|||
private $active;
|
||||
private $datasourceQuery;
|
||||
private $triggerObjectPHIDs;
|
||||
private $affectedObjectPHIDs;
|
||||
|
||||
private $needConditionsAndActions;
|
||||
private $needAppliedToPHIDs;
|
||||
|
@ -61,6 +62,11 @@ final class HeraldRuleQuery extends PhabricatorCursorPagedPolicyAwareQuery {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function withAffectedObjectPHIDs(array $phids) {
|
||||
$this->affectedObjectPHIDs = $phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function needConditionsAndActions($need) {
|
||||
$this->needConditionsAndActions = $need;
|
||||
return $this;
|
||||
|
@ -261,9 +267,31 @@ final class HeraldRuleQuery extends PhabricatorCursorPagedPolicyAwareQuery {
|
|||
$this->triggerObjectPHIDs);
|
||||
}
|
||||
|
||||
if ($this->affectedObjectPHIDs !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'edge_affects.dst IN (%Ls)',
|
||||
$this->affectedObjectPHIDs);
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
|
||||
$joins = parent::buildJoinClauseParts($conn);
|
||||
|
||||
if ($this->affectedObjectPHIDs !== null) {
|
||||
$joins[] = qsprintf(
|
||||
$conn,
|
||||
'JOIN %T edge_affects ON rule.phid = edge_affects.src
|
||||
AND edge_affects.type = %d',
|
||||
PhabricatorEdgeConfig::TABLE_NAME_EDGE,
|
||||
HeraldRuleActionAffectsObjectEdgeType::EDGECONST);
|
||||
}
|
||||
|
||||
return $joins;
|
||||
}
|
||||
|
||||
private function validateRuleAuthors(array $rules) {
|
||||
// "Global" and "Object" rules always have valid authors.
|
||||
foreach ($rules as $key => $rule) {
|
||||
|
|
|
@ -55,6 +55,10 @@ final class HeraldRuleSearchEngine extends PhabricatorApplicationSearchEngine {
|
|||
pht('(Show All)'),
|
||||
pht('Show Only Disabled Rules'),
|
||||
pht('Show Only Enabled Rules')),
|
||||
id(new PhabricatorPHIDsSearchField())
|
||||
->setLabel(pht('Affected Objects'))
|
||||
->setKey('affectedPHIDs')
|
||||
->setAliases(array('affectedPHID')),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -81,6 +85,10 @@ final class HeraldRuleSearchEngine extends PhabricatorApplicationSearchEngine {
|
|||
$query->withActive($map['active']);
|
||||
}
|
||||
|
||||
if ($map['affectedPHIDs']) {
|
||||
$query->withAffectedObjectPHIDs($map['affectedPHIDs']);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
|
@ -127,54 +135,18 @@ final class HeraldRuleSearchEngine extends PhabricatorApplicationSearchEngine {
|
|||
PhabricatorSavedQuery $query,
|
||||
array $handles) {
|
||||
assert_instances_of($rules, 'HeraldRule');
|
||||
|
||||
$viewer = $this->requireViewer();
|
||||
$handles = $viewer->loadHandles(mpull($rules, 'getAuthorPHID'));
|
||||
|
||||
$content_type_map = HeraldAdapter::getEnabledAdapterMap($viewer);
|
||||
|
||||
$list = id(new PHUIObjectItemListView())
|
||||
->setUser($viewer);
|
||||
foreach ($rules as $rule) {
|
||||
$monogram = $rule->getMonogram();
|
||||
|
||||
$item = id(new PHUIObjectItemView())
|
||||
->setObjectName($monogram)
|
||||
->setHeader($rule->getName())
|
||||
->setHref("/{$monogram}");
|
||||
|
||||
if ($rule->isPersonalRule()) {
|
||||
$item->addIcon('fa-user', pht('Personal Rule'));
|
||||
$item->addByline(
|
||||
pht(
|
||||
'Authored by %s',
|
||||
$handles[$rule->getAuthorPHID()]->renderLink()));
|
||||
} else if ($rule->isObjectRule()) {
|
||||
$item->addIcon('fa-briefcase', pht('Object Rule'));
|
||||
} else {
|
||||
$item->addIcon('fa-globe', pht('Global Rule'));
|
||||
}
|
||||
|
||||
if ($rule->getIsDisabled()) {
|
||||
$item->setDisabled(true);
|
||||
$item->addIcon('fa-lock grey', pht('Disabled'));
|
||||
} else if (!$rule->hasValidAuthor()) {
|
||||
$item->setDisabled(true);
|
||||
$item->addIcon('fa-user grey', pht('Author Not Active'));
|
||||
}
|
||||
|
||||
$content_type_name = idx($content_type_map, $rule->getContentType());
|
||||
$item->addAttribute(pht('Affects: %s', $content_type_name));
|
||||
|
||||
$list->addItem($item);
|
||||
}
|
||||
$list = id(new HeraldRuleListView())
|
||||
->setViewer($viewer)
|
||||
->setRules($rules)
|
||||
->newObjectList();
|
||||
|
||||
$result = new PhabricatorApplicationSearchResultView();
|
||||
$result->setObjectList($list);
|
||||
$result->setNoDataString(pht('No rules found.'));
|
||||
|
||||
return $result;
|
||||
|
||||
}
|
||||
|
||||
protected function getNewUserBody() {
|
||||
|
|
|
@ -6,6 +6,7 @@ final class HeraldRule extends HeraldDAO
|
|||
PhabricatorFlaggableInterface,
|
||||
PhabricatorPolicyInterface,
|
||||
PhabricatorDestructibleInterface,
|
||||
PhabricatorIndexableInterface,
|
||||
PhabricatorSubscribableInterface {
|
||||
|
||||
const TABLE_RULE_APPLIED = 'herald_ruleapplied';
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
<?php
|
||||
|
||||
final class HeraldRuleTransaction
|
||||
extends PhabricatorApplicationTransaction {
|
||||
extends PhabricatorModularTransaction {
|
||||
|
||||
const TYPE_EDIT = 'herald:edit';
|
||||
const TYPE_NAME = 'herald:name';
|
||||
const TYPE_DISABLE = 'herald:disable';
|
||||
|
||||
public function getApplicationName() {
|
||||
return 'herald';
|
||||
|
@ -15,121 +13,8 @@ final class HeraldRuleTransaction
|
|||
return HeraldRulePHIDType::TYPECONST;
|
||||
}
|
||||
|
||||
public function getApplicationTransactionCommentObject() {
|
||||
return new HeraldRuleTransactionComment();
|
||||
}
|
||||
|
||||
public function getColor() {
|
||||
$old = $this->getOldValue();
|
||||
$new = $this->getNewValue();
|
||||
|
||||
switch ($this->getTransactionType()) {
|
||||
case self::TYPE_DISABLE:
|
||||
if ($new) {
|
||||
return 'red';
|
||||
} else {
|
||||
return 'green';
|
||||
}
|
||||
}
|
||||
|
||||
return parent::getColor();
|
||||
}
|
||||
|
||||
public function getActionName() {
|
||||
$old = $this->getOldValue();
|
||||
$new = $this->getNewValue();
|
||||
|
||||
switch ($this->getTransactionType()) {
|
||||
case self::TYPE_DISABLE:
|
||||
if ($new) {
|
||||
return pht('Disabled');
|
||||
} else {
|
||||
return pht('Enabled');
|
||||
}
|
||||
case self::TYPE_NAME:
|
||||
return pht('Renamed');
|
||||
}
|
||||
|
||||
return parent::getActionName();
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
$old = $this->getOldValue();
|
||||
$new = $this->getNewValue();
|
||||
|
||||
switch ($this->getTransactionType()) {
|
||||
case self::TYPE_DISABLE:
|
||||
if ($new) {
|
||||
return 'fa-ban';
|
||||
} else {
|
||||
return 'fa-check';
|
||||
}
|
||||
}
|
||||
|
||||
return parent::getIcon();
|
||||
}
|
||||
|
||||
|
||||
public function getTitle() {
|
||||
$author_phid = $this->getAuthorPHID();
|
||||
|
||||
$old = $this->getOldValue();
|
||||
$new = $this->getNewValue();
|
||||
|
||||
switch ($this->getTransactionType()) {
|
||||
case self::TYPE_DISABLE:
|
||||
if ($new) {
|
||||
return pht(
|
||||
'%s disabled this rule.',
|
||||
$this->renderHandleLink($author_phid));
|
||||
} else {
|
||||
return pht(
|
||||
'%s enabled this rule.',
|
||||
$this->renderHandleLink($author_phid));
|
||||
}
|
||||
case self::TYPE_NAME:
|
||||
if ($old == null) {
|
||||
return pht(
|
||||
'%s created this rule.',
|
||||
$this->renderHandleLink($author_phid));
|
||||
} else {
|
||||
return pht(
|
||||
'%s renamed this rule from "%s" to "%s".',
|
||||
$this->renderHandleLink($author_phid),
|
||||
$old,
|
||||
$new);
|
||||
}
|
||||
case self::TYPE_EDIT:
|
||||
return pht(
|
||||
'%s edited this rule.',
|
||||
$this->renderHandleLink($author_phid));
|
||||
}
|
||||
|
||||
return parent::getTitle();
|
||||
}
|
||||
|
||||
public function hasChangeDetails() {
|
||||
switch ($this->getTransactionType()) {
|
||||
case self::TYPE_EDIT:
|
||||
return true;
|
||||
}
|
||||
return parent::hasChangeDetails();
|
||||
}
|
||||
|
||||
public function renderChangeDetails(PhabricatorUser $viewer) {
|
||||
$json = new PhutilJSON();
|
||||
switch ($this->getTransactionType()) {
|
||||
case self::TYPE_EDIT:
|
||||
return $this->renderTextCorpusChangeDetails(
|
||||
$viewer,
|
||||
$json->encodeFormatted($this->getOldValue()),
|
||||
$json->encodeFormatted($this->getNewValue()));
|
||||
}
|
||||
|
||||
return $this->renderTextCorpusChangeDetails(
|
||||
$viewer,
|
||||
$this->getOldValue(),
|
||||
$this->getNewValue());
|
||||
public function getBaseTransactionClass() {
|
||||
return 'HeraldRuleTransactionType';
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
<?php
|
||||
|
||||
final class HeraldRuleTransactionComment
|
||||
extends PhabricatorApplicationTransactionComment {
|
||||
|
||||
public function getApplicationTransactionObject() {
|
||||
return new HeraldRuleTransaction();
|
||||
}
|
||||
|
||||
}
|
65
src/applications/herald/view/HeraldRuleListView.php
Normal file
65
src/applications/herald/view/HeraldRuleListView.php
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
final class HeraldRuleListView
|
||||
extends AphrontView {
|
||||
|
||||
private $rules;
|
||||
|
||||
public function setRules(array $rules) {
|
||||
assert_instances_of($rules, 'HeraldRule');
|
||||
$this->rules = $rules;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
return $this->newObjectList();
|
||||
}
|
||||
|
||||
public function newObjectList() {
|
||||
$viewer = $this->getViewer();
|
||||
$rules = $this->rules;
|
||||
|
||||
$handles = $viewer->loadHandles(mpull($rules, 'getAuthorPHID'));
|
||||
|
||||
$content_type_map = HeraldAdapter::getEnabledAdapterMap($viewer);
|
||||
|
||||
$list = id(new PHUIObjectItemListView())
|
||||
->setViewer($viewer);
|
||||
foreach ($rules as $rule) {
|
||||
$monogram = $rule->getMonogram();
|
||||
|
||||
$item = id(new PHUIObjectItemView())
|
||||
->setObjectName($monogram)
|
||||
->setHeader($rule->getName())
|
||||
->setHref($rule->getURI());
|
||||
|
||||
if ($rule->isPersonalRule()) {
|
||||
$item->addIcon('fa-user', pht('Personal Rule'));
|
||||
$item->addByline(
|
||||
pht(
|
||||
'Authored by %s',
|
||||
$handles[$rule->getAuthorPHID()]->renderLink()));
|
||||
} else if ($rule->isObjectRule()) {
|
||||
$item->addIcon('fa-briefcase', pht('Object Rule'));
|
||||
} else {
|
||||
$item->addIcon('fa-globe', pht('Global Rule'));
|
||||
}
|
||||
|
||||
if ($rule->getIsDisabled()) {
|
||||
$item->setDisabled(true);
|
||||
$item->addIcon('fa-lock grey', pht('Disabled'));
|
||||
} else if (!$rule->hasValidAuthor()) {
|
||||
$item->setDisabled(true);
|
||||
$item->addIcon('fa-user grey', pht('Author Not Active'));
|
||||
}
|
||||
|
||||
$content_type_name = idx($content_type_map, $rule->getContentType());
|
||||
$item->addAttribute(pht('Affects: %s', $content_type_name));
|
||||
|
||||
$list->addItem($item);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
final class HeraldRuleDisableTransaction
|
||||
extends HeraldRuleTransactionType {
|
||||
|
||||
const TRANSACTIONTYPE = 'herald:disable';
|
||||
|
||||
public function generateOldValue($object) {
|
||||
return (bool)$object->getIsDisabled();
|
||||
}
|
||||
|
||||
public function generateNewValue($object, $value) {
|
||||
return (bool)$value;
|
||||
}
|
||||
|
||||
public function applyInternalEffects($object, $value) {
|
||||
$object->setIsDisabled((int)$value);
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
if ($this->getNewValue()) {
|
||||
return pht(
|
||||
'%s disabled this rule.',
|
||||
$this->renderAuthor());
|
||||
} else {
|
||||
return pht(
|
||||
'%s enabled this rule.',
|
||||
$this->renderAuthor());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
final class HeraldRuleEditTransaction
|
||||
extends HeraldRuleTransactionType {
|
||||
|
||||
const TRANSACTIONTYPE = 'herald:edit';
|
||||
|
||||
public function generateOldValue($object) {
|
||||
return id(new HeraldRuleSerializer())
|
||||
->serializeRule($object);
|
||||
}
|
||||
|
||||
public function applyInternalEffects($object, $value) {
|
||||
$new_state = id(new HeraldRuleSerializer())
|
||||
->deserializeRuleComponents($value);
|
||||
|
||||
$object->setMustMatchAll((int)$new_state['match_all']);
|
||||
$object->attachConditions($new_state['conditions']);
|
||||
$object->attachActions($new_state['actions']);
|
||||
|
||||
$new_repetition = $new_state['repetition_policy'];
|
||||
$object->setRepetitionPolicyStringConstant($new_repetition);
|
||||
}
|
||||
|
||||
public function applyExternalEffects($object, $value) {
|
||||
$object->saveConditions($object->getConditions());
|
||||
$object->saveActions($object->getActions());
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
return pht(
|
||||
'%s edited this rule.',
|
||||
$this->renderAuthor());
|
||||
}
|
||||
|
||||
public function hasChangeDetailView() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function newChangeDetailView() {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$old = $this->getOldValue();
|
||||
$new = $this->getNewValue();
|
||||
|
||||
$json = new PhutilJSON();
|
||||
$old_json = $json->encodeFormatted($old);
|
||||
$new_json = $json->encodeFormatted($new);
|
||||
|
||||
return id(new PhabricatorApplicationTransactionTextDiffDetailView())
|
||||
->setViewer($viewer)
|
||||
->setOldText($old_json)
|
||||
->setNewText($new_json);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
final class HeraldRuleNameTransaction
|
||||
extends HeraldRuleTransactionType {
|
||||
|
||||
const TRANSACTIONTYPE = 'herald:name';
|
||||
|
||||
public function generateOldValue($object) {
|
||||
return $object->getName();
|
||||
}
|
||||
|
||||
public function applyInternalEffects($object, $value) {
|
||||
$object->setName($value);
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
return pht(
|
||||
'%s renamed this rule from %s to %s.',
|
||||
$this->renderAuthor(),
|
||||
$this->renderOldValue(),
|
||||
$this->renderNewValue());
|
||||
}
|
||||
|
||||
public function validateTransactions($object, array $xactions) {
|
||||
$errors = array();
|
||||
|
||||
if ($this->isEmptyTextTransaction($object->getName(), $xactions)) {
|
||||
$errors[] = $this->newRequiredError(
|
||||
pht('Rules must have a name.'));
|
||||
}
|
||||
|
||||
$max_length = $object->getColumnMaximumByteLength('name');
|
||||
foreach ($xactions as $xaction) {
|
||||
$new_value = $xaction->getNewValue();
|
||||
|
||||
$new_length = strlen($new_value);
|
||||
if ($new_length > $max_length) {
|
||||
$errors[] = $this->newInvalidError(
|
||||
pht(
|
||||
'Rule names can be no longer than %s characters.',
|
||||
new PhutilNumber($max_length)));
|
||||
}
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
<?php
|
||||
|
||||
abstract class HeraldRuleTransactionType
|
||||
extends PhabricatorModularTransactionType {}
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
final class ManiphestTaskUnlockEngine
|
||||
extends PhabricatorUnlockEngine {
|
||||
|
||||
public function newUnlockOwnerTransactions($object, $user) {
|
||||
return array(
|
||||
$this->newTransaction($object)
|
||||
->setTransactionType(ManiphestTaskOwnerTransaction::TRANSACTIONTYPE)
|
||||
->setNewValue($user->getPHID()),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -21,7 +21,8 @@ final class ManiphestTask extends ManiphestDAO
|
|||
PhabricatorEditEngineSubtypeInterface,
|
||||
PhabricatorEditEngineLockableInterface,
|
||||
PhabricatorEditEngineMFAInterface,
|
||||
PhabricatorPolicyCodexInterface {
|
||||
PhabricatorPolicyCodexInterface,
|
||||
PhabricatorUnlockableInterface {
|
||||
|
||||
const MARKUP_FIELD_DESCRIPTION = 'markup:desc';
|
||||
|
||||
|
@ -649,4 +650,12 @@ final class ManiphestTask extends ManiphestDAO
|
|||
return new ManiphestTaskPolicyCodex();
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorUnlockableInterface )------------------------------------- */
|
||||
|
||||
|
||||
public function newUnlockEngine() {
|
||||
return new ManiphestTaskUnlockEngine();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -123,4 +123,14 @@ final class ManiphestTaskUnblockTransaction
|
|||
return parent::shouldHideForFeed();
|
||||
}
|
||||
|
||||
public function getRequiredCapabilities(
|
||||
$object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
|
||||
// When you close a task, we want to apply this transaction to its parents
|
||||
// even if you can not edit (or even see) those parents, so don't require
|
||||
// any capabilities. See PHI1059.
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,40 +8,72 @@ final class PhabricatorPolicyManagementUnlockWorkflow
|
|||
->setName('unlock')
|
||||
->setSynopsis(
|
||||
pht(
|
||||
'Unlock an object by setting its policies to allow anyone to view '.
|
||||
'and edit it.'))
|
||||
->setExamples('**unlock** D123')
|
||||
'Unlock an object which has policies that prevent it from being '.
|
||||
'viewed or edited.'))
|
||||
->setExamples('**unlock** --view __user__ __object__')
|
||||
->setArguments(
|
||||
array(
|
||||
array(
|
||||
'name' => 'objects',
|
||||
'wildcard' => true,
|
||||
'name' => 'view',
|
||||
'param' => 'username',
|
||||
'help' => pht(
|
||||
'Change the view policy of an object so that the specified '.
|
||||
'user may view it.'),
|
||||
),
|
||||
array(
|
||||
'name' => 'edit',
|
||||
'param' => 'username',
|
||||
'help' => pht(
|
||||
'Change the edit policy of an object so that the specified '.
|
||||
'user may edit it.'),
|
||||
),
|
||||
array(
|
||||
'name' => 'owner',
|
||||
'param' => 'username',
|
||||
'help' => pht(
|
||||
'Change the owner of an object to the specified user.'),
|
||||
),
|
||||
array(
|
||||
'name' => 'objects',
|
||||
'wildcard' => true,
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
public function execute(PhutilArgumentParser $args) {
|
||||
$console = PhutilConsole::getConsole();
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$obj_names = $args->getArg('objects');
|
||||
if (!$obj_names) {
|
||||
$object_names = $args->getArg('objects');
|
||||
if (!$object_names) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht('Specify the name of an object to unlock.'));
|
||||
} else if (count($obj_names) > 1) {
|
||||
} else if (count($object_names) > 1) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht('Specify the name of exactly one object to unlock.'));
|
||||
}
|
||||
|
||||
$object_name = head($object_names);
|
||||
|
||||
$object = id(new PhabricatorObjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withNames($obj_names)
|
||||
->withNames(array($object_name))
|
||||
->executeOne();
|
||||
|
||||
if (!$object) {
|
||||
$name = head($obj_names);
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht("No such object '%s'!", $name));
|
||||
pht(
|
||||
'Unable to find any object with the specified name ("%s").',
|
||||
$object_name));
|
||||
}
|
||||
|
||||
$view_user = $this->loadUser($args->getArg('view'));
|
||||
$edit_user = $this->loadUser($args->getArg('edit'));
|
||||
$owner_user = $this->loadUser($args->getArg('owner'));
|
||||
|
||||
if (!$view_user && !$edit_user && !$owner_user) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Choose which capabilities to unlock with "--view", "--edit", '.
|
||||
'or "--owner".'));
|
||||
}
|
||||
|
||||
$handle = id(new PhabricatorHandleQuery())
|
||||
|
@ -49,84 +81,73 @@ final class PhabricatorPolicyManagementUnlockWorkflow
|
|||
->withPHIDs(array($object->getPHID()))
|
||||
->executeOne();
|
||||
|
||||
if ($object instanceof PhabricatorApplication) {
|
||||
$application = $object;
|
||||
echo tsprintf(
|
||||
"<bg:blue>** %s **</bg> %s\n",
|
||||
pht('UNLOCKING'),
|
||||
pht('Unlocking: %s', $handle->getFullName()));
|
||||
|
||||
$console->writeOut(
|
||||
"%s\n",
|
||||
pht('Unlocking Application: %s', $handle->getFullName()));
|
||||
$engine = PhabricatorUnlockEngine::newUnlockEngineForObject($object);
|
||||
|
||||
// For applications, we can't unlock them in a normal way and don't want
|
||||
// to unlock every capability, just view and edit.
|
||||
$capabilities = array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
);
|
||||
$xactions = array();
|
||||
if ($view_user) {
|
||||
$xactions[] = $engine->newUnlockViewTransactions($object, $view_user);
|
||||
}
|
||||
if ($edit_user) {
|
||||
$xactions[] = $engine->newUnlockEditTransactions($object, $edit_user);
|
||||
}
|
||||
if ($owner_user) {
|
||||
$xactions[] = $engine->newUnlockOwnerTransactions($object, $owner_user);
|
||||
}
|
||||
$xactions = array_mergev($xactions);
|
||||
|
||||
$key = 'phabricator.application-settings';
|
||||
$config_entry = PhabricatorConfigEntry::loadConfigEntry($key);
|
||||
$value = $config_entry->getValue();
|
||||
$policy_application = new PhabricatorPolicyApplication();
|
||||
$content_source = $this->newContentSource();
|
||||
|
||||
foreach ($capabilities as $capability) {
|
||||
if ($application->isCapabilityEditable($capability)) {
|
||||
unset($value[$application->getPHID()]['policy'][$capability]);
|
||||
}
|
||||
}
|
||||
$editor = $object->getApplicationTransactionEditor()
|
||||
->setActor($viewer)
|
||||
->setActingAsPHID($policy_application->getPHID())
|
||||
->setContinueOnMissingFields(true)
|
||||
->setContinueOnNoEffect(true)
|
||||
->setContentSource($content_source);
|
||||
|
||||
$config_entry->setValue($value);
|
||||
$config_entry->save();
|
||||
$editor->applyTransactions($object, $xactions);
|
||||
|
||||
$console->writeOut("%s\n", pht('Saved application.'));
|
||||
echo tsprintf(
|
||||
"<bg:green>** %s **</bg> %s\n",
|
||||
pht('UNLOCKED'),
|
||||
pht('Modified object policies.'));
|
||||
|
||||
return 0;
|
||||
$uri = $handle->getURI();
|
||||
if (strlen($uri)) {
|
||||
echo tsprintf(
|
||||
"\n **%s**: __%s__\n\n",
|
||||
pht('Object URI'),
|
||||
PhabricatorEnv::getURI($uri));
|
||||
}
|
||||
|
||||
$console->writeOut("%s\n", pht('Unlocking: %s', $handle->getFullName()));
|
||||
return 0;
|
||||
}
|
||||
|
||||
$updated = false;
|
||||
foreach ($object->getCapabilities() as $capability) {
|
||||
switch ($capability) {
|
||||
case PhabricatorPolicyCapability::CAN_VIEW:
|
||||
try {
|
||||
$object->setViewPolicy(PhabricatorPolicies::POLICY_USER);
|
||||
$console->writeOut("%s\n", pht('Unlocked view policy.'));
|
||||
$updated = true;
|
||||
} catch (Exception $ex) {
|
||||
$console->writeOut("%s\n", pht('View policy is not mutable.'));
|
||||
}
|
||||
break;
|
||||
case PhabricatorPolicyCapability::CAN_EDIT:
|
||||
try {
|
||||
$object->setEditPolicy(PhabricatorPolicies::POLICY_USER);
|
||||
$console->writeOut("%s\n", pht('Unlocked edit policy.'));
|
||||
$updated = true;
|
||||
} catch (Exception $ex) {
|
||||
$console->writeOut("%s\n", pht('Edit policy is not mutable.'));
|
||||
}
|
||||
break;
|
||||
case PhabricatorPolicyCapability::CAN_JOIN:
|
||||
try {
|
||||
$object->setJoinPolicy(PhabricatorPolicies::POLICY_USER);
|
||||
$console->writeOut("%s\n", pht('Unlocked join policy.'));
|
||||
$updated = true;
|
||||
} catch (Exception $ex) {
|
||||
$console->writeOut("%s\n", pht('Join policy is not mutable.'));
|
||||
}
|
||||
break;
|
||||
}
|
||||
private function loadUser($username) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
if ($username === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($updated) {
|
||||
$object->save();
|
||||
$console->writeOut("%s\n", pht('Saved object.'));
|
||||
} else {
|
||||
$console->writeOut(
|
||||
"%s\n",
|
||||
$user = id(new PhabricatorPeopleQuery())
|
||||
->setViewer($viewer)
|
||||
->withUsernames(array($username))
|
||||
->executeOne();
|
||||
|
||||
if (!$user) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Object has no mutable policies. Try unlocking parent/container '.
|
||||
'object instead. For example, to gain access to a commit, unlock '.
|
||||
'the repository it belongs to.'));
|
||||
'No user with username "%s" exists.',
|
||||
$username));
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -124,29 +124,6 @@ final class PhabricatorRepositoryIdentityQuery
|
|||
return $where;
|
||||
}
|
||||
|
||||
protected function didFilterPage(array $identities) {
|
||||
$user_ids = array_filter(
|
||||
mpull($identities, 'getCurrentEffectiveUserPHID', 'getID'));
|
||||
if (!$user_ids) {
|
||||
return $identities;
|
||||
}
|
||||
|
||||
$users = id(new PhabricatorPeopleQuery())
|
||||
->withPHIDs($user_ids)
|
||||
->setViewer($this->getViewer())
|
||||
->execute();
|
||||
$users = mpull($users, null, 'getPHID');
|
||||
|
||||
foreach ($identities as $identity) {
|
||||
if ($identity->hasEffectiveUser()) {
|
||||
$user = idx($users, $identity->getCurrentEffectiveUserPHID());
|
||||
$identity->attachEffectiveUser($user);
|
||||
}
|
||||
}
|
||||
|
||||
return $identities;
|
||||
}
|
||||
|
||||
public function getQueryApplicationClass() {
|
||||
return 'PhabricatorDiffusionApplication';
|
||||
}
|
||||
|
|
|
@ -14,17 +14,6 @@ final class PhabricatorRepositoryIdentity
|
|||
protected $manuallySetUserPHID;
|
||||
protected $currentEffectiveUserPHID;
|
||||
|
||||
private $effectiveUser = self::ATTACHABLE;
|
||||
|
||||
public function attachEffectiveUser(PhabricatorUser $user) {
|
||||
$this->effectiveUser = $user;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getEffectiveUser() {
|
||||
return $this->assertAttached($this->effectiveUser);
|
||||
}
|
||||
|
||||
protected function getConfiguration() {
|
||||
return array(
|
||||
self::CONFIG_AUX_PHID => true,
|
||||
|
|
|
@ -144,7 +144,7 @@ EOTEXT
|
|||
->setHeaderText(pht('Builtin and Saved Queries'))
|
||||
->setCollapsed(true)
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->appendChild($this->buildRemarkup($info))
|
||||
->appendChild($this->newRemarkupDocumentationView($info))
|
||||
->appendChild($table);
|
||||
}
|
||||
|
||||
|
@ -223,7 +223,7 @@ EOTEXT
|
|||
);
|
||||
|
||||
if ($constants) {
|
||||
$constant_lists[] = $this->buildRemarkup(
|
||||
$constant_lists[] = $this->newRemarkupDocumentationView(
|
||||
pht(
|
||||
'Constants supported by the `%s` constraint:',
|
||||
'statuses'));
|
||||
|
@ -283,7 +283,7 @@ EOTEXT
|
|||
->setHeaderText(pht('Custom Query Constraints'))
|
||||
->setCollapsed(true)
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->appendChild($this->buildRemarkup($info))
|
||||
->appendChild($this->newRemarkupDocumentationView($info))
|
||||
->appendChild($table)
|
||||
->appendChild($constant_lists);
|
||||
}
|
||||
|
@ -391,9 +391,9 @@ EOTEXT
|
|||
->setHeaderText(pht('Result Ordering'))
|
||||
->setCollapsed(true)
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->appendChild($this->buildRemarkup($orders_info))
|
||||
->appendChild($this->newRemarkupDocumentationView($orders_info))
|
||||
->appendChild($orders_table)
|
||||
->appendChild($this->buildRemarkup($columns_info))
|
||||
->appendChild($this->newRemarkupDocumentationView($columns_info))
|
||||
->appendChild($columns_table);
|
||||
}
|
||||
|
||||
|
@ -472,7 +472,7 @@ EOTEXT
|
|||
->setHeaderText(pht('Object Fields'))
|
||||
->setCollapsed(true)
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->appendChild($this->buildRemarkup($info))
|
||||
->appendChild($this->newRemarkupDocumentationView($info))
|
||||
->appendChild($table);
|
||||
}
|
||||
|
||||
|
@ -562,7 +562,7 @@ EOTEXT
|
|||
->setHeaderText(pht('Attachments'))
|
||||
->setCollapsed(true)
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->appendChild($this->buildRemarkup($info))
|
||||
->appendChild($this->newRemarkupDocumentationView($info))
|
||||
->appendChild($table);
|
||||
}
|
||||
|
||||
|
@ -633,21 +633,7 @@ EOTEXT
|
|||
->setHeaderText(pht('Paging and Limits'))
|
||||
->setCollapsed(true)
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->appendChild($this->buildRemarkup($info));
|
||||
->appendChild($this->newRemarkupDocumentationView($info));
|
||||
}
|
||||
|
||||
private function buildRemarkup($remarkup) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$view = new PHUIRemarkupView($viewer, $remarkup);
|
||||
|
||||
$view->setRemarkupOptions(
|
||||
array(
|
||||
PHUIRemarkupView::OPTION_PRESERVE_LINEBREAKS => false,
|
||||
));
|
||||
|
||||
return id(new PHUIBoxView())
|
||||
->appendChild($view)
|
||||
->addPadding(PHUI::PADDING_LARGE);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorDefaultUnlockEngine
|
||||
extends PhabricatorUnlockEngine {}
|
81
src/applications/system/engine/PhabricatorUnlockEngine.php
Normal file
81
src/applications/system/engine/PhabricatorUnlockEngine.php
Normal file
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
abstract class PhabricatorUnlockEngine
|
||||
extends Phobject {
|
||||
|
||||
final public static function newUnlockEngineForObject($object) {
|
||||
if (!($object instanceof PhabricatorApplicationTransactionInterface)) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Object ("%s") does not implement interface "%s", so this type '.
|
||||
'of object can not be unlocked.',
|
||||
phutil_describe_type($object),
|
||||
'PhabricatorApplicationTransactionInterface'));
|
||||
}
|
||||
|
||||
if ($object instanceof PhabricatorUnlockableInterface) {
|
||||
$engine = $object->newUnlockEngine();
|
||||
} else {
|
||||
$engine = new PhabricatorDefaultUnlockEngine();
|
||||
}
|
||||
|
||||
return $engine;
|
||||
}
|
||||
|
||||
public function newUnlockViewTransactions($object, $user) {
|
||||
$type_view = PhabricatorTransactions::TYPE_VIEW_POLICY;
|
||||
|
||||
if (!$this->canApplyTransactionType($object, $type_view)) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Object view policy can not be unlocked because this object '.
|
||||
'does not have a mutable view policy.'));
|
||||
}
|
||||
|
||||
return array(
|
||||
$this->newTransaction($object)
|
||||
->setTransactionType($type_view)
|
||||
->setNewValue($user->getPHID()),
|
||||
);
|
||||
}
|
||||
|
||||
public function newUnlockEditTransactions($object, $user) {
|
||||
$type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY;
|
||||
|
||||
if (!$this->canApplyTransactionType($object, $type_edit)) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Object edit policy can not be unlocked because this object '.
|
||||
'does not have a mutable edit policy.'));
|
||||
}
|
||||
|
||||
return array(
|
||||
$this->newTransaction($object)
|
||||
->setTransactionType($type_edit)
|
||||
->setNewValue($user->getPHID()),
|
||||
);
|
||||
}
|
||||
|
||||
public function newUnlockOwnerTransactions($object, $user) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Object owner can not be unlocked: the unlocking engine ("%s") for '.
|
||||
'this object does not implement an owner unlocking mechanism.',
|
||||
get_class($this)));
|
||||
}
|
||||
|
||||
final protected function canApplyTransactionType($object, $type) {
|
||||
$xaction_types = $object->getApplicationTransactionEditor()
|
||||
->getTransactionTypesForObject($object);
|
||||
|
||||
$xaction_types = array_fuse($xaction_types);
|
||||
|
||||
return isset($xaction_types[$type]);
|
||||
}
|
||||
|
||||
final protected function newTransaction($object) {
|
||||
return $object->getApplicationTransactionTemplate();
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
interface PhabricatorUnlockableInterface {
|
||||
|
||||
public function newUnlockEngine();
|
||||
|
||||
}
|
||||
|
||||
// TEMPLATE IMPLEMENTATION /////////////////////////////////////////////////////
|
||||
|
||||
/* -( PhabricatorUnlockableInterface )------------------------------------- */
|
||||
/*
|
||||
|
||||
public function newUnlockEngine() {
|
||||
return new <<<...>>>UnlockEngine();
|
||||
}
|
||||
|
||||
*/
|
|
@ -8,15 +8,58 @@ final class TransactionSearchConduitAPIMethod
|
|||
}
|
||||
|
||||
public function getMethodDescription() {
|
||||
return pht('Read transactions for an object.');
|
||||
return pht('Read transactions and comments for an object.');
|
||||
}
|
||||
|
||||
public function getMethodStatus() {
|
||||
return self::METHOD_STATUS_UNSTABLE;
|
||||
}
|
||||
public function getMethodDocumentation() {
|
||||
$markup = pht(<<<EOREMARKUP
|
||||
When an object (like a task) is edited, Phabricator creates a "transaction"
|
||||
and applies it. This list of transactions on each object is the basis for
|
||||
essentially all edits and comments in Phabricator. Reviewing the transaction
|
||||
record allows you to see who edited an object, when, and how their edit changed
|
||||
things.
|
||||
|
||||
public function getMethodStatusDescription() {
|
||||
return pht('This method is new and experimental.');
|
||||
One common reason to call this method is that you're implmenting a webhook and
|
||||
just received a notification that an object has changed. See the Webhooks
|
||||
documentation for more detailed discussion of this use case.
|
||||
|
||||
Constraints
|
||||
===========
|
||||
|
||||
These constraints are supported:
|
||||
|
||||
- `phids` //Optional list<phid>.// Find specific transactions by PHID. This
|
||||
is most likely to be useful if you're responding to a webhook notification
|
||||
and want to inspect only the related events.
|
||||
- `authorPHIDs` //Optional list<phid>.// Find transactions with particular
|
||||
authors.
|
||||
|
||||
Transaction Format
|
||||
==================
|
||||
|
||||
Each transaction has custom data describing what the transaction did. The
|
||||
format varies from transaction to transaction. The easiest way to figure out
|
||||
exactly what a particular transaction looks like is to make the associated kind
|
||||
of edit to a test object, then query that object.
|
||||
|
||||
Not all transactions have data: by default, transactions have a `null` "type"
|
||||
and no additional data. This API does not expose raw transaction data because
|
||||
some of it is internal, oddly named, misspelled, confusing, not useful, or
|
||||
could create security or policy problems to expose directly.
|
||||
|
||||
New transactions are exposed (with correctly spelled, comprehensible types and
|
||||
useful, reasonable fields) as we become aware of use cases for them.
|
||||
|
||||
EOREMARKUP
|
||||
);
|
||||
|
||||
$markup = $this->newRemarkupDocumentationView($markup);
|
||||
|
||||
return id(new PHUIObjectBoxView())
|
||||
->setCollapsed(true)
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->setHeaderText(pht('Method Details'))
|
||||
->appendChild($markup);
|
||||
}
|
||||
|
||||
protected function defineParamTypes() {
|
||||
|
@ -73,24 +116,8 @@ final class TransactionSearchConduitAPIMethod
|
|||
->setViewer($viewer);
|
||||
|
||||
$constraints = $request->getValue('constraints', array());
|
||||
PhutilTypeSpec::checkMap(
|
||||
$constraints,
|
||||
array(
|
||||
'phids' => 'optional list<string>',
|
||||
));
|
||||
|
||||
$with_phids = idx($constraints, 'phids');
|
||||
|
||||
if ($with_phids === array()) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Constraint "phids" to "transaction.search" requires nonempty list, '.
|
||||
'empty list provided.'));
|
||||
}
|
||||
|
||||
if ($with_phids) {
|
||||
$xaction_query->withPHIDs($with_phids);
|
||||
}
|
||||
$xaction_query = $this->applyConstraints($constraints, $xaction_query);
|
||||
|
||||
$xactions = $xaction_query->executeWithCursorPager($pager);
|
||||
|
||||
|
@ -218,6 +245,14 @@ final class TransactionSearchConduitAPIMethod
|
|||
case PhabricatorTransactions::TYPE_CREATE:
|
||||
$type = 'create';
|
||||
break;
|
||||
case PhabricatorTransactions::TYPE_EDGE:
|
||||
switch ($xaction->getMetadataValue('edge:type')) {
|
||||
case PhabricatorProjectObjectHasProjectEdgeType::EDGECONST:
|
||||
$type = 'projects';
|
||||
$fields = $this->newEdgeTransactionFields($xaction);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -240,4 +275,69 @@ final class TransactionSearchConduitAPIMethod
|
|||
|
||||
return $this->addPagerResults($results, $pager);
|
||||
}
|
||||
|
||||
private function applyConstraints(
|
||||
array $constraints,
|
||||
PhabricatorApplicationTransactionQuery $query) {
|
||||
|
||||
PhutilTypeSpec::checkMap(
|
||||
$constraints,
|
||||
array(
|
||||
'phids' => 'optional list<string>',
|
||||
'authorPHIDs' => 'optional list<string>',
|
||||
));
|
||||
|
||||
$with_phids = idx($constraints, 'phids');
|
||||
|
||||
if ($with_phids === array()) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Constraint "phids" to "transaction.search" requires nonempty list, '.
|
||||
'empty list provided.'));
|
||||
}
|
||||
|
||||
if ($with_phids) {
|
||||
$query->withPHIDs($with_phids);
|
||||
}
|
||||
|
||||
$with_authors = idx($constraints, 'authorPHIDs');
|
||||
if ($with_authors === array()) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Constraint "authorPHIDs" to "transaction.search" requires '.
|
||||
'nonempty list, empty list provided.'));
|
||||
}
|
||||
|
||||
if ($with_authors) {
|
||||
$query->withAuthorPHIDs($with_authors);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
private function newEdgeTransactionFields(
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
|
||||
$record = PhabricatorEdgeChangeRecord::newFromTransaction($xaction);
|
||||
|
||||
$operations = array();
|
||||
foreach ($record->getAddedPHIDs() as $phid) {
|
||||
$operations[] = array(
|
||||
'operation' => 'add',
|
||||
'phid' => $phid,
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($record->getRemovedPHIDs() as $phid) {
|
||||
$operations[] = array(
|
||||
'operation' => 'remove',
|
||||
'phid' => $phid,
|
||||
);
|
||||
}
|
||||
|
||||
return array(
|
||||
'operations' => $operations,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3779,9 +3779,14 @@ abstract class PhabricatorApplicationTransactionEditor
|
|||
|
||||
$this->mustEncrypt = $adapter->getMustEncryptReasons();
|
||||
|
||||
$apply_xactions = $this->didApplyHeraldRules($object, $adapter, $xscript);
|
||||
assert_instances_of($apply_xactions, 'PhabricatorApplicationTransaction');
|
||||
|
||||
$queue_xactions = $adapter->getQueuedTransactions();
|
||||
|
||||
return array_merge(
|
||||
$this->didApplyHeraldRules($object, $adapter, $xscript),
|
||||
$adapter->getQueuedTransactions());
|
||||
array_values($apply_xactions),
|
||||
array_values($queue_xactions));
|
||||
}
|
||||
|
||||
protected function didApplyHeraldRules(
|
||||
|
|
|
@ -15,6 +15,7 @@ final class PhabricatorWorkerBulkJobEditor
|
|||
$types = parent::getTransactionTypes();
|
||||
|
||||
$types[] = PhabricatorWorkerBulkJobTransaction::TYPE_STATUS;
|
||||
$types[] = PhabricatorTransactions::TYPE_EDGE;
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
|
|
@ -11,23 +11,64 @@ final class PhabricatorWorkerManagementExecuteWorkflow
|
|||
pht(
|
||||
'Execute a task explicitly. This command ignores leases, is '.
|
||||
'dangerous, and may cause work to be performed twice.'))
|
||||
->setArguments($this->getTaskSelectionArguments());
|
||||
->setArguments(
|
||||
array_merge(
|
||||
array(
|
||||
array(
|
||||
'name' => 'retry',
|
||||
'help' => pht('Retry archived tasks.'),
|
||||
),
|
||||
array(
|
||||
'name' => 'repeat',
|
||||
'help' => pht('Repeat archived, successful tasks.'),
|
||||
),
|
||||
),
|
||||
$this->getTaskSelectionArguments()));
|
||||
}
|
||||
|
||||
public function execute(PhutilArgumentParser $args) {
|
||||
$console = PhutilConsole::getConsole();
|
||||
$tasks = $this->loadTasks($args);
|
||||
|
||||
$is_retry = $args->getArg('retry');
|
||||
$is_repeat = $args->getArg('repeat');
|
||||
|
||||
foreach ($tasks as $task) {
|
||||
$can_execute = !$task->isArchived();
|
||||
if (!$can_execute) {
|
||||
$console->writeOut(
|
||||
if (!$is_retry) {
|
||||
$console->writeOut(
|
||||
"**<bg:yellow> %s </bg>** %s\n",
|
||||
pht('ARCHIVED'),
|
||||
pht(
|
||||
'%s is already archived, and will not be executed. '.
|
||||
'Use "--retry" to execute archived tasks.',
|
||||
$this->describeTask($task)));
|
||||
continue;
|
||||
}
|
||||
|
||||
$result_success = PhabricatorWorkerArchiveTask::RESULT_SUCCESS;
|
||||
if ($task->getResult() == $result_success) {
|
||||
if (!$is_repeat) {
|
||||
$console->writeOut(
|
||||
"**<bg:yellow> %s </bg>** %s\n",
|
||||
pht('SUCCEEDED'),
|
||||
pht(
|
||||
'%s has already succeeded, and will not be retried. '.
|
||||
'Use "--repeat" to repeat successful tasks.',
|
||||
$this->describeTask($task)));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
echo tsprintf(
|
||||
"**<bg:yellow> %s </bg>** %s\n",
|
||||
pht('ARCHIVED'),
|
||||
pht(
|
||||
'%s is already archived, and can not be executed.',
|
||||
'Unarchiving %s.',
|
||||
$this->describeTask($task)));
|
||||
continue;
|
||||
|
||||
$task = $task->unarchiveTask();
|
||||
}
|
||||
|
||||
// NOTE: This ignores leases, maybe it should respect them without
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue