1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-11 16:16:14 +01:00

(stable) Promote 2017 Week 4

This commit is contained in:
epriestley 2017-01-27 11:56:02 -08:00
commit 2604c5af55
44 changed files with 2514 additions and 980 deletions

View file

@ -7,12 +7,12 @@
*/
return array(
'names' => array(
'conpherence.pkg.css' => '0b64e988',
'conpherence.pkg.css' => 'e25569a9',
'conpherence.pkg.js' => '6249a1cf',
'core.pkg.css' => 'e75e4f9d',
'core.pkg.css' => 'ea0e9c0c',
'core.pkg.js' => '2291d3b2',
'darkconsole.pkg.js' => 'e7393ebb',
'differential.pkg.css' => '9535a7e6',
'differential.pkg.css' => '4815647b',
'differential.pkg.js' => 'ddfeb49b',
'diffusion.pkg.css' => '91c5d3a6',
'diffusion.pkg.js' => '84c8f8fd',
@ -47,7 +47,7 @@ return array(
'rsrc/css/application/config/setup-issue.css' => 'f794cfc3',
'rsrc/css/application/config/unhandled-exception.css' => '4c96257a',
'rsrc/css/application/conpherence/durable-column.css' => 'd82e130c',
'rsrc/css/application/conpherence/header-pane.css' => '1c81cda6',
'rsrc/css/application/conpherence/header-pane.css' => 'db93ebc6',
'rsrc/css/application/conpherence/menu.css' => '4f51db5a',
'rsrc/css/application/conpherence/message-pane.css' => 'b085d40d',
'rsrc/css/application/conpherence/notification.css' => '965db05b',
@ -59,9 +59,9 @@ return array(
'rsrc/css/application/dashboard/dashboard.css' => 'bc6f2127',
'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a',
'rsrc/css/application/differential/add-comment.css' => 'c47f8c40',
'rsrc/css/application/differential/changeset-view.css' => 'e1621fd5',
'rsrc/css/application/differential/changeset-view.css' => '6a9bdf9c',
'rsrc/css/application/differential/core.css' => '5b7b8ff4',
'rsrc/css/application/differential/phui-inline-comment.css' => '5953c28e',
'rsrc/css/application/differential/phui-inline-comment.css' => 'be663c95',
'rsrc/css/application/differential/revision-comment.css' => '14b8565a',
'rsrc/css/application/differential/revision-history.css' => '0e8eb855',
'rsrc/css/application/differential/revision-list.css' => 'f3c47d33',
@ -83,7 +83,7 @@ return array(
'rsrc/css/application/owners/owners-path-editor.css' => '2f00933b',
'rsrc/css/application/paste/paste.css' => '1898e534',
'rsrc/css/application/people/people-profile.css' => '2473d929',
'rsrc/css/application/phame/phame.css' => 'aeb61182',
'rsrc/css/application/phame/phame.css' => '53fa6236',
'rsrc/css/application/pholio/pholio-edit.css' => '07676f51',
'rsrc/css/application/pholio/pholio-inline-comments.css' => '8e545e49',
'rsrc/css/application/pholio/pholio.css' => 'ca89d380',
@ -96,8 +96,8 @@ return array(
'rsrc/css/application/policy/policy-transaction-detail.css' => '82100a43',
'rsrc/css/application/policy/policy.css' => '957ea14c',
'rsrc/css/application/ponder/ponder-view.css' => 'fbd45f96',
'rsrc/css/application/project/project-card-view.css' => 'd27c67ae',
'rsrc/css/application/project/project-view.css' => '1e6f7072',
'rsrc/css/application/project/project-card-view.css' => 'f25746f5',
'rsrc/css/application/project/project-view.css' => '6936dc6e',
'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',
@ -107,7 +107,7 @@ return array(
'rsrc/css/application/slowvote/slowvote.css' => 'a94b7230',
'rsrc/css/application/tokens/tokens.css' => '3d0f239e',
'rsrc/css/application/uiexample/example.css' => '528b19de',
'rsrc/css/core/core.css' => 'd0801452',
'rsrc/css/core/core.css' => '50dd5fa6',
'rsrc/css/core/remarkup.css' => '4a2de2bb',
'rsrc/css/core/syntax.css' => '769d3498',
'rsrc/css/core/z-index.css' => '5e72c4e0',
@ -134,7 +134,7 @@ return array(
'rsrc/css/phui/phui-basic-nav-view.css' => '7093573b',
'rsrc/css/phui/phui-big-info-view.css' => 'bd903741',
'rsrc/css/phui/phui-box.css' => '33b629f8',
'rsrc/css/phui/phui-button.css' => '9718cb0c',
'rsrc/css/phui/phui-button.css' => '00ddac15',
'rsrc/css/phui/phui-chart.css' => '6bf6f78e',
'rsrc/css/phui/phui-cms.css' => 'be43c8a8',
'rsrc/css/phui/phui-comment-form.css' => '48fbd65d',
@ -145,11 +145,11 @@ return array(
'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf',
'rsrc/css/phui/phui-document.css' => 'c32e8dec',
'rsrc/css/phui/phui-feed-story.css' => '44a9c8e9',
'rsrc/css/phui/phui-fontkit.css' => '9cda225e',
'rsrc/css/phui/phui-fontkit.css' => '0b2da2d5',
'rsrc/css/phui/phui-form-view.css' => 'adca31ce',
'rsrc/css/phui/phui-form.css' => '2342b0e5',
'rsrc/css/phui/phui-form.css' => '5815af7b',
'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f',
'rsrc/css/phui/phui-header-view.css' => '6ec8f155',
'rsrc/css/phui/phui-header-view.css' => '92935c02',
'rsrc/css/phui/phui-hovercard.css' => 'e904f5dc',
'rsrc/css/phui/phui-icon-set-selector.css' => '1ab67aad',
'rsrc/css/phui/phui-icon.css' => '09f46dd9',
@ -159,7 +159,7 @@ return array(
'rsrc/css/phui/phui-invisible-character-view.css' => '6993d9f0',
'rsrc/css/phui/phui-lightbox.css' => '0a035e40',
'rsrc/css/phui/phui-list.css' => '9da2aa00',
'rsrc/css/phui/phui-object-box.css' => '6b487c57',
'rsrc/css/phui/phui-object-box.css' => '8b289e3d',
'rsrc/css/phui/phui-pager.css' => 'bea33d23',
'rsrc/css/phui/phui-pinboard-view.css' => '2495140e',
'rsrc/css/phui/phui-profile-menu.css' => 'c71ecdcd',
@ -170,7 +170,7 @@ return array(
'rsrc/css/phui/phui-status.css' => 'd5263e49',
'rsrc/css/phui/phui-tag-view.css' => '84d65f26',
'rsrc/css/phui/phui-timeline-view.css' => 'bc523970',
'rsrc/css/phui/phui-two-column-view.css' => 'a0d3858a',
'rsrc/css/phui/phui-two-column-view.css' => 'f63cad3c',
'rsrc/css/phui/workboards/phui-workboard-color.css' => 'b60ef38a',
'rsrc/css/phui/workboards/phui-workboard.css' => 'c88912ee',
'rsrc/css/phui/workboards/phui-workcard.css' => 'cca5fa92',
@ -439,7 +439,7 @@ return array(
'rsrc/js/application/phortune/phortune-credit-card-form.js' => '2290aeef',
'rsrc/js/application/policy/behavior-policy-control.js' => 'd0c516d5',
'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '5e9f347c',
'rsrc/js/application/projects/WorkboardBoard.js' => 'fe7cb52a',
'rsrc/js/application/projects/WorkboardBoard.js' => '8935deef',
'rsrc/js/application/projects/WorkboardCard.js' => 'c587b80f',
'rsrc/js/application/projects/WorkboardColumn.js' => '21df4ff5',
'rsrc/js/application/projects/WorkboardController.js' => '55baf5ed',
@ -540,7 +540,7 @@ return array(
'rsrc/js/phui/behavior-phui-tab-group.js' => '0a0b10e9',
'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8',
'rsrc/js/phuix/PHUIXActionView.js' => 'b3465b9b',
'rsrc/js/phuix/PHUIXAutocomplete.js' => '6d86ce8b',
'rsrc/js/phuix/PHUIXAutocomplete.js' => '7c492cd2',
'rsrc/js/phuix/PHUIXDropdownMenu.js' => '8018ee50',
'rsrc/js/phuix/PHUIXFormControl.js' => 'bbece68d',
'rsrc/js/phuix/PHUIXIconView.js' => 'bff6884b',
@ -565,7 +565,7 @@ return array(
'config-options-css' => '0ede4c9b',
'config-page-css' => 'c1d5121b',
'conpherence-durable-column-view' => 'd82e130c',
'conpherence-header-pane-css' => '1c81cda6',
'conpherence-header-pane-css' => 'db93ebc6',
'conpherence-menu-css' => '4f51db5a',
'conpherence-message-pane-css' => 'b085d40d',
'conpherence-notification-css' => '965db05b',
@ -573,7 +573,7 @@ return array(
'conpherence-thread-manager' => 'c8b5ee6f',
'conpherence-transaction-css' => '85129c68',
'd3' => 'a11a5ff2',
'differential-changeset-view-css' => 'e1621fd5',
'differential-changeset-view-css' => '6a9bdf9c',
'differential-core-view-css' => '5b7b8ff4',
'differential-inline-comment-editor' => '2e3f9738',
'differential-revision-add-comment-css' => 'c47f8c40',
@ -765,7 +765,7 @@ return array(
'javelin-view-renderer' => '6c2b09a2',
'javelin-view-visitor' => 'efe49472',
'javelin-websocket' => 'e292eaf4',
'javelin-workboard-board' => 'fe7cb52a',
'javelin-workboard-board' => '8935deef',
'javelin-workboard-card' => 'c587b80f',
'javelin-workboard-column' => '21df4ff5',
'javelin-workboard-controller' => '55baf5ed',
@ -785,7 +785,7 @@ return array(
'phabricator-busy' => '59a7976a',
'phabricator-chatlog-css' => 'd295b020',
'phabricator-content-source-view-css' => '4b8b05d4',
'phabricator-core-css' => 'd0801452',
'phabricator-core-css' => '50dd5fa6',
'phabricator-countdown-css' => '16c52f5c',
'phabricator-dashboard-css' => 'bc6f2127',
'phabricator-drag-and-drop-file-upload' => '58dea2fa',
@ -827,7 +827,7 @@ return array(
'phabricator-uiexample-reactor-sendclass' => '1def2711',
'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee',
'phabricator-zindex-css' => '5e72c4e0',
'phame-css' => 'aeb61182',
'phame-css' => '53fa6236',
'pholio-css' => 'ca89d380',
'pholio-edit-css' => '07676f51',
'pholio-inline-comments-css' => '8e545e49',
@ -842,7 +842,7 @@ return array(
'phui-basic-nav-view-css' => '7093573b',
'phui-big-info-view-css' => 'bd903741',
'phui-box-css' => '33b629f8',
'phui-button-css' => '9718cb0c',
'phui-button-css' => '00ddac15',
'phui-calendar-css' => '477acfaa',
'phui-calendar-day-css' => '572b1893',
'phui-calendar-list-css' => 'fcc9fb41',
@ -858,11 +858,11 @@ return array(
'phui-document-view-pro-css' => 'f56738ed',
'phui-feed-story-css' => '44a9c8e9',
'phui-font-icon-base-css' => '870a7360',
'phui-fontkit-css' => '9cda225e',
'phui-form-css' => '2342b0e5',
'phui-fontkit-css' => '0b2da2d5',
'phui-form-css' => '5815af7b',
'phui-form-view-css' => 'adca31ce',
'phui-head-thing-view-css' => 'fd311e5f',
'phui-header-view-css' => '6ec8f155',
'phui-header-view-css' => '92935c02',
'phui-hovercard' => '1bd28176',
'phui-hovercard-view-css' => 'e904f5dc',
'phui-icon-set-selector-css' => '1ab67aad',
@ -870,11 +870,11 @@ return array(
'phui-image-mask-css' => 'a8498f9c',
'phui-info-panel-css' => '27ea50a1',
'phui-info-view-css' => 'ec92802a',
'phui-inline-comment-view-css' => '5953c28e',
'phui-inline-comment-view-css' => 'be663c95',
'phui-invisible-character-view-css' => '6993d9f0',
'phui-lightbox-css' => '0a035e40',
'phui-list-view-css' => '9da2aa00',
'phui-object-box-css' => '6b487c57',
'phui-object-box-css' => '8b289e3d',
'phui-oi-big-ui-css' => '19f9369b',
'phui-oi-color-css' => 'cd2b9b77',
'phui-oi-drag-ui-css' => 'f12cbc9f',
@ -892,14 +892,14 @@ return array(
'phui-tag-view-css' => '84d65f26',
'phui-theme-css' => '798c69b8',
'phui-timeline-view-css' => 'bc523970',
'phui-two-column-view-css' => 'a0d3858a',
'phui-two-column-view-css' => 'f63cad3c',
'phui-workboard-color-css' => 'b60ef38a',
'phui-workboard-view-css' => 'c88912ee',
'phui-workcard-view-css' => 'cca5fa92',
'phui-workpanel-view-css' => 'a3a63478',
'phuix-action-list-view' => 'b5c256b8',
'phuix-action-view' => 'b3465b9b',
'phuix-autocomplete' => '6d86ce8b',
'phuix-autocomplete' => '7c492cd2',
'phuix-dropdown-menu' => '8018ee50',
'phuix-form-control-view' => 'bbece68d',
'phuix-icon-view' => 'bff6884b',
@ -907,8 +907,8 @@ return array(
'policy-edit-css' => '815c66f7',
'policy-transaction-detail-css' => '82100a43',
'ponder-view-css' => 'fbd45f96',
'project-card-view-css' => 'd27c67ae',
'project-view-css' => '1e6f7072',
'project-card-view-css' => 'f25746f5',
'project-view-css' => '6936dc6e',
'releeph-core' => '9b3c5733',
'releeph-preview-branch' => 'b7a6f4a5',
'releeph-request-differential-create-dialog' => '8d8b92cd',
@ -1396,6 +1396,9 @@ return array(
'69adf288' => array(
'javelin-install',
),
'6a9bdf9c' => array(
'phui-inline-comment-view-css',
),
'6ad39b6f' => array(
'javelin-install',
'javelin-event',
@ -1420,12 +1423,6 @@ return array(
'javelin-typeahead',
'javelin-uri',
),
'6d86ce8b' => array(
'javelin-install',
'javelin-dom',
'phuix-icon-view',
'phabricator-prefab',
),
'70baed2f' => array(
'javelin-install',
'javelin-dom',
@ -1493,6 +1490,12 @@ return array(
'owners-path-editor',
'javelin-behavior',
),
'7c492cd2' => array(
'javelin-install',
'javelin-dom',
'phuix-icon-view',
'phabricator-prefab',
),
'7cbe244b' => array(
'javelin-install',
'javelin-util',
@ -1570,6 +1573,15 @@ return array(
'javelin-stratcom',
'javelin-dom',
),
'8935deef' => array(
'javelin-install',
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-workflow',
'phabricator-draggable-list',
'javelin-workboard-column',
),
'8a41885b' => array(
'javelin-install',
'javelin-dom',
@ -2085,9 +2097,6 @@ return array(
'javelin-request',
'javelin-util',
),
'e1621fd5' => array(
'phui-inline-comment-view-css',
),
'e1d25dfb' => array(
'javelin-behavior',
'javelin-stratcom',
@ -2249,15 +2258,6 @@ return array(
'javelin-view-visitor',
'javelin-util',
),
'fe7cb52a' => array(
'javelin-install',
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-workflow',
'phabricator-draggable-list',
'javelin-workboard-column',
),
'fea0eb47' => array(
'javelin-install',
),

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,50 @@
#!/usr/bin/env php
<?php
require_once dirname(dirname(__FILE__)).'/__init_script__.php';
$args = new PhutilArgumentParser($argv);
$args->setTagline(pht('regenerate Emoji data sheets'));
$args->setSynopsis(<<<EOHELP
**emoji**
Rebuild Emoji data sheets.
EOHELP
);
$args->parseStandardArguments();
$args->parse(
array(
array(
'name' => 'force',
'help' => pht('Force regeneration even if sources have not changed.'),
),
));
$root = dirname(phutil_get_library_root('phabricator'));
// move this to an argument?
$path = $root.'/emoji_strategy.json';
$export_path = $root.'/resources/emoji/manifest.json';
if (Filesystem::pathExists($path)) {
$json = Filesystem::readFile($path);
$emojis = phutil_json_decode($json);
$data = array();
foreach ($emojis as $shortname => $emoji) {
$unicode = $emoji['unicode'];
$codes = explode('-', $unicode);
$hex = '';
foreach ($codes as $code) {
$hex .= phutil_utf8_encode_codepoint(hexdec($code));
}
$data[$shortname] = $hex;
}
ksort($data);
$json = new PhutilJSON();
$data = $json->encodeFormatted($data);
Filesystem::writeFile($export_path, $data);
echo pht('Done.')."\n";
} else {
echo pht('Path %s not exist.', $path)."\n";
}

View file

@ -671,12 +671,13 @@ phutil_register_library_map(array(
'DiffusionCommitRevisionReviewersHeraldField' => 'applications/diffusion/herald/DiffusionCommitRevisionReviewersHeraldField.php',
'DiffusionCommitRevisionSubscribersHeraldField' => 'applications/diffusion/herald/DiffusionCommitRevisionSubscribersHeraldField.php',
'DiffusionCommitSearchConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionCommitSearchConduitAPIMethod.php',
'DiffusionCommitStateTransaction' => 'applications/diffusion/xaction/DiffusionCommitStateTransaction.php',
'DiffusionCommitTagsController' => 'applications/diffusion/controller/DiffusionCommitTagsController.php',
'DiffusionCommitTransactionType' => 'applications/diffusion/xaction/DiffusionCommitTransactionType.php',
'DiffusionCommitVerifyTransaction' => 'applications/diffusion/xaction/DiffusionCommitVerifyTransaction.php',
'DiffusionCompareController' => 'applications/diffusion/controller/DiffusionCompareController.php',
'DiffusionConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionConduitAPIMethod.php',
'DiffusionController' => 'applications/diffusion/controller/DiffusionController.php',
'DiffusionCreateCommentConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionCreateCommentConduitAPIMethod.php',
'DiffusionCreateRepositoriesCapability' => 'applications/diffusion/capability/DiffusionCreateRepositoriesCapability.php',
'DiffusionDaemonLockException' => 'applications/diffusion/exception/DiffusionDaemonLockException.php',
'DiffusionDefaultEditCapability' => 'applications/diffusion/capability/DiffusionDefaultEditCapability.php',
@ -1412,6 +1413,7 @@ phutil_register_library_map(array(
'LiskRawMigrationIterator' => 'infrastructure/storage/lisk/LiskRawMigrationIterator.php',
'MacroConduitAPIMethod' => 'applications/macro/conduit/MacroConduitAPIMethod.php',
'MacroCreateMemeConduitAPIMethod' => 'applications/macro/conduit/MacroCreateMemeConduitAPIMethod.php',
'MacroEmojiExample' => 'applications/uiexample/examples/MacroEmojiExample.php',
'MacroQueryConduitAPIMethod' => 'applications/macro/conduit/MacroQueryConduitAPIMethod.php',
'ManiphestAssignEmailCommand' => 'applications/maniphest/command/ManiphestAssignEmailCommand.php',
'ManiphestAssigneeDatasource' => 'applications/maniphest/typeahead/ManiphestAssigneeDatasource.php',
@ -1830,7 +1832,6 @@ phutil_register_library_map(array(
'PhabricatorApplicationSearchEngine' => 'applications/search/engine/PhabricatorApplicationSearchEngine.php',
'PhabricatorApplicationSearchEngineTestCase' => 'applications/search/engine/__tests__/PhabricatorApplicationSearchEngineTestCase.php',
'PhabricatorApplicationSearchResultView' => 'applications/search/view/PhabricatorApplicationSearchResultView.php',
'PhabricatorApplicationStatusView' => 'applications/meta/view/PhabricatorApplicationStatusView.php',
'PhabricatorApplicationTestCase' => 'applications/base/__tests__/PhabricatorApplicationTestCase.php',
'PhabricatorApplicationTransaction' => 'applications/transactions/storage/PhabricatorApplicationTransaction.php',
'PhabricatorApplicationTransactionComment' => 'applications/transactions/storage/PhabricatorApplicationTransactionComment.php',
@ -2627,6 +2628,7 @@ phutil_register_library_map(array(
'PhabricatorEmailVarySubjectsSetting' => 'applications/settings/setting/PhabricatorEmailVarySubjectsSetting.php',
'PhabricatorEmailVerificationController' => 'applications/auth/controller/PhabricatorEmailVerificationController.php',
'PhabricatorEmbedFileRemarkupRule' => 'applications/files/markup/PhabricatorEmbedFileRemarkupRule.php',
'PhabricatorEmojiDatasource' => 'applications/macro/typeahead/PhabricatorEmojiDatasource.php',
'PhabricatorEmojiRemarkupRule' => 'applications/macro/markup/PhabricatorEmojiRemarkupRule.php',
'PhabricatorEmojiTranslation' => 'infrastructure/internationalization/translation/PhabricatorEmojiTranslation.php',
'PhabricatorEmptyQueryException' => 'infrastructure/query/PhabricatorEmptyQueryException.php',
@ -5377,12 +5379,13 @@ phutil_register_library_map(array(
'DiffusionCommitRevisionReviewersHeraldField' => 'DiffusionCommitHeraldField',
'DiffusionCommitRevisionSubscribersHeraldField' => 'DiffusionCommitHeraldField',
'DiffusionCommitSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'DiffusionCommitStateTransaction' => 'DiffusionCommitTransactionType',
'DiffusionCommitTagsController' => 'DiffusionController',
'DiffusionCommitTransactionType' => 'PhabricatorModularTransactionType',
'DiffusionCommitVerifyTransaction' => 'DiffusionCommitAuditTransaction',
'DiffusionCompareController' => 'DiffusionController',
'DiffusionConduitAPIMethod' => 'ConduitAPIMethod',
'DiffusionController' => 'PhabricatorController',
'DiffusionCreateCommentConduitAPIMethod' => 'DiffusionConduitAPIMethod',
'DiffusionCreateRepositoriesCapability' => 'PhabricatorPolicyCapability',
'DiffusionDaemonLockException' => 'Exception',
'DiffusionDefaultEditCapability' => 'PhabricatorPolicyCapability',
@ -6243,6 +6246,7 @@ phutil_register_library_map(array(
'LiskRawMigrationIterator' => 'PhutilBufferedIterator',
'MacroConduitAPIMethod' => 'ConduitAPIMethod',
'MacroCreateMemeConduitAPIMethod' => 'MacroConduitAPIMethod',
'MacroEmojiExample' => 'PhabricatorUIExample',
'MacroQueryConduitAPIMethod' => 'MacroConduitAPIMethod',
'ManiphestAssignEmailCommand' => 'ManiphestEmailCommand',
'ManiphestAssigneeDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
@ -6712,7 +6716,6 @@ phutil_register_library_map(array(
'PhabricatorApplicationSearchEngine' => 'Phobject',
'PhabricatorApplicationSearchEngineTestCase' => 'PhabricatorTestCase',
'PhabricatorApplicationSearchResultView' => 'Phobject',
'PhabricatorApplicationStatusView' => 'AphrontView',
'PhabricatorApplicationTestCase' => 'PhabricatorTestCase',
'PhabricatorApplicationTransaction' => array(
'PhabricatorLiskDAO',
@ -7641,6 +7644,7 @@ phutil_register_library_map(array(
'PhabricatorEmailVarySubjectsSetting' => 'PhabricatorSelectSetting',
'PhabricatorEmailVerificationController' => 'PhabricatorAuthController',
'PhabricatorEmbedFileRemarkupRule' => 'PhabricatorObjectRemarkupRule',
'PhabricatorEmojiDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorEmojiRemarkupRule' => 'PhutilRemarkupRule',
'PhabricatorEmojiTranslation' => 'PhutilTranslation',
'PhabricatorEmptyQueryException' => 'Exception',

View file

@ -7,12 +7,14 @@ final class PhabricatorAuditCommitStatusConstants extends Phobject {
const CONCERN_RAISED = 2;
const PARTIALLY_AUDITED = 3;
const FULLY_AUDITED = 4;
const NEEDS_VERIFICATION = 5;
public static function getStatusNameMap() {
$map = array(
self::NONE => pht('No Audits'),
self::NEEDS_AUDIT => pht('Audit Required'),
self::CONCERN_RAISED => pht('Concern Raised'),
self::NEEDS_VERIFICATION => pht('Needs Verification'),
self::PARTIALLY_AUDITED => pht('Partially Audited'),
self::FULLY_AUDITED => pht('Audited'),
);
@ -28,6 +30,7 @@ final class PhabricatorAuditCommitStatusConstants extends Phobject {
return array(
self::CONCERN_RAISED,
self::NEEDS_AUDIT,
self::NEEDS_VERIFICATION,
self::PARTIALLY_AUDITED,
);
}
@ -49,6 +52,9 @@ final class PhabricatorAuditCommitStatusConstants extends Phobject {
case self::NONE:
$color = 'bluegrey';
break;
case self::NEEDS_VERIFICATION:
$color = 'indigo';
break;
default:
$color = null;
break;
@ -56,7 +62,7 @@ final class PhabricatorAuditCommitStatusConstants extends Phobject {
return $color;
}
public static function getStatusIcon($code) {
public static function getStatusIcon($code) {
switch ($code) {
case self::CONCERN_RAISED:
$icon = 'fa-times-circle';
@ -73,6 +79,9 @@ final class PhabricatorAuditCommitStatusConstants extends Phobject {
case self::NONE:
$icon = 'fa-check';
break;
case self::NEEDS_VERIFICATION:
$icon = 'fa-refresh';
break;
default:
$icon = null;
break;

View file

@ -2,42 +2,6 @@
final class PhabricatorAuditCommentEditor extends PhabricatorEditor {
/**
* Load the PHIDs for all objects the user has the authority to act as an
* audit for. This includes themselves, and any packages they are an owner
* of.
*/
public static function loadAuditPHIDsForUser(PhabricatorUser $user) {
$phids = array();
// TODO: This method doesn't really use the right viewer, but in practice we
// never issue this query of this type on behalf of another user and are
// unlikely to do so in the future. This entire method should be refactored
// into a Query class, however, and then we should use a proper viewer.
// The user can audit on their own behalf.
$phids[$user->getPHID()] = true;
$owned_packages = id(new PhabricatorOwnersPackageQuery())
->setViewer($user)
->withAuthorityPHIDs(array($user->getPHID()))
->execute();
foreach ($owned_packages as $package) {
$phids[$package->getPHID()] = true;
}
// The user can audit on behalf of all projects they are a member of.
$projects = id(new PhabricatorProjectQuery())
->setViewer($user)
->withMemberPHIDs(array($user->getPHID()))
->execute();
foreach ($projects as $project) {
$phids[$project->getPHID()] = true;
}
return array_keys($phids);
}
public static function getMailThreading(
PhabricatorRepository $repository,
PhabricatorRepositoryCommit $commit) {

View file

@ -11,6 +11,7 @@ final class PhabricatorAuditEditor
private $auditorPHIDs = array();
private $didExpandInlineState = false;
private $oldAuditStatus = null;
public function addAuditReason($phid, $reason) {
if (!isset($this->auditReasonMap[$phid])) {
@ -60,7 +61,6 @@ final class PhabricatorAuditEditor
// TODO: These will get modernized eventually, but that can happen one
// at a time later on.
$types[] = PhabricatorAuditActionConstants::ACTION;
$types[] = PhabricatorAuditActionConstants::INLINE;
$types[] = PhabricatorAuditActionConstants::ADD_AUDITORS;
@ -79,6 +79,12 @@ final class PhabricatorAuditEditor
}
}
$this->oldAuditStatus = $object->getAuditStatus();
$object->loadAndAttachAuditAuthority(
$this->getActor(),
$this->getActingAsPHID());
return parent::expandTransactions($object, $xactions);
}
@ -98,7 +104,6 @@ final class PhabricatorAuditEditor
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorAuditActionConstants::ACTION:
case PhabricatorAuditActionConstants::INLINE:
case PhabricatorAuditTransaction::TYPE_COMMIT:
return null;
@ -116,7 +121,6 @@ final class PhabricatorAuditEditor
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorAuditActionConstants::ACTION:
case PhabricatorAuditActionConstants::INLINE:
case PhabricatorAuditActionConstants::ADD_AUDITORS:
case PhabricatorAuditTransaction::TYPE_COMMIT:
@ -131,7 +135,6 @@ final class PhabricatorAuditEditor
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorAuditActionConstants::ACTION:
case PhabricatorAuditActionConstants::INLINE:
case PhabricatorAuditActionConstants::ADD_AUDITORS:
case PhabricatorAuditTransaction::TYPE_COMMIT:
@ -146,7 +149,6 @@ final class PhabricatorAuditEditor
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorAuditActionConstants::ACTION:
case PhabricatorAuditTransaction::TYPE_COMMIT:
return;
case PhabricatorAuditActionConstants::INLINE:
@ -267,108 +269,44 @@ final class PhabricatorAuditEditor
case PhabricatorAuditTransaction::TYPE_COMMIT:
$import_status_flag = PhabricatorRepositoryCommit::IMPORTED_HERALD;
break;
case PhabricatorAuditActionConstants::ACTION:
$new = $xaction->getNewValue();
switch ($new) {
case PhabricatorAuditActionConstants::CLOSE:
// "Close" means wipe out all the concerns.
$requests = $object->getAudits();
foreach ($requests as $request) {
if ($request->getAuditStatus() == $status_concerned) {
$request
->setAuditStatus($status_closed)
->save();
}
}
break;
case PhabricatorAuditActionConstants::RESIGN:
$requests = $object->getAudits();
$requests = mpull($requests, null, 'getAuditorPHID');
$actor_request = idx($requests, $actor_phid);
// If the actor doesn't currently have a relationship to the
// commit, add one explicitly. For example, this allows members
// of a project to resign from a commit and have it drop out of
// their queue.
if (!$actor_request) {
$actor_request = id(new PhabricatorRepositoryAuditRequest())
->setCommitPHID($object->getPHID())
->setAuditorPHID($actor_phid);
$requests[] = $actor_request;
$object->attachAudits($requests);
}
$actor_request
->setAuditStatus($status_resigned)
->save();
break;
case PhabricatorAuditActionConstants::ACCEPT:
case PhabricatorAuditActionConstants::CONCERN:
if ($new == PhabricatorAuditActionConstants::ACCEPT) {
$new_status = $status_accepted;
} else {
$new_status = $status_concerned;
}
$requests = $object->getAudits();
$requests = mpull($requests, null, 'getAuditorPHID');
// Figure out which requests the actor has authority over: these
// are user requests where they are the auditor, and packages
// and projects they are a member of.
if ($actor_is_author) {
// When modifying your own commits, you act only on behalf of
// yourself, not your packages/projects -- the idea being that
// you can't accept your own commits.
$authority_phids = array($actor_phid);
} else {
$authority_phids =
PhabricatorAuditCommentEditor::loadAuditPHIDsForUser(
$this->requireActor());
}
$authority = array_select_keys(
$requests,
$authority_phids);
if (!$authority) {
// If the actor has no authority over any existing requests,
// create a new request for them.
$actor_request = id(new PhabricatorRepositoryAuditRequest())
->setCommitPHID($object->getPHID())
->setAuditorPHID($actor_phid)
->setAuditStatus($new_status)
->save();
$requests[$actor_phid] = $actor_request;
$object->attachAudits($requests);
} else {
// Otherwise, update the audit status of the existing requests.
foreach ($authority as $request) {
$request
->setAuditStatus($new_status)
->save();
}
}
break;
}
break;
}
}
$old_status = $this->oldAuditStatus;
$requests = $object->getAudits();
$object->updateAuditStatus($requests);
$new_status = $object->getAuditStatus();
$object->save();
if ($import_status_flag) {
$object->writeImportStatusFlag($import_status_flag);
}
$partial_status = PhabricatorAuditCommitStatusConstants::PARTIALLY_AUDITED;
// If the commit has changed state after this edit, add an informational
// transaction about the state change.
if ($old_status != $new_status) {
if ($new_status == $partial_status) {
// This state isn't interesting enough to get a transaction. The
// best way we could lead the user forward is something like "This
// commit still requires additional audits." but that's redundant and
// probably not very useful.
} else {
$xaction = $object->getApplicationTransactionTemplate()
->setTransactionType(DiffusionCommitStateTransaction::TRANSACTIONTYPE)
->setOldValue($old_status)
->setNewValue($new_status);
$xaction = $this->populateTransaction($object, $xaction);
$xaction->save();
}
}
// Collect auditor PHIDs for building mail.
$this->auditorPHIDs = mpull($object->getAudits(), 'getAuditorPHID');
@ -396,7 +334,6 @@ final class PhabricatorAuditEditor
if (!$this->didExpandInlineState) {
switch ($xaction->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
case PhabricatorAuditActionConstants::ACTION:
$this->didExpandInlineState = true;
$actor_phid = $this->getActingAsPHID();
@ -487,69 +424,6 @@ final class PhabricatorAuditEditor
return array_values(array_merge($head, $tail));
}
protected function validateTransaction(
PhabricatorLiskDAO $object,
$type,
array $xactions) {
$errors = parent::validateTransaction($object, $type, $xactions);
foreach ($xactions as $xaction) {
switch ($type) {
case PhabricatorAuditActionConstants::ACTION:
$error = $this->validateAuditAction(
$object,
$type,
$xaction,
$xaction->getNewValue());
if ($error) {
$errors[] = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
$error,
$xaction);
}
break;
}
}
return $errors;
}
private function validateAuditAction(
PhabricatorLiskDAO $object,
$type,
PhabricatorAuditTransaction $xaction,
$action) {
$can_author_close_key = 'audit.can-author-close-audit';
$can_author_close = PhabricatorEnv::getEnvConfig($can_author_close_key);
$actor_is_author = ($object->getAuthorPHID()) &&
($object->getAuthorPHID() == $this->getActingAsPHID());
switch ($action) {
case PhabricatorAuditActionConstants::CLOSE:
if (!$actor_is_author) {
return pht(
'You can not close this audit because you are not the author '.
'of the commit.');
}
if (!$can_author_close) {
return pht(
'You can not close this audit because "%s" is disabled in '.
'the Phabricator configuration.',
$can_author_close_key);
}
break;
}
return null;
}
protected function supportsSearch() {
return true;
}

View file

@ -498,4 +498,22 @@ final class PhabricatorAuditTransaction
}
return $tags;
}
public function shouldDisplayGroupWith(array $group) {
// Make the "This commit now requires audit." state message stand alone.
$type_state = DiffusionCommitStateTransaction::TRANSACTIONTYPE;
if ($this->getTransactionType() == $type_state) {
return false;
}
foreach ($group as $xaction) {
if ($xaction->getTransactionType() == $type_state) {
return false;
}
}
return parent::shouldDisplayGroupWith($group);
}
}

View file

@ -12,8 +12,6 @@ abstract class PhabricatorApplication
extends Phobject
implements PhabricatorPolicyInterface {
const MAX_STATUS_ITEMS = 100;
const GROUP_CORE = 'core';
const GROUP_UTILITIES = 'util';
const GROUP_ADMIN = 'admin';
@ -272,20 +270,6 @@ abstract class PhabricatorApplication
/* -( UI Integration )----------------------------------------------------- */
/**
* Render status elements (like "3 Waiting Reviews") for application list
* views. These provide a way to alert users to new or pending action items
* in applications.
*
* @param PhabricatorUser Viewing user.
* @return list<PhabricatorApplicationStatusView> Application status elements.
* @task ui
*/
public function loadStatus(PhabricatorUser $user) {
return array();
}
/**
* You can provide an optional piece of flavor text for the application. This
* is currently rendered in application launch views if the application has no

View file

@ -20,13 +20,13 @@ final class CelerityDefaultPostprocessor
public function buildVariables() {
return array(
// Fonts
'basefont' => "13px 'Segoe UI', 'Segoe UI Web Regular', ".
"'Segoe UI Symbol', 'Lato', 'Helvetica Neue', Helvetica, ".
"Arial, sans-serif",
'basefont' => "13px 'Segoe UI', 'Segoe UI Emoji', ".
"'Segoe UI Symbol', 'Lato', 'Helvetica Neue', ".
"Helvetica, Arial, sans-serif",
'fontfamily' => "'Segoe UI', 'Segoe UI Web Regular', ".
"'Segoe UI Symbol', 'Lato', 'Helvetica Neue', Helvetica, ".
"Arial, sans-serif",
'fontfamily' => "'Segoe UI', 'Segoe UI Emoji', ".
"'Segoe UI Symbol', 'Lato', 'Helvetica Neue', ".
"Helvetica, Arial, sans-serif",
// Drop Shadow
'dropshadow' => '0 2px 12px rgba(0, 0, 0, .20)',

View file

@ -103,82 +103,6 @@ final class PhabricatorDifferentialApplication extends PhabricatorApplication {
);
}
public static function loadNeedAttentionRevisions(PhabricatorUser $viewer) {
if (!$viewer->isLoggedIn()) {
return array();
}
$viewer_phid = $viewer->getPHID();
$responsible_phids = id(new DifferentialResponsibleDatasource())
->setViewer($viewer)
->evaluateTokens(array($viewer_phid));
$revision_query = id(new DifferentialRevisionQuery())
->setViewer($viewer)
->withStatus(DifferentialRevisionQuery::STATUS_OPEN)
->withResponsibleUsers($responsible_phids)
->needReviewerStatus(true)
->needRelationships(true)
->needFlags(true)
->needDrafts(true)
->setLimit(self::MAX_STATUS_ITEMS);
$revisions = $revision_query->execute();
$query = id(new PhabricatorSavedQuery())
->attachParameterMap(
array(
'responsiblePHIDs' => $responsible_phids,
));
$groups = id(new DifferentialRevisionRequiredActionResultBucket())
->setViewer($viewer)
->newResultGroups($query, $revisions);
$include = array();
foreach ($groups as $group) {
switch ($group->getKey()) {
case DifferentialRevisionRequiredActionResultBucket::KEY_MUSTREVIEW:
case DifferentialRevisionRequiredActionResultBucket::KEY_SHOULDREVIEW:
foreach ($group->getObjects() as $object) {
$include[] = $object;
}
break;
default:
break;
}
}
return $include;
}
public function loadStatus(PhabricatorUser $user) {
$revisions = self::loadNeedAttentionRevisions($user);
$limit = self::MAX_STATUS_ITEMS;
if (count($revisions) >= $limit) {
$display_count = ($limit - 1);
$display_label = pht(
'%s+ Active Review(s)',
new PhutilNumber($display_count));
} else {
$display_count = count($revisions);
$display_label = pht(
'%s Review(s) Need Attention',
new PhutilNumber($display_count));
}
$status = array();
$status[] = id(new PhabricatorApplicationStatusView())
->setType(PhabricatorApplicationStatusView::TYPE_WARNING)
->setText($display_label)
->setCount($display_count);
return $status;
}
public function supportsEmailIntegration() {
return true;
}

View file

@ -64,52 +64,11 @@ abstract class DifferentialCoreCustomField
continue;
}
}
if (is_string($value)) {
$parser = $this->getFieldParser();
$result = $parser->parseCorpus($value);
unset($result['__title__']);
unset($result['__summary__']);
if ($result) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
pht(
'The value you have entered in "%s" can not be parsed '.
'unambiguously when rendered in a commit message. Edit the '.
'message so that keywords like "Summary:" and "Test Plan:" do '.
'not appear at the beginning of lines. Parsed keys: %s.',
$this->getFieldName(),
implode(', ', array_keys($result))),
$xaction);
$errors[] = $error;
$this->setFieldError(pht('Invalid'));
continue;
}
}
}
return $errors;
}
private function getFieldParser() {
if (!$this->fieldParser) {
$viewer = $this->getViewer();
$parser = DifferentialCommitMessageParser::newStandardParser($viewer);
// Set custom title and summary keys so we can detect the presence of
// "Summary:" in, e.g., a test plan.
$parser->setTitleKey('__title__');
$parser->setSummaryKey('__summary__');
$this->fieldParser = $parser;
}
return $this->fieldParser;
}
public function canDisableField() {
return false;
}

View file

@ -50,4 +50,11 @@ final class DifferentialTestPlanCommitMessageField
);
}
public function validateTransactions($object, array $xactions) {
return $this->validateCommitMessageCorpusTransactions(
$object,
$xactions,
pht('Test Plan'));
}
}

View file

@ -54,4 +54,11 @@ final class DifferentialRevisionSummaryTransaction
return $changes;
}
public function validateTransactions($object, array $xactions) {
return $this->validateCommitMessageCorpusTransactions(
$object,
$xactions,
pht('Summary'));
}
}

View file

@ -1,4 +1,60 @@
<?php
abstract class DifferentialRevisionTransactionType
extends PhabricatorModularTransactionType {}
extends PhabricatorModularTransactionType {
protected function validateCommitMessageCorpusTransactions(
$object,
array $xactions,
$field_name) {
$errors = array();
foreach ($xactions as $xaction) {
$error = $this->validateMessageCorpus($xaction, $field_name);
if ($error) {
$errors[] = $error;
}
}
return $errors;
}
private function validateMessageCorpus($xaction, $field_name) {
$value = $xaction->getNewValue();
if (!strlen($value)) {
return null;
}
// Put a placeholder title on the message, because the first line of a
// message is now always parsed as a title.
$value = "<placeholder>\n".$value;
$viewer = $this->getActor();
$parser = DifferentialCommitMessageParser::newStandardParser($viewer);
// Set custom title and summary keys so we can detect the presence of
// "Summary:" in, e.g., a test plan.
$parser->setTitleKey('__title__');
$parser->setSummaryKey('__summary__');
$result = $parser->parseCorpus($value);
unset($result['__title__']);
unset($result['__summary__']);
if (!$result) {
return null;
}
return $this->newInvalidError(
pht(
'The value you have entered in "%s" can not be parsed '.
'unambiguously when rendered in a commit message. Edit the '.
'message so that keywords like "Summary:" and "Test Plan:" do '.
'not appear at the beginning of lines. Parsed keys: %s.',
$field_name,
implode(', ', array_keys($result))),
$xaction);
}
}

View file

@ -1,106 +0,0 @@
<?php
final class DiffusionCreateCommentConduitAPIMethod
extends DiffusionConduitAPIMethod {
public function getAPIMethodName() {
return 'diffusion.createcomment';
}
public function getMethodStatus() {
return self::METHOD_STATUS_DEPRECATED;
}
public function getMethodDescription() {
return pht(
'Add a comment to a Diffusion commit. By specifying an action '.
'of "%s", "%s", "%s", or "%s", auditing actions can '.
'be triggered. Defaults to "%s".',
'concern',
'accept',
'resign',
'close',
'comment');
}
protected function defineParamTypes() {
return array(
'phid' => 'required string',
'action' => 'optional string',
'message' => 'required string',
'silent' => 'optional bool',
);
}
protected function defineReturnType() {
return 'bool';
}
protected function defineErrorTypes() {
return array(
'ERR_BAD_COMMIT' => pht('No commit found with that PHID.'),
'ERR_BAD_ACTION' => pht('Invalid action type.'),
'ERR_MISSING_MESSAGE' => pht('Message is required.'),
);
}
protected function execute(ConduitAPIRequest $request) {
$commit_phid = $request->getValue('phid');
$commit = id(new DiffusionCommitQuery())
->setViewer($request->getUser())
->withPHIDs(array($commit_phid))
->needAuditRequests(true)
->executeOne();
if (!$commit) {
throw new ConduitException('ERR_BAD_COMMIT');
}
$message = trim($request->getValue('message'));
if (!$message) {
throw new ConduitException('ERR_MISSING_MESSAGE');
}
$action = $request->getValue('action');
if (!$action) {
$action = PhabricatorAuditActionConstants::COMMENT;
}
// Disallow ADD_CCS, ADD_AUDITORS forever.
if (!in_array($action, array(
PhabricatorAuditActionConstants::CONCERN,
PhabricatorAuditActionConstants::ACCEPT,
PhabricatorAuditActionConstants::COMMENT,
PhabricatorAuditActionConstants::RESIGN,
PhabricatorAuditActionConstants::CLOSE,
))) {
throw new ConduitException('ERR_BAD_ACTION');
}
$xactions = array();
if ($action != PhabricatorAuditActionConstants::COMMENT) {
$xactions[] = id(new PhabricatorAuditTransaction())
->setTransactionType(PhabricatorAuditActionConstants::ACTION)
->setNewValue($action);
}
if (strlen($message)) {
$xactions[] = id(new PhabricatorAuditTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_COMMENT)
->attachComment(
id(new PhabricatorAuditTransactionComment())
->setCommitPHID($commit->getPHID())
->setContent($message));
}
id(new PhabricatorAuditEditor())
->setActor($request->getUser())
->setContentSource($request->newContentSource())
->setDisableEmail($request->getValue('silent'))
->setContinueOnMissingFields(true)
->applyTransactions($commit, $xactions);
return true;
}
}

View file

@ -4,9 +4,6 @@ final class DiffusionCommitController extends DiffusionController {
const CHANGES_LIMIT = 100;
private $auditAuthorityPHIDs;
private $highlightedAudits;
private $commitParents;
private $commitRefs;
private $commitMerges;
@ -67,8 +64,7 @@ final class DiffusionCommitController extends DiffusionController {
}
$audit_requests = $commit->getAudits();
$this->auditAuthorityPHIDs =
PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($viewer);
$commit->loadAndAttachAuditAuthority($viewer);
$commit_data = $commit->getCommitData();
$is_foreign = $commit_data->getCommitDetail('foreign-svn-stub');
@ -209,10 +205,6 @@ final class DiffusionCommitController extends DiffusionController {
$timeline = $this->buildComments($commit);
$merge_table = $this->buildMergesTable($commit);
$highlighted_audits = $commit->getAuthorityAudits(
$viewer,
$this->auditAuthorityPHIDs);
$show_changesets = false;
$info_panel = null;
$change_list = null;
@ -520,13 +512,13 @@ final class DiffusionCommitController extends DiffusionController {
if ($user_requests) {
$view->addProperty(
pht('Auditors'),
$this->renderAuditStatusView($user_requests));
$this->renderAuditStatusView($commit, $user_requests));
}
if ($other_requests) {
$view->addProperty(
pht('Group Auditors'),
$this->renderAuditStatusView($other_requests));
$this->renderAuditStatusView($commit, $other_requests));
}
}
@ -739,85 +731,6 @@ final class DiffusionCommitController extends DiffusionController {
return $comment_view;
}
/**
* Return a map of available audit actions for rendering into a <select />.
* This shows the user valid actions, and does not show nonsense/invalid
* actions (like closing an already-closed commit, or resigning from a commit
* you have no association with).
*/
private function getAuditActions(
PhabricatorRepositoryCommit $commit,
array $audit_requests) {
assert_instances_of($audit_requests, 'PhabricatorRepositoryAuditRequest');
$viewer = $this->getViewer();
$user_is_author = ($commit->getAuthorPHID() == $viewer->getPHID());
$user_request = null;
foreach ($audit_requests as $audit_request) {
if ($audit_request->getAuditorPHID() == $viewer->getPHID()) {
$user_request = $audit_request;
break;
}
}
$actions = array();
$actions[PhabricatorAuditActionConstants::COMMENT] = true;
// We allow you to accept your own commits. A use case here is that you
// notice an issue with your own commit and "Raise Concern" as an indicator
// to other auditors that you're on top of the issue, then later resolve it
// and "Accept". You can not accept on behalf of projects or packages,
// however.
$actions[PhabricatorAuditActionConstants::ACCEPT] = true;
$actions[PhabricatorAuditActionConstants::CONCERN] = true;
// To resign, a user must have authority on some request and not be the
// commit's author.
if (!$user_is_author) {
$may_resign = false;
$authority_map = array_fill_keys($this->auditAuthorityPHIDs, true);
foreach ($audit_requests as $request) {
if (empty($authority_map[$request->getAuditorPHID()])) {
continue;
}
$may_resign = true;
break;
}
// If the user has already resigned, don't show "Resign...".
$status_resigned = PhabricatorAuditStatusConstants::RESIGNED;
if ($user_request) {
if ($user_request->getAuditStatus() == $status_resigned) {
$may_resign = false;
}
}
if ($may_resign) {
$actions[PhabricatorAuditActionConstants::RESIGN] = true;
}
}
$status_concern = PhabricatorAuditCommitStatusConstants::CONCERN_RAISED;
$concern_raised = ($commit->getAuditStatus() == $status_concern);
$can_close_option = PhabricatorEnv::getEnvConfig(
'audit.can-author-close-audit');
if ($can_close_option && $user_is_author && $concern_raised) {
$actions[PhabricatorAuditActionConstants::CLOSE] = true;
}
$actions[PhabricatorAuditActionConstants::ADD_AUDITORS] = true;
$actions[PhabricatorAuditActionConstants::ADD_CCS] = true;
foreach ($actions as $constant => $ignored) {
$actions[$constant] =
PhabricatorAuditActionConstants::getActionName($constant);
}
return $actions;
}
private function buildMergesTable(PhabricatorRepositoryCommit $commit) {
$viewer = $this->getViewer();
$drequest = $this->getDiffusionRequest();
@ -927,12 +840,12 @@ final class DiffusionCommitController extends DiffusionController {
return $file->getRedirectResponse();
}
private function renderAuditStatusView(array $audit_requests) {
private function renderAuditStatusView(
PhabricatorRepositoryCommit $commit,
array $audit_requests) {
assert_instances_of($audit_requests, 'PhabricatorRepositoryAuditRequest');
$viewer = $this->getViewer();
$authority_map = array_fill_keys($this->auditAuthorityPHIDs, true);
$view = new PHUIStatusListView();
foreach ($audit_requests as $request) {
$code = $request->getAuditStatus();
@ -952,7 +865,7 @@ final class DiffusionCommitController extends DiffusionController {
$target = $viewer->renderHandle($auditor_phid);
$item->setTarget($target);
if (isset($authority_map[$auditor_phid])) {
if ($commit->hasAuditAuthority($viewer, $request)) {
$item->setHighlighted(true);
}

View file

@ -33,6 +33,11 @@ final class DiffusionCommitRequiredActionResultBucket
->setNoDataString(pht('None of your commits have active concerns.'))
->setObjects($this->filterConcernRaised($phids));
$groups[] = $this->newGroup()
->setName(pht('Needs Verification'))
->setNoDataString(pht('No commits are awaiting your verification.'))
->setObjects($this->filterNeedsVerification($phids));
$groups[] = $this->newGroup()
->setName(pht('Ready to Audit'))
->setNoDataString(pht('No commits are waiting for you to audit them.'))
@ -82,6 +87,36 @@ final class DiffusionCommitRequiredActionResultBucket
return $results;
}
private function filterNeedsVerification(array $phids) {
$results = array();
$objects = $this->objects;
$status_verify = PhabricatorAuditCommitStatusConstants::NEEDS_VERIFICATION;
$has_concern = array(
PhabricatorAuditStatusConstants::CONCERNED,
);
$has_concern = array_fuse($has_concern);
foreach ($objects as $key => $object) {
if (isset($phids[$object->getAuthorPHID()])) {
continue;
}
if ($object->getAuditStatus() != $status_verify) {
continue;
}
if (!$this->hasAuditorsWithStatus($object, $phids, $has_concern)) {
continue;
}
$results[$key] = $object;
unset($this->objects[$key]);
}
return $results;
}
private function filterShouldAudit(array $phids) {
$results = array();
$objects = $this->objects;
@ -132,6 +167,7 @@ final class DiffusionCommitRequiredActionResultBucket
$status_waiting = array(
PhabricatorAuditCommitStatusConstants::NEEDS_AUDIT,
PhabricatorAuditCommitStatusConstants::NEEDS_VERIFICATION,
PhabricatorAuditCommitStatusConstants::PARTIALLY_AUDITED,
);
$status_waiting = array_fuse($status_waiting);

View file

@ -30,11 +30,6 @@ final class DiffusionCommitAcceptTransaction
return pht('Accepted');
}
public function generateOldValue($object) {
$actor = $this->getActor();
return $this->isViewerAcceptingAuditor($object, $actor);
}
public function applyExternalEffects($object, $value) {
$status = PhabricatorAuditStatusConstants::ACCEPTED;
$actor = $this->getActor();
@ -54,7 +49,7 @@ final class DiffusionCommitAcceptTransaction
}
}
if ($this->isViewerAcceptingAuditor($object, $viewer)) {
if ($this->isViewerFullyAccepted($object, $viewer)) {
throw new Exception(
pht(
'You can not accept this commit because you have already '.

View file

@ -7,6 +7,10 @@ abstract class DiffusionCommitAuditTransaction
return DiffusionCommitEditEngine::ACTIONGROUP_AUDIT;
}
public function generateOldValue($object) {
return false;
}
protected function isViewerAnyAuditor(
PhabricatorRepositoryCommit $commit,
PhabricatorUser $viewer) {
@ -18,22 +22,23 @@ abstract class DiffusionCommitAuditTransaction
PhabricatorUser $viewer) {
// This omits various inactive states like "Resigned" and "Not Required".
$active = array(
PhabricatorAuditStatusConstants::AUDIT_REQUIRED,
PhabricatorAuditStatusConstants::CONCERNED,
PhabricatorAuditStatusConstants::ACCEPTED,
PhabricatorAuditStatusConstants::AUDIT_REQUESTED,
);
$active = array_fuse($active);
return $this->isViewerAuditStatusAmong(
$commit,
$viewer,
array(
PhabricatorAuditStatusConstants::AUDIT_REQUIRED,
PhabricatorAuditStatusConstants::CONCERNED,
PhabricatorAuditStatusConstants::ACCEPTED,
PhabricatorAuditStatusConstants::AUDIT_REQUESTED,
));
$viewer_status = $this->getViewerAuditStatus($commit, $viewer);
return isset($active[$viewer_status]);
}
protected function isViewerAcceptingAuditor(
protected function isViewerFullyAccepted(
PhabricatorRepositoryCommit $commit,
PhabricatorUser $viewer) {
return $this->isViewerAuditStatusAmong(
return $this->isViewerAuditStatusFullyAmong(
$commit,
$viewer,
array(
@ -41,10 +46,10 @@ abstract class DiffusionCommitAuditTransaction
));
}
protected function isViewerRejectingAuditor(
protected function isViewerFullyRejected(
PhabricatorRepositoryCommit $commit,
PhabricatorUser $viewer) {
return $this->isViewerAuditStatusAmong(
return $this->isViewerAuditStatusFullyAmong(
$commit,
$viewer,
array(
@ -71,7 +76,7 @@ abstract class DiffusionCommitAuditTransaction
return null;
}
protected function isViewerAuditStatusAmong(
protected function isViewerAuditStatusFullyAmong(
PhabricatorRepositoryCommit $commit,
PhabricatorUser $viewer,
array $status_list) {
@ -82,7 +87,20 @@ abstract class DiffusionCommitAuditTransaction
}
$status_map = array_fuse($status_list);
return isset($status_map[$status]);
foreach ($commit->getAudits() as $audit) {
if (!$commit->hasAuditAuthority($viewer, $audit)) {
continue;
}
$status = $audit->getAuditStatus();
if (isset($status_map[$status])) {
continue;
}
return false;
}
return true;
}
protected function applyAuditorEffect(
@ -91,6 +109,9 @@ abstract class DiffusionCommitAuditTransaction
$value,
$status) {
$actor = $this->getActor();
$acting_phid = $this->getActingAsPHID();
$audits = $commit->getAudits();
$audits = mpull($audits, null, 'getAuditorPHID');
@ -98,13 +119,9 @@ abstract class DiffusionCommitAuditTransaction
$with_authority = ($status != PhabricatorAuditStatusConstants::RESIGNED);
if ($with_authority) {
$has_authority = PhabricatorAuditCommentEditor::loadAuditPHIDsForUser(
$viewer);
$has_authority = array_fuse($has_authority);
foreach ($audits as $audit) {
$auditor_phid = $audit->getAuditorPHID();
if (isset($has_authority[$auditor_phid])) {
$map[$auditor_phid] = $status;
if ($commit->hasAuditAuthority($actor, $audit, $acting_phid)) {
$map[$audit->getAuditorPHID()] = $status;
}
}
}

View file

@ -30,9 +30,11 @@ final class DiffusionCommitConcernTransaction
return pht('Raised Concern');
}
public function generateOldValue($object) {
$actor = $this->getActor();
return $this->isViewerRejectingAuditor($object, $actor);
public function applyInternalEffects($object, $value) {
// NOTE: We force the commit directly into "Concern Raised" so that we
// override a possible "Needs Verification" state.
$object->setAuditStatus(
PhabricatorAuditCommitStatusConstants::CONCERN_RAISED);
}
public function applyExternalEffects($object, $value) {
@ -50,11 +52,17 @@ final class DiffusionCommitConcernTransaction
'you did not author.'));
}
if ($this->isViewerRejectingAuditor($object, $viewer)) {
throw new Exception(
pht(
'You can not raise a concern with this commit because you have '.
'already raised a concern with it.'));
// Even if you've already raised a concern, you can raise again as long
// as the author requsted you verify.
$state_verify = PhabricatorAuditCommitStatusConstants::NEEDS_VERIFICATION;
if ($this->isViewerFullyRejected($object, $viewer)) {
if ($object->getAuditStatus() != $state_verify) {
throw new Exception(
pht(
'You can not raise a concern with this commit because you have '.
'already raised a concern with it.'));
}
}
}

View file

@ -0,0 +1,72 @@
<?php
final class DiffusionCommitStateTransaction
extends DiffusionCommitTransactionType {
const TRANSACTIONTYPE = 'diffusion.commit.state';
public function generateNewValue($object, $value) {
// NOTE: This transaction can not be generated or applied normally. It is
// written to the transaction log as a side effect of a state change.
throw new PhutilMethodNotImplementedException();
}
public function getIcon() {
$new = $this->getNewValue();
return PhabricatorAuditCommitStatusConstants::getStatusIcon($new);
}
public function getColor() {
$new = $this->getNewValue();
return PhabricatorAuditCommitStatusConstants::getStatusColor($new);
}
public function getTitle() {
$new = $this->getNewValue();
switch ($new) {
case PhabricatorAuditCommitStatusConstants::NONE:
return pht('This commit no longer requires audit.');
case PhabricatorAuditCommitStatusConstants::NEEDS_AUDIT:
return pht('This commit now requires audit.');
case PhabricatorAuditCommitStatusConstants::CONCERN_RAISED:
return pht('This commit now has outstanding concerns.');
case PhabricatorAuditCommitStatusConstants::NEEDS_VERIFICATION:
return pht('This commit now requires verification by auditors.');
case PhabricatorAuditCommitStatusConstants::FULLY_AUDITED:
return pht('All concerns with this commit have now been addressed.');
}
return null;
}
public function getTitleForFeed() {
$new = $this->getNewValue();
switch ($new) {
case PhabricatorAuditCommitStatusConstants::NONE:
return pht(
'%s no longer requires audit.',
$this->renderObject());
case PhabricatorAuditCommitStatusConstants::NEEDS_AUDIT:
return pht(
'%s now requires audit.',
$this->renderObject());
case PhabricatorAuditCommitStatusConstants::CONCERN_RAISED:
return pht(
'%s now has outstanding concerns.',
$this->renderObject());
case PhabricatorAuditCommitStatusConstants::NEEDS_VERIFICATION:
return pht(
'%s now requires verification by auditors.',
$this->renderObject());
case PhabricatorAuditCommitStatusConstants::FULLY_AUDITED:
return pht(
'All concerns with %s have now been addressed.',
$this->renderObject());
}
return null;
}
}

View file

@ -0,0 +1,73 @@
<?php
final class DiffusionCommitVerifyTransaction
extends DiffusionCommitAuditTransaction {
const TRANSACTIONTYPE = 'diffusion.commit.verify';
const ACTIONKEY = 'verify';
protected function getCommitActionLabel() {
return pht('Request Verification');
}
protected function getCommitActionDescription() {
return pht(
'Auditors will be asked to verify that concerns have been addressed.');
}
protected function getCommitActionGroupKey() {
return DiffusionCommitEditEngine::ACTIONGROUP_COMMIT;
}
public function getIcon() {
return 'fa-refresh';
}
public function getColor() {
return 'indigo';
}
protected function getCommitActionOrder() {
return 600;
}
public function getActionName() {
return pht('Requested Verification');
}
public function applyInternalEffects($object, $value) {
$object->setAuditStatus(
PhabricatorAuditCommitStatusConstants::NEEDS_VERIFICATION);
}
protected function validateAction($object, PhabricatorUser $viewer) {
if (!$this->isViewerCommitAuthor($object, $viewer)) {
throw new Exception(
pht(
'You can not request verification of this commit because you '.
'are not the author.'));
}
$status = $object->getAuditStatus();
if ($status != PhabricatorAuditCommitStatusConstants::CONCERN_RAISED) {
throw new Exception(
pht(
'You can not request verification of this commit because no '.
'auditors have raised conerns with it.'));
}
}
public function getTitle() {
return pht(
'%s requested verification of this commit.',
$this->renderAuthor());
}
public function getTitleForFeed() {
return pht(
'%s requested verification of %s.',
$this->renderAuthor(),
$this->renderObject());
}
}

View file

@ -32,32 +32,6 @@ final class PhabricatorFlagsApplication extends PhabricatorApplication {
return self::GROUP_UTILITIES;
}
public function loadStatus(PhabricatorUser $user) {
$status = array();
$limit = self::MAX_STATUS_ITEMS;
$flags = id(new PhabricatorFlagQuery())
->setViewer($user)
->withOwnerPHIDs(array($user->getPHID()))
->setLimit(self::MAX_STATUS_ITEMS)
->execute();
$count = count($flags);
if ($count >= $limit) {
$count_str = pht('%s+ Flagged Object(s)', new PhutilNumber($limit - 1));
} else {
$count_str = pht('%s Flagged Object(s)', new PhutilNumber($count));
}
$type = PhabricatorApplicationStatusView::TYPE_WARNING;
$status[] = id(new PhabricatorApplicationStatusView())
->setType($type)
->setText($count_str)
->setCount($count);
return $status;
}
public function getRoutes() {
return array(
'/flag/' => array(

View file

@ -31,7 +31,6 @@ abstract class PhabricatorHomeController extends PhabricatorController {
$tiles[] = id(new PhabricatorApplicationLaunchView())
->setApplication($home_app)
->setApplicationStatus($home_app->loadStatus($user))
->addClass('phabricator-application-launch-phone-only')
->setUser($user);
@ -44,7 +43,6 @@ abstract class PhabricatorHomeController extends PhabricatorController {
$tile = id(new PhabricatorApplicationLaunchView())
->setApplication($application)
->setApplicationStatus($application->loadStatus($user))
->setUser($user);
$tiles[] = $tile;

View file

@ -7,8 +7,10 @@ final class PhabricatorHomeMenuItemController
$viewer = $this->getViewer();
$type = $request->getURIData('type');
$custom_phid = null;
$menu = PhabricatorProfileMenuEngine::MENU_GLOBAL;
if ($type == 'personal') {
$custom_phid = $viewer->getPHID();
$menu = PhabricatorProfileMenuEngine::MENU_PERSONAL;
}
$application = 'PhabricatorHomeApplication';
@ -21,7 +23,9 @@ final class PhabricatorHomeMenuItemController
$engine = id(new PhabricatorHomeProfileMenuEngine())
->setProfileObject($home_app)
->setCustomPHID($custom_phid)
->setController($this);
->setMenuType($menu)
->setController($this)
->setShowNavigation(false);
return $engine->buildResponse();
}

View file

@ -13,6 +13,13 @@ final class PhabricatorEmojiRemarkupRule extends PhutilRemarkupRule {
$text);
}
public function markupEmojiJSON() {
$root = dirname(phutil_get_library_root('phabricator'));
$json = Filesystem::readFile(
$root.'/resources/emoji/manifest.json');
return $json;
}
public function markupEmoji(array $matches) {
if (!$this->isFlatText($matches[0])) {
return $matches[0];

View file

@ -0,0 +1,47 @@
<?php
final class PhabricatorEmojiDatasource extends PhabricatorTypeaheadDatasource {
public function getPlaceholderText() {
return pht('Type an emoji name...');
}
public function getBrowseTitle() {
return pht('Browse Emojis');
}
public function getDatasourceApplicationClass() {
return 'PhabricatorMacroApplication';
}
public function loadResults() {
$results = $this->buildResults();
return $this->filterResultsAgainstTokens($results);
}
protected function renderSpecialTokens(array $values) {
return $this->renderTokensFromResults($this->buildResults(), $values);
}
private function buildResults() {
$raw_query = $this->getRawQuery();
$data = id(new PhabricatorEmojiRemarkupRule())->markupEmojiJSON();
$emojis = phutil_json_decode($data);
$results = array();
foreach ($emojis as $shortname => $emoji) {
$display_name = $emoji.' '.$shortname;
$name = str_replace('_', ' ', $shortname);
$result = id(new PhabricatorTypeaheadResult())
->setPHID($shortname)
->setName($name)
->setDisplayname($display_name)
->setAutocomplete($emoji);
$results[$shortname] = $result;
}
return $results;
}
}

View file

@ -59,37 +59,6 @@ final class PhabricatorManiphestApplication extends PhabricatorApplication {
);
}
public function loadStatus(PhabricatorUser $user) {
$status = array();
if (!$user->isLoggedIn()) {
return $status;
}
$limit = self::MAX_STATUS_ITEMS;
$query = id(new ManiphestTaskQuery())
->setViewer($user)
->withStatuses(ManiphestTaskStatus::getOpenStatusConstants())
->withOwners(array($user->getPHID()))
->setLimit($limit);
$count = count($query->execute());
if ($count >= $limit) {
$count_str = pht('%s+ Assigned Task(s)', new PhutilNumber($limit - 1));
} else {
$count_str = pht('%s Assigned Task(s)', new PhutilNumber($count));
}
$type = PhabricatorApplicationStatusView::TYPE_WARNING;
$status[] = id(new PhabricatorApplicationStatusView())
->setType($type)
->setText($count_str)
->setCount($count);
return $status;
}
public function supportsEmailIntegration() {
return true;
}

View file

@ -3,18 +3,12 @@
final class PhabricatorApplicationLaunchView extends AphrontTagView {
private $application;
private $status;
public function setApplication(PhabricatorApplication $application) {
$this->application = $application;
return $this;
}
public function setApplicationStatus(array $status) {
$this->status = $status;
return $this;
}
protected function getTagName() {
return $this->application ? 'a' : 'div';
}
@ -49,65 +43,9 @@ final class PhabricatorApplicationLaunchView extends AphrontTagView {
),
$application->getShortDescription());
$counts = array();
$text = array();
if ($this->status) {
foreach ($this->status as $status) {
$type = $status->getType();
$counts[$type] = idx($counts, $type, 0) + $status->getCount();
if ($status->getCount()) {
$text[] = $status->getText();
}
}
}
$attention = PhabricatorApplicationStatusView::TYPE_NEEDS_ATTENTION;
$warning = PhabricatorApplicationStatusView::TYPE_WARNING;
if (!empty($counts[$attention]) || !empty($counts[$warning])) {
$count = idx($counts, $attention, 0);
$count1 = $count2 = '';
if ($count > 0) {
$count1 = phutil_tag(
'span',
array(
'class' => 'phabricator-application-attention-count',
),
$this->formatStatusItemCount($count));
}
if (!empty($counts[$warning])) {
$count2 = phutil_tag(
'span',
array(
'class' => 'phabricator-application-warning-count',
),
$this->formatStatusItemCount($counts[$warning]));
}
if (nonempty($count1) && nonempty($count2)) {
$numbers = array($count1, ' / ', $count2);
} else {
$numbers = array($count1, $count2);
}
Javelin::initBehavior('phabricator-tooltips');
$content[] = javelin_tag(
'span',
array(
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => implode("\n", $text),
'size' => 300,
'align' => 'E',
),
'class' => 'phabricator-application-launch-attention',
),
$numbers);
}
$classes = array();
$classes[] = 'phabricator-application-launch-icon';
$styles = array();
$classes[] = $application->getIcon();
$classes[] = 'phui-icon-view';
@ -128,13 +66,4 @@ final class PhabricatorApplicationLaunchView extends AphrontTagView {
);
}
private function formatStatusItemCount($count) {
$limit = PhabricatorApplication::MAX_STATUS_ITEMS;
if ($count >= $limit) {
return pht('%s+', new PhutilNumber($limit - 1));
} else {
return pht('%s', new PhutilNumber($count));
}
}
}

View file

@ -1,61 +0,0 @@
<?php
final class PhabricatorApplicationStatusView extends AphrontView {
private $count;
private $text;
private $type;
const TYPE_NEEDS_ATTENTION = 'needs';
const TYPE_INFO = 'info';
const TYPE_OKAY = 'okay';
const TYPE_WARNING = 'warning';
const TYPE_EMPTY = 'empty';
public function setType($type) {
$this->type = $type;
return $this;
}
public function getType() {
return $this->type;
}
public function setText($text) {
$this->text = $text;
return $this;
}
public function getText() {
return $this->text;
}
public function setCount($count) {
$this->count = $count;
return $this;
}
public function getCount() {
return $this->count;
}
public function render() {
$type = $this->type;
if (!$this->count) {
$type = self::TYPE_EMPTY;
}
$classes = array(
'phabricator-application-status',
'phabricator-application-status-type-'.$type,
);
return phutil_tag(
'span',
array(
'class' => implode(' ', $classes),
),
$this->text);
}
}

View file

@ -185,6 +185,7 @@ final class PhabricatorPasteQuery
$paste->getFilePHID(),
$paste->getLanguage(),
'snippet',
'v2',
PhabricatorHash::digestForIndex($paste->getTitle()),
));
}
@ -294,7 +295,8 @@ final class PhabricatorPasteQuery
$snippet_data = phutil_json_decode($caches[$key], true);
$snippet = new PhabricatorPasteSnippet(
phutil_safe_html($snippet_data['content']),
$snippet_data['type']);
$snippet_data['type'],
$snippet_data['contentLineCount']);
$paste->attachSnippet($snippet);
$have_cache[$paste->getPHID()] = true;
} else {
@ -326,6 +328,7 @@ final class PhabricatorPasteQuery
$snippet_data = array(
'content' => (string)$snippet->getContent(),
'type' => (string)$snippet->getType(),
'contentLineCount' => $snippet->getContentLineCount(),
);
$write_data[$this->getSnippetCacheKey($paste)] = phutil_json_encode(
$snippet_data);
@ -358,7 +361,8 @@ final class PhabricatorPasteQuery
}
$lines = phutil_split_lines($snippet);
if (count($lines) > 5) {
$line_count = count($lines);
if ($line_count > 5) {
$snippet_type = PhabricatorPasteSnippet::FIRST_LINES;
$snippet = implode('', array_slice($lines, 0, 5));
}
@ -368,7 +372,8 @@ final class PhabricatorPasteQuery
$snippet,
$paste->getTitle(),
$paste->getLanguage()),
$snippet_type);
$snippet_type,
$line_count);
}
private function highlightSource($source, $title, $language) {

View file

@ -166,7 +166,7 @@ final class PhabricatorPasteSearchEngine
$preview);
$created = phabricator_datetime($paste->getDateCreated(), $viewer);
$line_count = count($lines);
$line_count = $paste->getSnippet()->getContentLineCount();
$line_count = pht(
'%s Line(s)',
new PhutilNumber($line_count));

View file

@ -8,10 +8,12 @@ final class PhabricatorPasteSnippet extends Phobject {
private $content;
private $type;
private $contentLineCount;
public function __construct($content, $type) {
public function __construct($content, $type, $content_line_count) {
$this->content = $content;
$this->type = $type;
$this->contentLineCount = $content_line_count;
}
public function getContent() {
@ -21,4 +23,8 @@ final class PhabricatorPasteSnippet extends Phobject {
public function getType() {
return $this->type;
}
public function getContentLineCount() {
return $this->contentLineCount;
}
}

View file

@ -92,44 +92,6 @@ final class PhabricatorPeopleApplication extends PhabricatorApplication {
);
}
public function loadStatus(PhabricatorUser $user) {
if (!$user->getIsAdmin()) {
return array();
}
$limit = self::MAX_STATUS_ITEMS;
$need_approval = id(new PhabricatorPeopleQuery())
->setViewer($user)
->withIsApproved(false)
->withIsDisabled(false)
->setLimit($limit)
->execute();
if (!$need_approval) {
return array();
}
$status = array();
$count = count($need_approval);
if ($count >= $limit) {
$count_str = pht(
'%s+ User(s) Need Approval',
new PhutilNumber($limit - 1));
} else {
$count_str = pht(
'%s User(s) Need Approval',
new PhutilNumber($count));
}
$type = PhabricatorApplicationStatusView::TYPE_NEEDS_ATTENTION;
$status[] = id(new PhabricatorApplicationStatusView())
->setType($type)
->setText($count_str)
->setCount($count);
return $status;
}
public function getApplicationSearchDocumentTypes() {
return array(
PhabricatorPeopleUserPHIDType::TYPECONST,

View file

@ -46,27 +46,4 @@ final class PhabricatorPhrequentApplication extends PhabricatorApplication {
);
}
public function loadStatus(PhabricatorUser $user) {
$status = array();
$limit = self::MAX_STATUS_ITEMS;
// Show number of objects that are currently
// being tracked for a user.
$count = PhrequentUserTimeQuery::getUserTotalObjectsTracked($user, $limit);
if ($count >= $limit) {
$count_str = pht('%s+ Object(s) Tracked', new PhutilNumber($limit - 1));
} else {
$count_str = pht('%s Object(s) Tracked', new PhutilNumber($count));
}
$type = PhabricatorApplicationStatusView::TYPE_NEEDS_ATTENTION;
$status[] = id(new PhabricatorApplicationStatusView())
->setType($type)
->setText($count_str)
->setCount($count);
return $status;
}
}

View file

@ -41,6 +41,7 @@ final class PhabricatorRepositoryCommit
private $repository = self::ATTACHABLE;
private $customFields = self::ATTACHABLE;
private $drafts = array();
private $auditAuthorityPHIDs = array();
public function attachRepository(PhabricatorRepository $repository) {
$this->repository = $repository;
@ -180,30 +181,85 @@ final class PhabricatorRepositoryCommit
return $this->assertAttached($this->audits);
}
public function getAuthorityAudits(
PhabricatorUser $user,
array $authority_phids) {
public function loadAndAttachAuditAuthority(
PhabricatorUser $viewer,
$actor_phid = null) {
$authority = array_fill_keys($authority_phids, true);
$audits = $this->getAudits();
$authority_audits = array();
foreach ($audits as $audit) {
$has_authority = !empty($authority[$audit->getAuditorPHID()]);
if ($has_authority) {
$commit_author = $this->getAuthorPHID();
if ($actor_phid === null) {
$actor_phid = $viewer->getPHID();
}
// You don't have authority over package and project audits on your
// own commits.
// TODO: This method is a little weird and sketchy, but worlds better than
// what came before it. Eventually, this should probably live in a Query
// class.
$auditor_is_user = ($audit->getAuditorPHID() == $user->getPHID());
$user_is_author = ($commit_author == $user->getPHID());
// Figure out which requests the actor has authority over: these are user
// requests where they are the auditor, and packages and projects they are
// a member of.
if ($auditor_is_user || !$user_is_author) {
$authority_audits[$audit->getID()] = $audit;
if (!$actor_phid) {
$attach_key = $viewer->getCacheFragment();
$phids = array();
} else {
$attach_key = $actor_phid;
// At least currently, when modifying your own commits, you act only on
// behalf of yourself, not your packages/projects -- the idea being that
// you can't accept your own commits. This may change or depend on
// config.
$actor_is_author = ($actor_phid == $this->getAuthorPHID());
if ($actor_is_author) {
$phids = array($actor_phid);
} else {
$phids = array();
$phids[$actor_phid] = true;
$owned_packages = id(new PhabricatorOwnersPackageQuery())
->setViewer($viewer)
->withAuthorityPHIDs(array($actor_phid))
->execute();
foreach ($owned_packages as $package) {
$phids[$package->getPHID()] = true;
}
$projects = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->withMemberPHIDs(array($actor_phid))
->execute();
foreach ($projects as $project) {
$phids[$project->getPHID()] = true;
}
$phids = array_keys($phids);
}
}
return $authority_audits;
$this->auditAuthorityPHIDs[$attach_key] = array_fuse($phids);
return $this;
}
public function hasAuditAuthority(
PhabricatorUser $viewer,
PhabricatorRepositoryAuditRequest $audit,
$actor_phid = null) {
if ($actor_phid === null) {
$actor_phid = $viewer->getPHID();
}
if (!$actor_phid) {
$attach_key = $viewer->getCacheFragment();
} else {
$attach_key = $actor_phid;
}
$map = $this->assertAttachedKey($this->auditAuthorityPHIDs, $attach_key);
if (!$actor_phid) {
return false;
}
return isset($map[$audit->getAuditorPHID()]);
}
public function getAuditorPHIDsForEdit() {
@ -271,8 +327,17 @@ final class PhabricatorRepositoryCommit
}
}
$current_status = $this->getAuditStatus();
$status_verify = PhabricatorAuditCommitStatusConstants::NEEDS_VERIFICATION;
if ($any_concern) {
$status = PhabricatorAuditCommitStatusConstants::CONCERN_RAISED;
if ($current_status == $status_verify) {
// If the change is in "Needs Verification", we keep it there as
// long as any auditors still have concerns.
$status = $status_verify;
} else {
$status = PhabricatorAuditCommitStatusConstants::CONCERN_RAISED;
}
} else if ($any_accept) {
if ($any_need) {
$status = PhabricatorAuditCommitStatusConstants::PARTIALLY_AUDITED;

View file

@ -497,10 +497,34 @@ abstract class PhabricatorProfileMenuEngine extends Phobject {
$viewer = $this->getViewer();
$object = $this->getProfileObject();
PhabricatorPolicyFilter::requireCapability(
$viewer,
$object,
PhabricatorPolicyCapability::CAN_EDIT);
// If you're reordering global items, you need to be able to edit the
// object the menu appears on. If you're reordering custom items, you only
// need to be able to edit the custom object. Currently, the custom object
// is always the viewing user's own user object.
$custom_phid = $this->getCustomPHID();
if (!$custom_phid) {
PhabricatorPolicyFilter::requireCapability(
$viewer,
$object,
PhabricatorPolicyCapability::CAN_EDIT);
} else {
$policy_object = id(new PhabricatorObjectQuery())
->setViewer($viewer)
->withPHIDs(array($custom_phid))
->executeOne();
if (!$policy_object) {
throw new Exception(
pht(
'Failed to load custom PHID "%s"!',
$custom_phid));
}
PhabricatorPolicyFilter::requireCapability(
$viewer,
$policy_object,
PhabricatorPolicyCapability::CAN_EDIT);
}
$controller = $this->getController();
$request = $controller->getRequest();

View file

@ -0,0 +1,47 @@
<?php
final class MacroEmojiExample extends PhabricatorUIExample {
public function getName() {
return pht('Emoji Support');
}
public function getDescription() {
return pht('Shiny happy people holding hands');
}
public function renderExample() {
$raw = id(new PhabricatorEmojiRemarkupRule())
->markupEmojiJSON();
$json = phutil_json_decode($raw);
$content = array();
foreach ($json as $shortname => $hex) {
$display_name = ' '.$hex.' '.$shortname;
$content[] = phutil_tag(
'div',
array(
'class' => 'ms grouped',
'style' => 'width: 240px; height: 24px; float: left;',
),
$display_name);
}
$wrap = id(new PHUIObjectBoxView())
->setHeaderText(pht('Emojis'))
->addClass('grouped')
->appendChild($content);
return phutil_tag(
'div',
array(),
array(
$wrap,
));
}
}

View file

@ -1,71 +1,125 @@
@title Audit User Guide
@group userguide
Guide to the Audit (post-push code review) tool and workflow.
Guide to using Phabricator to audit published commits.
= Overview =
Phabricator supports two code review workflows, "review" (pre-push) and
"audit" (post-push). To understand the differences between the two, see
Overview
========
Phabricator supports two code review workflows, "review" (pre-publish) and
"audit" (post-publish). To understand the differences between the two, see
@{article:User Guide: Review vs Audit}.
This document summarizes the post-push "audit" workflow implemented by the
creatively-named //Audit// tool.
= How Audit Works =
How Audit Works
===============
Using auditing allows you to push and deploy code without waiting for code
review, while still doing code review eventually. The Audit tool primarily keeps
track of two things:
The audit workflow occurs after changes have been published. It provides ways
to track, discuss, and resolve issues with commits that are discovered after
they go through whatever review process you have in place (if you have one).
Two examples of how you might use audit are:
**Fix Issues**: If a problem is discovered after a change has already been
published, users can find the commit which introduced the problem and raise a
concern on it. This notifies the author of the commit and prompts them to
remedy the issue.
**Watch Changes**: In some cases, you may want to passively look over changes
that satisfy some criteria as they are published. For example, you may want to
review all Javascript changes at the end of the week to keep an eye on things,
or make sure that code which impacts a subsystem is looked at by someone on
that team, eventually.
Developers may also want other developers to take a second look at things if
they realize they aren't sure about something after a change has been published,
or just want to provide a heads-up.
You can configure Herald rules and Owners packages to automatically trigger
audits of commits that satisfy particular criteria.
Audit States and Actions
========================
The audit workflow primarily keeps track of two things:
- **Commits** and their audit state (like "Not Audited", "Approved", or
"Concern Raised").
- **Audit Requests** which ask a user (or some other entity) to audit a
commit. These can be triggered in a number of ways (see below).
- **Audit Requests** which ask a user (or some other entity, like a project
or package) to audit a commit. These can be triggered in a number of ways
(see below).
In the Audit tool's home screen and on the homepage you can see commits and
requests that require your action:
Users interact with commits by leaving comments and applying actions, like
accepting the changes or raising a concern. These actions change the state of
their own audit and the overall audit state of the commit. Here's an example of
a typical audit workflow:
- **Required Audits** are open audit requests that require you, a project
you are a member of, or a package you own to audit a commit. An audit
request is closed when you approve the associated commit.
- **Problem Commits** are commits you authored which someone has raised a
concern about in audit. Problem commits go away when you satisfy all the
auditors and get them to "Approve" the commit.
- Alice publishes a commit containing some Javascript.
- This triggers an audit request to Bailey, the Javascript technical
lead on the project (see below for a description of trigger mechanisms).
- Later, Bailey logs into Phabrictor and sees the audit request. She ignores
it for the moment, since it isn't blocking anything. At the end of the
week she looks through her open requests to see what the team has been
up to.
- Bailey notices a few minor problems with Alice's commit. She leaves
comments describing improvements and uses "Raise Concern" to send the
commit back into Alice's queue.
- Later, Alice logs into Phabricator and sees that Bailey has raised a
concern (usually, Alice will also get an email). She resolves the issue
somehow, maybe by making a followup commit with fixes.
- After the issues have been dealt with, she uses "Request Verification" to
return the change to Bailey so Bailey can verify that the concerns have
been addressed.
- Bailey uses "Accept Commit" to close the audit.
For example:
In {nav Diffusion > Browse Commits}, you can review commits and query for
commits with certain audit states. The default "Active Audits" view shows
all of the commits which are relevant to you given their audit state, divided
into buckets:
- Evan creates commit `abcdef1234` and pushes it to the remote.
- This triggers an audit request to Bob through some mechanism (see below for
a description of trigger mechanisms).
- Later, Bob logs into Phabricator and sees the audit request on his homepage.
- Bob clicks through and examines the commit. He notices a problem, so he
selects "Raise Concern" and describes the issue in a comment.
- Evan receives an email that Bob has raised a concern about his commit. He
opts not to deal with it immediately.
- Later, Evan logs into Phabricator and sees the commit on his homepage
under "Problem Commits".
- Evan resolves the issue somehow (e.g., by discussing it with Bob, or fixing
it in another commit).
- Now satisfied, Bob "Accepts" the original commit.
- This causes the request to disappear from Bob's queue, and the commit to
disappear from Evan's queue.
- **Needs Attention**: These are commits which you authored that another
user has raised a concern about: for example, maybe they believe they have
found a bug or some other problem. You should address the concerns.
- **Needs Verification**: These are commits which someone else authored
that you previously raised a concern about. The author has indicated that
they believe the concern has been addressed. You should verify that the
remedy is satisfactory and accept the change, or raise a further concern.
- **Ready to Audit**: These are commits which someone else authored that you
have been asked to audit, either by a user or by a system rule. You should
look over the changes and either accept them or raise concerns.
- **Waiting on Authors**: These are commits which someone else authored that
you previously raised a concern about. The author has not responded to the
concern yet. You may want to follow up.
- **Waiting on Auditors**: These are commits which you authored that someone
else needs to audit.
= Audit Triggers =
You can use the query constraints to filter this list or find commits that
match certain criteria.
Audit Triggers
==============
Audit requests can be triggered in a number of ways:
- You can add auditors explicitly from the web UI, using either "Edit Commit"
or the "Change Auditors" action. You might do this if you realize you are
not sure about something that you recently published and want a second
opinion.
- If you put `Auditors: username1, username2` in your commit message, it will
trigger an audit request to those users when you push it to a tracked
branch.
- You can create rules in Herald that trigger audits based on properties
of the commit -- like the files it touches, the text of the change, the
author, etc.
- You can create an audit request for yourself by commenting on any commit.
- You can create an Owners package and select "Enable Auditing" (this is an
advanced feature which is only likely to be useful for very large teams).
- You can create an Owners package and enable automatic auditing for the
package.
= Audits in Small Teams =
Audits in Small Teams
=====================
If you have a small team and don't need complicated trigger rules, you can set
up a simple audit workflow like this:
@ -84,7 +138,9 @@ commit should have //someone// look at it".
Once your team gets bigger, you can refine this ruleset so that developers see
only changes that are relevant to them.
= Audit Tips =
Audit Tips
==========
- When viewing a commit, audit requests you are responsible for are
highlighted. You are responsible for a request if it's a user request
@ -99,6 +155,8 @@ only changes that are relevant to them.
you submit a comment at the bottom of the page.
- Press "?" to view keyboard shortcuts.
= Next Steps =
Next Steps
==========
- Learn more about Herald at @{article:Herald User Guide}.

View file

@ -1,30 +1,45 @@
@title User Guide: Review vs Audit
@group userguide
Discusses the differences between Review and Audit workflows.
Discusses the differences between "review" and "audit" workflows.
= Overview =
Overview
========
Phabricator supports two similar but separate code review workflows:
Phabricator supports two similar but separate code review workflows: "review"
and "audit".
- **Differential** is used for pre-push code review, called "reviews"
elsewhere in the documentation. You can learn more in
@{article:Differential User Guide}.
- **Audit** is used for post-push code reviews, called "audits" elsewhere in
the documentation. You can learn more in @{article:Audit User Guide}.
Review occurs in **Differential**, before changes are published. You can learn
more in @{article:Differential User Guide}.
(By "pre-push", this document means review which blocks deployment of changes,
while "post-push" means review which happens after changes are deployed or
en route to deployment.)
Audit occurs in **Diffusion**, after changes are published. You can learn more
in @{article:Audit User Guide}.
Both are lightweight, asynchronous web-based workflows where reviewers/auditors
inspect code independently, from their own machines -- not synchronous review
sessions where authors and reviewers meet in person to discuss changes.
When this documentation discusses "unpublished changes", it refers to changes
which are still subject to being reworked in response to feedback. In many
workflows, these changes will only exist locally on the developer's machine,
but some workflows push tentative or temporary changes into remotes. The step
that "publishes" changes might be either pushing or merging them, depending on
your workflow.
= Advantages of Review =
Both the audit and review workflows are lightweight, asynchronous web-based
workflows where reviewers or auditors inspect code independently, from their
own machines -- not synchronous review sessions where authors and reviewers
meet in person to discuss changes.
Pre-push review is significantly more powerful than post-push auditing. You
gain these advantages by requiring review //before// changes may be pushed:
Broadly, review is normally a //blocking// workflow: in review workflows,
authors usually can not publish changes until review completes and reviewers
are satisfied.
In contrast, audit is normally a //nonblocking// workflow: in audit workflows,
changes usually move forward by default.
Advantages of Review
====================
Pre-publish review is significantly more powerful than post-publish auditing.
You gain these advantages by requiring review //before// changes may be
published:
- Authors have a strong incentive to craft small, well-formed changes that
will be readily understood, to explain them adequately, and to provide
@ -32,11 +47,12 @@ gain these advantages by requiring review //before// changes may be pushed:
- Reviewers have a real opportunity to make significant suggestions about
architecture or approach in review. These suggestions are less attractive
to adopt from audit, and may be much more difficult to adopt if significant
time has passed between push and audit.
time has passed between publish and audit.
- Authors have a strong incentive to fix problems and respond to feedback
received during review, because it blocks them. Authors have a much weaker
incentive to address problems raised during audit.
- Authors can ask reviewers to apply and verify fixes before they are pushed.
received during review because it blocks them. Authors have a much weaker
incentive to promptly address problems raised during audit.
- Authors can ask reviewers to apply and verify fixes before they are
published.
- Authors can easily pursue feedback early, and get course corrections on
approach or direction.
- Reviewers are better prepared to support a given change once it is in
@ -54,7 +70,7 @@ a blocking step into the process and generally wastes developer time that could
be better spent developing. This is less true than it appears, because the costs
are low and pay for themselves in other ways:
- Differential is fast and provides a very lightweight process for submitting
- Differential is fast and provides a lightweight process for submitting
code for review and for performing review.
- Authors are free to pursue other changes while code is being reviewed. With
appropriate change management (like local branching in Git) they can even
@ -87,13 +103,15 @@ are low and pay for themselves in other ways:
- With `arc patch`, it is roughly as easy to pull a change out of Differential
as it is to pull it out of the remote.
= Advantages of Audit =
Advantages of Audit
===================
Post-push review is significantly better than nothing. If you are unpersuaded
Post-publish audit is a less powerful workflow than pre-publish review, but can
supplement review and is better than nothing on its own. If you are unpersuaded
by the arguments above (or work on a team that is unswayed), audits provide
some of the benefits of review with less friction:
- Audits are driven entirely by Phabricator, users do not need to install
- Audits are driven entirely by Phabricator: users do not need to install
`arc`.
- Audits require little adjustment to existing workflows and little training.
- Audits are completely nonblocking, and send fewer notifications than review.
@ -101,7 +119,8 @@ some of the benefits of review with less friction:
on lower-importance changes or raise issues that are discovered after
review.
= Recommendations =
Recommendations
===============
Here are super biased recommendations from developers of code review software:
@ -117,7 +136,8 @@ Here are super biased recommendations from developers of code review software:
- If you aren't interested in review, just do audits. You can always
change your mind later. But consider review! It's really good, we promise!
= Next Steps =
Next Steps
==========
- Learn more about reviews in @{article:Differential User Guide}; or
- learn more about audits in @{article:Audit User Guide}.

View file

@ -55,6 +55,7 @@ final class PhabricatorRemarkupControl extends AphrontFormTextAreaControl {
$root_id = celerity_generate_unique_node_id();
$user_datasource = new PhabricatorPeopleDatasource();
$emoji_datasource = new PhabricatorEmojiDatasource();
$proj_datasource = id(new PhabricatorProjectDatasource())
->setParameters(
array(
@ -91,6 +92,12 @@ final class PhabricatorRemarkupControl extends AphrontFormTextAreaControl {
'headerText' => pht('Find Project:'),
'hintText' => $proj_datasource->getPlaceholderText(),
),
58 => array( // ":"
'datasourceURI' => $emoji_datasource->getDatasourceURI(),
'headerIcon' => 'fa-smile-o',
'headerText' => pht('Find Emoji:'),
'hintText' => $emoji_datasource->getPlaceholderText(),
),
),
));
Javelin::initBehavior('phabricator-tooltips', array());

View file

@ -204,7 +204,14 @@ JX.install('WorkboardBoard', {
if (!this._templates[phid]) {
for (var add_phid in response.columnMaps) {
this.getColumn(add_phid).newCard(phid);
var target_column = this.getColumn(add_phid);
if (!target_column) {
// If the column isn't visible, don't try to add a card to it.
continue;
}
target_column.newCard(phid);
}
}

View file

@ -118,7 +118,6 @@ JX.install('PHUIXAutocomplete', {
case '|': // Might be a table cell.
case '>': // Might be a blockquote.
case '!': // Might be a blockquote attribution line.
case ':': // Might be a "NOTE:".
// We'll let these autocomplete.
break;
default: