mirror of
https://we.phorge.it/source/phorge.git
synced 2025-01-21 04:01:30 +01:00
(stable) Promote 2019 Week 21
This commit is contained in:
commit
d9b41d3a0f
129 changed files with 4322 additions and 1153 deletions
|
@ -141,7 +141,7 @@ return array(
|
|||
'rsrc/css/phui/phui-big-info-view.css' => '362ad37b',
|
||||
'rsrc/css/phui/phui-box.css' => '5ed3b8cb',
|
||||
'rsrc/css/phui/phui-bulk-editor.css' => '374d5e30',
|
||||
'rsrc/css/phui/phui-chart.css' => '7853a69b',
|
||||
'rsrc/css/phui/phui-chart.css' => '10135a9d',
|
||||
'rsrc/css/phui/phui-cms.css' => '8c05c41e',
|
||||
'rsrc/css/phui/phui-comment-form.css' => '68a2d99a',
|
||||
'rsrc/css/phui/phui-comment-panel.css' => 'ec4e31c0',
|
||||
|
@ -183,7 +183,7 @@ return array(
|
|||
'rsrc/css/sprite-login.css' => '18b368a6',
|
||||
'rsrc/css/sprite-tokens.css' => 'f1896dc5',
|
||||
'rsrc/css/syntax/syntax-default.css' => '055fc231',
|
||||
'rsrc/externals/d3/d3.min.js' => 'd67475f5',
|
||||
'rsrc/externals/d3/d3.min.js' => '9d068042',
|
||||
'rsrc/externals/font/fontawesome/fontawesome-webfont.eot' => '23f8c698',
|
||||
'rsrc/externals/font/fontawesome/fontawesome-webfont.ttf' => '70983df0',
|
||||
'rsrc/externals/font/fontawesome/fontawesome-webfont.woff' => 'cd02f93b',
|
||||
|
@ -389,7 +389,9 @@ return array(
|
|||
'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'c715c123',
|
||||
'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => '6a85bc5a',
|
||||
'rsrc/js/application/drydock/drydock-live-operation-status.js' => '47a0728b',
|
||||
'rsrc/js/application/fact/Chart.js' => 'fcb0c07d',
|
||||
'rsrc/js/application/fact/Chart.js' => 'eec96de0',
|
||||
'rsrc/js/application/fact/ChartCurtainView.js' => '86954222',
|
||||
'rsrc/js/application/fact/ChartFunctionLabel.js' => '81de1dab',
|
||||
'rsrc/js/application/files/behavior-document-engine.js' => '243d6c22',
|
||||
'rsrc/js/application/files/behavior-icon-composer.js' => '38a6cedb',
|
||||
'rsrc/js/application/files/behavior-launch-icon-composer.js' => 'a17b84f1',
|
||||
|
@ -398,7 +400,6 @@ return array(
|
|||
'rsrc/js/application/herald/PathTypeahead.js' => 'ad486db3',
|
||||
'rsrc/js/application/herald/herald-rule-editor.js' => '0922e81d',
|
||||
'rsrc/js/application/maniphest/behavior-batch-selector.js' => '139ef688',
|
||||
'rsrc/js/application/maniphest/behavior-line-chart-legacy.js' => 'faf3ab6b',
|
||||
'rsrc/js/application/maniphest/behavior-line-chart.js' => 'ad258e28',
|
||||
'rsrc/js/application/maniphest/behavior-list-edit.js' => 'c687e867',
|
||||
'rsrc/js/application/owners/OwnersPathEditor.js' => '2a8b62d9',
|
||||
|
@ -551,7 +552,7 @@ return array(
|
|||
'conpherence-participant-pane-css' => '69e0058a',
|
||||
'conpherence-thread-manager' => 'aec8e38c',
|
||||
'conpherence-transaction-css' => '3a3f5e7e',
|
||||
'd3' => 'd67475f5',
|
||||
'd3' => '9d068042',
|
||||
'differential-changeset-view-css' => 'bde53589',
|
||||
'differential-core-view-css' => '7300a73e',
|
||||
'differential-revision-add-comment-css' => '7e5900d9',
|
||||
|
@ -628,7 +629,6 @@ return array(
|
|||
'javelin-behavior-launch-icon-composer' => 'a17b84f1',
|
||||
'javelin-behavior-lightbox-attachments' => 'c7e748bf',
|
||||
'javelin-behavior-line-chart' => 'ad258e28',
|
||||
'javelin-behavior-line-chart-legacy' => 'faf3ab6b',
|
||||
'javelin-behavior-linked-container' => '74446546',
|
||||
'javelin-behavior-maniphest-batch-selector' => '139ef688',
|
||||
'javelin-behavior-maniphest-list-editor' => 'c687e867',
|
||||
|
@ -698,7 +698,9 @@ return array(
|
|||
'javelin-behavior-user-menu' => '60cd9241',
|
||||
'javelin-behavior-view-placeholder' => 'a9942052',
|
||||
'javelin-behavior-workflow' => '9623adc1',
|
||||
'javelin-chart' => 'fcb0c07d',
|
||||
'javelin-chart' => 'eec96de0',
|
||||
'javelin-chart-curtain-view' => '86954222',
|
||||
'javelin-chart-function-label' => '81de1dab',
|
||||
'javelin-color' => '78f811c9',
|
||||
'javelin-cookie' => '05d290ef',
|
||||
'javelin-diffusion-locate-file-source' => '94243d89',
|
||||
|
@ -825,7 +827,7 @@ return array(
|
|||
'phui-calendar-day-css' => '9597d706',
|
||||
'phui-calendar-list-css' => 'ccd7e4e2',
|
||||
'phui-calendar-month-css' => 'cb758c42',
|
||||
'phui-chart-css' => '7853a69b',
|
||||
'phui-chart-css' => '10135a9d',
|
||||
'phui-cms-css' => '8c05c41e',
|
||||
'phui-comment-form-css' => '68a2d99a',
|
||||
'phui-comment-panel-css' => 'ec4e31c0',
|
||||
|
@ -2123,6 +2125,12 @@ return array(
|
|||
'phabricator-keyboard-shortcut',
|
||||
'javelin-stratcom',
|
||||
),
|
||||
'eec96de0' => array(
|
||||
'phui-chart-css',
|
||||
'd3',
|
||||
'javelin-chart-curtain-view',
|
||||
'javelin-chart-function-label',
|
||||
),
|
||||
'ef836bf2' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
|
@ -2182,16 +2190,6 @@ return array(
|
|||
'fa74cc35' => array(
|
||||
'phui-oi-list-view-css',
|
||||
),
|
||||
'faf3ab6b' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
'javelin-vector',
|
||||
'phui-chart-css',
|
||||
),
|
||||
'fcb0c07d' => array(
|
||||
'phui-chart-css',
|
||||
'd3',
|
||||
),
|
||||
'fdc13e4e' => array(
|
||||
'javelin-install',
|
||||
),
|
||||
|
|
|
@ -1,34 +1,7 @@
|
|||
<?php
|
||||
|
||||
// See T11741. Long ago, in T11922, we switched from "MyISAM FULLTEXT" to
|
||||
// "InnoDB FULLTEXT". This migration prompted installs to rebuild the index.
|
||||
|
||||
$use_mysql = false;
|
||||
|
||||
$services = PhabricatorSearchService::getAllServices();
|
||||
foreach ($services as $service) {
|
||||
$engine = $service->getEngine();
|
||||
if ($engine instanceof PhabricatorMySQLFulltextStorageEngine) {
|
||||
$use_mysql = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($use_mysql) {
|
||||
$field = new PhabricatorSearchDocumentField();
|
||||
$conn = $field->establishConnection('r');
|
||||
|
||||
// We're only going to require this if the index isn't empty: if you're on a
|
||||
// fresh install, you don't have to do anything.
|
||||
$any_documents = queryfx_one(
|
||||
$conn,
|
||||
'SELECT * FROM %T LIMIT 1',
|
||||
$field->getTableName());
|
||||
|
||||
if ($any_documents) {
|
||||
try {
|
||||
id(new PhabricatorConfigManualActivity())
|
||||
->setActivityType(PhabricatorConfigManualActivity::TYPE_REINDEX)
|
||||
->save();
|
||||
} catch (AphrontDuplicateKeyQueryException $ex) {
|
||||
// If we've already noted that this activity is required, just move on.
|
||||
}
|
||||
}
|
||||
}
|
||||
// Later, in T12974, we switched from "InnoDB FULLTEXT" to "Ferret", mostly
|
||||
// mooting this. The underlying tables and engines were later removed entirely.
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS {$NAMESPACE}_search.search_documentfield;
|
|
@ -538,6 +538,7 @@ phutil_register_library_map(array(
|
|||
'DifferentialMailEngineExtension' => 'applications/differential/engineextension/DifferentialMailEngineExtension.php',
|
||||
'DifferentialMailView' => 'applications/differential/mail/DifferentialMailView.php',
|
||||
'DifferentialManiphestTasksField' => 'applications/differential/customfield/DifferentialManiphestTasksField.php',
|
||||
'DifferentialNoReviewersDatasource' => 'applications/differential/typeahead/DifferentialNoReviewersDatasource.php',
|
||||
'DifferentialParseCacheGarbageCollector' => 'applications/differential/garbagecollector/DifferentialParseCacheGarbageCollector.php',
|
||||
'DifferentialParseCommitMessageConduitAPIMethod' => 'applications/differential/conduit/DifferentialParseCommitMessageConduitAPIMethod.php',
|
||||
'DifferentialParseRenderTestCase' => 'applications/differential/__tests__/DifferentialParseRenderTestCase.php',
|
||||
|
@ -561,6 +562,7 @@ phutil_register_library_map(array(
|
|||
'DifferentialReviewer' => 'applications/differential/storage/DifferentialReviewer.php',
|
||||
'DifferentialReviewerDatasource' => 'applications/differential/typeahead/DifferentialReviewerDatasource.php',
|
||||
'DifferentialReviewerForRevisionEdgeType' => 'applications/differential/edge/DifferentialReviewerForRevisionEdgeType.php',
|
||||
'DifferentialReviewerFunctionDatasource' => 'applications/differential/typeahead/DifferentialReviewerFunctionDatasource.php',
|
||||
'DifferentialReviewerStatus' => 'applications/differential/constants/DifferentialReviewerStatus.php',
|
||||
'DifferentialReviewersAddBlockingReviewersHeraldAction' => 'applications/differential/herald/DifferentialReviewersAddBlockingReviewersHeraldAction.php',
|
||||
'DifferentialReviewersAddBlockingSelfHeraldAction' => 'applications/differential/herald/DifferentialReviewersAddBlockingSelfHeraldAction.php',
|
||||
|
@ -997,6 +999,9 @@ phutil_register_library_map(array(
|
|||
'DiffusionServeController' => 'applications/diffusion/controller/DiffusionServeController.php',
|
||||
'DiffusionSetPasswordSettingsPanel' => 'applications/diffusion/panel/DiffusionSetPasswordSettingsPanel.php',
|
||||
'DiffusionSetupException' => 'applications/diffusion/exception/DiffusionSetupException.php',
|
||||
'DiffusionSourceHyperlinkEngineExtension' => 'applications/diffusion/engineextension/DiffusionSourceHyperlinkEngineExtension.php',
|
||||
'DiffusionSourceLinkRemarkupRule' => 'applications/diffusion/remarkup/DiffusionSourceLinkRemarkupRule.php',
|
||||
'DiffusionSourceLinkView' => 'applications/diffusion/view/DiffusionSourceLinkView.php',
|
||||
'DiffusionSubversionCommandEngine' => 'applications/diffusion/protocol/DiffusionSubversionCommandEngine.php',
|
||||
'DiffusionSubversionSSHWorkflow' => 'applications/diffusion/ssh/DiffusionSubversionSSHWorkflow.php',
|
||||
'DiffusionSubversionServeSSHWorkflow' => 'applications/diffusion/ssh/DiffusionSubversionServeSSHWorkflow.php',
|
||||
|
@ -1072,7 +1077,6 @@ phutil_register_library_map(array(
|
|||
'DivinerSymbolRemarkupRule' => 'applications/diviner/markup/DivinerSymbolRemarkupRule.php',
|
||||
'DivinerWorkflow' => 'applications/diviner/workflow/DivinerWorkflow.php',
|
||||
'DoorkeeperAsanaFeedWorker' => 'applications/doorkeeper/worker/DoorkeeperAsanaFeedWorker.php',
|
||||
'DoorkeeperAsanaRemarkupRule' => 'applications/doorkeeper/remarkup/DoorkeeperAsanaRemarkupRule.php',
|
||||
'DoorkeeperBridge' => 'applications/doorkeeper/bridge/DoorkeeperBridge.php',
|
||||
'DoorkeeperBridgeAsana' => 'applications/doorkeeper/bridge/DoorkeeperBridgeAsana.php',
|
||||
'DoorkeeperBridgeGitHub' => 'applications/doorkeeper/bridge/DoorkeeperBridgeGitHub.php',
|
||||
|
@ -1088,15 +1092,16 @@ phutil_register_library_map(array(
|
|||
'DoorkeeperExternalObjectQuery' => 'applications/doorkeeper/query/DoorkeeperExternalObjectQuery.php',
|
||||
'DoorkeeperFeedStoryPublisher' => 'applications/doorkeeper/engine/DoorkeeperFeedStoryPublisher.php',
|
||||
'DoorkeeperFeedWorker' => 'applications/doorkeeper/worker/DoorkeeperFeedWorker.php',
|
||||
'DoorkeeperHyperlinkEngineExtension' => 'applications/doorkeeper/engineextension/DoorkeeperHyperlinkEngineExtension.php',
|
||||
'DoorkeeperImportEngine' => 'applications/doorkeeper/engine/DoorkeeperImportEngine.php',
|
||||
'DoorkeeperJIRAFeedWorker' => 'applications/doorkeeper/worker/DoorkeeperJIRAFeedWorker.php',
|
||||
'DoorkeeperJIRARemarkupRule' => 'applications/doorkeeper/remarkup/DoorkeeperJIRARemarkupRule.php',
|
||||
'DoorkeeperMissingLinkException' => 'applications/doorkeeper/exception/DoorkeeperMissingLinkException.php',
|
||||
'DoorkeeperObjectRef' => 'applications/doorkeeper/engine/DoorkeeperObjectRef.php',
|
||||
'DoorkeeperRemarkupRule' => 'applications/doorkeeper/remarkup/DoorkeeperRemarkupRule.php',
|
||||
'DoorkeeperRemarkupURIInterface' => 'applications/doorkeeper/interface/DoorkeeperRemarkupURIInterface.php',
|
||||
'DoorkeeperSchemaSpec' => 'applications/doorkeeper/storage/DoorkeeperSchemaSpec.php',
|
||||
'DoorkeeperTagView' => 'applications/doorkeeper/view/DoorkeeperTagView.php',
|
||||
'DoorkeeperTagsController' => 'applications/doorkeeper/controller/DoorkeeperTagsController.php',
|
||||
'DoorkeeperURIRef' => 'applications/doorkeeper/engine/DoorkeeperURIRef.php',
|
||||
'DrydockAcquiredBrokenResourceException' => 'applications/drydock/exception/DrydockAcquiredBrokenResourceException.php',
|
||||
'DrydockAlmanacServiceHostBlueprintImplementation' => 'applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php',
|
||||
'DrydockApacheWebrootInterface' => 'applications/drydock/interface/webroot/DrydockApacheWebrootInterface.php',
|
||||
|
@ -2107,6 +2112,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorAccessLog' => 'infrastructure/log/PhabricatorAccessLog.php',
|
||||
'PhabricatorAccessLogConfigOptions' => 'applications/config/option/PhabricatorAccessLogConfigOptions.php',
|
||||
'PhabricatorAccessibilitySetting' => 'applications/settings/setting/PhabricatorAccessibilitySetting.php',
|
||||
'PhabricatorAccumulateChartFunction' => 'applications/fact/chart/PhabricatorAccumulateChartFunction.php',
|
||||
'PhabricatorActionListView' => 'view/layout/PhabricatorActionListView.php',
|
||||
'PhabricatorActionView' => 'view/layout/PhabricatorActionView.php',
|
||||
'PhabricatorActivitySettingsPanel' => 'applications/settings/panel/PhabricatorActivitySettingsPanel.php',
|
||||
|
@ -2657,9 +2663,16 @@ phutil_register_library_map(array(
|
|||
'PhabricatorChangesetResponse' => 'infrastructure/diff/PhabricatorChangesetResponse.php',
|
||||
'PhabricatorChartAxis' => 'applications/fact/chart/PhabricatorChartAxis.php',
|
||||
'PhabricatorChartDataQuery' => 'applications/fact/chart/PhabricatorChartDataQuery.php',
|
||||
'PhabricatorChartDataset' => 'applications/fact/chart/PhabricatorChartDataset.php',
|
||||
'PhabricatorChartDisplayData' => 'applications/fact/chart/PhabricatorChartDisplayData.php',
|
||||
'PhabricatorChartEngine' => 'applications/fact/engine/PhabricatorChartEngine.php',
|
||||
'PhabricatorChartFunction' => 'applications/fact/chart/PhabricatorChartFunction.php',
|
||||
'PhabricatorChartFunctionArgument' => 'applications/fact/chart/PhabricatorChartFunctionArgument.php',
|
||||
'PhabricatorChartFunctionArgumentParser' => 'applications/fact/chart/PhabricatorChartFunctionArgumentParser.php',
|
||||
'PhabricatorChartFunctionLabel' => 'applications/fact/chart/PhabricatorChartFunctionLabel.php',
|
||||
'PhabricatorChartInterval' => 'applications/fact/chart/PhabricatorChartInterval.php',
|
||||
'PhabricatorChartRenderingEngine' => 'applications/fact/engine/PhabricatorChartRenderingEngine.php',
|
||||
'PhabricatorChartStackedAreaDataset' => 'applications/fact/chart/PhabricatorChartStackedAreaDataset.php',
|
||||
'PhabricatorChatLogApplication' => 'applications/chatlog/application/PhabricatorChatLogApplication.php',
|
||||
'PhabricatorChatLogChannel' => 'applications/chatlog/storage/PhabricatorChatLogChannel.php',
|
||||
'PhabricatorChatLogChannelListController' => 'applications/chatlog/controller/PhabricatorChatLogChannelListController.php',
|
||||
|
@ -2695,6 +2708,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorCommitSearchEngine' => 'applications/audit/query/PhabricatorCommitSearchEngine.php',
|
||||
'PhabricatorCommitTagsField' => 'applications/repository/customfield/PhabricatorCommitTagsField.php',
|
||||
'PhabricatorCommonPasswords' => 'applications/auth/constants/PhabricatorCommonPasswords.php',
|
||||
'PhabricatorComposeChartFunction' => 'applications/fact/chart/PhabricatorComposeChartFunction.php',
|
||||
'PhabricatorConduitAPIController' => 'applications/conduit/controller/PhabricatorConduitAPIController.php',
|
||||
'PhabricatorConduitApplication' => 'applications/conduit/application/PhabricatorConduitApplication.php',
|
||||
'PhabricatorConduitCallManagementWorkflow' => 'applications/conduit/management/PhabricatorConduitCallManagementWorkflow.php',
|
||||
|
@ -2928,6 +2942,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorDashboardApplication' => 'applications/dashboard/application/PhabricatorDashboardApplication.php',
|
||||
'PhabricatorDashboardApplicationInstallWorkflow' => 'applications/dashboard/install/PhabricatorDashboardApplicationInstallWorkflow.php',
|
||||
'PhabricatorDashboardArchiveController' => 'applications/dashboard/controller/dashboard/PhabricatorDashboardArchiveController.php',
|
||||
'PhabricatorDashboardChartPanelChartTransaction' => 'applications/dashboard/xaction/panel/PhabricatorDashboardChartPanelChartTransaction.php',
|
||||
'PhabricatorDashboardChartPanelType' => 'applications/dashboard/paneltype/PhabricatorDashboardChartPanelType.php',
|
||||
'PhabricatorDashboardColumn' => 'applications/dashboard/layoutconfig/PhabricatorDashboardColumn.php',
|
||||
'PhabricatorDashboardConsoleController' => 'applications/dashboard/controller/PhabricatorDashboardConsoleController.php',
|
||||
'PhabricatorDashboardController' => 'applications/dashboard/controller/PhabricatorDashboardController.php',
|
||||
|
@ -3263,6 +3279,9 @@ phutil_register_library_map(array(
|
|||
'PhabricatorFeedStoryNotification' => 'applications/notification/storage/PhabricatorFeedStoryNotification.php',
|
||||
'PhabricatorFeedStoryPublisher' => 'applications/feed/PhabricatorFeedStoryPublisher.php',
|
||||
'PhabricatorFeedStoryReference' => 'applications/feed/storage/PhabricatorFeedStoryReference.php',
|
||||
'PhabricatorFeedTransactionListController' => 'applications/feed/controller/PhabricatorFeedTransactionListController.php',
|
||||
'PhabricatorFeedTransactionQuery' => 'applications/feed/query/PhabricatorFeedTransactionQuery.php',
|
||||
'PhabricatorFeedTransactionSearchEngine' => 'applications/feed/query/PhabricatorFeedTransactionSearchEngine.php',
|
||||
'PhabricatorFerretEngine' => 'applications/search/ferret/PhabricatorFerretEngine.php',
|
||||
'PhabricatorFerretEngineTestCase' => 'applications/search/ferret/__tests__/PhabricatorFerretEngineTestCase.php',
|
||||
'PhabricatorFerretFulltextEngineExtension' => 'applications/search/engineextension/PhabricatorFerretFulltextEngineExtension.php',
|
||||
|
@ -3425,6 +3444,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorHeraldContentSource' => 'applications/herald/contentsource/PhabricatorHeraldContentSource.php',
|
||||
'PhabricatorHexdumpDocumentEngine' => 'applications/files/document/PhabricatorHexdumpDocumentEngine.php',
|
||||
'PhabricatorHighSecurityRequestExceptionHandler' => 'aphront/handler/PhabricatorHighSecurityRequestExceptionHandler.php',
|
||||
'PhabricatorHigherOrderChartFunction' => 'applications/fact/chart/PhabricatorHigherOrderChartFunction.php',
|
||||
'PhabricatorHomeApplication' => 'applications/home/application/PhabricatorHomeApplication.php',
|
||||
'PhabricatorHomeConstants' => 'applications/home/constants/PhabricatorHomeConstants.php',
|
||||
'PhabricatorHomeController' => 'applications/home/controller/PhabricatorHomeController.php',
|
||||
|
@ -3602,6 +3622,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorMarkupInterface' => 'infrastructure/markup/PhabricatorMarkupInterface.php',
|
||||
'PhabricatorMarkupOneOff' => 'infrastructure/markup/PhabricatorMarkupOneOff.php',
|
||||
'PhabricatorMarkupPreviewController' => 'infrastructure/markup/PhabricatorMarkupPreviewController.php',
|
||||
'PhabricatorMaxChartFunction' => 'applications/fact/chart/PhabricatorMaxChartFunction.php',
|
||||
'PhabricatorMemeEngine' => 'applications/macro/engine/PhabricatorMemeEngine.php',
|
||||
'PhabricatorMemeRemarkupRule' => 'applications/macro/markup/PhabricatorMemeRemarkupRule.php',
|
||||
'PhabricatorMentionRemarkupRule' => 'applications/people/markup/PhabricatorMentionRemarkupRule.php',
|
||||
|
@ -3656,6 +3677,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorMetronome' => 'infrastructure/util/PhabricatorMetronome.php',
|
||||
'PhabricatorMetronomeTestCase' => 'infrastructure/util/__tests__/PhabricatorMetronomeTestCase.php',
|
||||
'PhabricatorMetronomicTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorMetronomicTriggerClock.php',
|
||||
'PhabricatorMinChartFunction' => 'applications/fact/chart/PhabricatorMinChartFunction.php',
|
||||
'PhabricatorModularTransaction' => 'applications/transactions/storage/PhabricatorModularTransaction.php',
|
||||
'PhabricatorModularTransactionType' => 'applications/transactions/storage/PhabricatorModularTransactionType.php',
|
||||
'PhabricatorMonogramDatasourceEngineExtension' => 'applications/typeahead/engineextension/PhabricatorMonogramDatasourceEngineExtension.php',
|
||||
|
@ -4127,6 +4149,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorProjectBoardReorderController' => 'applications/project/controller/PhabricatorProjectBoardReorderController.php',
|
||||
'PhabricatorProjectBoardViewController' => 'applications/project/controller/PhabricatorProjectBoardViewController.php',
|
||||
'PhabricatorProjectBuiltinsExample' => 'applications/uiexample/examples/PhabricatorProjectBuiltinsExample.php',
|
||||
'PhabricatorProjectBurndownChartEngine' => 'applications/project/chart/PhabricatorProjectBurndownChartEngine.php',
|
||||
'PhabricatorProjectCardView' => 'applications/project/view/PhabricatorProjectCardView.php',
|
||||
'PhabricatorProjectColorTransaction' => 'applications/project/xaction/PhabricatorProjectColorTransaction.php',
|
||||
'PhabricatorProjectColorsConfigType' => 'applications/project/config/PhabricatorProjectColorsConfigType.php',
|
||||
|
@ -4230,6 +4253,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorProjectProjectPHIDType' => 'applications/project/phid/PhabricatorProjectProjectPHIDType.php',
|
||||
'PhabricatorProjectQuery' => 'applications/project/query/PhabricatorProjectQuery.php',
|
||||
'PhabricatorProjectRemoveHeraldAction' => 'applications/project/herald/PhabricatorProjectRemoveHeraldAction.php',
|
||||
'PhabricatorProjectReportsController' => 'applications/project/controller/PhabricatorProjectReportsController.php',
|
||||
'PhabricatorProjectReportsProfileMenuItem' => 'applications/project/menuitem/PhabricatorProjectReportsProfileMenuItem.php',
|
||||
'PhabricatorProjectSchemaSpec' => 'applications/project/storage/PhabricatorProjectSchemaSpec.php',
|
||||
'PhabricatorProjectSearchEngine' => 'applications/project/query/PhabricatorProjectSearchEngine.php',
|
||||
'PhabricatorProjectSearchField' => 'applications/project/searchfield/PhabricatorProjectSearchField.php',
|
||||
|
@ -4332,6 +4357,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorRemarkupDocumentEngine' => 'applications/files/document/PhabricatorRemarkupDocumentEngine.php',
|
||||
'PhabricatorRemarkupEditField' => 'applications/transactions/editfield/PhabricatorRemarkupEditField.php',
|
||||
'PhabricatorRemarkupFigletBlockInterpreter' => 'infrastructure/markup/interpreter/PhabricatorRemarkupFigletBlockInterpreter.php',
|
||||
'PhabricatorRemarkupHyperlinkEngineExtension' => 'applications/remarkup/engineextension/PhabricatorRemarkupHyperlinkEngineExtension.php',
|
||||
'PhabricatorRemarkupUIExample' => 'applications/uiexample/examples/PhabricatorRemarkupUIExample.php',
|
||||
'PhabricatorRepositoriesSetupCheck' => 'applications/config/check/PhabricatorRepositoriesSetupCheck.php',
|
||||
'PhabricatorRepository' => 'applications/repository/storage/PhabricatorRepository.php',
|
||||
|
@ -4504,7 +4530,6 @@ phutil_register_library_map(array(
|
|||
'PhabricatorSearchDefaultController' => 'applications/search/controller/PhabricatorSearchDefaultController.php',
|
||||
'PhabricatorSearchDeleteController' => 'applications/search/controller/PhabricatorSearchDeleteController.php',
|
||||
'PhabricatorSearchDocument' => 'applications/search/storage/document/PhabricatorSearchDocument.php',
|
||||
'PhabricatorSearchDocumentField' => 'applications/search/storage/document/PhabricatorSearchDocumentField.php',
|
||||
'PhabricatorSearchDocumentFieldType' => 'applications/search/constants/PhabricatorSearchDocumentFieldType.php',
|
||||
'PhabricatorSearchDocumentQuery' => 'applications/search/query/PhabricatorSearchDocumentQuery.php',
|
||||
'PhabricatorSearchDocumentRelationship' => 'applications/search/storage/document/PhabricatorSearchDocumentRelationship.php',
|
||||
|
@ -4725,6 +4750,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorSubscriptionsUIEventListener' => 'applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php',
|
||||
'PhabricatorSubscriptionsUnsubscribeEmailCommand' => 'applications/subscriptions/command/PhabricatorSubscriptionsUnsubscribeEmailCommand.php',
|
||||
'PhabricatorSubtypeEditEngineExtension' => 'applications/transactions/engineextension/PhabricatorSubtypeEditEngineExtension.php',
|
||||
'PhabricatorSumChartFunction' => 'applications/fact/chart/PhabricatorSumChartFunction.php',
|
||||
'PhabricatorSupportApplication' => 'applications/support/application/PhabricatorSupportApplication.php',
|
||||
'PhabricatorSyntaxHighlighter' => 'infrastructure/markup/PhabricatorSyntaxHighlighter.php',
|
||||
'PhabricatorSyntaxHighlightingConfigOptions' => 'applications/config/option/PhabricatorSyntaxHighlightingConfigOptions.php',
|
||||
|
@ -4802,6 +4828,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorTransactionsApplication' => 'applications/transactions/application/PhabricatorTransactionsApplication.php',
|
||||
'PhabricatorTransactionsDestructionEngineExtension' => 'applications/transactions/engineextension/PhabricatorTransactionsDestructionEngineExtension.php',
|
||||
'PhabricatorTransactionsFulltextEngineExtension' => 'applications/transactions/engineextension/PhabricatorTransactionsFulltextEngineExtension.php',
|
||||
'PhabricatorTransactionsObjectTypeDatasource' => 'applications/transactions/typeahead/PhabricatorTransactionsObjectTypeDatasource.php',
|
||||
'PhabricatorTransformedFile' => 'applications/files/storage/PhabricatorTransformedFile.php',
|
||||
'PhabricatorTranslationSetting' => 'applications/settings/setting/PhabricatorTranslationSetting.php',
|
||||
'PhabricatorTranslationsConfigOptions' => 'applications/config/option/PhabricatorTranslationsConfigOptions.php',
|
||||
|
@ -4952,7 +4979,6 @@ phutil_register_library_map(array(
|
|||
'PhabricatorWorkingCopyDiscoveryTestCase' => 'applications/repository/engine/__tests__/PhabricatorWorkingCopyDiscoveryTestCase.php',
|
||||
'PhabricatorWorkingCopyPullTestCase' => 'applications/repository/engine/__tests__/PhabricatorWorkingCopyPullTestCase.php',
|
||||
'PhabricatorWorkingCopyTestCase' => 'applications/repository/engine/__tests__/PhabricatorWorkingCopyTestCase.php',
|
||||
'PhabricatorXChartFunction' => 'applications/fact/chart/PhabricatorXChartFunction.php',
|
||||
'PhabricatorXHPASTDAO' => 'applications/phpast/storage/PhabricatorXHPASTDAO.php',
|
||||
'PhabricatorXHPASTParseTree' => 'applications/phpast/storage/PhabricatorXHPASTParseTree.php',
|
||||
'PhabricatorXHPASTViewController' => 'applications/phpast/controller/PhabricatorXHPASTViewController.php',
|
||||
|
@ -6190,6 +6216,7 @@ phutil_register_library_map(array(
|
|||
'DifferentialMailEngineExtension' => 'PhabricatorMailEngineExtension',
|
||||
'DifferentialMailView' => 'Phobject',
|
||||
'DifferentialManiphestTasksField' => 'DifferentialCoreCustomField',
|
||||
'DifferentialNoReviewersDatasource' => 'PhabricatorTypeaheadDatasource',
|
||||
'DifferentialParseCacheGarbageCollector' => 'PhabricatorGarbageCollector',
|
||||
'DifferentialParseCommitMessageConduitAPIMethod' => 'DifferentialConduitAPIMethod',
|
||||
'DifferentialParseRenderTestCase' => 'PhabricatorTestCase',
|
||||
|
@ -6213,6 +6240,7 @@ phutil_register_library_map(array(
|
|||
'DifferentialReviewer' => 'DifferentialDAO',
|
||||
'DifferentialReviewerDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
||||
'DifferentialReviewerForRevisionEdgeType' => 'PhabricatorEdgeType',
|
||||
'DifferentialReviewerFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
||||
'DifferentialReviewerStatus' => 'Phobject',
|
||||
'DifferentialReviewersAddBlockingReviewersHeraldAction' => 'DifferentialReviewersHeraldAction',
|
||||
'DifferentialReviewersAddBlockingSelfHeraldAction' => 'DifferentialReviewersHeraldAction',
|
||||
|
@ -6670,6 +6698,9 @@ phutil_register_library_map(array(
|
|||
'DiffusionServeController' => 'DiffusionController',
|
||||
'DiffusionSetPasswordSettingsPanel' => 'PhabricatorSettingsPanel',
|
||||
'DiffusionSetupException' => 'Exception',
|
||||
'DiffusionSourceHyperlinkEngineExtension' => 'PhabricatorRemarkupHyperlinkEngineExtension',
|
||||
'DiffusionSourceLinkRemarkupRule' => 'PhutilRemarkupRule',
|
||||
'DiffusionSourceLinkView' => 'AphrontView',
|
||||
'DiffusionSubversionCommandEngine' => 'DiffusionCommandEngine',
|
||||
'DiffusionSubversionSSHWorkflow' => 'DiffusionSSHWorkflow',
|
||||
'DiffusionSubversionServeSSHWorkflow' => 'DiffusionSubversionSSHWorkflow',
|
||||
|
@ -6758,7 +6789,6 @@ phutil_register_library_map(array(
|
|||
'DivinerSymbolRemarkupRule' => 'PhutilRemarkupRule',
|
||||
'DivinerWorkflow' => 'PhabricatorManagementWorkflow',
|
||||
'DoorkeeperAsanaFeedWorker' => 'DoorkeeperFeedWorker',
|
||||
'DoorkeeperAsanaRemarkupRule' => 'DoorkeeperRemarkupRule',
|
||||
'DoorkeeperBridge' => 'Phobject',
|
||||
'DoorkeeperBridgeAsana' => 'DoorkeeperBridge',
|
||||
'DoorkeeperBridgeGitHub' => 'DoorkeeperBridge',
|
||||
|
@ -6776,15 +6806,15 @@ phutil_register_library_map(array(
|
|||
'DoorkeeperExternalObjectQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'DoorkeeperFeedStoryPublisher' => 'Phobject',
|
||||
'DoorkeeperFeedWorker' => 'FeedPushWorker',
|
||||
'DoorkeeperHyperlinkEngineExtension' => 'PhabricatorRemarkupHyperlinkEngineExtension',
|
||||
'DoorkeeperImportEngine' => 'Phobject',
|
||||
'DoorkeeperJIRAFeedWorker' => 'DoorkeeperFeedWorker',
|
||||
'DoorkeeperJIRARemarkupRule' => 'DoorkeeperRemarkupRule',
|
||||
'DoorkeeperMissingLinkException' => 'Exception',
|
||||
'DoorkeeperObjectRef' => 'Phobject',
|
||||
'DoorkeeperRemarkupRule' => 'PhutilRemarkupRule',
|
||||
'DoorkeeperSchemaSpec' => 'PhabricatorConfigSchemaSpec',
|
||||
'DoorkeeperTagView' => 'AphrontView',
|
||||
'DoorkeeperTagsController' => 'PhabricatorController',
|
||||
'DoorkeeperURIRef' => 'Phobject',
|
||||
'DrydockAcquiredBrokenResourceException' => 'Exception',
|
||||
'DrydockAlmanacServiceHostBlueprintImplementation' => 'DrydockBlueprintImplementation',
|
||||
'DrydockApacheWebrootInterface' => 'DrydockWebrootInterface',
|
||||
|
@ -7987,6 +8017,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorAccessLog' => 'Phobject',
|
||||
'PhabricatorAccessLogConfigOptions' => 'PhabricatorApplicationConfigOptions',
|
||||
'PhabricatorAccessibilitySetting' => 'PhabricatorSelectSetting',
|
||||
'PhabricatorAccumulateChartFunction' => 'PhabricatorChartFunction',
|
||||
'PhabricatorActionListView' => 'AphrontTagView',
|
||||
'PhabricatorActionView' => 'AphrontView',
|
||||
'PhabricatorActivitySettingsPanel' => 'PhabricatorSettingsPanel',
|
||||
|
@ -8086,7 +8117,10 @@ phutil_register_library_map(array(
|
|||
'PhabricatorApplicationsController' => 'PhabricatorController',
|
||||
'PhabricatorApplicationsListController' => 'PhabricatorApplicationsController',
|
||||
'PhabricatorApplyEditField' => 'PhabricatorEditField',
|
||||
'PhabricatorAsanaAuthProvider' => 'PhabricatorOAuth2AuthProvider',
|
||||
'PhabricatorAsanaAuthProvider' => array(
|
||||
'PhabricatorOAuth2AuthProvider',
|
||||
'DoorkeeperRemarkupURIInterface',
|
||||
),
|
||||
'PhabricatorAsanaConfigOptions' => 'PhabricatorApplicationConfigOptions',
|
||||
'PhabricatorAsanaSubtaskHasObjectEdgeType' => 'PhabricatorEdgeType',
|
||||
'PhabricatorAsanaTaskHasObjectEdgeType' => 'PhabricatorEdgeType',
|
||||
|
@ -8648,9 +8682,16 @@ phutil_register_library_map(array(
|
|||
'PhabricatorChangesetResponse' => 'AphrontProxyResponse',
|
||||
'PhabricatorChartAxis' => 'Phobject',
|
||||
'PhabricatorChartDataQuery' => 'Phobject',
|
||||
'PhabricatorChartDataset' => 'Phobject',
|
||||
'PhabricatorChartDisplayData' => 'Phobject',
|
||||
'PhabricatorChartEngine' => 'Phobject',
|
||||
'PhabricatorChartFunction' => 'Phobject',
|
||||
'PhabricatorChartFunctionArgument' => 'Phobject',
|
||||
'PhabricatorChartFunctionArgumentParser' => 'Phobject',
|
||||
'PhabricatorChartFunctionLabel' => 'Phobject',
|
||||
'PhabricatorChartInterval' => 'Phobject',
|
||||
'PhabricatorChartRenderingEngine' => 'Phobject',
|
||||
'PhabricatorChartStackedAreaDataset' => 'PhabricatorChartDataset',
|
||||
'PhabricatorChatLogApplication' => 'PhabricatorApplication',
|
||||
'PhabricatorChatLogChannel' => array(
|
||||
'PhabricatorChatLogDAO',
|
||||
|
@ -8691,6 +8732,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorCommitSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||
'PhabricatorCommitTagsField' => 'PhabricatorCommitCustomField',
|
||||
'PhabricatorCommonPasswords' => 'Phobject',
|
||||
'PhabricatorComposeChartFunction' => 'PhabricatorHigherOrderChartFunction',
|
||||
'PhabricatorConduitAPIController' => 'PhabricatorConduitController',
|
||||
'PhabricatorConduitApplication' => 'PhabricatorApplication',
|
||||
'PhabricatorConduitCallManagementWorkflow' => 'PhabricatorConduitManagementWorkflow',
|
||||
|
@ -8957,6 +8999,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorDashboardApplication' => 'PhabricatorApplication',
|
||||
'PhabricatorDashboardApplicationInstallWorkflow' => 'PhabricatorDashboardInstallWorkflow',
|
||||
'PhabricatorDashboardArchiveController' => 'PhabricatorDashboardController',
|
||||
'PhabricatorDashboardChartPanelChartTransaction' => 'PhabricatorDashboardPanelPropertyTransaction',
|
||||
'PhabricatorDashboardChartPanelType' => 'PhabricatorDashboardPanelType',
|
||||
'PhabricatorDashboardColumn' => 'Phobject',
|
||||
'PhabricatorDashboardConsoleController' => 'PhabricatorDashboardController',
|
||||
'PhabricatorDashboardController' => 'PhabricatorController',
|
||||
|
@ -9323,6 +9367,9 @@ phutil_register_library_map(array(
|
|||
'PhabricatorFeedStoryNotification' => 'PhabricatorFeedDAO',
|
||||
'PhabricatorFeedStoryPublisher' => 'Phobject',
|
||||
'PhabricatorFeedStoryReference' => 'PhabricatorFeedDAO',
|
||||
'PhabricatorFeedTransactionListController' => 'PhabricatorFeedController',
|
||||
'PhabricatorFeedTransactionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'PhabricatorFeedTransactionSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||
'PhabricatorFerretEngine' => 'Phobject',
|
||||
'PhabricatorFerretEngineTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorFerretFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension',
|
||||
|
@ -9520,6 +9567,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorHeraldContentSource' => 'PhabricatorContentSource',
|
||||
'PhabricatorHexdumpDocumentEngine' => 'PhabricatorDocumentEngine',
|
||||
'PhabricatorHighSecurityRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
|
||||
'PhabricatorHigherOrderChartFunction' => 'PhabricatorChartFunction',
|
||||
'PhabricatorHomeApplication' => 'PhabricatorApplication',
|
||||
'PhabricatorHomeConstants' => 'PhabricatorHomeController',
|
||||
'PhabricatorHomeController' => 'PhabricatorController',
|
||||
|
@ -9563,7 +9611,10 @@ phutil_register_library_map(array(
|
|||
'PhabricatorIteratedMD5PasswordHasher' => 'PhabricatorPasswordHasher',
|
||||
'PhabricatorIteratedMD5PasswordHasherTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorIteratorFileUploadSource' => 'PhabricatorFileUploadSource',
|
||||
'PhabricatorJIRAAuthProvider' => 'PhabricatorOAuth1AuthProvider',
|
||||
'PhabricatorJIRAAuthProvider' => array(
|
||||
'PhabricatorOAuth1AuthProvider',
|
||||
'DoorkeeperRemarkupURIInterface',
|
||||
),
|
||||
'PhabricatorJSONConfigType' => 'PhabricatorTextConfigType',
|
||||
'PhabricatorJSONDocumentEngine' => 'PhabricatorTextDocumentEngine',
|
||||
'PhabricatorJSONExportFormat' => 'PhabricatorExportFormat',
|
||||
|
@ -9698,6 +9749,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorMarkupInterface',
|
||||
),
|
||||
'PhabricatorMarkupPreviewController' => 'PhabricatorController',
|
||||
'PhabricatorMaxChartFunction' => 'PhabricatorChartFunction',
|
||||
'PhabricatorMemeEngine' => 'Phobject',
|
||||
'PhabricatorMemeRemarkupRule' => 'PhutilRemarkupRule',
|
||||
'PhabricatorMentionRemarkupRule' => 'PhutilRemarkupRule',
|
||||
|
@ -9764,6 +9816,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorMetronome' => 'Phobject',
|
||||
'PhabricatorMetronomeTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorMetronomicTriggerClock' => 'PhabricatorTriggerClock',
|
||||
'PhabricatorMinChartFunction' => 'PhabricatorChartFunction',
|
||||
'PhabricatorModularTransaction' => 'PhabricatorApplicationTransaction',
|
||||
'PhabricatorModularTransactionType' => 'Phobject',
|
||||
'PhabricatorMonogramDatasourceEngineExtension' => 'PhabricatorDatasourceEngineExtension',
|
||||
|
@ -10344,6 +10397,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorProjectBoardReorderController' => 'PhabricatorProjectBoardController',
|
||||
'PhabricatorProjectBoardViewController' => 'PhabricatorProjectBoardController',
|
||||
'PhabricatorProjectBuiltinsExample' => 'PhabricatorUIExample',
|
||||
'PhabricatorProjectBurndownChartEngine' => 'PhabricatorChartEngine',
|
||||
'PhabricatorProjectCardView' => 'AphrontTagView',
|
||||
'PhabricatorProjectColorTransaction' => 'PhabricatorProjectTransactionType',
|
||||
'PhabricatorProjectColorsConfigType' => 'PhabricatorJSONConfigType',
|
||||
|
@ -10459,6 +10513,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorProjectProjectPHIDType' => 'PhabricatorPHIDType',
|
||||
'PhabricatorProjectQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'PhabricatorProjectRemoveHeraldAction' => 'PhabricatorProjectHeraldAction',
|
||||
'PhabricatorProjectReportsController' => 'PhabricatorProjectController',
|
||||
'PhabricatorProjectReportsProfileMenuItem' => 'PhabricatorProfileMenuItem',
|
||||
'PhabricatorProjectSchemaSpec' => 'PhabricatorConfigSchemaSpec',
|
||||
'PhabricatorProjectSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||
'PhabricatorProjectSearchField' => 'PhabricatorSearchTokenizerField',
|
||||
|
@ -10573,6 +10629,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorRemarkupDocumentEngine' => 'PhabricatorDocumentEngine',
|
||||
'PhabricatorRemarkupEditField' => 'PhabricatorEditField',
|
||||
'PhabricatorRemarkupFigletBlockInterpreter' => 'PhutilRemarkupBlockInterpreter',
|
||||
'PhabricatorRemarkupHyperlinkEngineExtension' => 'PhutilRemarkupHyperlinkEngineExtension',
|
||||
'PhabricatorRemarkupUIExample' => 'PhabricatorUIExample',
|
||||
'PhabricatorRepositoriesSetupCheck' => 'PhabricatorSetupCheck',
|
||||
'PhabricatorRepository' => array(
|
||||
|
@ -10816,7 +10873,6 @@ phutil_register_library_map(array(
|
|||
'PhabricatorSearchDefaultController' => 'PhabricatorSearchBaseController',
|
||||
'PhabricatorSearchDeleteController' => 'PhabricatorSearchBaseController',
|
||||
'PhabricatorSearchDocument' => 'PhabricatorSearchDAO',
|
||||
'PhabricatorSearchDocumentField' => 'PhabricatorSearchDAO',
|
||||
'PhabricatorSearchDocumentFieldType' => 'Phobject',
|
||||
'PhabricatorSearchDocumentQuery' => 'PhabricatorPolicyAwareQuery',
|
||||
'PhabricatorSearchDocumentRelationship' => 'PhabricatorSearchDAO',
|
||||
|
@ -10861,7 +10917,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorSecuritySetupCheck' => 'PhabricatorSetupCheck',
|
||||
'PhabricatorSelectEditField' => 'PhabricatorEditField',
|
||||
'PhabricatorSelectSetting' => 'PhabricatorSetting',
|
||||
'PhabricatorSelfHyperlinkEngineExtension' => 'PhutilRemarkupHyperlinkEngineExtension',
|
||||
'PhabricatorSelfHyperlinkEngineExtension' => 'PhabricatorRemarkupHyperlinkEngineExtension',
|
||||
'PhabricatorSessionsSettingsPanel' => 'PhabricatorSettingsPanel',
|
||||
'PhabricatorSetConfigType' => 'PhabricatorTextConfigType',
|
||||
'PhabricatorSetting' => 'Phobject',
|
||||
|
@ -11053,6 +11109,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorSubscriptionsUIEventListener' => 'PhabricatorEventListener',
|
||||
'PhabricatorSubscriptionsUnsubscribeEmailCommand' => 'MetaMTAEmailTransactionCommand',
|
||||
'PhabricatorSubtypeEditEngineExtension' => 'PhabricatorEditEngineExtension',
|
||||
'PhabricatorSumChartFunction' => 'PhabricatorHigherOrderChartFunction',
|
||||
'PhabricatorSupportApplication' => 'PhabricatorApplication',
|
||||
'PhabricatorSyntaxHighlighter' => 'Phobject',
|
||||
'PhabricatorSyntaxHighlightingConfigOptions' => 'PhabricatorApplicationConfigOptions',
|
||||
|
@ -11140,6 +11197,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorTransactionsApplication' => 'PhabricatorApplication',
|
||||
'PhabricatorTransactionsDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension',
|
||||
'PhabricatorTransactionsFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension',
|
||||
'PhabricatorTransactionsObjectTypeDatasource' => 'PhabricatorTypeaheadDatasource',
|
||||
'PhabricatorTransformedFile' => 'PhabricatorFileDAO',
|
||||
'PhabricatorTranslationSetting' => 'PhabricatorOptionGroupSetting',
|
||||
'PhabricatorTranslationsConfigOptions' => 'PhabricatorApplicationConfigOptions',
|
||||
|
@ -11323,7 +11381,6 @@ phutil_register_library_map(array(
|
|||
'PhabricatorWorkingCopyDiscoveryTestCase' => 'PhabricatorWorkingCopyTestCase',
|
||||
'PhabricatorWorkingCopyPullTestCase' => 'PhabricatorWorkingCopyTestCase',
|
||||
'PhabricatorWorkingCopyTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorXChartFunction' => 'PhabricatorChartFunction',
|
||||
'PhabricatorXHPASTDAO' => 'PhabricatorLiskDAO',
|
||||
'PhabricatorXHPASTParseTree' => 'PhabricatorXHPASTDAO',
|
||||
'PhabricatorXHPASTViewController' => 'PhabricatorController',
|
||||
|
|
|
@ -663,7 +663,7 @@ final class AphrontRequest extends Phobject {
|
|||
}
|
||||
|
||||
public function isContinueRequest() {
|
||||
return $this->isFormPost() && $this->getStr('__continue__');
|
||||
return $this->isFormOrHisecPost() && $this->getStr('__continue__');
|
||||
}
|
||||
|
||||
public function isPreviewRequest() {
|
||||
|
|
|
@ -120,6 +120,13 @@ final class PhabricatorHighSecurityRequestExceptionHandler
|
|||
$dialog->addHiddenInput($key, $value);
|
||||
}
|
||||
|
||||
// See T13289. If the user hit a "some transactions have no effect" dialog
|
||||
// and elected to continue, we want to pass that flag through the MFA
|
||||
// dialog even though it is not normally a passthrough request parameter.
|
||||
if ($request->isContinueRequest()) {
|
||||
$dialog->addHiddenInput(AphrontRequest::TYPE_CONTINUE, 1);
|
||||
}
|
||||
|
||||
return $dialog;
|
||||
}
|
||||
|
||||
|
|
|
@ -239,7 +239,7 @@ final class PhabricatorAuditEditor
|
|||
$object);
|
||||
if ($request) {
|
||||
$xactions[] = $request;
|
||||
$this->setUnmentionablePHIDMap($request->getNewValue());
|
||||
$this->addUnmentionablePHIDs($request->getNewValue());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
@ -360,7 +360,6 @@ final class PhabricatorAuditEditor
|
|||
$flat_blocks = mpull($changes, 'getNewValue');
|
||||
$huge_block = implode("\n\n", $flat_blocks);
|
||||
$phid_map = array();
|
||||
$phid_map[] = $this->getUnmentionablePHIDMap();
|
||||
$monograms = array();
|
||||
|
||||
$task_refs = id(new ManiphestCustomFieldStatusParser())
|
||||
|
@ -385,7 +384,6 @@ final class PhabricatorAuditEditor
|
|||
->execute();
|
||||
$phid_map[] = mpull($objects, 'getPHID', 'getPHID');
|
||||
|
||||
|
||||
$reverts_refs = id(new DifferentialCustomFieldRevertsParser())
|
||||
->parseCorpus($huge_block);
|
||||
$reverts = array_mergev(ipull($reverts_refs, 'monograms'));
|
||||
|
@ -408,7 +406,7 @@ final class PhabricatorAuditEditor
|
|||
}
|
||||
|
||||
$phid_map = array_mergev($phid_map);
|
||||
$this->setUnmentionablePHIDMap($phid_map);
|
||||
$this->addUnmentionablePHIDs($phid_map);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ final class PhabricatorAuditTransaction
|
|||
|
||||
switch ($type) {
|
||||
case self::TYPE_COMMIT:
|
||||
return 3.0;
|
||||
return 300;
|
||||
}
|
||||
|
||||
return parent::getActionStrength();
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorAsanaAuthProvider extends PhabricatorOAuth2AuthProvider {
|
||||
final class PhabricatorAsanaAuthProvider
|
||||
extends PhabricatorOAuth2AuthProvider
|
||||
implements DoorkeeperRemarkupURIInterface {
|
||||
|
||||
public function getProviderName() {
|
||||
return pht('Asana');
|
||||
|
@ -46,4 +48,26 @@ final class PhabricatorAsanaAuthProvider extends PhabricatorOAuth2AuthProvider {
|
|||
return null;
|
||||
}
|
||||
|
||||
/* -( DoorkeeperRemarkupURIInterface )------------------------------------- */
|
||||
|
||||
public function getDoorkeeperURIRef(PhutilURI $uri) {
|
||||
$uri_string = phutil_string_cast($uri);
|
||||
|
||||
$pattern = '(https://app\\.asana\\.com/0/(\\d+)/(\\d+))';
|
||||
$matches = null;
|
||||
if (!preg_match($pattern, $uri_string, $matches)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$context_id = $matches[1];
|
||||
$task_id = $matches[2];
|
||||
|
||||
return id(new DoorkeeperURIRef())
|
||||
->setURI($uri)
|
||||
->setApplicationType(DoorkeeperBridgeAsana::APPTYPE_ASANA)
|
||||
->setApplicationDomain(DoorkeeperBridgeAsana::APPDOMAIN_ASANA)
|
||||
->setObjectType(DoorkeeperBridgeAsana::OBJTYPE_TASK)
|
||||
->setObjectID($task_id);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorJIRAAuthProvider extends PhabricatorOAuth1AuthProvider {
|
||||
|
||||
public function getJIRABaseURI() {
|
||||
return $this->getProviderConfig()->getProperty(self::PROPERTY_JIRA_URI);
|
||||
}
|
||||
final class PhabricatorJIRAAuthProvider
|
||||
extends PhabricatorOAuth1AuthProvider
|
||||
implements DoorkeeperRemarkupURIInterface {
|
||||
|
||||
public function getProviderName() {
|
||||
return pht('JIRA');
|
||||
|
@ -332,4 +330,33 @@ final class PhabricatorJIRAAuthProvider extends PhabricatorOAuth1AuthProvider {
|
|||
return $config->getProperty(self::PROPERTY_REPORT_COMMENT, true);
|
||||
}
|
||||
|
||||
/* -( DoorkeeperRemarkupURIInterface )------------------------------------- */
|
||||
|
||||
public function getDoorkeeperURIRef(PhutilURI $uri) {
|
||||
$uri_string = phutil_string_cast($uri);
|
||||
|
||||
$pattern = '((https?://\S+?)/browse/([A-Z]+-[1-9]\d*))';
|
||||
$matches = null;
|
||||
if (!preg_match($pattern, $uri_string, $matches)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$domain = $matches[1];
|
||||
$issue = $matches[2];
|
||||
|
||||
$config = $this->getProviderConfig();
|
||||
$base_uri = $config->getProperty(self::PROPERTY_JIRA_URI);
|
||||
|
||||
if ($domain !== rtrim($base_uri, '/')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return id(new DoorkeeperURIRef())
|
||||
->setURI($uri)
|
||||
->setApplicationType(DoorkeeperBridgeJIRA::APPTYPE_JIRA)
|
||||
->setApplicationDomain($this->getProviderDomain())
|
||||
->setObjectType(DoorkeeperBridgeJIRA::OBJTYPE_ISSUE)
|
||||
->setObjectID($issue);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -368,6 +368,10 @@ abstract class PhabricatorDaemonManagementWorkflow
|
|||
'class' => 'PhabricatorTriggerDaemon',
|
||||
'label' => 'trigger',
|
||||
),
|
||||
array(
|
||||
'class' => 'PhabricatorFactDaemon',
|
||||
'label' => 'fact',
|
||||
),
|
||||
array(
|
||||
'class' => 'PhabricatorTaskmasterDaemon',
|
||||
'label' => 'task',
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorDashboardChartPanelType
|
||||
extends PhabricatorDashboardPanelType {
|
||||
|
||||
public function getPanelTypeKey() {
|
||||
return 'chart';
|
||||
}
|
||||
|
||||
public function getPanelTypeName() {
|
||||
return pht('Chart Panel');
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return 'fa-area-chart';
|
||||
}
|
||||
|
||||
public function getPanelTypeDescription() {
|
||||
return pht('Show a chart.');
|
||||
}
|
||||
|
||||
protected function newEditEngineFields(PhabricatorDashboardPanel $panel) {
|
||||
$chart_field = id(new PhabricatorTextEditField())
|
||||
->setKey('chartKey')
|
||||
->setLabel(pht('Chart'))
|
||||
->setTransactionType(
|
||||
PhabricatorDashboardChartPanelChartTransaction::TRANSACTIONTYPE)
|
||||
->setValue($panel->getProperty('chartKey', ''));
|
||||
|
||||
return array(
|
||||
$chart_field,
|
||||
);
|
||||
}
|
||||
|
||||
public function renderPanelContent(
|
||||
PhabricatorUser $viewer,
|
||||
PhabricatorDashboardPanel $panel,
|
||||
PhabricatorDashboardPanelRenderingEngine $engine) {
|
||||
|
||||
$engine = id(new PhabricatorChartRenderingEngine())
|
||||
->setViewer($viewer);
|
||||
|
||||
$chart = $engine->loadChart($panel->getProperty('chartKey'));
|
||||
if (!$chart) {
|
||||
return pht('no such chart!');
|
||||
}
|
||||
|
||||
return $engine->newChartView();
|
||||
}
|
||||
|
||||
public function adjustPanelHeader(
|
||||
PhabricatorUser $viewer,
|
||||
PhabricatorDashboardPanel $panel,
|
||||
PhabricatorDashboardPanelRenderingEngine $engine,
|
||||
PHUIHeaderView $header) {
|
||||
|
||||
$key = $panel->getProperty('chartKey');
|
||||
$uri = PhabricatorChartRenderingEngine::getChartURI($key);
|
||||
|
||||
$icon = id(new PHUIIconView())
|
||||
->setIcon('fa-area-chart');
|
||||
|
||||
$button = id(new PHUIButtonView())
|
||||
->setTag('a')
|
||||
->setText(pht('View Chart'))
|
||||
->setIcon($icon)
|
||||
->setHref($uri)
|
||||
->setColor(PHUIButtonView::GREY);
|
||||
|
||||
$header->addActionLink($button);
|
||||
|
||||
return $header;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -12,13 +12,6 @@ abstract class PhabricatorDashboardPanelType extends Phobject {
|
|||
PhabricatorDashboardPanel $panel,
|
||||
PhabricatorDashboardPanelRenderingEngine $engine);
|
||||
|
||||
public function initializeFieldsFromRequest(
|
||||
PhabricatorDashboardPanel $panel,
|
||||
PhabricatorCustomFieldList $field_list,
|
||||
AphrontRequest $request) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should this panel pull content in over AJAX?
|
||||
*
|
||||
|
|
|
@ -55,33 +55,6 @@ final class PhabricatorDashboardQueryPanelType
|
|||
);
|
||||
}
|
||||
|
||||
public function initializeFieldsFromRequest(
|
||||
PhabricatorDashboardPanel $panel,
|
||||
PhabricatorCustomFieldList $field_list,
|
||||
AphrontRequest $request) {
|
||||
|
||||
$map = array();
|
||||
if (strlen($request->getStr('engine'))) {
|
||||
$map['class'] = $request->getStr('engine');
|
||||
}
|
||||
|
||||
if (strlen($request->getStr('query'))) {
|
||||
$map['key'] = $request->getStr('query');
|
||||
}
|
||||
|
||||
$full_map = array();
|
||||
foreach ($map as $key => $value) {
|
||||
$full_map["std:dashboard:core:{$key}"] = $value;
|
||||
}
|
||||
|
||||
foreach ($field_list->getFields() as $field) {
|
||||
$field_key = $field->getFieldKey();
|
||||
if (isset($full_map[$field_key])) {
|
||||
$field->setValueFromStorage($full_map[$field_key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function renderPanelContent(
|
||||
PhabricatorUser $viewer,
|
||||
PhabricatorDashboardPanel $panel,
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorDashboardChartPanelChartTransaction
|
||||
extends PhabricatorDashboardPanelPropertyTransaction {
|
||||
|
||||
const TRANSACTIONTYPE = 'chart.chartKey';
|
||||
|
||||
protected function getPropertyKey() {
|
||||
return 'chartKey';
|
||||
}
|
||||
|
||||
}
|
|
@ -846,13 +846,9 @@ final class DifferentialTransactionEditor
|
|||
$revert_phids = array();
|
||||
}
|
||||
|
||||
// See PHI574. Respect any unmentionable PHIDs which were set on the
|
||||
// Editor by the caller.
|
||||
$unmentionable_map = $this->getUnmentionablePHIDMap();
|
||||
$unmentionable_map += $task_phids;
|
||||
$unmentionable_map += $rev_phids;
|
||||
$unmentionable_map += $revert_phids;
|
||||
$this->setUnmentionablePHIDMap($unmentionable_map);
|
||||
$this->addUnmentionablePHIDs($task_phids);
|
||||
$this->addUnmentionablePHIDs($rev_phids);
|
||||
$this->addUnmentionablePHIDs($revert_phids);
|
||||
|
||||
$result = array();
|
||||
foreach ($edges as $type => $specs) {
|
||||
|
|
|
@ -26,6 +26,7 @@ final class DifferentialRevisionQuery
|
|||
private $isOpen;
|
||||
private $createdEpochMin;
|
||||
private $createdEpochMax;
|
||||
private $noReviewers;
|
||||
|
||||
const ORDER_MODIFIED = 'order-modified';
|
||||
const ORDER_CREATED = 'order-created';
|
||||
|
@ -98,7 +99,31 @@ final class DifferentialRevisionQuery
|
|||
* @task config
|
||||
*/
|
||||
public function withReviewers(array $reviewer_phids) {
|
||||
$this->reviewers = $reviewer_phids;
|
||||
if ($reviewer_phids === array()) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Empty "withReviewers()" constraint is invalid. Provide one or '.
|
||||
'more values, or remove the constraint.'));
|
||||
}
|
||||
|
||||
$with_none = false;
|
||||
|
||||
foreach ($reviewer_phids as $key => $phid) {
|
||||
switch ($phid) {
|
||||
case DifferentialNoReviewersDatasource::FUNCTION_TOKEN:
|
||||
$with_none = true;
|
||||
unset($reviewer_phids[$key]);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->noReviewers = $with_none;
|
||||
if ($reviewer_phids) {
|
||||
$this->reviewers = array_values($reviewer_phids);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -572,7 +597,7 @@ final class DifferentialRevisionQuery
|
|||
if ($this->reviewers) {
|
||||
$joins[] = qsprintf(
|
||||
$conn,
|
||||
'JOIN %T reviewer ON reviewer.revisionPHID = r.phid
|
||||
'LEFT JOIN %T reviewer ON reviewer.revisionPHID = r.phid
|
||||
AND reviewer.reviewerStatus != %s
|
||||
AND reviewer.reviewerPHID in (%Ls)',
|
||||
id(new DifferentialReviewer())->getTableName(),
|
||||
|
@ -580,6 +605,15 @@ final class DifferentialRevisionQuery
|
|||
$this->reviewers);
|
||||
}
|
||||
|
||||
if ($this->noReviewers) {
|
||||
$joins[] = qsprintf(
|
||||
$conn,
|
||||
'LEFT JOIN %T no_reviewer ON no_reviewer.revisionPHID = r.phid
|
||||
AND no_reviewer.reviewerStatus != %s',
|
||||
id(new DifferentialReviewer())->getTableName(),
|
||||
DifferentialReviewerStatus::STATUS_RESIGNED);
|
||||
}
|
||||
|
||||
if ($this->draftAuthors) {
|
||||
$joins[] = qsprintf(
|
||||
$conn,
|
||||
|
@ -715,6 +749,24 @@ final class DifferentialRevisionQuery
|
|||
$statuses);
|
||||
}
|
||||
|
||||
$reviewer_subclauses = array();
|
||||
|
||||
if ($this->noReviewers) {
|
||||
$reviewer_subclauses[] = qsprintf(
|
||||
$conn,
|
||||
'no_reviewer.reviewerPHID IS NULL');
|
||||
}
|
||||
|
||||
if ($this->reviewers) {
|
||||
$reviewer_subclauses[] = qsprintf(
|
||||
$conn,
|
||||
'reviewer.reviewerPHID IS NOT NULL');
|
||||
}
|
||||
|
||||
if ($reviewer_subclauses) {
|
||||
$where[] = qsprintf($conn, '%LO', $reviewer_subclauses);
|
||||
}
|
||||
|
||||
$where[] = $this->buildWhereClauseParts($conn);
|
||||
|
||||
return $this->formatWhereClause($conn, $where);
|
||||
|
@ -735,6 +787,10 @@ final class DifferentialRevisionQuery
|
|||
return true;
|
||||
}
|
||||
|
||||
if ($this->noReviewers) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return parent::shouldGroupQueryResultRows();
|
||||
}
|
||||
|
||||
|
|
|
@ -73,7 +73,7 @@ final class DifferentialRevisionSearchEngine
|
|||
->setLabel(pht('Reviewers'))
|
||||
->setKey('reviewerPHIDs')
|
||||
->setAliases(array('reviewer', 'reviewers', 'reviewerPHID'))
|
||||
->setDatasource(new DiffusionAuditorFunctionDatasource())
|
||||
->setDatasource(new DifferentialReviewerFunctionDatasource())
|
||||
->setDescription(
|
||||
pht('Find revisions with specific reviewers.')),
|
||||
id(new PhabricatorSearchDatasourceField())
|
||||
|
|
|
@ -130,7 +130,7 @@ final class DifferentialTransaction
|
|||
public function getActionStrength() {
|
||||
switch ($this->getTransactionType()) {
|
||||
case self::TYPE_ACTION:
|
||||
return 3;
|
||||
return 300;
|
||||
}
|
||||
|
||||
return parent::getActionStrength();
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
|
||||
final class DifferentialNoReviewersDatasource
|
||||
extends PhabricatorTypeaheadDatasource {
|
||||
|
||||
const FUNCTION_TOKEN = 'none()';
|
||||
|
||||
public function getBrowseTitle() {
|
||||
return pht('Browse No Reviewers');
|
||||
}
|
||||
|
||||
public function getPlaceholderText() {
|
||||
return pht('Type "none"...');
|
||||
}
|
||||
|
||||
public function getDatasourceApplicationClass() {
|
||||
return 'PhabricatorDifferentialApplication';
|
||||
}
|
||||
|
||||
public function getDatasourceFunctions() {
|
||||
return array(
|
||||
'none' => array(
|
||||
'name' => pht('No Reviewers'),
|
||||
'summary' => pht('Find results which have no reviewers.'),
|
||||
'description' => pht(
|
||||
"This function includes results which have no reviewers. Use a ".
|
||||
"query like this to find results with no reviewers:\n\n%s\n\n".
|
||||
"If you combine this function with other functions, the query will ".
|
||||
"return results which match the other selectors //or// have no ".
|
||||
"reviewers. For example, this query will find results which have ".
|
||||
"`alincoln` as a reviewer, and will also find results which have ".
|
||||
"no reviewers:".
|
||||
"\n\n%s",
|
||||
'> none()',
|
||||
'> alincoln, none()'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public function loadResults() {
|
||||
$results = array(
|
||||
$this->buildNoReviewersResult(),
|
||||
);
|
||||
return $this->filterResultsAgainstTokens($results);
|
||||
}
|
||||
|
||||
protected function evaluateFunction($function, array $argv_list) {
|
||||
$results = array();
|
||||
|
||||
foreach ($argv_list as $argv) {
|
||||
$results[] = self::FUNCTION_TOKEN;
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function renderFunctionTokens($function, array $argv_list) {
|
||||
$results = array();
|
||||
foreach ($argv_list as $argv) {
|
||||
$results[] = PhabricatorTypeaheadTokenView::newFromTypeaheadResult(
|
||||
$this->buildNoReviewersResult());
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
private function buildNoReviewersResult() {
|
||||
$name = pht('No Reviewers');
|
||||
|
||||
return $this->newFunctionResult()
|
||||
->setName($name.' none')
|
||||
->setDisplayName($name)
|
||||
->setIcon('fa-ban')
|
||||
->setPHID('none()')
|
||||
->setUnique(true)
|
||||
->addAttribute(pht('Select results with no reviewers.'));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
final class DifferentialReviewerFunctionDatasource
|
||||
extends PhabricatorTypeaheadCompositeDatasource {
|
||||
|
||||
public function getBrowseTitle() {
|
||||
return pht('Browse Reviewers');
|
||||
}
|
||||
|
||||
public function getPlaceholderText() {
|
||||
return pht('Type a user, project, package name or function...');
|
||||
}
|
||||
|
||||
public function getDatasourceApplicationClass() {
|
||||
return 'PhabricatorDifferentialApplication';
|
||||
}
|
||||
|
||||
public function getComponentDatasources() {
|
||||
return array(
|
||||
new PhabricatorProjectOrUserFunctionDatasource(),
|
||||
new PhabricatorOwnersPackageFunctionDatasource(),
|
||||
new DifferentialNoReviewersDatasource(),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -60,6 +60,16 @@ final class DifferentialRevisionListView extends AphrontView {
|
|||
$handle_phids = array();
|
||||
foreach ($this->revisions as $key => $revision) {
|
||||
$reviewers = $revision->getReviewers();
|
||||
|
||||
// Don't show reviewers who have resigned. The "Reviewers" constraint
|
||||
// does not respect these reviewers and they largely don't count as
|
||||
// reviewers.
|
||||
foreach ($reviewers as $reviewer_key => $reviewer) {
|
||||
if ($reviewer->isResigned()) {
|
||||
unset($reviewers[$reviewer_key]);
|
||||
}
|
||||
}
|
||||
|
||||
if (count($reviewers) > $reviewer_limit) {
|
||||
$reviewers = array_slice($reviewers, 0, $reviewer_limit);
|
||||
$reviewer_more[$key] = true;
|
||||
|
|
|
@ -40,7 +40,7 @@ abstract class DifferentialRevisionActionTransaction
|
|||
}
|
||||
|
||||
public function getActionStrength() {
|
||||
return 3;
|
||||
return 300;
|
||||
}
|
||||
|
||||
public function getRevisionActionOrderVector() {
|
||||
|
|
|
@ -10,6 +10,26 @@ final class DifferentialRevisionUpdateTransaction
|
|||
return $object->getActiveDiffPHID();
|
||||
}
|
||||
|
||||
public function generateNewValue($object, $value) {
|
||||
// See T13290. If we're updating the revision in response to a commit but
|
||||
// the revision is already closed, return the old value so we no-op this
|
||||
// transaction. We don't want to attach more than one commit-diff to a
|
||||
// revision.
|
||||
|
||||
// Although we can try to bail out earlier so we don't generate this
|
||||
// transaction in the first place, we may race another worker and end up
|
||||
// trying to apply it anyway. Here, we have a lock on the object and can
|
||||
// be certain about the object state.
|
||||
|
||||
if ($this->isCommitUpdate()) {
|
||||
if ($object->isClosed()) {
|
||||
return $this->generateOldValue($object);
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function applyInternalEffects($object, $value) {
|
||||
$should_review = $this->shouldRequestReviewAfterUpdate($object);
|
||||
if ($should_review) {
|
||||
|
@ -99,7 +119,7 @@ final class DifferentialRevisionUpdateTransaction
|
|||
}
|
||||
|
||||
public function getActionStrength() {
|
||||
return 2;
|
||||
return 200;
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
|
|
|
@ -26,7 +26,7 @@ final class DifferentialRevisionWrongBuildsTransaction
|
|||
}
|
||||
|
||||
public function getActionStrength() {
|
||||
return 4;
|
||||
return 400;
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
|
|
|
@ -22,7 +22,7 @@ final class DifferentialRevisionWrongStateTransaction
|
|||
}
|
||||
|
||||
public function getActionStrength() {
|
||||
return 4;
|
||||
return 400;
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
|
|
|
@ -40,6 +40,7 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication {
|
|||
new DiffusionCommitRemarkupRule(),
|
||||
new DiffusionRepositoryRemarkupRule(),
|
||||
new DiffusionRepositoryByIDRemarkupRule(),
|
||||
new DiffusionSourceLinkRemarkupRule(),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
final class DiffusionSourceHyperlinkEngineExtension
|
||||
extends PhabricatorRemarkupHyperlinkEngineExtension {
|
||||
|
||||
const LINKENGINEKEY = 'diffusion-src';
|
||||
|
||||
public function processHyperlinks(array $hyperlinks) {
|
||||
$engine = $this->getEngine();
|
||||
$viewer = $engine->getConfig('viewer');
|
||||
|
||||
if (!$viewer) {
|
||||
return;
|
||||
}
|
||||
|
||||
$hyperlinks = $this->getSelfLinks($hyperlinks);
|
||||
|
||||
$links = array();
|
||||
foreach ($hyperlinks as $link) {
|
||||
$uri = $link->getURI();
|
||||
$uri = new PhutilURI($uri);
|
||||
|
||||
$path = $uri->getPath();
|
||||
|
||||
$pattern =
|
||||
'(^'.
|
||||
'/(?:diffusion|source)'.
|
||||
'/(?P<identifier>[^/]+)'.
|
||||
'/browse'.
|
||||
'/(?P<blob>.*)'.
|
||||
'\z)';
|
||||
$matches = null;
|
||||
if (!preg_match($pattern, $path, $matches)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$links[] = array(
|
||||
'ref' => $link,
|
||||
'identifier' => $matches['identifier'],
|
||||
'blob' => $matches['blob'],
|
||||
);
|
||||
}
|
||||
|
||||
if (!$links) {
|
||||
return;
|
||||
}
|
||||
|
||||
$identifiers = ipull($links, 'identifier');
|
||||
|
||||
$query = id(new PhabricatorRepositoryQuery())
|
||||
->setViewer($viewer)
|
||||
->withIdentifiers($identifiers);
|
||||
|
||||
$query->execute();
|
||||
|
||||
$repository_map = $query->getIdentifierMap();
|
||||
|
||||
foreach ($links as $link) {
|
||||
$identifier = $link['identifier'];
|
||||
|
||||
$repository = idx($repository_map, $identifier);
|
||||
if (!$repository) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$ref = $link['ref'];
|
||||
$uri = $ref->getURI();
|
||||
|
||||
|
||||
$tag = id(new DiffusionSourceLinkView())
|
||||
->setViewer($viewer)
|
||||
->setRepository($repository)
|
||||
->setURI($uri)
|
||||
->setBlob($link['blob']);
|
||||
|
||||
if (!$ref->isEmbed()) {
|
||||
$tag->setText($uri);
|
||||
}
|
||||
|
||||
$ref->setResult($tag);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,221 @@
|
|||
<?php
|
||||
|
||||
final class DiffusionSourceLinkRemarkupRule
|
||||
extends PhutilRemarkupRule {
|
||||
|
||||
const KEY_SOURCELINKS = 'diffusion.links';
|
||||
|
||||
public function getPriority() {
|
||||
return 200.0;
|
||||
}
|
||||
|
||||
public function apply($text) {
|
||||
return preg_replace_callback(
|
||||
'@{(?:src|source)\b((?:[^}\\\\]+|\\\\.)*)}@m',
|
||||
array($this, 'markupSourceLink'),
|
||||
$text);
|
||||
}
|
||||
|
||||
public function markupSourceLink(array $matches) {
|
||||
$engine = $this->getEngine();
|
||||
$text_mode = $engine->isTextMode();
|
||||
$mail_mode = $engine->isHTMLMailMode();
|
||||
|
||||
if (!$this->isFlatText($matches[0]) || $text_mode || $mail_mode) {
|
||||
// We could do better than this in text mode and mail mode, but focus
|
||||
// on web mode first.
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
$metadata_key = self::KEY_SOURCELINKS;
|
||||
$metadata = $engine->getTextMetadata($metadata_key, array());
|
||||
|
||||
$token = $engine->storeText($matches[0]);
|
||||
|
||||
$metadata[] = array(
|
||||
'token' => $token,
|
||||
'raw' => $matches[0],
|
||||
'input' => $matches[1],
|
||||
);
|
||||
|
||||
$engine->setTextMetadata($metadata_key, $metadata);
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
public function didMarkupText() {
|
||||
$engine = $this->getEngine();
|
||||
$metadata_key = self::KEY_SOURCELINKS;
|
||||
$metadata = $engine->getTextMetadata($metadata_key, array());
|
||||
|
||||
if (!$metadata) {
|
||||
return;
|
||||
}
|
||||
|
||||
$viewer = $engine->getConfig('viewer');
|
||||
if (!$viewer) {
|
||||
return;
|
||||
}
|
||||
|
||||
$defaults = array(
|
||||
'repository' => null,
|
||||
'line' => null,
|
||||
'commit' => null,
|
||||
'ref' => null,
|
||||
);
|
||||
|
||||
$tags = array();
|
||||
foreach ($metadata as $ref) {
|
||||
$token = $ref['token'];
|
||||
$raw = $ref['raw'];
|
||||
$input = $ref['input'];
|
||||
|
||||
$pattern =
|
||||
'(^'.
|
||||
'[\s,]*'.
|
||||
'(?:"(?P<quotedpath>(?:[^\\\\"]+|\\.)+)"|(?P<rawpath>[^\s,]+))'.
|
||||
'[\s,]*'.
|
||||
'(?P<options>.*)'.
|
||||
'\z)';
|
||||
$matches = null;
|
||||
if (!preg_match($pattern, $input, $matches)) {
|
||||
$hint_text = pht(
|
||||
'Missing path, expected "{src path ...}" in: %s',
|
||||
$raw);
|
||||
$hint = $this->newSyntaxHint($hint_text);
|
||||
|
||||
$engine->overwriteStoredText($token, $hint);
|
||||
continue;
|
||||
}
|
||||
|
||||
$path = idx($matches, 'rawpath');
|
||||
if (!strlen($path)) {
|
||||
$path = idx($matches, 'quotedpath');
|
||||
$path = stripcslashes($path);
|
||||
}
|
||||
|
||||
$parts = explode(':', $path, 2);
|
||||
if (count($parts) == 2) {
|
||||
$repository = nonempty($parts[0], null);
|
||||
$path = $parts[1];
|
||||
} else {
|
||||
$repository = null;
|
||||
$path = $parts[0];
|
||||
}
|
||||
|
||||
$options = $matches['options'];
|
||||
|
||||
$parser = new PhutilSimpleOptions();
|
||||
$options = $parser->parse($options) + $defaults;
|
||||
|
||||
foreach ($options as $key => $value) {
|
||||
if (!array_key_exists($key, $defaults)) {
|
||||
$hint_text = pht(
|
||||
'Unknown option "%s" in: %s',
|
||||
$key,
|
||||
$raw);
|
||||
$hint = $this->newSyntaxHint($hint_text);
|
||||
|
||||
$engine->overwriteStoredText($token, $hint);
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
if ($options['repository'] !== null) {
|
||||
$repository = $options['repository'];
|
||||
}
|
||||
|
||||
if ($repository === null) {
|
||||
$hint_text = pht(
|
||||
'Missing repository, expected "{src repository:path ...}" '.
|
||||
'or "{src path repository=...}" in: %s',
|
||||
$raw);
|
||||
$hint = $this->newSyntaxHint($hint_text);
|
||||
|
||||
$engine->overwriteStoredText($token, $hint);
|
||||
continue;
|
||||
}
|
||||
|
||||
$tags[] = array(
|
||||
'token' => $token,
|
||||
'raw' => $raw,
|
||||
'identifier' => $repository,
|
||||
'path' => $path,
|
||||
'options' => $options,
|
||||
);
|
||||
}
|
||||
|
||||
if (!$tags) {
|
||||
return;
|
||||
}
|
||||
|
||||
$query = id(new PhabricatorRepositoryQuery())
|
||||
->setViewer($viewer)
|
||||
->withIdentifiers(ipull($tags, 'identifier'));
|
||||
|
||||
$query->execute();
|
||||
|
||||
$repository_map = $query->getIdentifierMap();
|
||||
|
||||
foreach ($tags as $tag) {
|
||||
$token = $tag['token'];
|
||||
|
||||
$identifier = $tag['identifier'];
|
||||
$repository = idx($repository_map, $identifier);
|
||||
if (!$repository) {
|
||||
// For now, just bail out here. Ideally, we should distingiush between
|
||||
// restricted and invalid repositories.
|
||||
continue;
|
||||
}
|
||||
|
||||
$drequest = DiffusionRequest::newFromDictionary(
|
||||
array(
|
||||
'user' => $viewer,
|
||||
'repository' => $repository,
|
||||
));
|
||||
|
||||
$options = $tag['options'];
|
||||
|
||||
$line = $options['line'];
|
||||
$commit = $options['commit'];
|
||||
$ref_name = $options['ref'];
|
||||
|
||||
$link_uri = $drequest->generateURI(
|
||||
array(
|
||||
'action' => 'browse',
|
||||
'path' => $tag['path'],
|
||||
'commit' => $commit,
|
||||
'line' => $line,
|
||||
'branch' => $ref_name,
|
||||
));
|
||||
|
||||
$view = id(new DiffusionSourceLinkView())
|
||||
->setRepository($repository)
|
||||
->setPath($tag['path'])
|
||||
->setURI($link_uri);
|
||||
|
||||
if ($line !== null) {
|
||||
$view->setLine($line);
|
||||
}
|
||||
|
||||
if ($commit !== null) {
|
||||
$view->setCommit($commit);
|
||||
}
|
||||
|
||||
if ($ref_name !== null) {
|
||||
$view->setRefName($ref_name);
|
||||
}
|
||||
|
||||
$engine->overwriteStoredText($token, $view);
|
||||
}
|
||||
}
|
||||
|
||||
private function newSyntaxHint($text) {
|
||||
return id(new PHUITagView())
|
||||
->setType(PHUITagView::TYPE_SHADE)
|
||||
->setColor('red')
|
||||
->setIcon('fa-exclamation-triangle')
|
||||
->setName($text);
|
||||
}
|
||||
|
||||
}
|
|
@ -2,10 +2,6 @@
|
|||
|
||||
final class DiffusionGitRequest extends DiffusionRequest {
|
||||
|
||||
public function supportsBranches() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function isStableCommit($symbol) {
|
||||
return preg_match('/^[a-f0-9]{40}\z/', $symbol);
|
||||
}
|
||||
|
|
|
@ -2,10 +2,6 @@
|
|||
|
||||
final class DiffusionMercurialRequest extends DiffusionRequest {
|
||||
|
||||
public function supportsBranches() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function isStableCommit($symbol) {
|
||||
return preg_match('/^[a-f0-9]{40}\z/', $symbol);
|
||||
}
|
||||
|
|
|
@ -28,7 +28,10 @@ abstract class DiffusionRequest extends Phobject {
|
|||
private $branchObject = false;
|
||||
private $refAlternatives;
|
||||
|
||||
abstract public function supportsBranches();
|
||||
final public function supportsBranches() {
|
||||
return $this->getRepository()->supportsRefs();
|
||||
}
|
||||
|
||||
abstract protected function isStableCommit($symbol);
|
||||
|
||||
protected function didInitialize() {
|
||||
|
|
|
@ -2,10 +2,6 @@
|
|||
|
||||
final class DiffusionSvnRequest extends DiffusionRequest {
|
||||
|
||||
public function supportsBranches() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function isStableCommit($symbol) {
|
||||
return preg_match('/^[1-9]\d*\z/', $symbol);
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ final class DiffusionReadmeView extends DiffusionView {
|
|||
$engine = $markup_object->newMarkupEngine($markup_field);
|
||||
|
||||
$readme_content = $content;
|
||||
$class = null;
|
||||
$class = 'ml';
|
||||
break;
|
||||
case 'rainbow':
|
||||
$content = id(new PhutilRainbowSyntaxHighlighter())
|
||||
|
@ -93,10 +93,12 @@ final class DiffusionReadmeView extends DiffusionView {
|
|||
break;
|
||||
}
|
||||
|
||||
$readme_content = phutil_tag_div($class, $readme_content);
|
||||
$document = id(new PHUIDocumentView())
|
||||
->setFluid(true)
|
||||
->appendChild($readme_content);
|
||||
$readme_content = phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => $class,
|
||||
),
|
||||
$readme_content);
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader($readme_name)
|
||||
|
@ -106,7 +108,7 @@ final class DiffusionReadmeView extends DiffusionView {
|
|||
->setHeader($header)
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->addClass('diffusion-mobile-view')
|
||||
->appendChild($document)
|
||||
->appendChild($readme_content)
|
||||
->addClass('diffusion-readme-view');
|
||||
}
|
||||
|
||||
|
|
208
src/applications/diffusion/view/DiffusionSourceLinkView.php
Normal file
208
src/applications/diffusion/view/DiffusionSourceLinkView.php
Normal file
|
@ -0,0 +1,208 @@
|
|||
<?php
|
||||
|
||||
final class DiffusionSourceLinkView
|
||||
extends AphrontView {
|
||||
|
||||
private $repository;
|
||||
private $text;
|
||||
private $uri;
|
||||
private $blob;
|
||||
private $blobMap;
|
||||
private $refName;
|
||||
private $path;
|
||||
private $line;
|
||||
private $commit;
|
||||
|
||||
public function setRepository($repository) {
|
||||
$this->repository = $repository;
|
||||
$this->blobMap = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRepository() {
|
||||
return $this->repository;
|
||||
}
|
||||
|
||||
public function setText($text) {
|
||||
$this->text = $text;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getText() {
|
||||
return $this->text;
|
||||
}
|
||||
|
||||
public function setURI($uri) {
|
||||
$this->uri = $uri;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
return $this->uri;
|
||||
}
|
||||
|
||||
public function setBlob($blob) {
|
||||
$this->blob = $blob;
|
||||
$this->blobMap = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBlob() {
|
||||
return $this->blob;
|
||||
}
|
||||
|
||||
public function setRefName($ref_name) {
|
||||
$this->refName = $ref_name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRefName() {
|
||||
return $this->refName;
|
||||
}
|
||||
|
||||
public function setPath($path) {
|
||||
$this->path = $path;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPath() {
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
public function setCommit($commit) {
|
||||
$this->commit = $commit;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCommit() {
|
||||
return $this->commit;
|
||||
}
|
||||
|
||||
public function setLine($line) {
|
||||
$this->line = $line;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLine() {
|
||||
return $this->line;
|
||||
}
|
||||
|
||||
public function getDisplayPath() {
|
||||
if ($this->path !== null) {
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
return $this->getBlobPath();
|
||||
}
|
||||
|
||||
public function getDisplayRefName() {
|
||||
if ($this->refName !== null) {
|
||||
return $this->refName;
|
||||
}
|
||||
|
||||
return $this->getBlobRefName();
|
||||
}
|
||||
|
||||
public function getDisplayCommit() {
|
||||
if ($this->commit !== null) {
|
||||
return $this->commit;
|
||||
}
|
||||
|
||||
return $this->getBlobCommit();
|
||||
}
|
||||
|
||||
public function getDisplayLine() {
|
||||
if ($this->line !== null) {
|
||||
return $this->line;
|
||||
}
|
||||
|
||||
return $this->getBlobLine();
|
||||
}
|
||||
|
||||
private function getBlobPath() {
|
||||
return idx($this->getBlobMap(), 'path');
|
||||
}
|
||||
|
||||
private function getBlobRefName() {
|
||||
return idx($this->getBlobMap(), 'branch');
|
||||
}
|
||||
|
||||
private function getBlobLine() {
|
||||
return idx($this->getBlobMap(), 'line');
|
||||
}
|
||||
|
||||
private function getBlobCommit() {
|
||||
return idx($this->getBlobMap(), 'commit');
|
||||
}
|
||||
|
||||
private function getBlobMap() {
|
||||
if ($this->blobMap === null) {
|
||||
$repository = $this->getRepository();
|
||||
$blob = $this->blob;
|
||||
|
||||
if ($repository && ($blob !== null)) {
|
||||
$map = DiffusionRequest::parseRequestBlob(
|
||||
$blob,
|
||||
$repository->supportsRefs());
|
||||
} else {
|
||||
$map = array();
|
||||
}
|
||||
|
||||
$this->blobMap = $map;
|
||||
}
|
||||
|
||||
return $this->blobMap;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$repository = $this->getRepository();
|
||||
$uri = $this->getURI();
|
||||
|
||||
$color = 'blue';
|
||||
$icon = 'fa-file-text-o';
|
||||
|
||||
$text = $this->getText();
|
||||
if (!strlen($text)) {
|
||||
$path = $this->getDisplayPath();
|
||||
|
||||
$line = $this->getDisplayLine();
|
||||
if ($line !== null) {
|
||||
$path = pht('%s:%s', $path, $line);
|
||||
}
|
||||
|
||||
if ($repository) {
|
||||
$path = pht('%s %s', $repository->getMonogram(), $path);
|
||||
}
|
||||
|
||||
if ($repository && $repository->supportsRefs()) {
|
||||
$default_ref = $repository->getDefaultBranch();
|
||||
} else {
|
||||
$default_ref = null;
|
||||
}
|
||||
|
||||
$ref_name = $this->getDisplayRefName();
|
||||
if ($ref_name === $default_ref) {
|
||||
$ref_name = null;
|
||||
}
|
||||
|
||||
$commit = $this->getDisplayCommit();
|
||||
if ($ref_name !== null && $commit !== null) {
|
||||
$text = pht('%s (on %s at %s)', $path, $ref_name, $commit);
|
||||
} else if ($ref_name !== null) {
|
||||
$text = pht('%s (on %s)', $path, $ref_name);
|
||||
} else if ($commit !== null) {
|
||||
$text = pht('%s (at %s)', $path, $commit);
|
||||
} else {
|
||||
$text = $path;
|
||||
}
|
||||
}
|
||||
|
||||
return id(new PHUITagView())
|
||||
->setType(PHUITagView::TYPE_SHADE)
|
||||
->setColor($color)
|
||||
->setIcon($icon)
|
||||
->setHref($uri)
|
||||
->setName($text);
|
||||
}
|
||||
|
||||
}
|
|
@ -139,10 +139,7 @@ final class DiffusionUpdateObjectAfterCommitWorker
|
|||
->setContentSource($content_source)
|
||||
->setContinueOnNoEffect(true)
|
||||
->setContinueOnMissingFields(true)
|
||||
->setUnmentionablePHIDMap(
|
||||
array(
|
||||
$commit_phid => $commit_phid,
|
||||
));
|
||||
->addUnmentionablePHIDs(array($commit_phid));
|
||||
|
||||
$editor->applyTransactions($task, $xactions);
|
||||
}
|
||||
|
|
|
@ -22,13 +22,6 @@ final class PhabricatorDoorkeeperApplication extends PhabricatorApplication {
|
|||
return pht('Connect to Other Software');
|
||||
}
|
||||
|
||||
public function getRemarkupRules() {
|
||||
return array(
|
||||
new DoorkeeperAsanaRemarkupRule(),
|
||||
new DoorkeeperJIRARemarkupRule(),
|
||||
);
|
||||
}
|
||||
|
||||
public function getRoutes() {
|
||||
return array(
|
||||
'/doorkeeper/' => array(
|
||||
|
|
|
@ -5,6 +5,16 @@ abstract class DoorkeeperBridge extends Phobject {
|
|||
private $viewer;
|
||||
private $context = array();
|
||||
private $throwOnMissingLink;
|
||||
private $timeout;
|
||||
|
||||
public function setTimeout($timeout) {
|
||||
$this->timeout = $timeout;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTimeout() {
|
||||
return $this->timeout;
|
||||
}
|
||||
|
||||
public function setThrowOnMissingLink($throw_on_missing_link) {
|
||||
$this->throwOnMissingLink = $throw_on_missing_link;
|
||||
|
|
|
@ -62,6 +62,11 @@ final class DoorkeeperBridgeAsana extends DoorkeeperBridge {
|
|||
$template = id(new PhutilAsanaFuture())
|
||||
->setAccessToken($token);
|
||||
|
||||
$timeout = $this->getTimeout();
|
||||
if ($timeout !== null) {
|
||||
$template->setTimeout($timeout);
|
||||
}
|
||||
|
||||
$futures = array();
|
||||
foreach ($id_map as $key => $id) {
|
||||
$futures[$key] = id(clone $template)
|
||||
|
|
|
@ -47,12 +47,20 @@ final class DoorkeeperBridgeJIRA extends DoorkeeperBridge {
|
|||
// (by querying all instances). For now, just query the one instance.
|
||||
$account = head($accounts);
|
||||
|
||||
$timeout = $this->getTimeout();
|
||||
|
||||
$futures = array();
|
||||
foreach ($id_map as $key => $id) {
|
||||
$futures[$key] = $provider->newJIRAFuture(
|
||||
$future = $provider->newJIRAFuture(
|
||||
$account,
|
||||
'rest/api/2/issue/'.phutil_escape_uri($id),
|
||||
'GET');
|
||||
|
||||
if ($timeout !== null) {
|
||||
$future->setTimeout($timeout);
|
||||
}
|
||||
|
||||
$futures[$key] = $future;
|
||||
}
|
||||
|
||||
$results = array();
|
||||
|
|
|
@ -26,6 +26,7 @@ final class DoorkeeperTagsController extends PhabricatorController {
|
|||
$refs = id(new DoorkeeperImportEngine())
|
||||
->setViewer($viewer)
|
||||
->setRefs($refs)
|
||||
->setTimeout(15)
|
||||
->execute();
|
||||
|
||||
$results = array();
|
||||
|
|
|
@ -8,6 +8,7 @@ final class DoorkeeperImportEngine extends Phobject {
|
|||
private $localOnly;
|
||||
private $throwOnMissingLink;
|
||||
private $context = array();
|
||||
private $timeout;
|
||||
|
||||
public function setViewer(PhabricatorUser $viewer) {
|
||||
$this->viewer = $viewer;
|
||||
|
@ -43,6 +44,15 @@ final class DoorkeeperImportEngine extends Phobject {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function setTimeout($timeout) {
|
||||
$this->timeout = $timeout;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTimeout() {
|
||||
return $this->timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure behavior if remote refs can not be retrieved because an
|
||||
* authentication link is missing.
|
||||
|
@ -98,10 +108,16 @@ final class DoorkeeperImportEngine extends Phobject {
|
|||
->setFilterMethod('isEnabled')
|
||||
->execute();
|
||||
|
||||
$timeout = $this->getTimeout();
|
||||
foreach ($bridges as $key => $bridge) {
|
||||
$bridge->setViewer($viewer);
|
||||
$bridge->setThrowOnMissingLink($this->throwOnMissingLink);
|
||||
$bridge->setContext($this->context);
|
||||
$bridge
|
||||
->setViewer($viewer)
|
||||
->setThrowOnMissingLink($this->throwOnMissingLink)
|
||||
->setContext($this->context);
|
||||
|
||||
if ($timeout !== null) {
|
||||
$bridge->setTimeout($timeout);
|
||||
}
|
||||
}
|
||||
|
||||
$working_set = $refs;
|
||||
|
|
91
src/applications/doorkeeper/engine/DoorkeeperURIRef.php
Normal file
91
src/applications/doorkeeper/engine/DoorkeeperURIRef.php
Normal file
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
|
||||
final class DoorkeeperURIRef extends Phobject {
|
||||
|
||||
private $uri;
|
||||
private $applicationType;
|
||||
private $applicationDomain;
|
||||
private $objectType;
|
||||
private $objectID;
|
||||
private $text;
|
||||
private $displayMode = self::DISPLAY_FULL;
|
||||
|
||||
const DISPLAY_FULL = 'full';
|
||||
const DISPLAY_SHORT = 'short';
|
||||
|
||||
public function setURI(PhutilURI $uri) {
|
||||
$this->uri = $uri;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
return $this->uri;
|
||||
}
|
||||
|
||||
public function setApplicationType($application_type) {
|
||||
$this->applicationType = $application_type;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getApplicationType() {
|
||||
return $this->applicationType;
|
||||
}
|
||||
|
||||
public function setApplicationDomain($application_domain) {
|
||||
$this->applicationDomain = $application_domain;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getApplicationDomain() {
|
||||
return $this->applicationDomain;
|
||||
}
|
||||
|
||||
public function setObjectType($object_type) {
|
||||
$this->objectType = $object_type;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getObjectType() {
|
||||
return $this->objectType;
|
||||
}
|
||||
|
||||
public function setObjectID($object_id) {
|
||||
$this->objectID = $object_id;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getObjectID() {
|
||||
return $this->objectID;
|
||||
}
|
||||
|
||||
public function setText($text) {
|
||||
$this->text = $text;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getText() {
|
||||
return $this->text;
|
||||
}
|
||||
|
||||
public function setDisplayMode($display_mode) {
|
||||
$options = array(
|
||||
self::DISPLAY_FULL => true,
|
||||
self::DISPLAY_SHORT => true,
|
||||
);
|
||||
|
||||
if (!isset($options[$display_mode])) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'DoorkeeperURIRef display mode "%s" is unknown.',
|
||||
$display_mode));
|
||||
}
|
||||
|
||||
$this->displayMode = $display_mode;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDisplayMode() {
|
||||
return $this->displayMode;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
final class DoorkeeperHyperlinkEngineExtension
|
||||
extends PhabricatorRemarkupHyperlinkEngineExtension {
|
||||
|
||||
const LINKENGINEKEY = 'doorkeeper';
|
||||
|
||||
public function processHyperlinks(array $hyperlinks) {
|
||||
$engine = $this->getEngine();
|
||||
$viewer = $engine->getConfig('viewer');
|
||||
|
||||
if (!$viewer) {
|
||||
return;
|
||||
}
|
||||
|
||||
$configs = id(new PhabricatorAuthProviderConfigQuery())
|
||||
->setViewer($viewer)
|
||||
->withIsEnabled(true)
|
||||
->execute();
|
||||
|
||||
$providers = array();
|
||||
foreach ($configs as $key => $config) {
|
||||
$provider = $config->getProvider();
|
||||
if (($provider instanceof DoorkeeperRemarkupURIInterface)) {
|
||||
$providers[] = $provider;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$providers) {
|
||||
return;
|
||||
}
|
||||
|
||||
$refs = array();
|
||||
foreach ($hyperlinks as $hyperlink) {
|
||||
$uri = $hyperlink->getURI();
|
||||
$uri = new PhutilURI($uri);
|
||||
|
||||
foreach ($providers as $provider) {
|
||||
$ref = $provider->getDoorkeeperURIRef($uri);
|
||||
|
||||
if (($ref !== null) && !($ref instanceof DoorkeeperURIRef)) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Expected "getDoorkeeperURIRef()" to return "null" or an '.
|
||||
'object of type "DoorkeeperURIRef", but got %s from provider '.
|
||||
'"%s".',
|
||||
phutil_describe_type($ref),
|
||||
get_class($provider)));
|
||||
}
|
||||
|
||||
if ($ref === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tag_id = celerity_generate_unique_node_id();
|
||||
$href = phutil_string_cast($ref->getURI());
|
||||
|
||||
$refs[] = array(
|
||||
'id' => $tag_id,
|
||||
'href' => $href,
|
||||
'ref' => array(
|
||||
$ref->getApplicationType(),
|
||||
$ref->getApplicationDomain(),
|
||||
$ref->getObjectType(),
|
||||
$ref->getObjectID(),
|
||||
),
|
||||
'view' => $ref->getDisplayMode(),
|
||||
);
|
||||
|
||||
$text = $ref->getText();
|
||||
if ($text === null) {
|
||||
$text = $href;
|
||||
}
|
||||
|
||||
$view = id(new PHUITagView())
|
||||
->setID($tag_id)
|
||||
->setName($text)
|
||||
->setHref($href)
|
||||
->setType(PHUITagView::TYPE_OBJECT)
|
||||
->setExternal(true);
|
||||
|
||||
$hyperlink->setResult($view);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($refs) {
|
||||
Javelin::initBehavior('doorkeeper-tag', array('tags' => $refs));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
interface DoorkeeperRemarkupURIInterface {
|
||||
|
||||
public function getDoorkeeperURIRef(PhutilURI $uri);
|
||||
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
<?php
|
||||
|
||||
final class DoorkeeperAsanaRemarkupRule
|
||||
extends DoorkeeperRemarkupRule {
|
||||
|
||||
public function apply($text) {
|
||||
return preg_replace_callback(
|
||||
'@https://app\\.asana\\.com/0/(\\d+)/(\\d+)@',
|
||||
array($this, 'markupAsanaLink'),
|
||||
$text);
|
||||
}
|
||||
|
||||
public function markupAsanaLink($matches) {
|
||||
return $this->addDoorkeeperTag(
|
||||
array(
|
||||
'href' => $matches[0],
|
||||
'tag' => array(
|
||||
'ref' => array(
|
||||
DoorkeeperBridgeAsana::APPTYPE_ASANA,
|
||||
DoorkeeperBridgeAsana::APPDOMAIN_ASANA,
|
||||
DoorkeeperBridgeAsana::OBJTYPE_TASK,
|
||||
$matches[2],
|
||||
),
|
||||
'extra' => array(
|
||||
'asana.context' => $matches[1],
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
<?php
|
||||
|
||||
final class DoorkeeperJIRARemarkupRule
|
||||
extends DoorkeeperRemarkupRule {
|
||||
|
||||
public function apply($text) {
|
||||
return preg_replace_callback(
|
||||
'@(https?://\S+?)/browse/([A-Z]+-[1-9]\d*)@',
|
||||
array($this, 'markupJIRALink'),
|
||||
$text);
|
||||
}
|
||||
|
||||
public function markupJIRALink($matches) {
|
||||
$match_domain = $matches[1];
|
||||
$match_issue = $matches[2];
|
||||
|
||||
// TODO: When we support multiple instances, deal with them here.
|
||||
$provider = PhabricatorJIRAAuthProvider::getJIRAProvider();
|
||||
if (!$provider) {
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
|
||||
$jira_base = $provider->getJIRABaseURI();
|
||||
if ($match_domain != rtrim($jira_base, '/')) {
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
return $this->addDoorkeeperTag(
|
||||
array(
|
||||
'href' => $matches[0],
|
||||
'tag' => array(
|
||||
'ref' => array(
|
||||
DoorkeeperBridgeJIRA::APPTYPE_JIRA,
|
||||
$provider->getProviderDomain(),
|
||||
DoorkeeperBridgeJIRA::OBJTYPE_ISSUE,
|
||||
$match_issue,
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,103 +0,0 @@
|
|||
<?php
|
||||
|
||||
abstract class DoorkeeperRemarkupRule extends PhutilRemarkupRule {
|
||||
|
||||
const KEY_TAGS = 'doorkeeper.tags';
|
||||
|
||||
const VIEW_FULL = 'full';
|
||||
const VIEW_SHORT = 'short';
|
||||
|
||||
public function getPriority() {
|
||||
return 350.0;
|
||||
}
|
||||
|
||||
protected function addDoorkeeperTag(array $spec) {
|
||||
PhutilTypeSpec::checkMap(
|
||||
$spec,
|
||||
array(
|
||||
'href' => 'string',
|
||||
'tag' => 'map<string, wild>',
|
||||
|
||||
'name' => 'optional string',
|
||||
'view' => 'optional string',
|
||||
));
|
||||
|
||||
$spec = $spec + array(
|
||||
'view' => self::VIEW_FULL,
|
||||
);
|
||||
|
||||
$views = array(
|
||||
self::VIEW_FULL,
|
||||
self::VIEW_SHORT,
|
||||
);
|
||||
$views = array_fuse($views);
|
||||
if (!isset($views[$spec['view']])) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Unsupported Doorkeeper tag view mode "%s". Supported modes are: %s.',
|
||||
$spec['view'],
|
||||
implode(', ', $views)));
|
||||
}
|
||||
|
||||
$key = self::KEY_TAGS;
|
||||
$engine = $this->getEngine();
|
||||
$token = $engine->storeText(get_class($this));
|
||||
|
||||
$tags = $engine->getTextMetadata($key, array());
|
||||
|
||||
$tags[] = array(
|
||||
'token' => $token,
|
||||
) + $spec + array(
|
||||
'extra' => array(),
|
||||
);
|
||||
|
||||
$engine->setTextMetadata($key, $tags);
|
||||
return $token;
|
||||
}
|
||||
|
||||
public function didMarkupText() {
|
||||
$key = self::KEY_TAGS;
|
||||
$engine = $this->getEngine();
|
||||
$tags = $engine->getTextMetadata($key, array());
|
||||
|
||||
if (!$tags) {
|
||||
return;
|
||||
}
|
||||
|
||||
$refs = array();
|
||||
foreach ($tags as $spec) {
|
||||
$href = $spec['href'];
|
||||
$name = idx($spec, 'name', $href);
|
||||
|
||||
$this->assertFlatText($href);
|
||||
$this->assertFlatText($name);
|
||||
|
||||
if ($this->getEngine()->isTextMode()) {
|
||||
$view = "{$name} <{$href}>";
|
||||
} else {
|
||||
$tag_id = celerity_generate_unique_node_id();
|
||||
|
||||
$refs[] = array(
|
||||
'id' => $tag_id,
|
||||
'view' => $spec['view'],
|
||||
) + $spec['tag'];
|
||||
|
||||
$view = id(new PHUITagView())
|
||||
->setID($tag_id)
|
||||
->setName($name)
|
||||
->setHref($href)
|
||||
->setType(PHUITagView::TYPE_OBJECT)
|
||||
->setExternal(true);
|
||||
}
|
||||
|
||||
$engine->overwriteStoredText($spec['token'], $view);
|
||||
}
|
||||
|
||||
if ($refs) {
|
||||
Javelin::initBehavior('doorkeeper-tag', array('tags' => $refs));
|
||||
}
|
||||
|
||||
$engine->setTextMetadata($key, array());
|
||||
}
|
||||
|
||||
}
|
|
@ -151,13 +151,15 @@ final class DrydockManagementLeaseWorkflow
|
|||
while (!$is_active) {
|
||||
$lease->reload();
|
||||
|
||||
$pager = id(new AphrontCursorPagerView())
|
||||
->setBeforeID($log_cursor);
|
||||
|
||||
// While we're waiting, show the user any logs which the daemons have
|
||||
// generated to give them some clue about what's going on.
|
||||
$logs = id(new DrydockLogQuery())
|
||||
->setViewer($viewer)
|
||||
->withLeasePHIDs(array($lease->getPHID()))
|
||||
->setBeforeID($log_cursor)
|
||||
->execute();
|
||||
->executeWithCursorPager($pager);
|
||||
if ($logs) {
|
||||
$logs = mpull($logs, null, 'getID');
|
||||
ksort($logs);
|
||||
|
|
|
@ -2,11 +2,17 @@
|
|||
|
||||
final class DrydockLogQuery extends DrydockQuery {
|
||||
|
||||
private $ids;
|
||||
private $blueprintPHIDs;
|
||||
private $resourcePHIDs;
|
||||
private $leasePHIDs;
|
||||
private $operationPHIDs;
|
||||
|
||||
public function withIDs(array $ids) {
|
||||
$this->ids = $ids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withBlueprintPHIDs(array $phids) {
|
||||
$this->blueprintPHIDs = $phids;
|
||||
return $this;
|
||||
|
@ -126,6 +132,13 @@ final class DrydockLogQuery extends DrydockQuery {
|
|||
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
|
||||
$where = parent::buildWhereClauseParts($conn);
|
||||
|
||||
if ($this->ids !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'id IN (%Ls)',
|
||||
$this->ids);
|
||||
}
|
||||
|
||||
if ($this->blueprintPHIDs !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
|
|
|
@ -30,7 +30,9 @@ final class PhabricatorFactApplication extends PhabricatorApplication {
|
|||
return array(
|
||||
'/fact/' => array(
|
||||
'' => 'PhabricatorFactHomeController',
|
||||
'(?<mode>chart|draw)/' => 'PhabricatorFactChartController',
|
||||
'chart/' => 'PhabricatorFactChartController',
|
||||
'chart/(?P<chartKey>[^/]+)/(?:(?P<mode>draw)/)?' =>
|
||||
'PhabricatorFactChartController',
|
||||
'object/(?<phid>[^/]+)/' => 'PhabricatorFactObjectController',
|
||||
),
|
||||
);
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorAccumulateChartFunction
|
||||
extends PhabricatorChartFunction {
|
||||
|
||||
const FUNCTIONKEY = 'accumulate';
|
||||
|
||||
protected function newArguments() {
|
||||
return array(
|
||||
$this->newArgument()
|
||||
->setName('x')
|
||||
->setType('function'),
|
||||
);
|
||||
}
|
||||
|
||||
public function getDomain() {
|
||||
return $this->getArgument('x')->getDomain();
|
||||
}
|
||||
|
||||
public function newInputValues(PhabricatorChartDataQuery $query) {
|
||||
return $this->getArgument('x')->newInputValues($query);
|
||||
}
|
||||
|
||||
public function evaluateFunction(array $xv) {
|
||||
// First, we're going to accumulate the underlying function. Then
|
||||
// we'll map the inputs through the accumulation.
|
||||
|
||||
$datasource = $this->getArgument('x');
|
||||
|
||||
// Use an unconstrained query to pull all the data from the underlying
|
||||
// source. We need to accumulate data since the beginning of time to
|
||||
// figure out the right Y-intercept -- otherwise, we'll always start at
|
||||
// "0" wherever our domain begins.
|
||||
$empty_query = new PhabricatorChartDataQuery();
|
||||
|
||||
$datasource_xv = $datasource->newInputValues($empty_query);
|
||||
if (!$datasource_xv) {
|
||||
// When the datasource has no datapoints, we can't evaluate the function
|
||||
// anywhere.
|
||||
return array_fill(0, count($xv), null);
|
||||
}
|
||||
|
||||
$yv = $datasource->evaluateFunction($datasource_xv);
|
||||
|
||||
$map = array_combine($datasource_xv, $yv);
|
||||
|
||||
$accumulator = 0;
|
||||
foreach ($map as $x => $y) {
|
||||
$accumulator += $y;
|
||||
$map[$x] = $accumulator;
|
||||
}
|
||||
|
||||
// The value of "accumulate(x)" is the largest datapoint in the map which
|
||||
// is no larger than "x".
|
||||
|
||||
$map_x = array_keys($map);
|
||||
$idx = -1;
|
||||
$max = count($map_x) - 1;
|
||||
|
||||
$yv = array();
|
||||
|
||||
$value = 0;
|
||||
foreach ($xv as $x) {
|
||||
// While the next "x" we need to evaluate the function at lies to the
|
||||
// right of the next datapoint, move the current datapoint forward until
|
||||
// we're at the rightmost datapoint which is not larger than "x".
|
||||
while ($idx < $max) {
|
||||
if ($map_x[$idx + 1] > $x) {
|
||||
break;
|
||||
}
|
||||
|
||||
$idx++;
|
||||
$value = $map[$map_x[$idx]];
|
||||
}
|
||||
|
||||
$yv[] = $value;
|
||||
}
|
||||
|
||||
return $yv;
|
||||
}
|
||||
|
||||
}
|
|
@ -34,4 +34,48 @@ final class PhabricatorChartDataQuery
|
|||
return $this->limit;
|
||||
}
|
||||
|
||||
public function selectInputValues(array $xv) {
|
||||
$result = array();
|
||||
|
||||
$x_min = $this->getMinimumValue();
|
||||
$x_max = $this->getMaximumValue();
|
||||
$limit = $this->getLimit();
|
||||
|
||||
if ($x_min !== null) {
|
||||
foreach ($xv as $key => $x) {
|
||||
if ($x < $x_min) {
|
||||
unset($xv[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($x_max !== null) {
|
||||
foreach ($xv as $key => $x) {
|
||||
if ($x > $x_max) {
|
||||
unset($xv[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we have too many data points, throw away some of the data.
|
||||
|
||||
// TODO: This doesn't work especially well right now.
|
||||
|
||||
if ($limit !== null) {
|
||||
$count = count($xv);
|
||||
if ($count > $limit) {
|
||||
$ii = 0;
|
||||
$every = ceil($count / $limit);
|
||||
foreach ($xv as $key => $x) {
|
||||
$ii++;
|
||||
if (($ii % $every) && ($ii != $count)) {
|
||||
unset($xv[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($xv);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
78
src/applications/fact/chart/PhabricatorChartDataset.php
Normal file
78
src/applications/fact/chart/PhabricatorChartDataset.php
Normal file
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
|
||||
abstract class PhabricatorChartDataset
|
||||
extends Phobject {
|
||||
|
||||
private $functions;
|
||||
|
||||
final public function getDatasetTypeKey() {
|
||||
return $this->getPhobjectClassConstant('DATASETKEY', 32);
|
||||
}
|
||||
|
||||
final public function getFunctions() {
|
||||
return $this->functions;
|
||||
}
|
||||
|
||||
final public function setFunctions(array $functions) {
|
||||
assert_instances_of($functions, 'PhabricatorComposeChartFunction');
|
||||
|
||||
$this->functions = $functions;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
final public static function getAllDatasetTypes() {
|
||||
return id(new PhutilClassMapQuery())
|
||||
->setAncestorClass(__CLASS__)
|
||||
->setUniqueMethod('getDatasetTypeKey')
|
||||
->execute();
|
||||
}
|
||||
|
||||
final public static function newFromDictionary(array $map) {
|
||||
PhutilTypeSpec::checkMap(
|
||||
$map,
|
||||
array(
|
||||
'type' => 'string',
|
||||
'functions' => 'list<wild>',
|
||||
));
|
||||
|
||||
$types = self::getAllDatasetTypes();
|
||||
|
||||
$dataset_type = $map['type'];
|
||||
if (!isset($types[$dataset_type])) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Trying to construct a dataset of type "%s", but this type is '.
|
||||
'unknown. Supported types are: %s.',
|
||||
$dataset_type,
|
||||
implode(', ', array_keys($types))));
|
||||
}
|
||||
|
||||
$dataset = id(clone $types[$dataset_type]);
|
||||
|
||||
$functions = array();
|
||||
foreach ($map['functions'] as $map) {
|
||||
$functions[] = PhabricatorChartFunction::newFromDictionary($map);
|
||||
}
|
||||
$dataset->setFunctions($functions);
|
||||
|
||||
return $dataset;
|
||||
}
|
||||
|
||||
final public function toDictionary() {
|
||||
return array(
|
||||
'type' => $this->getDatasetTypeKey(),
|
||||
'functions' => mpull($this->getFunctions(), 'toDictionary'),
|
||||
);
|
||||
}
|
||||
|
||||
final public function getChartDisplayData(
|
||||
PhabricatorChartDataQuery $data_query) {
|
||||
return $this->newChartDisplayData($data_query);
|
||||
}
|
||||
|
||||
abstract protected function newChartDisplayData(
|
||||
PhabricatorChartDataQuery $data_query);
|
||||
|
||||
|
||||
}
|
27
src/applications/fact/chart/PhabricatorChartDisplayData.php
Normal file
27
src/applications/fact/chart/PhabricatorChartDisplayData.php
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorChartDisplayData
|
||||
extends Phobject {
|
||||
|
||||
private $wireData;
|
||||
private $range;
|
||||
|
||||
public function setWireData(array $wire_data) {
|
||||
$this->wireData = $wire_data;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getWireData() {
|
||||
return $this->wireData;
|
||||
}
|
||||
|
||||
public function setRange(PhabricatorChartInterval $range) {
|
||||
$this->range = $range;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRange() {
|
||||
return $this->range;
|
||||
}
|
||||
|
||||
}
|
|
@ -3,11 +3,8 @@
|
|||
abstract class PhabricatorChartFunction
|
||||
extends Phobject {
|
||||
|
||||
private $xAxis;
|
||||
private $yAxis;
|
||||
|
||||
private $argumentParser;
|
||||
private $sourceFunction;
|
||||
private $functionLabel;
|
||||
|
||||
final public function getFunctionKey() {
|
||||
return $this->getPhobjectClassConstant('FUNCTIONKEY', 32);
|
||||
|
@ -44,13 +41,122 @@ abstract class PhabricatorChartFunction
|
|||
$parser->setHaveAllArguments(true);
|
||||
$parser->parseArguments();
|
||||
|
||||
$source_argument = $parser->getSourceFunctionArgument();
|
||||
if ($source_argument) {
|
||||
$source_function = $this->getArgument($source_argument->getName());
|
||||
$this->setSourceFunction($source_function);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setFunctionLabel(PhabricatorChartFunctionLabel $label) {
|
||||
$this->functionLabel = $label;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFunctionLabel() {
|
||||
if (!$this->functionLabel) {
|
||||
$this->functionLabel = id(new PhabricatorChartFunctionLabel())
|
||||
->setName(pht('Unlabeled Function'))
|
||||
->setColor('rgba(255, 0, 0, 1)')
|
||||
->setFillColor('rgba(255, 0, 0, 0.15)');
|
||||
}
|
||||
|
||||
return $this;
|
||||
return $this->functionLabel;
|
||||
}
|
||||
|
||||
final public static function newFromDictionary(array $map) {
|
||||
PhutilTypeSpec::checkMap(
|
||||
$map,
|
||||
array(
|
||||
'function' => 'string',
|
||||
'arguments' => 'list<wild>',
|
||||
));
|
||||
|
||||
$functions = self::getAllFunctions();
|
||||
|
||||
$function_name = $map['function'];
|
||||
if (!isset($functions[$function_name])) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Attempting to build function "%s" from dictionary, but that '.
|
||||
'function is unknown. Known functions are: %s.',
|
||||
$function_name,
|
||||
implode(', ', array_keys($functions))));
|
||||
}
|
||||
|
||||
$function = id(clone $functions[$function_name])
|
||||
->setArguments($map['arguments']);
|
||||
|
||||
return $function;
|
||||
}
|
||||
|
||||
public function toDictionary() {
|
||||
return array(
|
||||
'function' => $this->getFunctionKey(),
|
||||
'arguments' => $this->getArgumentParser()->getRawArguments(),
|
||||
);
|
||||
}
|
||||
|
||||
public function getSubfunctions() {
|
||||
$result = array();
|
||||
$result[] = $this;
|
||||
|
||||
foreach ($this->getFunctionArguments() as $argument) {
|
||||
foreach ($argument->getSubfunctions() as $subfunction) {
|
||||
$result[] = $subfunction;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getFunctionArguments() {
|
||||
$results = array();
|
||||
|
||||
$parser = $this->getArgumentParser();
|
||||
foreach ($parser->getAllArguments() as $argument) {
|
||||
if ($argument->getType() !== 'function') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = $argument->getName();
|
||||
$value = $this->getArgument($name);
|
||||
|
||||
if (!is_array($value)) {
|
||||
$results[] = $value;
|
||||
} else {
|
||||
foreach ($value as $arg_value) {
|
||||
$results[] = $arg_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function newDatapoints(PhabricatorChartDataQuery $query) {
|
||||
$xv = $this->newInputValues($query);
|
||||
|
||||
if ($xv === null) {
|
||||
$xv = $this->newDefaultInputValues($query);
|
||||
}
|
||||
|
||||
$xv = $query->selectInputValues($xv);
|
||||
|
||||
$n = count($xv);
|
||||
$yv = $this->evaluateFunction($xv);
|
||||
|
||||
$points = array();
|
||||
for ($ii = 0; $ii < $n; $ii++) {
|
||||
$y = $yv[$ii];
|
||||
|
||||
if ($y === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$points[] = array(
|
||||
'x' => $xv[$ii],
|
||||
'y' => $y,
|
||||
);
|
||||
}
|
||||
|
||||
return $points;
|
||||
}
|
||||
|
||||
abstract protected function newArguments();
|
||||
|
@ -73,96 +179,26 @@ abstract class PhabricatorChartFunction
|
|||
return $this->argumentParser;
|
||||
}
|
||||
|
||||
abstract public function evaluateFunction(array $xv);
|
||||
|
||||
public function getDomain() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public function newInputValues(PhabricatorChartDataQuery $query) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public function loadData() {
|
||||
return;
|
||||
}
|
||||
|
||||
protected function setSourceFunction(PhabricatorChartFunction $source) {
|
||||
$this->sourceFunction = $source;
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function getSourceFunction() {
|
||||
return $this->sourceFunction;
|
||||
}
|
||||
|
||||
final public function setXAxis(PhabricatorChartAxis $x_axis) {
|
||||
$this->xAxis = $x_axis;
|
||||
return $this;
|
||||
}
|
||||
|
||||
final public function getXAxis() {
|
||||
return $this->xAxis;
|
||||
}
|
||||
|
||||
final public function setYAxis(PhabricatorChartAxis $y_axis) {
|
||||
$this->yAxis = $y_axis;
|
||||
return $this;
|
||||
}
|
||||
|
||||
final public function getYAxis() {
|
||||
return $this->yAxis;
|
||||
}
|
||||
|
||||
protected function canEvaluateFunction() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function evaluateFunction($x) {
|
||||
throw new PhutilMethodNotImplementedException();
|
||||
}
|
||||
|
||||
public function hasDomain() {
|
||||
if ($this->canEvaluateFunction()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
throw new PhutilMethodNotImplementedException();
|
||||
}
|
||||
|
||||
public function getDatapoints(PhabricatorChartDataQuery $query) {
|
||||
if ($this->canEvaluateFunction()) {
|
||||
$points = $this->newSourceDatapoints($query);
|
||||
foreach ($points as $key => $point) {
|
||||
$y = $point['y'];
|
||||
$y = $this->evaluateFunction($y);
|
||||
$points[$key]['y'] = $y;
|
||||
}
|
||||
|
||||
return $points;
|
||||
}
|
||||
|
||||
return $this->newDatapoints($query);
|
||||
}
|
||||
|
||||
protected function newDatapoints(PhabricatorChartDataQuery $query) {
|
||||
throw new PhutilMethodNotImplementedException();
|
||||
}
|
||||
|
||||
protected function newSourceDatapoints(PhabricatorChartDataQuery $query) {
|
||||
$source = $this->getSourceFunction();
|
||||
if ($source) {
|
||||
return $source->getDatapoints($query);
|
||||
}
|
||||
|
||||
return $this->newDefaultDatapoints($query);
|
||||
}
|
||||
|
||||
protected function newDefaultDatapoints(PhabricatorChartDataQuery $query) {
|
||||
protected function newDefaultInputValues(PhabricatorChartDataQuery $query) {
|
||||
$x_min = $query->getMinimumValue();
|
||||
$x_max = $query->getMaximumValue();
|
||||
$limit = $query->getLimit();
|
||||
|
||||
$points = array();
|
||||
$steps = $this->newLinearSteps($x_min, $x_max, $limit);
|
||||
foreach ($steps as $step) {
|
||||
$points[] = array(
|
||||
'x' => $step,
|
||||
'y' => $step,
|
||||
);
|
||||
}
|
||||
|
||||
return $points;
|
||||
return $this->newLinearSteps($x_min, $x_max, $limit);
|
||||
}
|
||||
|
||||
protected function newLinearSteps($src, $dst, $count) {
|
||||
|
@ -213,5 +249,4 @@ abstract class PhabricatorChartFunction
|
|||
|
||||
return $steps;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ final class PhabricatorChartFunctionArgument
|
|||
|
||||
private $name;
|
||||
private $type;
|
||||
private $isSourceFunction;
|
||||
private $repeatable;
|
||||
|
||||
public function setName($name) {
|
||||
$this->name = $name;
|
||||
|
@ -16,11 +16,21 @@ final class PhabricatorChartFunctionArgument
|
|||
return $this->name;
|
||||
}
|
||||
|
||||
public function setRepeatable($repeatable) {
|
||||
$this->repeatable = $repeatable;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRepeatable() {
|
||||
return $this->repeatable;
|
||||
}
|
||||
|
||||
public function setType($type) {
|
||||
$types = array(
|
||||
'fact-key' => true,
|
||||
'function' => true,
|
||||
'number' => true,
|
||||
'phid' => true,
|
||||
);
|
||||
|
||||
if (!isset($types[$type])) {
|
||||
|
@ -40,17 +50,12 @@ final class PhabricatorChartFunctionArgument
|
|||
return $this->type;
|
||||
}
|
||||
|
||||
public function setIsSourceFunction($is_source_function) {
|
||||
$this->isSourceFunction = $is_source_function;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getIsSourceFunction() {
|
||||
return $this->isSourceFunction;
|
||||
}
|
||||
|
||||
public function newValue($value) {
|
||||
switch ($this->getType()) {
|
||||
case 'phid':
|
||||
// TODO: This could be validated better, but probably should not be
|
||||
// a primitive type.
|
||||
return $value;
|
||||
case 'fact-key':
|
||||
if (!is_string($value)) {
|
||||
throw new Exception(
|
||||
|
|
|
@ -11,6 +11,7 @@ final class PhabricatorChartFunctionArgumentParser
|
|||
private $argumentMap = array();
|
||||
private $argumentPosition = 0;
|
||||
private $argumentValues = array();
|
||||
private $repeatableArgument = null;
|
||||
|
||||
public function setFunction(PhabricatorChartFunction $function) {
|
||||
$this->function = $function;
|
||||
|
@ -55,6 +56,32 @@ final class PhabricatorChartFunctionArgumentParser
|
|||
$name));
|
||||
}
|
||||
|
||||
if ($this->repeatableArgument) {
|
||||
if ($spec->getRepeatable()) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Chart function "%s" emitted multiple repeatable argument '.
|
||||
'specifications ("%s" and "%s"). Only one argument may be '.
|
||||
'repeatable and it must be the last argument.',
|
||||
$this->getFunctionArgumentSignature(),
|
||||
$name,
|
||||
$this->repeatableArgument->getName()));
|
||||
} else {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Chart function "%s" emitted a repeatable argument ("%s"), then '.
|
||||
'another argument ("%s"). No arguments are permitted after a '.
|
||||
'repeatable argument.',
|
||||
$this->getFunctionArgumentSignature(),
|
||||
$this->repeatableArgument->getName(),
|
||||
$name));
|
||||
}
|
||||
}
|
||||
|
||||
if ($spec->getRepeatable()) {
|
||||
$this->repeatableArgument = $spec;
|
||||
}
|
||||
|
||||
$this->argumentMap[$name] = $spec;
|
||||
$this->unparsedArguments[] = $spec;
|
||||
|
||||
|
@ -72,12 +99,30 @@ final class PhabricatorChartFunctionArgumentParser
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function getAllArguments() {
|
||||
return array_values($this->argumentMap);
|
||||
}
|
||||
|
||||
public function getRawArguments() {
|
||||
return $this->rawArguments;
|
||||
}
|
||||
|
||||
public function parseArguments() {
|
||||
$have_count = count($this->rawArguments);
|
||||
$want_count = count($this->argumentMap);
|
||||
|
||||
if ($this->haveAllArguments) {
|
||||
if ($want_count !== $have_count) {
|
||||
if ($this->repeatableArgument) {
|
||||
if ($want_count > $have_count) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Function "%s" expects %s or more argument(s), but only %s '.
|
||||
'argument(s) were provided.',
|
||||
$this->getFunctionArgumentSignature(),
|
||||
$want_count,
|
||||
$have_count));
|
||||
}
|
||||
} else if ($want_count !== $have_count) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Function "%s" expects %s argument(s), but %s argument(s) were '.
|
||||
|
@ -105,6 +150,14 @@ final class PhabricatorChartFunctionArgumentParser
|
|||
$raw_argument = array_shift($this->unconsumedArguments);
|
||||
$this->argumentPosition++;
|
||||
|
||||
$is_repeatable = $argument->getRepeatable();
|
||||
|
||||
// If this argument is repeatable and we have more arguments, add it
|
||||
// back to the end of the list so we can continue parsing.
|
||||
if ($is_repeatable && $this->unconsumedArguments) {
|
||||
$this->unparsedArguments[] = $argument;
|
||||
}
|
||||
|
||||
try {
|
||||
$value = $argument->newValue($raw_argument);
|
||||
} catch (Exception $ex) {
|
||||
|
@ -118,7 +171,14 @@ final class PhabricatorChartFunctionArgumentParser
|
|||
$ex->getMessage()));
|
||||
}
|
||||
|
||||
$this->argumentValues[$name] = $value;
|
||||
if ($is_repeatable) {
|
||||
if (!isset($this->argumentValues[$name])) {
|
||||
$this->argumentValues[$name] = array();
|
||||
}
|
||||
$this->argumentValues[$name][] = $value;
|
||||
} else {
|
||||
$this->argumentValues[$name] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,7 +201,7 @@ final class PhabricatorChartFunctionArgumentParser
|
|||
$argument_list[] = $key;
|
||||
}
|
||||
|
||||
if (!$this->haveAllArguments) {
|
||||
if (!$this->haveAllArguments || $this->repeatableArgument) {
|
||||
$argument_list[] = '...';
|
||||
}
|
||||
|
||||
|
@ -151,43 +211,4 @@ final class PhabricatorChartFunctionArgumentParser
|
|||
implode(', ', $argument_list));
|
||||
}
|
||||
|
||||
public function getSourceFunctionArgument() {
|
||||
$required_type = 'function';
|
||||
|
||||
$sources = array();
|
||||
foreach ($this->argumentMap as $key => $argument) {
|
||||
if (!$argument->getIsSourceFunction()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($argument->getType() !== $required_type) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Function "%s" defines an argument "%s" which is marked as a '.
|
||||
'source function, but the type of this argument is not "%s".',
|
||||
$this->getFunctionArgumentSignature(),
|
||||
$argument->getName(),
|
||||
$required_type));
|
||||
}
|
||||
|
||||
$sources[$key] = $argument;
|
||||
}
|
||||
|
||||
if (!$sources) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (count($sources) > 1) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Function "%s" defines more than one argument as a source '.
|
||||
'function (arguments: %s). Functions must have zero or one '.
|
||||
'source function.',
|
||||
$this->getFunctionArgumentSignature(),
|
||||
implode(', ', array_keys($sources))));
|
||||
}
|
||||
|
||||
return head($sources);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorChartFunctionLabel
|
||||
extends Phobject {
|
||||
|
||||
private $name;
|
||||
private $color;
|
||||
private $icon;
|
||||
private $fillColor;
|
||||
|
||||
public function setName($name) {
|
||||
$this->name = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setColor($color) {
|
||||
$this->color = $color;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getColor() {
|
||||
return $this->color;
|
||||
}
|
||||
|
||||
public function setIcon($icon) {
|
||||
$this->icon = $icon;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return $this->icon;
|
||||
}
|
||||
|
||||
public function setFillColor($fill_color) {
|
||||
$this->fillColor = $fill_color;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFillColor() {
|
||||
return $this->fillColor;
|
||||
}
|
||||
|
||||
public function toWireFormat() {
|
||||
return array(
|
||||
'name' => $this->getName(),
|
||||
'color' => $this->getColor(),
|
||||
'icon' => $this->getIcon(),
|
||||
'fillColor' => $this->getFillColor(),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
62
src/applications/fact/chart/PhabricatorChartInterval.php
Normal file
62
src/applications/fact/chart/PhabricatorChartInterval.php
Normal file
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorChartInterval
|
||||
extends Phobject {
|
||||
|
||||
private $min;
|
||||
private $max;
|
||||
|
||||
public function __construct($min, $max) {
|
||||
$this->min = $min;
|
||||
$this->max = $max;
|
||||
}
|
||||
|
||||
public static function newFromIntervalList(array $intervals) {
|
||||
$min = null;
|
||||
$max = null;
|
||||
foreach ($intervals as $interval) {
|
||||
if ($interval === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$interval_min = $interval->getMin();
|
||||
if ($interval_min !== null) {
|
||||
if ($min === null) {
|
||||
$min = $interval_min;
|
||||
} else {
|
||||
$min = min($min, $interval_min);
|
||||
}
|
||||
}
|
||||
|
||||
$interval_max = $interval->getMax();
|
||||
if ($interval_max !== null) {
|
||||
if ($max === null) {
|
||||
$max = $interval_max;
|
||||
} else {
|
||||
$max = max($max, $interval_max);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new self($min, $max);
|
||||
}
|
||||
|
||||
public function setMin($min) {
|
||||
$this->min = $min;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getMin() {
|
||||
return $this->min;
|
||||
}
|
||||
|
||||
public function setMax($max) {
|
||||
$this->max = $max;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getMax() {
|
||||
return $this->max;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorChartStackedAreaDataset
|
||||
extends PhabricatorChartDataset {
|
||||
|
||||
const DATASETKEY = 'stacked-area';
|
||||
|
||||
protected function newChartDisplayData(
|
||||
PhabricatorChartDataQuery $data_query) {
|
||||
$functions = $this->getFunctions();
|
||||
|
||||
$reversed_functions = array_reverse($functions, true);
|
||||
|
||||
$function_points = array();
|
||||
foreach ($reversed_functions as $function_idx => $function) {
|
||||
$function_points[$function_idx] = array();
|
||||
|
||||
$datapoints = $function->newDatapoints($data_query);
|
||||
foreach ($datapoints as $point) {
|
||||
$x = $point['x'];
|
||||
$function_points[$function_idx][$x] = $point;
|
||||
}
|
||||
}
|
||||
|
||||
$raw_points = $function_points;
|
||||
|
||||
// We need to define every function we're drawing at every point where
|
||||
// any of the functions we're drawing are defined. If we don't, we'll
|
||||
// end up with weird gaps or overlaps between adjacent areas, and won't
|
||||
// know how much we need to lift each point above the baseline when
|
||||
// stacking the functions on top of one another.
|
||||
|
||||
$must_define = array();
|
||||
foreach ($function_points as $function_idx => $points) {
|
||||
foreach ($points as $x => $point) {
|
||||
$must_define[$x] = $x;
|
||||
}
|
||||
}
|
||||
ksort($must_define);
|
||||
|
||||
foreach ($reversed_functions as $function_idx => $function) {
|
||||
$missing = array();
|
||||
foreach ($must_define as $x) {
|
||||
if (!isset($function_points[$function_idx][$x])) {
|
||||
$missing[$x] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$missing) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$points = $function_points[$function_idx];
|
||||
|
||||
$values = array_keys($points);
|
||||
$cursor = -1;
|
||||
$length = count($values);
|
||||
|
||||
foreach ($missing as $x => $ignored) {
|
||||
// Move the cursor forward until we find the last point before "x"
|
||||
// which is defined.
|
||||
while ($cursor + 1 < $length && $values[$cursor + 1] < $x) {
|
||||
$cursor++;
|
||||
}
|
||||
|
||||
// If this new point is to the left of all defined points, we'll
|
||||
// assume the value is 0. If the point is to the right of all defined
|
||||
// points, we assume the value is the same as the last known value.
|
||||
|
||||
// If it's between two defined points, we average them.
|
||||
|
||||
if ($cursor < 0) {
|
||||
$y = 0;
|
||||
} else if ($cursor + 1 < $length) {
|
||||
$xmin = $values[$cursor];
|
||||
$xmax = $values[$cursor + 1];
|
||||
|
||||
$ymin = $points[$xmin]['y'];
|
||||
$ymax = $points[$xmax]['y'];
|
||||
|
||||
// Fill in the missing point by creating a linear interpolation
|
||||
// between the two adjacent points.
|
||||
$distance = ($x - $xmin) / ($xmax - $xmin);
|
||||
$y = $ymin + (($ymax - $ymin) * $distance);
|
||||
} else {
|
||||
$xmin = $values[$cursor];
|
||||
$y = $function_points[$function_idx][$xmin]['y'];
|
||||
}
|
||||
|
||||
$function_points[$function_idx][$x] = array(
|
||||
'x' => $x,
|
||||
'y' => $y,
|
||||
);
|
||||
}
|
||||
|
||||
ksort($function_points[$function_idx]);
|
||||
}
|
||||
|
||||
$range_min = null;
|
||||
$range_max = null;
|
||||
|
||||
$series = array();
|
||||
$baseline = array();
|
||||
foreach ($function_points as $function_idx => $points) {
|
||||
$below = idx($function_points, $function_idx - 1);
|
||||
|
||||
$bounds = array();
|
||||
foreach ($points as $x => $point) {
|
||||
if (!isset($baseline[$x])) {
|
||||
$baseline[$x] = 0;
|
||||
}
|
||||
|
||||
$y0 = $baseline[$x];
|
||||
$baseline[$x] += $point['y'];
|
||||
$y1 = $baseline[$x];
|
||||
|
||||
$bounds[] = array(
|
||||
'x' => $x,
|
||||
'y0' => $y0,
|
||||
'y1' => $y1,
|
||||
);
|
||||
|
||||
if (isset($raw_points[$function_idx][$x])) {
|
||||
$raw_points[$function_idx][$x]['y1'] = $y1;
|
||||
}
|
||||
|
||||
if ($range_min === null) {
|
||||
$range_min = $y0;
|
||||
}
|
||||
$range_min = min($range_min, $y0, $y1);
|
||||
|
||||
if ($range_max === null) {
|
||||
$range_max = $y1;
|
||||
}
|
||||
$range_max = max($range_max, $y0, $y1);
|
||||
}
|
||||
|
||||
$series[] = $bounds;
|
||||
}
|
||||
|
||||
$series = array_reverse($series);
|
||||
|
||||
$events = array();
|
||||
foreach ($raw_points as $function_idx => $points) {
|
||||
$event_list = array();
|
||||
foreach ($points as $point) {
|
||||
$event_list[] = $point;
|
||||
}
|
||||
$events[] = $event_list;
|
||||
}
|
||||
|
||||
$wire_labels = array();
|
||||
foreach ($functions as $function_key => $function) {
|
||||
$label = $function->getFunctionLabel();
|
||||
$wire_labels[] = $label->toWireFormat();
|
||||
}
|
||||
|
||||
$result = array(
|
||||
'type' => $this->getDatasetTypeKey(),
|
||||
'data' => $series,
|
||||
'events' => $events,
|
||||
'labels' => $wire_labels,
|
||||
);
|
||||
|
||||
return id(new PhabricatorChartDisplayData())
|
||||
->setWireData($result)
|
||||
->setRange(new PhabricatorChartInterval($range_min, $range_max));
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorComposeChartFunction
|
||||
extends PhabricatorHigherOrderChartFunction {
|
||||
|
||||
const FUNCTIONKEY = 'compose';
|
||||
|
||||
protected function newArguments() {
|
||||
return array(
|
||||
$this->newArgument()
|
||||
->setName('f')
|
||||
->setType('function')
|
||||
->setRepeatable(true),
|
||||
);
|
||||
}
|
||||
|
||||
public function evaluateFunction(array $xv) {
|
||||
$original_positions = array_keys($xv);
|
||||
$remaining_positions = $original_positions;
|
||||
foreach ($this->getFunctionArguments() as $function) {
|
||||
$xv = $function->evaluateFunction($xv);
|
||||
|
||||
// If a function evaluates to "null" at some positions, we want to return
|
||||
// "null" at those positions and stop evaluating the function.
|
||||
|
||||
// We also want to pass "evaluateFunction()" a natural list containing
|
||||
// only values it should evaluate: keys should not be important and we
|
||||
// should not pass "null". This simplifies implementation of functions.
|
||||
|
||||
// To do this, first create a map from original input positions to
|
||||
// function return values.
|
||||
$xv = array_combine($remaining_positions, $xv);
|
||||
|
||||
// If a function evaluated to "null" at any position where we evaluated
|
||||
// it, the result will be "null". We remove the position from the
|
||||
// vector so we stop evaluating it.
|
||||
foreach ($xv as $x => $y) {
|
||||
if ($y !== null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
unset($xv[$x]);
|
||||
}
|
||||
|
||||
// Store the remaining original input positions for the next round, then
|
||||
// throw away the array keys so we're passing the next function a natural
|
||||
// list with only non-"null" values.
|
||||
$remaining_positions = array_keys($xv);
|
||||
$xv = array_values($xv);
|
||||
|
||||
// If we have no more inputs to evaluate, we can bail out early rather
|
||||
// than passing empty vectors to functions for evaluation.
|
||||
if (!$xv) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$yv = array();
|
||||
$xv = array_combine($remaining_positions, $xv);
|
||||
foreach ($original_positions as $position) {
|
||||
if (isset($xv[$position])) {
|
||||
$y = $xv[$position];
|
||||
} else {
|
||||
$y = null;
|
||||
}
|
||||
$yv[$position] = $y;
|
||||
}
|
||||
|
||||
return $yv;
|
||||
}
|
||||
|
||||
}
|
|
@ -13,12 +13,16 @@ final class PhabricatorConstantChartFunction
|
|||
);
|
||||
}
|
||||
|
||||
protected function canEvaluateFunction() {
|
||||
return true;
|
||||
}
|
||||
public function evaluateFunction(array $xv) {
|
||||
$n = $this->getArgument('n');
|
||||
|
||||
protected function evaluateFunction($x) {
|
||||
return $this->getArgument('n');
|
||||
$yv = array();
|
||||
|
||||
foreach ($xv as $x) {
|
||||
$yv[] = $n;
|
||||
}
|
||||
|
||||
return $yv;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -6,20 +6,17 @@ final class PhabricatorCosChartFunction
|
|||
const FUNCTIONKEY = 'cos';
|
||||
|
||||
protected function newArguments() {
|
||||
return array(
|
||||
$this->newArgument()
|
||||
->setName('x')
|
||||
->setType('function')
|
||||
->setIsSourceFunction(true),
|
||||
);
|
||||
return array();
|
||||
}
|
||||
|
||||
protected function canEvaluateFunction() {
|
||||
return true;
|
||||
}
|
||||
public function evaluateFunction(array $xv) {
|
||||
$yv = array();
|
||||
|
||||
protected function evaluateFunction($x) {
|
||||
return cos(deg2rad($x));
|
||||
foreach ($xv as $x) {
|
||||
$yv[] = cos(deg2rad($x));
|
||||
}
|
||||
|
||||
return $yv;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ final class PhabricatorFactChartFunction
|
|||
const FUNCTIONKEY = 'fact';
|
||||
|
||||
private $fact;
|
||||
private $datapoints;
|
||||
private $map;
|
||||
|
||||
protected function newArguments() {
|
||||
$key_argument = $this->newArgument()
|
||||
|
@ -35,82 +35,68 @@ final class PhabricatorFactChartFunction
|
|||
$conn = $table->establishConnection('r');
|
||||
$table_name = $table->getTableName();
|
||||
|
||||
$where = array();
|
||||
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'keyID = %d',
|
||||
$key_id);
|
||||
|
||||
$parser = $this->getArgumentParser();
|
||||
|
||||
$parts = $fact->buildWhereClauseParts($conn, $parser);
|
||||
foreach ($parts as $part) {
|
||||
$where[] = $part;
|
||||
}
|
||||
|
||||
$data = queryfx_all(
|
||||
$conn,
|
||||
'SELECT value, epoch FROM %T WHERE keyID = %d ORDER BY epoch ASC',
|
||||
'SELECT value, epoch FROM %T WHERE %LA ORDER BY epoch ASC',
|
||||
$table_name,
|
||||
$key_id);
|
||||
if (!$data) {
|
||||
return;
|
||||
}
|
||||
$where);
|
||||
|
||||
$points = array();
|
||||
$map = array();
|
||||
if ($data) {
|
||||
foreach ($data as $row) {
|
||||
$value = (int)$row['value'];
|
||||
$epoch = (int)$row['epoch'];
|
||||
|
||||
$sum = 0;
|
||||
foreach ($data as $key => $row) {
|
||||
$sum += (int)$row['value'];
|
||||
$points[] = array(
|
||||
'x' => (int)$row['epoch'],
|
||||
'y' => $sum,
|
||||
);
|
||||
}
|
||||
|
||||
$this->datapoints = $points;
|
||||
}
|
||||
|
||||
public function getDatapoints(PhabricatorChartDataQuery $query) {
|
||||
$points = $this->datapoints;
|
||||
if (!$points) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$x_min = $query->getMinimumValue();
|
||||
$x_max = $query->getMaximumValue();
|
||||
$limit = $query->getLimit();
|
||||
|
||||
if ($x_min !== null) {
|
||||
foreach ($points as $key => $point) {
|
||||
if ($point['x'] < $x_min) {
|
||||
unset($points[$key]);
|
||||
if (!isset($map[$epoch])) {
|
||||
$map[$epoch] = 0;
|
||||
}
|
||||
|
||||
$map[$epoch] += $value;
|
||||
}
|
||||
}
|
||||
|
||||
if ($x_max !== null) {
|
||||
foreach ($points as $key => $point) {
|
||||
if ($point['x'] > $x_max) {
|
||||
unset($points[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we have too many data points, throw away some of the data.
|
||||
if ($limit !== null) {
|
||||
$count = count($points);
|
||||
if ($count > $limit) {
|
||||
$ii = 0;
|
||||
$every = ceil($count / $limit);
|
||||
foreach ($points as $key => $point) {
|
||||
$ii++;
|
||||
if (($ii % $every) && ($ii != $count)) {
|
||||
unset($points[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $points;
|
||||
}
|
||||
|
||||
public function hasDomain() {
|
||||
return true;
|
||||
$this->map = $map;
|
||||
}
|
||||
|
||||
public function getDomain() {
|
||||
// TODO: We can examine the data to fit a better domain.
|
||||
$min = head_key($this->map);
|
||||
$max = last_key($this->map);
|
||||
|
||||
$now = PhabricatorTime::getNow();
|
||||
return array($now - phutil_units('90 days in seconds'), $now);
|
||||
return new PhabricatorChartInterval($min, $max);
|
||||
}
|
||||
|
||||
public function newInputValues(PhabricatorChartDataQuery $query) {
|
||||
return array_keys($this->map);
|
||||
}
|
||||
|
||||
public function evaluateFunction(array $xv) {
|
||||
$map = $this->map;
|
||||
|
||||
$yv = array();
|
||||
|
||||
foreach ($xv as $x) {
|
||||
if (isset($map[$x])) {
|
||||
$yv[] = $map[$x];
|
||||
} else {
|
||||
$yv[] = null;
|
||||
}
|
||||
}
|
||||
|
||||
return $yv;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
abstract class PhabricatorHigherOrderChartFunction
|
||||
extends PhabricatorChartFunction {
|
||||
|
||||
public function getDomain() {
|
||||
$domains = array();
|
||||
foreach ($this->getFunctionArguments() as $function) {
|
||||
$domains[] = $function->getDomain();
|
||||
}
|
||||
|
||||
return PhabricatorChartInterval::newFromIntervalList($domains);
|
||||
}
|
||||
|
||||
public function newInputValues(PhabricatorChartDataQuery $query) {
|
||||
$map = array();
|
||||
foreach ($this->getFunctionArguments() as $function) {
|
||||
$xv = $function->newInputValues($query);
|
||||
if ($xv !== null) {
|
||||
foreach ($xv as $x) {
|
||||
$map[$x] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$map) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ksort($map);
|
||||
|
||||
return array_keys($map);
|
||||
}
|
||||
|
||||
}
|
40
src/applications/fact/chart/PhabricatorMaxChartFunction.php
Normal file
40
src/applications/fact/chart/PhabricatorMaxChartFunction.php
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorMaxChartFunction
|
||||
extends PhabricatorChartFunction {
|
||||
|
||||
const FUNCTIONKEY = 'max';
|
||||
|
||||
protected function newArguments() {
|
||||
return array(
|
||||
$this->newArgument()
|
||||
->setName('x')
|
||||
->setType('function'),
|
||||
$this->newArgument()
|
||||
->setName('max')
|
||||
->setType('number'),
|
||||
);
|
||||
}
|
||||
|
||||
public function getDomain() {
|
||||
return $this->getArgument('x')->getDomain();
|
||||
}
|
||||
|
||||
public function newInputValues(PhabricatorChartDataQuery $query) {
|
||||
return $this->getArgument('x')->newInputValues($query);
|
||||
}
|
||||
|
||||
public function evaluateFunction(array $xv) {
|
||||
$yv = $this->getArgument('x')->evaluateFunction($xv);
|
||||
$max = $this->getArgument('max');
|
||||
|
||||
foreach ($yv as $k => $y) {
|
||||
if ($y > $max) {
|
||||
$yv[$k] = null;
|
||||
}
|
||||
}
|
||||
|
||||
return $yv;
|
||||
}
|
||||
|
||||
}
|
40
src/applications/fact/chart/PhabricatorMinChartFunction.php
Normal file
40
src/applications/fact/chart/PhabricatorMinChartFunction.php
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorMinChartFunction
|
||||
extends PhabricatorChartFunction {
|
||||
|
||||
const FUNCTIONKEY = 'min';
|
||||
|
||||
protected function newArguments() {
|
||||
return array(
|
||||
$this->newArgument()
|
||||
->setName('x')
|
||||
->setType('function'),
|
||||
$this->newArgument()
|
||||
->setName('min')
|
||||
->setType('number'),
|
||||
);
|
||||
}
|
||||
|
||||
public function getDomain() {
|
||||
return $this->getArgument('x')->getDomain();
|
||||
}
|
||||
|
||||
public function newInputValues(PhabricatorChartDataQuery $query) {
|
||||
return $this->getArgument('x')->newInputValues($query);
|
||||
}
|
||||
|
||||
public function evaluateFunction(array $xv) {
|
||||
$yv = $this->getArgument('x')->evaluateFunction($xv);
|
||||
$min = $this->getArgument('min');
|
||||
|
||||
foreach ($yv as $k => $y) {
|
||||
if ($y < $min) {
|
||||
$yv[$k] = null;
|
||||
}
|
||||
}
|
||||
|
||||
return $yv;
|
||||
}
|
||||
|
||||
}
|
|
@ -7,22 +7,22 @@ final class PhabricatorScaleChartFunction
|
|||
|
||||
protected function newArguments() {
|
||||
return array(
|
||||
$this->newArgument()
|
||||
->setName('x')
|
||||
->setType('function')
|
||||
->setIsSourceFunction(true),
|
||||
$this->newArgument()
|
||||
->setName('scale')
|
||||
->setType('number'),
|
||||
);
|
||||
}
|
||||
|
||||
protected function canEvaluateFunction() {
|
||||
return true;
|
||||
}
|
||||
public function evaluateFunction(array $xv) {
|
||||
$scale = $this->getArgument('scale');
|
||||
|
||||
protected function evaluateFunction($x) {
|
||||
return $x * $this->getArgument('scale');
|
||||
$yv = array();
|
||||
|
||||
foreach ($xv as $x) {
|
||||
$yv[] = $x * $scale;
|
||||
}
|
||||
|
||||
return $yv;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,22 +7,22 @@ final class PhabricatorShiftChartFunction
|
|||
|
||||
protected function newArguments() {
|
||||
return array(
|
||||
$this->newArgument()
|
||||
->setName('x')
|
||||
->setType('function')
|
||||
->setIsSourceFunction(true),
|
||||
$this->newArgument()
|
||||
->setName('shift')
|
||||
->setType('number'),
|
||||
);
|
||||
}
|
||||
|
||||
protected function canEvaluateFunction() {
|
||||
return true;
|
||||
}
|
||||
public function evaluateFunction(array $xv) {
|
||||
$shift = $this->getArgument('shift');
|
||||
|
||||
protected function evaluateFunction($x) {
|
||||
return $x * $this->getArgument('shift');
|
||||
$yv = array();
|
||||
|
||||
foreach ($xv as $x) {
|
||||
$yv[] = $x + $shift;
|
||||
}
|
||||
|
||||
return $yv;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -6,20 +6,17 @@ final class PhabricatorSinChartFunction
|
|||
const FUNCTIONKEY = 'sin';
|
||||
|
||||
protected function newArguments() {
|
||||
return array(
|
||||
$this->newArgument()
|
||||
->setName('x')
|
||||
->setType('function')
|
||||
->setIsSourceFunction(true),
|
||||
);
|
||||
return array();
|
||||
}
|
||||
|
||||
protected function canEvaluateFunction() {
|
||||
return true;
|
||||
}
|
||||
public function evaluateFunction(array $xv) {
|
||||
$yv = array();
|
||||
|
||||
protected function evaluateFunction($x) {
|
||||
return sin(deg2rad($x));
|
||||
foreach ($xv as $x) {
|
||||
$yv[] = sin(deg2rad($x));
|
||||
}
|
||||
|
||||
return $yv;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
40
src/applications/fact/chart/PhabricatorSumChartFunction.php
Normal file
40
src/applications/fact/chart/PhabricatorSumChartFunction.php
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorSumChartFunction
|
||||
extends PhabricatorHigherOrderChartFunction {
|
||||
|
||||
const FUNCTIONKEY = 'sum';
|
||||
|
||||
protected function newArguments() {
|
||||
return array(
|
||||
$this->newArgument()
|
||||
->setName('f')
|
||||
->setType('function')
|
||||
->setRepeatable(true),
|
||||
);
|
||||
}
|
||||
|
||||
public function evaluateFunction(array $xv) {
|
||||
$fv = array();
|
||||
foreach ($this->getFunctionArguments() as $function) {
|
||||
$fv[] = $function->evaluateFunction($xv);
|
||||
}
|
||||
|
||||
$n = count($xv);
|
||||
$yv = array_fill(0, $n, null);
|
||||
|
||||
foreach ($fv as $f) {
|
||||
for ($ii = 0; $ii < $n; $ii++) {
|
||||
if ($f[$ii] !== null) {
|
||||
if (!isset($yv[$ii])) {
|
||||
$yv[$ii] = 0;
|
||||
}
|
||||
$yv[$ii] += $f[$ii];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $yv;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorXChartFunction
|
||||
extends PhabricatorChartFunction {
|
||||
|
||||
const FUNCTIONKEY = 'x';
|
||||
|
||||
protected function newArguments() {
|
||||
return array();
|
||||
}
|
||||
|
||||
protected function canEvaluateFunction() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function evaluateFunction($x) {
|
||||
return $x;
|
||||
}
|
||||
|
||||
}
|
|
@ -5,138 +5,38 @@ final class PhabricatorFactChartController extends PhabricatorFactController {
|
|||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getViewer();
|
||||
|
||||
$chart_key = $request->getURIData('chartKey');
|
||||
if ($chart_key === null) {
|
||||
return $this->newDemoChart();
|
||||
}
|
||||
|
||||
$engine = id(new PhabricatorChartRenderingEngine())
|
||||
->setViewer($viewer);
|
||||
|
||||
$chart = $engine->loadChart($chart_key);
|
||||
if (!$chart) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
// When drawing a chart, we send down a placeholder piece of HTML first,
|
||||
// then fetch the data via async request. Determine if we're drawing
|
||||
// the structure or actually pulling the data.
|
||||
$mode = $request->getURIData('mode');
|
||||
$is_chart_mode = ($mode === 'chart');
|
||||
$is_draw_mode = ($mode === 'draw');
|
||||
|
||||
$functions = array();
|
||||
// TODO: For now, always pull the data. We'll throw it away if we're just
|
||||
// drawing the frame, but this makes errors easier to debug.
|
||||
$chart_data = $engine->newChartData();
|
||||
|
||||
$functions[] = id(new PhabricatorFactChartFunction())
|
||||
->setArguments(array('tasks.count.create'));
|
||||
|
||||
$functions[] = id(new PhabricatorFactChartFunction())
|
||||
->setArguments(array('tasks.open-count.create'));
|
||||
|
||||
$x_function = id(new PhabricatorXChartFunction())
|
||||
->setArguments(array());
|
||||
|
||||
$functions[] = id(new PhabricatorConstantChartFunction())
|
||||
->setArguments(array(360));
|
||||
|
||||
$functions[] = id(new PhabricatorSinChartFunction())
|
||||
->setArguments(array($x_function));
|
||||
|
||||
$cos_function = id(new PhabricatorCosChartFunction())
|
||||
->setArguments(array($x_function));
|
||||
|
||||
$functions[] = id(new PhabricatorShiftChartFunction())
|
||||
->setArguments(
|
||||
array(
|
||||
array(
|
||||
'scale',
|
||||
array(
|
||||
'cos',
|
||||
array(
|
||||
'scale',
|
||||
array('x'),
|
||||
0.001,
|
||||
),
|
||||
),
|
||||
10,
|
||||
),
|
||||
200,
|
||||
));
|
||||
|
||||
list($domain_min, $domain_max) = $this->getDomain($functions);
|
||||
|
||||
$axis = id(new PhabricatorChartAxis())
|
||||
->setMinimumValue($domain_min)
|
||||
->setMaximumValue($domain_max);
|
||||
|
||||
$data_query = id(new PhabricatorChartDataQuery())
|
||||
->setMinimumValue($domain_min)
|
||||
->setMaximumValue($domain_max)
|
||||
->setLimit(2000);
|
||||
|
||||
$datasets = array();
|
||||
foreach ($functions as $function) {
|
||||
$function->setXAxis($axis);
|
||||
|
||||
$function->loadData();
|
||||
|
||||
$points = $function->getDatapoints($data_query);
|
||||
|
||||
$x = array();
|
||||
$y = array();
|
||||
|
||||
foreach ($points as $point) {
|
||||
$x[] = $point['x'];
|
||||
$y[] = $point['y'];
|
||||
}
|
||||
|
||||
$datasets[] = array(
|
||||
'x' => $x,
|
||||
'y' => $y,
|
||||
'color' => '#ff00ff',
|
||||
);
|
||||
if ($is_draw_mode) {
|
||||
return id(new AphrontAjaxResponse())->setContent($chart_data);
|
||||
}
|
||||
|
||||
|
||||
$y_min = 0;
|
||||
$y_max = 0;
|
||||
foreach ($datasets as $dataset) {
|
||||
if (!$dataset['y']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$y_min = min($y_min, min($dataset['y']));
|
||||
$y_max = max($y_max, max($dataset['y']));
|
||||
}
|
||||
|
||||
$chart_data = array(
|
||||
'datasets' => $datasets,
|
||||
'xMin' => $domain_min,
|
||||
'xMax' => $domain_max,
|
||||
'yMin' => $y_min,
|
||||
'yMax' => $y_max,
|
||||
);
|
||||
|
||||
// TODO: Move this back up, it's just down here for now to make
|
||||
// debugging easier so the main page throws a more visible exception when
|
||||
// something goes wrong.
|
||||
if ($is_chart_mode) {
|
||||
return $this->newChartResponse();
|
||||
}
|
||||
|
||||
return id(new AphrontAjaxResponse())->setContent($chart_data);
|
||||
$chart_view = $engine->newChartView();
|
||||
return $this->newChartResponse($chart_view);
|
||||
}
|
||||
|
||||
private function newChartResponse() {
|
||||
$request = $this->getRequest();
|
||||
$chart_node_id = celerity_generate_unique_node_id();
|
||||
|
||||
$chart_view = phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'id' => $chart_node_id,
|
||||
'style' => 'background: #ffffff; '.
|
||||
'height: 480px; ',
|
||||
),
|
||||
'');
|
||||
|
||||
$data_uri = $request->getRequestURI();
|
||||
$data_uri->setPath('/fact/draw/');
|
||||
|
||||
Javelin::initBehavior(
|
||||
'line-chart',
|
||||
array(
|
||||
'chartNodeID' => $chart_node_id,
|
||||
'dataURI' => (string)$data_uri,
|
||||
));
|
||||
|
||||
private function newChartResponse($chart_view) {
|
||||
$box = id(new PHUIObjectBoxView())
|
||||
->setHeaderText(pht('Chart'))
|
||||
->appendChild($chart_view);
|
||||
|
@ -151,52 +51,57 @@ final class PhabricatorFactChartController extends PhabricatorFactController {
|
|||
->setTitle($title)
|
||||
->setCrumbs($crumbs)
|
||||
->appendChild($box);
|
||||
|
||||
}
|
||||
|
||||
private function getDomain(array $functions) {
|
||||
$domain_min_list = null;
|
||||
$domain_max_list = null;
|
||||
foreach ($functions as $function) {
|
||||
if ($function->hasDomain()) {
|
||||
$domain = $function->getDomain();
|
||||
private function newDemoChart() {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
list($domain_min, $domain_max) = $domain;
|
||||
$argvs = array();
|
||||
|
||||
if ($domain_min !== null) {
|
||||
$domain_min_list[] = $domain_min;
|
||||
}
|
||||
$argvs[] = array('fact', 'tasks.count.create');
|
||||
|
||||
if ($domain_max !== null) {
|
||||
$domain_max_list[] = $domain_max;
|
||||
}
|
||||
}
|
||||
$argvs[] = array('constant', 360);
|
||||
|
||||
$argvs[] = array('fact', 'tasks.open-count.create');
|
||||
|
||||
$argvs[] = array(
|
||||
'sum',
|
||||
array(
|
||||
'accumulate',
|
||||
array('fact', 'tasks.count.create'),
|
||||
),
|
||||
array(
|
||||
'accumulate',
|
||||
array('fact', 'tasks.open-count.create'),
|
||||
),
|
||||
);
|
||||
|
||||
$argvs[] = array(
|
||||
'compose',
|
||||
array('scale', 0.001),
|
||||
array('cos'),
|
||||
array('scale', 100),
|
||||
array('shift', 800),
|
||||
);
|
||||
|
||||
$datasets = array();
|
||||
foreach ($argvs as $argv) {
|
||||
$datasets[] = PhabricatorChartDataset::newFromDictionary(
|
||||
array(
|
||||
'function' => $argv,
|
||||
));
|
||||
}
|
||||
|
||||
$domain_min = null;
|
||||
$domain_max = null;
|
||||
$chart = id(new PhabricatorFactChart())
|
||||
->setDatasets($datasets);
|
||||
|
||||
if ($domain_min_list) {
|
||||
$domain_min = min($domain_min_list);
|
||||
}
|
||||
$engine = id(new PhabricatorChartRenderingEngine())
|
||||
->setViewer($viewer)
|
||||
->setChart($chart);
|
||||
|
||||
if ($domain_max_list) {
|
||||
$domain_max = max($domain_max_list);
|
||||
}
|
||||
$chart = $engine->getStoredChart();
|
||||
|
||||
// If we don't have any domain data from the actual functions, pick a
|
||||
// plausible domain automatically.
|
||||
|
||||
if ($domain_max === null) {
|
||||
$domain_max = PhabricatorTime::getNow();
|
||||
}
|
||||
|
||||
if ($domain_min === null) {
|
||||
$domain_min = $domain_max - phutil_units('365 days in seconds');
|
||||
}
|
||||
|
||||
return array($domain_min, $domain_max);
|
||||
return id(new AphrontRedirectResponse())->setURI($chart->getURI());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ final class PhabricatorFactDaemon extends PhabricatorDaemon {
|
|||
}
|
||||
|
||||
$this->log(pht('Zzz...'));
|
||||
$this->sleep(60 * 5);
|
||||
$this->sleep(15);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
97
src/applications/fact/engine/PhabricatorChartEngine.php
Normal file
97
src/applications/fact/engine/PhabricatorChartEngine.php
Normal file
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
|
||||
abstract class PhabricatorChartEngine
|
||||
extends Phobject {
|
||||
|
||||
private $viewer;
|
||||
private $engineParameters = array();
|
||||
|
||||
const KEY_ENGINE = 'engineKey';
|
||||
const KEY_PARAMETERS = 'engineParameters';
|
||||
|
||||
final public function setViewer(PhabricatorUser $viewer) {
|
||||
$this->viewer = $viewer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
final public function getViewer() {
|
||||
return $this->viewer;
|
||||
}
|
||||
|
||||
final protected function setEngineParameter($key, $value) {
|
||||
$this->engineParameters[$key] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
final protected function getEngineParameter($key, $default = null) {
|
||||
return idx($this->engineParameters, $key, $default);
|
||||
}
|
||||
|
||||
final protected function getEngineParameters() {
|
||||
return $this->engineParameters;
|
||||
}
|
||||
|
||||
final public static function newFromChart(PhabricatorFactChart $chart) {
|
||||
$engine_key = $chart->getChartParameter(self::KEY_ENGINE);
|
||||
|
||||
$engine_map = self::getAllChartEngines();
|
||||
if (!isset($engine_map[$engine_key])) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Chart uses unknown engine key ("%s") and can not be rendered.',
|
||||
$engine_key));
|
||||
}
|
||||
|
||||
return clone id($engine_map[$engine_key]);
|
||||
}
|
||||
|
||||
final public static function getAllChartEngines() {
|
||||
return id(new PhutilClassMapQuery())
|
||||
->setAncestorClass(__CLASS__)
|
||||
->setUniqueMethod('getChartEngineKey')
|
||||
->execute();
|
||||
}
|
||||
|
||||
final public function getChartEngineKey() {
|
||||
return $this->getPhobjectClassConstant('CHARTENGINEKEY', 32);
|
||||
}
|
||||
|
||||
final public function buildChart(PhabricatorFactChart $chart) {
|
||||
$map = $chart->getChartParameter(self::KEY_PARAMETERS, array());
|
||||
return $this->newChart($chart, $map);
|
||||
}
|
||||
|
||||
abstract protected function newChart(PhabricatorFactChart $chart, array $map);
|
||||
|
||||
final public function buildChartPanel() {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$parameters = $this->getEngineParameters();
|
||||
|
||||
$chart = id(new PhabricatorFactChart())
|
||||
->setChartParameter(self::KEY_ENGINE, $this->getChartEngineKey())
|
||||
->setChartParameter(self::KEY_PARAMETERS, $this->getEngineParameters());
|
||||
|
||||
$rendering_engine = id(new PhabricatorChartRenderingEngine())
|
||||
->setViewer($viewer)
|
||||
->setChart($chart);
|
||||
|
||||
$chart = $rendering_engine->getStoredChart();
|
||||
|
||||
$panel_type = id(new PhabricatorDashboardChartPanelType())
|
||||
->getPanelTypeKey();
|
||||
|
||||
$chart_panel = id(new PhabricatorDashboardPanel())
|
||||
->setPanelType($panel_type)
|
||||
->setProperty('chartKey', $chart->getChartKey());
|
||||
|
||||
return $chart_panel;
|
||||
}
|
||||
|
||||
final protected function newFunction($name /* , ... */) {
|
||||
$argv = func_get_args();
|
||||
return id(new PhabricatorComposeChartFunction())
|
||||
->setArguments(array($argv));
|
||||
}
|
||||
|
||||
}
|
213
src/applications/fact/engine/PhabricatorChartRenderingEngine.php
Normal file
213
src/applications/fact/engine/PhabricatorChartRenderingEngine.php
Normal file
|
@ -0,0 +1,213 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorChartRenderingEngine
|
||||
extends Phobject {
|
||||
|
||||
private $viewer;
|
||||
private $chart;
|
||||
private $storedChart;
|
||||
|
||||
public function setViewer(PhabricatorUser $viewer) {
|
||||
$this->viewer = $viewer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getViewer() {
|
||||
return $this->viewer;
|
||||
}
|
||||
|
||||
public function setChart(PhabricatorFactChart $chart) {
|
||||
$this->chart = $chart;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getChart() {
|
||||
return $this->chart;
|
||||
}
|
||||
|
||||
public function loadChart($chart_key) {
|
||||
$chart = id(new PhabricatorFactChart())->loadOneWhere(
|
||||
'chartKey = %s',
|
||||
$chart_key);
|
||||
|
||||
if ($chart) {
|
||||
$this->setChart($chart);
|
||||
}
|
||||
|
||||
return $chart;
|
||||
}
|
||||
|
||||
public static function getChartURI($chart_key) {
|
||||
return id(new PhabricatorFactChart())
|
||||
->setChartKey($chart_key)
|
||||
->getURI();
|
||||
}
|
||||
|
||||
public function getStoredChart() {
|
||||
if (!$this->storedChart) {
|
||||
$chart = $this->getChart();
|
||||
$chart_key = $chart->getChartKey();
|
||||
if (!$chart_key) {
|
||||
$chart_key = $chart->newChartKey();
|
||||
|
||||
$stored_chart = id(new PhabricatorFactChart())->loadOneWhere(
|
||||
'chartKey = %s',
|
||||
$chart_key);
|
||||
if ($stored_chart) {
|
||||
$chart = $stored_chart;
|
||||
} else {
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
|
||||
try {
|
||||
$chart->save();
|
||||
} catch (AphrontDuplicateKeyQueryException $ex) {
|
||||
$chart = id(new PhabricatorFactChart())->loadOneWhere(
|
||||
'chartKey = %s',
|
||||
$chart_key);
|
||||
if (!$chart) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Failed to load chart with key "%s" after key collision. '.
|
||||
'This should not be possible.',
|
||||
$chart_key));
|
||||
}
|
||||
}
|
||||
|
||||
unset($unguarded);
|
||||
}
|
||||
$this->setChart($chart);
|
||||
}
|
||||
|
||||
$this->storedChart = $chart;
|
||||
}
|
||||
|
||||
return $this->storedChart;
|
||||
}
|
||||
|
||||
public function newChartView() {
|
||||
$chart = $this->getStoredChart();
|
||||
$chart_key = $chart->getChartKey();
|
||||
|
||||
$chart_node_id = celerity_generate_unique_node_id();
|
||||
|
||||
$chart_view = phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'id' => $chart_node_id,
|
||||
'class' => 'chart-hardpoint',
|
||||
));
|
||||
|
||||
$data_uri = urisprintf('/fact/chart/%s/draw/', $chart_key);
|
||||
|
||||
Javelin::initBehavior(
|
||||
'line-chart',
|
||||
array(
|
||||
'chartNodeID' => $chart_node_id,
|
||||
'dataURI' => (string)$data_uri,
|
||||
));
|
||||
|
||||
return $chart_view;
|
||||
}
|
||||
|
||||
public function newChartData() {
|
||||
$chart = $this->getStoredChart();
|
||||
$chart_key = $chart->getChartKey();
|
||||
|
||||
$chart_engine = PhabricatorChartEngine::newFromChart($chart)
|
||||
->setViewer($this->getViewer());
|
||||
$chart_engine->buildChart($chart);
|
||||
|
||||
$datasets = $chart->getDatasets();
|
||||
|
||||
$functions = array();
|
||||
foreach ($datasets as $dataset) {
|
||||
foreach ($dataset->getFunctions() as $function) {
|
||||
$functions[] = $function;
|
||||
}
|
||||
}
|
||||
|
||||
$subfunctions = array();
|
||||
foreach ($functions as $function) {
|
||||
foreach ($function->getSubfunctions() as $subfunction) {
|
||||
$subfunctions[] = $subfunction;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($subfunctions as $subfunction) {
|
||||
$subfunction->loadData();
|
||||
}
|
||||
|
||||
$domain = $this->getDomain($functions);
|
||||
|
||||
$axis = id(new PhabricatorChartAxis())
|
||||
->setMinimumValue($domain->getMin())
|
||||
->setMaximumValue($domain->getMax());
|
||||
|
||||
$data_query = id(new PhabricatorChartDataQuery())
|
||||
->setMinimumValue($domain->getMin())
|
||||
->setMaximumValue($domain->getMax())
|
||||
->setLimit(2000);
|
||||
|
||||
$wire_datasets = array();
|
||||
$ranges = array();
|
||||
foreach ($datasets as $dataset) {
|
||||
$display_data = $dataset->getChartDisplayData($data_query);
|
||||
|
||||
$ranges[] = $display_data->getRange();
|
||||
$wire_datasets[] = $display_data->getWireData();
|
||||
}
|
||||
|
||||
$range = $this->getRange($ranges);
|
||||
|
||||
$chart_data = array(
|
||||
'datasets' => $wire_datasets,
|
||||
'xMin' => $domain->getMin(),
|
||||
'xMax' => $domain->getMax(),
|
||||
'yMin' => $range->getMin(),
|
||||
'yMax' => $range->getMax(),
|
||||
);
|
||||
|
||||
return $chart_data;
|
||||
}
|
||||
|
||||
private function getDomain(array $functions) {
|
||||
$domains = array();
|
||||
foreach ($functions as $function) {
|
||||
$domains[] = $function->getDomain();
|
||||
}
|
||||
|
||||
$domain = PhabricatorChartInterval::newFromIntervalList($domains);
|
||||
|
||||
// If we don't have any domain data from the actual functions, pick a
|
||||
// plausible domain automatically.
|
||||
|
||||
if ($domain->getMax() === null) {
|
||||
$domain->setMax(PhabricatorTime::getNow());
|
||||
}
|
||||
|
||||
if ($domain->getMin() === null) {
|
||||
$domain->setMin($domain->getMax() - phutil_units('365 days in seconds'));
|
||||
}
|
||||
|
||||
return $domain;
|
||||
}
|
||||
|
||||
private function getRange(array $ranges) {
|
||||
$range = PhabricatorChartInterval::newFromIntervalList($ranges);
|
||||
|
||||
// Start the Y axis at 0 unless the chart has negative values.
|
||||
$min = $range->getMin();
|
||||
if ($min === null || $min >= 0) {
|
||||
$range->setMin(0);
|
||||
}
|
||||
|
||||
// If there's no maximum value, just pick a plausible default.
|
||||
$max = $range->getMax();
|
||||
if ($max === null) {
|
||||
$range->setMax($range->getMin() + 100);
|
||||
}
|
||||
|
||||
return $range;
|
||||
}
|
||||
|
||||
}
|
|
@ -38,7 +38,46 @@ abstract class PhabricatorFact extends Phobject {
|
|||
abstract protected function newTemplateDatapoint();
|
||||
|
||||
final public function getFunctionArguments() {
|
||||
return array();
|
||||
$key = $this->getKey();
|
||||
|
||||
$argv = array();
|
||||
|
||||
if (preg_match('/\.project\z/', $key)) {
|
||||
$argv[] = id(new PhabricatorChartFunctionArgument())
|
||||
->setName('phid')
|
||||
->setType('phid');
|
||||
}
|
||||
|
||||
if (preg_match('/\.owner\z/', $key)) {
|
||||
$argv[] = id(new PhabricatorChartFunctionArgument())
|
||||
->setName('phid')
|
||||
->setType('phid');
|
||||
}
|
||||
|
||||
return $argv;
|
||||
}
|
||||
|
||||
final public function buildWhereClauseParts(
|
||||
AphrontDatabaseConnection $conn,
|
||||
PhabricatorChartFunctionArgumentParser $arguments) {
|
||||
$where = array();
|
||||
|
||||
$has_phid = $this->getFunctionArguments();
|
||||
|
||||
if ($has_phid) {
|
||||
$phid = $arguments->getArgumentValue('phid');
|
||||
|
||||
$dimension_id = id(new PhabricatorFactObjectDimension())
|
||||
->newDimensionID($phid);
|
||||
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'dimensionID = %d',
|
||||
$dimension_id);
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ final class PhabricatorFactChart
|
|||
protected $chartKey;
|
||||
protected $chartParameters = array();
|
||||
|
||||
private $datasets = self::ATTACHABLE;
|
||||
|
||||
protected function getConfiguration() {
|
||||
return array(
|
||||
self::CONFIG_SERIALIZATION => array(
|
||||
|
@ -33,6 +35,12 @@ final class PhabricatorFactChart
|
|||
return idx($this->chartParameters, $key, $default);
|
||||
}
|
||||
|
||||
public function newChartKey() {
|
||||
$digest = serialize($this->chartParameters);
|
||||
$digest = PhabricatorHash::digestForIndex($digest);
|
||||
return $digest;
|
||||
}
|
||||
|
||||
public function save() {
|
||||
if ($this->getID()) {
|
||||
throw new Exception(
|
||||
|
@ -41,14 +49,25 @@ final class PhabricatorFactChart
|
|||
'overwrite an existing chart configuration.'));
|
||||
}
|
||||
|
||||
$digest = serialize($this->chartParameters);
|
||||
$digest = PhabricatorHash::digestForIndex($digest);
|
||||
|
||||
$this->chartKey = $digest;
|
||||
$this->chartKey = $this->newChartKey();
|
||||
|
||||
return parent::save();
|
||||
}
|
||||
|
||||
public function attachDatasets(array $datasets) {
|
||||
assert_instances_of($datasets, 'PhabricatorChartDataset');
|
||||
$this->datasets = $datasets;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDatasets() {
|
||||
return $this->assertAttached($this->datasets);
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
return urisprintf('/fact/chart/%s/', $this->getChartKey());
|
||||
}
|
||||
|
||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||
|
||||
public function getCapabilities() {
|
||||
|
|
|
@ -31,6 +31,10 @@ final class PhabricatorFeedApplication extends PhabricatorApplication {
|
|||
'/feed/' => array(
|
||||
'(?P<id>\d+)/' => 'PhabricatorFeedDetailController',
|
||||
'(?:query/(?P<queryKey>[^/]+)/)?' => 'PhabricatorFeedListController',
|
||||
'transactions/' => array(
|
||||
$this->getQueryRoutePattern()
|
||||
=> 'PhabricatorFeedTransactionListController',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,24 +1,4 @@
|
|||
<?php
|
||||
|
||||
abstract class PhabricatorFeedController extends PhabricatorController {
|
||||
|
||||
protected function buildSideNavView() {
|
||||
$user = $this->getRequest()->getUser();
|
||||
|
||||
$nav = new AphrontSideNavFilterView();
|
||||
$nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
|
||||
|
||||
id(new PhabricatorFeedSearchEngine())
|
||||
->setViewer($user)
|
||||
->addNavigationItems($nav->getMenu());
|
||||
|
||||
$nav->selectFilter(null);
|
||||
|
||||
return $nav;
|
||||
}
|
||||
|
||||
public function buildApplicationMenu() {
|
||||
return $this->buildSideNavView()->getMenu();
|
||||
}
|
||||
|
||||
}
|
||||
abstract class PhabricatorFeedController
|
||||
extends PhabricatorController {}
|
||||
|
|
|
@ -1,20 +1,27 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorFeedListController extends PhabricatorFeedController {
|
||||
final class PhabricatorFeedListController
|
||||
extends PhabricatorFeedController {
|
||||
|
||||
public function shouldAllowPublic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$querykey = $request->getURIData('queryKey');
|
||||
$navigation = array();
|
||||
|
||||
$controller = id(new PhabricatorApplicationSearchController())
|
||||
->setQueryKey($querykey)
|
||||
->setSearchEngine(new PhabricatorFeedSearchEngine())
|
||||
->setNavigation($this->buildSideNavView());
|
||||
$navigation[] = id(new PHUIListItemView())
|
||||
->setType(PHUIListItemView::TYPE_LABEL)
|
||||
->setName(pht('Transactions'));
|
||||
|
||||
return $this->delegateToController($controller);
|
||||
$navigation[] = id(new PHUIListItemView())
|
||||
->setName(pht('Transaction Logs'))
|
||||
->setHref($this->getApplicationURI('transactions/'));
|
||||
|
||||
return id(new PhabricatorFeedSearchEngine())
|
||||
->setController($this)
|
||||
->setNavigationItems($navigation)
|
||||
->buildResponse();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorFeedTransactionListController
|
||||
extends PhabricatorFeedController {
|
||||
|
||||
public function shouldAllowPublic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
return id(new PhabricatorFeedTransactionSearchEngine())
|
||||
->setController($this)
|
||||
->buildResponse();
|
||||
}
|
||||
|
||||
}
|
215
src/applications/feed/query/PhabricatorFeedTransactionQuery.php
Normal file
215
src/applications/feed/query/PhabricatorFeedTransactionQuery.php
Normal file
|
@ -0,0 +1,215 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorFeedTransactionQuery
|
||||
extends PhabricatorCursorPagedPolicyAwareQuery {
|
||||
|
||||
private $phids;
|
||||
private $authorPHIDs;
|
||||
private $objectTypes;
|
||||
private $createdMin;
|
||||
private $createdMax;
|
||||
|
||||
public function withPHIDs(array $phids) {
|
||||
$this->phids = $phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withAuthorPHIDs(array $phids) {
|
||||
$this->authorPHIDs = $phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withObjectTypes(array $types) {
|
||||
$this->objectTypes = $types;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withDateCreatedBetween($min, $max) {
|
||||
$this->createdMin = $min;
|
||||
$this->createdMax = $max;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function newResultObject() {
|
||||
// Return an arbitrary valid transaction object. The actual query may
|
||||
// return objects of any subclass of "ApplicationTransaction" when it is
|
||||
// executed, but we need to pick something concrete here to make some
|
||||
// integrations work (like automatic handling of PHIDs in data export).
|
||||
return new PhabricatorUserTransaction();
|
||||
}
|
||||
|
||||
protected function loadPage() {
|
||||
$queries = $this->newTransactionQueries();
|
||||
|
||||
$xactions = array();
|
||||
|
||||
if ($this->shouldLimitResults()) {
|
||||
$limit = $this->getRawResultLimit();
|
||||
if (!$limit) {
|
||||
$limit = null;
|
||||
}
|
||||
} else {
|
||||
$limit = null;
|
||||
}
|
||||
|
||||
// We're doing a bit of manual work to get paging working, because this
|
||||
// query aggregates the results of a large number of subqueries.
|
||||
|
||||
// Overall, we're ordering transactions by "<dateCreated, phid>". Ordering
|
||||
// by PHID is not very meaningful, but we don't need the ordering to be
|
||||
// especially meaningful, just consistent. Using PHIDs is easy and does
|
||||
// everything we need it to technically.
|
||||
|
||||
// To actually configure paging, if we have an external cursor, we load
|
||||
// the internal cursor first. Then we pass it to each subquery and the
|
||||
// subqueries pretend they just loaded a page where it was the last object.
|
||||
// This configures their queries properly and we can aggregate a cohesive
|
||||
// set of results by combining all the queries.
|
||||
|
||||
$cursor = $this->getExternalCursorString();
|
||||
if ($cursor !== null) {
|
||||
$cursor_object = $this->newInternalCursorFromExternalCursor($cursor);
|
||||
} else {
|
||||
$cursor_object = null;
|
||||
}
|
||||
|
||||
$is_reversed = $this->getIsQueryOrderReversed();
|
||||
|
||||
$created_min = $this->createdMin;
|
||||
$created_max = $this->createdMax;
|
||||
|
||||
$xaction_phids = $this->phids;
|
||||
$author_phids = $this->authorPHIDs;
|
||||
|
||||
foreach ($queries as $query) {
|
||||
$query->withDateCreatedBetween($created_min, $created_max);
|
||||
|
||||
if ($xaction_phids !== null) {
|
||||
$query->withPHIDs($xaction_phids);
|
||||
}
|
||||
|
||||
if ($author_phids !== null) {
|
||||
$query->withAuthorPHIDs($author_phids);
|
||||
}
|
||||
|
||||
if ($limit !== null) {
|
||||
$query->setLimit($limit);
|
||||
}
|
||||
|
||||
if ($cursor_object !== null) {
|
||||
$query
|
||||
->setAggregatePagingCursor($cursor_object)
|
||||
->setIsQueryOrderReversed($is_reversed);
|
||||
}
|
||||
|
||||
$query->setOrder('global');
|
||||
|
||||
$query_xactions = $query->execute();
|
||||
foreach ($query_xactions as $query_xaction) {
|
||||
$xactions[] = $query_xaction;
|
||||
}
|
||||
|
||||
$xactions = msortv($xactions, 'newGlobalSortVector');
|
||||
if ($is_reversed) {
|
||||
$xactions = array_reverse($xactions);
|
||||
}
|
||||
|
||||
if ($limit !== null) {
|
||||
$xactions = array_slice($xactions, 0, $limit);
|
||||
|
||||
// If we've found enough transactions to fill up the entire requested
|
||||
// page size, we can narrow the search window: transactions after the
|
||||
// last transaction we've found so far can't possibly be part of the
|
||||
// result set.
|
||||
|
||||
if (count($xactions) === $limit) {
|
||||
$last_date = last($xactions)->getDateCreated();
|
||||
if ($is_reversed) {
|
||||
if ($created_max === null) {
|
||||
$created_max = $last_date;
|
||||
} else {
|
||||
$created_max = min($created_max, $last_date);
|
||||
}
|
||||
} else {
|
||||
if ($created_min === null) {
|
||||
$created_min = $last_date;
|
||||
} else {
|
||||
$created_min = max($created_min, $last_date);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $xactions;
|
||||
}
|
||||
|
||||
public function getQueryApplicationClass() {
|
||||
return 'PhabricatorFeedApplication';
|
||||
}
|
||||
|
||||
private function newTransactionQueries() {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$queries = id(new PhutilClassMapQuery())
|
||||
->setAncestorClass('PhabricatorApplicationTransactionQuery')
|
||||
->execute();
|
||||
|
||||
$type_map = array();
|
||||
|
||||
// If we're querying for specific transaction PHIDs, we only need to
|
||||
// consider queries which may load transactions with subtypes present
|
||||
// in the list.
|
||||
|
||||
// For example, if we're loading Maniphest Task transaction PHIDs, we know
|
||||
// we only have to look at Maniphest Task transactions, since other types
|
||||
// of objects will never have the right transaction PHIDs.
|
||||
|
||||
$xaction_phids = $this->phids;
|
||||
if ($xaction_phids) {
|
||||
foreach ($xaction_phids as $xaction_phid) {
|
||||
$type_map[phid_get_subtype($xaction_phid)] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$object_types = $this->objectTypes;
|
||||
if ($object_types) {
|
||||
$object_types = array_fuse($object_types);
|
||||
}
|
||||
|
||||
$results = array();
|
||||
foreach ($queries as $query) {
|
||||
$query_type = $query->getTemplateApplicationTransaction()
|
||||
->getApplicationTransactionType();
|
||||
|
||||
if ($type_map) {
|
||||
if (!isset($type_map[$query_type])) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($object_types) {
|
||||
if (!isset($object_types[$query_type])) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$results[] = id(clone $query)
|
||||
->setViewer($viewer)
|
||||
->setParentQuery($this);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
protected function newExternalCursorStringForResult($object) {
|
||||
return (string)$object->getPHID();
|
||||
}
|
||||
|
||||
protected function applyExternalCursorConstraintsToQuery(
|
||||
PhabricatorCursorPagedPolicyAwareQuery $subquery,
|
||||
$cursor) {
|
||||
$subquery->withPHIDs(array($cursor));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,230 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorFeedTransactionSearchEngine
|
||||
extends PhabricatorApplicationSearchEngine {
|
||||
|
||||
public function getResultTypeDescription() {
|
||||
return pht('Transactions');
|
||||
}
|
||||
|
||||
public function getApplicationClassName() {
|
||||
return 'PhabricatorFeedApplication';
|
||||
}
|
||||
|
||||
public function newQuery() {
|
||||
return new PhabricatorFeedTransactionQuery();
|
||||
}
|
||||
|
||||
protected function buildCustomSearchFields() {
|
||||
return array(
|
||||
id(new PhabricatorUsersSearchField())
|
||||
->setLabel(pht('Authors'))
|
||||
->setKey('authorPHIDs')
|
||||
->setAliases(array('author', 'authors')),
|
||||
id(new PhabricatorSearchDatasourceField())
|
||||
->setLabel(pht('Object Types'))
|
||||
->setKey('objectTypes')
|
||||
->setAliases(array('objectType'))
|
||||
->setDatasource(new PhabricatorTransactionsObjectTypeDatasource()),
|
||||
id(new PhabricatorSearchDateField())
|
||||
->setLabel(pht('Created After'))
|
||||
->setKey('createdStart'),
|
||||
id(new PhabricatorSearchDateField())
|
||||
->setLabel(pht('Created Before'))
|
||||
->setKey('createdEnd'),
|
||||
);
|
||||
}
|
||||
|
||||
protected function buildQueryFromParameters(array $map) {
|
||||
$query = $this->newQuery();
|
||||
|
||||
if ($map['authorPHIDs']) {
|
||||
$query->withAuthorPHIDs($map['authorPHIDs']);
|
||||
}
|
||||
|
||||
if ($map['objectTypes']) {
|
||||
$query->withObjectTypes($map['objectTypes']);
|
||||
}
|
||||
|
||||
$created_min = $map['createdStart'];
|
||||
$created_max = $map['createdEnd'];
|
||||
|
||||
if ($created_min && $created_max) {
|
||||
if ($created_min > $created_max) {
|
||||
throw new PhabricatorSearchConstraintException(
|
||||
pht(
|
||||
'The specified "Created Before" date is earlier in time than the '.
|
||||
'specified "Created After" date, so this query can never match '.
|
||||
'any results.'));
|
||||
}
|
||||
}
|
||||
|
||||
if ($created_min || $created_max) {
|
||||
$query->withDateCreatedBetween($created_min, $created_max);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function getURI($path) {
|
||||
return '/feed/transactions/'.$path;
|
||||
}
|
||||
|
||||
protected function getBuiltinQueryNames() {
|
||||
$names = array(
|
||||
'all' => pht('All Transactions'),
|
||||
);
|
||||
|
||||
return $names;
|
||||
}
|
||||
|
||||
public function buildSavedQueryFromBuiltin($query_key) {
|
||||
$query = $this->newSavedQuery()
|
||||
->setQueryKey($query_key);
|
||||
|
||||
switch ($query_key) {
|
||||
case 'all':
|
||||
return $query;
|
||||
}
|
||||
|
||||
return parent::buildSavedQueryFromBuiltin($query_key);
|
||||
}
|
||||
|
||||
protected function renderResultList(
|
||||
array $objects,
|
||||
PhabricatorSavedQuery $query,
|
||||
array $handles) {
|
||||
assert_instances_of($objects, 'PhabricatorApplicationTransaction');
|
||||
|
||||
$viewer = $this->requireViewer();
|
||||
|
||||
$handle_phids = array();
|
||||
foreach ($objects as $object) {
|
||||
$author_phid = $object->getAuthorPHID();
|
||||
if ($author_phid !== null) {
|
||||
$handle_phids[] = $author_phid;
|
||||
}
|
||||
$object_phid = $object->getObjectPHID();
|
||||
if ($object_phid !== null) {
|
||||
$handle_phids[] = $object_phid;
|
||||
}
|
||||
}
|
||||
|
||||
$handles = $viewer->loadHandles($handle_phids);
|
||||
|
||||
$rows = array();
|
||||
foreach ($objects as $object) {
|
||||
$author_phid = $object->getAuthorPHID();
|
||||
$object_phid = $object->getObjectPHID();
|
||||
|
||||
try {
|
||||
$title = $object->getTitle();
|
||||
} catch (Exception $ex) {
|
||||
$title = null;
|
||||
}
|
||||
|
||||
$rows[] = array(
|
||||
$handles[$author_phid]->renderLink(),
|
||||
$handles[$object_phid]->renderLink(),
|
||||
AphrontTableView::renderSingleDisplayLine($title),
|
||||
phabricator_datetime($object->getDateCreated(), $viewer),
|
||||
);
|
||||
}
|
||||
|
||||
$table = id(new AphrontTableView($rows))
|
||||
->setHeaders(
|
||||
array(
|
||||
pht('Author'),
|
||||
pht('Object'),
|
||||
pht('Transaction'),
|
||||
pht('Date'),
|
||||
))
|
||||
->setColumnClasses(
|
||||
array(
|
||||
null,
|
||||
null,
|
||||
'wide',
|
||||
'right',
|
||||
));
|
||||
|
||||
return id(new PhabricatorApplicationSearchResultView())
|
||||
->setTable($table);
|
||||
}
|
||||
|
||||
protected function newExportFields() {
|
||||
$fields = array(
|
||||
id(new PhabricatorPHIDExportField())
|
||||
->setKey('authorPHID')
|
||||
->setLabel(pht('Author PHID')),
|
||||
id(new PhabricatorStringExportField())
|
||||
->setKey('author')
|
||||
->setLabel(pht('Author')),
|
||||
id(new PhabricatorStringExportField())
|
||||
->setKey('objectType')
|
||||
->setLabel(pht('Object Type')),
|
||||
id(new PhabricatorPHIDExportField())
|
||||
->setKey('objectPHID')
|
||||
->setLabel(pht('Object PHID')),
|
||||
id(new PhabricatorStringExportField())
|
||||
->setKey('objectName')
|
||||
->setLabel(pht('Object Name')),
|
||||
id(new PhabricatorStringExportField())
|
||||
->setKey('description')
|
||||
->setLabel(pht('Description')),
|
||||
);
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
protected function newExportData(array $xactions) {
|
||||
$viewer = $this->requireViewer();
|
||||
|
||||
$phids = array();
|
||||
foreach ($xactions as $xaction) {
|
||||
$phids[] = $xaction->getAuthorPHID();
|
||||
$phids[] = $xaction->getObjectPHID();
|
||||
}
|
||||
$handles = $viewer->loadHandles($phids);
|
||||
|
||||
$export = array();
|
||||
foreach ($xactions as $xaction) {
|
||||
$xaction_phid = $xaction->getPHID();
|
||||
|
||||
$author_phid = $xaction->getAuthorPHID();
|
||||
if ($author_phid) {
|
||||
$author_name = $handles[$author_phid]->getName();
|
||||
} else {
|
||||
$author_name = null;
|
||||
}
|
||||
|
||||
$object_phid = $xaction->getObjectPHID();
|
||||
if ($object_phid) {
|
||||
$object_name = $handles[$object_phid]->getName();
|
||||
} else {
|
||||
$object_name = null;
|
||||
}
|
||||
|
||||
$old_target = $xaction->getRenderingTarget();
|
||||
try {
|
||||
$description = $xaction
|
||||
->setRenderingTarget(PhabricatorApplicationTransaction::TARGET_TEXT)
|
||||
->getTitle();
|
||||
} catch (Exception $ex) {
|
||||
$description = null;
|
||||
}
|
||||
$xaction->setRenderingTarget($old_target);
|
||||
|
||||
$export[] = array(
|
||||
'authorPHID' => $author_phid,
|
||||
'author' => $author_name,
|
||||
'objectType' => phid_get_subtype($xaction_phid),
|
||||
'objectPHID' => $object_phid,
|
||||
'objectName' => $object_name,
|
||||
'description' => $description,
|
||||
);
|
||||
}
|
||||
|
||||
return $export;
|
||||
}
|
||||
|
||||
}
|
|
@ -260,24 +260,58 @@ final class HeraldTestConsoleController extends HeraldController {
|
|||
$query = PhabricatorApplicationTransactionQuery::newQueryForObject(
|
||||
$object);
|
||||
|
||||
$xactions = $query
|
||||
$query
|
||||
->withObjectPHIDs(array($object->getPHID()))
|
||||
->setViewer($viewer)
|
||||
->setLimit(100)
|
||||
->execute();
|
||||
->setViewer($viewer);
|
||||
|
||||
$xactions = new PhabricatorQueryIterator($query);
|
||||
|
||||
$applied = array();
|
||||
|
||||
// Pick the most recent group of transactions. This may not be exactly the
|
||||
// same as what Herald acted on: for example, we may select a single group
|
||||
// of transactions here which were really applied across two or more edits.
|
||||
// Since this is relatively rare and we show you what we picked, it's okay
|
||||
// that we just do roughly the right thing.
|
||||
$recent_id = null;
|
||||
$hard_limit = 1000;
|
||||
foreach ($xactions as $xaction) {
|
||||
if (!$xaction->shouldDisplayGroupWith($applied)) {
|
||||
|
||||
// If this transaction has Herald transcript metadata, it was applied by
|
||||
// Herald. Exclude it from the list because the Herald rule engine always
|
||||
// runs before Herald transactions apply, so there's no way that real
|
||||
// rules would have seen this transaction.
|
||||
$transcript_id = $xaction->getMetadataValue('herald:transcriptID');
|
||||
if ($transcript_id !== null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$group_id = $xaction->getTransactionGroupID();
|
||||
|
||||
// If this is the first transaction, save the group ID: we want to
|
||||
// select all transactions in the same group.
|
||||
if (!$applied) {
|
||||
$recent_id = $group_id;
|
||||
if ($recent_id === null) {
|
||||
// If the first transaction has no group ID, it is likely an older
|
||||
// transaction from before the introduction of group IDs. In this
|
||||
// case, select only the most recent transaction and bail out.
|
||||
$applied[] = $xaction;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If this transaction is from a different transaction group, we've
|
||||
// found all the transactions applied in the most recent group.
|
||||
if ($group_id !== $recent_id) {
|
||||
break;
|
||||
}
|
||||
|
||||
$applied[] = $xaction;
|
||||
|
||||
if (count($applied) > $hard_limit) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'This object ("%s") has more than %s transactions in its most '.
|
||||
'recent transaction group; this is too many.',
|
||||
$object->getPHID(),
|
||||
new PhutilNumber($hard_limit)));
|
||||
}
|
||||
}
|
||||
|
||||
return $applied;
|
||||
|
|
|
@ -337,7 +337,8 @@ final class ManiphestReportController extends ManiphestController {
|
|||
'the project recently, it is counted on the day it was '.
|
||||
'opened, not the day it was categorized. If a task was part '.
|
||||
'of this project in the past but no longer is, it is not '.
|
||||
'counted at all.');
|
||||
'counted at all. This table may not agree exactly with the chart '.
|
||||
'above.');
|
||||
$header = pht('Task Burn Rate for Project %s', $handle->renderLink());
|
||||
$caption = phutil_tag('p', array(), $inst);
|
||||
} else {
|
||||
|
@ -379,26 +380,29 @@ final class ManiphestReportController extends ManiphestController {
|
|||
|
||||
list($burn_x, $burn_y) = $this->buildSeries($data);
|
||||
|
||||
require_celerity_resource('d3');
|
||||
require_celerity_resource('phui-chart-css');
|
||||
if ($project_phid) {
|
||||
$projects = id(new PhabricatorProjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($project_phid))
|
||||
->execute();
|
||||
} else {
|
||||
$projects = array();
|
||||
}
|
||||
|
||||
Javelin::initBehavior('line-chart-legacy', array(
|
||||
'hardpoint' => $id,
|
||||
'x' => array(
|
||||
$burn_x,
|
||||
),
|
||||
'y' => array(
|
||||
$burn_y,
|
||||
),
|
||||
'xformat' => 'epoch',
|
||||
'yformat' => 'int',
|
||||
));
|
||||
$panel = id(new PhabricatorProjectBurndownChartEngine())
|
||||
->setViewer($viewer)
|
||||
->setProjects($projects)
|
||||
->buildChartPanel();
|
||||
|
||||
$box = id(new PHUIObjectBoxView())
|
||||
->setHeaderText(pht('Burnup Rate'))
|
||||
->appendChild($chart);
|
||||
$chart_panel = $panel->setName(pht('Burnup Rate'));
|
||||
|
||||
return array($filter, $box, $panel);
|
||||
$chart_view = id(new PhabricatorDashboardPanelRenderingEngine())
|
||||
->setViewer($viewer)
|
||||
->setPanel($chart_panel)
|
||||
->setParentPanelPHIDs(array())
|
||||
->renderPanel();
|
||||
|
||||
return array($filter, $chart_view, $panel);
|
||||
}
|
||||
|
||||
private function renderReportFilters(array $tokens, $has_window) {
|
||||
|
|
|
@ -31,7 +31,7 @@ final class ManiphestTaskOwnerTransaction
|
|||
}
|
||||
|
||||
public function getActionStrength() {
|
||||
return 1.2;
|
||||
return 120;
|
||||
}
|
||||
|
||||
public function getActionName() {
|
||||
|
|
|
@ -27,7 +27,7 @@ final class ManiphestTaskPriorityTransaction
|
|||
}
|
||||
|
||||
public function getActionStrength() {
|
||||
return 1.1;
|
||||
return 110;
|
||||
}
|
||||
|
||||
public function getActionName() {
|
||||
|
|
|
@ -22,7 +22,7 @@ final class ManiphestTaskStatusTransaction
|
|||
}
|
||||
|
||||
public function getActionStrength() {
|
||||
return 1.3;
|
||||
return 130;
|
||||
}
|
||||
|
||||
public function getActionName() {
|
||||
|
|
|
@ -14,7 +14,7 @@ final class ManiphestTaskTitleTransaction
|
|||
}
|
||||
|
||||
public function getActionStrength() {
|
||||
return 1.4;
|
||||
return 140;
|
||||
}
|
||||
|
||||
public function getActionName() {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorSelfHyperlinkEngineExtension
|
||||
extends PhutilRemarkupHyperlinkEngineExtension {
|
||||
extends PhabricatorRemarkupHyperlinkEngineExtension {
|
||||
|
||||
const LINKENGINEKEY = 'phabricator-self';
|
||||
|
||||
|
@ -15,15 +15,7 @@ final class PhabricatorSelfHyperlinkEngineExtension
|
|||
return;
|
||||
}
|
||||
|
||||
// Find links which point to resources on the Phabricator install itself.
|
||||
// We're going to try to enhance these.
|
||||
$self_links = array();
|
||||
foreach ($hyperlinks as $link) {
|
||||
$uri = $link->getURI();
|
||||
if (PhabricatorEnv::isSelfURI($uri)) {
|
||||
$self_links[] = $link;
|
||||
}
|
||||
}
|
||||
$self_links = $this->getSelfLinks($hyperlinks);
|
||||
|
||||
// For links in the form "/X123", we can reasonably guess that they are
|
||||
// fairly likely to be object names. Try to look them up.
|
||||
|
@ -53,11 +45,13 @@ final class PhabricatorSelfHyperlinkEngineExtension
|
|||
}
|
||||
|
||||
if ($object_map) {
|
||||
$handles = $viewer->loadHandles(mpull($object_map, 'getPHID'));
|
||||
$object_phids = mpull($object_map, 'getPHID');
|
||||
} else {
|
||||
$handles = array();
|
||||
$object_phids = array();
|
||||
}
|
||||
|
||||
$handles = $viewer->loadHandles($object_phids);
|
||||
|
||||
foreach ($object_names as $key => $object_name) {
|
||||
$object = idx($object_map, $object_name);
|
||||
if (!$object) {
|
||||
|
@ -83,6 +77,13 @@ final class PhabricatorSelfHyperlinkEngineExtension
|
|||
|
||||
unset($self_links[$key]);
|
||||
}
|
||||
|
||||
$key_mentioned = PhabricatorObjectRemarkupRule::KEY_MENTIONED_OBJECTS;
|
||||
$mentioned_phids = $engine->getTextMetadata($key_mentioned, array());
|
||||
foreach ($object_phids as $object_phid) {
|
||||
$mentioned_phids[$object_phid] = $object_phid;
|
||||
}
|
||||
$engine->setTextMetadata($key_mentioned, $mentioned_phids);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ final class PholioMockNameTransaction
|
|||
}
|
||||
|
||||
public function getActionStrength() {
|
||||
return 1.4;
|
||||
return 140;
|
||||
}
|
||||
|
||||
public function applyInternalEffects($object, $value) {
|
||||
|
|
|
@ -97,6 +97,10 @@ final class PhrictionEditController
|
|||
$content_text = $content->getContent();
|
||||
$is_draft_mode = ($document->getContent()->getVersion() != $max_version);
|
||||
|
||||
$default_view = $document->getViewPolicy();
|
||||
$default_edit = $document->getEditPolicy();
|
||||
$default_space = $document->getSpacePHID();
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
if ($is_new) {
|
||||
$save_as_draft = false;
|
||||
|
@ -122,6 +126,11 @@ final class PhrictionEditController
|
|||
|
||||
$xactions = array();
|
||||
|
||||
if ($is_new) {
|
||||
$xactions[] = id(new PhrictionTransaction())
|
||||
->setTransactionType(PhabricatorTransactions::TYPE_CREATE);
|
||||
}
|
||||
|
||||
$xactions[] = id(new PhrictionTransaction())
|
||||
->setTransactionType(PhrictionDocumentTitleTransaction::TRANSACTIONTYPE)
|
||||
->setNewValue($title);
|
||||
|
@ -130,13 +139,16 @@ final class PhrictionEditController
|
|||
->setNewValue($content_text);
|
||||
$xactions[] = id(new PhrictionTransaction())
|
||||
->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY)
|
||||
->setNewValue($v_view);
|
||||
->setNewValue($v_view)
|
||||
->setIsDefaultTransaction($is_new && ($v_view === $default_view));
|
||||
$xactions[] = id(new PhrictionTransaction())
|
||||
->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY)
|
||||
->setNewValue($v_edit);
|
||||
->setNewValue($v_edit)
|
||||
->setIsDefaultTransaction($is_new && ($v_edit === $default_edit));
|
||||
$xactions[] = id(new PhrictionTransaction())
|
||||
->setTransactionType(PhabricatorTransactions::TYPE_SPACE)
|
||||
->setNewValue($v_space);
|
||||
->setNewValue($v_space)
|
||||
->setIsDefaultTransaction($is_new && ($v_space === $default_space));
|
||||
$xactions[] = id(new PhrictionTransaction())
|
||||
->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS)
|
||||
->setNewValue(array('=' => $v_cc));
|
||||
|
|
|
@ -78,10 +78,13 @@ final class PhrictionDocument extends PhrictionDAO
|
|||
}
|
||||
|
||||
if ($parent_doc) {
|
||||
$space_phid = PhabricatorSpacesNamespaceQuery::getObjectSpacePHID(
|
||||
$parent_doc);
|
||||
|
||||
$document
|
||||
->setViewPolicy($parent_doc->getViewPolicy())
|
||||
->setEditPolicy($parent_doc->getEditPolicy())
|
||||
->setSpacePHID($parent_doc->getSpacePHID());
|
||||
->setSpacePHID($space_phid);
|
||||
} else {
|
||||
$default_view_policy = PhabricatorPolicies::getMostOpenPolicy();
|
||||
$document
|
||||
|
|
|
@ -19,7 +19,7 @@ final class PhrictionDocumentDeleteTransaction
|
|||
}
|
||||
|
||||
public function getActionStrength() {
|
||||
return 1.5;
|
||||
return 150;
|
||||
}
|
||||
|
||||
public function getActionName() {
|
||||
|
|
|
@ -32,7 +32,7 @@ abstract class PhrictionDocumentEditTransaction
|
|||
}
|
||||
|
||||
public function getActionStrength() {
|
||||
return 1.3;
|
||||
return 130;
|
||||
}
|
||||
|
||||
public function getActionName() {
|
||||
|
|
|
@ -37,7 +37,7 @@ final class PhrictionDocumentMoveToTransaction
|
|||
}
|
||||
|
||||
public function getActionStrength() {
|
||||
return 1.0;
|
||||
return 100;
|
||||
}
|
||||
|
||||
public function getActionName() {
|
||||
|
|
|
@ -21,7 +21,7 @@ final class PhrictionDocumentTitleTransaction
|
|||
}
|
||||
|
||||
public function getActionStrength() {
|
||||
return 1.4;
|
||||
return 140;
|
||||
}
|
||||
|
||||
public function getActionName() {
|
||||
|
|
|
@ -90,6 +90,29 @@ final class PhabricatorPolicyFilter extends Phobject {
|
|||
PhabricatorUser $user,
|
||||
PhabricatorPolicyInterface $object) {
|
||||
|
||||
$capabilities = self::getRequiredInteractCapabilities($object);
|
||||
|
||||
foreach ($capabilities as $capability) {
|
||||
if (!self::hasCapability($user, $object, $capability)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function requireCanInteract(
|
||||
PhabricatorUser $user,
|
||||
PhabricatorPolicyInterface $object) {
|
||||
|
||||
$capabilities = self::getRequiredInteractCapabilities($object);
|
||||
foreach ($capabilities as $capability) {
|
||||
self::requireCapability($user, $object, $capability);
|
||||
}
|
||||
}
|
||||
|
||||
private static function getRequiredInteractCapabilities(
|
||||
PhabricatorPolicyInterface $object) {
|
||||
$capabilities = $object->getCapabilities();
|
||||
$capabilities = array_fuse($capabilities);
|
||||
|
||||
|
@ -107,13 +130,7 @@ final class PhabricatorPolicyFilter extends Phobject {
|
|||
$require[] = $can_interact;
|
||||
}
|
||||
|
||||
foreach ($require as $capability) {
|
||||
if (!self::hasCapability($user, $object, $capability)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return $require;
|
||||
}
|
||||
|
||||
public function setViewer(PhabricatorUser $user) {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue