diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 8fc7cdeb50..d26e7a9ad9 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1926,6 +1926,7 @@ phutil_register_library_map(array( 'PhabricatorProjectBoardController' => 'applications/project/controller/PhabricatorProjectBoardController.php', 'PhabricatorProjectBoardDeleteController' => 'applications/project/controller/PhabricatorProjectBoardDeleteController.php', 'PhabricatorProjectBoardEditController' => 'applications/project/controller/PhabricatorProjectBoardEditController.php', + 'PhabricatorProjectBoardImportController' => 'applications/project/controller/PhabricatorProjectBoardImportController.php', 'PhabricatorProjectBoardReorderController' => 'applications/project/controller/PhabricatorProjectBoardReorderController.php', 'PhabricatorProjectBoardViewController' => 'applications/project/controller/PhabricatorProjectBoardViewController.php', 'PhabricatorProjectColumn' => 'applications/project/storage/PhabricatorProjectColumn.php', @@ -2620,6 +2621,7 @@ phutil_register_library_map(array( 'PonderVoteSaveController' => 'applications/ponder/controller/PonderVoteSaveController.php', 'ProjectBoardTaskCard' => 'applications/project/view/ProjectBoardTaskCard.php', 'ProjectConduitAPIMethod' => 'applications/project/conduit/ProjectConduitAPIMethod.php', + 'ProjectCreateConduitAPIMethod' => 'applications/project/conduit/ProjectCreateConduitAPIMethod.php', 'ProjectCreateProjectsCapability' => 'applications/project/capability/ProjectCreateProjectsCapability.php', 'ProjectQueryConduitAPIMethod' => 'applications/project/conduit/ProjectQueryConduitAPIMethod.php', 'ProjectRemarkupRule' => 'applications/project/remarkup/ProjectRemarkupRule.php', @@ -4752,6 +4754,7 @@ phutil_register_library_map(array( 'PhabricatorProjectBoardController' => 'PhabricatorProjectController', 'PhabricatorProjectBoardDeleteController' => 'PhabricatorProjectBoardController', 'PhabricatorProjectBoardEditController' => 'PhabricatorProjectBoardController', + 'PhabricatorProjectBoardImportController' => 'PhabricatorProjectBoardController', 'PhabricatorProjectBoardReorderController' => 'PhabricatorProjectBoardController', 'PhabricatorProjectBoardViewController' => 'PhabricatorProjectBoardController', 'PhabricatorProjectColumn' => array( @@ -5561,6 +5564,7 @@ phutil_register_library_map(array( 'PonderVoteEditor' => 'PhabricatorEditor', 'PonderVoteSaveController' => 'PonderController', 'ProjectConduitAPIMethod' => 'ConduitAPIMethod', + 'ProjectCreateConduitAPIMethod' => 'ProjectConduitAPIMethod', 'ProjectCreateProjectsCapability' => 'PhabricatorPolicyCapability', 'ProjectQueryConduitAPIMethod' => 'ProjectConduitAPIMethod', 'ProjectRemarkupRule' => 'PhabricatorObjectRemarkupRule', diff --git a/src/applications/project/application/PhabricatorProjectApplication.php b/src/applications/project/application/PhabricatorProjectApplication.php index f6c2c68923..c5d484157d 100644 --- a/src/applications/project/application/PhabricatorProjectApplication.php +++ b/src/applications/project/application/PhabricatorProjectApplication.php @@ -71,6 +71,8 @@ final class PhabricatorProjectApplication extends PhabricatorApplication { => 'PhabricatorProjectBoardDeleteController', 'column/(?:(?P\d+)/)?' => 'PhabricatorProjectColumnDetailController', + 'import/' + => 'PhabricatorProjectBoardImportController', 'reorder/' => 'PhabricatorProjectBoardReorderController', ), diff --git a/src/applications/project/controller/PhabricatorProjectBoardImportController.php b/src/applications/project/controller/PhabricatorProjectBoardImportController.php new file mode 100644 index 0000000000..75823b3308 --- /dev/null +++ b/src/applications/project/controller/PhabricatorProjectBoardImportController.php @@ -0,0 +1,85 @@ +projectID = $data['projectID']; + } + + public function processRequest() { + $request = $this->getRequest(); + $viewer = $request->getUser(); + + $project = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->withIDs(array($this->projectID)) + ->executeOne(); + if (!$project) { + return new Aphront404Response(); + } + $this->setProject($project); + + $columns = id(new PhabricatorProjectColumnQuery()) + ->setViewer($viewer) + ->withProjectPHIDs(array($project->getPHID())) + ->execute(); + if ($columns) { + return new Aphront400Response(); + } + + $project_id = $project->getID(); + $board_uri = $this->getApplicationURI("board/{$project_id}/"); + + if ($request->isFormPost()) { + $import_phid = $request->getArr('importProjectPHID'); + $import_phid = reset($import_phid); + + $import_columns = id(new PhabricatorProjectColumnQuery()) + ->setViewer($viewer) + ->withProjectPHIDs(array($import_phid)) + ->execute(); + if (!$import_columns) { + return new Aphront400Response(); + } + + $table = id(new PhabricatorProjectColumn()) + ->openTransaction(); + foreach ($import_columns as $import_column) { + if ($import_column->isHidden()) { + continue; + } + $new_column = PhabricatorProjectColumn::initializeNewColumn($viewer) + ->setSequence($import_column->getSequence()) + ->setProjectPHID($project->getPHID()) + ->setName($import_column->getName()) + ->save(); + } + $table->saveTransaction(); + + return id(new AphrontRedirectResponse())->setURI($board_uri); + } + + $proj_selector = id(new AphrontFormTokenizerControl()) + ->setName('importProjectPHID') + ->setUser($viewer) + ->setDatasource(id(new PhabricatorProjectDatasource()) + ->setParameters(array('mustHaveColumns' => true)) + ->setLimit(1)); + return $this->newDialog() + ->setTitle(pht('Import Columns')) + ->setWidth(AphrontDialogView::WIDTH_FORM) + ->appendParagraph(pht('Choose a project to import columns from:')) + ->appendChild($proj_selector) + ->addCancelButton($board_uri) + ->addSubmitButton(pht('Import')); + } + +} diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php index c61037e770..b9a173a987 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php @@ -54,16 +54,27 @@ final class PhabricatorProjectBoardViewController $columns = $column_query->execute(); $columns = mpull($columns, null, 'getSequence'); - // If there's no default column, create one now. if (empty($columns[0])) { - $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); - $column = PhabricatorProjectColumn::initializeNewColumn($viewer) - ->setSequence(0) - ->setProjectPHID($project->getPHID()) - ->save(); - $column->attachProject($project); - $columns[0] = $column; - unset($unguarded); + switch ($request->getStr('initialize-type')) { + case 'backlog-only': + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + $column = PhabricatorProjectColumn::initializeNewColumn($viewer) + ->setSequence(0) + ->setProjectPHID($project->getPHID()) + ->save(); + $column->attachProject($project); + $columns[0] = $column; + unset($unguarded); + break; + case 'import': + return id(new AphrontRedirectResponse()) + ->setURI( + $this->getApplicationURI('board/'.$project->getID().'/import/')); + break; + default: + return $this->initializeWorkboardDialog($project); + break; + } } ksort($columns); @@ -406,5 +417,31 @@ final class PhabricatorProjectBoardViewController return $manage_button; } + private function initializeWorkboardDialog(PhabricatorProject $project) { + + $instructions = pht('This workboard has not been setup yet.'); + $new_selector = id(new AphrontFormRadioButtonControl()) + ->setName('initialize-type') + ->setValue('backlog-only') + ->addButton( + 'backlog-only', + pht('New Empty Board'), + pht('Create a new board with just a backlog column.')) + ->addButton( + 'import', + pht('Import Columns'), + pht('Import board columns from another project.')); + + $dialog = id(new AphrontDialogView()) + ->setUser($this->getRequest()->getUser()) + ->setTitle(pht('New Workboard')) + ->addSubmitButton('Continue') + ->addCancelButton($this->getApplicationURI('view/'.$project->getID().'/')) + ->appendParagraph($instructions) + ->appendChild($new_selector); + + return id(new AphrontDialogResponse()) + ->setDialog($dialog); + } } diff --git a/src/applications/project/typeahead/PhabricatorProjectDatasource.php b/src/applications/project/typeahead/PhabricatorProjectDatasource.php index e57e3740ce..4b31e4bc80 100644 --- a/src/applications/project/typeahead/PhabricatorProjectDatasource.php +++ b/src/applications/project/typeahead/PhabricatorProjectDatasource.php @@ -29,9 +29,24 @@ final class PhabricatorProjectDatasource ->needSlugs(true) ->withDatasourceQuery($raw_query) ->execute(); + $projs = mpull($projs, null, 'getPHID'); + + $must_have_cols = $this->getParameter('mustHaveColumns', false); + if ($must_have_cols) { + $columns = id(new PhabricatorProjectColumnQuery()) + ->setViewer($viewer) + ->withProjectPHIDs(array_keys($projs)) + ->execute(); + $has_cols = mgroup($columns, 'getProjectPHID'); + } else { + $has_cols = array_fill_keys(array_keys($projs), true); + } $results = array(); foreach ($projs as $proj) { + if (!isset($has_cols[$proj->getPHID()])) { + continue; + } $closed = null; if ($proj->isArchived()) { $closed = pht('Archived');