1
0
Fork 0
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:
epriestley 2019-05-28 10:27:40 -07:00
commit d9b41d3a0f
129 changed files with 4322 additions and 1153 deletions

View file

@ -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',
),

View file

@ -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.

View file

@ -0,0 +1 @@
DROP TABLE IF EXISTS {$NAMESPACE}_search.search_documentfield;

View file

@ -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',

View file

@ -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() {

View file

@ -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;
}

View file

@ -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;
}

View file

@ -50,7 +50,7 @@ final class PhabricatorAuditTransaction
switch ($type) {
case self::TYPE_COMMIT:
return 3.0;
return 300;
}
return parent::getActionStrength();

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -368,6 +368,10 @@ abstract class PhabricatorDaemonManagementWorkflow
'class' => 'PhabricatorTriggerDaemon',
'label' => 'trigger',
),
array(
'class' => 'PhabricatorFactDaemon',
'label' => 'fact',
),
array(
'class' => 'PhabricatorTaskmasterDaemon',
'label' => 'task',

View file

@ -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;
}
}

View file

@ -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?
*

View file

@ -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,

View file

@ -0,0 +1,12 @@
<?php
final class PhabricatorDashboardChartPanelChartTransaction
extends PhabricatorDashboardPanelPropertyTransaction {
const TRANSACTIONTYPE = 'chart.chartKey';
protected function getPropertyKey() {
return 'chartKey';
}
}

View file

@ -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) {

View file

@ -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();
}

View file

@ -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())

View file

@ -130,7 +130,7 @@ final class DifferentialTransaction
public function getActionStrength() {
switch ($this->getTransactionType()) {
case self::TYPE_ACTION:
return 3;
return 300;
}
return parent::getActionStrength();

View file

@ -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.'));
}
}

View file

@ -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(),
);
}
}

View file

@ -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;

View file

@ -40,7 +40,7 @@ abstract class DifferentialRevisionActionTransaction
}
public function getActionStrength() {
return 3;
return 300;
}
public function getRevisionActionOrderVector() {

View file

@ -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() {

View file

@ -26,7 +26,7 @@ final class DifferentialRevisionWrongBuildsTransaction
}
public function getActionStrength() {
return 4;
return 400;
}
public function getTitle() {

View file

@ -22,7 +22,7 @@ final class DifferentialRevisionWrongStateTransaction
}
public function getActionStrength() {
return 4;
return 400;
}
public function getTitle() {

View file

@ -40,6 +40,7 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication {
new DiffusionCommitRemarkupRule(),
new DiffusionRepositoryRemarkupRule(),
new DiffusionRepositoryByIDRemarkupRule(),
new DiffusionSourceLinkRemarkupRule(),
);
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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() {

View file

@ -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);
}

View file

@ -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');
}

View 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);
}
}

View file

@ -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);
}

View file

@ -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(

View file

@ -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;

View file

@ -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)

View file

@ -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();

View file

@ -26,6 +26,7 @@ final class DoorkeeperTagsController extends PhabricatorController {
$refs = id(new DoorkeeperImportEngine())
->setViewer($viewer)
->setRefs($refs)
->setTimeout(15)
->execute();
$results = array();

View file

@ -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;

View 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;
}
}

View file

@ -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));
}
}
}

View file

@ -0,0 +1,7 @@
<?php
interface DoorkeeperRemarkupURIInterface {
public function getDoorkeeperURIRef(PhutilURI $uri);
}

View file

@ -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],
),
),
));
}
}

View file

@ -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,
),
),
));
}
}

View file

@ -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());
}
}

View file

@ -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);

View file

@ -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,

View file

@ -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',
),
);

View file

@ -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;
}
}

View file

@ -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);
}
}

View 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);
}

View 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;
}
}

View file

@ -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;
}
}

View file

@ -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(

View file

@ -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);
}
}

View file

@ -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(),
);
}
}

View 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;
}
}

View file

@ -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));
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View 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;
}
}

View 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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View 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;
}
}

View file

@ -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;
}
}

View file

@ -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());
}
}

View file

@ -15,7 +15,7 @@ final class PhabricatorFactDaemon extends PhabricatorDaemon {
}
$this->log(pht('Zzz...'));
$this->sleep(60 * 5);
$this->sleep(15);
}
}

View 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));
}
}

View 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;
}
}

View file

@ -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;
}
}

View file

@ -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() {

View file

@ -31,6 +31,10 @@ final class PhabricatorFeedApplication extends PhabricatorApplication {
'/feed/' => array(
'(?P<id>\d+)/' => 'PhabricatorFeedDetailController',
'(?:query/(?P<queryKey>[^/]+)/)?' => 'PhabricatorFeedListController',
'transactions/' => array(
$this->getQueryRoutePattern()
=> 'PhabricatorFeedTransactionListController',
),
),
);
}

View file

@ -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 {}

View file

@ -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();
}
}

View file

@ -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();
}
}

View 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));
}
}

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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) {

View file

@ -31,7 +31,7 @@ final class ManiphestTaskOwnerTransaction
}
public function getActionStrength() {
return 1.2;
return 120;
}
public function getActionName() {

View file

@ -27,7 +27,7 @@ final class ManiphestTaskPriorityTransaction
}
public function getActionStrength() {
return 1.1;
return 110;
}
public function getActionName() {

View file

@ -22,7 +22,7 @@ final class ManiphestTaskStatusTransaction
}
public function getActionStrength() {
return 1.3;
return 130;
}
public function getActionName() {

View file

@ -14,7 +14,7 @@ final class ManiphestTaskTitleTransaction
}
public function getActionStrength() {
return 1.4;
return 140;
}
public function getActionName() {

View file

@ -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);
}
}

View file

@ -10,7 +10,7 @@ final class PholioMockNameTransaction
}
public function getActionStrength() {
return 1.4;
return 140;
}
public function applyInternalEffects($object, $value) {

View file

@ -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));

View file

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

View file

@ -19,7 +19,7 @@ final class PhrictionDocumentDeleteTransaction
}
public function getActionStrength() {
return 1.5;
return 150;
}
public function getActionName() {

View file

@ -32,7 +32,7 @@ abstract class PhrictionDocumentEditTransaction
}
public function getActionStrength() {
return 1.3;
return 130;
}
public function getActionName() {

View file

@ -37,7 +37,7 @@ final class PhrictionDocumentMoveToTransaction
}
public function getActionStrength() {
return 1.0;
return 100;
}
public function getActionName() {

View file

@ -21,7 +21,7 @@ final class PhrictionDocumentTitleTransaction
}
public function getActionStrength() {
return 1.4;
return 140;
}
public function getActionName() {

View file

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