@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add a secret board view to Projects

Summary:
Ref T1344. This is //very// rough. Some UI issues:

- Empty states for the board and columns are junky.
- Column widths are crazy. I think we need to set them to fixed-width, since we may have an arbitrarily large number of columns?
- I don't think we have the header UI elements in M10 yet and that mock is pretty old, so I sort of very roughly approximated it.
- What should we do when you click a task title? Popping the whole task in a dialog is possible but needs a bunch of work to actually work. Might need to build "sheets" or something.
- Icons are slightly clipped for some reason.
- All the backend stuff is totally faked.

Generally, my plan is just to use these to implement all of T390. Specifically:

- "Kanban" projects will have "Backlog" on the left. You'll drag them toward the right as you make progress.
- "Milestone" projects will have "No Milestone" on the left, then "Milestone 9", "Milestone 8", etc.
- "Sprint" projects will have "Backlog" on the left, then "Sprint 31", "Sprint 30", etc.

So all of these things end up being pretty much exactly the same, with some minor text changes and new columns showing up on the left vs the right or whatever.

Test Plan: See screenshot.

Reviewers: chad, btrahan

Reviewed By: btrahan

CC: chad, aran, sascha-egerer

Maniphest Tasks: T1344

Differential Revision: https://secure.phabricator.com/D7374

+385 -4
+9
resources/sql/patches/20131020.col1.sql
··· 1 + CREATE TABLE {$NAMESPACE}_project.project_column ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + phid VARCHAR(64) NOT NULL COLLATE utf8_bin, 4 + name VARCHAR(255) NOT NULL, 5 + sequence INT UNSIGNED NOT NULL, 6 + projectPHID VARCHAR(64) NOT NULL COLLATE utf8_bin, 7 + UNIQUE KEY `key_sequence` (projectPHID, sequence), 8 + UNIQUE KEY `key_phid` (phid) 9 + ) ENGINE=InnoDB, COLLATE utf8_general_ci;
+1 -1
src/__celerity_resource_map__.php
··· 3966 3966 ), 3967 3967 'phui-workboard-view-css' => 3968 3968 array( 3969 - 'uri' => '/res/628679e5/rsrc/css/phui/phui-workboard-view.css', 3969 + 'uri' => '/res/445c7c7e/rsrc/css/phui/phui-workboard-view.css', 3970 3970 'type' => 'css', 3971 3971 'requires' => 3972 3972 array(
+18
src/__phutil_library_map__.php
··· 1515 1515 'PhabricatorPolicyTestObject' => 'applications/policy/__tests__/PhabricatorPolicyTestObject.php', 1516 1516 'PhabricatorPolicyType' => 'applications/policy/constants/PhabricatorPolicyType.php', 1517 1517 'PhabricatorProject' => 'applications/project/storage/PhabricatorProject.php', 1518 + 'PhabricatorProjectBoardController' => 'applications/project/controller/PhabricatorProjectBoardController.php', 1519 + 'PhabricatorProjectColumn' => 'applications/project/storage/PhabricatorProjectColumn.php', 1520 + 'PhabricatorProjectColumnQuery' => 'applications/project/query/PhabricatorProjectColumnQuery.php', 1518 1521 'PhabricatorProjectConstants' => 'applications/project/constants/PhabricatorProjectConstants.php', 1519 1522 'PhabricatorProjectController' => 'applications/project/controller/PhabricatorProjectController.php', 1520 1523 'PhabricatorProjectCreateController' => 'applications/project/controller/PhabricatorProjectCreateController.php', 1524 + 'PhabricatorProjectCustomFieldNumericIndex' => 'applications/project/storage/PhabricatorProjectCustomFieldNumericIndex.php', 1525 + 'PhabricatorProjectCustomFieldStorage' => 'applications/project/storage/PhabricatorProjectCustomFieldStorage.php', 1526 + 'PhabricatorProjectCustomFieldStringIndex' => 'applications/project/storage/PhabricatorProjectCustomFieldStringIndex.php', 1521 1527 'PhabricatorProjectDAO' => 'applications/project/storage/PhabricatorProjectDAO.php', 1522 1528 'PhabricatorProjectEditor' => 'applications/project/editor/PhabricatorProjectEditor.php', 1523 1529 'PhabricatorProjectEditorTestCase' => 'applications/project/editor/__tests__/PhabricatorProjectEditorTestCase.php', 1524 1530 'PhabricatorProjectListController' => 'applications/project/controller/PhabricatorProjectListController.php', 1525 1531 'PhabricatorProjectMembersEditController' => 'applications/project/controller/PhabricatorProjectMembersEditController.php', 1526 1532 'PhabricatorProjectNameCollisionException' => 'applications/project/exception/PhabricatorProjectNameCollisionException.php', 1533 + 'PhabricatorProjectPHIDTypeColumn' => 'applications/project/phid/PhabricatorProjectPHIDTypeColumn.php', 1527 1534 'PhabricatorProjectPHIDTypeProject' => 'applications/project/phid/PhabricatorProjectPHIDTypeProject.php', 1528 1535 'PhabricatorProjectProfile' => 'applications/project/storage/PhabricatorProjectProfile.php', 1529 1536 'PhabricatorProjectProfileController' => 'applications/project/controller/PhabricatorProjectProfileController.php', ··· 3733 3740 0 => 'PhabricatorProjectDAO', 3734 3741 1 => 'PhabricatorPolicyInterface', 3735 3742 ), 3743 + 'PhabricatorProjectBoardController' => 'PhabricatorProjectController', 3744 + 'PhabricatorProjectColumn' => 3745 + array( 3746 + 0 => 'PhabricatorProjectDAO', 3747 + 1 => 'PhabricatorPolicyInterface', 3748 + ), 3749 + 'PhabricatorProjectColumnQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 3736 3750 'PhabricatorProjectController' => 'PhabricatorController', 3737 3751 'PhabricatorProjectCreateController' => 'PhabricatorProjectController', 3752 + 'PhabricatorProjectCustomFieldNumericIndex' => 'PhabricatorCustomFieldNumericIndexStorage', 3753 + 'PhabricatorProjectCustomFieldStorage' => 'PhabricatorCustomFieldStorage', 3754 + 'PhabricatorProjectCustomFieldStringIndex' => 'PhabricatorCustomFieldStringIndexStorage', 3738 3755 'PhabricatorProjectDAO' => 'PhabricatorLiskDAO', 3739 3756 'PhabricatorProjectEditor' => 'PhabricatorEditor', 3740 3757 'PhabricatorProjectEditorTestCase' => 'PhabricatorTestCase', ··· 3745 3762 ), 3746 3763 'PhabricatorProjectMembersEditController' => 'PhabricatorProjectController', 3747 3764 'PhabricatorProjectNameCollisionException' => 'Exception', 3765 + 'PhabricatorProjectPHIDTypeColumn' => 'PhabricatorPHIDType', 3748 3766 'PhabricatorProjectPHIDTypeProject' => 'PhabricatorPHIDType', 3749 3767 'PhabricatorProjectProfile' => 'PhabricatorProjectDAO', 3750 3768 'PhabricatorProjectProfileController' => 'PhabricatorProjectController',
+1 -1
src/applications/maniphest/controller/ManiphestTaskEditController.php
··· 609 609 ->setPreviewURI($this->getApplicationURI('task/descriptionpreview/')); 610 610 611 611 if ($task->getID()) { 612 - $page_objects = array( $task->getPHID() ); 612 + $page_objects = array($task->getPHID()); 613 613 } else { 614 614 $page_objects = array(); 615 615 }
+1
src/applications/project/application/PhabricatorApplicationProject.php
··· 45 45 'picture/(?P<id>[1-9]\d*)/' => 46 46 'PhabricatorProjectProfilePictureController', 47 47 'create/' => 'PhabricatorProjectCreateController', 48 + 'board/(?P<id>[1-9]\d*)/' => 'PhabricatorProjectBoardController', 48 49 'update/(?P<id>[1-9]\d*)/(?P<action>[^/]+)/' 49 50 => 'PhabricatorProjectUpdateController', 50 51 ),
+152
src/applications/project/controller/PhabricatorProjectBoardController.php
··· 1 + <?php 2 + 3 + final class PhabricatorProjectBoardController 4 + extends PhabricatorProjectController { 5 + 6 + private $id; 7 + 8 + public function shouldAllowPublic() { 9 + return true; 10 + } 11 + 12 + public function willProcessRequest(array $data) { 13 + $this->id = $data['id']; 14 + } 15 + 16 + public function processRequest() { 17 + $request = $this->getRequest(); 18 + $viewer = $request->getUser(); 19 + 20 + $project = id(new PhabricatorProjectQuery()) 21 + ->setViewer($viewer) 22 + ->withIDs(array($this->id)) 23 + ->executeOne(); 24 + if (!$project) { 25 + return new Aphront404Response(); 26 + } 27 + 28 + $columns = id(new PhabricatorProjectColumnQuery()) 29 + ->setViewer($viewer) 30 + ->withProjectPHIDs(array($project->getPHID())) 31 + ->execute(); 32 + 33 + // TODO: Completely making this part up. 34 + $columns[] = id(new PhabricatorProjectColumn()) 35 + ->setName('Backlog') 36 + ->setPHID(0) 37 + ->setSequence(0); 38 + 39 + $columns[] = id(new PhabricatorProjectColumn()) 40 + ->setName('Assigned') 41 + ->setPHID(1) 42 + ->setSequence(1); 43 + 44 + $columns[] = id(new PhabricatorProjectColumn()) 45 + ->setName('In Progress') 46 + ->setPHID(2) 47 + ->setSequence(2); 48 + 49 + $columns[] = id(new PhabricatorProjectColumn()) 50 + ->setName('Completed') 51 + ->setPHID(3) 52 + ->setSequence(3); 53 + 54 + msort($columns, 'getSequence'); 55 + 56 + $tasks = id(new ManiphestTaskQuery()) 57 + ->setViewer($viewer) 58 + ->withAllProjects(array($project->getPHID())) 59 + ->withStatus(ManiphestTaskQuery::STATUS_OPEN) 60 + ->setOrderBy(ManiphestTaskQuery::ORDER_PRIORITY) 61 + ->execute(); 62 + $tasks = mpull($tasks, null, 'getPHID'); 63 + 64 + // TODO: This is also made up. 65 + $task_map = array(); 66 + foreach ($tasks as $task) { 67 + $task_map[mt_rand(0, 3)][] = $task->getPHID(); 68 + } 69 + 70 + $board = id(new PHUIWorkboardView()) 71 + ->setUser($viewer); 72 + 73 + foreach ($columns as $column) { 74 + $panel = id(new PHUIWorkpanelView()) 75 + ->setHeader($column->getName()); 76 + 77 + $cards = id(new PHUIObjectItemListView()) 78 + ->setUser($viewer) 79 + ->setCards(true) 80 + ->setFlush(true); 81 + $task_phids = idx($task_map, $column->getPHID(), array()); 82 + foreach (array_select_keys($tasks, $task_phids) as $task) { 83 + $cards->addItem($this->renderTaskCard($task)); 84 + } 85 + $panel->setCards($cards); 86 + 87 + $board->addPanel($panel); 88 + } 89 + 90 + $crumbs = $this->buildApplicationCrumbs(); 91 + 92 + $actions = id(new PhabricatorActionListView()) 93 + ->setUser($viewer) 94 + ->addAction( 95 + id(new PhabricatorActionView()) 96 + ->setName(pht('Add Column/Milestone/Sprint')) 97 + ->setHref($this->getApplicationURI('board/'.$this->id.'/edit/')) 98 + ->setIcon('create')); 99 + 100 + $plist = id(new PHUIPropertyListView()); 101 + // TODO: Need this to get actions to render. 102 + $plist->addProperty(pht('Ignore'), pht('This Property')); 103 + $plist->setActionList($actions); 104 + 105 + $header = id(new PHUIObjectBoxView()) 106 + ->setHeaderText($project->getName()) 107 + ->addPropertyList($plist); 108 + 109 + $board_box = id(new PHUIBoxView()) 110 + ->appendChild($board) 111 + ->addMargin(PHUI::MARGIN_LARGE); 112 + 113 + return $this->buildApplicationPage( 114 + array( 115 + $crumbs, 116 + $header, 117 + $board_box, 118 + ), 119 + array( 120 + 'title' => pht('Board'), 121 + 'device' => true, 122 + )); 123 + } 124 + 125 + private function renderTaskCard(ManiphestTask $task) { 126 + $request = $this->getRequest(); 127 + $viewer = $request->getUser(); 128 + 129 + $color_map = ManiphestTaskPriority::getColorMap(); 130 + $bar_color = idx($color_map, $task->getPriority(), 'grey'); 131 + 132 + // TODO: Batch this earlier on. 133 + $can_edit = PhabricatorPolicyFilter::hasCapability( 134 + $viewer, 135 + $task, 136 + PhabricatorPolicyCapability::CAN_EDIT); 137 + 138 + return id(new PHUIObjectItemView()) 139 + ->setObjectName('T'.$task->getID()) 140 + ->setHeader($task->getTitle()) 141 + ->setGrippable($can_edit) 142 + ->setHref('/T'.$task->getID()) 143 + ->addAction( 144 + id(new PHUIListItemView()) 145 + ->setName(pht('Edit')) 146 + ->setIcon('edit') 147 + ->setHref('/maniphest/task/edit/'.$task->getID().'/') 148 + ->setWorkflow(true)) 149 + ->setBarColor($bar_color); 150 + } 151 + 152 + }
+39
src/applications/project/phid/PhabricatorProjectPHIDTypeColumn.php
··· 1 + <?php 2 + 3 + final class PhabricatorProjectPHIDTypeColumn extends PhabricatorPHIDType { 4 + 5 + const TYPECONST = 'PCOL'; 6 + 7 + public function getTypeConstant() { 8 + return self::TYPECONST; 9 + } 10 + 11 + public function getTypeName() { 12 + return pht('Project Column'); 13 + } 14 + 15 + public function newObject() { 16 + return new PhabricatorProjectColumn(); 17 + } 18 + 19 + protected function buildQueryForObjects( 20 + PhabricatorObjectQuery $query, 21 + array $phids) { 22 + 23 + return id(new PhabricatorProjectColumnQuery()) 24 + ->withPHIDs($phids); 25 + } 26 + 27 + public function loadHandles( 28 + PhabricatorHandleQuery $query, 29 + array $handles, 30 + array $objects) { 31 + 32 + foreach ($handles as $phid => $handle) { 33 + $column = $objects[$phid]; 34 + 35 + $handle->setName($column->getName()); 36 + } 37 + } 38 + 39 + }
+99
src/applications/project/query/PhabricatorProjectColumnQuery.php
··· 1 + <?php 2 + 3 + final class PhabricatorProjectColumnQuery 4 + extends PhabricatorCursorPagedPolicyAwareQuery { 5 + 6 + private $ids; 7 + private $phids; 8 + private $projectPHIDs; 9 + 10 + public function withIDs(array $ids) { 11 + $this->ids = $ids; 12 + return $this; 13 + } 14 + 15 + public function withPHIDs(array $phids) { 16 + $this->phids = $phids; 17 + return $this; 18 + } 19 + 20 + public function withProjectPHIDs(array $project_phids) { 21 + $this->projectPHIDs = $project_phids; 22 + return $this; 23 + } 24 + 25 + protected function loadPage() { 26 + $table = new PhabricatorProjectColumn(); 27 + $conn_r = $table->establishConnection('r'); 28 + 29 + $data = queryfx_all( 30 + $conn_r, 31 + 'SELECT * FROM %T %Q %Q %Q', 32 + $table->getTableName(), 33 + $this->buildWhereClause($conn_r), 34 + $this->buildOrderClause($conn_r), 35 + $this->buildLimitClause($conn_r)); 36 + 37 + return $table->loadAllFromArray($data); 38 + } 39 + 40 + protected function willFilterPage(array $page) { 41 + $projects = array(); 42 + 43 + $project_phids = array_filter(mpull($page, 'getProjectPHID')); 44 + if ($project_phids) { 45 + $projects = id(new PhabricatorProjectQuery()) 46 + ->setParentQuery($this) 47 + ->setViewer($this->getViewer()) 48 + ->withPHIDs($project_phids) 49 + ->execute(); 50 + $projects = mpull($projects, null, 'getPHID'); 51 + } 52 + 53 + foreach ($page as $key => $column) { 54 + $phid = $column->getProjectPHID(); 55 + $project = idx($projects, $phid); 56 + if (!$project) { 57 + unset($page[$key]); 58 + continue; 59 + } 60 + $column->attachProject($project); 61 + } 62 + 63 + return $page; 64 + } 65 + 66 + private function buildWhereClause($conn_r) { 67 + $where = array(); 68 + 69 + if ($this->ids) { 70 + $where[] = qsprintf( 71 + $conn_r, 72 + 'id IN (%Ld)', 73 + $this->ids); 74 + } 75 + 76 + if ($this->phids) { 77 + $where[] = qsprintf( 78 + $conn_r, 79 + 'phid IN (%Ls)', 80 + $this->phids); 81 + } 82 + 83 + if ($this->projectPHIDs) { 84 + $where[] = qsprintf( 85 + $conn_r, 86 + 'projectPHID IN (%Ls)', 87 + $this->projectPHIDs); 88 + } 89 + 90 + $where[] = $this->buildPagingClause($conn_r); 91 + 92 + return $this->formatWhereClause($where); 93 + } 94 + 95 + public function getQueryApplicationClass() { 96 + return 'PhabricatorApplicationProject'; 97 + } 98 + 99 + }
+58
src/applications/project/storage/PhabricatorProjectColumn.php
··· 1 + <?php 2 + 3 + final class PhabricatorProjectColumn 4 + extends PhabricatorProjectDAO 5 + implements PhabricatorPolicyInterface { 6 + 7 + protected $name; 8 + protected $projectPHID; 9 + protected $sequence; 10 + 11 + private $project = self::ATTACHABLE; 12 + 13 + public function getConfiguration() { 14 + return array( 15 + self::CONFIG_AUX_PHID => true, 16 + ) + parent::getConfiguration(); 17 + } 18 + 19 + public function generatePHID() { 20 + return PhabricatorPHID::generateNewPHID( 21 + PhabricatorProjectPHIDTypeColumn::TYPECONST); 22 + } 23 + 24 + public function attachProject(PhabricatorProject $project) { 25 + $this->project = $project; 26 + return $this; 27 + } 28 + 29 + public function getProject() { 30 + return $this->assertAttached($this->project); 31 + } 32 + 33 + 34 + /* -( PhabricatorPolicyInterface )----------------------------------------- */ 35 + 36 + 37 + public function getCapabilities() { 38 + return array( 39 + PhabricatorPolicyCapability::CAN_VIEW, 40 + PhabricatorPolicyCapability::CAN_EDIT, 41 + ); 42 + } 43 + 44 + public function getPolicy($capability) { 45 + return $this->getProject()->getPolicy($capability); 46 + } 47 + 48 + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 49 + return $this->getProject()->hasAutomaticCapability( 50 + $capability, 51 + $viewer); 52 + } 53 + 54 + public function describeAutomaticCapability($capability) { 55 + return pht('Users must be able to see a project to see its board.'); 56 + } 57 + 58 + }
+4
src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
··· 1688 1688 'type' => 'sql', 1689 1689 'name' => $this->getPatchPath('20131020.pcustom.sql'), 1690 1690 ), 1691 + '20131020.col1.sql' => array( 1692 + 'type' => 'sql', 1693 + 'name' => $this->getPatchPath('20131020.col1.sql'), 1694 + ), 1691 1695 ); 1692 1696 } 1693 1697 }
+1 -1
src/view/layout/AphrontMultiColumnView.php
··· 6 6 const GUTTER_MEDIUM = 'mmr'; 7 7 const GUTTER_LARGE = 'mlr'; 8 8 9 - private $column = array(); 9 + private $columns = array(); 10 10 private $fluidLayout = false; 11 11 private $gutter; 12 12 private $shadow;
+1
src/view/phui/PHUIWorkpanelView.php
··· 18 18 } 19 19 20 20 public function setHeaderAction($header_action) { 21 + // TODO: This doesn't do anything? 21 22 $this->headerAction = $header_action; 22 23 return $this; 23 24 }
+1 -1
webroot/rsrc/css/phui/phui-workboard-view.css
··· 9 9 .phui-workboard-view-shadow { 10 10 padding: 8px; 11 11 min-height: 120px; 12 - overflow-x: scroll; 12 + overflow-x: auto; 13 13 border-radius: 5px; 14 14 background: rgba(150,150,150,.1); 15 15 box-shadow: inset 0 0 5px rgba(0,0,0,.5);