1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-02 03:32:42 +01:00

(stable) Promote 2015 Week 34

This commit is contained in:
epriestley 2015-08-22 06:15:34 -07:00
commit f2b2264e8d
105 changed files with 3049 additions and 1729 deletions

View file

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

View file

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

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_owners.owners_package
ADD status VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT};

View file

@ -0,0 +1,2 @@
UPDATE {$NAMESPACE}_owners.owners_package
SET status = 'active' WHERE status = '';

View file

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

View file

@ -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('<http-init>');
$multimeter->setEventViewer('<none>');
@ -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();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,76 +0,0 @@
<?php
final class ConpherenceFileWidgetView extends ConpherenceWidgetView {
public function render() {
$conpherence = $this->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);
}
}

View file

@ -0,0 +1,84 @@
<?php
final class DarkConsoleStartupPlugin extends DarkConsolePlugin {
public function getName() {
return pht('Startup');
}
public function getDescription() {
return pht('Timing information about the startup sequence.');
}
/**
* @phutil-external-symbol class PhabricatorStartup
*/
public function generateData() {
return PhabricatorStartup::getPhases();
}
public function renderPanel() {
$data = $this->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',
));
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,312 +0,0 @@
<?php
final class DifferentialDiffTableOfContentsView extends AphrontView {
private $changesets = array();
private $visibleChangesets = array();
private $references = array();
private $repository;
private $diff;
private $renderURI = '/differential/changeset/';
private $revisionID;
private $whitespace;
private $coverageMap;
public function setChangesets($changesets) {
$this->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);
}
}

View file

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

View file

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

View file

@ -1,103 +0,0 @@
<?php
final class DiffusionCommitChangeTableView extends DiffusionView {
private $pathChanges;
private $ownersPaths = array();
private $renderingReferences;
public function setPathChanges(array $path_changes) {
assert_instances_of($path_changes, 'DiffusionPathChange');
$this->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();
}
}

View file

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

View file

@ -0,0 +1,88 @@
<?php
abstract class HarbormasterArtifact extends Phobject {
private $buildArtifact;
abstract public function getArtifactTypeName();
public function getArtifactTypeSummary() {
return $this->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);
}
}

View file

@ -0,0 +1,66 @@
<?php
final class HarbormasterFileArtifact extends HarbormasterArtifact {
const ARTIFACTCONST = 'file';
public function getArtifactTypeName() {
return pht('File');
}
public function getArtifactTypeDescription() {
return pht(
'Stores a reference to file data which has been uploaded to '.
'Phabricator.');
}
public function getArtifactParameterSpecification() {
return array(
'filePHID' => '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;
}
}

View file

@ -0,0 +1,74 @@
<?php
final class HarbormasterHostArtifact extends HarbormasterArtifact {
const ARTIFACTCONST = 'host';
public function getArtifactTypeName() {
return pht('Drydock Host');
}
public function getArtifactTypeDescription() {
return pht('References a host lease from Drydock.');
}
public function getArtifactParameterSpecification() {
return array(
'drydockLeasePHID' => '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);
}
}
}

View file

@ -0,0 +1,109 @@
<?php
final class HarbormasterURIArtifact extends HarbormasterArtifact {
const ARTIFACTCONST = 'uri';
public function getArtifactTypeName() {
return pht('URI');
}
public function getArtifactTypeSummary() {
return pht('Stores a URI.');
}
public function getArtifactTypeDescription() {
return pht(
"Stores a URI.\n\n".
"With `ui.external`, you can use this artifact type to add links to ".
"build results in an external build system.");
}
public function getArtifactParameterSpecification() {
return array(
'uri' => '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('<Invalid URI>');
}
$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))));
}
}
}

View file

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

View file

@ -0,0 +1,129 @@
<?php
final class HarbormasterCreateArtifactConduitAPIMethod
extends HarbormasterConduitAPIMethod {
public function getAPIMethodName() {
return 'harbormaster.createartifact';
}
public function getMethodSummary() {
return pht('Create a build artifact.');
}
public function getMethodDescription() {
$types = HarbormasterArtifact::getAllArtifactTypes();
$types = msort($types, 'getArtifactTypeName');
$head_key = pht('Key');
$head_type = pht('Type');
$head_desc = pht('Description');
$head_atype = pht('Artifact Type');
$head_name = pht('Name');
$head_summary = pht('Summary');
$out = array();
$out[] = pht(
'Use this method to attach artifacts to build targets while running '.
'builds. Artifacts can be used to carry data through a complex build '.
'workflow, provide extra information to users, or store build results.');
$out[] = null;
$out[] = pht(
'When creating an artifact, you will choose an `artifactType` from '.
'this table. These types of artifacts are supported:');
$out[] = "| {$head_atype} | {$head_name} | {$head_summary} |";
$out[] = '|-------------|--------------|--------------|';
foreach ($types as $type) {
$type_name = $type->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<string, wild>',
);
}
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)),
);
}
}

View file

@ -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('<Unknown Artifact Type>');
$type_name = $artifact->getType();
}
return $list;
$rows[] = array(
$artifact->getArtifactKey(),
$type_name,
$summary,
);
}
$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(

View file

@ -483,7 +483,7 @@ final class HarbormasterBuildEngine extends Phobject {
->execute();
foreach ($artifacts as $artifact) {
$artifact->release();
$artifact->releaseArtifact();
}
}

View file

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

View file

@ -0,0 +1,35 @@
<?php
final class HarbormasterBuildArtifactPHIDType extends PhabricatorPHIDType {
const TYPECONST = 'HMBA';
public function getTypeName() {
return pht('Build Artifact');
}
public function newObject() {
return new HarbormasterBuildArtifact();
}
protected function buildQueryForObjects(
PhabricatorObjectQuery $query,
array $phids) {
return id(new HarbormasterBuildArtifactQuery())
->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));
}
}
}

View file

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

View file

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

View file

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

View file

@ -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');
@ -122,7 +123,7 @@ final class HarbormasterCommandBuildStepImplementation
array(
'name' => pht('Run on Host'),
'key' => $this->getSetting('hostartifact'),
'type' => HarbormasterBuildArtifact::TYPE_HOST,
'type' => HarbormasterHostArtifact::ARTIFACTCONST,
),
);
}

View file

@ -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(),
HarbormasterHostArtifact::ARTIFACTCONST,
array(
'drydockLeasePHID' => $lease->getPHID(),
));
$artifact->save();
}
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,
),
);
}

View file

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

View file

@ -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(
HarbormasterFileArtifact::ARTIFACTCONST,
array(
'filePHID' => $file->getPHID(),
));
$artifact->save();
}
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,
),
);
}

View file

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

View file

@ -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();
$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 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:
return null;
}
public function getArtifactImplementation() {
if ($this->artifactImplementation === null) {
$type = $this->getArtifactType();
$impl = HarbormasterArtifact::getArtifactType($type);
if (!$impl) {
return null;
}
public function loadDrydockLease() {
if ($this->getArtifactType() !== self::TYPE_HOST) {
throw new Exception(
pht(
'`%s` may only be called on host artifacts.',
__FUNCTION__));
$impl = clone $impl;
$impl->setBuildArtifact($this);
$this->artifactImplementation = $impl;
}
$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;
return $this->artifactImplementation;
}
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);
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,51 @@
<?php
final class PhabricatorMailRoutingRule extends Phobject {
const ROUTE_AS_NOTIFICATION = 'route.notification';
const ROUTE_AS_MAIL = 'route.mail';
public static function isStrongerThan($rule_u, $rule_v) {
$strength_u = self::getRuleStrength($rule_u);
$strength_v = self::getRuleStrength($rule_v);
return ($strength_u > $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');
}
}

View file

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

View file

@ -0,0 +1,14 @@
<?php
abstract class PhabricatorMailEmailHeraldField
extends HeraldField {
public function supportsObject($object) {
return ($object instanceof PhabricatorMetaMTAMail);
}
public function getFieldGroupKey() {
return PhabricatorMailEmailHeraldFieldGroup::FIELDGROUPKEY;
}
}

View file

@ -0,0 +1,15 @@
<?php
final class PhabricatorMailEmailHeraldFieldGroup extends HeraldFieldGroup {
const FIELDGROUPKEY = 'mail.message';
public function getGroupLabel() {
return pht('Message Fields');
}
protected function getGroupOrder() {
return 1000;
}
}

View file

@ -0,0 +1,20 @@
<?php
final class PhabricatorMailEmailSubjectHeraldField
extends PhabricatorMailEmailHeraldField {
const FIELDCONST = 'mail.message.subject';
public function getHeraldFieldName() {
return pht('Subject');
}
public function getHeraldFieldValue($object) {
return $object->getSubject();
}
protected function getHeraldFieldStandardType() {
return self::STANDARD_TEXT;
}
}

View file

@ -0,0 +1,62 @@
<?php
final class PhabricatorMailOutboundMailHeraldAdapter
extends HeraldAdapter {
private $mail;
public function getAdapterApplicationClass() {
return 'PhabricatorMetaMTAApplication';
}
public function getAdapterContentDescription() {
return pht('Route outbound email.');
}
protected function initializeNewAdapter() {
$this->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());
}
}

View file

@ -0,0 +1,46 @@
<?php
abstract class PhabricatorMailOutboundRoutingHeraldAction
extends HeraldAction {
const DO_ROUTE = 'do.route';
public function supportsObject($object) {
return ($object instanceof PhabricatorMetaMTAMail);
}
public function getActionGroupKey() {
return HeraldApplicationActionGroup::ACTIONGROUPKEY;
}
protected function applyRouting(HeraldRule $rule, $route, $phids) {
$adapter = $this->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.');
}
}
}

View file

@ -0,0 +1,34 @@
<?php
final class PhabricatorMailOutboundRoutingSelfEmailHeraldAction
extends PhabricatorMailOutboundRoutingHeraldAction {
const ACTIONCONST = 'routing.self.email';
public function getHeraldActionName() {
return pht('Deliver as email');
}
public function supportsRuleType($rule_type) {
return ($rule_type == HeraldRuleTypeConfig::RULE_TYPE_PERSONAL);
}
public function applyEffect($object, HeraldEffect $effect) {
$rule = $effect->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.');
}
}

View file

@ -0,0 +1,34 @@
<?php
final class PhabricatorMailOutboundRoutingSelfNotificationHeraldAction
extends PhabricatorMailOutboundRoutingHeraldAction {
const ACTIONCONST = 'routing.self.notification';
public function getHeraldActionName() {
return pht('Deliver as notification');
}
public function supportsRuleType($rule_type) {
return ($rule_type == HeraldRuleTypeConfig::RULE_TYPE_PERSONAL);
}
public function applyEffect($object, HeraldEffect $effect) {
$rule = $effect->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.');
}
}

View file

@ -14,6 +14,10 @@ abstract class PhabricatorMetaMTAEmailHeraldAction
return false;
}
if ($object instanceof PhabricatorMetaMTAMail) {
return false;
}
return true;
}

View file

@ -37,7 +37,7 @@ final class PhabricatorMetaMTAMailPHIDType extends PhabricatorPHIDType {
$handle
->setName($name)
->setFullName($name);
->setURI('/mail/detail/'.$id.'/');
}
}
}

View file

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

View file

@ -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,6 +967,22 @@ 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"
@ -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 )----------------------------------------- */

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -42,10 +42,6 @@ final class PhabricatorPonderApplication extends PhabricatorApplication {
);
}
public function isPrototype() {
return true;
}
public function getRoutes() {
return array(
'/Q(?P<id>[1-9]\d*)'

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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,6 +192,24 @@ final class PonderQuestionEditor
protected function shouldSendMail(
PhabricatorLiskDAO $object,
array $xactions) {
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;
}
@ -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(

View file

@ -1,14 +0,0 @@
<?php
final class PonderTransactionFeedStory
extends PhabricatorApplicationTransactionFeedStory {
public function getRequiredObjectPHIDs() {
$phids = parent::getRequiredObjectPHIDs();
$answer_phid = $this->getValue('answerPHID');
if ($answer_phid) {
$phids[] = $answer_phid;
}
return $phids;
}
}

View file

@ -0,0 +1,27 @@
<?php
final class PonderAnswerMailReceiver extends PhabricatorObjectMailReceiver {
public function isEnabled() {
$app_class = 'PhabricatorPonderApplication';
return PhabricatorApplication::isClassInstalled($app_class);
}
protected function getObjectPattern() {
return 'ANSR[1-9]\d*';
}
protected function loadObject($pattern, PhabricatorUser $viewer) {
$id = (int)trim($pattern, 'ANSR');
return id(new PonderAnswerQuery())
->setViewer($viewer)
->withIDs(array($id))
->executeOne();
}
protected function getTransactionReplyHandler() {
return new PonderAnswerReplyHandler();
}
}

View file

@ -0,0 +1,16 @@
<?php
final class PonderAnswerReplyHandler
extends PhabricatorApplicationTransactionReplyHandler {
public function validateMailReceiver($mail_receiver) {
if (!($mail_receiver instanceof PonderAnswer)) {
throw new Exception(pht('Mail receiver is not a %s!', 'PonderAnswer'));
}
}
public function getObjectPrefix() {
return 'ANSR';
}
}

View file

@ -129,7 +129,7 @@ final class PonderQuestionQuery
return $questions;
}
private function buildJoinsClause(AphrontDatabaseConnection $conn_r) {
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn_r) {
$joins = array();
if ($this->answererPHIDs) {
@ -141,7 +141,7 @@ final class PonderQuestionQuery
$this->answererPHIDs);
}
return implode(' ', $joins);
return $joins;
}
protected function getPrimaryTableAlias() {

View file

@ -96,7 +96,7 @@ final class PonderQuestionSearchEngine
array($this->requireViewer()->getPHID()));
case 'answered':
return $query->setParameter(
'answererPHIDs',
'answerers',
array($this->requireViewer()->getPHID()));
}

View file

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

View file

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

View file

@ -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) {
$vote_class = null;
if ($votes > 0) {
$vote_class = 'ponder-footer-action-helpful';
}
$icon = id(new PHUIIconView())
->setIconFont('fa-thumbs-up');
->setIconFont('fa-thumbs-up msr');
$helpful = phutil_tag(
'span',
array(
'class' => 'ponder-footer-action',
'class' => 'ponder-footer-action '.$vote_class,
),
array($votes, $icon));
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,
));

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,333 @@
<?php
final class PHUIDiffTableOfContentsItemView extends AphrontView {
private $changeset;
private $isVisible = true;
private $anchor;
private $coverage;
private $coverageID;
private $context;
private $packages;
public function setChangeset(DifferentialChangeset $changeset) {
$this->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);
}
}

View file

@ -0,0 +1,153 @@
<?php
final class PHUIDiffTableOfContentsListView extends AphrontView {
private $items = array();
private $authorityPackages;
public function addItem(PHUIDiffTableOfContentsItemView $item) {
$this->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);
}
}

View file

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

View file

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

View file

@ -1,23 +1,19 @@
<?php
$phabricator_root = dirname(dirname(__FILE__));
require_once $phabricator_root.'/support/PhabricatorStartup.php';
// If the preamble script exists, load it.
$preamble_path = $phabricator_root.'/support/preamble.php';
if (file_exists($preamble_path)) {
require_once $preamble_path;
}
PhabricatorStartup::didStartup();
phabricator_startup();
try {
PhabricatorStartup::beginStartupPhase('libraries');
PhabricatorStartup::loadCoreLibraries();
PhabricatorStartup::beginStartupPhase('purge');
PhabricatorCaches::destroyRequestCache();
PhabricatorStartup::beginStartupPhase('sink');
$sink = new AphrontPHPHTTPSink();
try {
PhabricatorStartup::beginStartupPhase('run');
AphrontApplicationConfiguration::runHTTPRequest($sink);
} catch (Exception $ex) {
try {
@ -36,3 +32,24 @@ try {
} catch (Exception $ex) {
PhabricatorStartup::didEncounterFatalException('Core Exception', $ex, false);
}
function phabricator_startup() {
// Load the PhabricatorStartup class itself.
$t_startup = microtime(true);
$root = dirname(dirname(__FILE__));
require_once $root.'/support/PhabricatorStartup.php';
// If the preamble script exists, load it.
$t_preamble = microtime(true);
$preamble_path = $root.'/support/preamble.php';
if (file_exists($preamble_path)) {
require_once $preamble_path;
}
$t_hook = microtime(true);
PhabricatorStartup::didStartup($t_startup);
PhabricatorStartup::recordStartupPhase('startup.init', $t_startup);
PhabricatorStartup::recordStartupPhase('preamble', $t_preamble);
PhabricatorStartup::recordStartupPhase('hook', $t_hook);
}

View file

@ -95,223 +95,6 @@
top: 142px;
}
/* files widget */
.conpherence-widget-pane #widgets-files .no-files {
width: 200px;
padding: 20px;
text-align: center;
color: {$greytext};
}
.device .conpherence-widget-pane #widgets-files .no-files {
width: 60px;
margin: 0 auto 0 auto;
}
.conpherence-widget-pane #widgets-files .file-entry {
padding: 8px 0;
margin: 0 4px 0 8px;
border-bottom: 1px solid {$thinblueborder};
}
.conpherence-widget-pane #widgets-files .file-entry a {
color: {$darkbluetext};
font-weight: bold;
}
.conpherence-widget-pane #widgets-files .file-icon {
width: 32px;
height: 32px;
float: left;
font-size: 24px;
color: {$blue};
margin: 2px 0 2px 4px;
}
.conpherence-widget-pane #widgets-files .file-title {
display: block;
position: relative;
top: -4px;
left: 0;
overflow-x: hidden;
width: 180px;
font-weight: bold;
text-overflow: ellipsis;
white-space: nowrap;
}
.conpherence-widget-pane #widgets-files .file-uploaded-by {
color: {$lightgreytext};
position: relative;
top: 0;
left: 0;
width: 180px;
font-size: {$smallestfontsize};
}
.device .conpherence-widget-pane #widgets-files .file-title,
.device .conpherence-widget-pane #widgets-files .file-uploaded-by {
width: 82%;
}
.device .conpherence-widget-pane #widgets-files .divider {
width: 80%;
margin: 8px 0px 0px 10%;
}
/* calendar widget */
.conpherence-widget-pane #widgets-calendar .aphront-multi-column-view {
width: 240px;
}
.device-phone .conpherence-widget-pane #widgets-calendar
.aphront-multi-column-view {
display: none;
}
.device-tablet .conpherence-widget-pane #widgets-calendar
.aphront-multi-column-view {
width: 100%;
}
.conpherence-widget-pane #widgets-calendar .aphront-multi-column-view
.aphront-multi-column-column {
background: white;
border-right: 1px solid {$thinblueborder};
text-align: center;
}
.device-phone .conpherence-widget-pane #widgets-calendar
.aphront-multi-column-view .aphront-multi-column-column {
border-right: 0;
}
.device-phone .conpherence-widget-pane #widgets-calendar
.aphront-multi-column-fluid .aphront-multi-column-5-up
.aphront-multi-column-column-outer {
width: 20%;
margin-bottom: 0px;
float: left;
clear: none;
}
.conpherence-widget-pane .no-events {
color: {$lightgreytext};
}
.conpherence-widget-pane #widgets-calendar .aphront-multi-column-view
.aphront-multi-column-column-last {
border-right: 0;
}
.conpherence-widget-pane #widgets-calendar .aphront-multi-column-view
.aphront-multi-column-column .day-column,
.conpherence-widget-pane #widgets-calendar .aphront-multi-column-view
.aphront-multi-column-column .day-column-active {
color: #bfbfbf;
background-color: white;
font-weight: bold;
padding: 0px 0px 10px 0px;
}
.conpherence-widget-pane #widgets-calendar .aphront-multi-column-view
.aphront-multi-column-column .day-column-active {
color: black;
}
.conpherence-widget-pane #widgets-calendar .aphront-multi-column-view
.aphront-multi-column-column .present ,
.conpherence-widget-pane #widgets-calendar .aphront-multi-column-view
.aphront-multi-column-column .sporadic ,
.conpherence-widget-pane #widgets-calendar .aphront-multi-column-view
.aphront-multi-column-column .away {
height: 10px;
margin: 5px 0px 5px 0px;
width: 100%;
}
.conpherence-widget-pane #widgets-calendar .aphront-multi-column-view
.aphront-multi-column-column .present {
background-color: white;
}
.conpherence-widget-pane #widgets-calendar .aphront-multi-column-view
.aphront-multi-column-column .sporadic {
background-color: rgb(222, 226, 232);
}
.conpherence-widget-pane #widgets-calendar .aphront-multi-column-view
.aphront-multi-column-column .away {
background-color: rgb(102, 204, 255);
}
.conpherence-widget-pane #widgets-calendar .aphront-multi-column-view
.day-name {
padding: 5px 0px 0px 0px;
font-size: {$smallerfontsize};
}
.conpherence-widget-pane #widgets-calendar .aphront-multi-column-view
.day-number {
font-size: {$biggestfontsize};
padding: 0 0 5px 0;
}
.conpherence-widget-pane #widgets-calendar .day-header {
overflow: hidden;
border-top: 1px solid {$thinblueborder};
padding: 8px 8px 0px 8px;
}
.conpherence-widget-pane #widgets-calendar .day-header.today .day-name,
.conpherence-widget-pane #widgets-calendar .day-header.today .day-date {
color: {$sky};
}
.conpherence-widget-pane #widgets-calendar .day-header .day-name {
float: left;
color: {$bluetext};
font-weight: bold;
text-transform: uppercase;
font-size: {$smallestfontsize};
}
.conpherence-widget-pane #widgets-calendar .day-header .day-date {
float: right;
color: {$lightbluetext};
font-size: {$smallestfontsize};
}
.conpherence-widget-pane #widgets-calendar .top-border {
border-top: 1px solid {$thinblueborder};
}
.conpherence-widget-pane #widgets-calendar .user-status {
padding: 10px 0px 10px 0px;
margin: 0px 0px 0px 10px;
}
.conpherence-widget-pane #widgets-calendar .user-status .icon {
border-radius: 8px;
height: 8px;
width: 8px;
margin-top: 4px;
float: left;
}
.conpherence-widget-pane #widgets-calendar .sporadic .icon {
background-color: {$orange};
}
.conpherence-widget-pane #widgets-calendar .away .icon {
background-color: {$red};
}
.conpherence-widget-pane #widgets-calendar .user-status .description {
width: 195px;
text-overflow: ellipsis;
margin: 0 0 0 16px;
}
.conpherence-widget-pane #widgets-calendar .user-status .participant {
font-size: {$smallestfontsize};
color: {$lightgreytext};
padding-top: 2px;
}
.device .conpherence-widget-pane #widgets-calendar .user-status .description,
.device .conpherence-widget-pane #widgets-calendar .user-status .participant {
/* we keep these short so no need to change the width */
}
.conpherence-widget-pane .widget-icon {
display: block;
height: 14px;

View file

@ -83,17 +83,13 @@
}
.device-phone .differential-inline-comment .inline-head-right {
float: none;
margin: 12px 0 4px 4px;
}
.device-phone .differential-inline-comment .inline-head-right .mml {
margin: 0 4px 0 0;
}
.device-phone .differential-inline-comment .differential-inline-comment-head {
padding: 0px 0px 4px 8px;
}
/* - Sythetic Comment ---------------------------------------------------------

Some files were not shown because too many files have changed in this diff Show more