mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-25 08:12:40 +01:00
(stable) Promote 2016 Week 7
This commit is contained in:
commit
70c679110e
113 changed files with 4153 additions and 1068 deletions
BIN
resources/builtin/image-526x526.png
Normal file
BIN
resources/builtin/image-526x526.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.4 KiB |
|
@ -7,18 +7,18 @@
|
|||
*/
|
||||
return array(
|
||||
'names' => array(
|
||||
'core.pkg.css' => 'e33b14a4',
|
||||
'core.pkg.js' => 'ef5e33db',
|
||||
'core.pkg.css' => 'b59766ad',
|
||||
'core.pkg.js' => 'd7daa6d8',
|
||||
'darkconsole.pkg.js' => 'e7393ebb',
|
||||
'differential.pkg.css' => '2de124c9',
|
||||
'differential.pkg.js' => '5c2ba922',
|
||||
'differential.pkg.js' => 'd0cd0df6',
|
||||
'diffusion.pkg.css' => 'f45955ed',
|
||||
'diffusion.pkg.js' => '3a9a8bfa',
|
||||
'maniphest.pkg.css' => '4845691a',
|
||||
'maniphest.pkg.js' => '949a7498',
|
||||
'rsrc/css/aphront/aphront-bars.css' => '231ac33c',
|
||||
'rsrc/css/aphront/dark-console.css' => '6378ef3d',
|
||||
'rsrc/css/aphront/dialog-view.css' => 'be0e3a46',
|
||||
'rsrc/css/aphront/dialog-view.css' => 'b4334e08',
|
||||
'rsrc/css/aphront/lightbox-attachment.css' => '7acac05d',
|
||||
'rsrc/css/aphront/list-filter-view.css' => '5d6f0526',
|
||||
'rsrc/css/aphront/multi-column.css' => 'fd18389d',
|
||||
|
@ -36,7 +36,7 @@ return array(
|
|||
'rsrc/css/application/base/notification-menu.css' => 'f31c0bde',
|
||||
'rsrc/css/application/base/phabricator-application-launch-view.css' => '95351601',
|
||||
'rsrc/css/application/base/phui-theme.css' => 'ab7b848c',
|
||||
'rsrc/css/application/base/standard-page-view.css' => 'c4467133',
|
||||
'rsrc/css/application/base/standard-page-view.css' => 'e709f6d0',
|
||||
'rsrc/css/application/chatlog/chatlog.css' => 'd295b020',
|
||||
'rsrc/css/application/conduit/conduit-api.css' => '7bc725c4',
|
||||
'rsrc/css/application/config/config-options.css' => '0ede4c9b',
|
||||
|
@ -93,7 +93,7 @@ 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' => '7b0df4da',
|
||||
'rsrc/css/application/project/project-card-view.css' => '9c3631e5',
|
||||
'rsrc/css/application/project/project-card-view.css' => '9418c97d',
|
||||
'rsrc/css/application/project/project-view.css' => '4693497c',
|
||||
'rsrc/css/application/releeph/releeph-core.css' => '9b3c5733',
|
||||
'rsrc/css/application/releeph/releeph-preview-branch.css' => 'b7a6f4a5',
|
||||
|
@ -106,7 +106,7 @@ return array(
|
|||
'rsrc/css/core/core.css' => '5b3563c8',
|
||||
'rsrc/css/core/remarkup.css' => 'e1c8b32f',
|
||||
'rsrc/css/core/syntax.css' => '9fd11da8',
|
||||
'rsrc/css/core/z-index.css' => '5c7025bf',
|
||||
'rsrc/css/core/z-index.css' => '5b6fcf3f',
|
||||
'rsrc/css/diviner/diviner-shared.css' => 'aa3656aa',
|
||||
'rsrc/css/font/font-aleo.css' => '8bdb2835',
|
||||
'rsrc/css/font/font-awesome.css' => 'c43323c5',
|
||||
|
@ -126,7 +126,7 @@ return array(
|
|||
'rsrc/css/phui/phui-box.css' => '6e8ac7fd',
|
||||
'rsrc/css/phui/phui-button.css' => 'd6ac72db',
|
||||
'rsrc/css/phui/phui-chart.css' => '6bf6f78e',
|
||||
'rsrc/css/phui/phui-crumbs-view.css' => '414406b5',
|
||||
'rsrc/css/phui/phui-crumbs-view.css' => '79d536e5',
|
||||
'rsrc/css/phui/phui-document-pro.css' => '8799acf7',
|
||||
'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf',
|
||||
'rsrc/css/phui/phui-document.css' => '9c71d2bf',
|
||||
|
@ -143,20 +143,21 @@ return array(
|
|||
'rsrc/css/phui/phui-info-view.css' => '6d7c3509',
|
||||
'rsrc/css/phui/phui-list.css' => '9da2aa00',
|
||||
'rsrc/css/phui/phui-object-box.css' => '407eaf5a',
|
||||
'rsrc/css/phui/phui-object-item-list-view.css' => 'fe594a65',
|
||||
'rsrc/css/phui/phui-object-item-list-view.css' => 'be31c3a7',
|
||||
'rsrc/css/phui/phui-pager.css' => 'bea33d23',
|
||||
'rsrc/css/phui/phui-pinboard-view.css' => '2495140e',
|
||||
'rsrc/css/phui/phui-profile-menu.css' => 'ab4fcf5f',
|
||||
'rsrc/css/phui/phui-profile-menu.css' => 'f709256c',
|
||||
'rsrc/css/phui/phui-property-list-view.css' => '27b2849e',
|
||||
'rsrc/css/phui/phui-remarkup-preview.css' => '1a8f2591',
|
||||
'rsrc/css/phui/phui-segment-bar-view.css' => '46342871',
|
||||
'rsrc/css/phui/phui-spacing.css' => '042804d6',
|
||||
'rsrc/css/phui/phui-status.css' => '888cedb8',
|
||||
'rsrc/css/phui/phui-tag-view.css' => '9d5d4400',
|
||||
'rsrc/css/phui/phui-timeline-view.css' => '2efceff8',
|
||||
'rsrc/css/phui/phui-two-column-view.css' => 'c75bfc5b',
|
||||
'rsrc/css/phui/workboards/phui-workboard.css' => 'b07a5524',
|
||||
'rsrc/css/phui/workboards/phui-workcard.css' => 'b4322ca7',
|
||||
'rsrc/css/phui/workboards/phui-workpanel.css' => 'e1bd8d04',
|
||||
'rsrc/css/phui/workboards/phui-workboard.css' => 'e9e56029',
|
||||
'rsrc/css/phui/workboards/phui-workcard.css' => '3646fb96',
|
||||
'rsrc/css/phui/workboards/phui-workpanel.css' => 'a78c0661',
|
||||
'rsrc/css/sprite-login.css' => '60e8560e',
|
||||
'rsrc/css/sprite-menu.css' => '9dd65b92',
|
||||
'rsrc/css/sprite-tokens.css' => '4f399012',
|
||||
|
@ -414,7 +415,11 @@ 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/behavior-project-boards.js' => '48470f95',
|
||||
'rsrc/js/application/projects/WorkboardBoard.js' => '52291776',
|
||||
'rsrc/js/application/projects/WorkboardCard.js' => 'c587b80f',
|
||||
'rsrc/js/application/projects/WorkboardColumn.js' => 'f05d6e5d',
|
||||
'rsrc/js/application/projects/WorkboardController.js' => '55baf5ed',
|
||||
'rsrc/js/application/projects/behavior-project-boards.js' => '14a1faae',
|
||||
'rsrc/js/application/projects/behavior-project-create.js' => '065227cc',
|
||||
'rsrc/js/application/projects/behavior-reorder-columns.js' => 'e1d25dfb',
|
||||
'rsrc/js/application/releeph/releeph-preview-branch.js' => 'b2b4fbaf',
|
||||
|
@ -446,9 +451,9 @@ return array(
|
|||
'rsrc/js/application/uiexample/gesture-example.js' => '558829c2',
|
||||
'rsrc/js/application/uiexample/notification-example.js' => '8ce821c5',
|
||||
'rsrc/js/core/Busy.js' => '59a7976a',
|
||||
'rsrc/js/core/DragAndDropFileUpload.js' => 'ad10aeac',
|
||||
'rsrc/js/core/DraggableList.js' => '8905523d',
|
||||
'rsrc/js/core/FileUpload.js' => '477359c8',
|
||||
'rsrc/js/core/DragAndDropFileUpload.js' => '81f182b5',
|
||||
'rsrc/js/core/DraggableList.js' => '5a13c79f',
|
||||
'rsrc/js/core/FileUpload.js' => '680ea2c8',
|
||||
'rsrc/js/core/Hovercard.js' => '1bd28176',
|
||||
'rsrc/js/core/KeyboardShortcut.js' => '1ae869f2',
|
||||
'rsrc/js/core/KeyboardShortcutManager.js' => 'c1700f6f',
|
||||
|
@ -506,14 +511,14 @@ return array(
|
|||
'rsrc/js/phuix/PHUIXActionView.js' => '8cf6d262',
|
||||
'rsrc/js/phuix/PHUIXAutocomplete.js' => '9196fb06',
|
||||
'rsrc/js/phuix/PHUIXDropdownMenu.js' => 'bd4c8dca',
|
||||
'rsrc/js/phuix/PHUIXFormControl.js' => '8fba1997',
|
||||
'rsrc/js/phuix/PHUIXFormControl.js' => 'a7763e11',
|
||||
'rsrc/js/phuix/PHUIXIconView.js' => 'bff6884b',
|
||||
),
|
||||
'symbols' => array(
|
||||
'almanac-css' => 'dbb9b3af',
|
||||
'aphront-bars' => '231ac33c',
|
||||
'aphront-dark-console-css' => '6378ef3d',
|
||||
'aphront-dialog-view-css' => 'be0e3a46',
|
||||
'aphront-dialog-view-css' => 'b4334e08',
|
||||
'aphront-list-filter-view-css' => '5d6f0526',
|
||||
'aphront-multi-column-view-css' => 'fd18389d',
|
||||
'aphront-panel-view-css' => '8427b78d',
|
||||
|
@ -654,7 +659,7 @@ return array(
|
|||
'javelin-behavior-phui-profile-menu' => '12884df9',
|
||||
'javelin-behavior-policy-control' => 'd0c516d5',
|
||||
'javelin-behavior-policy-rule-editor' => '5e9f347c',
|
||||
'javelin-behavior-project-boards' => '48470f95',
|
||||
'javelin-behavior-project-boards' => '14a1faae',
|
||||
'javelin-behavior-project-create' => '065227cc',
|
||||
'javelin-behavior-quicksand-blacklist' => '7927a7d3',
|
||||
'javelin-behavior-recurring-edit' => '5f1c4d5f',
|
||||
|
@ -721,6 +726,10 @@ return array(
|
|||
'javelin-view-renderer' => '6c2b09a2',
|
||||
'javelin-view-visitor' => 'efe49472',
|
||||
'javelin-websocket' => 'e292eaf4',
|
||||
'javelin-workboard-board' => '52291776',
|
||||
'javelin-workboard-card' => 'c587b80f',
|
||||
'javelin-workboard-column' => 'f05d6e5d',
|
||||
'javelin-workboard-controller' => '55baf5ed',
|
||||
'javelin-workflow' => '5b2e3e2b',
|
||||
'lightbox-attachment-css' => '7acac05d',
|
||||
'maniphest-batch-editor' => 'b0f0b6d5',
|
||||
|
@ -741,11 +750,11 @@ return array(
|
|||
'phabricator-core-css' => '5b3563c8',
|
||||
'phabricator-countdown-css' => 'e7544472',
|
||||
'phabricator-dashboard-css' => 'eb458607',
|
||||
'phabricator-drag-and-drop-file-upload' => 'ad10aeac',
|
||||
'phabricator-draggable-list' => '8905523d',
|
||||
'phabricator-drag-and-drop-file-upload' => '81f182b5',
|
||||
'phabricator-draggable-list' => '5a13c79f',
|
||||
'phabricator-fatal-config-template-css' => '8e6c6fcd',
|
||||
'phabricator-feed-css' => 'ecd4ec57',
|
||||
'phabricator-file-upload' => '477359c8',
|
||||
'phabricator-file-upload' => '680ea2c8',
|
||||
'phabricator-filetree-view-css' => 'fccf9f82',
|
||||
'phabricator-flag-css' => '5337623f',
|
||||
'phabricator-keyboard-shortcut' => '1ae869f2',
|
||||
|
@ -764,7 +773,7 @@ return array(
|
|||
'phabricator-side-menu-view-css' => '3a3d9f41',
|
||||
'phabricator-slowvote-css' => 'da0afb1b',
|
||||
'phabricator-source-code-view-css' => 'cbeef983',
|
||||
'phabricator-standard-page-view' => 'c4467133',
|
||||
'phabricator-standard-page-view' => 'e709f6d0',
|
||||
'phabricator-textareautils' => '9e54692d',
|
||||
'phabricator-title' => 'df5e11d2',
|
||||
'phabricator-tooltip' => '6323f942',
|
||||
|
@ -779,7 +788,7 @@ return array(
|
|||
'phabricator-uiexample-reactor-select' => 'a155550f',
|
||||
'phabricator-uiexample-reactor-sendclass' => '1def2711',
|
||||
'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee',
|
||||
'phabricator-zindex-css' => '5c7025bf',
|
||||
'phabricator-zindex-css' => '5b6fcf3f',
|
||||
'phame-css' => '1dbbacf9',
|
||||
'pholio-css' => '95174bdd',
|
||||
'pholio-edit-css' => '3ad9d1ee',
|
||||
|
@ -799,7 +808,7 @@ return array(
|
|||
'phui-calendar-list-css' => 'c1c7f338',
|
||||
'phui-calendar-month-css' => '476be7e0',
|
||||
'phui-chart-css' => '6bf6f78e',
|
||||
'phui-crumbs-view-css' => '414406b5',
|
||||
'phui-crumbs-view-css' => '79d536e5',
|
||||
'phui-document-summary-view-css' => '9ca48bdf',
|
||||
'phui-document-view-css' => '9c71d2bf',
|
||||
'phui-document-view-pro-css' => '8799acf7',
|
||||
|
@ -819,32 +828,33 @@ return array(
|
|||
'phui-inline-comment-view-css' => '0fdb3667',
|
||||
'phui-list-view-css' => '9da2aa00',
|
||||
'phui-object-box-css' => '407eaf5a',
|
||||
'phui-object-item-list-view-css' => 'fe594a65',
|
||||
'phui-object-item-list-view-css' => 'be31c3a7',
|
||||
'phui-pager-css' => 'bea33d23',
|
||||
'phui-pinboard-view-css' => '2495140e',
|
||||
'phui-profile-menu-css' => 'ab4fcf5f',
|
||||
'phui-profile-menu-css' => 'f709256c',
|
||||
'phui-property-list-view-css' => '27b2849e',
|
||||
'phui-remarkup-preview-css' => '1a8f2591',
|
||||
'phui-segment-bar-view-css' => '46342871',
|
||||
'phui-spacing-css' => '042804d6',
|
||||
'phui-status-list-view-css' => '888cedb8',
|
||||
'phui-tag-view-css' => '9d5d4400',
|
||||
'phui-theme-css' => 'ab7b848c',
|
||||
'phui-timeline-view-css' => '2efceff8',
|
||||
'phui-two-column-view-css' => 'c75bfc5b',
|
||||
'phui-workboard-view-css' => 'b07a5524',
|
||||
'phui-workcard-view-css' => 'b4322ca7',
|
||||
'phui-workpanel-view-css' => 'e1bd8d04',
|
||||
'phui-workboard-view-css' => 'e9e56029',
|
||||
'phui-workcard-view-css' => '3646fb96',
|
||||
'phui-workpanel-view-css' => 'a78c0661',
|
||||
'phuix-action-list-view' => 'b5c256b8',
|
||||
'phuix-action-view' => '8cf6d262',
|
||||
'phuix-autocomplete' => '9196fb06',
|
||||
'phuix-dropdown-menu' => 'bd4c8dca',
|
||||
'phuix-form-control-view' => '8fba1997',
|
||||
'phuix-form-control-view' => 'a7763e11',
|
||||
'phuix-icon-view' => 'bff6884b',
|
||||
'policy-css' => '957ea14c',
|
||||
'policy-edit-css' => '815c66f7',
|
||||
'policy-transaction-detail-css' => '82100a43',
|
||||
'ponder-view-css' => '7b0df4da',
|
||||
'project-card-view-css' => '9c3631e5',
|
||||
'project-card-view-css' => '9418c97d',
|
||||
'project-view-css' => '4693497c',
|
||||
'releeph-core' => '9b3c5733',
|
||||
'releeph-preview-branch' => 'b7a6f4a5',
|
||||
|
@ -956,6 +966,15 @@ return array(
|
|||
'javelin-dom',
|
||||
'javelin-history',
|
||||
),
|
||||
'14a1faae' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
'javelin-util',
|
||||
'javelin-vector',
|
||||
'javelin-stratcom',
|
||||
'javelin-workflow',
|
||||
'javelin-workboard-controller',
|
||||
),
|
||||
'1ad0a787' => array(
|
||||
'javelin-install',
|
||||
'javelin-reactor',
|
||||
|
@ -1133,11 +1152,6 @@ return array(
|
|||
'javelin-dom',
|
||||
'javelin-workflow',
|
||||
),
|
||||
'477359c8' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
'phabricator-notification',
|
||||
),
|
||||
47830651 => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
|
@ -1154,15 +1168,6 @@ return array(
|
|||
'javelin-dom',
|
||||
'javelin-workflow',
|
||||
),
|
||||
'48470f95' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
'javelin-util',
|
||||
'javelin-vector',
|
||||
'javelin-stratcom',
|
||||
'javelin-workflow',
|
||||
'phabricator-draggable-list',
|
||||
),
|
||||
'49b73b36' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
|
@ -1209,6 +1214,15 @@ return array(
|
|||
'javelin-dom',
|
||||
'javelin-reactor-dom',
|
||||
),
|
||||
52291776 => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
'javelin-util',
|
||||
'javelin-stratcom',
|
||||
'javelin-workflow',
|
||||
'phabricator-draggable-list',
|
||||
'javelin-workboard-column',
|
||||
),
|
||||
'5359e785' => array(
|
||||
'javelin-install',
|
||||
'javelin-util',
|
||||
|
@ -1239,6 +1253,16 @@ return array(
|
|||
'javelin-vector',
|
||||
'javelin-dom',
|
||||
),
|
||||
'55baf5ed' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
'javelin-util',
|
||||
'javelin-vector',
|
||||
'javelin-stratcom',
|
||||
'javelin-workflow',
|
||||
'phabricator-drag-and-drop-file-upload',
|
||||
'javelin-workboard-board',
|
||||
),
|
||||
'56a1ca03' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-behavior-device',
|
||||
|
@ -1265,6 +1289,14 @@ return array(
|
|||
'javelin-dom',
|
||||
'javelin-stratcom',
|
||||
),
|
||||
'5a13c79f' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
'javelin-stratcom',
|
||||
'javelin-util',
|
||||
'javelin-vector',
|
||||
'javelin-magical-init',
|
||||
),
|
||||
'5b2e3e2b' => array(
|
||||
'javelin-stratcom',
|
||||
'javelin-request',
|
||||
|
@ -1331,6 +1363,11 @@ return array(
|
|||
'javelin-request',
|
||||
'javelin-workflow',
|
||||
),
|
||||
'680ea2c8' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
'phabricator-notification',
|
||||
),
|
||||
'6882e80a' => array(
|
||||
'javelin-dom',
|
||||
),
|
||||
|
@ -1450,6 +1487,14 @@ return array(
|
|||
'javelin-vector',
|
||||
'javelin-stratcom',
|
||||
),
|
||||
'81f182b5' => array(
|
||||
'javelin-install',
|
||||
'javelin-util',
|
||||
'javelin-request',
|
||||
'javelin-dom',
|
||||
'javelin-uri',
|
||||
'phabricator-file-upload',
|
||||
),
|
||||
'834a1173' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-scrollbar',
|
||||
|
@ -1487,14 +1532,6 @@ return array(
|
|||
'javelin-stratcom',
|
||||
'javelin-dom',
|
||||
),
|
||||
'8905523d' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
'javelin-stratcom',
|
||||
'javelin-util',
|
||||
'javelin-vector',
|
||||
'javelin-magical-init',
|
||||
),
|
||||
'8a41885b' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
|
@ -1525,10 +1562,6 @@ return array(
|
|||
'javelin-stratcom',
|
||||
'javelin-install',
|
||||
),
|
||||
'8fba1997' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
),
|
||||
'901935ef' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
|
@ -1622,6 +1655,13 @@ return array(
|
|||
'javelin-uri',
|
||||
'phabricator-notification',
|
||||
),
|
||||
'a7763e11' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
),
|
||||
'a78c0661' => array(
|
||||
'phui-workcard-view-css',
|
||||
),
|
||||
'a80d0378' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
|
@ -1674,14 +1714,6 @@ return array(
|
|||
'javelin-util',
|
||||
'phabricator-busy',
|
||||
),
|
||||
'ad10aeac' => array(
|
||||
'javelin-install',
|
||||
'javelin-util',
|
||||
'javelin-request',
|
||||
'javelin-dom',
|
||||
'javelin-uri',
|
||||
'phabricator-file-upload',
|
||||
),
|
||||
'b064af76' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
|
@ -1786,6 +1818,9 @@ return array(
|
|||
'javelin-dom',
|
||||
'javelin-vector',
|
||||
),
|
||||
'c587b80f' => array(
|
||||
'javelin-install',
|
||||
),
|
||||
'c72aa091' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
|
@ -1918,9 +1953,6 @@ return array(
|
|||
'javelin-dom',
|
||||
'phabricator-prefab',
|
||||
),
|
||||
'e1bd8d04' => array(
|
||||
'phui-workcard-view-css',
|
||||
),
|
||||
'e1d25dfb' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
|
@ -2023,6 +2055,10 @@ return array(
|
|||
'javelin-workflow',
|
||||
'javelin-json',
|
||||
),
|
||||
'f05d6e5d' => array(
|
||||
'javelin-install',
|
||||
'javelin-workboard-card',
|
||||
),
|
||||
'f411b6ae' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
|
|
5
resources/sql/autopatches/20160206.cover.1.sql
Normal file
5
resources/sql/autopatches/20160206.cover.1.sql
Normal file
|
@ -0,0 +1,5 @@
|
|||
ALTER TABLE {$NAMESPACE}_maniphest.maniphest_task
|
||||
ADD properties LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT};
|
||||
|
||||
UPDATE {$NAMESPACE}_maniphest.maniphest_task
|
||||
SET properties = '{}' WHERE properties = '';
|
2
resources/sql/autopatches/20160208.task.1.sql
Normal file
2
resources/sql/autopatches/20160208.task.1.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_maniphest.maniphest_task
|
||||
DROP projectPHIDs;
|
2
resources/sql/autopatches/20160208.task.2.sql
Normal file
2
resources/sql/autopatches/20160208.task.2.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_maniphest.maniphest_task
|
||||
DROP attached;
|
2
resources/sql/autopatches/20160208.task.3.sql
Normal file
2
resources/sql/autopatches/20160208.task.3.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_maniphest.maniphest_task
|
||||
ADD points DOUBLE;
|
2
resources/sql/autopatches/20160212.proj.1.sql
Normal file
2
resources/sql/autopatches/20160212.proj.1.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_project.project
|
||||
ADD properties LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT};
|
2
resources/sql/autopatches/20160212.proj.2.sql
Normal file
2
resources/sql/autopatches/20160212.proj.2.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
UPDATE {$NAMESPACE}_project.project
|
||||
SET properties = '{}' WHERE properties = '';
|
|
@ -1,29 +1,3 @@
|
|||
<?php
|
||||
|
||||
echo pht('Migrating task dependencies to edges...')."\n";
|
||||
$table = new ManiphestTask();
|
||||
$table->openTransaction();
|
||||
|
||||
foreach (new LiskMigrationIterator($table) as $task) {
|
||||
$id = $task->getID();
|
||||
echo pht('Task %d: ', $id);
|
||||
|
||||
$deps = $task->getAttachedPHIDs(ManiphestTaskPHIDType::TYPECONST);
|
||||
if (!$deps) {
|
||||
echo "-\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
$editor = new PhabricatorEdgeEditor();
|
||||
foreach ($deps as $dep) {
|
||||
$editor->addEdge(
|
||||
$task->getPHID(),
|
||||
ManiphestTaskDependsOnTaskEdgeType::EDGECONST,
|
||||
$dep);
|
||||
}
|
||||
$editor->save();
|
||||
echo pht('OKAY')."\n";
|
||||
}
|
||||
|
||||
$table->saveTransaction();
|
||||
echo pht('Done.')."\n";
|
||||
// From 2013-2016, this migration moved dependent tasks to edges.
|
||||
|
|
|
@ -1,28 +1,3 @@
|
|||
<?php
|
||||
|
||||
echo pht('Migrating task revisions to edges...')."\n";
|
||||
$table = new ManiphestTask();
|
||||
$table->establishConnection('w');
|
||||
|
||||
foreach (new LiskMigrationIterator($table) as $task) {
|
||||
$id = $task->getID();
|
||||
echo pht('Task %d: ', $id);
|
||||
|
||||
$revs = $task->getAttachedPHIDs(DifferentialRevisionPHIDType::TYPECONST);
|
||||
if (!$revs) {
|
||||
echo "-\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
$editor = new PhabricatorEdgeEditor();
|
||||
foreach ($revs as $rev) {
|
||||
$editor->addEdge(
|
||||
$task->getPHID(),
|
||||
ManiphestTaskHasRevisionEdgeType::EDGECONST,
|
||||
$rev);
|
||||
}
|
||||
$editor->save();
|
||||
echo pht('OKAY')."\n";
|
||||
}
|
||||
|
||||
echo pht('Done.')."\n";
|
||||
// From 2013-2016, this migration moved revisions attached to tasks to edges.
|
||||
|
|
|
@ -129,7 +129,7 @@ try {
|
|||
throw new Exception(
|
||||
pht(
|
||||
'Invalid device name ("%s"). There is no device with this name.',
|
||||
$device->getName()));
|
||||
$device_name));
|
||||
}
|
||||
|
||||
// We're authenticated as a device, but we're going to read the user out of
|
||||
|
|
|
@ -241,6 +241,7 @@ phutil_register_library_map(array(
|
|||
'ConduitPHIDParameterType' => 'applications/conduit/parametertype/ConduitPHIDParameterType.php',
|
||||
'ConduitParameterType' => 'applications/conduit/parametertype/ConduitParameterType.php',
|
||||
'ConduitPingConduitAPIMethod' => 'applications/conduit/method/ConduitPingConduitAPIMethod.php',
|
||||
'ConduitPointsParameterType' => 'applications/conduit/parametertype/ConduitPointsParameterType.php',
|
||||
'ConduitProjectListParameterType' => 'applications/conduit/parametertype/ConduitProjectListParameterType.php',
|
||||
'ConduitQueryConduitAPIMethod' => 'applications/conduit/method/ConduitQueryConduitAPIMethod.php',
|
||||
'ConduitResultSearchEngineExtension' => 'applications/conduit/query/ConduitResultSearchEngineExtension.php',
|
||||
|
@ -1299,6 +1300,7 @@ phutil_register_library_map(array(
|
|||
'ManiphestHovercardEngineExtension' => 'applications/maniphest/engineextension/ManiphestHovercardEngineExtension.php',
|
||||
'ManiphestInfoConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestInfoConduitAPIMethod.php',
|
||||
'ManiphestNameIndex' => 'applications/maniphest/storage/ManiphestNameIndex.php',
|
||||
'ManiphestPointsConfigOptionType' => 'applications/maniphest/config/ManiphestPointsConfigOptionType.php',
|
||||
'ManiphestPriorityConfigOptionType' => 'applications/maniphest/config/ManiphestPriorityConfigOptionType.php',
|
||||
'ManiphestPriorityEmailCommand' => 'applications/maniphest/command/ManiphestPriorityEmailCommand.php',
|
||||
'ManiphestProjectNameFulltextEngineExtension' => 'applications/maniphest/engineextension/ManiphestProjectNameFulltextEngineExtension.php',
|
||||
|
@ -1339,6 +1341,7 @@ phutil_register_library_map(array(
|
|||
'ManiphestTaskOpenStatusDatasource' => 'applications/maniphest/typeahead/ManiphestTaskOpenStatusDatasource.php',
|
||||
'ManiphestTaskPHIDResolver' => 'applications/maniphest/httpparametertype/ManiphestTaskPHIDResolver.php',
|
||||
'ManiphestTaskPHIDType' => 'applications/maniphest/phid/ManiphestTaskPHIDType.php',
|
||||
'ManiphestTaskPoints' => 'applications/maniphest/constants/ManiphestTaskPoints.php',
|
||||
'ManiphestTaskPriority' => 'applications/maniphest/constants/ManiphestTaskPriority.php',
|
||||
'ManiphestTaskPriorityDatasource' => 'applications/maniphest/typeahead/ManiphestTaskPriorityDatasource.php',
|
||||
'ManiphestTaskPriorityHeraldAction' => 'applications/maniphest/herald/ManiphestTaskPriorityHeraldAction.php',
|
||||
|
@ -1522,6 +1525,8 @@ phutil_register_library_map(array(
|
|||
'PHUIPropertyListView' => 'view/phui/PHUIPropertyListView.php',
|
||||
'PHUIRemarkupPreviewPanel' => 'view/phui/PHUIRemarkupPreviewPanel.php',
|
||||
'PHUIRemarkupView' => 'infrastructure/markup/view/PHUIRemarkupView.php',
|
||||
'PHUISegmentBarSegmentView' => 'view/phui/PHUISegmentBarSegmentView.php',
|
||||
'PHUISegmentBarView' => 'view/phui/PHUISegmentBarView.php',
|
||||
'PHUISpacesNamespaceContextView' => 'applications/spaces/view/PHUISpacesNamespaceContextView.php',
|
||||
'PHUIStatusItemView' => 'view/phui/PHUIStatusItemView.php',
|
||||
'PHUIStatusListView' => 'view/phui/PHUIStatusListView.php',
|
||||
|
@ -1815,6 +1820,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorBinariesSetupCheck' => 'applications/config/check/PhabricatorBinariesSetupCheck.php',
|
||||
'PhabricatorBitbucketAuthProvider' => 'applications/auth/provider/PhabricatorBitbucketAuthProvider.php',
|
||||
'PhabricatorBoardLayoutEngine' => 'applications/project/engine/PhabricatorBoardLayoutEngine.php',
|
||||
'PhabricatorBoardRenderingEngine' => 'applications/project/engine/PhabricatorBoardRenderingEngine.php',
|
||||
'PhabricatorBoardResponseEngine' => 'applications/project/engine/PhabricatorBoardResponseEngine.php',
|
||||
'PhabricatorBot' => 'infrastructure/daemon/bot/PhabricatorBot.php',
|
||||
'PhabricatorBotChannel' => 'infrastructure/daemon/bot/target/PhabricatorBotChannel.php',
|
||||
'PhabricatorBotDebugLogHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotDebugLogHandler.php',
|
||||
|
@ -2203,6 +2210,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorEditEngineExtension' => 'applications/transactions/engineextension/PhabricatorEditEngineExtension.php',
|
||||
'PhabricatorEditEngineExtensionModule' => 'applications/transactions/engineextension/PhabricatorEditEngineExtensionModule.php',
|
||||
'PhabricatorEditEngineListController' => 'applications/transactions/controller/PhabricatorEditEngineListController.php',
|
||||
'PhabricatorEditEnginePointsCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEnginePointsCommentAction.php',
|
||||
'PhabricatorEditEngineQuery' => 'applications/transactions/query/PhabricatorEditEngineQuery.php',
|
||||
'PhabricatorEditEngineSearchEngine' => 'applications/transactions/query/PhabricatorEditEngineSearchEngine.php',
|
||||
'PhabricatorEditEngineSelectCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineSelectCommentAction.php',
|
||||
|
@ -2810,6 +2818,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorPhurlURLViewController' => 'applications/phurl/controller/PhabricatorPhurlURLViewController.php',
|
||||
'PhabricatorPirateEnglishTranslation' => 'infrastructure/internationalization/translation/PhabricatorPirateEnglishTranslation.php',
|
||||
'PhabricatorPlatformSite' => 'aphront/site/PhabricatorPlatformSite.php',
|
||||
'PhabricatorPointsEditField' => 'applications/transactions/editfield/PhabricatorPointsEditField.php',
|
||||
'PhabricatorPolicies' => 'applications/policy/constants/PhabricatorPolicies.php',
|
||||
'PhabricatorPolicy' => 'applications/policy/storage/PhabricatorPolicy.php',
|
||||
'PhabricatorPolicyApplication' => 'applications/policy/application/PhabricatorPolicyApplication.php',
|
||||
|
@ -2859,6 +2868,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorProjectApplication' => 'applications/project/application/PhabricatorProjectApplication.php',
|
||||
'PhabricatorProjectArchiveController' => 'applications/project/controller/PhabricatorProjectArchiveController.php',
|
||||
'PhabricatorProjectBoardController' => 'applications/project/controller/PhabricatorProjectBoardController.php',
|
||||
'PhabricatorProjectBoardDisableController' => 'applications/project/controller/PhabricatorProjectBoardDisableController.php',
|
||||
'PhabricatorProjectBoardImportController' => 'applications/project/controller/PhabricatorProjectBoardImportController.php',
|
||||
'PhabricatorProjectBoardReorderController' => 'applications/project/controller/PhabricatorProjectBoardReorderController.php',
|
||||
'PhabricatorProjectBoardViewController' => 'applications/project/controller/PhabricatorProjectBoardViewController.php',
|
||||
|
@ -2879,12 +2889,14 @@ phutil_register_library_map(array(
|
|||
'PhabricatorProjectConfiguredCustomField' => 'applications/project/customfield/PhabricatorProjectConfiguredCustomField.php',
|
||||
'PhabricatorProjectController' => 'applications/project/controller/PhabricatorProjectController.php',
|
||||
'PhabricatorProjectCoreTestCase' => 'applications/project/__tests__/PhabricatorProjectCoreTestCase.php',
|
||||
'PhabricatorProjectCoverController' => 'applications/project/controller/PhabricatorProjectCoverController.php',
|
||||
'PhabricatorProjectCustomField' => 'applications/project/customfield/PhabricatorProjectCustomField.php',
|
||||
'PhabricatorProjectCustomFieldNumericIndex' => 'applications/project/storage/PhabricatorProjectCustomFieldNumericIndex.php',
|
||||
'PhabricatorProjectCustomFieldStorage' => 'applications/project/storage/PhabricatorProjectCustomFieldStorage.php',
|
||||
'PhabricatorProjectCustomFieldStringIndex' => 'applications/project/storage/PhabricatorProjectCustomFieldStringIndex.php',
|
||||
'PhabricatorProjectDAO' => 'applications/project/storage/PhabricatorProjectDAO.php',
|
||||
'PhabricatorProjectDatasource' => 'applications/project/typeahead/PhabricatorProjectDatasource.php',
|
||||
'PhabricatorProjectDefaultController' => 'applications/project/controller/PhabricatorProjectDefaultController.php',
|
||||
'PhabricatorProjectDescriptionField' => 'applications/project/customfield/PhabricatorProjectDescriptionField.php',
|
||||
'PhabricatorProjectDetailsProfilePanel' => 'applications/project/profilepanel/PhabricatorProjectDetailsProfilePanel.php',
|
||||
'PhabricatorProjectEditController' => 'applications/project/controller/PhabricatorProjectEditController.php',
|
||||
|
@ -2894,6 +2906,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorProjectHeraldAction' => 'applications/project/herald/PhabricatorProjectHeraldAction.php',
|
||||
'PhabricatorProjectHeraldAdapter' => 'applications/project/herald/PhabricatorProjectHeraldAdapter.php',
|
||||
'PhabricatorProjectHeraldFieldGroup' => 'applications/project/herald/PhabricatorProjectHeraldFieldGroup.php',
|
||||
'PhabricatorProjectHovercardEngineExtension' => 'applications/project/engineextension/PhabricatorProjectHovercardEngineExtension.php',
|
||||
'PhabricatorProjectIconSet' => 'applications/project/icon/PhabricatorProjectIconSet.php',
|
||||
'PhabricatorProjectIconsConfigOptionType' => 'applications/project/config/PhabricatorProjectIconsConfigOptionType.php',
|
||||
'PhabricatorProjectInterface' => 'applications/project/interface/PhabricatorProjectInterface.php',
|
||||
|
@ -2924,6 +2937,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorProjectOrUserFunctionDatasource' => 'applications/project/typeahead/PhabricatorProjectOrUserFunctionDatasource.php',
|
||||
'PhabricatorProjectPHIDResolver' => 'applications/phid/resolver/PhabricatorProjectPHIDResolver.php',
|
||||
'PhabricatorProjectPanelController' => 'applications/project/controller/PhabricatorProjectPanelController.php',
|
||||
'PhabricatorProjectPointsProfilePanel' => 'applications/project/profilepanel/PhabricatorProjectPointsProfilePanel.php',
|
||||
'PhabricatorProjectProfileController' => 'applications/project/controller/PhabricatorProjectProfileController.php',
|
||||
'PhabricatorProjectProfilePanelEngine' => 'applications/project/engine/PhabricatorProjectProfilePanelEngine.php',
|
||||
'PhabricatorProjectProjectHasMemberEdgeType' => 'applications/project/edge/PhabricatorProjectProjectHasMemberEdgeType.php',
|
||||
|
@ -3365,6 +3379,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorUnsubscribedFromObjectEdgeType' => 'applications/transactions/edges/PhabricatorUnsubscribedFromObjectEdgeType.php',
|
||||
'PhabricatorUser' => 'applications/people/storage/PhabricatorUser.php',
|
||||
'PhabricatorUserBlurbField' => 'applications/people/customfield/PhabricatorUserBlurbField.php',
|
||||
'PhabricatorUserCardView' => 'applications/people/view/PhabricatorUserCardView.php',
|
||||
'PhabricatorUserConfigOptions' => 'applications/people/config/PhabricatorUserConfigOptions.php',
|
||||
'PhabricatorUserConfiguredCustomField' => 'applications/people/customfield/PhabricatorUserConfiguredCustomField.php',
|
||||
'PhabricatorUserConfiguredCustomFieldStorage' => 'applications/people/storage/PhabricatorUserConfiguredCustomFieldStorage.php',
|
||||
|
@ -3817,7 +3832,6 @@ phutil_register_library_map(array(
|
|||
'ProjectDefaultJoinCapability' => 'applications/project/capability/ProjectDefaultJoinCapability.php',
|
||||
'ProjectDefaultViewCapability' => 'applications/project/capability/ProjectDefaultViewCapability.php',
|
||||
'ProjectEditConduitAPIMethod' => 'applications/project/conduit/ProjectEditConduitAPIMethod.php',
|
||||
'ProjectHovercardEngineExtension' => 'applications/project/events/ProjectHovercardEngineExtension.php',
|
||||
'ProjectQueryConduitAPIMethod' => 'applications/project/conduit/ProjectQueryConduitAPIMethod.php',
|
||||
'ProjectRemarkupRule' => 'applications/project/remarkup/ProjectRemarkupRule.php',
|
||||
'ProjectRemarkupRuleTestCase' => 'applications/project/remarkup/__tests__/ProjectRemarkupRuleTestCase.php',
|
||||
|
@ -4221,7 +4235,7 @@ phutil_register_library_map(array(
|
|||
'ConduitGetCapabilitiesConduitAPIMethod' => 'ConduitAPIMethod',
|
||||
'ConduitGetCertificateConduitAPIMethod' => 'ConduitAPIMethod',
|
||||
'ConduitIntListParameterType' => 'ConduitListParameterType',
|
||||
'ConduitIntParameterType' => 'ConduitListParameterType',
|
||||
'ConduitIntParameterType' => 'ConduitParameterType',
|
||||
'ConduitListParameterType' => 'ConduitParameterType',
|
||||
'ConduitLogGarbageCollector' => 'PhabricatorGarbageCollector',
|
||||
'ConduitMethodDoesNotExistException' => 'ConduitMethodNotFoundException',
|
||||
|
@ -4230,6 +4244,7 @@ phutil_register_library_map(array(
|
|||
'ConduitPHIDParameterType' => 'ConduitParameterType',
|
||||
'ConduitParameterType' => 'Phobject',
|
||||
'ConduitPingConduitAPIMethod' => 'ConduitAPIMethod',
|
||||
'ConduitPointsParameterType' => 'ConduitParameterType',
|
||||
'ConduitProjectListParameterType' => 'ConduitListParameterType',
|
||||
'ConduitQueryConduitAPIMethod' => 'ConduitAPIMethod',
|
||||
'ConduitResultSearchEngineExtension' => 'PhabricatorSearchEngineExtension',
|
||||
|
@ -5447,6 +5462,7 @@ phutil_register_library_map(array(
|
|||
'ManiphestHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension',
|
||||
'ManiphestInfoConduitAPIMethod' => 'ManiphestConduitAPIMethod',
|
||||
'ManiphestNameIndex' => 'ManiphestDAO',
|
||||
'ManiphestPointsConfigOptionType' => 'PhabricatorConfigJSONOptionType',
|
||||
'ManiphestPriorityConfigOptionType' => 'PhabricatorConfigJSONOptionType',
|
||||
'ManiphestPriorityEmailCommand' => 'ManiphestEmailCommand',
|
||||
'ManiphestProjectNameFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension',
|
||||
|
@ -5503,6 +5519,7 @@ phutil_register_library_map(array(
|
|||
'ManiphestTaskOpenStatusDatasource' => 'PhabricatorTypeaheadDatasource',
|
||||
'ManiphestTaskPHIDResolver' => 'PhabricatorPHIDResolver',
|
||||
'ManiphestTaskPHIDType' => 'PhabricatorPHIDType',
|
||||
'ManiphestTaskPoints' => 'Phobject',
|
||||
'ManiphestTaskPriority' => 'ManiphestConstants',
|
||||
'ManiphestTaskPriorityDatasource' => 'PhabricatorTypeaheadDatasource',
|
||||
'ManiphestTaskPriorityHeraldAction' => 'HeraldAction',
|
||||
|
@ -5702,6 +5719,8 @@ phutil_register_library_map(array(
|
|||
'PHUIPropertyListView' => 'AphrontView',
|
||||
'PHUIRemarkupPreviewPanel' => 'AphrontTagView',
|
||||
'PHUIRemarkupView' => 'AphrontView',
|
||||
'PHUISegmentBarSegmentView' => 'AphrontTagView',
|
||||
'PHUISegmentBarView' => 'AphrontTagView',
|
||||
'PHUISpacesNamespaceContextView' => 'AphrontView',
|
||||
'PHUIStatusItemView' => 'AphrontTagView',
|
||||
'PHUIStatusListView' => 'AphrontTagView',
|
||||
|
@ -6043,6 +6062,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorBinariesSetupCheck' => 'PhabricatorSetupCheck',
|
||||
'PhabricatorBitbucketAuthProvider' => 'PhabricatorOAuth1AuthProvider',
|
||||
'PhabricatorBoardLayoutEngine' => 'Phobject',
|
||||
'PhabricatorBoardRenderingEngine' => 'Phobject',
|
||||
'PhabricatorBoardResponseEngine' => 'Phobject',
|
||||
'PhabricatorBot' => 'PhabricatorDaemon',
|
||||
'PhabricatorBotChannel' => 'PhabricatorBotTarget',
|
||||
'PhabricatorBotDebugLogHandler' => 'PhabricatorBotHandler',
|
||||
|
@ -6500,6 +6521,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorEditEngineExtension' => 'Phobject',
|
||||
'PhabricatorEditEngineExtensionModule' => 'PhabricatorConfigModule',
|
||||
'PhabricatorEditEngineListController' => 'PhabricatorEditEngineController',
|
||||
'PhabricatorEditEnginePointsCommentAction' => 'PhabricatorEditEngineCommentAction',
|
||||
'PhabricatorEditEngineQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'PhabricatorEditEngineSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||
'PhabricatorEditEngineSelectCommentAction' => 'PhabricatorEditEngineCommentAction',
|
||||
|
@ -7198,6 +7220,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorPhurlURLViewController' => 'PhabricatorPhurlController',
|
||||
'PhabricatorPirateEnglishTranslation' => 'PhutilTranslation',
|
||||
'PhabricatorPlatformSite' => 'PhabricatorSite',
|
||||
'PhabricatorPointsEditField' => 'PhabricatorEditField',
|
||||
'PhabricatorPolicies' => 'PhabricatorPolicyConstants',
|
||||
'PhabricatorPolicy' => array(
|
||||
'PhabricatorPolicyDAO',
|
||||
|
@ -7271,6 +7294,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorProjectApplication' => 'PhabricatorApplication',
|
||||
'PhabricatorProjectArchiveController' => 'PhabricatorProjectController',
|
||||
'PhabricatorProjectBoardController' => 'PhabricatorProjectController',
|
||||
'PhabricatorProjectBoardDisableController' => 'PhabricatorProjectBoardController',
|
||||
'PhabricatorProjectBoardImportController' => 'PhabricatorProjectBoardController',
|
||||
'PhabricatorProjectBoardReorderController' => 'PhabricatorProjectBoardController',
|
||||
'PhabricatorProjectBoardViewController' => 'PhabricatorProjectBoardController',
|
||||
|
@ -7302,12 +7326,14 @@ phutil_register_library_map(array(
|
|||
),
|
||||
'PhabricatorProjectController' => 'PhabricatorController',
|
||||
'PhabricatorProjectCoreTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorProjectCoverController' => 'PhabricatorProjectController',
|
||||
'PhabricatorProjectCustomField' => 'PhabricatorCustomField',
|
||||
'PhabricatorProjectCustomFieldNumericIndex' => 'PhabricatorCustomFieldNumericIndexStorage',
|
||||
'PhabricatorProjectCustomFieldStorage' => 'PhabricatorCustomFieldStorage',
|
||||
'PhabricatorProjectCustomFieldStringIndex' => 'PhabricatorCustomFieldStringIndexStorage',
|
||||
'PhabricatorProjectDAO' => 'PhabricatorLiskDAO',
|
||||
'PhabricatorProjectDatasource' => 'PhabricatorTypeaheadDatasource',
|
||||
'PhabricatorProjectDefaultController' => 'PhabricatorProjectBoardController',
|
||||
'PhabricatorProjectDescriptionField' => 'PhabricatorProjectStandardCustomField',
|
||||
'PhabricatorProjectDetailsProfilePanel' => 'PhabricatorProfilePanel',
|
||||
'PhabricatorProjectEditController' => 'PhabricatorProjectController',
|
||||
|
@ -7317,6 +7343,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorProjectHeraldAction' => 'HeraldAction',
|
||||
'PhabricatorProjectHeraldAdapter' => 'HeraldAdapter',
|
||||
'PhabricatorProjectHeraldFieldGroup' => 'HeraldFieldGroup',
|
||||
'PhabricatorProjectHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension',
|
||||
'PhabricatorProjectIconSet' => 'PhabricatorIconSet',
|
||||
'PhabricatorProjectIconsConfigOptionType' => 'PhabricatorConfigJSONOptionType',
|
||||
'PhabricatorProjectListController' => 'PhabricatorProjectController',
|
||||
|
@ -7346,6 +7373,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorProjectOrUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
||||
'PhabricatorProjectPHIDResolver' => 'PhabricatorPHIDResolver',
|
||||
'PhabricatorProjectPanelController' => 'PhabricatorProjectController',
|
||||
'PhabricatorProjectPointsProfilePanel' => 'PhabricatorProfilePanel',
|
||||
'PhabricatorProjectProfileController' => 'PhabricatorProjectController',
|
||||
'PhabricatorProjectProfilePanelEngine' => 'PhabricatorProfilePanelEngine',
|
||||
'PhabricatorProjectProjectHasMemberEdgeType' => 'PhabricatorEdgeType',
|
||||
|
@ -7865,6 +7893,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorFulltextInterface',
|
||||
),
|
||||
'PhabricatorUserBlurbField' => 'PhabricatorUserCustomField',
|
||||
'PhabricatorUserCardView' => 'AphrontTagView',
|
||||
'PhabricatorUserConfigOptions' => 'PhabricatorApplicationConfigOptions',
|
||||
'PhabricatorUserConfiguredCustomField' => array(
|
||||
'PhabricatorUserCustomField',
|
||||
|
@ -8452,7 +8481,6 @@ phutil_register_library_map(array(
|
|||
'ProjectDefaultJoinCapability' => 'PhabricatorPolicyCapability',
|
||||
'ProjectDefaultViewCapability' => 'PhabricatorPolicyCapability',
|
||||
'ProjectEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
|
||||
'ProjectHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension',
|
||||
'ProjectQueryConduitAPIMethod' => 'ProjectConduitAPIMethod',
|
||||
'ProjectRemarkupRule' => 'PhabricatorObjectRemarkupRule',
|
||||
'ProjectRemarkupRuleTestCase' => 'PhabricatorTestCase',
|
||||
|
|
|
@ -266,7 +266,6 @@ final class PhabricatorCalendarEventSearchEngine
|
|||
$list = new PHUIObjectItemListView();
|
||||
|
||||
foreach ($events as $event) {
|
||||
$duration = '';
|
||||
$event_date_info = $this->getEventDateLabel($event);
|
||||
$creator_handle = $handles[$event->getUserPHID()];
|
||||
$attendees = array();
|
||||
|
@ -275,18 +274,6 @@ final class PhabricatorCalendarEventSearchEngine
|
|||
$attendees[] = $invitee->getInviteePHID();
|
||||
}
|
||||
|
||||
$attendees = pht(
|
||||
'Attending: %s',
|
||||
$viewer->renderHandleList($attendees)
|
||||
->setAsInline(1)
|
||||
->render());
|
||||
|
||||
if (strlen($event->getDuration()) > 0) {
|
||||
$duration = pht(
|
||||
'Duration: %s',
|
||||
$event->getDuration());
|
||||
}
|
||||
|
||||
if ($event->getIsGhostEvent()) {
|
||||
$title_text = $event->getMonogram()
|
||||
.' ('
|
||||
|
@ -302,9 +289,25 @@ final class PhabricatorCalendarEventSearchEngine
|
|||
->setObject($event)
|
||||
->setHeader($title_text)
|
||||
->setHref($event->getURI())
|
||||
->addAttribute($event_date_info)
|
||||
->addAttribute($attendees)
|
||||
->addIcon('none', $duration);
|
||||
->addAttribute($event_date_info);
|
||||
|
||||
if ($attendees) {
|
||||
$attending = pht(
|
||||
'Attending: %s',
|
||||
$viewer->renderHandleList($attendees)
|
||||
->setAsInline(1)
|
||||
->render());
|
||||
|
||||
$item->addAttribute($attending);
|
||||
}
|
||||
|
||||
if (strlen($event->getDuration()) > 0) {
|
||||
$duration = pht(
|
||||
'Duration: %s',
|
||||
$event->getDuration());
|
||||
|
||||
$item->addIcon('none', $duration);
|
||||
}
|
||||
|
||||
$list->addItem($item);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
|
||||
final class ConduitIntParameterType
|
||||
extends ConduitListParameterType {
|
||||
extends ConduitParameterType {
|
||||
|
||||
protected function getParameterValue(array $request, $key) {
|
||||
$value = parent::getParameterValue($request, $key);
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
final class ConduitPointsParameterType
|
||||
extends ConduitParameterType {
|
||||
|
||||
protected function getParameterValue(array $request, $key) {
|
||||
$value = parent::getParameterValue($request, $key);
|
||||
|
||||
if (($value !== null) && !is_numeric($value)) {
|
||||
$this->raiseValidationException(
|
||||
$request,
|
||||
$key,
|
||||
pht('Expected numeric points value, got something else.'));
|
||||
}
|
||||
|
||||
if ($value !== null) {
|
||||
$value = (double)$value;
|
||||
if ($value < 0) {
|
||||
$this->raiseValidationException(
|
||||
$request,
|
||||
$key,
|
||||
pht('Point values must be nonnegative.'));
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
protected function getParameterTypeName() {
|
||||
return 'points';
|
||||
}
|
||||
|
||||
protected function getParameterFormatDescriptions() {
|
||||
return array(
|
||||
pht('A nonnegative number, or null.'),
|
||||
);
|
||||
}
|
||||
|
||||
protected function getParameterExamples() {
|
||||
return array(
|
||||
'null',
|
||||
'0',
|
||||
'1',
|
||||
'15',
|
||||
'0.5',
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -92,10 +92,9 @@ final class DiffusionCommitController extends DiffusionController {
|
|||
$engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine();
|
||||
$engine->setConfig('viewer', $user);
|
||||
|
||||
require_celerity_resource('phabricator-remarkup-css');
|
||||
|
||||
$headsup_view = id(new PHUIHeaderView())
|
||||
->setHeader(nonempty($commit->getSummary(), pht('Commit Detail')));
|
||||
->setHeader(nonempty($commit->getSummary(), pht('Commit Detail')))
|
||||
->setSubheader(pht('Commit: %s', $commit->getCommitIdentifier()));
|
||||
|
||||
$headsup_actions = $this->renderHeadsupActionList($commit, $repository);
|
||||
|
||||
|
|
|
@ -147,7 +147,7 @@ abstract class DiffusionController extends PhabricatorController {
|
|||
$crumb_list[] = $crumb;
|
||||
|
||||
$stable_commit = $drequest->getStableCommit();
|
||||
$commit_name = $repository->formatCommitName($stable_commit);
|
||||
$commit_name = $repository->formatCommitName($stable_commit, $local = true);
|
||||
$commit_uri = $repository->getCommitURI($stable_commit);
|
||||
|
||||
if ($spec['tags']) {
|
||||
|
@ -171,8 +171,7 @@ abstract class DiffusionController extends PhabricatorController {
|
|||
|
||||
if ($spec['commit']) {
|
||||
$crumb = id(new PHUICrumbView())
|
||||
->setName($commit_name)
|
||||
->setHref($commit_uri);
|
||||
->setName($commit_name);
|
||||
$crumb_list[] = $crumb;
|
||||
return $crumb_list;
|
||||
}
|
||||
|
|
|
@ -427,11 +427,14 @@ final class DiffusionServeController extends DiffusionController {
|
|||
'$PATH'));
|
||||
}
|
||||
|
||||
// NOTE: We do not set HTTP_CONTENT_ENCODING here, because we already
|
||||
// decompressed the request when we read the request body, so the body is
|
||||
// just plain data with no encoding.
|
||||
|
||||
$env = array(
|
||||
'REQUEST_METHOD' => $_SERVER['REQUEST_METHOD'],
|
||||
'QUERY_STRING' => $query_string,
|
||||
'CONTENT_TYPE' => $request->getHTTPHeader('Content-Type'),
|
||||
'HTTP_CONTENT_ENCODING' => $request->getHTTPHeader('Content-Encoding'),
|
||||
'REMOTE_ADDR' => $_SERVER['REMOTE_ADDR'],
|
||||
'GIT_PROJECT_ROOT' => $repository_root,
|
||||
'GIT_HTTP_EXPORT_ALL' => '1',
|
||||
|
|
|
@ -7,6 +7,7 @@ final class PhabricatorFileThumbnailTransform
|
|||
const TRANSFORM_PINBOARD = 'pinboard';
|
||||
const TRANSFORM_THUMBGRID = 'thumbgrid';
|
||||
const TRANSFORM_PREVIEW = 'preview';
|
||||
const TRANSFORM_WORKCARD = 'workcard';
|
||||
|
||||
private $name;
|
||||
private $key;
|
||||
|
@ -73,6 +74,11 @@ final class PhabricatorFileThumbnailTransform
|
|||
->setName(pht('Preview (220px)'))
|
||||
->setKey(self::TRANSFORM_PREVIEW)
|
||||
->setDimensions(220, null),
|
||||
id(new self())
|
||||
->setName(pht('Workcard (526px)'))
|
||||
->setKey(self::TRANSFORM_WORKCARD)
|
||||
->setScaleUp(true)
|
||||
->setDimensions(526, null),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ abstract class HeraldAdapter extends Phobject {
|
|||
const CONDITION_IS_ME = 'me';
|
||||
const CONDITION_IS_NOT_ME = '!me';
|
||||
const CONDITION_REGEXP = 'regexp';
|
||||
const CONDITION_NOT_REGEXP = '!regexp';
|
||||
const CONDITION_RULE = 'conditions';
|
||||
const CONDITION_NOT_RULE = '!conditions';
|
||||
const CONDITION_EXISTS = 'exists';
|
||||
|
@ -322,6 +323,7 @@ abstract class HeraldAdapter extends Phobject {
|
|||
self::CONDITION_IS_ME => pht('is myself'),
|
||||
self::CONDITION_IS_NOT_ME => pht('is not myself'),
|
||||
self::CONDITION_REGEXP => pht('matches regexp'),
|
||||
self::CONDITION_NOT_REGEXP => pht('does not match regexp'),
|
||||
self::CONDITION_RULE => pht('matches:'),
|
||||
self::CONDITION_NOT_RULE => pht('does not match:'),
|
||||
self::CONDITION_EXISTS => pht('exists'),
|
||||
|
@ -364,16 +366,18 @@ abstract class HeraldAdapter extends Phobject {
|
|||
|
||||
switch ($condition_type) {
|
||||
case self::CONDITION_CONTAINS:
|
||||
// "Contains" can take an array of strings, as in "Any changed
|
||||
// filename" for diffs.
|
||||
case self::CONDITION_NOT_CONTAINS:
|
||||
// "Contains and "does not contain" can take an array of strings, as in
|
||||
// "Any changed filename" for diffs.
|
||||
|
||||
$result_if_match = ($condition_type == self::CONDITION_CONTAINS);
|
||||
|
||||
foreach ((array)$field_value as $value) {
|
||||
if (stripos($value, $condition_value) !== false) {
|
||||
return true;
|
||||
return $result_if_match;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
case self::CONDITION_NOT_CONTAINS:
|
||||
return (stripos($field_value, $condition_value) === false);
|
||||
return !$result_if_match;
|
||||
case self::CONDITION_IS:
|
||||
return ($field_value == $condition_value);
|
||||
case self::CONDITION_IS_NOT:
|
||||
|
@ -427,6 +431,9 @@ abstract class HeraldAdapter extends Phobject {
|
|||
case self::CONDITION_NEVER:
|
||||
return false;
|
||||
case self::CONDITION_REGEXP:
|
||||
case self::CONDITION_NOT_REGEXP:
|
||||
$result_if_match = ($condition_type == self::CONDITION_REGEXP);
|
||||
|
||||
foreach ((array)$field_value as $value) {
|
||||
// We add the 'S' flag because we use the regexp multiple times.
|
||||
// It shouldn't cause any troubles if the flag is already there
|
||||
|
@ -437,10 +444,10 @@ abstract class HeraldAdapter extends Phobject {
|
|||
pht('Regular expression is not valid!'));
|
||||
}
|
||||
if ($result) {
|
||||
return true;
|
||||
return $result_if_match;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return !$result_if_match;
|
||||
case self::CONDITION_REGEXP_PAIR:
|
||||
// Match a JSON-encoded pair of regular expressions against a
|
||||
// dictionary. The first regexp must match the dictionary key, and the
|
||||
|
@ -509,6 +516,7 @@ abstract class HeraldAdapter extends Phobject {
|
|||
|
||||
switch ($condition_type) {
|
||||
case self::CONDITION_REGEXP:
|
||||
case self::CONDITION_NOT_REGEXP:
|
||||
$ok = @preg_match($condition_value, '');
|
||||
if ($ok === false) {
|
||||
throw new HeraldInvalidConditionException(
|
||||
|
|
|
@ -47,6 +47,7 @@ abstract class HeraldField extends Phobject {
|
|||
HeraldAdapter::CONDITION_IS,
|
||||
HeraldAdapter::CONDITION_IS_NOT,
|
||||
HeraldAdapter::CONDITION_REGEXP,
|
||||
HeraldAdapter::CONDITION_NOT_REGEXP,
|
||||
);
|
||||
case self::STANDARD_PHID:
|
||||
return array(
|
||||
|
@ -76,12 +77,16 @@ abstract class HeraldField extends Phobject {
|
|||
case self::STANDARD_TEXT_LIST:
|
||||
return array(
|
||||
HeraldAdapter::CONDITION_CONTAINS,
|
||||
HeraldAdapter::CONDITION_NOT_CONTAINS,
|
||||
HeraldAdapter::CONDITION_REGEXP,
|
||||
HeraldAdapter::CONDITION_NOT_REGEXP,
|
||||
);
|
||||
case self::STANDARD_TEXT_MAP:
|
||||
return array(
|
||||
HeraldAdapter::CONDITION_CONTAINS,
|
||||
HeraldAdapter::CONDITION_NOT_CONTAINS,
|
||||
HeraldAdapter::CONDITION_REGEXP,
|
||||
HeraldAdapter::CONDITION_NOT_REGEXP,
|
||||
HeraldAdapter::CONDITION_REGEXP_PAIR,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -59,6 +59,7 @@ final class ManiphestGetTaskTransactionsConduitAPIMethod
|
|||
|
||||
$results[$task_id][] = array(
|
||||
'taskID' => $task_id,
|
||||
'transactionID' => $transaction->getID(),
|
||||
'transactionPHID' => $transaction->getPHID(),
|
||||
'transactionType' => $transaction->getTransactionType(),
|
||||
'oldValue' => $transaction->getOldValue(),
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
final class ManiphestPointsConfigOptionType
|
||||
extends PhabricatorConfigJSONOptionType {
|
||||
|
||||
public function validateOption(PhabricatorConfigOption $option, $value) {
|
||||
ManiphestTaskPoints::validateConfiguration($value);
|
||||
}
|
||||
|
||||
}
|
|
@ -111,6 +111,7 @@ final class PhabricatorManiphestConfigOptions
|
|||
'name' => pht('Invalid'),
|
||||
'name.full' => pht('Closed, Invalid'),
|
||||
'closed' => true,
|
||||
'claim' => false,
|
||||
'prefixes' => array(
|
||||
'invalidate',
|
||||
'invalidates',
|
||||
|
@ -126,6 +127,7 @@ final class PhabricatorManiphestConfigOptions
|
|||
'transaction.icon' => 'fa-files-o',
|
||||
'special' => ManiphestTaskStatus::SPECIAL_DUPLICATE,
|
||||
'closed' => true,
|
||||
'claim' => false,
|
||||
),
|
||||
'spite' => array(
|
||||
'name' => pht('Spite'),
|
||||
|
@ -202,6 +204,9 @@ The keys you can provide in a specification are:
|
|||
tasks can not be created or edited to have this status. Existing tasks with
|
||||
this status will not be affected, but you can batch edit them or let them
|
||||
die out on their own.
|
||||
- `claim` //Optional bool.// By default, closing an unassigned task claims
|
||||
it. You can set this to `false` to disable this behavior for a particular
|
||||
status.
|
||||
|
||||
Statuses will appear in the UI in the order specified. Note the status marked
|
||||
`special` as `duplicate` is not settable directly and will not appear in UI
|
||||
|
@ -255,6 +260,40 @@ EOTEXT
|
|||
);
|
||||
$fields_json = id(new PhutilJSON())->encodeFormatted($fields_example);
|
||||
|
||||
$points_type = 'custom:ManiphestPointsConfigOptionType';
|
||||
|
||||
$points_example_1 = array(
|
||||
'enabled' => true,
|
||||
'label' => pht('Story Points'),
|
||||
'action' => pht('Change Story Points'),
|
||||
);
|
||||
$points_json_1 = id(new PhutilJSON())->encodeFormatted($points_example_1);
|
||||
|
||||
$points_example_2 = array(
|
||||
'enabled' => true,
|
||||
'label' => pht('Estimated Hours'),
|
||||
'action' => pht('Change Estimate'),
|
||||
);
|
||||
$points_json_2 = id(new PhutilJSON())->encodeFormatted($points_example_2);
|
||||
|
||||
$points_description = $this->deformat(pht(<<<EOTEXT
|
||||
Activates a points field on tasks. You can use points for estimation or
|
||||
planning. If configured, points will appear on workboards.
|
||||
|
||||
To activate points, set this value to a map with these keys:
|
||||
|
||||
- `enabled` //Optional bool.// Use `true` to enable points, or
|
||||
`false` to disable them.
|
||||
- `label` //Optional string.// Label for points, like "Story Points" or
|
||||
"Estimated Hours". If omitted, points will be called "Points".
|
||||
- `action` //Optional string.// Label for the action which changes points
|
||||
in Maniphest, like "Change Estimate". If omitted, the action will
|
||||
be called "Change Points".
|
||||
|
||||
See the example below for a starting point.
|
||||
EOTEXT
|
||||
));
|
||||
|
||||
return array(
|
||||
$this->newOption('maniphest.custom-field-definitions', 'wild', array())
|
||||
->setSummary(pht('Custom Maniphest fields.'))
|
||||
|
@ -336,7 +375,11 @@ EOTEXT
|
|||
'"Needs Triage" panel on the home page. You should adjust this if '.
|
||||
'you adjust priorities using `%s`.',
|
||||
'maniphest.priorities')),
|
||||
|
||||
$this->newOption('maniphest.points', $points_type, array())
|
||||
->setSummary(pht('Configure point values for tasks.'))
|
||||
->setDescription($points_description)
|
||||
->addExample($points_json_1, pht('Points Config'))
|
||||
->addExample($points_json_2, pht('Hours Config')),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
41
src/applications/maniphest/constants/ManiphestTaskPoints.php
Normal file
41
src/applications/maniphest/constants/ManiphestTaskPoints.php
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
final class ManiphestTaskPoints extends Phobject {
|
||||
|
||||
public static function getIsEnabled() {
|
||||
$config = self::getPointsConfig();
|
||||
return idx($config, 'enabled');
|
||||
}
|
||||
|
||||
public static function getPointsLabel() {
|
||||
$config = self::getPointsConfig();
|
||||
return idx($config, 'label', pht('Points'));
|
||||
}
|
||||
|
||||
public static function getPointsActionLabel() {
|
||||
$config = self::getPointsConfig();
|
||||
return idx($config, 'action', pht('Change Points'));
|
||||
}
|
||||
|
||||
private static function getPointsConfig() {
|
||||
return PhabricatorEnv::getEnvConfig('maniphest.points');
|
||||
}
|
||||
|
||||
public static function validateConfiguration($config) {
|
||||
if (!is_array($config)) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Configuration is not valid. Maniphest points configuration must '.
|
||||
'be a dictionary.'));
|
||||
}
|
||||
|
||||
PhutilTypeSpec::checkMap(
|
||||
$config,
|
||||
array(
|
||||
'enabled' => 'optional bool',
|
||||
'label' => 'optional string',
|
||||
'action' => 'optional string',
|
||||
));
|
||||
}
|
||||
|
||||
}
|
|
@ -155,6 +155,10 @@ final class ManiphestTaskStatus extends ManiphestConstants {
|
|||
return false;
|
||||
}
|
||||
|
||||
public static function isClaimStatus($status) {
|
||||
return self::getStatusAttribute($status, 'claim', true);
|
||||
}
|
||||
|
||||
public static function isClosedStatus($status) {
|
||||
return !self::isOpenStatus($status);
|
||||
}
|
||||
|
@ -279,6 +283,7 @@ final class ManiphestTaskStatus extends ManiphestConstants {
|
|||
'suffixes' => 'optional list<string>',
|
||||
'keywords' => 'optional list<string>',
|
||||
'disabled' => 'optional bool',
|
||||
'claim' => 'optional bool',
|
||||
));
|
||||
}
|
||||
|
||||
|
|
|
@ -229,6 +229,15 @@ final class ManiphestTaskDetailController extends ManiphestController {
|
|||
|
||||
$view->addProperty(pht('Author'), $author);
|
||||
|
||||
if (ManiphestTaskPoints::getIsEnabled()) {
|
||||
$points = $task->getPoints();
|
||||
if ($points !== null) {
|
||||
$view->addProperty(
|
||||
ManiphestTaskPoints::getPointsLabel(),
|
||||
$task->getPoints());
|
||||
}
|
||||
}
|
||||
|
||||
$source = $task->getOriginalEmailSource();
|
||||
if ($source) {
|
||||
$subject = '[T'.$task->getID().'] '.$task->getTitle();
|
||||
|
|
|
@ -9,6 +9,7 @@ final class ManiphestTaskEditController extends ManiphestController {
|
|||
->addContextParameter('responseType')
|
||||
->addContextParameter('columnPHID')
|
||||
->addContextParameter('order')
|
||||
->addContextParameter('visiblePHIDs')
|
||||
->buildResponse();
|
||||
}
|
||||
|
||||
|
|
|
@ -77,7 +77,7 @@ final class ManiphestEditEngine
|
|||
$owner_value = array($this->getViewer()->getPHID());
|
||||
}
|
||||
|
||||
return array(
|
||||
$fields = array(
|
||||
id(new PhabricatorHandlesEditField())
|
||||
->setKey('parent')
|
||||
->setLabel(pht('Parent Task'))
|
||||
|
@ -149,18 +149,37 @@ final class ManiphestEditEngine
|
|||
->setValue($object->getPriority())
|
||||
->setOptions($priority_map)
|
||||
->setCommentActionLabel(pht('Change Priority')),
|
||||
id(new PhabricatorRemarkupEditField())
|
||||
->setKey('description')
|
||||
->setLabel(pht('Description'))
|
||||
->setDescription(pht('Task description.'))
|
||||
->setConduitDescription(pht('Update the task description.'))
|
||||
->setConduitTypeDescription(pht('New task description.'))
|
||||
->setTransactionType(ManiphestTransaction::TYPE_DESCRIPTION)
|
||||
->setValue($object->getDescription())
|
||||
->setPreviewPanel(
|
||||
id(new PHUIRemarkupPreviewPanel())
|
||||
->setHeader(pht('Description Preview'))),
|
||||
);
|
||||
|
||||
if (ManiphestTaskPoints::getIsEnabled()) {
|
||||
$points_label = ManiphestTaskPoints::getPointsLabel();
|
||||
$action_label = ManiphestTaskPoints::getPointsActionLabel();
|
||||
|
||||
$fields[] = id(new PhabricatorPointsEditField())
|
||||
->setKey('points')
|
||||
->setLabel($points_label)
|
||||
->setDescription(pht('Point value of the task.'))
|
||||
->setConduitDescription(pht('Change the task point value.'))
|
||||
->setConduitTypeDescription(pht('New task point value.'))
|
||||
->setTransactionType(ManiphestTransaction::TYPE_POINTS)
|
||||
->setIsCopyable(true)
|
||||
->setValue($object->getPoints())
|
||||
->setCommentActionLabel($action_label);
|
||||
}
|
||||
|
||||
$fields[] = id(new PhabricatorRemarkupEditField())
|
||||
->setKey('description')
|
||||
->setLabel(pht('Description'))
|
||||
->setDescription(pht('Task description.'))
|
||||
->setConduitDescription(pht('Update the task description.'))
|
||||
->setConduitTypeDescription(pht('New task description.'))
|
||||
->setTransactionType(ManiphestTransaction::TYPE_DESCRIPTION)
|
||||
->setValue($object->getDescription())
|
||||
->setPreviewPanel(
|
||||
id(new PHUIRemarkupPreviewPanel())
|
||||
->setHeader(pht('Description Preview')));
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
private function getTaskStatusMap(ManiphestTask $task) {
|
||||
|
@ -270,7 +289,11 @@ final class ManiphestEditEngine
|
|||
$viewer = $request->getViewer();
|
||||
|
||||
$column_phid = $request->getStr('columnPHID');
|
||||
$order = $request->getStr('order');
|
||||
|
||||
$visible_phids = $request->getStrList('visiblePHIDs');
|
||||
if (!$visible_phids) {
|
||||
$visible_phids = array();
|
||||
}
|
||||
|
||||
$column = id(new PhabricatorProjectColumnQuery())
|
||||
->setViewer($viewer)
|
||||
|
@ -280,93 +303,15 @@ final class ManiphestEditEngine
|
|||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
// If the workboard's project and all descendant projects have been removed
|
||||
// from the card's project list, we are going to remove it from the board
|
||||
// completely.
|
||||
$board_phid = $column->getProjectPHID();
|
||||
$object_phid = $task->getPHID();
|
||||
|
||||
// TODO: If the user did something sneaky and changed a subproject, we'll
|
||||
// currently leave the card where it was but should really move it to the
|
||||
// proper new column.
|
||||
|
||||
$descendant_projects = id(new PhabricatorProjectQuery())
|
||||
return id(new PhabricatorBoardResponseEngine())
|
||||
->setViewer($viewer)
|
||||
->withAncestorProjectPHIDs(array($column->getProjectPHID()))
|
||||
->execute();
|
||||
$board_phids = mpull($descendant_projects, 'getPHID', 'getPHID');
|
||||
$board_phids[$column->getProjectPHID()] = $column->getProjectPHID();
|
||||
|
||||
$project_map = array_fuse($task->getProjectPHIDs());
|
||||
$remove_card = !array_intersect_key($board_phids, $project_map);
|
||||
|
||||
$positions = id(new PhabricatorProjectColumnPositionQuery())
|
||||
->setViewer($viewer)
|
||||
->withBoardPHIDs(array($column->getProjectPHID()))
|
||||
->withColumnPHIDs(array($column->getPHID()))
|
||||
->execute();
|
||||
$task_phids = mpull($positions, 'getObjectPHID');
|
||||
|
||||
$column_tasks = id(new ManiphestTaskQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs($task_phids)
|
||||
->needProjectPHIDs(true)
|
||||
->execute();
|
||||
|
||||
if ($order == PhabricatorProjectColumn::ORDER_NATURAL) {
|
||||
// TODO: This is a little bit awkward, because PHP and JS use
|
||||
// slightly different sort order parameters to achieve the same
|
||||
// effect. It would be good to unify this a bit at some point.
|
||||
$sort_map = array();
|
||||
foreach ($positions as $position) {
|
||||
$sort_map[$position->getObjectPHID()] = array(
|
||||
-$position->getSequence(),
|
||||
$position->getID(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$sort_map = mpull(
|
||||
$column_tasks,
|
||||
'getPrioritySortVector',
|
||||
'getPHID');
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'removeFromBoard' => $remove_card,
|
||||
'sortMap' => $sort_map,
|
||||
);
|
||||
|
||||
// TODO: This should just use HandlePool once we get through the EditEngine
|
||||
// transition.
|
||||
$owner = null;
|
||||
if ($task->getOwnerPHID()) {
|
||||
$owner = id(new PhabricatorHandleQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($task->getOwnerPHID()))
|
||||
->executeOne();
|
||||
}
|
||||
|
||||
$handle_phids = $task->getProjectPHIDs();
|
||||
$handle_phids = array_fuse($handle_phids);
|
||||
$handle_phids = array_diff_key($handle_phids, $board_phids);
|
||||
|
||||
$project_handles = $viewer->loadHandles($handle_phids);
|
||||
$project_handles = iterator_to_array($project_handles);
|
||||
|
||||
$tasks = id(new ProjectBoardTaskCard())
|
||||
->setViewer($viewer)
|
||||
->setTask($task)
|
||||
->setOwner($owner)
|
||||
->setProjectHandles($project_handles)
|
||||
->setCanEdit(true)
|
||||
->getItem();
|
||||
|
||||
$tasks->addClass('phui-workcard');
|
||||
|
||||
$payload = array(
|
||||
'tasks' => $tasks,
|
||||
'data' => $data,
|
||||
);
|
||||
|
||||
return id(new AphrontAjaxResponse())->setContent($payload);
|
||||
->setBoardPHID($board_phid)
|
||||
->setObjectPHID($object_phid)
|
||||
->setVisiblePHIDs($visible_phids)
|
||||
->buildResponse();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -28,6 +28,8 @@ final class ManiphestTransactionEditor
|
|||
$types[] = ManiphestTransaction::TYPE_UNBLOCK;
|
||||
$types[] = ManiphestTransaction::TYPE_PARENT;
|
||||
$types[] = ManiphestTransaction::TYPE_COLUMN;
|
||||
$types[] = ManiphestTransaction::TYPE_COVER_IMAGE;
|
||||
$types[] = ManiphestTransaction::TYPE_POINTS;
|
||||
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
|
||||
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
|
||||
|
||||
|
@ -66,6 +68,14 @@ final class ManiphestTransactionEditor
|
|||
return $xaction->getOldValue();
|
||||
case ManiphestTransaction::TYPE_SUBPRIORITY:
|
||||
return $object->getSubpriority();
|
||||
case ManiphestTransaction::TYPE_COVER_IMAGE:
|
||||
return $object->getCoverImageFilePHID();
|
||||
case ManiphestTransaction::TYPE_POINTS:
|
||||
$points = $object->getPoints();
|
||||
if ($points !== null) {
|
||||
$points = (double)$points;
|
||||
}
|
||||
return $points;
|
||||
case ManiphestTransaction::TYPE_MERGED_INTO:
|
||||
case ManiphestTransaction::TYPE_MERGED_FROM:
|
||||
return null;
|
||||
|
@ -92,10 +102,20 @@ final class ManiphestTransactionEditor
|
|||
case ManiphestTransaction::TYPE_MERGED_INTO:
|
||||
case ManiphestTransaction::TYPE_MERGED_FROM:
|
||||
case ManiphestTransaction::TYPE_UNBLOCK:
|
||||
case ManiphestTransaction::TYPE_COVER_IMAGE:
|
||||
return $xaction->getNewValue();
|
||||
case ManiphestTransaction::TYPE_PARENT:
|
||||
case ManiphestTransaction::TYPE_COLUMN:
|
||||
return $xaction->getNewValue();
|
||||
case ManiphestTransaction::TYPE_POINTS:
|
||||
$value = $xaction->getNewValue();
|
||||
if (!strlen($value)) {
|
||||
$value = null;
|
||||
}
|
||||
if ($value !== null) {
|
||||
$value = (double)$value;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -161,6 +181,35 @@ final class ManiphestTransactionEditor
|
|||
case ManiphestTransaction::TYPE_MERGED_INTO:
|
||||
$object->setStatus(ManiphestTaskStatus::getDuplicateStatus());
|
||||
return;
|
||||
case ManiphestTransaction::TYPE_COVER_IMAGE:
|
||||
$file_phid = $xaction->getNewValue();
|
||||
|
||||
if ($file_phid) {
|
||||
$file = id(new PhabricatorFileQuery())
|
||||
->setViewer($this->getActor())
|
||||
->withPHIDs(array($file_phid))
|
||||
->executeOne();
|
||||
} else {
|
||||
$file = null;
|
||||
}
|
||||
|
||||
if (!$file || !$file->isTransformableImage()) {
|
||||
$object->setProperty('cover.filePHID', null);
|
||||
$object->setProperty('cover.thumbnailPHID', null);
|
||||
return;
|
||||
}
|
||||
|
||||
$xform_key = PhabricatorFileThumbnailTransform::TRANSFORM_WORKCARD;
|
||||
|
||||
$xform = PhabricatorFileTransform::getTransformByKey($xform_key)
|
||||
->executeTransform($file);
|
||||
|
||||
$object->setProperty('cover.filePHID', $file->getPHID());
|
||||
$object->setProperty('cover.thumbnailPHID', $xform->getPHID());
|
||||
return;
|
||||
case ManiphestTransaction::TYPE_POINTS:
|
||||
$object->setPoints($xaction->getNewValue());
|
||||
return;
|
||||
case ManiphestTransaction::TYPE_MERGED_FROM:
|
||||
case ManiphestTransaction::TYPE_PARENT:
|
||||
case ManiphestTransaction::TYPE_COLUMN:
|
||||
|
@ -819,6 +868,65 @@ final class ManiphestTransactionEditor
|
|||
}
|
||||
}
|
||||
break;
|
||||
case ManiphestTransaction::TYPE_COVER_IMAGE:
|
||||
foreach ($xactions as $xaction) {
|
||||
$old = $xaction->getOldValue();
|
||||
$new = $xaction->getNewValue();
|
||||
if (!$new) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($new === $old) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$file = id(new PhabricatorFileQuery())
|
||||
->setViewer($this->getActor())
|
||||
->withPHIDs(array($new))
|
||||
->executeOne();
|
||||
if (!$file) {
|
||||
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
||||
$type,
|
||||
pht('Invalid'),
|
||||
pht('File "%s" is not valid.', $new),
|
||||
$xaction);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$file->isTransformableImage()) {
|
||||
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
||||
$type,
|
||||
pht('Invalid'),
|
||||
pht('File "%s" is not a valid image file.', $new),
|
||||
$xaction);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ManiphestTransaction::TYPE_POINTS:
|
||||
foreach ($xactions as $xaction) {
|
||||
$new = $xaction->getNewValue();
|
||||
if (strlen($new) && !is_numeric($new)) {
|
||||
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
||||
$type,
|
||||
pht('Invalid'),
|
||||
pht('Points value must be numeric or empty.'),
|
||||
$xaction);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((double)$new < 0) {
|
||||
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
||||
$type,
|
||||
pht('Invalid'),
|
||||
pht('Points value must be nonnegative.'),
|
||||
$xaction);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return $errors;
|
||||
|
@ -863,8 +971,11 @@ final class ManiphestTransactionEditor
|
|||
// If the task is not assigned, not being assigned, currently open, and
|
||||
// being closed, try to assign the actor as the owner.
|
||||
if ($is_unassigned && !$any_assign && $is_open && $is_closing) {
|
||||
$is_claim = ManiphestTaskStatus::isClaimStatus($new_status);
|
||||
|
||||
// Don't assign the actor if they aren't a real user.
|
||||
if ($actor_phid) {
|
||||
// Don't claim the task if the status is configured to not claim.
|
||||
if ($actor_phid && $is_claim) {
|
||||
$results[] = id(new ManiphestTransaction())
|
||||
->setTransactionType(ManiphestTransaction::TYPE_OWNER)
|
||||
->setNewValue($actor_phid);
|
||||
|
@ -941,5 +1052,19 @@ final class ManiphestTransactionEditor
|
|||
->executeOne();
|
||||
}
|
||||
|
||||
protected function extractFilePHIDsFromCustomTransaction(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
$phids = parent::extractFilePHIDsFromCustomTransaction($object, $xaction);
|
||||
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case ManiphestTransaction::TYPE_COVER_IMAGE:
|
||||
$phids[] = $xaction->getNewValue();
|
||||
break;
|
||||
}
|
||||
|
||||
return $phids;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -34,19 +34,16 @@ final class ManiphestTask extends ManiphestDAO
|
|||
protected $viewPolicy = PhabricatorPolicies::POLICY_USER;
|
||||
protected $editPolicy = PhabricatorPolicies::POLICY_USER;
|
||||
|
||||
protected $projectPHIDs = array();
|
||||
|
||||
protected $ownerOrdering;
|
||||
protected $spacePHID;
|
||||
protected $properties = array();
|
||||
protected $points;
|
||||
|
||||
private $subscriberPHIDs = self::ATTACHABLE;
|
||||
private $groupByProjectPHID = self::ATTACHABLE;
|
||||
private $customFields = self::ATTACHABLE;
|
||||
private $edgeProjectPHIDs = self::ATTACHABLE;
|
||||
|
||||
// TODO: This field is unused and should eventually be removed.
|
||||
protected $attached = array();
|
||||
|
||||
public static function initializeNewTask(PhabricatorUser $actor) {
|
||||
$app = id(new PhabricatorApplicationQuery())
|
||||
->setViewer($actor)
|
||||
|
@ -71,9 +68,7 @@ final class ManiphestTask extends ManiphestDAO
|
|||
return array(
|
||||
self::CONFIG_AUX_PHID => true,
|
||||
self::CONFIG_SERIALIZATION => array(
|
||||
'ccPHIDs' => self::SERIALIZATION_JSON,
|
||||
'attached' => self::SERIALIZATION_JSON,
|
||||
'projectPHIDs' => self::SERIALIZATION_JSON,
|
||||
'properties' => self::SERIALIZATION_JSON,
|
||||
),
|
||||
self::CONFIG_COLUMN_SCHEMA => array(
|
||||
'ownerPHID' => 'phid?',
|
||||
|
@ -86,11 +81,7 @@ final class ManiphestTask extends ManiphestDAO
|
|||
'ownerOrdering' => 'text64?',
|
||||
'originalEmailSource' => 'text255?',
|
||||
'subpriority' => 'double',
|
||||
|
||||
// T6203/NULLABILITY
|
||||
// This should not be nullable. It's going away soon anyway.
|
||||
'ccPHIDs' => 'text?',
|
||||
|
||||
'points' => 'double?',
|
||||
),
|
||||
self::CONFIG_KEY_SCHEMA => array(
|
||||
'key_phid' => null,
|
||||
|
@ -141,10 +132,6 @@ final class ManiphestTask extends ManiphestDAO
|
|||
ManiphestTaskDependedOnByTaskEdgeType::EDGECONST);
|
||||
}
|
||||
|
||||
public function getAttachedPHIDs($type) {
|
||||
return array_keys(idx($this->attached, $type, array()));
|
||||
}
|
||||
|
||||
public function generatePHID() {
|
||||
return PhabricatorPHID::generateNewPHID(ManiphestTaskPHIDType::TYPECONST);
|
||||
}
|
||||
|
@ -207,11 +194,70 @@ final class ManiphestTask extends ManiphestDAO
|
|||
return ManiphestTaskStatus::isClosedStatus($this->getStatus());
|
||||
}
|
||||
|
||||
public function getPrioritySortVector() {
|
||||
public function setProperty($key, $value) {
|
||||
$this->properties[$key] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getProperty($key, $default = null) {
|
||||
return idx($this->properties, $key, $default);
|
||||
}
|
||||
|
||||
public function getCoverImageFilePHID() {
|
||||
return idx($this->properties, 'cover.filePHID');
|
||||
}
|
||||
|
||||
public function getCoverImageThumbnailPHID() {
|
||||
return idx($this->properties, 'cover.thumbnailPHID');
|
||||
}
|
||||
|
||||
public function getWorkboardOrderVectors() {
|
||||
return array(
|
||||
$this->getPriority(),
|
||||
-$this->getSubpriority(),
|
||||
$this->getID(),
|
||||
PhabricatorProjectColumn::ORDER_PRIORITY => array(
|
||||
(int)-$this->getPriority(),
|
||||
(double)-$this->getSubpriority(),
|
||||
(int)-$this->getID(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
private function comparePriorityTo(ManiphestTask $other) {
|
||||
$upri = $this->getPriority();
|
||||
$vpri = $other->getPriority();
|
||||
|
||||
if ($upri != $vpri) {
|
||||
return ($upri - $vpri);
|
||||
}
|
||||
|
||||
$usub = $this->getSubpriority();
|
||||
$vsub = $other->getSubpriority();
|
||||
|
||||
if ($usub != $vsub) {
|
||||
return ($usub - $vsub);
|
||||
}
|
||||
|
||||
$uid = $this->getID();
|
||||
$vid = $other->getID();
|
||||
|
||||
if ($uid != $vid) {
|
||||
return ($uid - $vid);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function isLowerPriorityThan(ManiphestTask $other) {
|
||||
return ($this->comparePriorityTo($other) < 0);
|
||||
}
|
||||
|
||||
public function isHigherPriorityThan(ManiphestTask $other) {
|
||||
return ($this->comparePriorityTo($other) > 0);
|
||||
}
|
||||
|
||||
public function getWorkboardProperties() {
|
||||
return array(
|
||||
'status' => $this->getStatus(),
|
||||
'points' => (double)$this->getPoints(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -416,6 +462,10 @@ final class ManiphestTask extends ManiphestDAO
|
|||
->setKey('priority')
|
||||
->setType('map<string, wild>')
|
||||
->setDescription(pht('Information about task priority.')),
|
||||
id(new PhabricatorConduitSearchFieldSpecification())
|
||||
->setKey('points')
|
||||
->setType('points')
|
||||
->setDescription(pht('Point value of the task.')),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -442,6 +492,7 @@ final class ManiphestTask extends ManiphestDAO
|
|||
'ownerPHID' => $this->getOwnerPHID(),
|
||||
'status' => $status_info,
|
||||
'priority' => $priority_info,
|
||||
'points' => $this->getPoints(),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,8 @@ final class ManiphestTransaction
|
|||
const TYPE_UNBLOCK = 'unblock';
|
||||
const TYPE_PARENT = 'parent';
|
||||
const TYPE_COLUMN = 'column';
|
||||
const TYPE_COVER_IMAGE = 'cover-image';
|
||||
const TYPE_POINTS = 'points';
|
||||
|
||||
// NOTE: this type is deprecated. Keep it around for legacy installs
|
||||
// so any transactions render correctly.
|
||||
|
@ -162,11 +164,27 @@ final class ManiphestTransaction
|
|||
sort($new_cols);
|
||||
|
||||
return ($old_cols === $new_cols);
|
||||
case self::TYPE_COVER_IMAGE:
|
||||
// At least for now, don't show these.
|
||||
return true;
|
||||
case self::TYPE_POINTS:
|
||||
if (!ManiphestTaskPoints::getIsEnabled()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return parent::shouldHide();
|
||||
}
|
||||
|
||||
public function shouldHideForMail(array $xactions) {
|
||||
switch ($this->getTransactionType()) {
|
||||
case self::TYPE_POINTS:
|
||||
return true;
|
||||
}
|
||||
|
||||
return parent::shouldHideForMail($xactions);
|
||||
}
|
||||
|
||||
public function shouldHideForFeed() {
|
||||
switch ($this->getTransactionType()) {
|
||||
case self::TYPE_UNBLOCK:
|
||||
|
@ -177,6 +195,8 @@ final class ManiphestTransaction
|
|||
return true;
|
||||
}
|
||||
break;
|
||||
case self::TYPE_POINTS:
|
||||
return true;
|
||||
}
|
||||
|
||||
return parent::shouldHideForFeed();
|
||||
|
@ -620,6 +640,23 @@ final class ManiphestTransaction
|
|||
$this->renderHandleList($new));
|
||||
break;
|
||||
|
||||
case self::TYPE_POINTS:
|
||||
if ($old === null) {
|
||||
return pht(
|
||||
'%s set the point value for this task to %s.',
|
||||
$this->renderHandleLink($author_phid),
|
||||
$new);
|
||||
} else if ($new === null) {
|
||||
return pht(
|
||||
'%s removed the point value for this task.',
|
||||
$this->renderHandleLink($author_phid));
|
||||
} else {
|
||||
return pht(
|
||||
'%s changed the point value for this task from %s to %s.',
|
||||
$this->renderHandleLink($author_phid),
|
||||
$old,
|
||||
$new);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -130,7 +130,10 @@ final class PhabricatorPeopleProfileViewController
|
|||
->setViewer($viewer)
|
||||
->withMemberPHIDs(array($user->getPHID()))
|
||||
->needImages(true)
|
||||
->withStatus(PhabricatorProjectQuery::STATUS_OPEN)
|
||||
->withStatuses(
|
||||
array(
|
||||
PhabricatorProjectStatus::STATUS_ACTIVE,
|
||||
))
|
||||
->execute();
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
|
|
|
@ -25,6 +25,7 @@ final class PeopleHovercardEngineExtension
|
|||
->setViewer($viewer)
|
||||
->withPHIDs($phids)
|
||||
->needAvailability(true)
|
||||
->needProfileImage(true)
|
||||
->needProfile(true)
|
||||
->needBadges(true)
|
||||
->execute();
|
||||
|
@ -47,69 +48,12 @@ final class PeopleHovercardEngineExtension
|
|||
return;
|
||||
}
|
||||
|
||||
$hovercard->setTitle($user->getUsername());
|
||||
$user_card = id(new PhabricatorUserCardView())
|
||||
->setProfile($user)
|
||||
->setViewer($viewer);
|
||||
|
||||
$profile = $user->getUserProfile();
|
||||
$detail = $user->getRealName();
|
||||
if ($profile->getTitle()) {
|
||||
$detail .= ' - '.$profile->getTitle();
|
||||
}
|
||||
$hovercard->setDetail($detail);
|
||||
$hovercard->appendChild($user_card);
|
||||
|
||||
if ($user->getIsDisabled()) {
|
||||
$hovercard->addField(pht('Account'), pht('Disabled'));
|
||||
} else if (!$user->isUserActivated()) {
|
||||
$hovercard->addField(pht('Account'), pht('Not Activated'));
|
||||
} else if (PhabricatorApplication::isClassInstalledForViewer(
|
||||
'PhabricatorCalendarApplication',
|
||||
$viewer)) {
|
||||
$hovercard->addField(
|
||||
pht('Status'),
|
||||
$user->getAvailabilityDescription($viewer));
|
||||
}
|
||||
|
||||
$hovercard->addField(
|
||||
pht('User Since'),
|
||||
phabricator_date($user->getDateCreated(), $viewer));
|
||||
|
||||
if ($profile->getBlurb()) {
|
||||
$hovercard->addField(pht('Blurb'),
|
||||
id(new PhutilUTF8StringTruncator())
|
||||
->setMaximumGlyphs(120)
|
||||
->truncateString($profile->getBlurb()));
|
||||
}
|
||||
|
||||
$badges = $this->buildBadges($user, $viewer);
|
||||
foreach ($badges as $badge) {
|
||||
$hovercard->addBadge($badge);
|
||||
}
|
||||
}
|
||||
|
||||
private function buildBadges(
|
||||
PhabricatorUser $user,
|
||||
$viewer) {
|
||||
|
||||
$class = 'PhabricatorBadgesApplication';
|
||||
$items = array();
|
||||
|
||||
if (PhabricatorApplication::isClassInstalledForViewer($class, $viewer)) {
|
||||
$badge_phids = $user->getBadgePHIDs();
|
||||
if ($badge_phids) {
|
||||
$badges = id(new PhabricatorBadgesQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs($badge_phids)
|
||||
->withStatuses(array(PhabricatorBadgesBadge::STATUS_ACTIVE))
|
||||
->execute();
|
||||
|
||||
foreach ($badges as $badge) {
|
||||
$items[] = id(new PHUIBadgeMiniView())
|
||||
->setIcon($badge->getIcon())
|
||||
->setHeader($badge->getName())
|
||||
->setQuality($badge->getQuality());
|
||||
}
|
||||
}
|
||||
}
|
||||
return $items;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
151
src/applications/people/view/PhabricatorUserCardView.php
Normal file
151
src/applications/people/view/PhabricatorUserCardView.php
Normal file
|
@ -0,0 +1,151 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorUserCardView extends AphrontTagView {
|
||||
|
||||
private $profile;
|
||||
private $viewer;
|
||||
private $tag;
|
||||
|
||||
public function setProfile(PhabricatorUser $profile) {
|
||||
$this->profile = $profile;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setViewer(PhabricatorUser $viewer) {
|
||||
$this->viewer = $viewer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setTag($tag) {
|
||||
$this->tag = $tag;
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function getTagName() {
|
||||
if ($this->tag) {
|
||||
return $this->tag;
|
||||
}
|
||||
return 'div';
|
||||
}
|
||||
|
||||
protected function getTagAttributes() {
|
||||
$classes = array();
|
||||
$classes[] = 'project-card-view';
|
||||
|
||||
if ($this->profile->getIsDisabled()) {
|
||||
$classes[] = 'project-card-grey';
|
||||
} else {
|
||||
$classes[] = 'project-card-blue';
|
||||
}
|
||||
|
||||
return array(
|
||||
'class' => implode($classes, ' '),
|
||||
);
|
||||
}
|
||||
|
||||
protected function getTagContent() {
|
||||
|
||||
$user = $this->profile;
|
||||
$profile = $user->loadUserProfile();
|
||||
$picture = $user->getProfileImageURI();
|
||||
$viewer = $this->viewer;
|
||||
|
||||
require_celerity_resource('project-card-view-css');
|
||||
|
||||
$profile_icon = PhabricatorPeopleIconSet::getIconIcon($profile->getIcon());
|
||||
$profile_title = $profile->getDisplayTitle();
|
||||
|
||||
$tag = id(new PHUITagView())
|
||||
->setIcon($profile_icon)
|
||||
->setName($profile_title)
|
||||
->addClass('project-view-header-tag')
|
||||
->setType(PHUITagView::TYPE_SHADE);
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader(array($user->getFullName(), $tag))
|
||||
->setUser($viewer)
|
||||
->setImage($picture);
|
||||
|
||||
$body = array();
|
||||
|
||||
$body[] = $this->addItem(
|
||||
pht('User Since'),
|
||||
phabricator_date($profile->getDateCreated(), $viewer));
|
||||
|
||||
if (PhabricatorApplication::isClassInstalledForViewer(
|
||||
'PhabricatorCalendarApplication',
|
||||
$viewer)) {
|
||||
$availability = $user->getAvailabilityDescription($viewer);
|
||||
$body[] = $this->addItem(pht('Status'), $availability);
|
||||
}
|
||||
|
||||
$badges = $this->buildBadges($user, $viewer);
|
||||
if ($badges) {
|
||||
$badges = id(new PHUIBadgeBoxView())
|
||||
->addItems($badges)
|
||||
->setCollapsed(true);
|
||||
$body[] = phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'phui-hovercard-body-item hovercard-badges',
|
||||
),
|
||||
$badges);
|
||||
}
|
||||
|
||||
$body = phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'project-card-body',
|
||||
),
|
||||
$body);
|
||||
|
||||
$card = phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'project-card-inner',
|
||||
),
|
||||
array(
|
||||
$header,
|
||||
$body,
|
||||
));
|
||||
|
||||
return $card;
|
||||
}
|
||||
|
||||
private function addItem($label, $value) {
|
||||
$item = array(
|
||||
phutil_tag('strong', array(), $label),
|
||||
': ',
|
||||
phutil_tag('span', array(), $value),
|
||||
);
|
||||
return phutil_tag_div('project-card-item', $item);
|
||||
}
|
||||
|
||||
private function buildBadges(
|
||||
PhabricatorUser $user,
|
||||
$viewer) {
|
||||
|
||||
$class = 'PhabricatorBadgesApplication';
|
||||
$items = array();
|
||||
|
||||
if (PhabricatorApplication::isClassInstalledForViewer($class, $viewer)) {
|
||||
$badge_phids = $user->getBadgePHIDs();
|
||||
if ($badge_phids) {
|
||||
$badges = id(new PhabricatorBadgesQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs($badge_phids)
|
||||
->withStatuses(array(PhabricatorBadgesBadge::STATUS_ACTIVE))
|
||||
->execute();
|
||||
|
||||
foreach ($badges as $badge) {
|
||||
$items[] = id(new PHUIBadgeMiniView())
|
||||
->setIcon($badge->getIcon())
|
||||
->setHeader($badge->getName())
|
||||
->setQuality($badge->getQuality());
|
||||
}
|
||||
}
|
||||
}
|
||||
return $items;
|
||||
}
|
||||
|
||||
}
|
|
@ -230,6 +230,10 @@ final class PhabricatorPolicyQuery
|
|||
->setViewer($viewer)
|
||||
->withMemberPHIDs(array($viewer->getPHID()))
|
||||
->withIsMilestone(false)
|
||||
->withStatuses(
|
||||
array(
|
||||
PhabricatorProjectStatus::STATUS_ACTIVE,
|
||||
))
|
||||
->setLimit($default_limit)
|
||||
->execute();
|
||||
$default_projects = mpull($default_projects, null, 'getPHID');
|
||||
|
|
|
@ -65,13 +65,12 @@ final class PhabricatorProjectApplication extends PhabricatorApplication {
|
|||
=> $this->getPanelRouting('PhabricatorProjectPanelController'),
|
||||
'subprojects/(?P<id>[1-9]\d*)/'
|
||||
=> 'PhabricatorProjectSubprojectsController',
|
||||
'milestones/(?P<id>[1-9]\d*)/'
|
||||
=> 'PhabricatorProjectMilestonesController',
|
||||
'board/(?P<id>[1-9]\d*)/'.
|
||||
'(?P<filter>filter/)?'.
|
||||
'(?:query/(?P<queryKey>[^/]+)/)?'
|
||||
=> 'PhabricatorProjectBoardViewController',
|
||||
'move/(?P<id>[1-9]\d*)/' => 'PhabricatorProjectMoveController',
|
||||
'cover/' => 'PhabricatorProjectCoverController',
|
||||
'board/(?P<projectID>[1-9]\d*)/' => array(
|
||||
'edit/(?:(?P<id>\d+)/)?'
|
||||
=> 'PhabricatorProjectColumnEditController',
|
||||
|
@ -83,6 +82,8 @@ final class PhabricatorProjectApplication extends PhabricatorApplication {
|
|||
=> 'PhabricatorProjectBoardImportController',
|
||||
'reorder/'
|
||||
=> 'PhabricatorProjectBoardReorderController',
|
||||
'disable/'
|
||||
=> 'PhabricatorProjectBoardDisableController',
|
||||
),
|
||||
'update/(?P<id>[1-9]\d*)/(?P<action>[^/]+)/'
|
||||
=> 'PhabricatorProjectUpdateController',
|
||||
|
@ -93,6 +94,8 @@ final class PhabricatorProjectApplication extends PhabricatorApplication {
|
|||
=> 'PhabricatorProjectSilenceController',
|
||||
'warning/(?P<id>[1-9]\d*)/'
|
||||
=> 'PhabricatorProjectSubprojectWarningController',
|
||||
'default/(?P<projectID>[1-9]\d*)/(?P<target>[^/]+)/'
|
||||
=> 'PhabricatorProjectDefaultController',
|
||||
),
|
||||
'/tag/' => array(
|
||||
'(?P<slug>[^/]+)/' => 'PhabricatorProjectViewController',
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorProjectBoardDisableController
|
||||
extends PhabricatorProjectBoardController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getUser();
|
||||
$project_id = $request->getURIData('projectID');
|
||||
|
||||
$project = id(new PhabricatorProjectQuery())
|
||||
->setViewer($viewer)
|
||||
->requireCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
))
|
||||
->withIDs(array($project_id))
|
||||
->executeOne();
|
||||
if (!$project) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
if (!$project->getHasWorkboard()) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$this->setProject($project);
|
||||
$id = $project->getID();
|
||||
|
||||
$board_uri = $this->getApplicationURI("board/{$id}/");
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
$xactions = array();
|
||||
|
||||
$xactions[] = id(new PhabricatorProjectTransaction())
|
||||
->setTransactionType(PhabricatorProjectTransaction::TYPE_HASWORKBOARD)
|
||||
->setNewValue(0);
|
||||
|
||||
id(new PhabricatorProjectTransactionEditor())
|
||||
->setActor($viewer)
|
||||
->setContentSourceFromRequest($request)
|
||||
->setContinueOnNoEffect(true)
|
||||
->setContinueOnMissingFields(true)
|
||||
->applyTransactions($project, $xactions);
|
||||
|
||||
return id(new AphrontRedirectResponse())
|
||||
->setURI($board_uri);
|
||||
}
|
||||
|
||||
return $this->newDialog()
|
||||
->setTitle(pht('Disable Workboard'))
|
||||
->appendParagraph(
|
||||
pht(
|
||||
'Disabling a workboard hides the board. Objects on the board '.
|
||||
'will no longer be annotated with column names in other '.
|
||||
'applications. You can restore the workboard later.'))
|
||||
->addCancelButton($board_uri)
|
||||
->addSubmitButton(pht('Disable Workboard'));
|
||||
}
|
||||
|
||||
}
|
|
@ -50,6 +50,10 @@ final class PhabricatorProjectBoardImportController
|
|||
if ($import_column->isHidden()) {
|
||||
continue;
|
||||
}
|
||||
if ($import_column->getProxy()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$new_column = PhabricatorProjectColumn::initializeNewColumn($viewer)
|
||||
->setSequence($import_column->getSequence())
|
||||
->setProjectPHID($project->getPHID())
|
||||
|
|
|
@ -7,9 +7,7 @@ final class PhabricatorProjectBoardViewController
|
|||
|
||||
private $id;
|
||||
private $slug;
|
||||
private $handles;
|
||||
private $queryKey;
|
||||
private $filter;
|
||||
private $sortKey;
|
||||
private $showHidden;
|
||||
|
||||
|
@ -57,10 +55,18 @@ final class PhabricatorProjectBoardViewController
|
|||
$search_engine->getQueryResultsPageURI($saved->getQueryKey())));
|
||||
}
|
||||
|
||||
$query_key = $request->getURIData('queryKey');
|
||||
if (!$query_key) {
|
||||
$query_key = 'open';
|
||||
$query_key = $this->getDefaultFilter($project);
|
||||
|
||||
$request_query = $request->getStr('filter');
|
||||
if (strlen($request_query)) {
|
||||
$query_key = $request_query;
|
||||
}
|
||||
|
||||
$uri_query = $request->getURIData('queryKey');
|
||||
if (strlen($uri_query)) {
|
||||
$query_key = $uri_query;
|
||||
}
|
||||
|
||||
$this->queryKey = $query_key;
|
||||
|
||||
$custom_query = null;
|
||||
|
@ -122,18 +128,37 @@ final class PhabricatorProjectBoardViewController
|
|||
->setViewer($viewer)
|
||||
->setBoardPHIDs(array($board_phid))
|
||||
->setObjectPHIDs(array_keys($tasks))
|
||||
->setFetchAllBoards(true)
|
||||
->executeLayout();
|
||||
|
||||
$columns = $layout_engine->getColumns($board_phid);
|
||||
if (!$columns) {
|
||||
if (!$columns || !$project->getHasWorkboard()) {
|
||||
$has_normal_columns = false;
|
||||
|
||||
foreach ($columns as $column) {
|
||||
if (!$column->getProxyPHID()) {
|
||||
$has_normal_columns = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||
$viewer,
|
||||
$project,
|
||||
PhabricatorPolicyCapability::CAN_EDIT);
|
||||
if (!$can_edit) {
|
||||
$content = $this->buildNoAccessContent($project);
|
||||
|
||||
if (!$has_normal_columns) {
|
||||
if (!$can_edit) {
|
||||
$content = $this->buildNoAccessContent($project);
|
||||
} else {
|
||||
$content = $this->buildInitializeContent($project);
|
||||
}
|
||||
} else {
|
||||
$content = $this->buildInitializeContent($project);
|
||||
if (!$can_edit) {
|
||||
$content = $this->buildDisabledContent($project);
|
||||
} else {
|
||||
$content = $this->buildEnableContent($project);
|
||||
}
|
||||
}
|
||||
|
||||
if ($content instanceof AphrontResponse) {
|
||||
|
@ -213,35 +238,16 @@ final class PhabricatorProjectBoardViewController
|
|||
|
||||
$board = id(new PHUIWorkboardView())
|
||||
->setUser($viewer)
|
||||
->setID($board_id);
|
||||
|
||||
$behavior_config = array(
|
||||
'boardID' => $board_id,
|
||||
'projectPHID' => $project->getPHID(),
|
||||
'moveURI' => $this->getApplicationURI('move/'.$project->getID().'/'),
|
||||
'createURI' => $this->getCreateURI(),
|
||||
'order' => $this->sortKey,
|
||||
);
|
||||
$this->initBehavior(
|
||||
'project-boards',
|
||||
$behavior_config);
|
||||
|
||||
$this->handles = ManiphestTaskListView::loadTaskHandles($viewer, $tasks);
|
||||
|
||||
$all_project_phids = array();
|
||||
foreach ($tasks as $task) {
|
||||
foreach ($task->getProjectPHIDs() as $project_phid) {
|
||||
$all_project_phids[$project_phid] = $project_phid;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($select_phids as $phid) {
|
||||
unset($all_project_phids[$phid]);
|
||||
}
|
||||
|
||||
$all_handles = $viewer->loadHandles($all_project_phids);
|
||||
$all_handles = iterator_to_array($all_handles);
|
||||
->setID($board_id)
|
||||
->addSigil('jx-workboard')
|
||||
->setMetadata(
|
||||
array(
|
||||
'boardPHID' => $project->getPHID(),
|
||||
));
|
||||
|
||||
$visible_columns = array();
|
||||
$column_phids = array();
|
||||
$visible_phids = array();
|
||||
foreach ($columns as $column) {
|
||||
if (!$this->showHidden) {
|
||||
if ($column->isHidden()) {
|
||||
|
@ -268,11 +274,40 @@ final class PhabricatorProjectBoardViewController
|
|||
$column_tasks = array_select_keys($column_tasks, array_keys($tasks));
|
||||
}
|
||||
|
||||
$column_phid = $column->getPHID();
|
||||
|
||||
$visible_columns[$column_phid] = $column;
|
||||
$column_phids[$column_phid] = $column_tasks;
|
||||
|
||||
foreach ($column_tasks as $phid => $task) {
|
||||
$visible_phids[$phid] = $phid;
|
||||
}
|
||||
}
|
||||
|
||||
$rendering_engine = id(new PhabricatorBoardRenderingEngine())
|
||||
->setViewer($viewer)
|
||||
->setObjects(array_select_keys($tasks, $visible_phids))
|
||||
->setEditMap($task_can_edit_map)
|
||||
->setExcludedProjectPHIDs($select_phids);
|
||||
|
||||
$templates = array();
|
||||
$column_maps = array();
|
||||
$all_tasks = array();
|
||||
foreach ($visible_columns as $column_phid => $column) {
|
||||
$column_tasks = $column_phids[$column_phid];
|
||||
|
||||
$panel = id(new PHUIWorkpanelView())
|
||||
->setHeader($column->getDisplayName())
|
||||
->setSubHeader($column->getDisplayType())
|
||||
->addSigil('workpanel');
|
||||
|
||||
$proxy = $column->getProxy();
|
||||
if ($proxy) {
|
||||
$proxy_id = $proxy->getID();
|
||||
$href = $this->getApplicationURI("view/{$proxy_id}/");
|
||||
$panel->setHref($href);
|
||||
}
|
||||
|
||||
$header_icon = $column->getHeaderIcon();
|
||||
if ($header_icon) {
|
||||
$panel->setHeaderIcon($header_icon);
|
||||
|
@ -290,14 +325,17 @@ final class PhabricatorProjectBoardViewController
|
|||
$column_menu = $this->buildColumnMenu($project, $column);
|
||||
$panel->addHeaderAction($column_menu);
|
||||
|
||||
$tag_id = celerity_generate_unique_node_id();
|
||||
$tag_content_id = celerity_generate_unique_node_id();
|
||||
|
||||
$count_tag = id(new PHUITagView())
|
||||
->setType(PHUITagView::TYPE_SHADE)
|
||||
->setShade(PHUITagView::COLOR_BLUE)
|
||||
->setID($tag_id)
|
||||
->setName(phutil_tag('span', array('id' => $tag_content_id), '-'))
|
||||
->addSigil('column-points')
|
||||
->setName(
|
||||
javelin_tag(
|
||||
'span',
|
||||
array(
|
||||
'sigil' => 'column-points-content',
|
||||
),
|
||||
pht('-')))
|
||||
->setStyle('display: none');
|
||||
|
||||
$panel->setHeaderTag($count_tag);
|
||||
|
@ -311,38 +349,52 @@ final class PhabricatorProjectBoardViewController
|
|||
->setMetadata(
|
||||
array(
|
||||
'columnPHID' => $column->getPHID(),
|
||||
'countTagID' => $tag_id,
|
||||
'countTagContentID' => $tag_content_id,
|
||||
'pointLimit' => $column->getPointLimit(),
|
||||
));
|
||||
|
||||
foreach ($column_tasks as $task) {
|
||||
$owner = null;
|
||||
if ($task->getOwnerPHID()) {
|
||||
$owner = $this->handles[$task->getOwnerPHID()];
|
||||
}
|
||||
$can_edit = idx($task_can_edit_map, $task->getPHID(), false);
|
||||
$object_phid = $task->getPHID();
|
||||
|
||||
$handles = array_select_keys($all_handles, $task->getProjectPHIDs());
|
||||
$card = $rendering_engine->renderCard($object_phid);
|
||||
$templates[$object_phid] = hsprintf('%s', $card->getItem());
|
||||
$column_maps[$column_phid][] = $object_phid;
|
||||
|
||||
$cards->addItem(id(new ProjectBoardTaskCard())
|
||||
->setViewer($viewer)
|
||||
->setProjectHandles($handles)
|
||||
->setTask($task)
|
||||
->setOwner($owner)
|
||||
->setCanEdit($can_edit)
|
||||
->getItem());
|
||||
$all_tasks[$object_phid] = $task;
|
||||
}
|
||||
|
||||
$panel->setCards($cards);
|
||||
$board->addPanel($panel);
|
||||
}
|
||||
|
||||
$behavior_config = array(
|
||||
'moveURI' => $this->getApplicationURI('move/'.$project->getID().'/'),
|
||||
'createURI' => $this->getCreateURI(),
|
||||
'uploadURI' => '/file/dropupload/',
|
||||
'coverURI' => $this->getApplicationURI('cover/'),
|
||||
'chunkThreshold' => PhabricatorFileStorageEngine::getChunkThreshold(),
|
||||
'pointsEnabled' => ManiphestTaskPoints::getIsEnabled(),
|
||||
|
||||
'boardPHID' => $project->getPHID(),
|
||||
'order' => $this->sortKey,
|
||||
'templateMap' => $templates,
|
||||
'columnMaps' => $column_maps,
|
||||
'orderMaps' => mpull($all_tasks, 'getWorkboardOrderVectors'),
|
||||
'propertyMaps' => mpull($all_tasks, 'getWorkboardProperties'),
|
||||
|
||||
'boardID' => $board_id,
|
||||
'projectPHID' => $project->getPHID(),
|
||||
);
|
||||
$this->initBehavior('project-boards', $behavior_config);
|
||||
|
||||
|
||||
$sort_menu = $this->buildSortMenu(
|
||||
$viewer,
|
||||
$project,
|
||||
$this->sortKey);
|
||||
|
||||
$filter_menu = $this->buildFilterMenu(
|
||||
$viewer,
|
||||
$project,
|
||||
$custom_query,
|
||||
$search_engine,
|
||||
$query_key);
|
||||
|
@ -361,6 +413,9 @@ final class PhabricatorProjectBoardViewController
|
|||
->addClass('project-board-wrapper');
|
||||
|
||||
$nav = $this->getProfileMenu();
|
||||
$divider = id(new PHUIListItemView())
|
||||
->setType(PHUIListItemView::TYPE_DIVIDER);
|
||||
$fullscreen = $this->buildFullscreenMenu();
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->addTextCrumb(pht('Workboard'));
|
||||
|
@ -368,7 +423,9 @@ final class PhabricatorProjectBoardViewController
|
|||
|
||||
$crumbs->addAction($sort_menu);
|
||||
$crumbs->addAction($filter_menu);
|
||||
$crumbs->addAction($divider);
|
||||
$crumbs->addAction($manage_menu);
|
||||
$crumbs->addAction($fullscreen);
|
||||
|
||||
return $this->newPage()
|
||||
->setTitle(
|
||||
|
@ -397,20 +454,49 @@ final class PhabricatorProjectBoardViewController
|
|||
$this->showHidden = $request->getBool('hidden');
|
||||
$this->id = $project->getID();
|
||||
|
||||
$sort_key = $request->getStr('order');
|
||||
switch ($sort_key) {
|
||||
$sort_key = $this->getDefaultSort($project);
|
||||
|
||||
$request_sort = $request->getStr('order');
|
||||
if ($this->isValidSort($request_sort)) {
|
||||
$sort_key = $request_sort;
|
||||
}
|
||||
|
||||
$this->sortKey = $sort_key;
|
||||
}
|
||||
|
||||
private function getDefaultSort(PhabricatorProject $project) {
|
||||
$default_sort = $project->getDefaultWorkboardSort();
|
||||
|
||||
if ($this->isValidSort($default_sort)) {
|
||||
return $default_sort;
|
||||
}
|
||||
|
||||
return PhabricatorProjectColumn::DEFAULT_ORDER;
|
||||
}
|
||||
|
||||
private function getDefaultFilter(PhabricatorProject $project) {
|
||||
$default_filter = $project->getDefaultWorkboardFilter();
|
||||
|
||||
if (strlen($default_filter)) {
|
||||
return $default_filter;
|
||||
}
|
||||
|
||||
return 'open';
|
||||
}
|
||||
|
||||
private function isValidSort($sort) {
|
||||
switch ($sort) {
|
||||
case PhabricatorProjectColumn::ORDER_NATURAL:
|
||||
case PhabricatorProjectColumn::ORDER_PRIORITY:
|
||||
break;
|
||||
default:
|
||||
$sort_key = PhabricatorProjectColumn::DEFAULT_ORDER;
|
||||
break;
|
||||
return true;
|
||||
}
|
||||
$this->sortKey = $sort_key;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function buildSortMenu(
|
||||
PhabricatorUser $viewer,
|
||||
PhabricatorProject $project,
|
||||
$sort_key) {
|
||||
|
||||
$sort_icon = id(new PHUIIconView())
|
||||
|
@ -441,6 +527,24 @@ final class PhabricatorProjectBoardViewController
|
|||
$items[] = $item;
|
||||
}
|
||||
|
||||
$id = $project->getID();
|
||||
|
||||
$save_uri = "default/{$id}/sort/";
|
||||
$save_uri = $this->getApplicationURI($save_uri);
|
||||
$save_uri = $this->getURIWithState($save_uri, $force = true);
|
||||
|
||||
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||
$viewer,
|
||||
$project,
|
||||
PhabricatorPolicyCapability::CAN_EDIT);
|
||||
|
||||
$items[] = id(new PhabricatorActionView())
|
||||
->setIcon('fa-floppy-o')
|
||||
->setName(pht('Save as Default'))
|
||||
->setHref($save_uri)
|
||||
->setWorkflow(true)
|
||||
->setDisabled(!$can_edit);
|
||||
|
||||
$sort_menu = id(new PhabricatorActionListView())
|
||||
->setUser($viewer);
|
||||
foreach ($items as $item) {
|
||||
|
@ -448,7 +552,7 @@ final class PhabricatorProjectBoardViewController
|
|||
}
|
||||
|
||||
$sort_button = id(new PHUIListItemView())
|
||||
->setName(pht('Sort: %s', $active_order))
|
||||
->setName($active_order)
|
||||
->setIcon('fa-sort-amount-asc')
|
||||
->setHref('#')
|
||||
->addSigil('boards-dropdown-menu')
|
||||
|
@ -459,8 +563,10 @@ final class PhabricatorProjectBoardViewController
|
|||
|
||||
return $sort_button;
|
||||
}
|
||||
|
||||
private function buildFilterMenu(
|
||||
PhabricatorUser $viewer,
|
||||
PhabricatorProject $project,
|
||||
$custom_query,
|
||||
PhabricatorApplicationSearchEngine $engine,
|
||||
$query_key) {
|
||||
|
@ -503,18 +609,40 @@ final class PhabricatorProjectBoardViewController
|
|||
$uri = $engine->getQueryResultsPageURI($key);
|
||||
}
|
||||
|
||||
$uri = $this->getURIWithState($uri);
|
||||
$uri = $this->getURIWithState($uri)
|
||||
->setQueryParam('filter', null);
|
||||
$item->setHref($uri);
|
||||
|
||||
$items[] = $item;
|
||||
}
|
||||
|
||||
$id = $project->getID();
|
||||
|
||||
$filter_uri = $this->getApplicationURI("board/{$id}/filter/");
|
||||
$filter_uri = $this->getURIWithState($filter_uri, $force = true);
|
||||
|
||||
$items[] = id(new PhabricatorActionView())
|
||||
->setIcon('fa-cog')
|
||||
->setHref($this->getApplicationURI('board/'.$this->id.'/filter/'))
|
||||
->setHref($filter_uri)
|
||||
->setWorkflow(true)
|
||||
->setName(pht('Advanced Filter...'));
|
||||
|
||||
$save_uri = "default/{$id}/filter/";
|
||||
$save_uri = $this->getApplicationURI($save_uri);
|
||||
$save_uri = $this->getURIWithState($save_uri, $force = true);
|
||||
|
||||
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||
$viewer,
|
||||
$project,
|
||||
PhabricatorPolicyCapability::CAN_EDIT);
|
||||
|
||||
$items[] = id(new PhabricatorActionView())
|
||||
->setIcon('fa-floppy-o')
|
||||
->setName(pht('Save as Default'))
|
||||
->setHref($save_uri)
|
||||
->setWorkflow(true)
|
||||
->setDisabled(!$can_edit);
|
||||
|
||||
$filter_menu = id(new PhabricatorActionListView())
|
||||
->setUser($viewer);
|
||||
foreach ($items as $item) {
|
||||
|
@ -522,7 +650,7 @@ final class PhabricatorProjectBoardViewController
|
|||
}
|
||||
|
||||
$filter_button = id(new PHUIListItemView())
|
||||
->setName(pht('Filter: %s', $active_filter))
|
||||
->setName($active_filter)
|
||||
->setIcon('fa-search')
|
||||
->setHref('#')
|
||||
->addSigil('boards-dropdown-menu')
|
||||
|
@ -541,6 +669,12 @@ final class PhabricatorProjectBoardViewController
|
|||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
|
||||
$id = $project->getID();
|
||||
|
||||
$disable_uri = $this->getApplicationURI("board/{$id}/disable/");
|
||||
$add_uri = $this->getApplicationURI("board/{$id}/edit/");
|
||||
$reorder_uri = $this->getApplicationURI("board/{$id}/reorder/");
|
||||
|
||||
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||
$viewer,
|
||||
$project,
|
||||
|
@ -551,14 +685,14 @@ final class PhabricatorProjectBoardViewController
|
|||
$manage_items[] = id(new PhabricatorActionView())
|
||||
->setIcon('fa-plus')
|
||||
->setName(pht('Add Column'))
|
||||
->setHref($this->getApplicationURI('board/'.$this->id.'/edit/'))
|
||||
->setHref($add_uri)
|
||||
->setDisabled(!$can_edit)
|
||||
->setWorkflow(!$can_edit);
|
||||
|
||||
$manage_items[] = id(new PhabricatorActionView())
|
||||
->setIcon('fa-exchange')
|
||||
->setName(pht('Reorder Columns'))
|
||||
->setHref($this->getApplicationURI('board/'.$this->id.'/reorder/'))
|
||||
->setHref($reorder_uri)
|
||||
->setDisabled(!$can_edit)
|
||||
->setWorkflow(true);
|
||||
|
||||
|
@ -592,6 +726,13 @@ final class PhabricatorProjectBoardViewController
|
|||
->setHref($batch_edit_uri)
|
||||
->setDisabled(!$can_batch_edit);
|
||||
|
||||
$manage_items[] = id(new PhabricatorActionView())
|
||||
->setIcon('fa-ban')
|
||||
->setName(pht('Disable Workboard'))
|
||||
->setHref($disable_uri)
|
||||
->setWorkflow(true)
|
||||
->setDisabled(!$can_edit);
|
||||
|
||||
$manage_menu = id(new PhabricatorActionListView())
|
||||
->setUser($viewer);
|
||||
foreach ($manage_items as $item) {
|
||||
|
@ -599,18 +740,38 @@ final class PhabricatorProjectBoardViewController
|
|||
}
|
||||
|
||||
$manage_button = id(new PHUIListItemView())
|
||||
->setName(pht('Manage Board'))
|
||||
->setIcon('fa-cog')
|
||||
->setHref('#')
|
||||
->addSigil('boards-dropdown-menu')
|
||||
->addSigil('has-tooltip')
|
||||
->setMetadata(
|
||||
array(
|
||||
'tip' => pht('Manage'),
|
||||
'align' => 'S',
|
||||
'items' => hsprintf('%s', $manage_menu),
|
||||
));
|
||||
|
||||
return $manage_button;
|
||||
}
|
||||
|
||||
private function buildFullscreenMenu() {
|
||||
|
||||
$up = id(new PHUIListItemView())
|
||||
->setIcon('fa-arrows-alt')
|
||||
->setHref('#')
|
||||
->addClass('phui-workboard-expand-icon')
|
||||
->addSigil('jx-toggle-class')
|
||||
->addSigil('has-tooltip')
|
||||
->setMetaData(array(
|
||||
'tip' => pht('Fullscreen'),
|
||||
'map' => array(
|
||||
'phabricator-standard-page' => 'phui-workboard-fullscreen',
|
||||
),
|
||||
));
|
||||
|
||||
return $up;
|
||||
}
|
||||
|
||||
private function buildColumnMenu(
|
||||
PhabricatorProject $project,
|
||||
PhabricatorProjectColumn $column) {
|
||||
|
@ -639,6 +800,7 @@ final class PhabricatorProjectBoardViewController
|
|||
->setMetadata(
|
||||
array(
|
||||
'columnPHID' => $column->getPHID(),
|
||||
'boardPHID' => $project->getPHID(),
|
||||
'projectPHID' => $default_phid,
|
||||
));
|
||||
|
||||
|
@ -711,22 +873,31 @@ final class PhabricatorProjectBoardViewController
|
|||
* the rest of the board state persistent. If no URI is provided, this method
|
||||
* starts with the request URI.
|
||||
*
|
||||
* @param string|null URI to add state parameters to.
|
||||
* @return PhutilURI URI with state parameters.
|
||||
* @param string|null URI to add state parameters to.
|
||||
* @param bool True to explicitly include all state.
|
||||
* @return PhutilURI URI with state parameters.
|
||||
*/
|
||||
private function getURIWithState($base = null) {
|
||||
private function getURIWithState($base = null, $force = false) {
|
||||
$project = $this->getProject();
|
||||
|
||||
if ($base === null) {
|
||||
$base = $this->getRequest()->getRequestURI();
|
||||
}
|
||||
|
||||
$base = new PhutilURI($base);
|
||||
|
||||
if ($this->sortKey != PhabricatorProjectColumn::DEFAULT_ORDER) {
|
||||
if ($force || ($this->sortKey != $this->getDefaultSort($project))) {
|
||||
$base->setQueryParam('order', $this->sortKey);
|
||||
} else {
|
||||
$base->setQueryParam('order', null);
|
||||
}
|
||||
|
||||
if ($force || ($this->queryKey != $this->getDefaultFilter($project))) {
|
||||
$base->setQueryParam('filter', $this->queryKey);
|
||||
} else {
|
||||
$base->setQueryParam('filter', null);
|
||||
}
|
||||
|
||||
$base->setQueryParam('hidden', $this->showHidden ? 'true' : null);
|
||||
|
||||
return $base;
|
||||
|
@ -849,4 +1020,59 @@ final class PhabricatorProjectBoardViewController
|
|||
->addCancelButton($profile_uri);
|
||||
}
|
||||
|
||||
|
||||
private function buildEnableContent(PhabricatorProject $project) {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$id = $project->getID();
|
||||
$profile_uri = $this->getApplicationURI("profile/{$id}/");
|
||||
$board_uri = $this->getApplicationURI("board/{$id}/");
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
$xactions = array();
|
||||
|
||||
$xactions[] = id(new PhabricatorProjectTransaction())
|
||||
->setTransactionType(PhabricatorProjectTransaction::TYPE_HASWORKBOARD)
|
||||
->setNewValue(1);
|
||||
|
||||
id(new PhabricatorProjectTransactionEditor())
|
||||
->setActor($viewer)
|
||||
->setContentSourceFromRequest($request)
|
||||
->setContinueOnNoEffect(true)
|
||||
->setContinueOnMissingFields(true)
|
||||
->applyTransactions($project, $xactions);
|
||||
|
||||
return id(new AphrontRedirectResponse())
|
||||
->setURI($board_uri);
|
||||
}
|
||||
|
||||
return $this->newDialog()
|
||||
->setTitle(pht('Workboard Disabled'))
|
||||
->addHiddenInput('initialize', 1)
|
||||
->appendParagraph(
|
||||
pht(
|
||||
'This workboard has been disabled, but can be restored to its '.
|
||||
'former glory.'))
|
||||
->addCancelButton($profile_uri)
|
||||
->addSubmitButton(pht('Enable Workboard'));
|
||||
}
|
||||
|
||||
private function buildDisabledContent(PhabricatorProject $project) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$id = $project->getID();
|
||||
|
||||
$profile_uri = $this->getApplicationURI("profile/{$id}/");
|
||||
|
||||
return $this->newDialog()
|
||||
->setTitle(pht('Workboard Disabled'))
|
||||
->appendParagraph(
|
||||
pht(
|
||||
'This workboard has been disabled, and you do not have permission '.
|
||||
'to enable it. Only users who can edit this project can restore '.
|
||||
'the workboard.'))
|
||||
->addCancelButton($profile_uri);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -120,9 +120,12 @@ final class PhabricatorProjectColumnDetailController
|
|||
->setActionList($actions);
|
||||
|
||||
$limit = $column->getPointLimit();
|
||||
$properties->addProperty(
|
||||
pht('Point Limit'),
|
||||
$limit ? $limit : pht('No Limit'));
|
||||
if ($limit === null) {
|
||||
$limit_text = pht('No Limit');
|
||||
} else {
|
||||
$limit_text = $limit;
|
||||
}
|
||||
$properties->addProperty(pht('Point Limit'), $limit_text);
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
|
|
@ -52,42 +52,81 @@ final class PhabricatorProjectColumnHideController
|
|||
->addCancelButton($view_uri, pht('Okay'));
|
||||
}
|
||||
|
||||
$proxy = $column->getProxy();
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
if ($column->isHidden()) {
|
||||
$new_status = PhabricatorProjectColumn::STATUS_ACTIVE;
|
||||
if ($proxy) {
|
||||
if ($proxy->isArchived()) {
|
||||
$new_status = PhabricatorProjectStatus::STATUS_ACTIVE;
|
||||
} else {
|
||||
$new_status = PhabricatorProjectStatus::STATUS_ARCHIVED;
|
||||
}
|
||||
|
||||
$xactions = array();
|
||||
|
||||
$xactions[] = id(new PhabricatorProjectTransaction())
|
||||
->setTransactionType(PhabricatorProjectTransaction::TYPE_STATUS)
|
||||
->setNewValue($new_status);
|
||||
|
||||
id(new PhabricatorProjectTransactionEditor())
|
||||
->setActor($viewer)
|
||||
->setContentSourceFromRequest($request)
|
||||
->setContinueOnNoEffect(true)
|
||||
->setContinueOnMissingFields(true)
|
||||
->applyTransactions($proxy, $xactions);
|
||||
} else {
|
||||
$new_status = PhabricatorProjectColumn::STATUS_HIDDEN;
|
||||
if ($column->isHidden()) {
|
||||
$new_status = PhabricatorProjectColumn::STATUS_ACTIVE;
|
||||
} else {
|
||||
$new_status = PhabricatorProjectColumn::STATUS_HIDDEN;
|
||||
}
|
||||
|
||||
$type_status = PhabricatorProjectColumnTransaction::TYPE_STATUS;
|
||||
$xactions = array(
|
||||
id(new PhabricatorProjectColumnTransaction())
|
||||
->setTransactionType($type_status)
|
||||
->setNewValue($new_status),
|
||||
);
|
||||
|
||||
$editor = id(new PhabricatorProjectColumnTransactionEditor())
|
||||
->setActor($viewer)
|
||||
->setContinueOnNoEffect(true)
|
||||
->setContinueOnMissingFields(true)
|
||||
->setContentSourceFromRequest($request)
|
||||
->applyTransactions($column, $xactions);
|
||||
}
|
||||
|
||||
$type_status = PhabricatorProjectColumnTransaction::TYPE_STATUS;
|
||||
$xactions = array(
|
||||
id(new PhabricatorProjectColumnTransaction())
|
||||
->setTransactionType($type_status)
|
||||
->setNewValue($new_status),
|
||||
);
|
||||
|
||||
$editor = id(new PhabricatorProjectColumnTransactionEditor())
|
||||
->setActor($viewer)
|
||||
->setContinueOnNoEffect(true)
|
||||
->setContentSourceFromRequest($request)
|
||||
->applyTransactions($column, $xactions);
|
||||
|
||||
return id(new AphrontRedirectResponse())->setURI($view_uri);
|
||||
}
|
||||
|
||||
if ($column->isHidden()) {
|
||||
$title = pht('Show Column');
|
||||
if ($proxy) {
|
||||
if ($column->isHidden()) {
|
||||
$title = pht('Activate and Show Column');
|
||||
$body = pht(
|
||||
'This column is hidden because it represents an archived '.
|
||||
'subproject. Do you want to activate the subproject so the '.
|
||||
'column is visible again?');
|
||||
$button = pht('Activate Subproject');
|
||||
} else {
|
||||
$title = pht('Archive and Hide Column');
|
||||
$body = pht(
|
||||
'This column is visible because it represents an active '.
|
||||
'subproject. Do you want to hide the column by archiving the '.
|
||||
'subproject?');
|
||||
$button = pht('Archive Subproject');
|
||||
}
|
||||
} else {
|
||||
$title = pht('Hide Column');
|
||||
}
|
||||
|
||||
if ($column->isHidden()) {
|
||||
$body = pht(
|
||||
'Are you sure you want to show this column?');
|
||||
} else {
|
||||
$body = pht(
|
||||
'Are you sure you want to hide this column? It will no longer '.
|
||||
'appear on the workboard.');
|
||||
if ($column->isHidden()) {
|
||||
$title = pht('Show Column');
|
||||
$body = pht('Are you sure you want to show this column?');
|
||||
$button = pht('Show Column');
|
||||
} else {
|
||||
$title = pht('Hide Column');
|
||||
$body = pht(
|
||||
'Are you sure you want to hide this column? It will no longer '.
|
||||
'appear on the workboard.');
|
||||
$button = pht('Hide Column');
|
||||
}
|
||||
}
|
||||
|
||||
$dialog = $this->newDialog()
|
||||
|
@ -96,7 +135,7 @@ final class PhabricatorProjectColumnHideController
|
|||
->appendChild($body)
|
||||
->setDisableWorkflowOnCancel(true)
|
||||
->addCancelButton($view_uri)
|
||||
->addSubmitButton($title);
|
||||
->addSubmitButton($button);
|
||||
|
||||
foreach ($request->getPassthroughRequestData() as $key => $value) {
|
||||
$dialog->addHiddenInput($key, $value);
|
||||
|
|
|
@ -147,4 +147,21 @@ abstract class PhabricatorProjectController extends PhabricatorController {
|
|||
return $this;
|
||||
}
|
||||
|
||||
protected function newCardResponse($board_phid, $object_phid) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$request = $this->getRequest();
|
||||
$visible_phids = $request->getStrList('visiblePHIDs');
|
||||
if (!$visible_phids) {
|
||||
$visible_phids = array();
|
||||
}
|
||||
|
||||
return id(new PhabricatorBoardResponseEngine())
|
||||
->setViewer($viewer)
|
||||
->setBoardPHID($board_phid)
|
||||
->setObjectPHID($object_phid)
|
||||
->setVisiblePHIDs($visible_phids)
|
||||
->buildResponse();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorProjectCoverController
|
||||
extends PhabricatorProjectController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getViewer();
|
||||
|
||||
$request->validateCSRF();
|
||||
|
||||
$board_phid = $request->getStr('boardPHID');
|
||||
$object_phid = $request->getStr('objectPHID');
|
||||
$file_phid = $request->getStr('filePHID');
|
||||
|
||||
$object = id(new ManiphestTaskQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($object_phid))
|
||||
->requireCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
))
|
||||
->executeOne();
|
||||
if (!$object) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$file = id(new PhabricatorFileQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($file_phid))
|
||||
->executeOne();
|
||||
if (!$file) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$xactions = array();
|
||||
|
||||
$xactions[] = id(new ManiphestTransaction())
|
||||
->setTransactionType(ManiphestTransaction::TYPE_COVER_IMAGE)
|
||||
->setNewValue($file->getPHID());
|
||||
|
||||
$editor = id(new ManiphestTransactionEditor())
|
||||
->setActor($viewer)
|
||||
->setContinueOnMissingFields(true)
|
||||
->setContinueOnNoEffect(true)
|
||||
->setContentSourceFromRequest($request);
|
||||
|
||||
$editor->applyTransactions($object, $xactions);
|
||||
|
||||
return $this->newCardResponse($board_phid, $object_phid);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorProjectDefaultController
|
||||
extends PhabricatorProjectBoardController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getViewer();
|
||||
$project_id = $request->getURIData('projectID');
|
||||
|
||||
$project = id(new PhabricatorProjectQuery())
|
||||
->setViewer($viewer)
|
||||
->requireCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
))
|
||||
->withIDs(array($project_id))
|
||||
->executeOne();
|
||||
if (!$project) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
$this->setProject($project);
|
||||
|
||||
$target = $request->getURIData('target');
|
||||
switch ($target) {
|
||||
case 'filter':
|
||||
$title = pht('Set Board Default Filter');
|
||||
$body = pht(
|
||||
'Make the current filter the new default filter for this board? '.
|
||||
'All users will see the new filter as the default when they view '.
|
||||
'the board.');
|
||||
$button = pht('Save Default Filter');
|
||||
|
||||
$xaction_value = $request->getStr('filter');
|
||||
$xaction_type = PhabricatorProjectTransaction::TYPE_DEFAULT_FILTER;
|
||||
break;
|
||||
case 'sort':
|
||||
$title = pht('Set Board Default Order');
|
||||
$body = pht(
|
||||
'Make the current sort order the new default order for this board? '.
|
||||
'All users will see the new order as the default when they view '.
|
||||
'the board.');
|
||||
$button = pht('Save Default Order');
|
||||
|
||||
$xaction_value = $request->getStr('order');
|
||||
$xaction_type = PhabricatorProjectTransaction::TYPE_DEFAULT_SORT;
|
||||
break;
|
||||
default:
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$id = $project->getID();
|
||||
|
||||
$view_uri = $this->getApplicationURI("board/{$id}/");
|
||||
$view_uri = new PhutilURI($view_uri);
|
||||
foreach ($request->getPassthroughRequestData() as $key => $value) {
|
||||
$view_uri->setQueryParam($key, $value);
|
||||
}
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
$xactions = array();
|
||||
|
||||
$xactions[] = id(new PhabricatorProjectTransaction())
|
||||
->setTransactionType($xaction_type)
|
||||
->setNewValue($xaction_value);
|
||||
|
||||
id(new PhabricatorProjectTransactionEditor())
|
||||
->setActor($viewer)
|
||||
->setContentSourceFromRequest($request)
|
||||
->setContinueOnNoEffect(true)
|
||||
->setContinueOnMissingFields(true)
|
||||
->applyTransactions($project, $xactions);
|
||||
|
||||
return id(new AphrontRedirectResponse())->setURI($view_uri);
|
||||
}
|
||||
|
||||
$dialog = $this->newDialog()
|
||||
->setTitle($title)
|
||||
->appendChild($body)
|
||||
->setDisableWorkflowOnCancel(true)
|
||||
->addCancelButton($view_uri)
|
||||
->addSubmitButton($title);
|
||||
|
||||
foreach ($request->getPassthroughRequestData() as $key => $value) {
|
||||
$dialog->addHiddenInput($key, $value);
|
||||
}
|
||||
|
||||
return $dialog;
|
||||
}
|
||||
}
|
|
@ -7,13 +7,14 @@ final class PhabricatorProjectMoveController
|
|||
$viewer = $request->getViewer();
|
||||
$id = $request->getURIData('id');
|
||||
|
||||
$request->validateCSRF();
|
||||
|
||||
$column_phid = $request->getStr('columnPHID');
|
||||
$object_phid = $request->getStr('objectPHID');
|
||||
$after_phid = $request->getStr('afterPHID');
|
||||
$before_phid = $request->getStr('beforePHID');
|
||||
$order = $request->getStr('order', PhabricatorProjectColumn::DEFAULT_ORDER);
|
||||
|
||||
|
||||
$project = id(new PhabricatorProjectQuery())
|
||||
->setViewer($viewer)
|
||||
->requireCapabilities(
|
||||
|
@ -89,55 +90,13 @@ final class PhabricatorProjectMoveController
|
|||
'projectPHID' => $column->getProjectPHID(),
|
||||
));
|
||||
|
||||
$task_phids = array();
|
||||
if ($after_phid) {
|
||||
$task_phids[] = $after_phid;
|
||||
}
|
||||
if ($before_phid) {
|
||||
$task_phids[] = $before_phid;
|
||||
}
|
||||
|
||||
if ($task_phids && ($order == PhabricatorProjectColumn::ORDER_PRIORITY)) {
|
||||
$tasks = id(new ManiphestTaskQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs($task_phids)
|
||||
->needProjectPHIDs(true)
|
||||
->requireCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
))
|
||||
->execute();
|
||||
if (count($tasks) != count($task_phids)) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
$tasks = mpull($tasks, null, 'getPHID');
|
||||
|
||||
$try = array(
|
||||
array($after_phid, true),
|
||||
array($before_phid, false),
|
||||
);
|
||||
|
||||
$pri = null;
|
||||
$sub = null;
|
||||
foreach ($try as $spec) {
|
||||
list($task_phid, $is_after) = $spec;
|
||||
$task = idx($tasks, $task_phid);
|
||||
if ($task) {
|
||||
list($pri, $sub) = ManiphestTransactionEditor::getAdjacentSubpriority(
|
||||
$task,
|
||||
$is_after);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($pri !== null) {
|
||||
$xactions[] = id(new ManiphestTransaction())
|
||||
->setTransactionType(ManiphestTransaction::TYPE_PRIORITY)
|
||||
->setNewValue($pri);
|
||||
$xactions[] = id(new ManiphestTransaction())
|
||||
->setTransactionType(ManiphestTransaction::TYPE_SUBPRIORITY)
|
||||
->setNewValue($sub);
|
||||
if ($order == PhabricatorProjectColumn::ORDER_PRIORITY) {
|
||||
$priority_xactions = $this->getPriorityTransactions(
|
||||
$object,
|
||||
$after_phid,
|
||||
$before_phid);
|
||||
foreach ($priority_xactions as $xaction) {
|
||||
$xactions[] = $xaction;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -175,56 +134,100 @@ final class PhabricatorProjectMoveController
|
|||
|
||||
$editor->applyTransactions($object, $xactions);
|
||||
|
||||
$owner = null;
|
||||
if ($object->getOwnerPHID()) {
|
||||
$owner = id(new PhabricatorHandleQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($object->getOwnerPHID()))
|
||||
->executeOne();
|
||||
return $this->newCardResponse($board_phid, $object_phid);
|
||||
}
|
||||
|
||||
private function getPriorityTransactions(
|
||||
ManiphestTask $task,
|
||||
$after_phid,
|
||||
$before_phid) {
|
||||
|
||||
list($after_task, $before_task) = $this->loadPriorityTasks(
|
||||
$after_phid,
|
||||
$before_phid);
|
||||
|
||||
$must_move = false;
|
||||
if ($after_task && !$task->isLowerPriorityThan($after_task)) {
|
||||
$must_move = true;
|
||||
}
|
||||
|
||||
// Reload the object so it reflects edits which have been applied.
|
||||
$object = id(new ManiphestTaskQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($object_phid))
|
||||
->needProjectPHIDs(true)
|
||||
->requireCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
))
|
||||
->executeOne();
|
||||
if ($before_task && !$task->isHigherPriorityThan($before_task)) {
|
||||
$must_move = true;
|
||||
}
|
||||
|
||||
$except_phids = array($board_phid);
|
||||
if ($project->getHasSubprojects() || $project->getHasMilestones()) {
|
||||
$descendants = id(new PhabricatorProjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withAncestorProjectPHIDs($except_phids)
|
||||
->execute();
|
||||
foreach ($descendants as $descendant) {
|
||||
$except_phids[] = $descendant->getPHID();
|
||||
// The move doesn't require a priority change to be valid, so don't
|
||||
// change the priority since we are not being forced to.
|
||||
if (!$must_move) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$try = array(
|
||||
array($after_task, true),
|
||||
array($before_task, false),
|
||||
);
|
||||
|
||||
$pri = null;
|
||||
$sub = null;
|
||||
foreach ($try as $spec) {
|
||||
list($task, $is_after) = $spec;
|
||||
|
||||
if (!$task) {
|
||||
continue;
|
||||
}
|
||||
|
||||
list($pri, $sub) = ManiphestTransactionEditor::getAdjacentSubpriority(
|
||||
$task,
|
||||
$is_after);
|
||||
}
|
||||
|
||||
$except_phids = array_fuse($except_phids);
|
||||
$handle_phids = array_fuse($object->getProjectPHIDs());
|
||||
$handle_phids = array_diff_key($handle_phids, $except_phids);
|
||||
$xactions = array();
|
||||
if ($pri !== null) {
|
||||
$xactions[] = id(new ManiphestTransaction())
|
||||
->setTransactionType(ManiphestTransaction::TYPE_PRIORITY)
|
||||
->setNewValue($pri);
|
||||
$xactions[] = id(new ManiphestTransaction())
|
||||
->setTransactionType(ManiphestTransaction::TYPE_SUBPRIORITY)
|
||||
->setNewValue($sub);
|
||||
}
|
||||
|
||||
$project_handles = $viewer->loadHandles($handle_phids);
|
||||
$project_handles = iterator_to_array($project_handles);
|
||||
return $xactions;
|
||||
}
|
||||
|
||||
$card = id(new ProjectBoardTaskCard())
|
||||
private function loadPriorityTasks($after_phid, $before_phid) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$task_phids = array();
|
||||
|
||||
if ($after_phid) {
|
||||
$task_phids[] = $after_phid;
|
||||
}
|
||||
if ($before_phid) {
|
||||
$task_phids[] = $before_phid;
|
||||
}
|
||||
|
||||
if (!$task_phids) {
|
||||
return array(null, null);
|
||||
}
|
||||
|
||||
$tasks = id(new ManiphestTaskQuery())
|
||||
->setViewer($viewer)
|
||||
->setTask($object)
|
||||
->setOwner($owner)
|
||||
->setCanEdit(true)
|
||||
->setProjectHandles($project_handles)
|
||||
->getItem();
|
||||
->withPHIDs($task_phids)
|
||||
->execute();
|
||||
$tasks = mpull($tasks, null, 'getPHID');
|
||||
|
||||
$card->addClass('phui-workcard');
|
||||
if ($after_phid) {
|
||||
$after_task = idx($tasks, $after_phid);
|
||||
} else {
|
||||
$after_task = null;
|
||||
}
|
||||
|
||||
return id(new AphrontAjaxResponse())->setContent(
|
||||
array('task' => $card));
|
||||
if ($before_phid) {
|
||||
$before_task = idx($tasks, $before_phid);
|
||||
} else {
|
||||
$before_task = null;
|
||||
}
|
||||
|
||||
return array($after_task, $before_task);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -201,6 +201,10 @@ final class PhabricatorProjectProfileController
|
|||
->withParentProjectPHIDs(array($project->getPHID()))
|
||||
->needImages(true)
|
||||
->withIsMilestone(true)
|
||||
->withStatuses(
|
||||
array(
|
||||
PhabricatorProjectStatus::STATUS_ACTIVE,
|
||||
))
|
||||
->setOrder('newest')
|
||||
->execute();
|
||||
if (!$milestones) {
|
||||
|
@ -244,6 +248,10 @@ final class PhabricatorProjectProfileController
|
|||
->setViewer($viewer)
|
||||
->withParentProjectPHIDs(array($project->getPHID()))
|
||||
->needImages(true)
|
||||
->withStatuses(
|
||||
array(
|
||||
PhabricatorProjectStatus::STATUS_ACTIVE,
|
||||
))
|
||||
->withIsMilestone(false)
|
||||
->setLimit($limit)
|
||||
->execute();
|
||||
|
|
|
@ -47,10 +47,12 @@ final class PhabricatorProjectColumnTransactionEditor
|
|||
case PhabricatorProjectColumnTransaction::TYPE_STATUS:
|
||||
return $xaction->getNewValue();
|
||||
case PhabricatorProjectColumnTransaction::TYPE_LIMIT:
|
||||
if ($xaction->getNewValue()) {
|
||||
$value = $xaction->getNewValue();
|
||||
if (strlen($value)) {
|
||||
return (int)$xaction->getNewValue();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return parent::getCustomTransactionNewValue($object, $xaction);
|
||||
|
@ -104,7 +106,9 @@ final class PhabricatorProjectColumnTransactionEditor
|
|||
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
||||
$type,
|
||||
pht('Invalid'),
|
||||
pht('Column point limit must be empty, or a positive integer.'),
|
||||
pht(
|
||||
'Column point limit must either be empty or a nonnegative '.
|
||||
'integer.'),
|
||||
$xaction);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,6 +39,9 @@ final class PhabricatorProjectTransactionEditor
|
|||
$types[] = PhabricatorProjectTransaction::TYPE_LOCKED;
|
||||
$types[] = PhabricatorProjectTransaction::TYPE_PARENT;
|
||||
$types[] = PhabricatorProjectTransaction::TYPE_MILESTONE;
|
||||
$types[] = PhabricatorProjectTransaction::TYPE_HASWORKBOARD;
|
||||
$types[] = PhabricatorProjectTransaction::TYPE_DEFAULT_SORT;
|
||||
$types[] = PhabricatorProjectTransaction::TYPE_DEFAULT_FILTER;
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
@ -65,9 +68,15 @@ final class PhabricatorProjectTransactionEditor
|
|||
return $object->getColor();
|
||||
case PhabricatorProjectTransaction::TYPE_LOCKED:
|
||||
return (int)$object->getIsMembershipLocked();
|
||||
case PhabricatorProjectTransaction::TYPE_HASWORKBOARD:
|
||||
return (int)$object->getHasWorkboard();
|
||||
case PhabricatorProjectTransaction::TYPE_PARENT:
|
||||
case PhabricatorProjectTransaction::TYPE_MILESTONE:
|
||||
return null;
|
||||
case PhabricatorProjectTransaction::TYPE_DEFAULT_SORT:
|
||||
return $object->getDefaultWorkboardSort();
|
||||
case PhabricatorProjectTransaction::TYPE_DEFAULT_FILTER:
|
||||
return $object->getDefaultWorkboardFilter();
|
||||
}
|
||||
|
||||
return parent::getCustomTransactionOldValue($object, $xaction);
|
||||
|
@ -86,7 +95,11 @@ final class PhabricatorProjectTransactionEditor
|
|||
case PhabricatorProjectTransaction::TYPE_LOCKED:
|
||||
case PhabricatorProjectTransaction::TYPE_PARENT:
|
||||
case PhabricatorProjectTransaction::TYPE_MILESTONE:
|
||||
case PhabricatorProjectTransaction::TYPE_DEFAULT_SORT:
|
||||
case PhabricatorProjectTransaction::TYPE_DEFAULT_FILTER:
|
||||
return $xaction->getNewValue();
|
||||
case PhabricatorProjectTransaction::TYPE_HASWORKBOARD:
|
||||
return (int)$xaction->getNewValue();
|
||||
case PhabricatorProjectTransaction::TYPE_SLUGS:
|
||||
return $this->normalizeSlugs($xaction->getNewValue());
|
||||
}
|
||||
|
@ -131,6 +144,15 @@ final class PhabricatorProjectTransactionEditor
|
|||
$object->setMilestoneNumber($number);
|
||||
$object->setParentProjectPHID($xaction->getNewValue());
|
||||
return;
|
||||
case PhabricatorProjectTransaction::TYPE_HASWORKBOARD:
|
||||
$object->setHasWorkboard($xaction->getNewValue());
|
||||
return;
|
||||
case PhabricatorProjectTransaction::TYPE_DEFAULT_SORT:
|
||||
$object->setDefaultWorkboardSort($xaction->getNewValue());
|
||||
return;
|
||||
case PhabricatorProjectTransaction::TYPE_DEFAULT_FILTER:
|
||||
$object->setDefaultWorkboardFilter($xaction->getNewValue());
|
||||
return;
|
||||
}
|
||||
|
||||
return parent::applyCustomInternalTransaction($object, $xaction);
|
||||
|
@ -147,11 +169,12 @@ final class PhabricatorProjectTransactionEditor
|
|||
case PhabricatorProjectTransaction::TYPE_NAME:
|
||||
// First, add the old name as a secondary slug; this is helpful
|
||||
// for renames and generally a good thing to do.
|
||||
if ($old !== null) {
|
||||
$this->addSlug($object, $old, false);
|
||||
if (!$this->getIsMilestone()) {
|
||||
if ($old !== null) {
|
||||
$this->addSlug($object, $old, false);
|
||||
}
|
||||
$this->addSlug($object, $new, false);
|
||||
}
|
||||
$this->addSlug($object, $new, false);
|
||||
|
||||
return;
|
||||
case PhabricatorProjectTransaction::TYPE_SLUGS:
|
||||
$old = $xaction->getOldValue();
|
||||
|
@ -172,6 +195,9 @@ final class PhabricatorProjectTransactionEditor
|
|||
case PhabricatorProjectTransaction::TYPE_LOCKED:
|
||||
case PhabricatorProjectTransaction::TYPE_PARENT:
|
||||
case PhabricatorProjectTransaction::TYPE_MILESTONE:
|
||||
case PhabricatorProjectTransaction::TYPE_HASWORKBOARD:
|
||||
case PhabricatorProjectTransaction::TYPE_DEFAULT_SORT:
|
||||
case PhabricatorProjectTransaction::TYPE_DEFAULT_FILTER:
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -631,6 +657,7 @@ final class PhabricatorProjectTransactionEditor
|
|||
}
|
||||
break;
|
||||
case PhabricatorProjectTransaction::TYPE_PARENT:
|
||||
case PhabricatorProjectTransaction::TYPE_MILESTONE:
|
||||
$materialize = true;
|
||||
$new_parent = $object->getParentProject();
|
||||
break;
|
||||
|
@ -669,6 +696,11 @@ final class PhabricatorProjectTransactionEditor
|
|||
->rematerialize($object);
|
||||
}
|
||||
|
||||
if ($new_parent) {
|
||||
id(new PhabricatorProjectsMembershipIndexEngineExtension())
|
||||
->rematerialize($new_parent);
|
||||
}
|
||||
|
||||
return parent::applyFinalEffects($object, $xactions);
|
||||
}
|
||||
|
||||
|
@ -851,8 +883,16 @@ final class PhabricatorProjectTransactionEditor
|
|||
PhabricatorLiskDAO $object,
|
||||
array $xactions) {
|
||||
|
||||
// Herald rules may run on behalf of other users and need to execute
|
||||
// membership checks against ancestors.
|
||||
$project = id(new PhabricatorProjectQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->withPHIDs(array($object->getPHID()))
|
||||
->needAncestorMembers(true)
|
||||
->executeOne();
|
||||
|
||||
return id(new PhabricatorProjectHeraldAdapter())
|
||||
->setProject($object);
|
||||
->setProject($project);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ final class PhabricatorBoardLayoutEngine extends Phobject {
|
|||
private $columnMap = array();
|
||||
private $objectColumnMap = array();
|
||||
private $boardLayout = array();
|
||||
private $fetchAllBoards;
|
||||
|
||||
private $remQueue = array();
|
||||
private $addQueue = array();
|
||||
|
@ -40,6 +41,18 @@ final class PhabricatorBoardLayoutEngine extends Phobject {
|
|||
return $this->objectPHIDs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all boards, even if the board is disabled.
|
||||
*/
|
||||
public function setFetchAllBoards($fetch_all) {
|
||||
$this->fetchAllBoards = $fetch_all;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFetchAllBoards() {
|
||||
return $this->fetchAllBoards;
|
||||
}
|
||||
|
||||
public function executeLayout() {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
|
@ -73,9 +86,14 @@ final class PhabricatorBoardLayoutEngine extends Phobject {
|
|||
return array_select_keys($this->columnMap, array_keys($columns));
|
||||
}
|
||||
|
||||
public function getColumnObjectPHIDs($board_phid, $column_phid) {
|
||||
public function getColumnObjectPositions($board_phid, $column_phid) {
|
||||
$columns = idx($this->boardLayout, $board_phid, array());
|
||||
$positions = idx($columns, $column_phid, array());
|
||||
return idx($columns, $column_phid, array());
|
||||
}
|
||||
|
||||
|
||||
public function getColumnObjectPHIDs($board_phid, $column_phid) {
|
||||
$positions = $this->getColumnObjectPositions($board_phid, $column_phid);
|
||||
return mpull($positions, 'getObjectPHID');
|
||||
}
|
||||
|
||||
|
@ -301,9 +319,11 @@ final class PhabricatorBoardLayoutEngine extends Phobject {
|
|||
->execute();
|
||||
$boards = mpull($boards, null, 'getPHID');
|
||||
|
||||
foreach ($boards as $key => $board) {
|
||||
if (!$board->getHasWorkboard()) {
|
||||
unset($boards[$key]);
|
||||
if (!$this->fetchAllBoards) {
|
||||
foreach ($boards as $key => $board) {
|
||||
if (!$board->getHasWorkboard()) {
|
||||
unset($boards[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -346,7 +366,12 @@ final class PhabricatorBoardLayoutEngine extends Phobject {
|
|||
if ($board->getHasMilestones() || $board->getHasSubprojects()) {
|
||||
$child_projects = idx($children, $board_phid, array());
|
||||
|
||||
$next_sequence = last($board_columns)->getSequence() + 1;
|
||||
if ($board_columns) {
|
||||
$next_sequence = last($board_columns)->getSequence() + 1;
|
||||
} else {
|
||||
$next_sequence = 1;
|
||||
}
|
||||
|
||||
$proxy_columns = mpull($board_columns, null, 'getProxyPHID');
|
||||
foreach ($child_projects as $child_phid => $child) {
|
||||
if (isset($proxy_columns[$child_phid])) {
|
||||
|
@ -413,6 +438,7 @@ final class PhabricatorBoardLayoutEngine extends Phobject {
|
|||
$position_groups = mgroup($positions, 'getObjectPHID');
|
||||
|
||||
$layout = array();
|
||||
$default_phid = null;
|
||||
foreach ($columns as $column) {
|
||||
$column_phid = $column->getPHID();
|
||||
$layout[$column_phid] = array();
|
||||
|
@ -435,7 +461,7 @@ final class PhabricatorBoardLayoutEngine extends Phobject {
|
|||
|
||||
// If we have proxies, we need to force cards into the correct proxy
|
||||
// columns.
|
||||
if ($proxy_map) {
|
||||
if ($proxy_map && $object_phids) {
|
||||
$edge_query = id(new PhabricatorEdgeQuery())
|
||||
->withSourcePHIDs($object_phids)
|
||||
->withEdgeTypes(
|
||||
|
@ -545,8 +571,9 @@ final class PhabricatorBoardLayoutEngine extends Phobject {
|
|||
}
|
||||
}
|
||||
|
||||
// If the object has no position, put it on the default column.
|
||||
if (!$positions) {
|
||||
// If the object has no position, put it on the default column if
|
||||
// one exists.
|
||||
if (!$positions && $default_phid) {
|
||||
$new_position = id(new PhabricatorProjectColumnPosition())
|
||||
->setBoardPHID($board_phid)
|
||||
->setColumnPHID($default_phid)
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorBoardRenderingEngine extends Phobject {
|
||||
|
||||
private $viewer;
|
||||
private $objects;
|
||||
private $excludedProjectPHIDs;
|
||||
private $editMap;
|
||||
|
||||
private $loaded;
|
||||
private $handles;
|
||||
private $coverFiles;
|
||||
|
||||
public function setViewer(PhabricatorUser $viewer) {
|
||||
$this->viewer = $viewer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getViewer() {
|
||||
return $this->viewer;
|
||||
}
|
||||
|
||||
public function setObjects(array $objects) {
|
||||
$this->objects = mpull($objects, null, 'getPHID');
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getObjects() {
|
||||
return $this->objects;
|
||||
}
|
||||
|
||||
public function setExcludedProjectPHIDs(array $phids) {
|
||||
$this->excludedProjectPHIDs = $phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getExcludedProjectPHIDs() {
|
||||
return $this->excludedProjectPHIDs;
|
||||
}
|
||||
|
||||
public function setEditMap(array $edit_map) {
|
||||
$this->editMap = $edit_map;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getEditMap() {
|
||||
return $this->editMap;
|
||||
}
|
||||
|
||||
public function renderCard($phid) {
|
||||
$this->willRender();
|
||||
|
||||
$viewer = $this->getViewer();
|
||||
$object = idx($this->getObjects(), $phid);
|
||||
|
||||
$card = id(new ProjectBoardTaskCard())
|
||||
->setViewer($viewer)
|
||||
->setTask($object)
|
||||
->setCanEdit($this->getCanEdit($phid));
|
||||
|
||||
$owner_phid = $object->getOwnerPHID();
|
||||
if ($owner_phid) {
|
||||
$owner_handle = $this->handles[$owner_phid];
|
||||
$card->setOwner($owner_handle);
|
||||
}
|
||||
|
||||
$project_phids = $object->getProjectPHIDs();
|
||||
$project_handles = array_select_keys($this->handles, $project_phids);
|
||||
if ($project_handles) {
|
||||
$card->setProjectHandles($project_handles);
|
||||
}
|
||||
|
||||
$cover_phid = $object->getCoverImageThumbnailPHID();
|
||||
if ($cover_phid) {
|
||||
$cover_file = idx($this->coverFiles, $cover_phid);
|
||||
if ($cover_file) {
|
||||
$card->setCoverImageFile($cover_file);
|
||||
}
|
||||
}
|
||||
|
||||
return $card;
|
||||
}
|
||||
|
||||
private function willRender() {
|
||||
if ($this->loaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
$phids = array();
|
||||
foreach ($this->objects as $object) {
|
||||
$owner_phid = $object->getOwnerPHID();
|
||||
if ($owner_phid) {
|
||||
$phids[$owner_phid] = $owner_phid;
|
||||
}
|
||||
|
||||
foreach ($object->getProjectPHIDs() as $phid) {
|
||||
$phids[$phid] = $phid;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->excludedProjectPHIDs) {
|
||||
foreach ($this->excludedProjectPHIDs as $excluded_phid) {
|
||||
unset($phids[$excluded_phid]);
|
||||
}
|
||||
}
|
||||
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$handles = $viewer->loadHandles($phids);
|
||||
$handles = iterator_to_array($handles);
|
||||
$this->handles = $handles;
|
||||
|
||||
$cover_phids = array();
|
||||
foreach ($this->objects as $object) {
|
||||
$cover_phid = $object->getCoverImageThumbnailPHID();
|
||||
if ($cover_phid) {
|
||||
$cover_phids[$cover_phid] = $cover_phid;
|
||||
}
|
||||
}
|
||||
|
||||
if ($cover_phids) {
|
||||
$cover_files = id(new PhabricatorFileQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs($cover_phids)
|
||||
->execute();
|
||||
$cover_files = mpull($cover_files, null, 'getPHID');
|
||||
} else {
|
||||
$cover_files = array();
|
||||
}
|
||||
|
||||
$this->coverFiles = $cover_files;
|
||||
|
||||
$this->loaded = true;
|
||||
}
|
||||
|
||||
private function getCanEdit($phid) {
|
||||
if ($this->editMap === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return idx($this->editMap, $phid);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorBoardResponseEngine extends Phobject {
|
||||
|
||||
private $viewer;
|
||||
private $boardPHID;
|
||||
private $objectPHID;
|
||||
private $visiblePHIDs;
|
||||
|
||||
public function setViewer(PhabricatorUser $viewer) {
|
||||
$this->viewer = $viewer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getViewer() {
|
||||
return $this->viewer;
|
||||
}
|
||||
|
||||
public function setBoardPHID($board_phid) {
|
||||
$this->boardPHID = $board_phid;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBoardPHID() {
|
||||
return $this->boardPHID;
|
||||
}
|
||||
|
||||
public function setObjectPHID($object_phid) {
|
||||
$this->objectPHID = $object_phid;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getObjectPHID() {
|
||||
return $this->objectPHID;
|
||||
}
|
||||
|
||||
public function setVisiblePHIDs(array $visible_phids) {
|
||||
$this->visiblePHIDs = $visible_phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getVisiblePHIDs() {
|
||||
return $this->visiblePHIDs;
|
||||
}
|
||||
|
||||
public function buildResponse() {
|
||||
$viewer = $this->getViewer();
|
||||
$object_phid = $this->getObjectPHID();
|
||||
$board_phid = $this->getBoardPHID();
|
||||
|
||||
// Load all the other tasks that are visible in the affected columns and
|
||||
// perform layout for them.
|
||||
$visible_phids = $this->getAllVisiblePHIDs();
|
||||
|
||||
$layout_engine = id(new PhabricatorBoardLayoutEngine())
|
||||
->setViewer($viewer)
|
||||
->setBoardPHIDs(array($board_phid))
|
||||
->setObjectPHIDs($visible_phids)
|
||||
->executeLayout();
|
||||
|
||||
$object_columns = $layout_engine->getObjectColumns(
|
||||
$board_phid,
|
||||
$object_phid);
|
||||
|
||||
$natural = array();
|
||||
foreach ($object_columns as $column_phid => $column) {
|
||||
$column_object_phids = $layout_engine->getColumnObjectPHIDs(
|
||||
$board_phid,
|
||||
$column_phid);
|
||||
$natural[$column_phid] = array_values($column_object_phids);
|
||||
}
|
||||
|
||||
$all_visible = id(new ManiphestTaskQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs($visible_phids)
|
||||
->execute();
|
||||
|
||||
$order_maps = array();
|
||||
foreach ($all_visible as $visible) {
|
||||
$order_maps[$visible->getPHID()] = $visible->getWorkboardOrderVectors();
|
||||
}
|
||||
|
||||
$object = id(new ManiphestTaskQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($object_phid))
|
||||
->needProjectPHIDs(true)
|
||||
->executeOne();
|
||||
if (!$object) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$template = $this->buildTemplate($object);
|
||||
|
||||
$payload = array(
|
||||
'objectPHID' => $object_phid,
|
||||
'cardHTML' => $template,
|
||||
'columnMaps' => $natural,
|
||||
'orderMaps' => $order_maps,
|
||||
'propertyMaps' => array(
|
||||
$object_phid => $object->getWorkboardProperties(),
|
||||
),
|
||||
);
|
||||
|
||||
return id(new AphrontAjaxResponse())
|
||||
->setContent($payload);
|
||||
}
|
||||
|
||||
private function buildTemplate($object) {
|
||||
$viewer = $this->getViewer();
|
||||
$object_phid = $this->getObjectPHID();
|
||||
|
||||
$excluded_phids = $this->loadExcludedProjectPHIDs();
|
||||
|
||||
$rendering_engine = id(new PhabricatorBoardRenderingEngine())
|
||||
->setViewer($viewer)
|
||||
->setObjects(array($object))
|
||||
->setExcludedProjectPHIDs($excluded_phids);
|
||||
|
||||
$card = $rendering_engine->renderCard($object_phid);
|
||||
|
||||
return hsprintf('%s', $card->getItem());
|
||||
}
|
||||
|
||||
private function loadExcludedProjectPHIDs() {
|
||||
$viewer = $this->getViewer();
|
||||
$board_phid = $this->getBoardPHID();
|
||||
|
||||
$exclude_phids = array($board_phid);
|
||||
|
||||
$descendants = id(new PhabricatorProjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withAncestorProjectPHIDs($exclude_phids)
|
||||
->execute();
|
||||
|
||||
foreach ($descendants as $descendant) {
|
||||
$exclude_phids[] = $descendant->getPHID();
|
||||
}
|
||||
|
||||
return array_fuse($exclude_phids);
|
||||
}
|
||||
|
||||
private function getAllVisiblePHIDs() {
|
||||
$visible_phids = $this->getVisiblePHIDs();
|
||||
$visible_phids[] = $this->getObjectPHID();
|
||||
$visible_phids = array_fuse($visible_phids);
|
||||
return $visible_phids;
|
||||
}
|
||||
|
||||
}
|
|
@ -43,17 +43,7 @@ final class PhabricatorProjectEditEngine
|
|||
}
|
||||
|
||||
protected function newEditableObject() {
|
||||
$project = PhabricatorProject::initializeNewProject($this->getViewer());
|
||||
|
||||
$milestone = $this->getMilestoneProject();
|
||||
if ($milestone) {
|
||||
$default_name = pht(
|
||||
'Milestone %s',
|
||||
new PhutilNumber($milestone->loadNextMilestoneNumber()));
|
||||
$project->setName($default_name);
|
||||
}
|
||||
|
||||
return $project;
|
||||
return PhabricatorProject::initializeNewProject($this->getViewer());
|
||||
}
|
||||
|
||||
protected function newObjectQuery() {
|
||||
|
@ -88,15 +78,11 @@ final class PhabricatorProjectEditEngine
|
|||
|
||||
protected function getObjectCreateCancelURI($object) {
|
||||
$parent = $this->getParentProject();
|
||||
if ($parent) {
|
||||
$id = $parent->getID();
|
||||
return "/project/subprojects/{$id}/";
|
||||
}
|
||||
|
||||
$milestone = $this->getMilestoneProject();
|
||||
if ($milestone) {
|
||||
$id = $milestone->getID();
|
||||
return "/project/milestones/{$id}/";
|
||||
|
||||
if ($parent || $milestone) {
|
||||
$id = nonempty($parent, $milestone)->getID();
|
||||
return "/project/subprojects/{$id}/";
|
||||
}
|
||||
|
||||
return parent::getObjectCreateCancelURI($object);
|
||||
|
@ -143,6 +129,7 @@ final class PhabricatorProjectEditEngine
|
|||
array(
|
||||
'parent',
|
||||
'milestone',
|
||||
'milestone.previous',
|
||||
'name',
|
||||
'std:project:internal:description',
|
||||
'icon',
|
||||
|
@ -170,8 +157,26 @@ final class PhabricatorProjectEditEngine
|
|||
$parent_phid = null;
|
||||
}
|
||||
|
||||
$previous_milestone_phid = null;
|
||||
if ($milestone) {
|
||||
$milestone_phid = $milestone->getPHID();
|
||||
|
||||
// Load the current milestone so we can show the user a hint about what
|
||||
// it was called, so they don't have to remember if the next one should
|
||||
// be "Sprint 287" or "Sprint 278".
|
||||
|
||||
$number = ($milestone->loadNextMilestoneNumber() - 1);
|
||||
if ($number > 0) {
|
||||
$previous_milestone = id(new PhabricatorProjectQuery())
|
||||
->setViewer($this->getViewer())
|
||||
->withParentProjectPHIDs(array($milestone->getPHID()))
|
||||
->withIsMilestone(true)
|
||||
->withMilestoneNumberBetween($number, $number)
|
||||
->executeOne();
|
||||
if ($previous_milestone) {
|
||||
$previous_milestone_phid = $previous_milestone->getPHID();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$milestone_phid = null;
|
||||
}
|
||||
|
@ -207,6 +212,14 @@ final class PhabricatorProjectEditEngine
|
|||
->setIsDefaultable(false)
|
||||
->setIsLockable(false)
|
||||
->setIsLocked(true),
|
||||
id(new PhabricatorHandlesEditField())
|
||||
->setKey('milestone.previous')
|
||||
->setLabel(pht('Previous Milestone'))
|
||||
->setSingleValue($previous_milestone_phid)
|
||||
->setIsReorderable(false)
|
||||
->setIsDefaultable(false)
|
||||
->setIsLockable(false)
|
||||
->setIsLocked(true),
|
||||
id(new PhabricatorTextEditField())
|
||||
->setKey('name')
|
||||
->setLabel(pht('Name'))
|
||||
|
|
|
@ -20,6 +20,10 @@ final class PhabricatorProjectProfilePanelEngine
|
|||
->setBuiltinKey(PhabricatorProject::PANEL_PROFILE)
|
||||
->setPanelKey(PhabricatorProjectDetailsProfilePanel::PANELKEY);
|
||||
|
||||
$panels[] = $this->newPanel()
|
||||
->setBuiltinKey(PhabricatorProject::PANEL_POINTS)
|
||||
->setPanelKey(PhabricatorProjectPointsProfilePanel::PANELKEY);
|
||||
|
||||
$panels[] = $this->newPanel()
|
||||
->setBuiltinKey(PhabricatorProject::PANEL_WORKBOARD)
|
||||
->setPanelKey(PhabricatorProjectWorkboardProfilePanel::PANELKEY);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
final class ProjectHovercardEngineExtension
|
||||
final class PhabricatorProjectHovercardEngineExtension
|
||||
extends PhabricatorHovercardEngineExtension {
|
||||
|
||||
const EXTENSIONKEY = 'project.card';
|
|
@ -0,0 +1,192 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorProjectPointsProfilePanel
|
||||
extends PhabricatorProfilePanel {
|
||||
|
||||
const PANELKEY = 'project.points';
|
||||
|
||||
public function getPanelTypeName() {
|
||||
return pht('Project Points');
|
||||
}
|
||||
|
||||
private function getDefaultName() {
|
||||
return pht('Points Bar');
|
||||
}
|
||||
|
||||
public function shouldEnableForObject($object) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
// Only render this element for milestones.
|
||||
if (!$object->isMilestone()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't show if points aren't configured.
|
||||
if (!ManiphestTaskPoints::getIsEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Points are only available if Maniphest is installed.
|
||||
$class = 'PhabricatorManiphestApplication';
|
||||
if (!PhabricatorApplication::isClassInstalledForViewer($class, $viewer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getDisplayName(
|
||||
PhabricatorProfilePanelConfiguration $config) {
|
||||
return $this->getDefaultName();
|
||||
}
|
||||
|
||||
public function buildEditEngineFields(
|
||||
PhabricatorProfilePanelConfiguration $config) {
|
||||
return array(
|
||||
id(new PhabricatorInstructionsEditField())
|
||||
->setValue(
|
||||
pht(
|
||||
'This is a progress bar which shows how many points of work '.
|
||||
'are complete within the milestone. It has no configurable '.
|
||||
'settings.')),
|
||||
);
|
||||
}
|
||||
|
||||
protected function newNavigationMenuItems(
|
||||
PhabricatorProfilePanelConfiguration $config) {
|
||||
$viewer = $this->getViewer();
|
||||
$project = $config->getProfileObject();
|
||||
|
||||
$limit = 250;
|
||||
|
||||
$tasks = id(new ManiphestTaskQuery())
|
||||
->setViewer($viewer)
|
||||
->withEdgeLogicPHIDs(
|
||||
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
|
||||
PhabricatorQueryConstraint::OPERATOR_AND,
|
||||
array($project->getPHID()))
|
||||
->setLimit($limit + 1)
|
||||
->execute();
|
||||
|
||||
if (count($tasks) > $limit) {
|
||||
return $this->renderError(
|
||||
pht(
|
||||
'Too many tasks to compute statistics for (more than %s).',
|
||||
new PhutilNumber($limit)));
|
||||
}
|
||||
|
||||
if (!$tasks) {
|
||||
return $this->renderError(
|
||||
pht(
|
||||
'This milestone has no tasks yet.'));
|
||||
}
|
||||
|
||||
$statuses = array();
|
||||
$points_done = 0;
|
||||
$points_total = 0;
|
||||
$no_points = 0;
|
||||
foreach ($tasks as $task) {
|
||||
$points = $task->getPoints();
|
||||
|
||||
if ($points === null) {
|
||||
$no_points++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$points) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$status = $task->getStatus();
|
||||
if (empty($statuses[$status])) {
|
||||
$statuses[$status] = 0;
|
||||
}
|
||||
$statuses[$status] += $points;
|
||||
|
||||
if (ManiphestTaskStatus::isClosedStatus($status)) {
|
||||
$points_done += $points;
|
||||
}
|
||||
|
||||
$points_total += $points;
|
||||
}
|
||||
|
||||
if ($no_points == count($tasks)) {
|
||||
return $this->renderError(
|
||||
pht('No tasks have assigned point values.'));
|
||||
}
|
||||
|
||||
|
||||
if (!$points_total) {
|
||||
return $this->renderError(
|
||||
pht('All tasks with assigned point values are worth zero points.'));
|
||||
}
|
||||
|
||||
$label = pht(
|
||||
'%s of %s %s',
|
||||
new PhutilNumber($points_done),
|
||||
new PhutilNumber($points_total),
|
||||
ManiphestTaskPoints::getPointsLabel());
|
||||
|
||||
$bar = id(new PHUISegmentBarView())
|
||||
->setLabel($label);
|
||||
|
||||
$map = ManiphestTaskStatus::getTaskStatusMap();
|
||||
$statuses = array_select_keys($statuses, array_keys($map));
|
||||
|
||||
foreach ($statuses as $status => $points) {
|
||||
if (!$points) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ManiphestTaskStatus::isClosedStatus($status)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$color = ManiphestTaskStatus::getStatusColor($status);
|
||||
if (!$color) {
|
||||
$color = 'sky';
|
||||
}
|
||||
|
||||
$tooltip = pht(
|
||||
'%s %s',
|
||||
new PhutilNumber($points),
|
||||
ManiphestTaskStatus::getTaskStatusName($status));
|
||||
|
||||
$bar->newSegment()
|
||||
->setWidth($points / $points_total)
|
||||
->setColor($color)
|
||||
->setTooltip($tooltip);
|
||||
}
|
||||
|
||||
$bar = phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'phui-profile-segment-bar',
|
||||
),
|
||||
$bar);
|
||||
|
||||
$item = $this->newItem()
|
||||
->appendChild($bar);
|
||||
|
||||
return array(
|
||||
$item,
|
||||
);
|
||||
}
|
||||
|
||||
private function renderError($message) {
|
||||
$message = phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'phui-profile-menu-error',
|
||||
),
|
||||
$message);
|
||||
|
||||
$item = $this->newItem()
|
||||
->appendChild($message);
|
||||
|
||||
return array(
|
||||
$item,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -13,6 +13,14 @@ final class PhabricatorProjectSubprojectsProfilePanel
|
|||
return pht('Subprojects');
|
||||
}
|
||||
|
||||
public function shouldEnableForObject($object) {
|
||||
if ($object->isMilestone()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getDisplayName(
|
||||
PhabricatorProfilePanelConfiguration $config) {
|
||||
$name = $config->getPanelProperty('name');
|
||||
|
|
|
@ -18,6 +18,18 @@ final class PhabricatorProjectWorkboardProfilePanel
|
|||
return true;
|
||||
}
|
||||
|
||||
public function shouldEnableForObject($object) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
// Workboards are only available if Maniphest is installed.
|
||||
$class = 'PhabricatorManiphestApplication';
|
||||
if (!PhabricatorApplication::isClassInstalledForViewer($class, $viewer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getDisplayName(
|
||||
PhabricatorProfilePanelConfiguration $config) {
|
||||
$name = $config->getPanelProperty('name');
|
||||
|
@ -42,14 +54,6 @@ final class PhabricatorProjectWorkboardProfilePanel
|
|||
|
||||
protected function newNavigationMenuItems(
|
||||
PhabricatorProfilePanelConfiguration $config) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
// Workboards are only available if Maniphest is installed.
|
||||
$class = 'PhabricatorManiphestApplication';
|
||||
if (!PhabricatorApplication::isClassInstalledForViewer($class, $viewer)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$project = $config->getProfileObject();
|
||||
|
||||
$has_workboard = $project->getHasWorkboard();
|
||||
|
|
|
@ -20,6 +20,8 @@ final class PhabricatorProjectQuery
|
|||
private $hasSubprojects;
|
||||
private $minDepth;
|
||||
private $maxDepth;
|
||||
private $minMilestoneNumber;
|
||||
private $maxMilestoneNumber;
|
||||
|
||||
private $status = 'status-any';
|
||||
const STATUS_ANY = 'status-any';
|
||||
|
@ -27,6 +29,7 @@ final class PhabricatorProjectQuery
|
|||
const STATUS_CLOSED = 'status-closed';
|
||||
const STATUS_ACTIVE = 'status-active';
|
||||
const STATUS_ARCHIVED = 'status-archived';
|
||||
private $statuses;
|
||||
|
||||
private $needSlugs;
|
||||
private $needMembers;
|
||||
|
@ -49,6 +52,11 @@ final class PhabricatorProjectQuery
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function withStatuses(array $statuses) {
|
||||
$this->statuses = $statuses;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withMemberPHIDs(array $member_phids) {
|
||||
$this->memberPHIDs = $member_phids;
|
||||
return $this;
|
||||
|
@ -105,6 +113,12 @@ final class PhabricatorProjectQuery
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function withMilestoneNumberBetween($min, $max) {
|
||||
$this->minMilestoneNumber = $min;
|
||||
$this->maxMilestoneNumber = $max;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function needMembers($need_members) {
|
||||
$this->needMembers = $need_members;
|
||||
return $this;
|
||||
|
@ -387,6 +401,13 @@ final class PhabricatorProjectQuery
|
|||
$filter);
|
||||
}
|
||||
|
||||
if ($this->statuses !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'status IN (%Ls)',
|
||||
$this->statuses);
|
||||
}
|
||||
|
||||
if ($this->ids !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
|
@ -481,6 +502,7 @@ final class PhabricatorProjectQuery
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
if ($this->hasSubprojects !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
|
@ -502,6 +524,20 @@ final class PhabricatorProjectQuery
|
|||
$this->maxDepth);
|
||||
}
|
||||
|
||||
if ($this->minMilestoneNumber !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'milestoneNumber >= %d',
|
||||
$this->minMilestoneNumber);
|
||||
}
|
||||
|
||||
if ($this->maxMilestoneNumber !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'milestoneNumber <= %d',
|
||||
$this->maxMilestoneNumber);
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ final class PhabricatorProjectSearchField
|
|||
|
||||
if ($slugs) {
|
||||
$projects = id(new PhabricatorProjectQuery())
|
||||
->setViewer($this->requireViewer())
|
||||
->setViewer($this->getViewer())
|
||||
->withSlugs($slugs)
|
||||
->execute();
|
||||
foreach ($projects as $project) {
|
||||
|
|
|
@ -36,6 +36,8 @@ final class PhabricatorProject extends PhabricatorProjectDAO
|
|||
protected $projectDepth;
|
||||
protected $projectPathKey;
|
||||
|
||||
protected $properties = array();
|
||||
|
||||
private $memberPHIDs = self::ATTACHABLE;
|
||||
private $watcherPHIDs = self::ATTACHABLE;
|
||||
private $sparseWatchers = self::ATTACHABLE;
|
||||
|
@ -48,6 +50,7 @@ final class PhabricatorProject extends PhabricatorProjectDAO
|
|||
const TABLE_DATASOURCE_TOKEN = 'project_datasourcetoken';
|
||||
|
||||
const PANEL_PROFILE = 'project.profile';
|
||||
const PANEL_POINTS = 'project.points';
|
||||
const PANEL_WORKBOARD = 'project.workboard';
|
||||
const PANEL_MEMBERS = 'project.members';
|
||||
const PANEL_MANAGE = 'project.manage';
|
||||
|
@ -197,6 +200,9 @@ final class PhabricatorProject extends PhabricatorProjectDAO
|
|||
protected function getConfiguration() {
|
||||
return array(
|
||||
self::CONFIG_AUX_PHID => true,
|
||||
self::CONFIG_SERIALIZATION => array(
|
||||
'properties' => self::SERIALIZATION_JSON,
|
||||
),
|
||||
self::CONFIG_COLUMN_SCHEMA => array(
|
||||
'name' => 'sort128',
|
||||
'status' => 'text32',
|
||||
|
@ -548,6 +554,31 @@ final class PhabricatorProject extends PhabricatorProjectDAO
|
|||
return idx($map, $color, $color);
|
||||
}
|
||||
|
||||
public function getProperty($key, $default = null) {
|
||||
return idx($this->properties, $key, $default);
|
||||
}
|
||||
|
||||
public function setProperty($key, $value) {
|
||||
$this->properties[$key] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDefaultWorkboardSort() {
|
||||
return $this->getProperty('workboard.sort.default');
|
||||
}
|
||||
|
||||
public function setDefaultWorkboardSort($sort) {
|
||||
return $this->setProperty('workboard.sort.default', $sort);
|
||||
}
|
||||
|
||||
public function getDefaultWorkboardFilter() {
|
||||
return $this->getProperty('workboard.filter.default');
|
||||
}
|
||||
|
||||
public function setDefaultWorkboardFilter($filter) {
|
||||
return $this->setProperty('workboard.filter.default', $filter);
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorCustomFieldInterface )------------------------------------ */
|
||||
|
||||
|
|
|
@ -86,6 +86,11 @@ final class PhabricatorProjectColumn
|
|||
}
|
||||
|
||||
public function isHidden() {
|
||||
$proxy = $this->getProxy();
|
||||
if ($proxy) {
|
||||
return $proxy->isArchived();
|
||||
}
|
||||
|
||||
return ($this->getStatus() == self::STATUS_HIDDEN);
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,9 @@ final class PhabricatorProjectTransaction
|
|||
const TYPE_LOCKED = 'project:locked';
|
||||
const TYPE_PARENT = 'project:parent';
|
||||
const TYPE_MILESTONE = 'project:milestone';
|
||||
const TYPE_HASWORKBOARD = 'project:hasworkboard';
|
||||
const TYPE_DEFAULT_SORT = 'project:sort';
|
||||
const TYPE_DEFAULT_FILTER = 'project:filter';
|
||||
|
||||
// NOTE: This is deprecated, members are just a normal edge now.
|
||||
const TYPE_MEMBERS = 'project:members';
|
||||
|
@ -65,8 +68,29 @@ final class PhabricatorProjectTransaction
|
|||
return parent::getColor();
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
public function shouldHideForFeed() {
|
||||
switch ($this->getTransactionType()) {
|
||||
case self::TYPE_HASWORKBOARD:
|
||||
case self::TYPE_DEFAULT_SORT:
|
||||
case self::TYPE_DEFAULT_FILTER:
|
||||
return true;
|
||||
}
|
||||
|
||||
return parent::shouldHideForFeed();
|
||||
}
|
||||
|
||||
public function shouldHideForMail(array $xactions) {
|
||||
switch ($this->getTransactionType()) {
|
||||
case self::TYPE_HASWORKBOARD:
|
||||
case self::TYPE_DEFAULT_SORT:
|
||||
case self::TYPE_DEFAULT_FILTER:
|
||||
return true;
|
||||
}
|
||||
|
||||
return parent::shouldHideForMail($xactions);
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
$old = $this->getOldValue();
|
||||
$new = $this->getNewValue();
|
||||
|
||||
|
@ -246,6 +270,27 @@ final class PhabricatorProjectTransaction
|
|||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case self::TYPE_HASWORKBOARD:
|
||||
if ($new) {
|
||||
return pht(
|
||||
'%s enabled the workboard for this project.',
|
||||
$author_handle);
|
||||
} else {
|
||||
return pht(
|
||||
'%s disabled the workboard for this project.',
|
||||
$author_handle);
|
||||
}
|
||||
|
||||
case self::TYPE_DEFAULT_SORT:
|
||||
return pht(
|
||||
'%s changed the default sort order for the project workboard.',
|
||||
$author_handle);
|
||||
|
||||
case self::TYPE_DEFAULT_FILTER:
|
||||
return pht(
|
||||
'%s changed the default filter for the project workboard.',
|
||||
$author_handle);
|
||||
}
|
||||
|
||||
return parent::getTitle();
|
||||
|
@ -366,6 +411,7 @@ final class PhabricatorProjectTransaction
|
|||
$object_handle,
|
||||
$this->renderSlugList($rem));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return parent::getTitleForFeed();
|
||||
|
|
|
@ -38,6 +38,8 @@ final class PhabricatorProjectDatasource
|
|||
$query->withIsMilestone(false);
|
||||
}
|
||||
|
||||
$for_autocomplete = $this->getParameter('autocomplete');
|
||||
|
||||
$projs = $this->executeQuery($query);
|
||||
|
||||
$projs = mpull($projs, null, 'getPHID');
|
||||
|
@ -58,6 +60,23 @@ final class PhabricatorProjectDatasource
|
|||
if (!isset($has_cols[$proj->getPHID()])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$slug = $proj->getPrimarySlug();
|
||||
if (!strlen($slug)) {
|
||||
foreach ($proj->getSlugs() as $slug_object) {
|
||||
$slug = $slug_object->getSlug();
|
||||
if (strlen($slug)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we're building results for the autocompleter and this project
|
||||
// doesn't have any usable slugs, don't return it as a result.
|
||||
if ($for_autocomplete && !strlen($slug)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$closed = null;
|
||||
if ($proj->isArchived()) {
|
||||
$closed = pht('Archived');
|
||||
|
@ -78,7 +97,6 @@ final class PhabricatorProjectDatasource
|
|||
->setPriorityType('proj')
|
||||
->setClosed($closed);
|
||||
|
||||
$slug = $proj->getPrimarySlug();
|
||||
if (strlen($slug)) {
|
||||
$proj_result->setAutocomplete('#'.$slug);
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ final class ProjectBoardTaskCard extends Phobject {
|
|||
private $task;
|
||||
private $owner;
|
||||
private $canEdit;
|
||||
private $coverImageFile;
|
||||
|
||||
public function setViewer(PhabricatorUser $viewer) {
|
||||
$this->viewer = $viewer;
|
||||
|
@ -25,6 +26,15 @@ final class ProjectBoardTaskCard extends Phobject {
|
|||
return $this->projectHandles;
|
||||
}
|
||||
|
||||
public function setCoverImageFile(PhabricatorFile $cover_image_file) {
|
||||
$this->coverImageFile = $cover_image_file;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCoverImageFile() {
|
||||
return $this->coverImageFile;
|
||||
}
|
||||
|
||||
public function setTask(ManiphestTask $task) {
|
||||
$this->task = $task;
|
||||
return $this;
|
||||
|
@ -68,10 +78,6 @@ final class ProjectBoardTaskCard extends Phobject {
|
|||
->setHref('/T'.$task->getID())
|
||||
->addSigil('project-card')
|
||||
->setDisabled($task->isClosed())
|
||||
->setMetadata(
|
||||
array(
|
||||
'objectPHID' => $task->getPHID(),
|
||||
))
|
||||
->addAction(
|
||||
id(new PHUIListItemView())
|
||||
->setName(pht('Edit'))
|
||||
|
@ -84,6 +90,23 @@ final class ProjectBoardTaskCard extends Phobject {
|
|||
$card->addHandleIcon($owner, $owner->getName());
|
||||
}
|
||||
|
||||
$cover_file = $this->getCoverImageFile();
|
||||
if ($cover_file) {
|
||||
$card->setCoverImage($cover_file->getBestURI());
|
||||
}
|
||||
|
||||
if (ManiphestTaskPoints::getIsEnabled()) {
|
||||
$points = $task->getPoints();
|
||||
if ($points !== null) {
|
||||
$points_tag = id(new PHUITagView())
|
||||
->setType(PHUITagView::TYPE_SHADE)
|
||||
->setShade(PHUITagView::COLOR_BLUE)
|
||||
->setSlimShady(true)
|
||||
->setName($points);
|
||||
$card->addAttribute($points_tag);
|
||||
}
|
||||
}
|
||||
|
||||
if ($task->isClosed()) {
|
||||
$icon = ManiphestTaskStatus::getStatusIcon($task->getStatus());
|
||||
$icon = id(new PHUIIconView())
|
||||
|
@ -100,6 +123,8 @@ final class ProjectBoardTaskCard extends Phobject {
|
|||
$card->addAttribute($tag_list);
|
||||
}
|
||||
|
||||
$card->addClass('phui-workcard');
|
||||
|
||||
return $card;
|
||||
}
|
||||
|
||||
|
|
|
@ -236,9 +236,18 @@ abstract class PhabricatorProfilePanelEngine extends Phobject {
|
|||
->withProfilePHIDs(array($object->getPHID()))
|
||||
->execute();
|
||||
|
||||
foreach ($stored_panels as $stored_panel) {
|
||||
$impl = $stored_panel->getPanel();
|
||||
$impl->setViewer($viewer);
|
||||
}
|
||||
|
||||
// Merge the stored panels into the builtin panels. If a builtin panel has
|
||||
// a stored version, replace the defaults with the stored changes.
|
||||
foreach ($stored_panels as $stored_panel) {
|
||||
if (!$stored_panel->shouldEnableForObject($object)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$builtin_key = $stored_panel->getBuiltinKey();
|
||||
if ($builtin_key !== null) {
|
||||
// If this builtin actually exists, replace the builtin with the
|
||||
|
@ -255,12 +264,6 @@ abstract class PhabricatorProfilePanelEngine extends Phobject {
|
|||
}
|
||||
}
|
||||
|
||||
foreach ($panels as $panel) {
|
||||
$impl = $panel->getPanel();
|
||||
|
||||
$impl->setViewer($viewer);
|
||||
}
|
||||
|
||||
$panels = msort($panels, 'getSortKey');
|
||||
|
||||
// Normalize keys since callers shouldn't rely on this array being
|
||||
|
@ -302,6 +305,7 @@ abstract class PhabricatorProfilePanelEngine extends Phobject {
|
|||
$builtins = $this->getBuiltinProfilePanels($object);
|
||||
|
||||
$panels = PhabricatorProfilePanel::getAllPanels();
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$order = 1;
|
||||
$map = array();
|
||||
|
@ -335,12 +339,19 @@ abstract class PhabricatorProfilePanelEngine extends Phobject {
|
|||
$panel_key));
|
||||
}
|
||||
|
||||
$panel = clone $panel;
|
||||
$panel->setViewer($viewer);
|
||||
|
||||
$builtin
|
||||
->setProfilePHID($object->getPHID())
|
||||
->attachPanel($panel)
|
||||
->attachProfileObject($object)
|
||||
->setPanelOrder($order);
|
||||
|
||||
if (!$builtin->shouldEnableForObject($object)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$map[$builtin_key] = $builtin;
|
||||
|
||||
$order++;
|
||||
|
|
|
@ -30,6 +30,10 @@ abstract class PhabricatorProfilePanel extends Phobject {
|
|||
return false;
|
||||
}
|
||||
|
||||
public function shouldEnableForObject($object) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function canHidePanel(
|
||||
PhabricatorProfilePanelConfiguration $config) {
|
||||
return true;
|
||||
|
|
|
@ -109,6 +109,10 @@ final class PhabricatorProfilePanelConfiguration
|
|||
return $this->getPanel()->canHidePanel($this);
|
||||
}
|
||||
|
||||
public function shouldEnableForObject($object) {
|
||||
return $this->getPanel()->shouldEnableForObject($object);
|
||||
}
|
||||
|
||||
public function getSortKey() {
|
||||
$order = $this->getPanelOrder();
|
||||
if ($order === null) {
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorEditEnginePointsCommentAction
|
||||
extends PhabricatorEditEngineCommentAction {
|
||||
|
||||
public function getPHUIXControlType() {
|
||||
return 'points';
|
||||
}
|
||||
|
||||
public function getPHUIXControlSpecification() {
|
||||
return array(
|
||||
'value' => $this->getValue(),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorPointsEditField
|
||||
extends PhabricatorEditField {
|
||||
|
||||
protected function newControl() {
|
||||
return new AphrontFormTextControl();
|
||||
}
|
||||
|
||||
protected function newConduitParameterType() {
|
||||
return new ConduitPointsParameterType();
|
||||
}
|
||||
|
||||
protected function newCommentAction() {
|
||||
return id(new PhabricatorEditEnginePointsCommentAction());
|
||||
}
|
||||
}
|
|
@ -175,8 +175,20 @@ final class PhabricatorEditEngineConfiguration
|
|||
}
|
||||
|
||||
private function reorderFields(array $fields) {
|
||||
// Fields which can not be reordered are fixed in order at the top of the
|
||||
// form. These are used to show instructions or contextual information.
|
||||
|
||||
$fixed = array();
|
||||
foreach ($fields as $key => $field) {
|
||||
if (!$field->getIsReorderable()) {
|
||||
$fixed[$key] = $field;
|
||||
}
|
||||
}
|
||||
|
||||
$keys = $this->getFieldOrder();
|
||||
$fields = array_select_keys($fields, $keys) + $fields;
|
||||
|
||||
$fields = $fixed + array_select_keys($fields, $keys) + $fields;
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,10 +12,7 @@ final class PhabricatorAphrontBarUIExample extends PhabricatorUIExample {
|
|||
|
||||
public function renderExample() {
|
||||
$out = array();
|
||||
$out[] = $this->renderTestThings('AphrontProgressBarView', 13, 10);
|
||||
$out[] = $this->renderTestThings('AphrontGlyphBarView', 13, 10);
|
||||
$out[] = $this->renderWeirdOrderGlyphBars();
|
||||
$out[] = $this->renderAsciiStarBar();
|
||||
$out[] = $this->renderRainbow();
|
||||
return $out;
|
||||
}
|
||||
|
||||
|
@ -26,48 +23,46 @@ final class PhabricatorAphrontBarUIExample extends PhabricatorUIExample {
|
|||
->appendChild($thing);
|
||||
}
|
||||
|
||||
private function renderTestThings($class, $max, $incr) {
|
||||
private function renderRainbow() {
|
||||
$colors = array(
|
||||
'red',
|
||||
'orange',
|
||||
'yellow',
|
||||
'green',
|
||||
'blue',
|
||||
'indigo',
|
||||
'violet',
|
||||
);
|
||||
|
||||
$labels = array(
|
||||
pht('Empty'),
|
||||
pht('Red'),
|
||||
pht('Orange'),
|
||||
pht('Yellow'),
|
||||
pht('Green'),
|
||||
pht('Blue'),
|
||||
pht('Indigo'),
|
||||
pht('Violet'),
|
||||
);
|
||||
|
||||
$bars = array();
|
||||
for ($ii = 0; $ii <= $max; $ii++) {
|
||||
$bars[] = newv($class, array())
|
||||
->setValue($ii * $incr)
|
||||
->setMax($max * $incr)
|
||||
->setCaption("{$ii} outta {$max} ain't bad!");
|
||||
}
|
||||
return $this->wrap("Test {$class}", $bars);
|
||||
}
|
||||
|
||||
private function renderWeirdOrderGlyphBars() {
|
||||
$views = array();
|
||||
$indices = array(1, 3, 7, 4, 2, 8, 9, 5, 10, 6);
|
||||
$max = count($indices);
|
||||
foreach ($indices as $index) {
|
||||
$views[] = id(new AphrontGlyphBarView())
|
||||
->setValue($index)
|
||||
->setMax($max)
|
||||
->setNumGlyphs(5)
|
||||
->setCaption("Lol score is {$index}/{$max}")
|
||||
->setGlyph(hsprintf('%s', 'LOL!'))
|
||||
->setBackgroundGlyph(hsprintf('%s', '____'));
|
||||
$views[] = hsprintf('<div style="clear:both;"></div>');
|
||||
for ($jj = -1; $jj < count($colors); $jj++) {
|
||||
$bar = id(new PHUISegmentBarView())
|
||||
->setLabel($labels[$jj + 1]);
|
||||
for ($ii = 0; $ii <= $jj; $ii++) {
|
||||
$bar->newSegment()
|
||||
->setWidth(1 / 7)
|
||||
->setColor($colors[$ii]);
|
||||
}
|
||||
$bars[] = $bar;
|
||||
}
|
||||
|
||||
return $this->wrap(
|
||||
pht('Glyph bars in weird order'),
|
||||
$views);
|
||||
}
|
||||
$bars = phutil_implode_html(
|
||||
phutil_tag('br'),
|
||||
$bars);
|
||||
|
||||
private function renderAsciiStarBar() {
|
||||
$bar = id(new AphrontGlyphBarView())
|
||||
->setValue(50)
|
||||
->setMax(100)
|
||||
->setCaption(pht('Glyphs!'))
|
||||
->setNumGlyphs(10)
|
||||
->setGlyph(hsprintf('%s', '*'));
|
||||
|
||||
return $this->wrap(
|
||||
pht('ASCII star glyph bar'),
|
||||
$bar);
|
||||
return $this->wrap(pht('Rainbow Bars'), $bars);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -28,11 +28,10 @@ Databases
|
|||
=========
|
||||
|
||||
Each Phabricator application has its own database. The names are prefixed by
|
||||
`phabricator_` (this is configurable). This design has two advantages:
|
||||
`phabricator_` (this is configurable).
|
||||
|
||||
- Each database is easier to comprehend and to maintain.
|
||||
- We don't do cross-database joins so each database can live on its own
|
||||
machine. This gives us flexibility in sharding data later.
|
||||
Phabricator uses a separate database for each application. To understand why,
|
||||
see @{article:Why does Phabricator need so many databases?}.
|
||||
|
||||
Connections
|
||||
===========
|
||||
|
|
130
src/docs/flavor/so_many_databases.diviner
Normal file
130
src/docs/flavor/so_many_databases.diviner
Normal file
|
@ -0,0 +1,130 @@
|
|||
@title Why does Phabricator need so many databases?
|
||||
@group lore
|
||||
|
||||
Phabricator uses about 60 databases (and we may have added more by the time you
|
||||
read this document). This sometimes comes as a surprise, since you might assume
|
||||
it would only use one database.
|
||||
|
||||
The approach we use is designed to work at scale for huge installs with many
|
||||
thousands of users. We care a lot about working well for large installs, and
|
||||
about scaling up gracefully to meet the needs of growing organizations. We want
|
||||
small startups to be able to install Phabricator and have it grow with them as
|
||||
they expand to many thousands of employees.
|
||||
|
||||
A cost of this approach is that it makes Phabricator more difficult to install
|
||||
on shared hosts which require a lot of work to create or authorize access to
|
||||
each database. However, Phabricator does a lot of advanced or complex things
|
||||
which are difficult to configure or manage on shared hosts, and we don't
|
||||
recommend installing it on a shared host. The install documentation explicitly
|
||||
discouarges installing on shared hosts.
|
||||
|
||||
Broadly, in cases where we must choose between operating well at scale for
|
||||
growing organizations and installing easily on shared hosts, we prioritize
|
||||
operating at scale.
|
||||
|
||||
|
||||
Listing Databases
|
||||
=================
|
||||
|
||||
You can get a full list of the databases Phabricator needs with `bin/storage
|
||||
databases`. It will look something like this:
|
||||
|
||||
```
|
||||
$ /core/lib/phabricator/bin/storage databases
|
||||
secure_audit
|
||||
secure_calendar
|
||||
secure_chatlog
|
||||
secure_conduit
|
||||
secure_countdown
|
||||
secure_daemon
|
||||
secure_differential
|
||||
secure_draft
|
||||
secure_drydock
|
||||
secure_feed
|
||||
...<dozens more databases>...
|
||||
```
|
||||
|
||||
Roughly, each application has its own database, and then there are some
|
||||
databases which support internal systems or shared infrastructure.
|
||||
|
||||
|
||||
Operating at Scale
|
||||
==================
|
||||
|
||||
This storage design is aimed at large installs that may need more than one
|
||||
physical database server to handle the load the install generates.
|
||||
|
||||
The primary reason we use a separate database for each application is to allow
|
||||
large installs to scale up by spreading database load across more hardware. A
|
||||
large organization with many thousands of active users may find themselves
|
||||
limited by the capacity of a single database backend.
|
||||
|
||||
If so, they can launch a second backend, move some applications over to it, and
|
||||
continue piling on more users.
|
||||
|
||||
This can't continue forever, but provides a substantial amount of headroom for
|
||||
large installs to spread the workload across more hardware and continue scaling
|
||||
up.
|
||||
|
||||
To make this possible, we put each application in its own database and use
|
||||
database boundaries to enforce the logical constraints that the application
|
||||
must have in order for this to work. For example, we can not perform joins
|
||||
between separable tables, because they may not be on the same hardware.
|
||||
|
||||
Establishing boundaries with application databases is a simple, straightforward
|
||||
way to partition storage and make administrative operations like spreading load
|
||||
realistic.
|
||||
|
||||
|
||||
Ease of Development
|
||||
===================
|
||||
|
||||
This design is also easier for us to work with, and easier for users who
|
||||
want to work with the raw data in the database.
|
||||
|
||||
We have a large number of tables (more than 400) and we can not reasonably
|
||||
reduce the number of tables very much (each table generally represents some
|
||||
meaningful type of object in some application). It's easier to develop with
|
||||
tables which are organized into separate application databases, just like it's
|
||||
easier to work with a large project if you organize source files into
|
||||
directories.
|
||||
|
||||
If you aren't developing Phabricator and never look at the data in the
|
||||
database, you probably won't benefit from this organization. However, if you
|
||||
are a developer or want to extend Phabricator or look under the hood, it's
|
||||
easier to find what you're looking for and work with the tables when they're
|
||||
organized by application.
|
||||
|
||||
|
||||
More Databases Cost Nothing
|
||||
===========================
|
||||
|
||||
In almost all cases, creating more databases has zero cost, just like
|
||||
organizing source code into directories has zero cost. Even if we didn't derive
|
||||
enormous benefits from this approach at scale, there is little reason //not//
|
||||
to organize storage like this.
|
||||
|
||||
There are a handful of administrative tasks which are very slightly more
|
||||
complex to perform on multiple databases, but these are all either automated
|
||||
with `bin/storage` or easy to build on top of the list of databases emitted by
|
||||
`bin/storage databases`.
|
||||
|
||||
For example, you can dump all the databases with `bin/storage dump`, and you
|
||||
can destroy all the databases with `bin/storage destroy`.
|
||||
|
||||
As mentioned above, an exception to this is that if you're installing on a
|
||||
shared host and need to jump through hoops to individually authorize access to
|
||||
each database, databases do cost something.
|
||||
|
||||
However, this cost is an artificial cost imposed by the selected environment,
|
||||
and this is only the first of many issues you'll run into trying to install and
|
||||
run Phabricator on a shared host. These issues are why we strongly discourage
|
||||
using shared hosts, and recommend against them in the install guide.
|
||||
|
||||
|
||||
Next Steps
|
||||
==========
|
||||
|
||||
Continue by:
|
||||
|
||||
- learning more about databases in @{article:Database Schema}.
|
|
@ -52,7 +52,7 @@ inherent complexity, like these:
|
|||
|
||||
- {icon times, color=red} A 100MB wiki page takes a long time to render.
|
||||
- {icon times, color=red} A turing-complete simulation of Conway's Game of
|
||||
Life implented in 958,000 Herald rules executes slowly.
|
||||
Life implemented in 958,000 Herald rules executes slowly.
|
||||
- {icon times, color=red} Uploading an 8GB file takes several minutes.
|
||||
|
||||
Generally, the path forward will be:
|
||||
|
|
|
@ -1446,5 +1446,19 @@ abstract class PhabricatorCustomField extends Phobject {
|
|||
return null;
|
||||
}
|
||||
|
||||
public function getHeraldFieldStandardType() {
|
||||
if ($this->proxy) {
|
||||
return $this->proxy->getHeraldFieldStandardType();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getHeraldDatasource() {
|
||||
if ($this->proxy) {
|
||||
return $this->proxy->getHeraldDatasource();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -65,8 +65,20 @@ final class PhabricatorCustomFieldHeraldField extends HeraldField {
|
|||
return $this->getCustomField()->getHeraldFieldConditions();
|
||||
}
|
||||
|
||||
protected function getHeraldFieldStandardType() {
|
||||
return $this->getCustomField()->getHeraldFieldStandardType();
|
||||
}
|
||||
|
||||
public function getHeraldFieldValueType($condition) {
|
||||
if ($this->getHeraldFieldStandardType()) {
|
||||
return parent::getHeraldFieldValueType($condition);
|
||||
}
|
||||
|
||||
return $this->getCustomField()->getHeraldFieldValueType($condition);
|
||||
}
|
||||
|
||||
protected function getDatasource() {
|
||||
return $this->getCustomField()->getHeraldDatasource();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -129,6 +129,10 @@ final class PhabricatorStandardCustomFieldBool
|
|||
);
|
||||
}
|
||||
|
||||
public function getHeraldFieldStandardType() {
|
||||
return HeraldField::STANDARD_BOOL;
|
||||
}
|
||||
|
||||
protected function getHTTPParameterType() {
|
||||
return new AphrontBoolHTTPParameterType();
|
||||
}
|
||||
|
|
|
@ -77,9 +77,14 @@ final class PhabricatorStandardCustomFieldLink
|
|||
HeraldAdapter::CONDITION_IS,
|
||||
HeraldAdapter::CONDITION_IS_NOT,
|
||||
HeraldAdapter::CONDITION_REGEXP,
|
||||
HeraldAdapter::CONDITION_NOT_REGEXP,
|
||||
);
|
||||
}
|
||||
|
||||
public function getHeraldFieldStandardType() {
|
||||
return HeraldField::STANDARD_TEXT;
|
||||
}
|
||||
|
||||
protected function getHTTPParameterType() {
|
||||
return new AphrontStringHTTPParameterType();
|
||||
}
|
||||
|
|
|
@ -241,6 +241,10 @@ abstract class PhabricatorStandardCustomFieldPHIDs
|
|||
);
|
||||
}
|
||||
|
||||
public function getHeraldFieldStandardType() {
|
||||
return HeraldField::STANDARD_PHID_NULLABLE;
|
||||
}
|
||||
|
||||
public function getHeraldFieldValue() {
|
||||
// If the field has a `null` value, make sure we hand an `array()` to
|
||||
// Herald.
|
||||
|
|
|
@ -92,9 +92,14 @@ final class PhabricatorStandardCustomFieldRemarkup
|
|||
HeraldAdapter::CONDITION_IS,
|
||||
HeraldAdapter::CONDITION_IS_NOT,
|
||||
HeraldAdapter::CONDITION_REGEXP,
|
||||
HeraldAdapter::CONDITION_NOT_REGEXP,
|
||||
);
|
||||
}
|
||||
|
||||
public function getHeraldFieldStandardType() {
|
||||
return HeraldField::STANDARD_TEXT;
|
||||
}
|
||||
|
||||
protected function getHTTPParameterType() {
|
||||
return new AphrontStringHTTPParameterType();
|
||||
}
|
||||
|
|
|
@ -60,9 +60,14 @@ final class PhabricatorStandardCustomFieldText
|
|||
HeraldAdapter::CONDITION_IS,
|
||||
HeraldAdapter::CONDITION_IS_NOT,
|
||||
HeraldAdapter::CONDITION_REGEXP,
|
||||
HeraldAdapter::CONDITION_NOT_REGEXP,
|
||||
);
|
||||
}
|
||||
|
||||
public function getHeraldFieldStandardType() {
|
||||
return HeraldField::STANDARD_TEXT;
|
||||
}
|
||||
|
||||
protected function getHTTPParameterType() {
|
||||
return new AphrontStringHTTPParameterType();
|
||||
}
|
||||
|
|
|
@ -44,4 +44,12 @@ abstract class PhabricatorStandardCustomFieldTokenizer
|
|||
->setDatasource($this->getDatasource());
|
||||
}
|
||||
|
||||
public function getHeraldFieldStandardType() {
|
||||
return HeraldField::STANDARD_PHID_LIST;
|
||||
}
|
||||
|
||||
public function getHeraldDatasource() {
|
||||
return $this->getDatasource();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1514,6 +1514,20 @@ final class PhabricatorUSEnglishTranslation
|
|||
'Permanently destroyed %s object.',
|
||||
'Permanently destroyed %s objects.',
|
||||
),
|
||||
|
||||
'%s added %s watcher(s) for %s: %s.' => array(
|
||||
array(
|
||||
'%s added a watcher for %3$s: %4$s.',
|
||||
'%s added watchers for %3$s: %4$s.',
|
||||
),
|
||||
),
|
||||
|
||||
'%s removed %s watcher(s) for %s: %s.' => array(
|
||||
array(
|
||||
'%s removed a watcher for %3$s: %4$s.',
|
||||
'%s removed watchers for %3$s: %4$s.',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -45,7 +45,11 @@ final class PhabricatorRemarkupControl extends AphrontFormTextAreaControl {
|
|||
$root_id = celerity_generate_unique_node_id();
|
||||
|
||||
$user_datasource = new PhabricatorPeopleDatasource();
|
||||
$proj_datasource = new PhabricatorProjectDatasource();
|
||||
$proj_datasource = id(new PhabricatorProjectDatasource())
|
||||
->setParameters(
|
||||
array(
|
||||
'autocomplete' => 1,
|
||||
));
|
||||
|
||||
Javelin::initBehavior(
|
||||
'phabricator-remarkup-assist',
|
||||
|
|
|
@ -73,12 +73,16 @@ final class PHUICrumbView extends AphrontView {
|
|||
->setIcon($this->icon);
|
||||
}
|
||||
|
||||
// Surround the crumb name with spaces so that double clicking it only
|
||||
// selects the crumb itself.
|
||||
$name = array(' ', $this->name, ' ');
|
||||
|
||||
$name = phutil_tag(
|
||||
'span',
|
||||
array(
|
||||
'class' => 'phui-crumb-name',
|
||||
),
|
||||
$this->name);
|
||||
$name);
|
||||
|
||||
$divider = null;
|
||||
if (!$this->isLastCrumb) {
|
||||
|
|
|
@ -52,6 +52,15 @@ final class PHUICrumbsView extends AphrontView {
|
|||
if ($this->actions) {
|
||||
$actions = array();
|
||||
foreach ($this->actions as $action) {
|
||||
if ($action->getType() == PHUIListItemView::TYPE_DIVIDER) {
|
||||
$actions[] = phutil_tag(
|
||||
'span',
|
||||
array(
|
||||
'class' => 'phui-crumb-action-divider',
|
||||
));
|
||||
continue;
|
||||
}
|
||||
|
||||
$icon = null;
|
||||
if ($action->getIcon()) {
|
||||
$icon_name = $action->getIcon();
|
||||
|
@ -63,19 +72,26 @@ final class PHUICrumbsView extends AphrontView {
|
|||
->setIcon($icon_name);
|
||||
|
||||
}
|
||||
$name = phutil_tag(
|
||||
'span',
|
||||
array(
|
||||
'class' => 'phui-crumbs-action-name',
|
||||
),
|
||||
$action->getName());
|
||||
|
||||
$action_classes = $action->getClasses();
|
||||
$action_classes[] = 'phui-crumbs-action';
|
||||
|
||||
$name = null;
|
||||
if ($action->getName()) {
|
||||
$name = phutil_tag(
|
||||
'span',
|
||||
array(
|
||||
'class' => 'phui-crumbs-action-name',
|
||||
),
|
||||
$action->getName());
|
||||
} else {
|
||||
$action_classes[] = 'phui-crumbs-action-icon';
|
||||
}
|
||||
|
||||
$action_sigils = $action->getSigils();
|
||||
if ($action->getWorkflow()) {
|
||||
$action_sigils[] = 'workflow';
|
||||
}
|
||||
$action_classes = $action->getClasses();
|
||||
$action_classes[] = 'phui-crumbs-action';
|
||||
|
||||
if ($action->getDisabled()) {
|
||||
$action_classes[] = 'phui-crumbs-action-disabled';
|
||||
|
|
|
@ -22,6 +22,7 @@ final class PHUIHeaderView extends AphrontTagView {
|
|||
private $epoch;
|
||||
private $actionIcons = array();
|
||||
private $badges = array();
|
||||
private $href;
|
||||
|
||||
public function setHeader($header) {
|
||||
$this->header = $header;
|
||||
|
@ -147,6 +148,15 @@ final class PHUIHeaderView extends AphrontTagView {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function setHref($href) {
|
||||
$this->href = $href;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getHref() {
|
||||
return $this->href;
|
||||
}
|
||||
|
||||
protected function getTagName() {
|
||||
return 'div';
|
||||
}
|
||||
|
@ -290,12 +300,25 @@ final class PHUIHeaderView extends AphrontTagView {
|
|||
->setIcon($this->headerIcon);
|
||||
$left[] = $icon;
|
||||
}
|
||||
|
||||
$header_content = $this->header;
|
||||
|
||||
$href = $this->getHref();
|
||||
if ($href !== null) {
|
||||
$header_content = phutil_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => $href,
|
||||
),
|
||||
$header_content);
|
||||
}
|
||||
|
||||
$left[] = phutil_tag(
|
||||
'span',
|
||||
array(
|
||||
'class' => 'phui-header-header',
|
||||
),
|
||||
$this->header);
|
||||
$header_content);
|
||||
|
||||
if ($this->subheader || $this->badges) {
|
||||
$badges = null;
|
||||
|
|
|
@ -27,6 +27,7 @@ final class PHUIObjectItemView extends AphrontTagView {
|
|||
private $countdownNum;
|
||||
private $countdownNoun;
|
||||
private $launchButton;
|
||||
private $coverImage;
|
||||
|
||||
const AGE_FRESH = 'fresh';
|
||||
const AGE_STALE = 'stale';
|
||||
|
@ -150,6 +151,11 @@ final class PHUIObjectItemView extends AphrontTagView {
|
|||
return $this->imageIcon;
|
||||
}
|
||||
|
||||
public function setCoverImage($image) {
|
||||
$this->coverImage = $image;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setState($state) {
|
||||
$this->state = $state;
|
||||
switch ($state) {
|
||||
|
@ -720,16 +726,45 @@ final class PHUIObjectItemView extends AphrontTagView {
|
|||
$actions);
|
||||
}
|
||||
|
||||
return phutil_tag(
|
||||
$frame_content = phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'phui-object-item-frame',
|
||||
'class' => 'phui-object-item-frame-content',
|
||||
),
|
||||
array(
|
||||
$actions,
|
||||
$image,
|
||||
$box,
|
||||
));
|
||||
|
||||
$frame_cover = null;
|
||||
if ($this->coverImage) {
|
||||
$cover_image = phutil_tag(
|
||||
'img',
|
||||
array(
|
||||
'src' => $this->coverImage,
|
||||
'class' => 'phui-object-item-cover-image',
|
||||
));
|
||||
|
||||
$frame_cover = phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'phui-object-item-frame-cover',
|
||||
),
|
||||
$cover_image);
|
||||
}
|
||||
|
||||
$frame = phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'phui-object-item-frame',
|
||||
),
|
||||
array(
|
||||
$frame_cover,
|
||||
$frame_content,
|
||||
));
|
||||
|
||||
return $frame;
|
||||
}
|
||||
|
||||
private function renderStatusIcon($icon, $label) {
|
||||
|
|
79
src/view/phui/PHUISegmentBarSegmentView.php
Normal file
79
src/view/phui/PHUISegmentBarSegmentView.php
Normal file
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
final class PHUISegmentBarSegmentView extends AphrontTagView {
|
||||
|
||||
private $width;
|
||||
private $color;
|
||||
private $position;
|
||||
private $tooltip;
|
||||
|
||||
public function setWidth($width) {
|
||||
$this->width = $width;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getWidth() {
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
public function setColor($color) {
|
||||
$this->color = $color;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setPosition($position) {
|
||||
$this->position = $position;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setTooltip($tooltip) {
|
||||
$this->tooltip = $tooltip;
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function canAppendChild() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getTagAttributes() {
|
||||
$classes = array(
|
||||
'phui-segment-bar-segment-view',
|
||||
);
|
||||
|
||||
if ($this->color) {
|
||||
$classes[] = $this->color;
|
||||
}
|
||||
|
||||
// Convert width to a percentage, and round it up slightly so that bars
|
||||
// are full if they have, e.g., three segments at 1/3 + 1/3 + 1/3.
|
||||
$width = 100 * $this->width;
|
||||
$width = ceil(100 * $width) / 100;
|
||||
$width = sprintf('%.2f%%', $width);
|
||||
|
||||
$left = 100 * $this->position;
|
||||
$left = floor(100 * $left) / 100;
|
||||
$left = sprintf('%.2f%%', $left);
|
||||
|
||||
$tooltip = $this->tooltip;
|
||||
if (strlen($tooltip)) {
|
||||
Javelin::initBehavior('phabricator-tooltips');
|
||||
|
||||
$sigil = 'has-tooltip';
|
||||
$meta = array(
|
||||
'tip' => $tooltip,
|
||||
'align' => 'E',
|
||||
);
|
||||
} else {
|
||||
$sigil = null;
|
||||
$meta = null;
|
||||
}
|
||||
|
||||
return array(
|
||||
'class' => implode(' ', $classes),
|
||||
'style' => "left: {$left}; width: {$width};",
|
||||
'sigil' => $sigil,
|
||||
'meta' => $meta,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
65
src/view/phui/PHUISegmentBarView.php
Normal file
65
src/view/phui/PHUISegmentBarView.php
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
final class PHUISegmentBarView extends AphrontTagView {
|
||||
|
||||
private $label;
|
||||
private $segments = array();
|
||||
|
||||
public function setLabel($label) {
|
||||
$this->label = $label;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function newSegment() {
|
||||
$segment = new PHUISegmentBarSegmentView();
|
||||
$this->segments[] = $segment;
|
||||
return $segment;
|
||||
}
|
||||
|
||||
protected function canAppendChild() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getTagAttributes() {
|
||||
return array(
|
||||
'class' => 'phui-segment-bar-view',
|
||||
);
|
||||
}
|
||||
|
||||
protected function getTagContent() {
|
||||
require_celerity_resource('phui-segment-bar-view-css');
|
||||
|
||||
$label = $this->label;
|
||||
if (strlen($label)) {
|
||||
$label = phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'phui-segment-bar-label',
|
||||
),
|
||||
$label);
|
||||
}
|
||||
|
||||
$segments = $this->segments;
|
||||
|
||||
$position = 0;
|
||||
foreach ($segments as $segment) {
|
||||
$segment->setPosition($position);
|
||||
$position += $segment->getWidth();
|
||||
}
|
||||
|
||||
$segments = array_reverse($segments);
|
||||
|
||||
$segments = phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'phui-segment-bar-segments',
|
||||
),
|
||||
$segments);
|
||||
|
||||
return array(
|
||||
$label,
|
||||
$segments,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -25,12 +25,13 @@ final class PHUIWorkboardView extends AphrontTagView {
|
|||
$view->addColumn($panel);
|
||||
}
|
||||
|
||||
$board = phutil_tag(
|
||||
$board = javelin_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'phui-workboard-view-shadow',
|
||||
),
|
||||
$view);
|
||||
array(
|
||||
'class' => 'phui-workboard-view-shadow',
|
||||
'sigil' => 'workboard-shadow lock-scroll-y-while-dragging',
|
||||
),
|
||||
$view);
|
||||
|
||||
return $board;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ final class PHUIWorkpanelView extends AphrontTagView {
|
|||
private $headerActions = array();
|
||||
private $headerTag;
|
||||
private $headerIcon;
|
||||
private $href;
|
||||
|
||||
public function setHeaderIcon($icon) {
|
||||
$this->headerIcon = $icon;
|
||||
|
@ -49,6 +50,15 @@ final class PHUIWorkpanelView extends AphrontTagView {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function setHref($href) {
|
||||
$this->href = $href;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getHref() {
|
||||
return $this->href;
|
||||
}
|
||||
|
||||
protected function getTagAttributes() {
|
||||
return array(
|
||||
'class' => 'phui-workpanel-view',
|
||||
|
@ -85,13 +95,20 @@ final class PHUIWorkpanelView extends AphrontTagView {
|
|||
$header->addActionIcon($action);
|
||||
}
|
||||
|
||||
$href = $this->getHref();
|
||||
if ($href !== null) {
|
||||
$header->setHref($href);
|
||||
}
|
||||
|
||||
$body = phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'phui-workpanel-body',
|
||||
'class' => 'phui-workpanel-body-content',
|
||||
),
|
||||
$this->cards);
|
||||
|
||||
$body = phutil_tag_div('phui-workpanel-body', $body);
|
||||
|
||||
$view = id(new PHUIBoxView())
|
||||
->setColor(PHUIBoxView::GREY)
|
||||
->addClass('phui-workpanel-view-inner')
|
||||
|
|
|
@ -10,13 +10,17 @@
|
|||
background-color: #fff;
|
||||
}
|
||||
|
||||
.jx-client-dialog .aphront-dialog-view {
|
||||
box-shadow: {$dropshadow};
|
||||
}
|
||||
|
||||
.device-phone .aphront-dialog-view {
|
||||
margin: 16px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.aphront-dialog-view-standalone {
|
||||
margin: auto;
|
||||
margin: 32px auto;
|
||||
}
|
||||
|
||||
.aphront-dialog-head {
|
||||
|
@ -33,6 +37,7 @@
|
|||
|
||||
.aphront-dialog-view-width-full {
|
||||
width: 90%;
|
||||
max-width: 1040px;
|
||||
}
|
||||
|
||||
.aphront-dialog-body {
|
||||
|
|
|
@ -136,18 +136,6 @@ a.handle-availability-disabled {
|
|||
right: 0;
|
||||
}
|
||||
|
||||
/* Fixes so pages actually print when magic scrollbar is present */
|
||||
!print .main-page-frame {
|
||||
position: static;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
!print .jx-scrollbar-viewport {
|
||||
position: static;
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.jx-scrollbar-test {
|
||||
position: absolute;
|
||||
left: -300px;
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
.project-card-view .phui-header-shell {
|
||||
margin: 0;
|
||||
padding: 12px 12px 16px 12px;
|
||||
padding: 12px 12px 4px 12px;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
@ -43,6 +43,7 @@
|
|||
.project-card-view .phui-header-subheader {
|
||||
font-size: {$normalfontsize};
|
||||
margin-top: 12px;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.project-card-view .phui-header-header .phui-tag-view {
|
||||
|
@ -63,6 +64,10 @@
|
|||
color: {$bluetext};
|
||||
}
|
||||
|
||||
.project-card-view .project-card-body {
|
||||
padding: 0 12px 12px 76px;
|
||||
color: {$darkbluetext};
|
||||
}
|
||||
|
||||
/* Colors */
|
||||
|
||||
|
|
|
@ -110,10 +110,6 @@ div.phui-calendar-day-event {
|
|||
z-index: 9;
|
||||
}
|
||||
|
||||
.drag-frame {
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.jx-mask {
|
||||
z-index: 10;
|
||||
}
|
||||
|
@ -142,6 +138,10 @@ div.jx-typeahead-results {
|
|||
z-index: 15;
|
||||
}
|
||||
|
||||
.drag-frame {
|
||||
z-index: 16;
|
||||
}
|
||||
|
||||
.jx-hovercard-container {
|
||||
z-index: 17;
|
||||
}
|
||||
|
|
|
@ -89,12 +89,15 @@
|
|||
}
|
||||
|
||||
a.phui-crumbs-action .phui-icon-view {
|
||||
margin-right: 5px;
|
||||
color: {$darkbluetext};
|
||||
}
|
||||
|
||||
a.phui-crumbs-action .phui-crumbs-action-name {
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
.device-phone a.phui-crumbs-action .phui-icon-view {
|
||||
margin-left: 5px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.phui-crumb-divider {
|
||||
|
@ -112,3 +115,11 @@ body .phui-crumbs-view + .phui-object-box {
|
|||
body .phui-crumbs-view + .phui-object-item-list-view {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.phui-crumb-action-divider {
|
||||
border-left: 1px solid {$lightgreyborder};
|
||||
}
|
||||
|
||||
.phui-crumbs-action-icon + .phui-crumbs-action-icon {
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
|
|
@ -56,6 +56,10 @@ ul.phui-object-item-list-view {
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
.phui-object-item-cover-image {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.phui-object-item-no-bar .phui-object-item-frame {
|
||||
border-width: 1px;
|
||||
}
|
||||
|
@ -303,7 +307,7 @@ ul.phui-object-item-list-view {
|
|||
.phui-object-item-attribute {
|
||||
display: inline-block;
|
||||
color: {$greytext};
|
||||
vertical-align: middle;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.phui-object-item-attribute-spacer {
|
||||
|
|
|
@ -149,6 +149,25 @@
|
|||
color: {$menu.profile.text};
|
||||
}
|
||||
|
||||
.phui-profile-menu .phabricator-side-menu .phui-profile-menu-error {
|
||||
color: {$greytext};
|
||||
font-size: {$smallerfontsize};
|
||||
padding: 18px 15px;
|
||||
}
|
||||
|
||||
.phui-profile-menu .phabricator-side-menu .phui-profile-segment-bar {
|
||||
color: {$menu.profile.text};
|
||||
font-size: {$smallerfontsize};
|
||||
-webkit-font-smoothing: antialiased;
|
||||
padding: 8px 12px 16px;
|
||||
}
|
||||
|
||||
.phui-profile-menu .phui-profile-menu-collapsed .phabricator-side-menu
|
||||
.phui-profile-segment-bar {
|
||||
padding: 8px 8px 16px;
|
||||
}
|
||||
|
||||
|
||||
.phui-profile-menu .phabricator-side-menu .phui-profile-menu-spacer {
|
||||
box-sizing: border-box;
|
||||
height: {$menu.profile.item.height};
|
||||
|
@ -300,3 +319,7 @@
|
|||
max-width: {$menu.profile.width};
|
||||
}
|
||||
}
|
||||
|
||||
!print .phui-profile-menu .phabricator-side-menu {
|
||||
display: none;
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue