diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 64d3ff99f3..446ce87a89 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,10 +7,10 @@ */ return array( 'names' => array( - 'core.pkg.css' => '33799ec4', + 'core.pkg.css' => 'aced76a5', 'core.pkg.js' => 'a590b451', 'darkconsole.pkg.js' => 'e7393ebb', - 'differential.pkg.css' => '9451634c', + 'differential.pkg.css' => '2de124c9', 'differential.pkg.js' => 'ebef29b1', 'diffusion.pkg.css' => '385e85b3', 'diffusion.pkg.js' => '0115b37c', @@ -51,7 +51,7 @@ return array( 'rsrc/css/application/conpherence/notification.css' => '6cdcc253', 'rsrc/css/application/conpherence/transaction.css' => '85d0974c', 'rsrc/css/application/conpherence/update.css' => 'faf6be09', - 'rsrc/css/application/conpherence/widget-pane.css' => '419fd50c', + 'rsrc/css/application/conpherence/widget-pane.css' => '775eaaba', 'rsrc/css/application/contentsource/content-source-view.css' => '4b8b05d4', 'rsrc/css/application/countdown/timer.css' => 'e7544472', 'rsrc/css/application/daemon/bulk-job.css' => 'df9c1d4a', @@ -60,7 +60,7 @@ return array( 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', 'rsrc/css/application/differential/changeset-view.css' => 'b6b0d1bb', 'rsrc/css/application/differential/core.css' => '7ac3cabc', - 'rsrc/css/application/differential/phui-inline-comment.css' => '9fadd6b8', + 'rsrc/css/application/differential/phui-inline-comment.css' => '0fdb3667', 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', 'rsrc/css/application/differential/revision-history.css' => '0e8eb855', 'rsrc/css/application/differential/revision-list.css' => 'f3c47d33', @@ -93,18 +93,18 @@ return array( 'rsrc/css/application/policy/policy-edit.css' => '815c66f7', 'rsrc/css/application/policy/policy-transaction-detail.css' => '82100a43', 'rsrc/css/application/policy/policy.css' => '957ea14c', - 'rsrc/css/application/ponder/ponder-view.css' => '6a399881', + 'rsrc/css/application/ponder/ponder-view.css' => 'bef48f86', 'rsrc/css/application/projects/project-icon.css' => '4e3eaa5a', 'rsrc/css/application/releeph/releeph-core.css' => '9b3c5733', 'rsrc/css/application/releeph/releeph-preview-branch.css' => 'b7a6f4a5', 'rsrc/css/application/releeph/releeph-request-differential-create-dialog.css' => '8d8b92cd', 'rsrc/css/application/releeph/releeph-request-typeahead.css' => '667a48ae', 'rsrc/css/application/search/search-results.css' => '7dea472c', - 'rsrc/css/application/slowvote/slowvote.css' => '7c27f0f9', + 'rsrc/css/application/slowvote/slowvote.css' => '475b4bd2', 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', 'rsrc/css/core/core.css' => 'a76cefc9', - 'rsrc/css/core/remarkup.css' => 'de13dcbe', + 'rsrc/css/core/remarkup.css' => '73fc4395', 'rsrc/css/core/syntax.css' => '9fd11da8', 'rsrc/css/core/z-index.css' => '57ddcaa2', 'rsrc/css/diviner/diviner-shared.css' => '5a337049', @@ -148,7 +148,7 @@ return array( 'rsrc/css/phui/phui-tag-view.css' => '402691cc', 'rsrc/css/phui/phui-text.css' => 'cf019f54', 'rsrc/css/phui/phui-timeline-view.css' => 'f1bccf73', - 'rsrc/css/phui/phui-two-column-view.css' => 'add0a7d1', + 'rsrc/css/phui/phui-two-column-view.css' => '39ecafb1', 'rsrc/css/phui/phui-workboard-view.css' => '6704d68d', 'rsrc/css/phui/phui-workpanel-view.css' => 'adec7699', 'rsrc/css/sprite-login.css' => '1ebb9bf9', @@ -346,7 +346,7 @@ return array( 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '01774ab2', 'rsrc/js/application/conpherence/behavior-drag-and-drop-photo.js' => 'cf86d16a', 'rsrc/js/application/conpherence/behavior-durable-column.js' => 'c72aa091', - 'rsrc/js/application/conpherence/behavior-menu.js' => 'd3782c93', + 'rsrc/js/application/conpherence/behavior-menu.js' => '1d45c74d', 'rsrc/js/application/conpherence/behavior-pontificate.js' => '21ba5861', 'rsrc/js/application/conpherence/behavior-quicksand-blacklist.js' => '7927a7d3', 'rsrc/js/application/conpherence/behavior-widget-pane.js' => 'a8458711', @@ -514,7 +514,7 @@ return array( 'conpherence-thread-manager' => '01774ab2', 'conpherence-transaction-css' => '85d0974c', 'conpherence-update-css' => 'faf6be09', - 'conpherence-widget-pane-css' => '419fd50c', + 'conpherence-widget-pane-css' => '775eaaba', 'differential-changeset-view-css' => 'b6b0d1bb', 'differential-core-view-css' => '7ac3cabc', 'differential-inline-comment-editor' => 'd4c87bf4', @@ -552,7 +552,7 @@ return array( 'javelin-behavior-choose-control' => '6153c708', 'javelin-behavior-config-reorder-fields' => 'b6993408', 'javelin-behavior-conpherence-drag-and-drop-photo' => 'cf86d16a', - 'javelin-behavior-conpherence-menu' => 'd3782c93', + 'javelin-behavior-conpherence-menu' => '1d45c74d', 'javelin-behavior-conpherence-pontificate' => '21ba5861', 'javelin-behavior-conpherence-widget-pane' => 'a8458711', 'javelin-behavior-countdown-timer' => 'e4cc26b3', @@ -737,11 +737,11 @@ return array( 'phabricator-object-selector-css' => '85ee8ce6', 'phabricator-phtize' => 'd254d646', 'phabricator-prefab' => '6920d200', - 'phabricator-remarkup-css' => 'de13dcbe', + 'phabricator-remarkup-css' => '73fc4395', 'phabricator-search-results-css' => '7dea472c', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-side-menu-view-css' => 'bec2458e', - 'phabricator-slowvote-css' => '7c27f0f9', + 'phabricator-slowvote-css' => '475b4bd2', 'phabricator-source-code-view-css' => '5e0178de', 'phabricator-standard-page-view' => '4d176b67', 'phabricator-textareautils' => '5c93c52c', @@ -788,7 +788,7 @@ return array( 'phui-image-mask-css' => '5a8b09c8', 'phui-info-panel-css' => '27ea50a1', 'phui-info-view-css' => '5b16bac6', - 'phui-inline-comment-view-css' => '9fadd6b8', + 'phui-inline-comment-view-css' => '0fdb3667', 'phui-list-view-css' => '125599df', 'phui-object-box-css' => '407eaf5a', 'phui-object-item-list-view-css' => '36ce366c', @@ -802,7 +802,7 @@ return array( 'phui-text-css' => 'cf019f54', 'phui-theme-css' => '6b451f24', 'phui-timeline-view-css' => 'f1bccf73', - 'phui-two-column-view-css' => 'add0a7d1', + 'phui-two-column-view-css' => '39ecafb1', 'phui-workboard-view-css' => '6704d68d', 'phui-workpanel-view-css' => 'adec7699', 'phuix-action-list-view' => 'b5c256b8', @@ -811,7 +811,7 @@ return array( 'policy-css' => '957ea14c', 'policy-edit-css' => '815c66f7', 'policy-transaction-detail-css' => '82100a43', - 'ponder-view-css' => '6a399881', + 'ponder-view-css' => 'bef48f86', 'project-icon-css' => '4e3eaa5a', 'raphael-core' => '51ee6b43', 'raphael-g' => '40dde778', @@ -955,6 +955,20 @@ return array( 'javelin-dom', 'javelin-vector', ), + '1d45c74d' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-workflow', + 'javelin-behavior-device', + 'javelin-history', + 'javelin-vector', + 'javelin-scrollbar', + 'phabricator-title', + 'phabricator-shaped-request', + 'conpherence-thread-manager', + ), '1def2711' => array( 'javelin-install', 'javelin-dom', @@ -1798,20 +1812,6 @@ return array( 'd254d646' => array( 'javelin-util', ), - 'd3782c93' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-workflow', - 'javelin-behavior-device', - 'javelin-history', - 'javelin-vector', - 'javelin-scrollbar', - 'phabricator-title', - 'phabricator-shaped-request', - 'conpherence-thread-manager', - ), 'd4505101' => array( 'javelin-stratcom', 'javelin-install', diff --git a/resources/sql/autopatches/20150814.harbormater.artifact.phid.sql b/resources/sql/autopatches/20150814.harbormater.artifact.phid.sql new file mode 100644 index 0000000000..1f03268ff0 --- /dev/null +++ b/resources/sql/autopatches/20150814.harbormater.artifact.phid.sql @@ -0,0 +1,5 @@ +ALTER TABLE {$NAMESPACE}_harbormaster.harbormaster_buildartifact + ADD phid VARBINARY(64) NOT NULL AFTER id; + +UPDATE {$NAMESPACE}_harbormaster.harbormaster_buildartifact + SET phid = CONCAT('PHID-HMBA-', id) WHERE phid = ''; diff --git a/resources/sql/autopatches/20150815.owners.status.1.sql b/resources/sql/autopatches/20150815.owners.status.1.sql new file mode 100644 index 0000000000..591e9efeef --- /dev/null +++ b/resources/sql/autopatches/20150815.owners.status.1.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_owners.owners_package + ADD status VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20150815.owners.status.2.sql b/resources/sql/autopatches/20150815.owners.status.2.sql new file mode 100644 index 0000000000..8f8c05af06 --- /dev/null +++ b/resources/sql/autopatches/20150815.owners.status.2.sql @@ -0,0 +1,2 @@ +UPDATE {$NAMESPACE}_owners.owners_package + SET status = 'active' WHERE status = ''; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 3bb3ad8c5b..0942ad73d6 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -232,7 +232,6 @@ phutil_register_library_map(array( 'ConpherenceDAO' => 'applications/conpherence/storage/ConpherenceDAO.php', 'ConpherenceDurableColumnView' => 'applications/conpherence/view/ConpherenceDurableColumnView.php', 'ConpherenceEditor' => 'applications/conpherence/editor/ConpherenceEditor.php', - 'ConpherenceFileWidgetView' => 'applications/conpherence/view/ConpherenceFileWidgetView.php', 'ConpherenceFormDragAndDropUploadControl' => 'applications/conpherence/view/ConpherenceFormDragAndDropUploadControl.php', 'ConpherenceFulltextQuery' => 'applications/conpherence/query/ConpherenceFulltextQuery.php', 'ConpherenceHovercardEventListener' => 'applications/conpherence/events/ConpherenceHovercardEventListener.php', @@ -287,6 +286,7 @@ phutil_register_library_map(array( 'DarkConsolePlugin' => 'applications/console/plugin/DarkConsolePlugin.php', 'DarkConsoleRequestPlugin' => 'applications/console/plugin/DarkConsoleRequestPlugin.php', 'DarkConsoleServicesPlugin' => 'applications/console/plugin/DarkConsoleServicesPlugin.php', + 'DarkConsoleStartupPlugin' => 'applications/console/plugin/DarkConsoleStartupPlugin.php', 'DarkConsoleXHProfPlugin' => 'applications/console/plugin/DarkConsoleXHProfPlugin.php', 'DarkConsoleXHProfPluginAPI' => 'applications/console/plugin/xhprof/DarkConsoleXHProfPluginAPI.php', 'DatabaseConfigurationProvider' => 'infrastructure/storage/configuration/DatabaseConfigurationProvider.php', @@ -367,7 +367,6 @@ phutil_register_library_map(array( 'DifferentialDiffQuery' => 'applications/differential/query/DifferentialDiffQuery.php', 'DifferentialDiffRepositoryHeraldField' => 'applications/differential/herald/DifferentialDiffRepositoryHeraldField.php', 'DifferentialDiffRepositoryProjectsHeraldField' => 'applications/differential/herald/DifferentialDiffRepositoryProjectsHeraldField.php', - 'DifferentialDiffTableOfContentsView' => 'applications/differential/view/DifferentialDiffTableOfContentsView.php', 'DifferentialDiffTestCase' => 'applications/differential/storage/__tests__/DifferentialDiffTestCase.php', 'DifferentialDiffTransaction' => 'applications/differential/storage/DifferentialDiffTransaction.php', 'DifferentialDiffTransactionQuery' => 'applications/differential/query/DifferentialDiffTransactionQuery.php', @@ -519,7 +518,6 @@ phutil_register_library_map(array( 'DiffusionCommitAutocloseHeraldField' => 'applications/diffusion/herald/DiffusionCommitAutocloseHeraldField.php', 'DiffusionCommitBranchesController' => 'applications/diffusion/controller/DiffusionCommitBranchesController.php', 'DiffusionCommitBranchesHeraldField' => 'applications/diffusion/herald/DiffusionCommitBranchesHeraldField.php', - 'DiffusionCommitChangeTableView' => 'applications/diffusion/view/DiffusionCommitChangeTableView.php', 'DiffusionCommitCommitterHeraldField' => 'applications/diffusion/herald/DiffusionCommitCommitterHeraldField.php', 'DiffusionCommitController' => 'applications/diffusion/controller/DiffusionCommitController.php', 'DiffusionCommitDiffContentAddedHeraldField' => 'applications/diffusion/herald/DiffusionCommitDiffContentAddedHeraldField.php', @@ -915,12 +913,14 @@ phutil_register_library_map(array( 'FundSchemaSpec' => 'applications/fund/storage/FundSchemaSpec.php', 'HarbormasterArcLintBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterArcLintBuildStepImplementation.php', 'HarbormasterArcUnitBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterArcUnitBuildStepImplementation.php', + 'HarbormasterArtifact' => 'applications/harbormaster/artifact/HarbormasterArtifact.php', 'HarbormasterAutotargetsTestCase' => 'applications/harbormaster/__tests__/HarbormasterAutotargetsTestCase.php', 'HarbormasterBuild' => 'applications/harbormaster/storage/build/HarbormasterBuild.php', 'HarbormasterBuildAbortedException' => 'applications/harbormaster/exception/HarbormasterBuildAbortedException.php', 'HarbormasterBuildActionController' => 'applications/harbormaster/controller/HarbormasterBuildActionController.php', 'HarbormasterBuildArcanistAutoplan' => 'applications/harbormaster/autoplan/HarbormasterBuildArcanistAutoplan.php', 'HarbormasterBuildArtifact' => 'applications/harbormaster/storage/build/HarbormasterBuildArtifact.php', + 'HarbormasterBuildArtifactPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildArtifactPHIDType.php', 'HarbormasterBuildArtifactQuery' => 'applications/harbormaster/query/HarbormasterBuildArtifactQuery.php', 'HarbormasterBuildAutoplan' => 'applications/harbormaster/autoplan/HarbormasterBuildAutoplan.php', 'HarbormasterBuildCommand' => 'applications/harbormaster/storage/HarbormasterBuildCommand.php', @@ -980,9 +980,12 @@ phutil_register_library_map(array( 'HarbormasterCommandBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterCommandBuildStepImplementation.php', 'HarbormasterConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterConduitAPIMethod.php', 'HarbormasterController' => 'applications/harbormaster/controller/HarbormasterController.php', + 'HarbormasterCreateArtifactConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterCreateArtifactConduitAPIMethod.php', 'HarbormasterDAO' => 'applications/harbormaster/storage/HarbormasterDAO.php', 'HarbormasterExternalBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterExternalBuildStepGroup.php', + 'HarbormasterFileArtifact' => 'applications/harbormaster/artifact/HarbormasterFileArtifact.php', 'HarbormasterHTTPRequestBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php', + 'HarbormasterHostArtifact' => 'applications/harbormaster/artifact/HarbormasterHostArtifact.php', 'HarbormasterLeaseHostBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterLeaseHostBuildStepImplementation.php', 'HarbormasterLintMessagesController' => 'applications/harbormaster/controller/HarbormasterLintMessagesController.php', 'HarbormasterLintPropertyView' => 'applications/harbormaster/view/HarbormasterLintPropertyView.php', @@ -1018,6 +1021,7 @@ phutil_register_library_map(array( 'HarbormasterTestBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterTestBuildStepGroup.php', 'HarbormasterThrowExceptionBuildStep' => 'applications/harbormaster/step/HarbormasterThrowExceptionBuildStep.php', 'HarbormasterUIEventListener' => 'applications/harbormaster/event/HarbormasterUIEventListener.php', + 'HarbormasterURIArtifact' => 'applications/harbormaster/artifact/HarbormasterURIArtifact.php', 'HarbormasterUnitMessagesController' => 'applications/harbormaster/controller/HarbormasterUnitMessagesController.php', 'HarbormasterUnitPropertyView' => 'applications/harbormaster/view/HarbormasterUnitPropertyView.php', 'HarbormasterUploadArtifactBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php', @@ -1356,6 +1360,8 @@ phutil_register_library_map(array( 'PHUIDiffInlineCommentView' => 'infrastructure/diff/view/PHUIDiffInlineCommentView.php', 'PHUIDiffOneUpInlineCommentRowScaffold' => 'infrastructure/diff/view/PHUIDiffOneUpInlineCommentRowScaffold.php', 'PHUIDiffRevealIconView' => 'infrastructure/diff/view/PHUIDiffRevealIconView.php', + 'PHUIDiffTableOfContentsItemView' => 'infrastructure/diff/view/PHUIDiffTableOfContentsItemView.php', + 'PHUIDiffTableOfContentsListView' => 'infrastructure/diff/view/PHUIDiffTableOfContentsListView.php', 'PHUIDiffTwoUpInlineCommentRowScaffold' => 'infrastructure/diff/view/PHUIDiffTwoUpInlineCommentRowScaffold.php', 'PHUIDocumentExample' => 'applications/uiexample/examples/PHUIDocumentExample.php', 'PHUIDocumentView' => 'view/phui/PHUIDocumentView.php', @@ -2241,6 +2247,9 @@ phutil_register_library_map(array( 'PhabricatorMacroTransactionComment' => 'applications/macro/storage/PhabricatorMacroTransactionComment.php', 'PhabricatorMacroTransactionQuery' => 'applications/macro/query/PhabricatorMacroTransactionQuery.php', 'PhabricatorMacroViewController' => 'applications/macro/controller/PhabricatorMacroViewController.php', + 'PhabricatorMailEmailHeraldField' => 'applications/metamta/herald/PhabricatorMailEmailHeraldField.php', + 'PhabricatorMailEmailHeraldFieldGroup' => 'applications/metamta/herald/PhabricatorMailEmailHeraldFieldGroup.php', + 'PhabricatorMailEmailSubjectHeraldField' => 'applications/metamta/herald/PhabricatorMailEmailSubjectHeraldField.php', 'PhabricatorMailImplementationAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationAdapter.php', 'PhabricatorMailImplementationAmazonSESAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationAmazonSESAdapter.php', 'PhabricatorMailImplementationMailgunAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationMailgunAdapter.php', @@ -2257,10 +2266,15 @@ phutil_register_library_map(array( 'PhabricatorMailManagementShowOutboundWorkflow' => 'applications/metamta/management/PhabricatorMailManagementShowOutboundWorkflow.php', 'PhabricatorMailManagementVolumeWorkflow' => 'applications/metamta/management/PhabricatorMailManagementVolumeWorkflow.php', 'PhabricatorMailManagementWorkflow' => 'applications/metamta/management/PhabricatorMailManagementWorkflow.php', + 'PhabricatorMailOutboundMailHeraldAdapter' => 'applications/metamta/herald/PhabricatorMailOutboundMailHeraldAdapter.php', + 'PhabricatorMailOutboundRoutingHeraldAction' => 'applications/metamta/herald/PhabricatorMailOutboundRoutingHeraldAction.php', + 'PhabricatorMailOutboundRoutingSelfEmailHeraldAction' => 'applications/metamta/herald/PhabricatorMailOutboundRoutingSelfEmailHeraldAction.php', + 'PhabricatorMailOutboundRoutingSelfNotificationHeraldAction' => 'applications/metamta/herald/PhabricatorMailOutboundRoutingSelfNotificationHeraldAction.php', 'PhabricatorMailOutboundStatus' => 'applications/metamta/constants/PhabricatorMailOutboundStatus.php', 'PhabricatorMailReceiver' => 'applications/metamta/receiver/PhabricatorMailReceiver.php', 'PhabricatorMailReceiverTestCase' => 'applications/metamta/receiver/__tests__/PhabricatorMailReceiverTestCase.php', 'PhabricatorMailReplyHandler' => 'applications/metamta/replyhandler/PhabricatorMailReplyHandler.php', + 'PhabricatorMailRoutingRule' => 'applications/metamta/constants/PhabricatorMailRoutingRule.php', 'PhabricatorMailSetupCheck' => 'applications/config/check/PhabricatorMailSetupCheck.php', 'PhabricatorMailTarget' => 'applications/metamta/replyhandler/PhabricatorMailTarget.php', 'PhabricatorMailgunConfigOptions' => 'applications/config/option/PhabricatorMailgunConfigOptions.php', @@ -3392,8 +3406,10 @@ phutil_register_library_map(array( 'PonderAnswerEditor' => 'applications/ponder/editor/PonderAnswerEditor.php', 'PonderAnswerHasVotingUserEdgeType' => 'applications/ponder/edge/PonderAnswerHasVotingUserEdgeType.php', 'PonderAnswerHistoryController' => 'applications/ponder/controller/PonderAnswerHistoryController.php', + 'PonderAnswerMailReceiver' => 'applications/ponder/mail/PonderAnswerMailReceiver.php', 'PonderAnswerPHIDType' => 'applications/ponder/phid/PonderAnswerPHIDType.php', 'PonderAnswerQuery' => 'applications/ponder/query/PonderAnswerQuery.php', + 'PonderAnswerReplyHandler' => 'applications/ponder/mail/PonderAnswerReplyHandler.php', 'PonderAnswerSaveController' => 'applications/ponder/controller/PonderAnswerSaveController.php', 'PonderAnswerStatus' => 'applications/ponder/constants/PonderAnswerStatus.php', 'PonderAnswerTransaction' => 'applications/ponder/storage/PonderAnswerTransaction.php', @@ -3428,7 +3444,6 @@ phutil_register_library_map(array( 'PonderRemarkupRule' => 'applications/ponder/remarkup/PonderRemarkupRule.php', 'PonderSchemaSpec' => 'applications/ponder/storage/PonderSchemaSpec.php', 'PonderSearchIndexer' => 'applications/ponder/search/PonderSearchIndexer.php', - 'PonderTransactionFeedStory' => 'applications/ponder/feed/PonderTransactionFeedStory.php', 'PonderVotableInterface' => 'applications/ponder/storage/PonderVotableInterface.php', 'PonderVote' => 'applications/ponder/constants/PonderVote.php', 'PonderVoteEditor' => 'applications/ponder/editor/PonderVoteEditor.php', @@ -3838,7 +3853,6 @@ phutil_register_library_map(array( 'ConpherenceDAO' => 'PhabricatorLiskDAO', 'ConpherenceDurableColumnView' => 'AphrontTagView', 'ConpherenceEditor' => 'PhabricatorApplicationTransactionEditor', - 'ConpherenceFileWidgetView' => 'ConpherenceWidgetView', 'ConpherenceFormDragAndDropUploadControl' => 'AphrontFormControl', 'ConpherenceFulltextQuery' => 'PhabricatorOffsetPagedQuery', 'ConpherenceHovercardEventListener' => 'PhabricatorEventListener', @@ -3899,6 +3913,7 @@ phutil_register_library_map(array( 'DarkConsolePlugin' => 'Phobject', 'DarkConsoleRequestPlugin' => 'DarkConsolePlugin', 'DarkConsoleServicesPlugin' => 'DarkConsolePlugin', + 'DarkConsoleStartupPlugin' => 'DarkConsolePlugin', 'DarkConsoleXHProfPlugin' => 'DarkConsolePlugin', 'DarkConsoleXHProfPluginAPI' => 'Phobject', 'DefaultDatabaseConfigurationProvider' => array( @@ -3990,7 +4005,6 @@ phutil_register_library_map(array( 'DifferentialDiffQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'DifferentialDiffRepositoryHeraldField' => 'DifferentialDiffHeraldField', 'DifferentialDiffRepositoryProjectsHeraldField' => 'DifferentialDiffHeraldField', - 'DifferentialDiffTableOfContentsView' => 'AphrontView', 'DifferentialDiffTestCase' => 'PhutilTestCase', 'DifferentialDiffTransaction' => 'PhabricatorApplicationTransaction', 'DifferentialDiffTransactionQuery' => 'PhabricatorApplicationTransactionQuery', @@ -4162,7 +4176,6 @@ phutil_register_library_map(array( 'DiffusionCommitAutocloseHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitBranchesController' => 'DiffusionController', 'DiffusionCommitBranchesHeraldField' => 'DiffusionCommitHeraldField', - 'DiffusionCommitChangeTableView' => 'DiffusionView', 'DiffusionCommitCommitterHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitController' => 'DiffusionController', 'DiffusionCommitDiffContentAddedHeraldField' => 'DiffusionCommitHeraldField', @@ -4603,6 +4616,7 @@ phutil_register_library_map(array( 'FundSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'HarbormasterArcLintBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterArcUnitBuildStepImplementation' => 'HarbormasterBuildStepImplementation', + 'HarbormasterArtifact' => 'Phobject', 'HarbormasterAutotargetsTestCase' => 'PhabricatorTestCase', 'HarbormasterBuild' => array( 'HarbormasterDAO', @@ -4616,6 +4630,7 @@ phutil_register_library_map(array( 'HarbormasterDAO', 'PhabricatorPolicyInterface', ), + 'HarbormasterBuildArtifactPHIDType' => 'PhabricatorPHIDType', 'HarbormasterBuildArtifactQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'HarbormasterBuildAutoplan' => 'Phobject', 'HarbormasterBuildCommand' => 'HarbormasterDAO', @@ -4700,9 +4715,12 @@ phutil_register_library_map(array( 'HarbormasterCommandBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterConduitAPIMethod' => 'ConduitAPIMethod', 'HarbormasterController' => 'PhabricatorController', + 'HarbormasterCreateArtifactConduitAPIMethod' => 'HarbormasterConduitAPIMethod', 'HarbormasterDAO' => 'PhabricatorLiskDAO', 'HarbormasterExternalBuildStepGroup' => 'HarbormasterBuildStepGroup', + 'HarbormasterFileArtifact' => 'HarbormasterArtifact', 'HarbormasterHTTPRequestBuildStepImplementation' => 'HarbormasterBuildStepImplementation', + 'HarbormasterHostArtifact' => 'HarbormasterArtifact', 'HarbormasterLeaseHostBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterLintMessagesController' => 'HarbormasterController', 'HarbormasterLintPropertyView' => 'AphrontView', @@ -4738,6 +4756,7 @@ phutil_register_library_map(array( 'HarbormasterTestBuildStepGroup' => 'HarbormasterBuildStepGroup', 'HarbormasterThrowExceptionBuildStep' => 'HarbormasterBuildStepImplementation', 'HarbormasterUIEventListener' => 'PhabricatorEventListener', + 'HarbormasterURIArtifact' => 'HarbormasterArtifact', 'HarbormasterUnitMessagesController' => 'HarbormasterController', 'HarbormasterUnitPropertyView' => 'AphrontView', 'HarbormasterUploadArtifactBuildStepImplementation' => 'HarbormasterBuildStepImplementation', @@ -5137,6 +5156,8 @@ phutil_register_library_map(array( 'PHUIDiffInlineCommentView' => 'AphrontView', 'PHUIDiffOneUpInlineCommentRowScaffold' => 'PHUIDiffInlineCommentRowScaffold', 'PHUIDiffRevealIconView' => 'AphrontView', + 'PHUIDiffTableOfContentsItemView' => 'AphrontView', + 'PHUIDiffTableOfContentsListView' => 'AphrontView', 'PHUIDiffTwoUpInlineCommentRowScaffold' => 'PHUIDiffInlineCommentRowScaffold', 'PHUIDocumentExample' => 'PhabricatorUIExample', 'PHUIDocumentView' => 'AphrontTagView', @@ -6166,6 +6187,9 @@ phutil_register_library_map(array( 'PhabricatorMacroTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhabricatorMacroTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorMacroViewController' => 'PhabricatorMacroController', + 'PhabricatorMailEmailHeraldField' => 'HeraldField', + 'PhabricatorMailEmailHeraldFieldGroup' => 'HeraldFieldGroup', + 'PhabricatorMailEmailSubjectHeraldField' => 'PhabricatorMailEmailHeraldField', 'PhabricatorMailImplementationAdapter' => 'Phobject', 'PhabricatorMailImplementationAmazonSESAdapter' => 'PhabricatorMailImplementationPHPMailerLiteAdapter', 'PhabricatorMailImplementationMailgunAdapter' => 'PhabricatorMailImplementationAdapter', @@ -6182,10 +6206,15 @@ phutil_register_library_map(array( 'PhabricatorMailManagementShowOutboundWorkflow' => 'PhabricatorMailManagementWorkflow', 'PhabricatorMailManagementVolumeWorkflow' => 'PhabricatorMailManagementWorkflow', 'PhabricatorMailManagementWorkflow' => 'PhabricatorManagementWorkflow', + 'PhabricatorMailOutboundMailHeraldAdapter' => 'HeraldAdapter', + 'PhabricatorMailOutboundRoutingHeraldAction' => 'HeraldAction', + 'PhabricatorMailOutboundRoutingSelfEmailHeraldAction' => 'PhabricatorMailOutboundRoutingHeraldAction', + 'PhabricatorMailOutboundRoutingSelfNotificationHeraldAction' => 'PhabricatorMailOutboundRoutingHeraldAction', 'PhabricatorMailOutboundStatus' => 'Phobject', 'PhabricatorMailReceiver' => 'Phobject', 'PhabricatorMailReceiverTestCase' => 'PhabricatorTestCase', 'PhabricatorMailReplyHandler' => 'Phobject', + 'PhabricatorMailRoutingRule' => 'Phobject', 'PhabricatorMailSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorMailTarget' => 'Phobject', 'PhabricatorMailgunConfigOptions' => 'PhabricatorApplicationConfigOptions', @@ -7579,8 +7608,10 @@ phutil_register_library_map(array( 'PonderAnswerEditor' => 'PonderEditor', 'PonderAnswerHasVotingUserEdgeType' => 'PhabricatorEdgeType', 'PonderAnswerHistoryController' => 'PonderController', + 'PonderAnswerMailReceiver' => 'PhabricatorObjectMailReceiver', 'PonderAnswerPHIDType' => 'PhabricatorPHIDType', 'PonderAnswerQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PonderAnswerReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PonderAnswerSaveController' => 'PonderController', 'PonderAnswerStatus' => 'PonderConstants', 'PonderAnswerTransaction' => 'PhabricatorApplicationTransaction', @@ -7626,7 +7657,6 @@ phutil_register_library_map(array( 'PonderRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'PonderSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PonderSearchIndexer' => 'PhabricatorSearchDocumentIndexer', - 'PonderTransactionFeedStory' => 'PhabricatorApplicationTransactionFeedStory', 'PonderVote' => 'PonderConstants', 'PonderVoteEditor' => 'PhabricatorEditor', 'PonderVotingUserHasAnswerEdgeType' => 'PhabricatorEdgeType', diff --git a/src/aphront/configuration/AphrontApplicationConfiguration.php b/src/aphront/configuration/AphrontApplicationConfiguration.php index 0a33f6f77c..7943b8046d 100644 --- a/src/aphront/configuration/AphrontApplicationConfiguration.php +++ b/src/aphront/configuration/AphrontApplicationConfiguration.php @@ -58,6 +58,7 @@ abstract class AphrontApplicationConfiguration extends Phobject { * @phutil-external-symbol class PhabricatorStartup */ public static function runHTTPRequest(AphrontHTTPSink $sink) { + PhabricatorStartup::beginStartupPhase('multimeter'); $multimeter = MultimeterControl::newInstance(); $multimeter->setEventContext(''); $multimeter->setEventViewer(''); @@ -67,6 +68,7 @@ abstract class AphrontApplicationConfiguration extends Phobject { // request object first. $write_guard = new AphrontWriteGuard('id'); + PhabricatorStartup::beginStartupPhase('env.init'); PhabricatorEnv::initializeWebEnvironment(); $multimeter->setSampleRate( @@ -78,6 +80,7 @@ abstract class AphrontApplicationConfiguration extends Phobject { } // This is the earliest we can get away with this, we need env config first. + PhabricatorStartup::beginStartupPhase('log.access'); PhabricatorAccessLog::init(); $access_log = PhabricatorAccessLog::getLog(); PhabricatorStartup::setAccessLog($access_log); @@ -89,6 +92,11 @@ abstract class AphrontApplicationConfiguration extends Phobject { )); DarkConsoleXHProfPluginAPI::hookProfiler(); + + // We just activated the profiler, so we don't need to keep track of + // startup phases anymore: it can take over from here. + PhabricatorStartup::beginStartupPhase('startup.done'); + DarkConsoleErrorLogPluginAPI::registerErrorHandler(); $response = PhabricatorSetupCheck::willProcessRequest(); diff --git a/src/applications/audit/editor/PhabricatorAuditCommentEditor.php b/src/applications/audit/editor/PhabricatorAuditCommentEditor.php index 9f4bd42f29..03476f70a7 100644 --- a/src/applications/audit/editor/PhabricatorAuditCommentEditor.php +++ b/src/applications/audit/editor/PhabricatorAuditCommentEditor.php @@ -20,7 +20,7 @@ final class PhabricatorAuditCommentEditor extends PhabricatorEditor { $owned_packages = id(new PhabricatorOwnersPackageQuery()) ->setViewer($user) - ->withOwnerPHIDs(array($user->getPHID())) + ->withAuthorityPHIDs(array($user->getPHID())) ->execute(); foreach ($owned_packages as $package) { $phids[$package->getPHID()] = true; diff --git a/src/applications/badges/editor/PhabricatorBadgesEditor.php b/src/applications/badges/editor/PhabricatorBadgesEditor.php index a6cd90bb92..d7403601ec 100644 --- a/src/applications/badges/editor/PhabricatorBadgesEditor.php +++ b/src/applications/badges/editor/PhabricatorBadgesEditor.php @@ -201,7 +201,7 @@ final class PhabricatorBadgesEditor $body->addLinkSection( pht('BADGE DETAIL'), - PhabricatorEnv::getProductionURI('/badge/view/'.$object->getID().'/')); + PhabricatorEnv::getProductionURI('/badges/view/'.$object->getID().'/')); return $body; } diff --git a/src/applications/conpherence/constants/ConpherenceWidgetConfigConstants.php b/src/applications/conpherence/constants/ConpherenceWidgetConfigConstants.php index e7983a7504..36d4f7d617 100644 --- a/src/applications/conpherence/constants/ConpherenceWidgetConfigConstants.php +++ b/src/applications/conpherence/constants/ConpherenceWidgetConfigConstants.php @@ -30,23 +30,6 @@ final class ConpherenceWidgetConfigConstants extends ConpherenceConstants { 'customHref' => null, ), ), - 'widgets-files' => array( - 'name' => pht('Files'), - 'icon' => 'fa-files-o', - 'deviceOnly' => false, - 'hasCreate' => false, - ), - 'widgets-calendar' => array( - 'name' => pht('Calendar'), - 'icon' => 'fa-calendar', - 'deviceOnly' => false, - 'hasCreate' => true, - 'createData' => array( - 'refreshFromResponse' => false, - 'action' => ConpherenceUpdateActions::ADD_STATUS, - 'customHref' => '/calendar/event/create/', - ), - ), 'widgets-settings' => array( 'name' => pht('Notifications'), 'icon' => 'fa-wrench', diff --git a/src/applications/conpherence/controller/ConpherenceController.php b/src/applications/conpherence/controller/ConpherenceController.php index 3317b0c092..8faae68546 100644 --- a/src/applications/conpherence/controller/ConpherenceController.php +++ b/src/applications/conpherence/controller/ConpherenceController.php @@ -27,14 +27,6 @@ abstract class ConpherenceController extends PhabricatorController { ->addSigil('conpherence-widget-adder') ->setMetadata(array('widget' => 'widgets-people'))); - $nav->addMenuItem( - id(new PHUIListItemView()) - ->setName(pht('New Calendar Item')) - ->setType(PHUIListItemView::TYPE_LINK) - ->setHref('/calendar/event/create/') - ->addSigil('conpherence-widget-adder') - ->setMetadata(array('widget' => 'widgets-calendar'))); - return $nav; } diff --git a/src/applications/conpherence/controller/ConpherenceUpdateController.php b/src/applications/conpherence/controller/ConpherenceUpdateController.php index 0739c4054c..6012c81437 100644 --- a/src/applications/conpherence/controller/ConpherenceUpdateController.php +++ b/src/applications/conpherence/controller/ConpherenceUpdateController.php @@ -524,13 +524,6 @@ final class ConpherenceUpdateController ->renderSingleThread($conpherence, $policy_objects); $nav_item = hsprintf('%s', $nav_item); break; - case ConpherenceUpdateActions::MESSAGE: - $file_widget = id(new ConpherenceFileWidgetView()) - ->setUser($this->getRequest()->getUser()) - ->setConpherence($conpherence) - ->setUpdateURI($widget_uri); - $file_widget = hsprintf('%s', $file_widget->render()); - break; case ConpherenceUpdateActions::ADD_PERSON: $people_widget = id(new ConpherencePeopleWidgetView()) ->setUser($user) diff --git a/src/applications/conpherence/controller/ConpherenceWidgetController.php b/src/applications/conpherence/controller/ConpherenceWidgetController.php index 852ceb9e74..0408896a16 100644 --- a/src/applications/conpherence/controller/ConpherenceWidgetController.php +++ b/src/applications/conpherence/controller/ConpherenceWidgetController.php @@ -41,13 +41,6 @@ final class ConpherenceWidgetController extends ConpherenceController { case 'widgets-people': $content = $this->renderPeopleWidgetPaneContent(); break; - case 'widgets-files': - $content = $this->renderFileWidgetPaneContent(); - break; - case 'widgets-calendar': - $widget = $this->renderCalendarWidgetPaneContent(); - $content = phutil_implode_html('', $widget); - break; case 'widgets-settings': $content = $this->renderSettingsWidgetPaneContent(); break; @@ -94,23 +87,6 @@ final class ConpherenceWidgetController extends ConpherenceController { 'sigil' => 'widgets-people', ), $this->renderPeopleWidgetPaneContent()); - $widgets[] = javelin_tag( - 'div', - array( - 'class' => 'widgets-body', - 'id' => 'widgets-files', - 'sigil' => 'widgets-files', - 'style' => 'display: none;', - ), - $this->renderFileWidgetPaneContent()); - $widgets[] = phutil_tag( - 'div', - array( - 'class' => 'widgets-body', - 'id' => 'widgets-calendar', - 'style' => 'display: none;', - ), - $this->renderCalendarWidgetPaneContent()); $widgets[] = phutil_tag( 'div', array( @@ -139,12 +115,6 @@ final class ConpherenceWidgetController extends ConpherenceController { ->setUpdateURI($this->getWidgetURI()); } - private function renderFileWidgetPaneContent() { - return id(new ConpherenceFileWidgetView()) - ->setUser($this->getViewer()) - ->setConpherence($this->getConpherence()) - ->setUpdateURI($this->getWidgetURI()); - } private function renderSettingsWidgetPaneContent() { $viewer = $this->getViewer(); @@ -227,205 +197,6 @@ final class ConpherenceWidgetController extends ConpherenceController { $layout); } - private function renderCalendarWidgetPaneContent() { - $user = $this->getRequest()->getUser(); - - $conpherence = $this->getConpherence(); - $participants = $conpherence->getParticipants(); - $widget_data = $conpherence->getWidgetData(); - - // TODO: This panel is built around an outdated notion of events and isn't - // invitee-aware. - - $statuses = $widget_data['events']; - $handles = $conpherence->getHandles(); - $content = array(); - $layout = id(new AphrontMultiColumnView()) - ->setFluidLayout(true); - $timestamps = CalendarTimeUtil::getCalendarWidgetTimestamps($user); - $today = $timestamps['today']; - $epoch_stamps = $timestamps['epoch_stamps']; - $one_day = 24 * 60 * 60; - $is_today = false; - $calendar_columns = 0; - $list_days = 0; - foreach ($epoch_stamps as $day) { - // build a header for the new day - if ($day->format('Ymd') == $today->format('Ymd')) { - $active_class = 'today'; - $is_today = true; - } else { - $active_class = ''; - $is_today = false; - } - - $should_draw_list = $list_days < 7; - $list_days++; - - if ($should_draw_list) { - $content[] = phutil_tag( - 'div', - array( - 'class' => 'day-header '.$active_class, - ), - array( - phutil_tag( - 'div', - array( - 'class' => 'day-name', - ), - $day->format('l')), - phutil_tag( - 'div', - array( - 'class' => 'day-date', - ), - $day->format('m/d/y')), - )); - } - - $week_day_number = $day->format('w'); - - $epoch_start = $day->format('U'); - $next_day = clone $day; - $next_day->modify('+1 day'); - $epoch_end = $next_day->format('U'); - - $first_status_of_the_day = true; - $statuses_of_the_day = array(); - // keep looking through statuses where we last left off - foreach ($statuses as $status) { - if ($status->getDateFrom() >= $epoch_end) { - // This list is sorted, so we can stop looking. - break; - } - - if ($status->getDateFrom() < $epoch_end && - $status->getDateTo() > $epoch_start) { - $statuses_of_the_day[$status->getUserPHID()] = $status; - if ($should_draw_list) { - $top_border = ''; - if (!$first_status_of_the_day) { - $top_border = ' top-border'; - } - $timespan = $status->getDateTo() - $status->getDateFrom(); - if ($timespan > $one_day) { - $time_str = 'm/d'; - } else { - $time_str = 'h:i A'; - } - $epoch_range = - phabricator_format_local_time( - $status->getDateFrom(), - $user, - $time_str). - ' - '. - phabricator_format_local_time( - $status->getDateTo(), - $user, - $time_str); - - if (isset($handles[$status->getUserPHID()])) { - $secondary_info = pht( - '%s, %s', - $handles[$status->getUserPHID()]->getName(), - $epoch_range); - } else { - $secondary_info = $epoch_range; - } - - $content[] = phutil_tag( - 'div', - array( - 'class' => 'user-status '.$top_border, - ), - array( - phutil_tag( - 'div', - array( - 'class' => 'icon', - ), - ''), - phutil_tag( - 'div', - array( - 'class' => 'description', - ), - array( - $status->getName(), - phutil_tag( - 'div', - array( - 'class' => 'participant', - ), - $secondary_info), - )), - )); - } - $first_status_of_the_day = false; - } - } - - // we didn't get a status on this day so add a spacer - if ($first_status_of_the_day && $should_draw_list) { - $content[] = phutil_tag( - 'div', - array('class' => 'no-events pm'), - pht('No Events Scheduled.')); - } - if ($is_today || ($calendar_columns && $calendar_columns < 3)) { - $active_class = ''; - if ($is_today) { - $active_class = '-active'; - } - $inner_layout = array(); - foreach ($participants as $phid => $participant) { - $status = idx($statuses_of_the_day, $phid, false); - if ($status) { - $inner_layout[] = phutil_tag( - 'div', - array(), - ''); - } else { - $inner_layout[] = phutil_tag( - 'div', - array( - 'class' => 'present', - ), - ''); - } - } - $layout->addColumn( - phutil_tag( - 'div', - array( - 'class' => 'day-column'.$active_class, - ), - array( - phutil_tag( - 'div', - array( - 'class' => 'day-name', - ), - $day->format('D')), - phutil_tag( - 'div', - array( - 'class' => 'day-number', - ), - $day->format('j')), - $inner_layout, - ))); - $calendar_columns++; - } - } - - return array( - $layout, - $content, - ); - } - private function getWidgetURI() { $conpherence = $this->getConpherence(); return $this->getApplicationURI('update/'.$conpherence->getID().'/'); diff --git a/src/applications/conpherence/view/ConpherenceFileWidgetView.php b/src/applications/conpherence/view/ConpherenceFileWidgetView.php deleted file mode 100644 index 3cfb8072e3..0000000000 --- a/src/applications/conpherence/view/ConpherenceFileWidgetView.php +++ /dev/null @@ -1,76 +0,0 @@ -getConpherence(); - $widget_data = $conpherence->getWidgetData(); - $files = $widget_data['files']; - $files_authors = $widget_data['files_authors']; - $files_html = array(); - - foreach ($files as $file) { - $icon_class = $file->getDisplayIconForMimeType(); - $icon_view = phutil_tag( - 'div', - array( - 'class' => 'file-icon phui-font-fa phui-icon-view '.$icon_class, - ), - ''); - $file_view = id(new PhabricatorFileLinkView()) - ->setFilePHID($file->getPHID()) - ->setFileName(id(new PhutilUTF8StringTruncator()) - ->setMaximumGlyphs(28) - ->truncateString($file->getName())) - ->setFileViewable($file->isViewableImage()) - ->setFileViewURI($file->getBestURI()) - ->setCustomClass('file-title'); - - $who_done_it_text = ''; - // system generated files don't have authors - if ($file->getAuthorPHID()) { - $who_done_it_text = pht( - 'By %s ', - $files_authors[$file->getPHID()]->renderLink()); - } - $date_text = phabricator_relative_date( - $file->getDateCreated(), - $this->getUser()); - - $who_done_it = phutil_tag( - 'div', - array( - 'class' => 'file-uploaded-by', - ), - pht('%s%s.', $who_done_it_text, $date_text)); - - $files_html[] = phutil_tag( - 'div', - array( - 'class' => 'file-entry', - ), - array( - $icon_view, - $file_view, - $who_done_it, - )); - } - - if (empty($files)) { - $files_html[] = javelin_tag( - 'div', - array( - 'class' => 'no-files', - 'sigil' => 'no-files', - ), - pht('No files.')); - } - - return phutil_tag( - 'div', - array('class' => 'file-list'), - $files_html); - - } - -} diff --git a/src/applications/console/plugin/DarkConsoleStartupPlugin.php b/src/applications/console/plugin/DarkConsoleStartupPlugin.php new file mode 100644 index 0000000000..4cc166725f --- /dev/null +++ b/src/applications/console/plugin/DarkConsoleStartupPlugin.php @@ -0,0 +1,84 @@ +getData(); + + // Compute the time offset and duration of each startup phase. + $prev_key = null; + $init = null; + $phases = array(); + foreach ($data as $key => $value) { + if ($init === null) { + $init = $value; + } + + $offset = (int)floor(1000 * ($value - $init)); + + $phases[$key] = array( + 'time' => $value, + 'offset' => $value - $init, + ); + + + if ($prev_key !== null) { + $phases[$prev_key]['duration'] = $value - $phases[$prev_key]['time']; + } + $prev_key = $key; + } + + // Render the phases. + $rows = array(); + foreach ($phases as $key => $phase) { + $offset_ms = (int)floor(1000 * $phase['offset']); + + if (isset($phase['duration'])) { + $duration_us = (int)floor(1000000 * $phase['duration']); + } else { + $duration_us = null; + } + + $rows[] = array( + $key, + pht('+%s ms', new PhutilNumber($offset_ms)), + ($duration_us === null) + ? pht('-') + : pht('%s us', new PhutilNumber($duration_us)), + null, + ); + } + + return id(new AphrontTableView($rows)) + ->setHeaders( + array( + pht('Phase'), + pht('Offset'), + pht('Duration'), + null, + )) + ->setColumnClasses( + array( + '', + 'n right', + 'n right', + 'wide', + )); + } + +} diff --git a/src/applications/console/plugin/xhprof/DarkConsoleXHProfPluginAPI.php b/src/applications/console/plugin/xhprof/DarkConsoleXHProfPluginAPI.php index ea2f1bbadb..747ddd2267 100644 --- a/src/applications/console/plugin/xhprof/DarkConsoleXHProfPluginAPI.php +++ b/src/applications/console/plugin/xhprof/DarkConsoleXHProfPluginAPI.php @@ -124,7 +124,13 @@ final class DarkConsoleXHProfPluginAPI extends Phobject { self::startProfiler(); } + + /** + * @phutil-external-symbol class PhabricatorStartup + */ private static function startProfiler() { + PhabricatorStartup::beginStartupPhase('profiler.init'); + self::includeXHProfLib(); xhprof_enable(); @@ -132,15 +138,23 @@ final class DarkConsoleXHProfPluginAPI extends Phobject { self::$profilerRunning = true; } + + /** + * @phutil-external-symbol class PhabricatorStartup + */ public static function getProfileFilePHID() { + if (!self::isProfilerRunning()) { + return; + } + + PhabricatorStartup::beginStartupPhase('profiler.stop'); self::stopProfiler(); + PhabricatorStartup::beginStartupPhase('profiler.done'); + return self::$profileFilePHID; } private static function stopProfiler() { - if (!self::isProfilerRunning()) { - return; - } $data = xhprof_disable(); $data = @json_encode($data); diff --git a/src/applications/countdown/editor/PhabricatorCountdownEditor.php b/src/applications/countdown/editor/PhabricatorCountdownEditor.php index 97f8e5b947..f0962beae2 100644 --- a/src/applications/countdown/editor/PhabricatorCountdownEditor.php +++ b/src/applications/countdown/editor/PhabricatorCountdownEditor.php @@ -147,12 +147,10 @@ final class PhabricatorCountdownEditor public function getMailTagsMap() { return array( - PhabricatorCountdownTransaction::MAILTAG_TITLE => - pht('Someone changes the countdown title.'), - PhabricatorCountdownTransaction::MAILTAG_DESCRIPTION => - pht('Someone changes the countdown description.'), - PhabricatorCountdownTransaction::MAILTAG_EPOCH => - pht('Someone changes the countdown end date.'), + PhabricatorCountdownTransaction::MAILTAG_DETAILS => + pht('Someone changes the countdown details.'), + PhabricatorCountdownTransaction::MAILTAG_COMMENT => + pht('Someone comments on a countdown.'), PhabricatorCountdownTransaction::MAILTAG_OTHER => pht('Other countdown activity not listed above occurs.'), ); diff --git a/src/applications/countdown/storage/PhabricatorCountdownTransaction.php b/src/applications/countdown/storage/PhabricatorCountdownTransaction.php index eace665a89..72303a369f 100644 --- a/src/applications/countdown/storage/PhabricatorCountdownTransaction.php +++ b/src/applications/countdown/storage/PhabricatorCountdownTransaction.php @@ -7,9 +7,8 @@ final class PhabricatorCountdownTransaction const TYPE_EPOCH = 'countdown:epoch'; const TYPE_DESCRIPTION = 'countdown:description'; - const MAILTAG_TITLE = 'countdown:title'; - const MAILTAG_EPOCH = 'countdown:epoch'; - const MAILTAG_DESCRIPTION = 'countdown:description'; + const MAILTAG_DETAILS = 'countdown:details'; + const MAILTAG_COMMENT = 'countdown:comment'; const MAILTAG_OTHER = 'countdown:other'; public function getApplicationName() { @@ -135,14 +134,13 @@ final class PhabricatorCountdownTransaction $tags = parent::getMailTags(); switch ($this->getTransactionType()) { + case PhabricatorTransactions::TYPE_COMMENT: + $tags[] = self::MAILTAG_COMMENT; + break; case self::TYPE_TITLE: - $tags[] = self::MAILTAG_TITLE; - break; case self::TYPE_EPOCH: - $tags[] = self::MAILTAG_EPOCH; - break; case self::TYPE_DESCRIPTION: - $tags[] = self::MAILTAG_DESCRIPTION; + $tags[] = self::MAILTAG_DETAILS; break; default: $tags[] = self::MAILTAG_OTHER; diff --git a/src/applications/differential/controller/DifferentialController.php b/src/applications/differential/controller/DifferentialController.php index 30e8c92d39..4f38015ece 100644 --- a/src/applications/differential/controller/DifferentialController.php +++ b/src/applications/differential/controller/DifferentialController.php @@ -21,4 +21,75 @@ abstract class DifferentialController extends PhabricatorController { return $this->buildSideNavView(true)->getMenu(); } + protected function buildTableOfContents( + array $changesets, + array $visible_changesets, + array $coverage) { + $viewer = $this->getViewer(); + + $toc_view = id(new PHUIDiffTableOfContentsListView()) + ->setUser($viewer); + + $have_owners = PhabricatorApplication::isClassInstalledForViewer( + 'PhabricatorOwnersApplication', + $viewer); + if ($have_owners) { + $repository_phid = null; + if ($changesets) { + $changeset = head($changesets); + $diff = $changeset->getDiff(); + $repository_phid = $diff->getRepositoryPHID(); + } + + if (!$repository_phid) { + $have_owners = false; + } else { + if ($viewer->getPHID()) { + $packages = id(new PhabricatorOwnersPackageQuery()) + ->setViewer($viewer) + ->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE)) + ->withAuthorityPHIDs(array($viewer->getPHID())) + ->execute(); + $toc_view->setAuthorityPackages($packages); + } + + // TODO: For Subversion, we should adjust these paths to be relative to + // the repository root where possible. + $paths = mpull($changesets, 'getFilename'); + + $control_query = id(new PhabricatorOwnersPackageQuery()) + ->setViewer($viewer) + ->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE)) + ->withControl($repository_phid, $paths); + $control_query->execute(); + } + } + + foreach ($changesets as $changeset_id => $changeset) { + $is_visible = isset($visible_changesets[$changeset_id]); + $anchor = $changeset->getAnchorName(); + + $filename = $changeset->getFilename(); + $coverage_id = 'differential-mcoverage-'.md5($filename); + + $item = id(new PHUIDiffTableOfContentsItemView()) + ->setChangeset($changeset) + ->setIsVisible($is_visible) + ->setAnchor($anchor) + ->setCoverage(idx($coverage, $filename)) + ->setCoverageID($coverage_id); + + if ($have_owners) { + $packages = $control_query->getControllingPackagesForPath( + $repository_phid, + $changeset->getFilename()); + $item->setPackages($packages); + } + + $toc_view->addItem($item); + } + + return $toc_view; + } + } diff --git a/src/applications/differential/controller/DifferentialDiffViewController.php b/src/applications/differential/controller/DifferentialDiffViewController.php index 28e7c2fc54..6ffd57396c 100644 --- a/src/applications/differential/controller/DifferentialDiffViewController.php +++ b/src/applications/differential/controller/DifferentialDiffViewController.php @@ -116,10 +116,10 @@ final class DifferentialDiffViewController extends DifferentialController { $changesets = $diff->loadChangesets(); $changesets = msort($changesets, 'getSortKey'); - $table_of_contents = id(new DifferentialDiffTableOfContentsView()) - ->setChangesets($changesets) - ->setVisibleChangesets($changesets) - ->setCoverageMap($diff->loadCoverageMap($viewer)); + $table_of_contents = $this->buildTableOfContents( + $changesets, + $changesets, + $diff->loadCoverageMap($viewer)); $refs = array(); foreach ($changesets as $changeset) { diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index 085e888fe5..4af487252c 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -349,18 +349,10 @@ final class DifferentialRevisionViewController extends DifferentialController { $other_view = $this->renderOtherRevisions($other_revisions); } - $toc_view = new DifferentialDiffTableOfContentsView(); - $toc_view->setChangesets($changesets); - $toc_view->setVisibleChangesets($visible_changesets); - $toc_view->setRenderingReferences($rendering_references); - $toc_view->setCoverageMap($target->loadCoverageMap($user)); - if ($repository) { - $toc_view->setRepository($repository); - } - $toc_view->setDiff($target); - $toc_view->setUser($user); - $toc_view->setRevisionID($revision->getID()); - $toc_view->setWhitespace($whitespace); + $toc_view = $this->buildTableOfContents( + $changesets, + $visible_changesets, + $target->loadCoverageMap($user)); $comment_form = null; if (!$viewer_is_anonymous) { @@ -1044,5 +1036,4 @@ final class DifferentialRevisionViewController extends DifferentialController { return $view; } - } diff --git a/src/applications/differential/storage/DifferentialDiff.php b/src/applications/differential/storage/DifferentialDiff.php index e48ed5630d..7a5bc24203 100644 --- a/src/applications/differential/storage/DifferentialDiff.php +++ b/src/applications/differential/storage/DifferentialDiff.php @@ -101,9 +101,15 @@ final class DifferentialDiff if (!$this->getID()) { return array(); } - return id(new DifferentialChangeset())->loadAllWhere( + $changesets = id(new DifferentialChangeset())->loadAllWhere( 'diffID = %d', $this->getID()); + + foreach ($changesets as $changeset) { + $changeset->attachDiff($this); + } + + return $changesets; } public function save() { diff --git a/src/applications/differential/view/DifferentialDiffTableOfContentsView.php b/src/applications/differential/view/DifferentialDiffTableOfContentsView.php deleted file mode 100644 index 26161096fe..0000000000 --- a/src/applications/differential/view/DifferentialDiffTableOfContentsView.php +++ /dev/null @@ -1,312 +0,0 @@ -changesets = $changesets; - return $this; - } - - public function setVisibleChangesets($visible_changesets) { - $this->visibleChangesets = $visible_changesets; - return $this; - } - - public function setRenderingReferences(array $references) { - $this->references = $references; - return $this; - } - - public function setRepository(PhabricatorRepository $repository) { - $this->repository = $repository; - return $this; - } - - public function setDiff(DifferentialDiff $diff) { - $this->diff = $diff; - return $this; - } - - public function setCoverageMap(array $coverage_map) { - $this->coverageMap = $coverage_map; - return $this; - } - - public function setRevisionID($revision_id) { - $this->revisionID = $revision_id; - return $this; - } - - public function setWhitespace($whitespace) { - $this->whitespace = $whitespace; - return $this; - } - - public function render() { - - $this->requireResource('differential-core-view-css'); - $this->requireResource('differential-table-of-contents-css'); - $this->requireResource('phui-text-css'); - - $rows = array(); - - $changesets = $this->changesets; - $paths = array(); - foreach ($changesets as $id => $changeset) { - $type = $changeset->getChangeType(); - $ftype = $changeset->getFileType(); - $ref = idx($this->references, $id); - $display_file = $changeset->getDisplayFilename(); - - $meta = null; - if (DifferentialChangeType::isOldLocationChangeType($type)) { - $away = $changeset->getAwayPaths(); - if (count($away) > 1) { - $meta = array(); - if ($type == DifferentialChangeType::TYPE_MULTICOPY) { - $meta[] = pht('Deleted after being copied to multiple locations:'); - } else { - $meta[] = pht('Copied to multiple locations:'); - } - foreach ($away as $path) { - $meta[] = $path; - } - $meta = phutil_implode_html(phutil_tag('br'), $meta); - } else { - if ($type == DifferentialChangeType::TYPE_MOVE_AWAY) { - $display_file = $this->renderRename( - $display_file, - reset($away), - "\xE2\x86\x92"); - } else { - $meta = pht('Copied to %s', reset($away)); - } - } - } else if ($type == DifferentialChangeType::TYPE_MOVE_HERE) { - $old_file = $changeset->getOldFile(); - $display_file = $this->renderRename( - $display_file, - $old_file, - "\xE2\x86\x90"); - } else if ($type == DifferentialChangeType::TYPE_COPY_HERE) { - $meta = pht('Copied from %s', $changeset->getOldFile()); - } - - $link = $this->renderChangesetLink($changeset, $ref, $display_file); - - $line_count = $changeset->getAffectedLineCount(); - if ($line_count == 0) { - $lines = ''; - } else { - $lines = ' '.pht('(%d line(s))', $line_count); - } - - $char = DifferentialChangeType::getSummaryCharacterForChangeType($type); - $chartitle = DifferentialChangeType::getFullNameForChangeType($type); - $desc = DifferentialChangeType::getShortNameForFileType($ftype); - $color = DifferentialChangeType::getSummaryColorForChangeType($type); - if ($desc) { - $desc = '('.$desc.')'; - } - $pchar = - ($changeset->getOldProperties() === $changeset->getNewProperties()) - ? '' - : phutil_tag( - 'span', - array('title' => pht('Properties Changed')), - 'M'); - - $fname = $changeset->getFilename(); - $cov = $this->renderCoverage($this->coverageMap, $fname); - if ($cov === null) { - $mcov = $cov = phutil_tag('em', array(), '-'); - } else { - $mcov = phutil_tag( - 'div', - array( - 'id' => 'differential-mcoverage-'.md5($fname), - 'class' => 'differential-mcoverage-loading', - ), - (isset($this->visibleChangesets[$id]) ? - pht('Loading...') : pht('?'))); - } - - if ($meta) { - $meta = phutil_tag( - 'div', - array( - 'class' => 'differential-toc-meta', - ), - $meta); - } - - if ($this->diff && $this->repository) { - $paths[] = - $changeset->getAbsoluteRepositoryPath($this->repository, $this->diff); - } - - $char = phutil_tag('span', array('class' => 'phui-text-'.$color), $char); - - $rows[] = array( - $char, - $pchar, - $desc, - array($link, $lines, $meta), - $cov, - $mcov, - ); - } - - $editor_link = null; - if ($paths && $this->user) { - $editor_link = $this->user->loadEditorLink( - $paths, - 1, // line number - $this->repository->getCallsign()); - if ($editor_link) { - $editor_link = - phutil_tag( - 'a', - array( - 'href' => $editor_link, - 'class' => 'button differential-toc-edit-all', - ), - pht('Open All in Editor')); - } - } - - $reveal_link = javelin_tag( - 'a', - array( - 'sigil' => 'differential-reveal-all', - 'mustcapture' => true, - 'class' => 'button differential-toc-reveal-all', - ), - pht('Show All Context')); - - $buttons = phutil_tag( - 'div', - array( - 'class' => 'differential-toc-buttons grouped', - ), - array( - $editor_link, - $reveal_link, - )); - - $table = id(new AphrontTableView($rows)); - $table->setHeaders( - array( - '', - '', - '', - pht('Path'), - pht('Coverage (All)'), - pht('Coverage (Touched)'), - )); - $table->setColumnClasses( - array( - 'differential-toc-char center', - 'differential-toc-prop center', - 'differential-toc-ftype center', - 'differential-toc-file wide', - 'differential-toc-cov', - 'differential-toc-cov', - )); - $table->setDeviceVisibility( - array( - true, - true, - true, - true, - false, - false, - )); - $anchor = id(new PhabricatorAnchorView()) - ->setAnchorName('toc') - ->setNavigationMarker(true); - - return id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Table of Contents')) - ->setTable($table) - ->appendChild($anchor) - ->appendChild($buttons); - } - - private function renderRename($display_file, $other_file, $arrow) { - $old = explode('/', $display_file); - $new = explode('/', $other_file); - - $start = count($old); - foreach ($old as $index => $part) { - if (!isset($new[$index]) || $part != $new[$index]) { - $start = $index; - break; - } - } - - $end = count($old); - foreach (array_reverse($old) as $from_end => $part) { - $index = count($new) - $from_end - 1; - if (!isset($new[$index]) || $part != $new[$index]) { - $end = $from_end; - break; - } - } - - $rename = - '{'. - implode('/', array_slice($old, $start, count($old) - $end - $start)). - ' '.$arrow.' '. - implode('/', array_slice($new, $start, count($new) - $end - $start)). - '}'; - - array_splice($new, $start, count($new) - $end - $start, $rename); - return implode('/', $new); - } - - private function renderCoverage(array $coverage, $file) { - $info = idx($coverage, $file); - if (!$info) { - return null; - } - - $not_covered = substr_count($info, 'U'); - $covered = substr_count($info, 'C'); - - if (!$not_covered && !$covered) { - return null; - } - - return sprintf('%d%%', 100 * ($covered / ($covered + $not_covered))); - } - - - private function renderChangesetLink( - DifferentialChangeset $changeset, - $ref, - $display_file) { - - return javelin_tag( - 'a', - array( - 'href' => '#'.$changeset->getAnchorName(), - 'sigil' => 'differential-load', - 'meta' => array( - 'id' => 'diff-'.$changeset->getAnchorName(), - ), - ), - $display_file); - } - -} diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index 3d7bfb78fd..e8bd46b735 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -111,25 +111,6 @@ abstract class DiffusionBrowseController extends DiffusionController { ->setIcon('fa-home') ->setDisabled(!$behind_head)); - // TODO: Ideally, this should live in Owners and be event-triggered, but - // there's no reasonable object for it to react to right now. - - $owners = 'PhabricatorOwnersApplication'; - if (PhabricatorApplication::isClassInstalled($owners)) { - $owners_uri = id(new PhutilURI('/owners/view/search/')) - ->setQueryParams( - array( - 'repository' => $drequest->getCallsign(), - 'path' => '/'.$drequest->getPath(), - )); - - $view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Find Owners')) - ->setHref((string)$owners_uri) - ->setIcon('fa-users')); - } - return $view; } @@ -137,7 +118,7 @@ abstract class DiffusionBrowseController extends DiffusionController { DiffusionRequest $drequest, PhabricatorActionListView $actions) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) @@ -180,6 +161,46 @@ abstract class DiffusionBrowseController extends DiffusionController { } } + $repository = $drequest->getRepository(); + + $owners = 'PhabricatorOwnersApplication'; + if (PhabricatorApplication::isClassInstalled($owners)) { + $package_query = id(new PhabricatorOwnersPackageQuery()) + ->setViewer($viewer) + ->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE)) + ->withControl( + $repository->getPHID(), + array( + $drequest->getPath(), + )); + + $package_query->execute(); + + $packages = $package_query->getControllingPackagesForPath( + $repository->getPHID(), + $drequest->getPath()); + + if ($packages) { + $ownership = id(new PHUIStatusListView()) + ->setUser($viewer); + + foreach ($packages as $package) { + $icon = 'fa-list-alt'; + $color = 'grey'; + + $item = id(new PHUIStatusItemView()) + ->setIcon($icon, $color) + ->setTarget($viewer->renderHandle($package->getPHID())); + + $ownership->addItem($item); + } + } else { + $ownership = phutil_tag('em', array(), pht('None')); + } + + $view->addProperty(pht('Packages'), $ownership); + } + return $view; } diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index 74441be360..6117bcdaca 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -75,7 +75,6 @@ final class DiffusionCommitController extends DiffusionController { $commit_data = $commit->getCommitData(); $is_foreign = $commit_data->getCommitDetail('foreign-svn-stub'); - $changesets = null; if ($is_foreign) { $subpath = $commit_data->getCommitDetail('svn-subpath'); @@ -181,24 +180,6 @@ final class DiffusionCommitController extends DiffusionController { $user, $this->auditAuthorityPHIDs); - $owners_paths = array(); - if ($highlighted_audits) { - $packages = id(new PhabricatorOwnersPackage())->loadAllWhere( - 'phid IN (%Ls)', - mpull($highlighted_audits, 'getAuditorPHID')); - if ($packages) { - $owners_paths = id(new PhabricatorOwnersPath())->loadAllWhere( - 'repositoryPHID = %s AND packageID IN (%Ld)', - $repository->getPHID(), - mpull($packages, 'getID')); - } - } - - $change_table = new DiffusionCommitChangeTableView(); - $change_table->setDiffusionRequest($drequest); - $change_table->setPathChanges($changes); - $change_table->setOwnersPaths($owners_paths); - $count = count($changes); $bad_commit = null; @@ -210,6 +191,7 @@ final class DiffusionCommitController extends DiffusionController { 'r'.$callsign.$commit->getCommitIdentifier()); } + $show_changesets = false; if ($bad_commit) { $content[] = $this->renderStatusMessage( pht('Bad Commit'), @@ -235,6 +217,8 @@ final class DiffusionCommitController extends DiffusionController { 'Changes are not shown.', $hard_limit)); } else { + $show_changesets = true; + // The user has clicked "Show All Changes", and we should show all the // changes inline even if there are more than the soft limit. $show_all_details = $request->getBool('show_all'); @@ -264,15 +248,20 @@ final class DiffusionCommitController extends DiffusionController { $header->addActionLink($button); } + $changesets = DiffusionPathChange::convertToDifferentialChangesets( + $user, + $changes); + + // TODO: This table and panel shouldn't really be separate, but we need + // to clean up the "Load All Files" interaction first. + $change_table = $this->buildTableOfContents( + $changesets); + $change_panel->setTable($change_table); $change_panel->setHeader($header); $content[] = $change_panel; - $changesets = DiffusionPathChange::convertToDifferentialChangesets( - $user, - $changes); - $vcs = $repository->getVersionControlSystem(); switch ($vcs) { case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: @@ -353,12 +342,6 @@ final class DiffusionCommitController extends DiffusionController { $change_list->setInlineCommentControllerURI( '/diffusion/inline/edit/'.phutil_escape_uri($commit->getPHID()).'/'); - $change_references = array(); - foreach ($changesets as $key => $changeset) { - $change_references[$changeset->getID()] = $references[$key]; - } - $change_table->setRenderingReferences($change_references); - $content[] = $change_list->render(); } @@ -375,7 +358,7 @@ final class DiffusionCommitController extends DiffusionController { $show_filetree = $prefs->getPreference($pref_filetree); $collapsed = $prefs->getPreference($pref_collapse); - if ($changesets && $show_filetree) { + if ($show_changesets && $show_filetree) { $nav = id(new DifferentialChangesetFileTreeSideNavBuilder()) ->setTitle($short_name) ->setBaseURI(new PhutilURI('/'.$commit_id)) @@ -1082,4 +1065,74 @@ final class DiffusionCommitController extends DiffusionController { return $parser->processCorpus($corpus); } + private function buildTableOfContents(array $changesets) { + $drequest = $this->getDiffusionRequest(); + $viewer = $this->getViewer(); + + $toc_view = id(new PHUIDiffTableOfContentsListView()) + ->setUser($viewer); + + // TODO: This is hacky, we just want access to the linkX() methods on + // DiffusionView. + $diffusion_view = id(new DiffusionEmptyResultView()) + ->setDiffusionRequest($drequest); + + $have_owners = PhabricatorApplication::isClassInstalledForViewer( + 'PhabricatorOwnersApplication', + $viewer); + + if (!$changesets) { + $have_owners = false; + } + + if ($have_owners) { + if ($viewer->getPHID()) { + $packages = id(new PhabricatorOwnersPackageQuery()) + ->setViewer($viewer) + ->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE)) + ->withAuthorityPHIDs(array($viewer->getPHID())) + ->execute(); + $toc_view->setAuthorityPackages($packages); + } + + $repository = $drequest->getRepository(); + $repository_phid = $repository->getPHID(); + + $control_query = id(new PhabricatorOwnersPackageQuery()) + ->setViewer($viewer) + ->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE)) + ->withControl($repository_phid, mpull($changesets, 'getFilename')); + $control_query->execute(); + } + + foreach ($changesets as $changeset_id => $changeset) { + $path = $changeset->getFilename(); + $anchor = substr(md5($path), 0, 8); + + $history_link = $diffusion_view->linkHistory($path); + $browse_link = $diffusion_view->linkBrowse($path); + + $item = id(new PHUIDiffTableOfContentsItemView()) + ->setChangeset($changeset) + ->setAnchor($anchor) + ->setContext( + array( + $history_link, + ' ', + $browse_link, + )); + + if ($have_owners) { + $packages = $control_query->getControllingPackagesForPath( + $repository_phid, + $changeset->getFilename()); + $item->setPackages($packages); + } + + $toc_view->addItem($item); + } + + return $toc_view; + } + } diff --git a/src/applications/diffusion/view/DiffusionCommitChangeTableView.php b/src/applications/diffusion/view/DiffusionCommitChangeTableView.php deleted file mode 100644 index 538e225ae7..0000000000 --- a/src/applications/diffusion/view/DiffusionCommitChangeTableView.php +++ /dev/null @@ -1,103 +0,0 @@ -pathChanges = $path_changes; - return $this; - } - - public function setOwnersPaths(array $owners_paths) { - assert_instances_of($owners_paths, 'PhabricatorOwnersPath'); - $this->ownersPaths = $owners_paths; - return $this; - } - - public function setRenderingReferences(array $value) { - $this->renderingReferences = $value; - return $this; - } - - public function render() { - $rows = array(); - $rowc = array(); - - // TODO: Experiment with path stack rendering. - - // TODO: Copy Away and Move Away are rendered junkily still. - - foreach ($this->pathChanges as $id => $change) { - $path = $change->getPath(); - $hash = substr(md5($path), 0, 8); - if ($change->getFileType() == DifferentialChangeType::FILE_DIRECTORY) { - $path .= '/'; - } - - if (isset($this->renderingReferences[$id])) { - $path_column = javelin_tag( - 'a', - array( - 'href' => '#'.$hash, - 'meta' => array( - 'id' => 'diff-'.$hash, - 'ref' => $this->renderingReferences[$id], - ), - 'sigil' => 'differential-load', - ), - $path); - } else { - $path_column = $path; - } - - $rows[] = array( - $this->linkHistory($change->getPath()), - $this->linkBrowse($change->getPath()), - $this->linkChange( - $change->getChangeType(), - $change->getFileType(), - $change->getPath()), - $path_column, - ); - - $row_class = null; - foreach ($this->ownersPaths as $owners_path) { - $excluded = $owners_path->getExcluded(); - $owners_path = $owners_path->getPath(); - if (strncmp('/'.$path, $owners_path, strlen($owners_path)) == 0) { - if ($excluded) { - $row_class = null; - break; - } - $row_class = 'highlighted'; - } - } - $rowc[] = $row_class; - } - - $view = new AphrontTableView($rows); - $view->setHeaders( - array( - pht('History'), - pht('Browse'), - pht('Change'), - pht('Path'), - )); - $view->setColumnClasses( - array( - '', - '', - '', - 'wide', - )); - $view->setRowClasses($rowc); - $view->setNoDataString(pht('This change has not been fully parsed yet.')); - - return $view->render(); - } - -} diff --git a/src/applications/drydock/query/DrydockLeaseQuery.php b/src/applications/drydock/query/DrydockLeaseQuery.php index d5d62f950e..23cad90e9c 100644 --- a/src/applications/drydock/query/DrydockLeaseQuery.php +++ b/src/applications/drydock/query/DrydockLeaseQuery.php @@ -27,19 +27,12 @@ final class DrydockLeaseQuery extends DrydockQuery { return $this; } + public function newResultObject() { + return new DrydockLease(); + } + protected function loadPage() { - $table = new DrydockLease(); - $conn_r = $table->establishConnection('r'); - - $data = queryfx_all( - $conn_r, - 'SELECT lease.* FROM %T lease %Q %Q %Q', - $table->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - - return $table->loadAllFromArray($data); + return $this->loadStandardPage($this->newResultObject()); } protected function willFilterPage(array $leases) { @@ -69,40 +62,38 @@ final class DrydockLeaseQuery extends DrydockQuery { return $leases; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); - if ($this->resourceIDs) { + if ($this->resourceIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'resourceID IN (%Ld)', $this->resourceIDs); } - if ($this->ids) { + if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } - if ($this->phids) { + if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'phid IN (%Ls)', $this->phids); } - if ($this->statuses) { + if ($this->statuses !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'status IN (%Ld)', $this->statuses); } - $where[] = $this->buildPagingClause($conn_r); - - return $this->formatWhereClause($where); + return $where; } } diff --git a/src/applications/harbormaster/artifact/HarbormasterArtifact.php b/src/applications/harbormaster/artifact/HarbormasterArtifact.php new file mode 100644 index 0000000000..a0d3a86101 --- /dev/null +++ b/src/applications/harbormaster/artifact/HarbormasterArtifact.php @@ -0,0 +1,88 @@ +getArtifactTypeDescription(); + } + + abstract public function getArtifactTypeDescription(); + abstract public function getArtifactParameterSpecification(); + abstract public function getArtifactParameterDescriptions(); + abstract public function willCreateArtifact(PhabricatorUser $actor); + + public function validateArtifactData(array $artifact_data) { + $artifact_spec = $this->getArtifactParameterSpecification(); + PhutilTypeSpec::checkMap($artifact_data, $artifact_spec); + } + + public function renderArtifactSummary(PhabricatorUser $viewer) { + return null; + } + + public function releaseArtifact(PhabricatorUser $actor) { + return; + } + + public function getArtifactDataExample() { + return null; + } + + public function setBuildArtifact(HarbormasterBuildArtifact $build_artifact) { + $this->buildArtifact = $build_artifact; + return $this; + } + + public function getBuildArtifact() { + return $this->buildArtifact; + } + + final public function getArtifactConstant() { + $class = new ReflectionClass($this); + + $const = $class->getConstant('ARTIFACTCONST'); + if ($const === false) { + throw new Exception( + pht( + '"%s" class "%s" must define a "%s" property.', + __CLASS__, + get_class($this), + 'ARTIFACTCONST')); + } + + $limit = self::getArtifactConstantByteLimit(); + if (!is_string($const) || (strlen($const) > $limit)) { + throw new Exception( + pht( + '"%s" class "%s" has an invalid "%s" property. Action constants '. + 'must be strings and no more than %s bytes in length.', + __CLASS__, + get_class($this), + 'ARTIFACTCONST', + new PhutilNumber($limit))); + } + + return $const; + } + + final public static function getArtifactConstantByteLimit() { + return 32; + } + + final public static function getAllArtifactTypes() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getArtifactConstant') + ->execute(); + } + + final public static function getArtifactType($type) { + return idx(self::getAllArtifactTypes(), $type); + } + +} diff --git a/src/applications/harbormaster/artifact/HarbormasterFileArtifact.php b/src/applications/harbormaster/artifact/HarbormasterFileArtifact.php new file mode 100644 index 0000000000..91fc025c21 --- /dev/null +++ b/src/applications/harbormaster/artifact/HarbormasterFileArtifact.php @@ -0,0 +1,66 @@ + 'string', + ); + } + + public function getArtifactParameterDescriptions() { + return array( + 'filePHID' => pht('File to create an artifact from.'), + ); + } + + public function getArtifactDataExample() { + return array( + 'filePHID' => 'PHID-FILE-abcdefghijklmnopqrst', + ); + } + + public function renderArtifactSummary(PhabricatorUser $viewer) { + $artifact = $this->getBuildArtifact(); + $file_phid = $artifact->getProperty('filePHID'); + return $viewer->renderHandle($file_phid); + } + + public function willCreateArtifact(PhabricatorUser $actor) { + // NOTE: This is primarily making sure the actor has permission to view the + // file. We don't want to let you run builds using files you don't have + // permission to see, since this could let you violate permissions. + $this->loadArtifactFile($actor); + } + + public function loadArtifactFile(PhabricatorUser $viewer) { + $artifact = $this->getBuildArtifact(); + $file_phid = $artifact->getProperty('filePHID'); + + $file = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($file_phid)) + ->executeOne(); + if (!$file) { + throw new Exception( + pht( + 'File PHID "%s" does not correspond to a valid file.', + $file_phid)); + } + + return $file; + } + +} diff --git a/src/applications/harbormaster/artifact/HarbormasterHostArtifact.php b/src/applications/harbormaster/artifact/HarbormasterHostArtifact.php new file mode 100644 index 0000000000..73d00844af --- /dev/null +++ b/src/applications/harbormaster/artifact/HarbormasterHostArtifact.php @@ -0,0 +1,74 @@ + 'string', + ); + } + + public function getArtifactParameterDescriptions() { + return array( + 'drydockLeasePHID' => pht( + 'Drydock host lease to create an artifact from.'), + ); + } + + public function getArtifactDataExample() { + return array( + 'drydockLeasePHID' => 'PHID-DRYL-abcdefghijklmnopqrst', + ); + } + + public function renderArtifactSummary(PhabricatorUser $viewer) { + $artifact = $this->getBuildArtifact(); + $file_phid = $artifact->getProperty('drydockLeasePHID'); + return $viewer->renderHandle($file_phid); + } + + public function willCreateArtifact(PhabricatorUser $actor) { + $this->loadArtifactLease($actor); + } + + public function loadArtifactLease(PhabricatorUser $viewer) { + $artifact = $this->getBuildArtifact(); + $lease_phid = $artifact->getProperty('drydockLeasePHID'); + + $lease = id(new DrydockLeaseQuery()) + ->setViewer($viewer) + ->withPHIDs(array($lease_phid)) + ->executeOne(); + if (!$lease) { + throw new Exception( + pht( + 'Drydock lease PHID "%s" does not correspond to a valid lease.', + $lease_phid)); + } + + return $lease; + } + + public function releaseArtifact(PhabricatorUser $actor) { + $lease = $this->loadArtifactLease($actor); + $resource = $lease->getResource(); + $blueprint = $resource->getBlueprint(); + + if ($lease->isActive()) { + $blueprint->releaseLease($resource, $lease); + } + } + + +} diff --git a/src/applications/harbormaster/artifact/HarbormasterURIArtifact.php b/src/applications/harbormaster/artifact/HarbormasterURIArtifact.php new file mode 100644 index 0000000000..311492de20 --- /dev/null +++ b/src/applications/harbormaster/artifact/HarbormasterURIArtifact.php @@ -0,0 +1,109 @@ + 'string', + 'name' => 'optional string', + 'ui.external' => 'optional bool', + ); + } + + public function getArtifactParameterDescriptions() { + return array( + 'uri' => pht('The URI to store.'), + 'name' => pht('Optional label for this URI.'), + 'ui.external' => pht( + 'If true, display this URI in the UI as an link to '. + 'additional build details in an external build system.'), + ); + } + + public function getArtifactDataExample() { + return array( + 'uri' => 'https://buildserver.mycompany.com/build/123/', + 'name' => pht('View External Build Results'), + 'ui.external' => true, + ); + } + + public function renderArtifactSummary(PhabricatorUser $viewer) { + return $this->renderLink(); + } + + public function isExternalLink() { + $artifact = $this->getBuildArtifact(); + return (bool)$artifact->getProperty('ui.external', false); + } + + public function renderLink() { + $artifact = $this->getBuildArtifact(); + $uri = $artifact->getProperty('uri'); + + try { + $this->validateURI($uri); + } catch (Exception $ex) { + return pht(''); + } + + $name = $artifact->getProperty('name', $uri); + + return phutil_tag( + 'a', + array( + 'href' => $uri, + 'target' => '_blank', + ), + $name); + } + + public function willCreateArtifact(PhabricatorUser $actor) { + $artifact = $this->getBuildArtifact(); + $uri = $artifact->getProperty('uri'); + $this->validateURI($uri); + } + + private function validateURI($raw_uri) { + $uri = new PhutilURI($raw_uri); + + $protocol = $uri->getProtocol(); + if (!strlen($protocol)) { + throw new Exception( + pht( + 'Unable to identify the protocol for URI "%s". URIs must be '. + 'fully qualified and have an identifiable protocol.', + $raw_uri)); + } + + $protocol_key = 'uri.allowed-protocols'; + $protocols = PhabricatorEnv::getEnvConfig($protocol_key); + if (empty($protocols[$protocol])) { + throw new Exception( + pht( + 'URI "%s" does not have an allowable protocol. Configure '. + 'protocols in `%s`. Allowed protocols are: %s.', + $raw_uri, + $protocol_key, + implode(', ', array_keys($protocols)))); + } + } + +} diff --git a/src/applications/harbormaster/conduit/HarbormasterConduitAPIMethod.php b/src/applications/harbormaster/conduit/HarbormasterConduitAPIMethod.php index 33d239dc12..ccd76d2bbc 100644 --- a/src/applications/harbormaster/conduit/HarbormasterConduitAPIMethod.php +++ b/src/applications/harbormaster/conduit/HarbormasterConduitAPIMethod.php @@ -15,4 +15,16 @@ abstract class HarbormasterConduitAPIMethod extends ConduitAPIMethod { return pht('All Harbormaster APIs are new and subject to change.'); } + protected function returnArtifactList(array $artifacts) { + $list = array(); + + foreach ($artifacts as $artifact) { + $list[] = array( + 'phid' => $artifact->getPHID(), + ); + } + + return $list; + } + } diff --git a/src/applications/harbormaster/conduit/HarbormasterCreateArtifactConduitAPIMethod.php b/src/applications/harbormaster/conduit/HarbormasterCreateArtifactConduitAPIMethod.php new file mode 100644 index 0000000000..56702bf99e --- /dev/null +++ b/src/applications/harbormaster/conduit/HarbormasterCreateArtifactConduitAPIMethod.php @@ -0,0 +1,129 @@ +getArtifactTypeName(); + $type_const = $type->getArtifactConstant(); + $type_summary = $type->getArtifactTypeSummary(); + $out[] = "| `{$type_const}` | **{$type_name}** | {$type_summary} |"; + } + + $out[] = null; + $out[] = pht( + 'Each artifact also needs an `artifactKey`, which names the artifact. '. + 'Finally, you will provide some `artifactData` to fill in the content '. + 'of the artifact. The data you provide depends on what type of artifact '. + 'you are creating.'); + + foreach ($types as $type) { + $type_name = $type->getArtifactTypeName(); + $type_const = $type->getArtifactConstant(); + + $out[] = $type_name; + $out[] = '--------------------------'; + $out[] = null; + $out[] = $type->getArtifactTypeDescription(); + $out[] = null; + $out[] = pht( + 'Create an artifact of this type by passing `%s` as the '. + '`artifactType`. When creating an artifact of this type, provide '. + 'these parameters as a dictionary to `artifactData`:', + $type_const); + + $spec = $type->getArtifactParameterSpecification(); + $desc = $type->getArtifactParameterDescriptions(); + $out[] = "| {$head_key} | {$head_type} | {$head_desc} |"; + $out[] = '|-------------|--------------|--------------|'; + foreach ($spec as $key => $key_type) { + $key_desc = idx($desc, $key); + $out[] = "| `{$key}` | //{$key_type}// | {$key_desc} |"; + } + + $example = $type->getArtifactDataExample(); + if ($example !== null) { + $json = new PhutilJSON(); + $rendered = $json->encodeFormatted($example); + + $out[] = pht('For example:'); + $out[] = '```lang=json'; + $out[] = $rendered; + $out[] = '```'; + } + } + + return implode("\n", $out); + } + + protected function defineParamTypes() { + return array( + 'buildTargetPHID' => 'phid', + 'artifactKey' => 'string', + 'artifactType' => 'string', + 'artifactData' => 'map', + ); + } + + protected function defineReturnType() { + return 'wild'; + } + + protected function execute(ConduitAPIRequest $request) { + $viewer = $request->getUser(); + + $build_target_phid = $request->getValue('buildTargetPHID'); + $build_target = id(new HarbormasterBuildTargetQuery()) + ->setViewer($viewer) + ->withPHIDs(array($build_target_phid)) + ->executeOne(); + if (!$build_target) { + throw new Exception( + pht( + 'No such build target "%s"!', + $build_target_phid)); + } + + $artifact = $build_target->createArtifact( + $viewer, + $request->getValue('artifactKey'), + $request->getValue('artifactType'), + $request->getValue('artifactData')); + + return array( + 'data' => $this->returnArtifactList(array($artifact)), + ); + } + +} diff --git a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php index cdc5baec60..1a9832c588 100644 --- a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php @@ -3,17 +3,11 @@ final class HarbormasterBuildViewController extends HarbormasterController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { + public function handleRequest(AphrontRequest $request) { $request = $this->getRequest(); $viewer = $request->getUser(); - $id = $this->id; + $id = $request->getURIData('id'); $generation = $request->getInt('g'); $build = id(new HarbormasterBuildQuery()) @@ -73,6 +67,18 @@ final class HarbormasterBuildViewController $messages = array(); } + if ($build_targets) { + $artifacts = id(new HarbormasterBuildArtifactQuery()) + ->setViewer($viewer) + ->withBuildTargetPHIDs(mpull($build_targets, 'getPHID')) + ->execute(); + $artifacts = msort($artifacts, 'getArtifactKey'); + $artifacts = mgroup($artifacts, 'getBuildTargetPHID'); + } else { + $artifacts = array(); + } + + $targets = array(); foreach ($build_targets as $build_target) { $header = id(new PHUIHeaderView()) @@ -83,6 +89,27 @@ final class HarbormasterBuildViewController ->setHeader($header); $properties = new PHUIPropertyListView(); + + $target_artifacts = idx($artifacts, $build_target->getPHID(), array()); + + $links = array(); + $type_uri = HarbormasterURIArtifact::ARTIFACTCONST; + foreach ($target_artifacts as $artifact) { + if ($artifact->getArtifactType() == $type_uri) { + $impl = $artifact->getArtifactImplementation(); + if ($impl->isExternalLink()) { + $links[] = $impl->renderLink(); + } + } + } + + if ($links) { + $links = phutil_implode_html(phutil_tag('br'), $links); + $properties->addProperty( + pht('External Link'), + $links); + } + $status_view = new PHUIStatusListView(); $item = new PHUIStatusItemView(); @@ -183,9 +210,9 @@ final class HarbormasterBuildViewController $properties->addRawContent($this->buildProperties($variables)); $target_box->addPropertyList($properties, pht('Variables')); - $artifacts = $this->buildArtifacts($build_target); + $artifacts_tab = $this->buildArtifacts($build_target, $target_artifacts); $properties = new PHUIPropertyListView(); - $properties->addRawContent($artifacts); + $properties->addRawContent($artifacts_tab); $target_box->addPropertyList($properties, pht('Artifacts')); $build_messages = idx($messages, $build_target->getPHID(), array()); @@ -225,28 +252,45 @@ final class HarbormasterBuildViewController } private function buildArtifacts( - HarbormasterBuildTarget $build_target) { - - $request = $this->getRequest(); - $viewer = $request->getUser(); - - $artifacts = id(new HarbormasterBuildArtifactQuery()) - ->setViewer($viewer) - ->withBuildTargetPHIDs(array($build_target->getPHID())) - ->execute(); - - $list = id(new PHUIObjectItemListView()) - ->setNoDataString(pht('This target has no associated artifacts.')) - ->setFlush(true); + HarbormasterBuildTarget $build_target, + array $artifacts) { + $viewer = $this->getViewer(); + $rows = array(); foreach ($artifacts as $artifact) { - $item = $artifact->getObjectItemView($viewer); - if ($item !== null) { - $list->addItem($item); + $impl = $artifact->getArtifactImplementation(); + + if ($impl) { + $summary = $impl->renderArtifactSummary($viewer); + $type_name = $impl->getArtifactTypeName(); + } else { + $summary = pht(''); + $type_name = $artifact->getType(); } + + $rows[] = array( + $artifact->getArtifactKey(), + $type_name, + $summary, + ); } - return $list; + $table = id(new AphrontTableView($rows)) + ->setNoDataString(pht('This target has no associated artifacts.')) + ->setHeaders( + array( + pht('Key'), + pht('Type'), + pht('Summary'), + )) + ->setColumnClasses( + array( + 'pri', + '', + 'wide', + )); + + return $table; } private function buildLog( diff --git a/src/applications/harbormaster/engine/HarbormasterBuildEngine.php b/src/applications/harbormaster/engine/HarbormasterBuildEngine.php index fd895979cd..f2ed44bb0d 100644 --- a/src/applications/harbormaster/engine/HarbormasterBuildEngine.php +++ b/src/applications/harbormaster/engine/HarbormasterBuildEngine.php @@ -483,7 +483,7 @@ final class HarbormasterBuildEngine extends Phobject { ->execute(); foreach ($artifacts as $artifact) { - $artifact->release(); + $artifact->releaseArtifact(); } } diff --git a/src/applications/harbormaster/event/HarbormasterUIEventListener.php b/src/applications/harbormaster/event/HarbormasterUIEventListener.php index d0a79f4b12..82ffcad560 100644 --- a/src/applications/harbormaster/event/HarbormasterUIEventListener.php +++ b/src/applications/harbormaster/event/HarbormasterUIEventListener.php @@ -16,7 +16,7 @@ final class HarbormasterUIEventListener } private function handlePropertyEvent($ui_event) { - $user = $ui_event->getUser(); + $viewer = $ui_event->getUser(); $object = $ui_event->getValue('object'); if (!$object || !$object->getPHID()) { @@ -52,10 +52,11 @@ final class HarbormasterUIEventListener } $buildable = id(new HarbormasterBuildableQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withManualBuildables(false) ->withBuildablePHIDs(array($buildable_phid)) ->needBuilds(true) + ->needTargets(true) ->executeOne(); if (!$buildable) { return; @@ -63,10 +64,26 @@ final class HarbormasterUIEventListener $builds = $buildable->getBuilds(); - $build_handles = id(new PhabricatorHandleQuery()) - ->setViewer($user) - ->withPHIDs(mpull($builds, 'getPHID')) - ->execute(); + $targets = array(); + foreach ($builds as $build) { + foreach ($build->getBuildTargets() as $target) { + $targets[] = $target; + } + } + + if ($targets) { + $artifacts = id(new HarbormasterBuildArtifactQuery()) + ->setViewer($viewer) + ->withBuildTargetPHIDs(mpull($targets, 'getPHID')) + ->withArtifactTypes( + array( + HarbormasterURIArtifact::ARTIFACTCONST, + )) + ->execute(); + $artifacts = mgroup($artifacts, 'getBuildTargetPHID'); + } else { + $artifacts = array(); + } $status_view = new PHUIStatusListView(); @@ -87,6 +104,7 @@ final class HarbormasterUIEventListener $target = phutil_tag('strong', array(), $target); + $status_view ->addItem( id(new PHUIStatusItemView()) @@ -95,7 +113,23 @@ final class HarbormasterUIEventListener foreach ($builds as $build) { $item = new PHUIStatusItemView(); - $item->setTarget($build_handles[$build->getPHID()]->renderLink()); + $item->setTarget($viewer->renderHandle($build->getPHID())); + + $links = array(); + foreach ($build->getBuildTargets() as $build_target) { + $uris = idx($artifacts, $build_target->getPHID(), array()); + foreach ($uris as $uri) { + $impl = $uri->getArtifactImplementation(); + if ($impl->isExternalLink()) { + $links[] = $impl->renderLink(); + } + } + } + + if ($links) { + $links = phutil_implode_html(" \xC2\xB7 ", $links); + $item->setNote($links); + } $status = $build->getBuildStatus(); $status_name = HarbormasterBuild::getBuildStatusName($status); @@ -104,7 +138,6 @@ final class HarbormasterUIEventListener $item->setIcon($icon, $color, $status_name); - $status_view->addItem($item); } diff --git a/src/applications/harbormaster/phid/HarbormasterBuildArtifactPHIDType.php b/src/applications/harbormaster/phid/HarbormasterBuildArtifactPHIDType.php new file mode 100644 index 0000000000..5c3eb992f7 --- /dev/null +++ b/src/applications/harbormaster/phid/HarbormasterBuildArtifactPHIDType.php @@ -0,0 +1,35 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $artifact = $objects[$phid]; + $artifact_id = $artifact->getID(); + $handle->setName(pht('Build Artifact %d', $artifact_id)); + } + } + +} diff --git a/src/applications/harbormaster/query/HarbormasterBuildArtifactQuery.php b/src/applications/harbormaster/query/HarbormasterBuildArtifactQuery.php index 553780aa76..8f50aa1bb2 100644 --- a/src/applications/harbormaster/query/HarbormasterBuildArtifactQuery.php +++ b/src/applications/harbormaster/query/HarbormasterBuildArtifactQuery.php @@ -6,7 +6,7 @@ final class HarbormasterBuildArtifactQuery private $ids; private $buildTargetPHIDs; private $artifactTypes; - private $artifactKeys; + private $artifactIndexes; private $keyBuildPHID; private $keyBuildGeneration; @@ -25,29 +25,17 @@ final class HarbormasterBuildArtifactQuery return $this; } - public function withArtifactKeys( - $build_phid, - $build_gen, - array $artifact_keys) { - $this->keyBuildPHID = $build_phid; - $this->keyBuildGeneration = $build_gen; - $this->artifactKeys = $artifact_keys; + public function withArtifactIndexes(array $artifact_indexes) { + $this->artifactIndexes = $artifact_indexes; return $this; } + public function newResultObject() { + return new HarbormasterBuildArtifact(); + } + protected function loadPage() { - $table = new HarbormasterBuildArtifact(); - $conn_r = $table->establishConnection('r'); - - $data = queryfx_all( - $conn_r, - 'SELECT * FROM %T %Q %Q %Q', - $table->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - - return $table->loadAllFromArray($data); + return $this->loadStandardPage($this->newResultObject()); } protected function willFilterPage(array $page) { @@ -75,46 +63,38 @@ final class HarbormasterBuildArtifactQuery return $page; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); - if ($this->ids) { + if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } - if ($this->buildTargetPHIDs) { + if ($this->buildTargetPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'buildTargetPHID IN (%Ls)', $this->buildTargetPHIDs); } - if ($this->artifactTypes) { + if ($this->artifactTypes !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'artifactType in (%Ls)', $this->artifactTypes); } - if ($this->artifactKeys) { - $indexes = array(); - foreach ($this->artifactKeys as $key) { - $indexes[] = PhabricatorHash::digestForIndex( - $this->keyBuildPHID.$this->keyBuildGeneration.$key); - } - + if ($this->artifactIndexes !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'artifactIndex IN (%Ls)', - $indexes); + $this->artifactIndexes); } - $where[] = $this->buildPagingClause($conn_r); - - return $this->formatWhereClause($where); + return $where; } public function getQueryApplicationClass() { diff --git a/src/applications/harbormaster/query/HarbormasterBuildQuery.php b/src/applications/harbormaster/query/HarbormasterBuildQuery.php index d0b1e7420c..1ee1ecb3a8 100644 --- a/src/applications/harbormaster/query/HarbormasterBuildQuery.php +++ b/src/applications/harbormaster/query/HarbormasterBuildQuery.php @@ -40,19 +40,12 @@ final class HarbormasterBuildQuery return $this; } + public function newResultObject() { + return new HarbormasterBuild(); + } + protected function loadPage() { - $table = new HarbormasterBuild(); - $conn_r = $table->establishConnection('r'); - - $data = queryfx_all( - $conn_r, - 'SELECT * FROM %T %Q %Q %Q', - $table->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - - return $table->loadAllFromArray($data); + return $this->loadStandardPage($this->newResultObject()); } protected function willFilterPage(array $page) { @@ -136,47 +129,45 @@ final class HarbormasterBuildQuery return $page; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'phid in (%Ls)', $this->phids); } if ($this->buildStatuses !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'buildStatus in (%Ls)', $this->buildStatuses); } if ($this->buildablePHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'buildablePHID IN (%Ls)', $this->buildablePHIDs); } if ($this->buildPlanPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'buildPlanPHID IN (%Ls)', $this->buildPlanPHIDs); } - $where[] = $this->buildPagingClause($conn_r); - - return $this->formatWhereClause($where); + return $where; } public function getQueryApplicationClass() { diff --git a/src/applications/harbormaster/query/HarbormasterBuildTargetQuery.php b/src/applications/harbormaster/query/HarbormasterBuildTargetQuery.php index c61c0bd8b0..6872be835b 100644 --- a/src/applications/harbormaster/query/HarbormasterBuildTargetQuery.php +++ b/src/applications/harbormaster/query/HarbormasterBuildTargetQuery.php @@ -34,55 +34,46 @@ final class HarbormasterBuildTargetQuery return $this; } - protected function loadPage() { - $table = new HarbormasterBuildTarget(); - $conn_r = $table->establishConnection('r'); - - $data = queryfx_all( - $conn_r, - 'SELECT * FROM %T %Q %Q %Q', - $table->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - - return $table->loadAllFromArray($data); + public function newResultObject() { + return new HarbormasterBuildTarget(); } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); + } - if ($this->ids) { + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); + + if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } - if ($this->phids) { + if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'phid in (%Ls)', $this->phids); } - if ($this->buildPHIDs) { + if ($this->buildPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'buildPHID in (%Ls)', $this->buildPHIDs); } - if ($this->buildGenerations) { + if ($this->buildGenerations !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'buildGeneration in (%Ld)', $this->buildGenerations); } - $where[] = $this->buildPagingClause($conn_r); - - return $this->formatWhereClause($where); + return $where; } protected function didFilterPage(array $page) { diff --git a/src/applications/harbormaster/step/HarbormasterCommandBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterCommandBuildStepImplementation.php index 931ade75a7..a99c56d0a3 100644 --- a/src/applications/harbormaster/step/HarbormasterCommandBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterCommandBuildStepImplementation.php @@ -39,13 +39,14 @@ final class HarbormasterCommandBuildStepImplementation public function execute( HarbormasterBuild $build, HarbormasterBuildTarget $build_target) { + $viewer = PhabricatorUser::getOmnipotentUser(); $settings = $this->getSettings(); $variables = $build_target->getVariables(); - $artifact = $build->loadArtifact($settings['hostartifact']); - - $lease = $artifact->loadDrydockLease(); + $artifact = $build_target->loadArtifact($settings['hostartifact']); + $impl = $artifact->getArtifactImplementation(); + $lease = $impl->loadArtifactLease($viewer); $this->platform = $lease->getAttribute('platform'); @@ -120,9 +121,9 @@ final class HarbormasterCommandBuildStepImplementation public function getArtifactInputs() { return array( array( - 'name' => pht('Run on Host'), - 'key' => $this->getSetting('hostartifact'), - 'type' => HarbormasterBuildArtifact::TYPE_HOST, + 'name' => pht('Run on Host'), + 'key' => $this->getSetting('hostartifact'), + 'type' => HarbormasterHostArtifact::ARTIFACTCONST, ), ); } diff --git a/src/applications/harbormaster/step/HarbormasterLeaseHostBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterLeaseHostBuildStepImplementation.php index d5f461506a..127025fd26 100644 --- a/src/applications/harbormaster/step/HarbormasterLeaseHostBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterLeaseHostBuildStepImplementation.php @@ -36,14 +36,13 @@ final class HarbormasterLeaseHostBuildStepImplementation $lease->waitUntilActive(); // Create the associated artifact. - $artifact = $build->createArtifact( - $build_target, + $artifact = $build_target->createArtifact( + PhabricatorUser::getOmnipotentUser(), $settings['name'], - HarbormasterBuildArtifact::TYPE_HOST); - $artifact->setArtifactData(array( - 'drydock-lease' => $lease->getID(), - )); - $artifact->save(); + HarbormasterHostArtifact::ARTIFACTCONST, + array( + 'drydockLeasePHID' => $lease->getPHID(), + )); } public function getArtifactOutputs() { @@ -51,7 +50,7 @@ final class HarbormasterLeaseHostBuildStepImplementation array( 'name' => pht('Leased Host'), 'key' => $this->getSetting('name'), - 'type' => HarbormasterBuildArtifact::TYPE_HOST, + 'type' => HarbormasterHostArtifact::ARTIFACTCONST, ), ); } diff --git a/src/applications/harbormaster/step/HarbormasterPublishFragmentBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterPublishFragmentBuildStepImplementation.php index dc59717a23..fc0c830150 100644 --- a/src/applications/harbormaster/step/HarbormasterPublishFragmentBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterPublishFragmentBuildStepImplementation.php @@ -29,33 +29,34 @@ final class HarbormasterPublishFragmentBuildStepImplementation $settings = $this->getSettings(); $variables = $build_target->getVariables(); + $viewer = PhabricatorUser::getOmnipotentUser(); $path = $this->mergeVariables( 'vsprintf', $settings['path'], $variables); - $artifact = $build->loadArtifact($settings['artifact']); - - $file = $artifact->loadPhabricatorFile(); + $artifact = $build_target->loadArtifact($settings['artifact']); + $impl = $artifact->getArtifactImplementation(); + $file = $impl->loadArtifactFile($viewer); $fragment = id(new PhragmentFragmentQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->setViewer($viewer) ->withPaths(array($path)) ->executeOne(); if ($fragment === null) { PhragmentFragment::createFromFile( - PhabricatorUser::getOmnipotentUser(), + $viewer, $file, $path, PhabricatorPolicies::getMostOpenPolicy(), PhabricatorPolicies::POLICY_USER); } else { if ($file->getMimeType() === 'application/zip') { - $fragment->updateFromZIP(PhabricatorUser::getOmnipotentUser(), $file); + $fragment->updateFromZIP($viewer, $file); } else { - $fragment->updateFromFile(PhabricatorUser::getOmnipotentUser(), $file); + $fragment->updateFromFile($viewer, $file); } } } @@ -65,7 +66,7 @@ final class HarbormasterPublishFragmentBuildStepImplementation array( 'name' => pht('Publishes File'), 'key' => $this->getSetting('artifact'), - 'type' => HarbormasterBuildArtifact::TYPE_FILE, + 'type' => HarbormasterFileArtifact::ARTIFACTCONST, ), ); } diff --git a/src/applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php index 75b48eb3c6..f8bdb7fdfd 100644 --- a/src/applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php @@ -25,6 +25,7 @@ final class HarbormasterUploadArtifactBuildStepImplementation public function execute( HarbormasterBuild $build, HarbormasterBuildTarget $build_target) { + $viewer = PhabricatorUser::getOmnipotentUser(); $settings = $this->getSettings(); $variables = $build_target->getVariables(); @@ -34,9 +35,9 @@ final class HarbormasterUploadArtifactBuildStepImplementation $settings['path'], $variables); - $artifact = $build->loadArtifact($settings['hostartifact']); - - $lease = $artifact->loadDrydockLease(); + $artifact = $build_target->loadArtifact($settings['hostartifact']); + $impl = $artifact->getArtifactImplementation(); + $lease = $impl->loadArtifactLease($viewer); $interface = $lease->getInterface('filesystem'); @@ -44,14 +45,13 @@ final class HarbormasterUploadArtifactBuildStepImplementation $file = $interface->saveFile($path, $settings['name']); // Insert the artifact record. - $artifact = $build->createArtifact( - $build_target, + $artifact = $build_target->createArtifact( + $viewer, $settings['name'], - HarbormasterBuildArtifact::TYPE_FILE); - $artifact->setArtifactData(array( - 'filePHID' => $file->getPHID(), - )); - $artifact->save(); + HarbormasterFileArtifact::ARTIFACTCONST, + array( + 'filePHID' => $file->getPHID(), + )); } public function getArtifactInputs() { @@ -59,7 +59,7 @@ final class HarbormasterUploadArtifactBuildStepImplementation array( 'name' => pht('Upload From Host'), 'key' => $this->getSetting('hostartifact'), - 'type' => HarbormasterBuildArtifact::TYPE_HOST, + 'type' => HarbormasterHostArtifact::ARTIFACTCONST, ), ); } @@ -69,7 +69,7 @@ final class HarbormasterUploadArtifactBuildStepImplementation array( 'name' => pht('Uploaded File'), 'key' => $this->getSetting('name'), - 'type' => HarbormasterBuildArtifact::TYPE_FILE, + 'type' => HarbormasterHostArtifact::ARTIFACTCONST, ), ); } diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuild.php b/src/applications/harbormaster/storage/build/HarbormasterBuild.php index 5ea9f2f07b..e35744ecc9 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuild.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuild.php @@ -235,36 +235,6 @@ final class HarbormasterBuild extends HarbormasterDAO return $log; } - public function createArtifact( - HarbormasterBuildTarget $build_target, - $artifact_key, - $artifact_type) { - - $artifact = - HarbormasterBuildArtifact::initializeNewBuildArtifact($build_target); - $artifact->setArtifactKey( - $this->getPHID(), - $this->getBuildGeneration(), - $artifact_key); - $artifact->setArtifactType($artifact_type); - $artifact->save(); - return $artifact; - } - - public function loadArtifact($name) { - $artifact = id(new HarbormasterBuildArtifactQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withArtifactKeys( - $this->getPHID(), - $this->getBuildGeneration(), - array($name)) - ->executeOne(); - if ($artifact === null) { - throw new Exception(pht('Artifact not found!')); - } - return $artifact; - } - public function retrieveVariablesFromBuild() { $results = array( 'buildable.diff' => null, diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php b/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php index f96ed6243c..0b195db88f 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php @@ -10,19 +10,18 @@ final class HarbormasterBuildArtifact extends HarbormasterDAO protected $artifactData = array(); private $buildTarget = self::ATTACHABLE; - - const TYPE_FILE = 'file'; - const TYPE_HOST = 'host'; - const TYPE_URI = 'uri'; + private $artifactImplementation; public static function initializeNewBuildArtifact( HarbormasterBuildTarget $build_target) { return id(new HarbormasterBuildArtifact()) + ->attachBuildTarget($build_target) ->setBuildTargetPHID($build_target->getPHID()); } protected function getConfiguration() { return array( + self::CONFIG_AUX_PHID => true, self::CONFIG_SERIALIZATION => array( 'artifactData' => self::SERIALIZATION_JSON, ), @@ -43,6 +42,11 @@ final class HarbormasterBuildArtifact extends HarbormasterDAO ) + parent::getConfiguration(); } + public function generatePHID() { + return PhabricatorPHID::generateNewPHID( + HarbormasterBuildArtifactPHIDType::TYPECONST); + } + public function attachBuildTarget(HarbormasterBuildTarget $build_target) { $this->buildTarget = $build_target; return $this; @@ -52,113 +56,58 @@ final class HarbormasterBuildArtifact extends HarbormasterDAO return $this->assertAttached($this->buildTarget); } - public function setArtifactKey($build_phid, $build_gen, $key) { - $this->artifactIndex = - PhabricatorHash::digestForIndex($build_phid.$build_gen.$key); + public function setArtifactKey($key) { + $target = $this->getBuildTarget(); + $this->artifactIndex = self::getArtifactIndex($target, $key); $this->artifactKey = $key; return $this; } - public function getObjectItemView(PhabricatorUser $viewer) { - $data = $this->getArtifactData(); - switch ($this->getArtifactType()) { - case self::TYPE_FILE: - $handle = id(new PhabricatorHandleQuery()) - ->setViewer($viewer) - ->withPHIDs($data) - ->executeOne(); + public static function getArtifactIndex( + HarbormasterBuildTarget $target, + $artifact_key) { - return id(new PHUIObjectItemView()) - ->setObjectName(pht('File')) - ->setHeader($handle->getFullName()) - ->setHref($handle->getURI()); - case self::TYPE_HOST: - $leases = id(new DrydockLeaseQuery()) - ->setViewer($viewer) - ->withIDs(array($data['drydock-lease'])) - ->execute(); - $lease = idx($leases, $data['drydock-lease']); + $build = $target->getBuild(); - return id(new PHUIObjectItemView()) - ->setObjectName(pht('Drydock Lease')) - ->setHeader($lease->getID()) - ->setHref('/drydock/lease/'.$lease->getID()); - case self::TYPE_URI: - return id(new PHUIObjectItemView()) - ->setObjectName($data['name']) - ->setHeader($data['uri']) - ->setHref($data['uri']); - default: + $parts = array( + $build->getPHID(), + $target->getBuildGeneration(), + $artifact_key, + ); + $parts = implode("\0", $parts); + + return PhabricatorHash::digestForIndex($parts); + } + + public function releaseArtifact() { + $impl = $this->getArtifactImplementation(); + + if ($impl) { + $impl->releaseArtifact(PhabricatorUser::getOmnipotentUser()); + } + + return null; + } + + public function getArtifactImplementation() { + if ($this->artifactImplementation === null) { + $type = $this->getArtifactType(); + $impl = HarbormasterArtifact::getArtifactType($type); + if (!$impl) { return null; + } + + $impl = clone $impl; + $impl->setBuildArtifact($this); + $this->artifactImplementation = $impl; } + + return $this->artifactImplementation; } - public function loadDrydockLease() { - if ($this->getArtifactType() !== self::TYPE_HOST) { - throw new Exception( - pht( - '`%s` may only be called on host artifacts.', - __FUNCTION__)); - } - $data = $this->getArtifactData(); - - // FIXME: Is there a better way of doing this? - // TODO: Policy stuff, etc. - $lease = id(new DrydockLease())->load( - $data['drydock-lease']); - if ($lease === null) { - throw new Exception(pht('Associated Drydock lease not found!')); - } - $resource = id(new DrydockResource())->load( - $lease->getResourceID()); - if ($resource === null) { - throw new Exception(pht('Associated Drydock resource not found!')); - } - $lease->attachResource($resource); - - return $lease; - } - - public function loadPhabricatorFile() { - if ($this->getArtifactType() !== self::TYPE_FILE) { - throw new Exception( - pht( - '`%s` may only be called on file artifacts.', - __FUNCTION__)); - } - - $data = $this->getArtifactData(); - - // The data for TYPE_FILE is an array with a single PHID in it. - $phid = $data['filePHID']; - - $file = id(new PhabricatorFileQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withPHIDs(array($phid)) - ->executeOne(); - if ($file === null) { - throw new Exception(pht('Associated file not found!')); - } - return $file; - } - - public function release() { - switch ($this->getArtifactType()) { - case self::TYPE_HOST: - $this->releaseDrydockLease(); - break; - } - } - - public function releaseDrydockLease() { - $lease = $this->loadDrydockLease(); - $resource = $lease->getResource(); - $blueprint = $resource->getBlueprint(); - - if ($lease->isActive()) { - $blueprint->releaseLease($resource, $lease); - } + public function getProperty($key, $default = null) { + return idx($this->artifactData, $key, $default); } diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php b/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php index b2e0a2aa83..7f08292787 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php @@ -201,6 +201,54 @@ final class HarbormasterBuildTarget extends HarbormasterDAO ); } + public function createArtifact( + PhabricatorUser $actor, + $artifact_key, + $artifact_type, + array $artifact_data) { + + $impl = HarbormasterArtifact::getArtifactType($artifact_type); + if (!$impl) { + throw new Exception( + pht( + 'There is no implementation available for artifacts of type "%s".', + $artifact_type)); + } + + $impl->validateArtifactData($artifact_data); + + $artifact = HarbormasterBuildArtifact::initializeNewBuildArtifact($this) + ->setArtifactKey($artifact_key) + ->setArtifactType($artifact_type) + ->setArtifactData($artifact_data); + + $impl = $artifact->getArtifactImplementation(); + $impl->willCreateArtifact($actor); + + return $artifact->save(); + } + + public function loadArtifact($artifact_key) { + $indexes = array(); + + $indexes[] = HarbormasterBuildArtifact::getArtifactIndex( + $this, + $artifact_key); + + $artifact = id(new HarbormasterBuildArtifactQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withArtifactIndexes($indexes) + ->executeOne(); + if ($artifact === null) { + throw new Exception( + pht( + 'Artifact "%s" not found!', + $artifact_key)); + } + + return $artifact; + } + /* -( Status )------------------------------------------------------------- */ diff --git a/src/applications/herald/controller/HeraldTestConsoleController.php b/src/applications/herald/controller/HeraldTestConsoleController.php index 9b5091664f..64b7c5867b 100644 --- a/src/applications/herald/controller/HeraldTestConsoleController.php +++ b/src/applications/herald/controller/HeraldTestConsoleController.php @@ -48,6 +48,9 @@ final class HeraldTestConsoleController extends HeraldController { } else if ($object instanceof PonderQuestion) { $adapter = id(new HeraldPonderQuestionAdapter()) ->setQuestion($object); + } else if ($object instanceof PhabricatorMetaMTAMail) { + $adapter = id(new PhabricatorMailOutboundMailHeraldAdapter()) + ->setObject($object); } else { throw new Exception(pht('Can not build adapter for object!')); } diff --git a/src/applications/herald/controller/HeraldTranscriptController.php b/src/applications/herald/controller/HeraldTranscriptController.php index 64563b7ef8..3e4b0b074c 100644 --- a/src/applications/herald/controller/HeraldTranscriptController.php +++ b/src/applications/herald/controller/HeraldTranscriptController.php @@ -356,7 +356,17 @@ final class HeraldTranscriptController extends HeraldController { // Handle older transcripts which used a static string to record // action results. - if (!is_array($log)) { + + if ($xscript->getDryRun()) { + $action_list->addItem( + id(new PHUIStatusItemView()) + ->setIcon('fa-ban', 'grey') + ->setTarget(pht('Dry Run')) + ->setNote( + pht( + 'This was a dry run, so no actions were taken.'))); + continue; + } else if (!is_array($log)) { $action_list->addItem( id(new PHUIStatusItemView()) ->setIcon('fa-clock-o', 'grey') diff --git a/src/applications/macro/query/PhabricatorMacroSearchEngine.php b/src/applications/macro/query/PhabricatorMacroSearchEngine.php index 2000b3275a..039f396d5f 100644 --- a/src/applications/macro/query/PhabricatorMacroSearchEngine.php +++ b/src/applications/macro/query/PhabricatorMacroSearchEngine.php @@ -111,7 +111,9 @@ final class PhabricatorMacroSearchEngine switch ($query_key) { case 'active': - return $query; + return $query->setParameter( + 'status', + PhabricatorMacroQuery::STATUS_ACTIVE); case 'all': return $query->setParameter( 'status', diff --git a/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php b/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php index c669e2f858..d92f562911 100644 --- a/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php +++ b/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php @@ -25,7 +25,7 @@ final class PhabricatorManiphestConfigOptions 100 => array( 'name' => pht('Unbreak Now!'), 'short' => pht('Unbreak!'), - 'color' => 'indigo', + 'color' => 'pink', 'keywords' => array('unbreak'), ), 90 => array( diff --git a/src/applications/metamta/constants/PhabricatorMailRoutingRule.php b/src/applications/metamta/constants/PhabricatorMailRoutingRule.php new file mode 100644 index 0000000000..1e0e1b9e26 --- /dev/null +++ b/src/applications/metamta/constants/PhabricatorMailRoutingRule.php @@ -0,0 +1,51 @@ + $strength_v); + } + + public static function getRuleStrength($const) { + $strength = array( + self::ROUTE_AS_NOTIFICATION => 1, + self::ROUTE_AS_MAIL => 2, + ); + + return idx($strength, $const, 0); + } + + public static function getRuleName($const) { + $names = array( + self::ROUTE_AS_NOTIFICATION => pht('Route as Notification'), + self::ROUTE_AS_MAIL => pht('Route as Mail'), + ); + + return idx($names, $const, $const); + } + + public static function getRuleIcon($const) { + $icons = array( + self::ROUTE_AS_NOTIFICATION => 'fa-bell', + self::ROUTE_AS_MAIL => 'fa-envelope', + ); + + return idx($icons, $const, 'fa-question-circle'); + } + + public static function getRuleColor($const) { + $colors = array( + self::ROUTE_AS_NOTIFICATION => 'grey', + self::ROUTE_AS_MAIL => 'grey', + ); + + return idx($colors, $const, 'yellow'); + } + +} diff --git a/src/applications/metamta/controller/PhabricatorMetaMTAMailViewController.php b/src/applications/metamta/controller/PhabricatorMetaMTAMailViewController.php index faaa08e472..7cb1ad71d6 100644 --- a/src/applications/metamta/controller/PhabricatorMetaMTAMailViewController.php +++ b/src/applications/metamta/controller/PhabricatorMetaMTAMailViewController.php @@ -86,6 +86,10 @@ final class PhabricatorMetaMTAMailViewController pht('Cc'), $cc_list); + $properties->addProperty( + pht('Sent'), + phabricator_datetime($mail->getDateCreated(), $viewer)); + $properties->addSectionHeader( pht('Message'), PHUIPropertyListView::ICON_SUMMARY); @@ -144,23 +148,16 @@ final class PhabricatorMetaMTAMailViewController $actors = $mail->getDeliveredActors(); $reasons = null; if (!$actors) { - // TODO: We can get rid of this special-cased message after these changes - // have been live for a while, but provide a more tailored message for - // now so things are a little less confusing for users. - if ($mail->getStatus() == PhabricatorMetaMTAMail::STATUS_SENT) { - $delivery = phutil_tag( - 'em', - array(), - pht( - 'This is an older message that predates recording delivery '. - 'information, so none is available.')); - } else { - $delivery = phutil_tag( - 'em', - array(), + if ($mail->getStatus() == PhabricatorMailOutboundStatus::STATUS_QUEUE) { + $delivery = $this->renderEmptyMessage( pht( 'This message has not been delivered yet, so delivery information '. 'is not available.')); + } else { + $delivery = $this->renderEmptyMessage( + pht( + 'This is an older message that predates recording delivery '. + 'information, so none is available.')); } } else { $actor = idx($actors, $viewer->getPHID()); @@ -214,6 +211,127 @@ final class PhabricatorMetaMTAMailViewController $properties->addProperty(pht('Delivery'), $delivery); if ($reasons) { $properties->addProperty(pht('Reasons'), $reasons); + $properties->addProperty( + null, + $this->renderEmptyMessage( + pht( + 'Delivery reasons are listed from weakest to strongest.'))); + } + + $properties->addSectionHeader(pht('Routing Rules')); + + $map = $mail->getDeliveredRoutingMap(); + $routing_detail = null; + if ($map === null) { + if ($mail->getStatus() == PhabricatorMailOutboundStatus::STATUS_QUEUE) { + $routing_result = $this->renderEmptyMessage( + pht( + 'This message has not been sent yet, so routing rules have '. + 'not been computed.')); + } else { + $routing_result = $this->renderEmptyMessage( + pht( + 'This is an older message which predates routing rules.')); + } + } else { + $rule = idx($map, $viewer->getPHID()); + if ($rule === null) { + $rule = idx($map, 'default'); + } + + if ($rule === null) { + $routing_result = $this->renderEmptyMessage( + pht( + 'No routing rules applied when delivering this message to you.')); + } else { + $rule_const = $rule['rule']; + $reason_phid = $rule['reason']; + switch ($rule_const) { + case PhabricatorMailRoutingRule::ROUTE_AS_NOTIFICATION: + $routing_result = pht( + 'This message was routed as a notification because it '. + 'matched %s.', + $viewer->renderHandle($reason_phid)->render()); + break; + case PhabricatorMailRoutingRule::ROUTE_AS_MAIL: + $routing_result = pht( + 'This message was routed as an email because it matched %s.', + $viewer->renderHandle($reason_phid)->render()); + break; + default: + $routing_result = pht('Unknown routing rule "%s".', $rule_const); + break; + } + } + + $routing_rules = $mail->getDeliveredRoutingRules(); + if ($routing_rules) { + $rules = array(); + foreach ($routing_rules as $rule) { + $phids = idx($rule, 'phids'); + if ($phids === null) { + $rules[] = $rule; + } else if (in_array($viewer->getPHID(), $phids)) { + $rules[] = $rule; + } + } + + // Reorder rules by strength. + foreach ($rules as $key => $rule) { + $const = $rule['routingRule']; + $phids = $rule['phids']; + + if ($phids === null) { + $type = 'A'; + } else { + $type = 'B'; + } + + $rules[$key]['strength'] = sprintf( + '~%s%08d', + $type, + PhabricatorMailRoutingRule::getRuleStrength($const)); + } + $rules = isort($rules, 'strength'); + + $routing_detail = id(new PHUIStatusListView()); + foreach ($rules as $rule) { + $const = $rule['routingRule']; + $phids = $rule['phids']; + + $name = PhabricatorMailRoutingRule::getRuleName($const); + + $icon = PhabricatorMailRoutingRule::getRuleIcon($const); + $color = PhabricatorMailRoutingRule::getRuleColor($const); + + if ($phids === null) { + $kind = pht('Global'); + } else { + $kind = pht('Personal'); + } + + $target = array($kind, ': ', $name); + $target = phutil_tag('strong', array(), $target); + + $item = id(new PHUIStatusItemView()) + ->setTarget($target) + ->setNote($viewer->renderHandle($rule['reasonPHID'])) + ->setIcon($icon, $color); + + $routing_detail->addItem($item); + } + } + } + + $properties->addProperty(pht('Effective Rule'), $routing_result); + + if ($routing_detail !== null) { + $properties->addProperty(pht('All Matching Rules'), $routing_detail); + $properties->addProperty( + null, + $this->renderEmptyMessage( + pht( + 'Matching rules are listed from weakest to strongest.'))); } return $properties; @@ -252,4 +370,8 @@ final class PhabricatorMetaMTAMailViewController return $properties; } + private function renderEmptyMessage($message) { + return phutil_tag('em', array(), $message); + } + } diff --git a/src/applications/metamta/herald/PhabricatorMailEmailHeraldField.php b/src/applications/metamta/herald/PhabricatorMailEmailHeraldField.php new file mode 100644 index 0000000000..815ddef69a --- /dev/null +++ b/src/applications/metamta/herald/PhabricatorMailEmailHeraldField.php @@ -0,0 +1,14 @@ +getSubject(); + } + + protected function getHeraldFieldStandardType() { + return self::STANDARD_TEXT; + } + +} diff --git a/src/applications/metamta/herald/PhabricatorMailOutboundMailHeraldAdapter.php b/src/applications/metamta/herald/PhabricatorMailOutboundMailHeraldAdapter.php new file mode 100644 index 0000000000..43c0a63300 --- /dev/null +++ b/src/applications/metamta/herald/PhabricatorMailOutboundMailHeraldAdapter.php @@ -0,0 +1,62 @@ +mail = $this->newObject(); + } + + protected function newObject() { + return new PhabricatorMetaMTAMail(); + } + + public function getObject() { + return $this->mail; + } + + public function setObject(PhabricatorMetaMTAMail $mail) { + $this->mail = $mail; + return $this; + } + + public function getAdapterContentName() { + return pht('Outbound Mail'); + } + + public function isSingleEventAdapter() { + return true; + } + + public function getRepetitionOptions() { + return array( + HeraldRepetitionPolicyConfig::FIRST, + ); + } + + public function supportsRuleType($rule_type) { + switch ($rule_type) { + case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: + case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: + return true; + case HeraldRuleTypeConfig::RULE_TYPE_OBJECT: + default: + return false; + } + } + + public function getHeraldName() { + return pht('Mail %d', $this->getObject()->getID()); + } + +} diff --git a/src/applications/metamta/herald/PhabricatorMailOutboundRoutingHeraldAction.php b/src/applications/metamta/herald/PhabricatorMailOutboundRoutingHeraldAction.php new file mode 100644 index 0000000000..9813110538 --- /dev/null +++ b/src/applications/metamta/herald/PhabricatorMailOutboundRoutingHeraldAction.php @@ -0,0 +1,46 @@ +getAdapter(); + $mail = $adapter->getObject(); + $mail->addRoutingRule($route, $phids, $rule->getPHID()); + + $this->logEffect( + self::DO_ROUTE, + array( + 'route' => $route, + 'phids' => $phids, + )); + } + + protected function getActionEffectMap() { + return array( + self::DO_ROUTE => array( + 'icon' => 'fa-arrow-right', + 'color' => 'green', + 'name' => pht('Routed Message'), + ), + ); + } + + protected function renderActionEffectDescription($type, $data) { + switch ($type) { + case self::DO_ROUTE: + return pht('Routed mail.'); + } + } + +} diff --git a/src/applications/metamta/herald/PhabricatorMailOutboundRoutingSelfEmailHeraldAction.php b/src/applications/metamta/herald/PhabricatorMailOutboundRoutingSelfEmailHeraldAction.php new file mode 100644 index 0000000000..d39128cf67 --- /dev/null +++ b/src/applications/metamta/herald/PhabricatorMailOutboundRoutingSelfEmailHeraldAction.php @@ -0,0 +1,34 @@ +getRule(); + $author_phid = $rule->getAuthorPHID(); + + $this->applyRouting( + $rule, + PhabricatorMailRoutingRule::ROUTE_AS_MAIL, + array($author_phid)); + } + + public function getHeraldActionStandardType() { + return self::STANDARD_NONE; + } + + public function renderActionDescription($value) { + return pht('Deliver as email.'); + } + +} diff --git a/src/applications/metamta/herald/PhabricatorMailOutboundRoutingSelfNotificationHeraldAction.php b/src/applications/metamta/herald/PhabricatorMailOutboundRoutingSelfNotificationHeraldAction.php new file mode 100644 index 0000000000..24838b7a79 --- /dev/null +++ b/src/applications/metamta/herald/PhabricatorMailOutboundRoutingSelfNotificationHeraldAction.php @@ -0,0 +1,34 @@ +getRule(); + $author_phid = $rule->getAuthorPHID(); + + $this->applyRouting( + $rule, + PhabricatorMailRoutingRule::ROUTE_AS_NOTIFICATION, + array($author_phid)); + } + + public function getHeraldActionStandardType() { + return self::STANDARD_NONE; + } + + public function renderActionDescription($value) { + return pht('Deliver as notification.'); + } + +} diff --git a/src/applications/metamta/herald/PhabricatorMetaMTAEmailHeraldAction.php b/src/applications/metamta/herald/PhabricatorMetaMTAEmailHeraldAction.php index 835bde8d4d..ec7036ebe0 100644 --- a/src/applications/metamta/herald/PhabricatorMetaMTAEmailHeraldAction.php +++ b/src/applications/metamta/herald/PhabricatorMetaMTAEmailHeraldAction.php @@ -14,6 +14,10 @@ abstract class PhabricatorMetaMTAEmailHeraldAction return false; } + if ($object instanceof PhabricatorMetaMTAMail) { + return false; + } + return true; } diff --git a/src/applications/metamta/phid/PhabricatorMetaMTAMailPHIDType.php b/src/applications/metamta/phid/PhabricatorMetaMTAMailPHIDType.php index 8159045dbd..7117b50f8e 100644 --- a/src/applications/metamta/phid/PhabricatorMetaMTAMailPHIDType.php +++ b/src/applications/metamta/phid/PhabricatorMetaMTAMailPHIDType.php @@ -37,7 +37,7 @@ final class PhabricatorMetaMTAMailPHIDType extends PhabricatorPHIDType { $handle ->setName($name) - ->setFullName($name); + ->setURI('/mail/detail/'.$id.'/'); } } } diff --git a/src/applications/metamta/query/PhabricatorMetaMTAActor.php b/src/applications/metamta/query/PhabricatorMetaMTAActor.php index 59e3f297de..a15a5b2390 100644 --- a/src/applications/metamta/query/PhabricatorMetaMTAActor.php +++ b/src/applications/metamta/query/PhabricatorMetaMTAActor.php @@ -18,6 +18,8 @@ final class PhabricatorMetaMTAActor extends Phobject { const REASON_BOT = 'bot'; const REASON_FORCE = 'force'; const REASON_FORCE_HERALD = 'force-herald'; + const REASON_ROUTE_AS_NOTIFICATION = 'route-as-notification'; + const REASON_ROUTE_AS_MAIL = 'route-as-mail'; private $phid; private $emailAddress; @@ -77,6 +79,7 @@ final class PhabricatorMetaMTAActor extends Phobject { case self::REASON_NONE: case self::REASON_FORCE: case self::REASON_FORCE_HERALD: + case self::REASON_ROUTE_AS_MAIL: return true; default: // All other reasons cause the message to not be delivered. @@ -99,6 +102,8 @@ final class PhabricatorMetaMTAActor extends Phobject { self::REASON_UNLOADABLE => pht('Bad Recipient'), self::REASON_FORCE => pht('Forced Mail'), self::REASON_FORCE_HERALD => pht('Forced by Herald'), + self::REASON_ROUTE_AS_NOTIFICATION => pht('Route as Notification'), + self::REASON_ROUTE_AS_MAIL => pht('Route as Mail'), ); return idx($names, $reason, pht('Unknown ("%s")', $reason)); @@ -147,6 +152,12 @@ final class PhabricatorMetaMTAActor extends Phobject { self::REASON_FORCE_HERALD => pht( 'This recipient was added by a "Send me an Email" rule in Herald, '. 'which overrides some delivery settings.'), + self::REASON_ROUTE_AS_NOTIFICATION => pht( + 'This message was downgraded to a notification by outbound mail '. + 'rules in Herald.'), + self::REASON_ROUTE_AS_MAIL => pht( + 'This message was upgraded to email by outbound mail rules '. + 'in Herald.'), ); return idx($descriptions, $reason, pht('Unknown Reason ("%s")', $reason)); diff --git a/src/applications/metamta/storage/PhabricatorMetaMTAMail.php b/src/applications/metamta/storage/PhabricatorMetaMTAMail.php index b68ac93c5c..abcbb7a082 100644 --- a/src/applications/metamta/storage/PhabricatorMetaMTAMail.php +++ b/src/applications/metamta/storage/PhabricatorMetaMTAMail.php @@ -16,6 +16,7 @@ final class PhabricatorMetaMTAMail protected $relatedPHID; private $recipientExpansionMap; + private $routingMap; public function __construct() { @@ -656,6 +657,9 @@ final class PhabricatorMetaMTAMail } $this->setParam('actors.sent', $actor_list); + $this->setParam('routing.sent', $this->getParam('routing')); + $this->setParam('routingmap.sent', $this->getRoutingRuleMap()); + if (!$add_to && !$add_cc) { $this->setStatus(PhabricatorMailOutboundStatus::STATUS_VOID); $this->setMessage( @@ -963,9 +967,25 @@ final class PhabricatorMetaMTAMail } } + foreach ($deliverable as $phid) { + switch ($this->getRoutingRule($phid)) { + case PhabricatorMailRoutingRule::ROUTE_AS_NOTIFICATION: + $actors[$phid]->setUndeliverable( + PhabricatorMetaMTAActor::REASON_ROUTE_AS_NOTIFICATION); + break; + case PhabricatorMailRoutingRule::ROUTE_AS_MAIL: + $actors[$phid]->setDeliverable( + PhabricatorMetaMTAActor::REASON_ROUTE_AS_MAIL); + break; + default: + // No change. + break; + } + } + // If recipients were initially deliverable and were added by "Send me an // email" Herald rules, annotate them as such and make them deliverable - // again, overriding any changes made by the "self mail" and "mail tags" + // again, overriding any changes made by the "self mail" and "mail tags" // settings. $force_recipients = $this->getForceHeraldMailRecipientPHIDs(); $force_recipients = array_fuse($force_recipients); @@ -1065,6 +1085,82 @@ final class PhabricatorMetaMTAMail return $this->getParam('actors.sent'); } + public function getDeliveredRoutingRules() { + return $this->getParam('routing.sent'); + } + + public function getDeliveredRoutingMap() { + return $this->getParam('routingmap.sent'); + } + + +/* -( Routing )------------------------------------------------------------ */ + + + public function addRoutingRule($routing_rule, $phids, $reason_phid) { + $routing = $this->getParam('routing', array()); + $routing[] = array( + 'routingRule' => $routing_rule, + 'phids' => $phids, + 'reasonPHID' => $reason_phid, + ); + $this->setParam('routing', $routing); + + // Throw the routing map away so we rebuild it. + $this->routingMap = null; + + return $this; + } + + private function getRoutingRule($phid) { + $map = $this->getRoutingRuleMap(); + + $info = idx($map, $phid, idx($map, 'default')); + if ($info) { + return idx($info, 'rule'); + } + + return null; + } + + private function getRoutingRuleMap() { + if ($this->routingMap === null) { + $map = array(); + + $routing = $this->getParam('routing', array()); + foreach ($routing as $route) { + $phids = $route['phids']; + if ($phids === null) { + $phids = array('default'); + } + + foreach ($phids as $phid) { + $new_rule = $route['routingRule']; + + $current_rule = idx($map, $phid); + if ($current_rule === null) { + $is_stronger = true; + } else { + $is_stronger = PhabricatorMailRoutingRule::isStrongerThan( + $new_rule, + $current_rule); + } + + if ($is_stronger) { + $map[$phid] = array( + 'rule' => $new_rule, + 'reason' => $route['reasonPHID'], + ); + } + } + } + + $this->routingMap = $map; + } + + return $this->routingMap; + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/owners/conduit/OwnersQueryConduitAPIMethod.php b/src/applications/owners/conduit/OwnersQueryConduitAPIMethod.php index 5473ec6e00..a3b7b52cab 100644 --- a/src/applications/owners/conduit/OwnersQueryConduitAPIMethod.php +++ b/src/applications/owners/conduit/OwnersQueryConduitAPIMethod.php @@ -110,7 +110,6 @@ final class OwnersQueryConduitAPIMethod extends OwnersConduitAPIMethod { 'phid' => $package->getPHID(), 'name' => $package->getName(), 'description' => $package->getDescription(), - 'primaryOwner' => $package->getPrimaryOwnerPHID(), 'owners' => $owners, 'paths' => $paths, ); @@ -142,7 +141,7 @@ final class OwnersQueryConduitAPIMethod extends OwnersConduitAPIMethod { $query = id(new PhabricatorOwnersPackageQuery()) ->setViewer($request->getUser()); - $query->withOwnerPHIDs(array($request->getValue('userAffiliated'))); + $query->withAuthorityPHIDs(array($request->getValue('userAffiliated'))); $packages = $query->execute(); } else if ($is_owner_query) { diff --git a/src/applications/owners/controller/PhabricatorOwnersDetailController.php b/src/applications/owners/controller/PhabricatorOwnersDetailController.php index 6ec31eadfc..19f305dc09 100644 --- a/src/applications/owners/controller/PhabricatorOwnersDetailController.php +++ b/src/applications/owners/controller/PhabricatorOwnersDetailController.php @@ -13,12 +13,14 @@ final class PhabricatorOwnersDetailController $package = id(new PhabricatorOwnersPackageQuery()) ->setViewer($viewer) ->withIDs(array($request->getURIData('id'))) + ->needPaths(true) + ->needOwners(true) ->executeOne(); if (!$package) { return new Aphront404Response(); } - $paths = $package->loadPaths(); + $paths = $package->getPaths(); $repository_phids = array(); foreach ($paths as $path) { @@ -39,9 +41,20 @@ final class PhabricatorOwnersDetailController $properties = $this->buildPackagePropertyView($package); $properties->setActionList($actions); + if ($package->isArchived()) { + $header_icon = 'fa-ban'; + $header_name = pht('Archived'); + $header_color = 'dark'; + } else { + $header_icon = 'fa-check'; + $header_name = pht('Active'); + $header_color = 'bluegrey'; + } + $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($package->getName()) + ->setStatus($header_icon, $header_color, $header_name) ->setPolicyObject($package); $panel = id(new PHUIObjectBoxView()) @@ -149,16 +162,7 @@ final class PhabricatorOwnersDetailController $view = id(new PHUIPropertyListView()) ->setUser($viewer); - $primary_phid = $package->getPrimaryOwnerPHID(); - if ($primary_phid) { - $primary_owner = $viewer->renderHandle($primary_phid); - } else { - $primary_owner = phutil_tag('em', array(), pht('None')); - } - $view->addProperty(pht('Primary Owner'), $primary_owner); - - // TODO: needOwners() this on the Query. - $owners = $package->loadOwners(); + $owners = $package->getOwners(); if ($owners) { $owner_list = $viewer->renderHandleList(mpull($owners, 'getUserPHID')); } else { diff --git a/src/applications/owners/controller/PhabricatorOwnersEditController.php b/src/applications/owners/controller/PhabricatorOwnersEditController.php index 9f1987814d..af5ae29be8 100644 --- a/src/applications/owners/controller/PhabricatorOwnersEditController.php +++ b/src/applications/owners/controller/PhabricatorOwnersEditController.php @@ -17,6 +17,7 @@ final class PhabricatorOwnersEditController // TODO: Support this capability. // PhabricatorPolicyCapability::CAN_EDIT, )) + ->needOwners(true) ->executeOne(); if (!$package) { return new Aphront404Response(); @@ -28,14 +29,12 @@ final class PhabricatorOwnersEditController } $e_name = true; - $e_primary = true; $v_name = $package->getName(); - $v_primary = $package->getPrimaryOwnerPHID(); - // TODO: Pull these off needOwners() on the Query. - $v_owners = mpull($package->loadOwners(), 'getUserPHID'); + $v_owners = mpull($package->getOwners(), 'getUserPHID'); $v_auditing = $package->getAuditingEnabled(); $v_description = $package->getDescription(); + $v_status = $package->getStatus(); $errors = array(); @@ -43,30 +42,21 @@ final class PhabricatorOwnersEditController $xactions = array(); $v_name = $request->getStr('name'); - $v_primary = head($request->getArr('primary')); $v_owners = $request->getArr('owners'); $v_auditing = ($request->getStr('auditing') == 'enabled'); $v_description = $request->getStr('description'); - - if ($v_primary) { - $v_owners[] = $v_primary; - $v_owners = array_unique($v_owners); - } + $v_status = $request->getStr('status'); $type_name = PhabricatorOwnersPackageTransaction::TYPE_NAME; - $type_primary = PhabricatorOwnersPackageTransaction::TYPE_PRIMARY; $type_owners = PhabricatorOwnersPackageTransaction::TYPE_OWNERS; $type_auditing = PhabricatorOwnersPackageTransaction::TYPE_AUDITING; $type_description = PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION; + $type_status = PhabricatorOwnersPackageTransaction::TYPE_STATUS; $xactions[] = id(new PhabricatorOwnersPackageTransaction()) ->setTransactionType($type_name) ->setNewValue($v_name); - $xactions[] = id(new PhabricatorOwnersPackageTransaction()) - ->setTransactionType($type_primary) - ->setNewValue($v_primary); - $xactions[] = id(new PhabricatorOwnersPackageTransaction()) ->setTransactionType($type_owners) ->setNewValue($v_owners); @@ -79,6 +69,12 @@ final class PhabricatorOwnersEditController ->setTransactionType($type_description) ->setNewValue($v_description); + if (!$is_new) { + $xactions[] = id(new PhabricatorOwnersPackageTransaction()) + ->setTransactionType($type_status) + ->setNewValue($v_status); + } + $editor = id(new PhabricatorOwnersPackageTransactionEditor()) ->setActor($viewer) ->setContentSourceFromRequest($request) @@ -102,16 +98,9 @@ final class PhabricatorOwnersEditController $validation_exception = $ex; $e_name = $ex->getShortMessage($type_name); - $e_primary = $ex->getShortMessage($type_primary); } } - if ($v_primary) { - $value_primary_owner = array($v_primary); - } else { - $value_primary_owner = array(); - } - if ($is_new) { $cancel_uri = '/owners/'; $title = pht('New Package'); @@ -130,21 +119,23 @@ final class PhabricatorOwnersEditController ->setName('name') ->setValue($v_name) ->setError($e_name)) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new PhabricatorProjectOrUserDatasource()) - ->setLabel(pht('Primary Owner')) - ->setName('primary') - ->setLimit(1) - ->setValue($value_primary_owner) - ->setError($e_primary)) ->appendControl( id(new AphrontFormTokenizerControl()) ->setDatasource(new PhabricatorProjectOrUserDatasource()) ->setLabel(pht('Owners')) ->setName('owners') - ->setValue($v_owners)) - ->appendChild( + ->setValue($v_owners)); + + if (!$is_new) { + $form->appendChild( + id(new AphrontFormSelectControl()) + ->setLabel(pht('Status')) + ->setName('status') + ->setValue($v_status) + ->setOptions($package->getStatusNameMap())); + } + + $form->appendChild( id(new AphrontFormSelectControl()) ->setName('auditing') ->setLabel(pht('Auditing')) diff --git a/src/applications/owners/controller/PhabricatorOwnersPathsController.php b/src/applications/owners/controller/PhabricatorOwnersPathsController.php index 95df2cb807..ad278d5296 100644 --- a/src/applications/owners/controller/PhabricatorOwnersPathsController.php +++ b/src/applications/owners/controller/PhabricatorOwnersPathsController.php @@ -15,6 +15,7 @@ final class PhabricatorOwnersPathsController // TODO: Support this capability. // PhabricatorPolicyCapability::CAN_EDIT, )) + ->needPaths(true) ->executeOne(); if (!$package) { return new Aphront404Response(); @@ -66,7 +67,7 @@ final class PhabricatorOwnersPathsController return id(new AphrontRedirectResponse()) ->setURI('/owners/package/'.$package->getID().'/'); } else { - $paths = $package->loadPaths(); + $paths = $package->getPaths(); $path_refs = mpull($paths, 'getRef'); } diff --git a/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php b/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php index 8ce1e48c29..8feeeb1408 100644 --- a/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php +++ b/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php @@ -15,11 +15,11 @@ final class PhabricatorOwnersPackageTransactionEditor $types = parent::getTransactionTypes(); $types[] = PhabricatorOwnersPackageTransaction::TYPE_NAME; - $types[] = PhabricatorOwnersPackageTransaction::TYPE_PRIMARY; $types[] = PhabricatorOwnersPackageTransaction::TYPE_OWNERS; $types[] = PhabricatorOwnersPackageTransaction::TYPE_AUDITING; $types[] = PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION; $types[] = PhabricatorOwnersPackageTransaction::TYPE_PATHS; + $types[] = PhabricatorOwnersPackageTransaction::TYPE_STATUS; return $types; } @@ -31,8 +31,6 @@ final class PhabricatorOwnersPackageTransactionEditor switch ($xaction->getTransactionType()) { case PhabricatorOwnersPackageTransaction::TYPE_NAME: return $object->getName(); - case PhabricatorOwnersPackageTransaction::TYPE_PRIMARY: - return $object->getPrimaryOwnerPHID(); case PhabricatorOwnersPackageTransaction::TYPE_OWNERS: // TODO: needOwners() this on the Query. $phids = mpull($object->loadOwners(), 'getUserPHID'); @@ -43,9 +41,10 @@ final class PhabricatorOwnersPackageTransactionEditor case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION: return $object->getDescription(); case PhabricatorOwnersPackageTransaction::TYPE_PATHS: - // TODO: needPaths() this on the query - $paths = $object->loadPaths(); + $paths = $object->getPaths(); return mpull($paths, 'getRef'); + case PhabricatorOwnersPackageTransaction::TYPE_STATUS: + return $object->getStatus(); } } @@ -55,9 +54,9 @@ final class PhabricatorOwnersPackageTransactionEditor switch ($xaction->getTransactionType()) { case PhabricatorOwnersPackageTransaction::TYPE_NAME: - case PhabricatorOwnersPackageTransaction::TYPE_PRIMARY: case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION: case PhabricatorOwnersPackageTransaction::TYPE_PATHS: + case PhabricatorOwnersPackageTransaction::TYPE_STATUS: return $xaction->getNewValue(); case PhabricatorOwnersPackageTransaction::TYPE_AUDITING: return (int)$xaction->getNewValue(); @@ -95,9 +94,6 @@ final class PhabricatorOwnersPackageTransactionEditor case PhabricatorOwnersPackageTransaction::TYPE_NAME: $object->setName($xaction->getNewValue()); return; - case PhabricatorOwnersPackageTransaction::TYPE_PRIMARY: - $object->setPrimaryOwnerPHID($xaction->getNewValue()); - return; case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION: $object->setDescription($xaction->getNewValue()); return; @@ -107,6 +103,9 @@ final class PhabricatorOwnersPackageTransactionEditor case PhabricatorOwnersPackageTransaction::TYPE_OWNERS: case PhabricatorOwnersPackageTransaction::TYPE_PATHS: return; + case PhabricatorOwnersPackageTransaction::TYPE_STATUS: + $object->setStatus($xaction->getNewValue()); + return; } return parent::applyCustomInternalTransaction($object, $xaction); @@ -118,9 +117,9 @@ final class PhabricatorOwnersPackageTransactionEditor switch ($xaction->getTransactionType()) { case PhabricatorOwnersPackageTransaction::TYPE_NAME: - case PhabricatorOwnersPackageTransaction::TYPE_PRIMARY: case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION: case PhabricatorOwnersPackageTransaction::TYPE_AUDITING: + case PhabricatorOwnersPackageTransaction::TYPE_STATUS: return; case PhabricatorOwnersPackageTransaction::TYPE_OWNERS: $old = $xaction->getOldValue(); @@ -152,8 +151,7 @@ final class PhabricatorOwnersPackageTransactionEditor $old = $xaction->getOldValue(); $new = $xaction->getNewValue(); - // TODO: needPaths this - $paths = $object->loadPaths(); + $paths = $object->getPaths(); $diffs = PhabricatorOwnersPath::getTransactionValueChanges($old, $new); list($rem, $add) = $diffs; @@ -202,22 +200,6 @@ final class PhabricatorOwnersPackageTransactionEditor $errors[] = $error; } break; - case PhabricatorOwnersPackageTransaction::TYPE_PRIMARY: - $missing = $this->validateIsEmptyTextField( - $object->getPrimaryOwnerPHID(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Packages must have a primary owner.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - break; } return $errors; @@ -247,7 +229,6 @@ final class PhabricatorOwnersPackageTransactionEditor protected function getMailTo(PhabricatorLiskDAO $object) { return array( - $object->getPrimaryOwnerPHID(), $this->requireActor()->getPHID(), ); } diff --git a/src/applications/owners/query/PhabricatorOwnersPackageQuery.php b/src/applications/owners/query/PhabricatorOwnersPackageQuery.php index 1d34d7be58..f57ddf5a58 100644 --- a/src/applications/owners/query/PhabricatorOwnersPackageQuery.php +++ b/src/applications/owners/query/PhabricatorOwnersPackageQuery.php @@ -6,17 +6,38 @@ final class PhabricatorOwnersPackageQuery private $ids; private $phids; private $ownerPHIDs; + private $authorityPHIDs; private $repositoryPHIDs; + private $paths; private $namePrefix; + private $statuses; + + private $controlMap = array(); + private $controlResults; + + private $needPaths; + private $needOwners; + /** - * Owners are direct owners, and members of owning projects. + * Query owner PHIDs exactly. This does not expand authorities, so a user + * PHID will not match projects the user is a member of. */ public function withOwnerPHIDs(array $phids) { $this->ownerPHIDs = $phids; return $this; } + /** + * Query owner authority. This will expand authorities, so a user PHID will + * match both packages they own directly and packages owned by a project they + * are a member of. + */ + public function withAuthorityPHIDs(array $phids) { + $this->authorityPHIDs = $phids; + return $this; + } + public function withPHIDs(array $phids) { $this->phids = $phids; return $this; @@ -32,30 +53,104 @@ final class PhabricatorOwnersPackageQuery return $this; } + public function withPaths(array $paths) { + $this->paths = $paths; + return $this; + } + + public function withStatuses(array $statuses) { + $this->statuses = $statuses; + return $this; + } + + public function withControl($repository_phid, array $paths) { + if (empty($this->controlMap[$repository_phid])) { + $this->controlMap[$repository_phid] = array(); + } + + foreach ($paths as $path) { + $path = (string)$path; + $this->controlMap[$repository_phid][$path] = $path; + } + + // We need to load paths to execute control queries. + $this->needPaths = true; + + return $this; + } + public function withNamePrefix($prefix) { $this->namePrefix = $prefix; return $this; } + public function needPaths($need_paths) { + $this->needPaths = $need_paths; + return $this; + } + + public function needOwners($need_owners) { + $this->needOwners = $need_owners; + return $this; + } + public function newResultObject() { return new PhabricatorOwnersPackage(); } + protected function willExecute() { + $this->controlResults = array(); + } + protected function loadPage() { return $this->loadStandardPage(new PhabricatorOwnersPackage()); } + protected function didFilterPage(array $packages) { + if ($this->needPaths) { + $package_ids = mpull($packages, 'getID'); + + $paths = id(new PhabricatorOwnersPath())->loadAllWhere( + 'packageID IN (%Ld)', + $package_ids); + $paths = mgroup($paths, 'getPackageID'); + + foreach ($packages as $package) { + $package->attachPaths(idx($paths, $package->getID(), array())); + } + } + + if ($this->needOwners) { + $package_ids = mpull($packages, 'getID'); + + $owners = id(new PhabricatorOwnersOwner())->loadAllWhere( + 'packageID IN (%Ld)', + $package_ids); + $owners = mgroup($owners, 'getPackageID'); + + foreach ($packages as $package) { + $package->attachOwners(idx($owners, $package->getID(), array())); + } + } + + if ($this->controlMap) { + $this->controlResults += mpull($packages, null, 'getID'); + } + + return $packages; + } + protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { $joins = parent::buildJoinClauseParts($conn); - if ($this->ownerPHIDs !== null) { + if ($this->shouldJoinOwnersTable()) { $joins[] = qsprintf( $conn, 'JOIN %T o ON o.packageID = p.id', id(new PhabricatorOwnersOwner())->getTableName()); } - if ($this->repositoryPHIDs !== null) { + if ($this->shouldJoinPathTable()) { $joins[] = qsprintf( $conn, 'JOIN %T rpath ON rpath.packageID = p.id', @@ -89,21 +184,33 @@ final class PhabricatorOwnersPackageQuery $this->repositoryPHIDs); } - if ($this->ownerPHIDs !== null) { - $base_phids = $this->ownerPHIDs; - - $projects = id(new PhabricatorProjectQuery()) - ->setViewer($this->getViewer()) - ->withMemberPHIDs($base_phids) - ->execute(); - $project_phids = mpull($projects, 'getPHID'); - - $all_phids = array_merge($base_phids, $project_phids); - + if ($this->authorityPHIDs !== null) { + $authority_phids = $this->expandAuthority($this->authorityPHIDs); $where[] = qsprintf( $conn, 'o.userPHID IN (%Ls)', - $all_phids); + $authority_phids); + } + + if ($this->ownerPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'o.userPHID IN (%Ls)', + $this->ownerPHIDs); + } + + if ($this->paths !== null) { + $where[] = qsprintf( + $conn, + 'rpath.path IN (%Ls)', + $this->getFragmentsForPaths($this->paths)); + } + + if ($this->statuses !== null) { + $where[] = qsprintf( + $conn, + 'p.status IN (%Ls)', + $this->statuses); } if (strlen($this->namePrefix)) { @@ -115,15 +222,29 @@ final class PhabricatorOwnersPackageQuery phutil_utf8_strtolower($this->namePrefix)); } + if ($this->controlMap) { + $clauses = array(); + foreach ($this->controlMap as $repository_phid => $paths) { + $fragments = $this->getFragmentsForPaths($paths); + + $clauses[] = qsprintf( + $conn, + '(rpath.repositoryPHID = %s AND rpath.path IN (%Ls))', + $repository_phid, + $fragments); + } + $where[] = implode(' OR ', $clauses); + } + return $where; } protected function shouldGroupQueryResultRows() { - if ($this->repositoryPHIDs) { + if ($this->shouldJoinOwnersTable()) { return true; } - if ($this->ownerPHIDs) { + if ($this->shouldJoinPathTable()) { return true; } @@ -167,4 +288,109 @@ final class PhabricatorOwnersPackageQuery return 'p'; } + private function shouldJoinOwnersTable() { + if ($this->ownerPHIDs !== null) { + return true; + } + + if ($this->authorityPHIDs !== null) { + return true; + } + + return false; + } + + private function shouldJoinPathTable() { + if ($this->repositoryPHIDs !== null) { + return true; + } + + if ($this->paths !== null) { + return true; + } + + if ($this->controlMap) { + return true; + } + + return false; + } + + private function expandAuthority(array $phids) { + $projects = id(new PhabricatorProjectQuery()) + ->setViewer($this->getViewer()) + ->withMemberPHIDs($phids) + ->execute(); + $project_phids = mpull($projects, 'getPHID'); + + return array_fuse($phids) + array_fuse($project_phids); + } + + private function getFragmentsForPaths(array $paths) { + $fragments = array(); + + foreach ($paths as $path) { + foreach (PhabricatorOwnersPackage::splitPath($path) as $fragment) { + $fragments[$fragment] = $fragment; + } + } + + return $fragments; + } + + +/* -( Path Control )------------------------------------------------------- */ + + + /** + * Get a list of all packages which control a path or its parent directories, + * ordered from weakest to strongest. + * + * The first package has the most specific claim on the path; the last + * package has the most general claim. Multiple packages may have claims of + * equal strength, so this ordering is primarily one of usability and + * convenience. + * + * @return list List of controlling packages. + */ + public function getControllingPackagesForPath($repository_phid, $path) { + $path = (string)$path; + + if (!isset($this->controlMap[$repository_phid][$path])) { + throw new PhutilInvalidStateException('withControl'); + } + + if ($this->controlResults === null) { + throw new PhutilInvalidStateException('execute'); + } + + $packages = $this->controlResults; + + $matches = array(); + foreach ($packages as $package_id => $package) { + $best_match = null; + $include = false; + + foreach ($package->getPaths() as $package_path) { + $strength = $package_path->getPathMatchStrength($path); + if ($strength > $best_match) { + $best_match = $strength; + $include = !$package_path->getExcluded(); + } + } + + if ($best_match && $include) { + $matches[$package_id] = array( + 'strength' => $best_match, + 'package' => $package, + ); + } + } + + $matches = isort($matches, 'strength'); + $matches = array_reverse($matches); + + return array_values(ipull($matches, 'package')); + } + } diff --git a/src/applications/owners/query/PhabricatorOwnersPackageSearchEngine.php b/src/applications/owners/query/PhabricatorOwnersPackageSearchEngine.php index fc2f8f620f..d3131bba47 100644 --- a/src/applications/owners/query/PhabricatorOwnersPackageSearchEngine.php +++ b/src/applications/owners/query/PhabricatorOwnersPackageSearchEngine.php @@ -18,29 +18,47 @@ final class PhabricatorOwnersPackageSearchEngine protected function buildCustomSearchFields() { return array( id(new PhabricatorSearchDatasourceField()) - ->setLabel(pht('Owners')) - ->setKey('ownerPHIDs') - ->setAliases(array('owner', 'owners')) + ->setLabel(pht('Authority')) + ->setKey('authorityPHIDs') + ->setAliases(array('authority', 'authorities')) ->setDatasource(new PhabricatorProjectOrUserDatasource()), id(new PhabricatorSearchDatasourceField()) ->setLabel(pht('Repositories')) ->setKey('repositoryPHIDs') ->setAliases(array('repository', 'repositories')) ->setDatasource(new DiffusionRepositoryDatasource()), + id(new PhabricatorSearchStringListField()) + ->setLabel(pht('Paths')) + ->setKey('paths') + ->setAliases(array('path')), + id(new PhabricatorSearchCheckboxesField()) + ->setKey('statuses') + ->setLabel(pht('Status')) + ->setOptions( + id(new PhabricatorOwnersPackage()) + ->getStatusNameMap()), ); } protected function buildQueryFromParameters(array $map) { $query = $this->newQuery(); - if ($map['ownerPHIDs']) { - $query->withOwnerPHIDs($map['ownerPHIDs']); + if ($map['authorityPHIDs']) { + $query->withAuthorityPHIDs($map['authorityPHIDs']); } if ($map['repositoryPHIDs']) { $query->withRepositoryPHIDs($map['repositoryPHIDs']); } + if ($map['paths']) { + $query->withPaths($map['paths']); + } + + if ($map['statuses']) { + $query->withStatuses($map['statuses']); + } + return $query; } @@ -52,10 +70,11 @@ final class PhabricatorOwnersPackageSearchEngine $names = array(); if ($this->requireViewer()->isLoggedIn()) { - $names['owned'] = pht('Owned'); + $names['authority'] = pht('Owned'); } $names += array( + 'active' => pht('Active Packages'), 'all' => pht('All Packages'), ); @@ -69,9 +88,15 @@ final class PhabricatorOwnersPackageSearchEngine switch ($query_key) { case 'all': return $query; - case 'owned': + case 'active': return $query->setParameter( - 'ownerPHIDs', + 'statuses', + array( + PhabricatorOwnersPackage::STATUS_ACTIVE, + )); + case 'authority': + return $query->setParameter( + 'authorityPHIDs', array($this->requireViewer()->getPHID())); } @@ -97,6 +122,10 @@ final class PhabricatorOwnersPackageSearchEngine ->setHeader($package->getName()) ->setHref('/owners/package/'.$id.'/'); + if ($package->isArchived()) { + $item->setDisabled(true); + } + $list->addItem($item); } diff --git a/src/applications/owners/storage/PhabricatorOwnersPackage.php b/src/applications/owners/storage/PhabricatorOwnersPackage.php index ecf1862323..d9a24b179a 100644 --- a/src/applications/owners/storage/PhabricatorOwnersPackage.php +++ b/src/applications/owners/storage/PhabricatorOwnersPackage.php @@ -12,11 +12,20 @@ final class PhabricatorOwnersPackage protected $description; protected $primaryOwnerPHID; protected $mailKey; + protected $status; + + private $paths = self::ATTACHABLE; + private $owners = self::ATTACHABLE; + + const STATUS_ACTIVE = 'active'; + const STATUS_ARCHIVED = 'archived'; public static function initializeNewPackage(PhabricatorUser $actor) { return id(new PhabricatorOwnersPackage()) ->setAuditingEnabled(0) - ->setPrimaryOwnerPHID($actor->getPHID()); + ->attachPaths(array()) + ->setStatus(self::STATUS_ACTIVE) + ->attachOwners(array()); } public function getCapabilities() { @@ -37,6 +46,13 @@ final class PhabricatorOwnersPackage return null; } + public static function getStatusNameMap() { + return array( + self::STATUS_ACTIVE => pht('Active'), + self::STATUS_ARCHIVED => pht('Archived'), + ); + } + protected function getConfiguration() { return array( // This information is better available from the history table. @@ -49,6 +65,7 @@ final class PhabricatorOwnersPackage 'primaryOwnerPHID' => 'phid?', 'auditingEnabled' => 'bool', 'mailKey' => 'bytes20', + 'status' => 'text32', ), self::CONFIG_KEY_SCHEMA => array( 'key_phid' => null, @@ -77,6 +94,10 @@ final class PhabricatorOwnersPackage return parent::save(); } + public function isArchived() { + return ($this->getStatus() == self::STATUS_ARCHIVED); + } + public function setName($name) { $this->name = $name; if (!$this->getID()) { @@ -225,17 +246,40 @@ final class PhabricatorOwnersPackage return $ids; } - private static function splitPath($path) { - $result = array('/'); + public static function splitPath($path) { $trailing_slash = preg_match('@/$@', $path) ? '/' : ''; $path = trim($path, '/'); $parts = explode('/', $path); + + $result = array(); while (count($parts)) { $result[] = '/'.implode('/', $parts).$trailing_slash; $trailing_slash = '/'; array_pop($parts); } - return $result; + $result[] = '/'; + + return array_reverse($result); + } + + public function attachPaths(array $paths) { + assert_instances_of($paths, 'PhabricatorOwnersPath'); + $this->paths = $paths; + return $this; + } + + public function getPaths() { + return $this->assertAttached($this->paths); + } + + public function attachOwners(array $owners) { + assert_instances_of($owners, 'PhabricatorOwnersOwner'); + $this->owners = $owners; + return $this; + } + + public function getOwners() { + return $this->assertAttached($this->owners); } diff --git a/src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php b/src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php index 3686125887..edb4f5db8f 100644 --- a/src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php +++ b/src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php @@ -9,6 +9,7 @@ final class PhabricatorOwnersPackageTransaction const TYPE_AUDITING = 'owners.auditing'; const TYPE_DESCRIPTION = 'owners.description'; const TYPE_PATHS = 'owners.paths'; + const TYPE_STATUS = 'owners.status'; public function getApplicationName() { return 'owners'; @@ -25,14 +26,6 @@ final class PhabricatorOwnersPackageTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case self::TYPE_PRIMARY: - if ($old) { - $phids[] = $old; - } - if ($new) { - $phids[] = $new; - } - break; case self::TYPE_OWNERS: $add = array_diff($new, $old); foreach ($add as $phid) { @@ -55,6 +48,9 @@ final class PhabricatorOwnersPackageTransaction switch ($this->getTransactionType()) { case self::TYPE_DESCRIPTION: return ($old === null); + case self::TYPE_PRIMARY: + // TODO: Eventually, remove these transactions entirely. + return true; } } @@ -76,12 +72,6 @@ final class PhabricatorOwnersPackageTransaction $old, $new); } - case self::TYPE_PRIMARY: - return pht( - '%s changed the primary owner for this package from %s to %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($old), - $this->renderHandleLink($new)); case self::TYPE_OWNERS: $add = array_diff($new, $old); $rem = array_diff($old, $new); @@ -126,6 +116,16 @@ final class PhabricatorOwnersPackageTransaction return pht( '%s updated paths for this package.', $this->renderHandleLink($author_phid)); + case self::TYPE_STATUS: + if ($new == PhabricatorOwnersPackage::STATUS_ACTIVE) { + return pht( + '%s activated this package.', + $this->renderHandleLink($author_phid)); + } else if ($new == PhabricatorOwnersPackage::STATUS_ARCHIVED) { + return pht( + '%s archived this package.', + $this->renderHandleLink($author_phid)); + } } return parent::getTitle(); diff --git a/src/applications/owners/storage/PhabricatorOwnersPath.php b/src/applications/owners/storage/PhabricatorOwnersPath.php index f65d6052db..33ab109719 100644 --- a/src/applications/owners/storage/PhabricatorOwnersPath.php +++ b/src/applications/owners/storage/PhabricatorOwnersPath.php @@ -70,4 +70,36 @@ final class PhabricatorOwnersPath extends PhabricatorOwnersDAO { return isset($set[$ref['repositoryPHID']][$ref['path']][$ref['excluded']]); } + /** + * Get the number of directory matches between this path specification and + * some real path. + */ + public function getPathMatchStrength($path) { + $this_path = $this->getPath(); + + if ($this_path === '/') { + // The root path "/" just matches everything with strength 1. + return 1; + } + + $self_fragments = PhabricatorOwnersPackage::splitPath($this_path); + $path_fragments = PhabricatorOwnersPackage::splitPath($path); + + $self_count = count($self_fragments); + $path_count = count($path_fragments); + if ($self_count > $path_count) { + // If this path is longer (and therefor more specific) than the target + // path, we don't match it at all. + return 0; + } + + for ($ii = 0; $ii < $self_count; $ii++) { + if ($self_fragments[$ii] != $path_fragments[$ii]) { + return 0; + } + } + + return $self_count; + } + } diff --git a/src/applications/ponder/application/PhabricatorPonderApplication.php b/src/applications/ponder/application/PhabricatorPonderApplication.php index c91ba6803c..38546a8a7d 100644 --- a/src/applications/ponder/application/PhabricatorPonderApplication.php +++ b/src/applications/ponder/application/PhabricatorPonderApplication.php @@ -42,10 +42,6 @@ final class PhabricatorPonderApplication extends PhabricatorApplication { ); } - public function isPrototype() { - return true; - } - public function getRoutes() { return array( '/Q(?P[1-9]\d*)' diff --git a/src/applications/ponder/controller/PonderAnswerCommentController.php b/src/applications/ponder/controller/PonderAnswerCommentController.php index 5531177586..4b60fb939a 100644 --- a/src/applications/ponder/controller/PonderAnswerCommentController.php +++ b/src/applications/ponder/controller/PonderAnswerCommentController.php @@ -19,11 +19,12 @@ final class PonderAnswerCommentController extends PonderController { } $is_preview = $request->isPreviewRequest(); -// $draft = PhabricatorDraft::buildFromRequest($request); $qid = $answer->getQuestion()->getID(); $aid = $answer->getID(); - $view_uri = "/Q{$qid}#A{$aid}"; + + // TODO, this behaves badly when redirecting to the answer + $view_uri = "/Q{$qid}"; $xactions = array(); $xactions[] = id(new PonderAnswerTransaction()) @@ -46,9 +47,6 @@ final class PonderAnswerCommentController extends PonderController { ->setException($ex); } -// if ($draft) { -// $draft->replaceOrDelete(); -// } if ($request->isAjax() && $is_preview) { return id(new PhabricatorApplicationTransactionResponse()) diff --git a/src/applications/ponder/controller/PonderAnswerHistoryController.php b/src/applications/ponder/controller/PonderAnswerHistoryController.php index b2c42de8c8..136cd8532e 100644 --- a/src/applications/ponder/controller/PonderAnswerHistoryController.php +++ b/src/applications/ponder/controller/PonderAnswerHistoryController.php @@ -2,6 +2,10 @@ final class PonderAnswerHistoryController extends PonderController { + public function shouldAllowPublic() { + return true; + } + public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $id = $request->getURIData('id'); diff --git a/src/applications/ponder/controller/PonderQuestionHistoryController.php b/src/applications/ponder/controller/PonderQuestionHistoryController.php index 91ded8c094..e7801ab4b1 100644 --- a/src/applications/ponder/controller/PonderQuestionHistoryController.php +++ b/src/applications/ponder/controller/PonderQuestionHistoryController.php @@ -2,6 +2,10 @@ final class PonderQuestionHistoryController extends PonderController { + public function shouldAllowPublic() { + return true; + } + public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $id = $request->getURIData('id'); diff --git a/src/applications/ponder/controller/PonderQuestionViewController.php b/src/applications/ponder/controller/PonderQuestionViewController.php index 06f924510f..fde0f067a3 100644 --- a/src/applications/ponder/controller/PonderQuestionViewController.php +++ b/src/applications/ponder/controller/PonderQuestionViewController.php @@ -2,6 +2,10 @@ final class PonderQuestionViewController extends PonderController { + public function shouldAllowPublic() { + return true; + } + public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $id = $request->getURIData('id'); @@ -205,35 +209,28 @@ final class PonderQuestionViewController extends PonderController { private function buildAnswers(array $answers) { $viewer = $this->getViewer(); - $xactions = id(new PonderAnswerTransactionQuery()) - ->setViewer($viewer) - ->withTransactionTypes(array(PhabricatorTransactions::TYPE_COMMENT)) - ->withObjectPHIDs(mpull($answers, 'getPHID')) - ->execute(); - - $engine = id(new PhabricatorMarkupEngine()) - ->setViewer($viewer); - foreach ($xactions as $xaction) { - if ($xaction->getComment()) { - $engine->addObject( - $xaction->getComment(), - PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT); - } - } - $engine->process(); - - $xaction_groups = mgroup($xactions, 'getObjectPHID'); + $author_phids = mpull($answers, 'getAuthorPHID'); + $handles = $this->loadViewerHandles($author_phids); + $answers_sort = array_reverse(msort($answers, 'getVoteCount')); $view = array(); - foreach ($answers as $answer) { - $xactions = idx($xaction_groups, $answer->getPHID(), array()); + foreach ($answers_sort as $answer) { $id = $answer->getID(); + $handle = $handles[$answer->getAuthorPHID()]; + + $timeline = $this->buildTransactionTimeline( + $answer, + id(new PonderAnswerTransactionQuery()) + ->withTransactionTypes(array(PhabricatorTransactions::TYPE_COMMENT))); + $xactions = $timeline->getTransactions(); + $view[] = id(new PonderAnswerView()) ->setUser($viewer) ->setAnswer($answer) ->setTransactions($xactions) - ->setMarkupEngine($engine); + ->setTimeline($timeline) + ->setHandle($handle); } diff --git a/src/applications/ponder/editor/PonderAnswerEditor.php b/src/applications/ponder/editor/PonderAnswerEditor.php index b9e3c42869..fdb5c48437 100644 --- a/src/applications/ponder/editor/PonderAnswerEditor.php +++ b/src/applications/ponder/editor/PonderAnswerEditor.php @@ -86,16 +86,17 @@ final class PonderAnswerEditor extends PonderEditor { } protected function buildReplyHandler(PhabricatorLiskDAO $object) { - $question = $object->getQuestion(); - return id(new PonderQuestionReplyHandler()) - ->setMailReceiver($question); + return id(new PonderAnswerReplyHandler()) + ->setMailReceiver($object); } protected function buildMailTemplate(PhabricatorLiskDAO $object) { - $question = $object->getQuestion(); - return parent::buildMailTemplate($question); - } + $id = $object->getID(); + return id(new PhabricatorMetaMTAMail()) + ->setSubject("ANSR{$id}") + ->addHeader('Thread-Topic', "ANSR{$id}"); + } protected function buildMailBody( PhabricatorLiskDAO $object, diff --git a/src/applications/ponder/editor/PonderEditor.php b/src/applications/ponder/editor/PonderEditor.php index 89e525ce7c..24c6f2d8d2 100644 --- a/src/applications/ponder/editor/PonderEditor.php +++ b/src/applications/ponder/editor/PonderEditor.php @@ -7,23 +7,6 @@ abstract class PonderEditor return 'PhabricatorPonderApplication'; } - protected function shouldPublishFeedStory( - PhabricatorLiskDAO $object, - array $xactions) { - return true; - } - - protected function buildMailTemplate(PhabricatorLiskDAO $object) { - $id = $object->getID(); - $title = $object->getTitle(); - $original_title = $object->getOriginalTitle(); - - return id(new PhabricatorMetaMTAMail()) - ->setSubject("Q{$id}: {$title}") - ->addHeader('Thread-Topic', "Q{$id}: {$original_title}"); - } - - protected function getMailTo(PhabricatorLiskDAO $object) { return array( $object->getAuthorPHID(), diff --git a/src/applications/ponder/editor/PonderQuestionEditor.php b/src/applications/ponder/editor/PonderQuestionEditor.php index eeac4b3414..c2d9ef231b 100644 --- a/src/applications/ponder/editor/PonderQuestionEditor.php +++ b/src/applications/ponder/editor/PonderQuestionEditor.php @@ -177,30 +177,13 @@ final class PonderQuestionEditor return true; } - protected function getFeedStoryType() { - return 'PonderTransactionFeedStory'; - } - - protected function getFeedStoryData( - PhabricatorLiskDAO $object, - array $xactions) { - - $data = parent::getFeedStoryData($object, $xactions); - $answer = $this->getAnswer(); - if ($answer) { - $data['answerPHID'] = $answer->getPHID(); - } - - return $data; - } - protected function shouldImplyCC( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PonderQuestionTransaction::TYPE_ANSWERS: - return true; + return false; } return parent::shouldImplyCC($object, $xaction); @@ -209,7 +192,25 @@ final class PonderQuestionEditor protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { - return true; + foreach ($xactions as $xaction) { + switch ($xaction->getTransactionType()) { + case PonderQuestionTransaction::TYPE_ANSWERS: + return false; + } + } + return true; + } + + protected function shouldPublishFeedStory( + PhabricatorLiskDAO $object, + array $xactions) { + foreach ($xactions as $xaction) { + switch ($xaction->getTransactionType()) { + case PonderQuestionTransaction::TYPE_ANSWERS: + return false; + } + } + return true; } public function getMailTagsMap() { @@ -230,6 +231,16 @@ final class PonderQuestionEditor ->setMailReceiver($object); } + protected function buildMailTemplate(PhabricatorLiskDAO $object) { + $id = $object->getID(); + $title = $object->getTitle(); + $original_title = $object->getOriginalTitle(); + + return id(new PhabricatorMetaMTAMail()) + ->setSubject("Q{$id}: {$title}") + ->addHeader('Thread-Topic', "Q{$id}: {$original_title}"); + } + protected function buildMailBody( PhabricatorLiskDAO $object, array $xactions) { @@ -248,14 +259,6 @@ final class PonderQuestionEditor $body->addRawSection($new); } } - // If the user gave an answer, add the answer text. Also update - // the header and uri to be more answer-specific. - if ($type == PonderQuestionTransaction::TYPE_ANSWERS) { - $answer = $this->getAnswer(); - $body->addRawSection($answer->getContent()); - $header = pht('ANSWER DETAIL'); - $uri = $answer->getURI(); - } } $body->addLinkSection( diff --git a/src/applications/ponder/feed/PonderTransactionFeedStory.php b/src/applications/ponder/feed/PonderTransactionFeedStory.php deleted file mode 100644 index 354170af51..0000000000 --- a/src/applications/ponder/feed/PonderTransactionFeedStory.php +++ /dev/null @@ -1,14 +0,0 @@ -getValue('answerPHID'); - if ($answer_phid) { - $phids[] = $answer_phid; - } - return $phids; - } -} diff --git a/src/applications/ponder/mail/PonderAnswerMailReceiver.php b/src/applications/ponder/mail/PonderAnswerMailReceiver.php new file mode 100644 index 0000000000..d7269ac861 --- /dev/null +++ b/src/applications/ponder/mail/PonderAnswerMailReceiver.php @@ -0,0 +1,27 @@ +setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + } + + protected function getTransactionReplyHandler() { + return new PonderAnswerReplyHandler(); + } + +} diff --git a/src/applications/ponder/mail/PonderAnswerReplyHandler.php b/src/applications/ponder/mail/PonderAnswerReplyHandler.php new file mode 100644 index 0000000000..bfc077a7ad --- /dev/null +++ b/src/applications/ponder/mail/PonderAnswerReplyHandler.php @@ -0,0 +1,16 @@ +answererPHIDs) { @@ -141,7 +141,7 @@ final class PonderQuestionQuery $this->answererPHIDs); } - return implode(' ', $joins); + return $joins; } protected function getPrimaryTableAlias() { diff --git a/src/applications/ponder/query/PonderQuestionSearchEngine.php b/src/applications/ponder/query/PonderQuestionSearchEngine.php index b4cb7aef21..6692d8282a 100644 --- a/src/applications/ponder/query/PonderQuestionSearchEngine.php +++ b/src/applications/ponder/query/PonderQuestionSearchEngine.php @@ -96,7 +96,7 @@ final class PonderQuestionSearchEngine array($this->requireViewer()->getPHID())); case 'answered': return $query->setParameter( - 'answererPHIDs', + 'answerers', array($this->requireViewer()->getPHID())); } diff --git a/src/applications/ponder/storage/PonderQuestionTransaction.php b/src/applications/ponder/storage/PonderQuestionTransaction.php index 3c2734ea71..9fb4edbf05 100644 --- a/src/applications/ponder/storage/PonderQuestionTransaction.php +++ b/src/applications/ponder/storage/PonderQuestionTransaction.php @@ -336,23 +336,4 @@ final class PonderQuestionTransaction return reset($add); } - /** - * Generally, the answer object is only available if the transaction - * type is `self::TYPE_ANSWERS`. - * - * Some stories - notably ones made before D7027 - will be of the more - * generic @{class:PhabricatorApplicationTransactionFeedStory}. These - * poor stories won't have the PonderAnswer loaded, and thus will have - * less cool information. - */ - private function getNewAnswerObject(PhabricatorFeedStory $story) { - if ($story instanceof PonderTransactionFeedStory) { - $answer_phid = $this->getNewAnswerPHID(); - if ($answer_phid) { - return $story->getObject($answer_phid); - } - } - return null; - } - } diff --git a/src/applications/ponder/view/PonderAddAnswerView.php b/src/applications/ponder/view/PonderAddAnswerView.php index 5ffe99edc7..5d3be0460d 100644 --- a/src/applications/ponder/view/PonderAddAnswerView.php +++ b/src/applications/ponder/view/PonderAddAnswerView.php @@ -34,13 +34,42 @@ final class PonderAddAnswerView extends AphrontView { $info_panel = null; if ($question->getStatus() != PonderQuestionStatus::STATUS_OPEN) { $info_panel = id(new PHUIInfoView()) - ->setSeverity(PHUIInfoView::SEVERITY_WARNING) + ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) ->appendChild( pht( 'This question has been marked as closed, but you can still leave a new answer.')); } + $box_style = null; + $own_question = null; + $hide_action_id = celerity_generate_unique_node_id(); + $show_action_id = celerity_generate_unique_node_id(); + if ($question->getAuthorPHID() == $viewer->getPHID()) { + $box_style = 'display: none;'; + $open_link = javelin_tag( + 'a', + array( + 'sigil' => 'reveal-content', + 'class' => 'mml', + 'id' => $hide_action_id, + 'href' => '#', + 'meta' => array( + 'showIDs' => array($show_action_id), + 'hideIDs' => array($hide_action_id), + ), + ), + pht('Add an answer.')); + $own_question = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_WARNING) + ->setID($hide_action_id) + ->appendChild( + pht( + 'This is your own question. You are welcome to provide + an answer if you have found a resolution.')) + ->appendChild($open_link); + } + $header = id(new PHUIHeaderView()) ->setHeader(pht('Add Answer')); @@ -69,6 +98,15 @@ final class PonderAddAnswerView extends AphrontView { $box->setInfoView($info_panel); } - return $box; + $box = phutil_tag( + 'div', + array( + 'style' => $box_style, + 'class' => 'mlt', + 'id' => $show_action_id, + ), + $box); + + return array($own_question, $box); } } diff --git a/src/applications/ponder/view/PonderAnswerView.php b/src/applications/ponder/view/PonderAnswerView.php index 532bc9f2cd..6054e4f538 100644 --- a/src/applications/ponder/view/PonderAnswerView.php +++ b/src/applications/ponder/view/PonderAnswerView.php @@ -4,7 +4,8 @@ final class PonderAnswerView extends AphrontTagView { private $answer; private $transactions; - private $engine; + private $timeline; + private $handle; public function setAnswer($answer) { $this->answer = $answer; @@ -16,8 +17,13 @@ final class PonderAnswerView extends AphrontTagView { return $this; } - public function setMarkupEngine(PhabricatorMarkupEngine $engine) { - $this->engine = $engine; + public function setTimeline($timeline) { + $this->timeline = $timeline; + return $this; + } + + public function setHandle($handle) { + $this->handle = $handle; return $this; } @@ -34,6 +40,7 @@ final class PonderAnswerView extends AphrontTagView { $status = $answer->getStatus(); $author_phid = $answer->getAuthorPHID(); $actions = $this->buildAnswerActions(); + $handle = $this->handle; $id = $answer->getID(); if ($status == PonderAnswerStatus::ANSWER_STATUS_HIDDEN) { @@ -72,9 +79,11 @@ final class PonderAnswerView extends AphrontTagView { $header = id(new PHUIHeaderView()) ->setUser($viewer) - ->setEpoch($answer->getDateCreated()) - ->setHeader($viewer->renderHandle($author_phid)) - ->addActionLink($action_button); + ->setEpoch($answer->getDateModified()) + ->setHeader($handle->getName()) + ->addActionLink($action_button) + ->setImage($handle->getImageURI()) + ->setImageURL($handle->getURI()); $content = phutil_tag( 'div', @@ -95,17 +104,19 @@ final class PonderAnswerView extends AphrontTagView { ->setCount(count($this->transactions)); $votes = $answer->getVoteCount(); - if ($votes) { - $icon = id(new PHUIIconView()) - ->setIconFont('fa-thumbs-up'); - $helpful = phutil_tag( - 'span', - array( - 'class' => 'ponder-footer-action', - ), - array($votes, $icon)); - $footer->addAction($helpful); + $vote_class = null; + if ($votes > 0) { + $vote_class = 'ponder-footer-action-helpful'; } + $icon = id(new PHUIIconView()) + ->setIconFont('fa-thumbs-up msr'); + $helpful = phutil_tag( + 'span', + array( + 'class' => 'ponder-footer-action '.$vote_class, + ), + array($icon, $votes)); + $footer->addAction($helpful); $answer_view = id(new PHUIObjectBoxView()) ->setHeader($header) @@ -113,12 +124,6 @@ final class PonderAnswerView extends AphrontTagView { ->appendChild($content) ->appendChild($footer); - $transaction_view = id(new PhabricatorApplicationTransactionView()) - ->setUser($viewer) - ->setObjectPHID($answer->getPHID()) - ->setTransactions($this->transactions) - ->setMarkupEngine($this->engine); - $comment_view = id(new PhabricatorApplicationTransactionCommentView()) ->setUser($viewer) ->setObjectPHID($answer->getPHID()) @@ -134,7 +139,7 @@ final class PonderAnswerView extends AphrontTagView { 'style' => 'display: none;', ), array( - $transaction_view, + $this->timeline, $comment_view, )); diff --git a/src/applications/ponder/view/PonderFooterView.php b/src/applications/ponder/view/PonderFooterView.php index 8f55b86aeb..b1f346b147 100644 --- a/src/applications/ponder/view/PonderFooterView.php +++ b/src/applications/ponder/view/PonderFooterView.php @@ -36,9 +36,13 @@ final class PonderFooterView extends AphrontTagView { $content_id = $this->contentID; if ($this->count == 0) { + $icon = id(new PHUIIconView()) + ->setIconFont('fa-plus-circle msr'); $text = pht('Add a Comment'); } else { - $text = pht('Show %s Comments', new PhutilNumber($this->count)); + $icon = id(new PHUIIconView()) + ->setIconFont('fa-comments msr'); + $text = pht('Show %d Comment(s)', new PhutilNumber($this->count)); } $actions = array(); @@ -54,7 +58,7 @@ final class PonderFooterView extends AphrontTagView { 'hideIDs' => array($hide_action_id), ), ), - $text); + array($icon, $text)); $show_action = javelin_tag( 'a', @@ -69,12 +73,12 @@ final class PonderFooterView extends AphrontTagView { 'hideIDs' => array($content_id, $show_action_id), ), ), - pht('Hide Comments')); + array($icon, pht('Hide Comments'))); $actions[] = $hide_action; $actions[] = $show_action; - return array($actions, $this->actions); + return array($this->actions, $actions); } } diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index 45782758f8..a5044360de 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -1055,6 +1055,7 @@ abstract class PhabricatorApplicationTransactionEditor } if ($this->shouldPublishFeedStory($object, $xactions)) { + $mailed = array(); foreach ($messages as $mail) { foreach ($mail->buildRecipientList() as $phid) { @@ -2299,6 +2300,8 @@ abstract class PhabricatorApplicationTransactionEditor } } + $this->runHeraldMailRules($messages); + return $messages; } @@ -3140,4 +3143,16 @@ abstract class PhabricatorApplicationTransactionEditor ); } + private function runHeraldMailRules(array $messages) { + foreach ($messages as $message) { + $engine = new HeraldEngine(); + $adapter = id(new PhabricatorMailOutboundMailHeraldAdapter()) + ->setObject($message); + + $rules = $engine->loadRulesForAdapter($adapter); + $effects = $engine->applyRules($rules, $adapter); + $engine->applyEffects($effects, $adapter, $rules); + } + } + } diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php index c7be284ecd..e33049dcc7 100644 --- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php +++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php @@ -642,23 +642,20 @@ abstract class PhabricatorApplicationTransaction $this->renderHandleLink($author_phid)); case PhabricatorTransactions::TYPE_VIEW_POLICY: return pht( - '%s changed the visibility of this %s from "%s" to "%s".', + '%s changed the visibility from "%s" to "%s".', $this->renderHandleLink($author_phid), - $this->getApplicationObjectTypeName(), $this->renderPolicyName($old, 'old'), $this->renderPolicyName($new, 'new')); case PhabricatorTransactions::TYPE_EDIT_POLICY: return pht( - '%s changed the edit policy of this %s from "%s" to "%s".', + '%s changed the edit policy from "%s" to "%s".', $this->renderHandleLink($author_phid), - $this->getApplicationObjectTypeName(), $this->renderPolicyName($old, 'old'), $this->renderPolicyName($new, 'new')); case PhabricatorTransactions::TYPE_JOIN_POLICY: return pht( - '%s changed the join policy of this %s from "%s" to "%s".', + '%s changed the join policy from "%s" to "%s".', $this->renderHandleLink($author_phid), - $this->getApplicationObjectTypeName(), $this->renderPolicyName($old, 'old'), $this->renderPolicyName($new, 'new')); case PhabricatorTransactions::TYPE_SPACE: diff --git a/src/docs/user/field/darkconsole.diviner b/src/docs/user/field/darkconsole.diviner index d235f6c1e4..a31f0492ae 100644 --- a/src/docs/user/field/darkconsole.diviner +++ b/src/docs/user/field/darkconsole.diviner @@ -138,6 +138,25 @@ If the services tab looks fine, and particularly if a page is slow but the tool to understand problems in PHP is XHProf. +Plugin: Startup +=============== + +The "Startup" plugin shows information about startup phases. This information +can provide insight about performance problems which occur before the profiler +can start. + +Normally, the profiler is the best tool for understanding runtime performance, +but some work is performed before the profiler starts (for example, loading +libraries and configuration). If there is a substantial difference between the +wall time reported by the profiler and the "Entire Page" cost reported by the +Services tab, the Startup tab can help account for that time. + +It is normal for starting the profiler to increase the cost of the page +somewhat: the profiler itself adds overhead while it is running, and the page +must do some work after the profiler is stopped to save the profile and +complete other shutdown operations. + + Plugin: XHProf ============== diff --git a/src/infrastructure/diff/view/PHUIDiffTableOfContentsItemView.php b/src/infrastructure/diff/view/PHUIDiffTableOfContentsItemView.php new file mode 100644 index 0000000000..8f6e4e48d7 --- /dev/null +++ b/src/infrastructure/diff/view/PHUIDiffTableOfContentsItemView.php @@ -0,0 +1,333 @@ +changeset = $changeset; + return $this; + } + + public function getChangeset() { + return $this->changeset; + } + + public function setIsVisible($is_visible) { + $this->isVisible = $is_visible; + return $this; + } + + public function getIsVisible() { + return $this->isVisible; + } + + public function setAnchor($anchor) { + $this->anchor = $anchor; + return $this; + } + + public function getAnchor() { + return $this->anchor; + } + + public function setCoverage($coverage) { + $this->coverage = $coverage; + return $this; + } + + public function getCoverage() { + return $this->coverage; + } + + public function setCoverageID($coverage_id) { + $this->coverageID = $coverage_id; + return $this; + } + + public function getCoverageID() { + return $this->coverageID; + } + + public function setContext($context) { + $this->context = $context; + return $this; + } + + public function getContext() { + return $this->context; + } + + public function setPackages(array $packages) { + assert_instances_of($packages, 'PhabricatorOwnersPackage'); + $this->packages = mpull($packages, null, 'getPHID'); + return $this; + } + + public function getPackages() { + return $this->packages; + } + + public function render() { + $changeset = $this->getChangeset(); + + $cells = array(); + + $cells[] = $this->getContext(); + + $cells[] = $this->renderPathChangeCharacter(); + $cells[] = $this->renderPropertyChangeCharacter(); + $cells[] = $this->renderPropertyChangeDescription(); + + $link = $this->renderChangesetLink(); + $lines = $this->renderChangesetLines(); + $meta = $this->renderChangesetMetadata(); + + $cells[] = array( + $link, + $lines, + $meta, + ); + + $cells[] = $this->renderCoverage(); + $cells[] = $this->renderModifiedCoverage(); + + $cells[] = $this->renderPackages(); + + return $cells; + } + + private function renderPathChangeCharacter() { + $changeset = $this->getChangeset(); + $type = $changeset->getChangeType(); + + $color = DifferentialChangeType::getSummaryColorForChangeType($type); + $char = DifferentialChangeType::getSummaryCharacterForChangeType($type); + $title = DifferentialChangeType::getFullNameForChangeType($type); + + return javelin_tag( + 'span', + array( + 'sigil' => 'has-tooltip', + 'meta' => array( + 'tip' => $title, + 'align' => 'E', + ), + 'class' => 'phui-text-'.$color, + ), + $char); + } + + private function renderPropertyChangeCharacter() { + $changeset = $this->getChangeset(); + + $old = $changeset->getOldProperties(); + $new = $changeset->getNewProperties(); + + if ($old === $new) { + return null; + } + + return javelin_tag( + 'span', + array( + 'sigil' => 'has-tooltip', + 'meta' => array( + 'tip' => pht('Properties Modified'), + 'align' => 'E', + 'size' => 200, + ), + ), + 'M'); + } + + private function renderPropertyChangeDescription() { + $changeset = $this->getChangeset(); + + $file_type = $changeset->getFileType(); + + $desc = DifferentialChangeType::getShortNameForFileType($file_type); + if ($desc === null) { + return null; + } + + return pht('(%s)', $desc); + } + + private function renderChangesetLink() { + $anchor = $this->getAnchor(); + + $changeset = $this->getChangeset(); + $name = $changeset->getDisplayFilename(); + + $change_type = $changeset->getChangeType(); + if (DifferentialChangeType::isOldLocationChangeType($change_type)) { + $away = $changeset->getAwayPaths(); + if (count($away) == 1) { + if ($change_type == DifferentialChangeType::TYPE_MOVE_AWAY) { + $right_arrow = "\xE2\x86\x92"; + $name = $this->renderRename($name, head($away), $right_arrow); + } + } + } else if ($change_type == DifferentialChangeType::TYPE_MOVE_HERE) { + $left_arrow = "\xE2\x86\x90"; + $name = $this->renderRename($name, $changeset->getOldFile(), $left_arrow); + } + + return javelin_tag( + 'a', + array( + 'href' => '#'.$anchor, + 'sigil' => 'differential-load', + 'meta' => array( + 'id' => 'diff-'.$anchor, + ), + ), + $name); + } + + private function renderChangesetLines() { + $changeset = $this->getChangeset(); + + $line_count = $changeset->getAffectedLineCount(); + if (!$line_count) { + return null; + } + + return ' '.pht('(%d line(s))', $line_count); + } + + private function renderCoverage() { + $not_applicable = '-'; + + $coverage = $this->getCoverage(); + if (!strlen($coverage)) { + return $not_applicable; + } + + $covered = substr_count($coverage, 'C'); + $not_covered = substr_count($coverage, 'U'); + + if (!$not_covered && !$covered) { + return $not_applicable; + } + + return sprintf('%d%%', 100 * ($covered / ($covered + $not_covered))); + } + + private function renderModifiedCoverage() { + $not_applicable = '-'; + + $coverage = $this->getCoverage(); + if (!strlen($coverage)) { + return $not_applicable; + } + + if ($this->getIsVisible()) { + $label = pht('Loading...'); + } else { + $label = pht('?'); + } + + return phutil_tag( + 'div', + array( + 'id' => $this->getCoverageID(), + 'class' => 'differential-mcoverage-loading', + ), + $label); + } + + private function renderChangesetMetadata() { + $changeset = $this->getChangeset(); + $type = $changeset->getChangeType(); + + $meta = array(); + if (DifferentialChangeType::isOldLocationChangeType($type)) { + $away = $changeset->getAwayPaths(); + if (count($away) > 1) { + if ($type == DifferentialChangeType::TYPE_MULTICOPY) { + $meta[] = pht('Deleted after being copied to multiple locations:'); + } else { + $meta[] = pht('Copied to multiple locations:'); + } + foreach ($away as $path) { + $meta[] = $path; + } + } else { + if ($type == DifferentialChangeType::TYPE_MOVE_AWAY) { + // This case is handled when we render the path. + } else { + $meta[] = pht('Copied to %s', head($away)); + } + } + } else if ($type == DifferentialChangeType::TYPE_COPY_HERE) { + $meta[] = pht('Copied from %s', $changeset->getOldFile()); + } + + if (!$meta) { + return null; + } + + $meta = phutil_implode_html(phutil_tag('br'), $meta); + + return phutil_tag( + 'div', + array( + 'class' => 'differential-toc-meta', + ), + $meta); + } + + private function renderPackages() { + $packages = $this->getPackages(); + if (!$packages) { + return null; + } + + $viewer = $this->getUser(); + $package_phids = mpull($packages, 'getPHID'); + + return $viewer->renderHandleList($package_phids); + } + + private function renderRename($self, $other, $arrow) { + $old = explode('/', $self); + $new = explode('/', $other); + + $start = count($old); + foreach ($old as $index => $part) { + if (!isset($new[$index]) || $part != $new[$index]) { + $start = $index; + break; + } + } + + $end = count($old); + foreach (array_reverse($old) as $from_end => $part) { + $index = count($new) - $from_end - 1; + if (!isset($new[$index]) || $part != $new[$index]) { + $end = $from_end; + break; + } + } + + $rename = + '{'. + implode('/', array_slice($old, $start, count($old) - $end - $start)). + ' '.$arrow.' '. + implode('/', array_slice($new, $start, count($new) - $end - $start)). + '}'; + + array_splice($new, $start, count($new) - $end - $start, $rename); + + return implode('/', $new); + } + +} diff --git a/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php b/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php new file mode 100644 index 0000000000..c24acf7bb0 --- /dev/null +++ b/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php @@ -0,0 +1,153 @@ +items[] = $item; + return $this; + } + + public function setAuthorityPackages(array $authority_packages) { + assert_instances_of($authority_packages, 'PhabricatorOwnersPackage'); + $this->authorityPackages = $authority_packages; + return $this; + } + + public function getAuthorityPackages() { + return $this->authorityPackages; + } + + public function render() { + $this->requireResource('differential-core-view-css'); + $this->requireResource('differential-table-of-contents-css'); + $this->requireResource('phui-text-css'); + + Javelin::initBehavior('phabricator-tooltips'); + + if ($this->getAuthorityPackages()) { + $authority = mpull($this->getAuthorityPackages(), null, 'getPHID'); + } else { + $authority = array(); + } + + $items = $this->items; + + $rows = array(); + $rowc = array(); + foreach ($items as $item) { + $item->setUser($this->getUser()); + $rows[] = $item->render(); + + $have_authority = false; + + $packages = $item->getPackages(); + if ($packages) { + if (array_intersect_key($packages, $authority)) { + $have_authority = true; + } + } + + if ($have_authority) { + $rowc[] = 'highlighted'; + } else { + $rowc[] = null; + } + } + + // Check if any item has content in these columns. If no item does, we'll + // just hide them. + $any_coverage = false; + $any_context = false; + $any_packages = false; + foreach ($items as $item) { + if ($item->getContext() !== null) { + $any_context = true; + } + + if (strlen($item->getCoverage())) { + $any_coverage = true; + } + + if ($item->getPackages() !== null) { + $any_packages = true; + } + } + + $reveal_link = javelin_tag( + 'a', + array( + 'sigil' => 'differential-reveal-all', + 'mustcapture' => true, + 'class' => 'button differential-toc-reveal-all', + ), + pht('Show All Context')); + + $buttons = phutil_tag( + 'div', + array( + 'class' => 'differential-toc-buttons grouped', + ), + $reveal_link); + + $table = id(new AphrontTableView($rows)) + ->setRowClasses($rowc) + ->setHeaders( + array( + null, + null, + null, + null, + pht('Path'), + pht('Coverage (All)'), + pht('Coverage (Touched)'), + pht('Packages'), + )) + ->setColumnClasses( + array( + 'center', + 'differential-toc-char center', + 'differential-toc-prop center', + 'differential-toc-ftype center', + 'differential-toc-file wide', + 'differential-toc-cov', + 'differential-toc-cov', + null, + )) + ->setColumnVisibility( + array( + $any_context, + true, + true, + true, + true, + $any_coverage, + $any_coverage, + $any_packages, + )) + ->setDeviceVisibility( + array( + true, + true, + true, + true, + true, + false, + false, + true, + )); + + $anchor = id(new PhabricatorAnchorView()) + ->setAnchorName('toc') + ->setNavigationMarker(true); + + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Table of Contents')) + ->setTable($table) + ->appendChild($anchor) + ->appendChild($buttons); + } + +} diff --git a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php index 76f2bab8ba..9ac0d0b591 100644 --- a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php +++ b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php @@ -33,6 +33,7 @@ final class PhabricatorUSEnglishTranslation '%d diff(s)' => array('%d diff', '%d diffs'), '%d Answer(s)' => array('%d Answer', '%d Answers'), + 'Show %d Comment(s)' => array('Show %d Comment', 'Show %d Comments'), '%s DIFF LINK(S)' => array('DIFF LINK', 'DIFF LINKS'), 'You successfully created %d diff(s).' => array( diff --git a/support/PhabricatorStartup.php b/support/PhabricatorStartup.php index fd612f27b0..198cc1b244 100644 --- a/support/PhabricatorStartup.php +++ b/support/PhabricatorStartup.php @@ -34,6 +34,7 @@ * @task apocalypse In Case Of Apocalypse * @task validation Validation * @task ratelimit Rate Limiting + * @task phases Startup Phase Timers */ final class PhabricatorStartup { @@ -43,6 +44,7 @@ final class PhabricatorStartup { private static $capturingOutput; private static $rawInput; private static $oldMemoryLimit; + private static $phases; // TODO: For now, disable rate limiting entirely by default. We need to // iterate on it a bit for Conduit, some of the specific score levels, and @@ -89,10 +91,14 @@ final class PhabricatorStartup { /** + * @param float Request start time, from `microtime(true)`. * @task hook */ - public static function didStartup() { - self::$startTime = microtime(true); + public static function didStartup($start_time) { + self::$startTime = $start_time; + + self::$phases = array(); + self::$accessLog = null; static $registered; @@ -854,4 +860,58 @@ final class PhabricatorStartup { exit(1); } + +/* -( Startup Timers )----------------------------------------------------- */ + + + /** + * Record the beginning of a new startup phase. + * + * For phases which occur before @{class:PhabricatorStartup} loads, save the + * time and record it with @{method:recordStartupPhase} after the class is + * available. + * + * @param string Phase name. + * @task phases + */ + public static function beginStartupPhase($phase) { + self::recordStartupPhase($phase, microtime(true)); + } + + + /** + * Record the start time of a previously executed startup phase. + * + * For startup phases which occur after @{class:PhabricatorStartup} loads, + * use @{method:beginStartupPhase} instead. This method can be used to + * record a time before the class loads, then hand it over once the class + * becomes available. + * + * @param string Phase name. + * @param float Phase start time, from `microtime(true)`. + * @task phases + */ + public static function recordStartupPhase($phase, $time) { + self::$phases[$phase] = $time; + } + + + /** + * Get information about startup phase timings. + * + * Sometimes, performance problems can occur before we start the profiler. + * Since the profiler can't examine these phases, it isn't useful in + * understanding their performance costs. + * + * Instead, the startup process marks when it enters various phases using + * @{method:beginStartupPhase}. A later call to this method can retrieve this + * information, which can be examined to gain greater insight into where + * time was spent. The output is still crude, but better than nothing. + * + * @task phases + */ + public static function getPhases() { + return self::$phases; + } + } diff --git a/webroot/index.php b/webroot/index.php index 3e749be0a9..59e5b71075 100644 --- a/webroot/index.php +++ b/webroot/index.php @@ -1,23 +1,19 @@