diff --git a/src/applications/releeph/controller/project/ReleephProjectCreateController.php b/src/applications/releeph/controller/project/ReleephProjectCreateController.php index 9d4bd89c2f..d478aebe6c 100644 --- a/src/applications/releeph/controller/project/ReleephProjectCreateController.php +++ b/src/applications/releeph/controller/project/ReleephProjectCreateController.php @@ -1,149 +1,171 @@ getRequest(); $name = trim($request->getStr('name')); $trunk_branch = trim($request->getStr('trunkBranch')); $arc_pr_id = $request->getInt('arcPrID'); - - // Only allow arc projects with repositories. Sort and re-key by ID. - $arc_projects = id(new PhabricatorRepositoryArcanistProject())->loadAll(); - $arc_projects = mpull( - msort( - mfilter($arc_projects, 'getRepositoryID'), - 'getName'), - null, - 'getID'); + $arc_projects = $this->loadArcProjects(); $e_name = true; $e_trunk_branch = true; $errors = array(); if ($request->isFormPost()) { if (!$name) { $e_name = pht('Required'); - $errors[] = - pht('Your Releeph project should have a simple descriptive name.'); + $errors[] = pht( + 'Your Releeph project should have a simple descriptive name.'); } if (!$trunk_branch) { $e_trunk_branch = pht('Required'); - $errors[] = - pht('You must specify which branch you will be picking from.'); - } - - $all_names = mpull(id(new ReleephProject())->loadAll(), 'getName'); - - if (in_array($name, $all_names)) { - $errors[] = pht('Releeph project name %s is already taken', $name); + $errors[] = pht( + 'You must specify which branch you will be picking from.'); } $arc_project = $arc_projects[$arc_pr_id]; $pr_repository = $arc_project->loadRepository(); if (!$errors) { $releeph_project = id(new ReleephProject()) ->setName($name) ->setTrunkBranch($trunk_branch) ->setRepositoryID($pr_repository->getID()) ->setRepositoryPHID($pr_repository->getPHID()) ->setArcanistProjectID($arc_project->getID()) ->setCreatedByUserPHID($request->getUser()->getPHID()) - ->setIsActive(1) - ->save(); + ->setIsActive(1); - return id(new AphrontRedirectResponse())->setURI('/releeph/'); + try { + $releeph_project->save(); + + return id(new AphrontRedirectResponse()) + ->setURI($releeph_project->getURI()); + } catch (AphrontQueryDuplicateKeyException $ex) { + $e_name = pht('Not Unique'); + $errors[] = pht( + 'Another project already uses this name.'); + } } } $error_view = null; if ($errors) { $error_view = new AphrontErrorView(); $error_view->setErrors($errors); - $error_view->setTitle(pht('Form Errors')); } - // Make our own optgroup select control - $arc_project_choices = array(); - $pr_repositories = mpull( - msort( - array_filter( - // Some arc-projects don't have repositories - mpull($arc_projects, 'loadRepository')), - 'getName'), - null, - 'getID'); - - foreach ($pr_repositories as $pr_repo_id => $pr_repository) { - $options = array(); - foreach ($arc_projects as $arc_project) { - if ($arc_project->getRepositoryID() == $pr_repo_id) { - $options[$arc_project->getID()] = $arc_project->getName(); - } - } - $arc_project_choices[$pr_repository->getName()] = $options; - } + $arc_project_options = $this->getArcProjectSelectOptions($arc_projects); $project_name_input = id(new AphrontFormTextControl()) ->setLabel(pht('Name')) ->setDisableAutocomplete(true) ->setName('name') ->setValue($name) ->setError($e_name) ->setCaption(pht('A name like "Thrift" but not "Thrift releases".')); $arc_project_input = id(new AphrontFormSelectControl()) ->setLabel(pht('Arc Project')) ->setName('arcPrID') ->setValue($arc_pr_id) ->setCaption(pht( 'If your Arc project isn\'t listed, associate it with a repository %s', phutil_tag( 'a', array( 'href' => '/repository/', 'target' => '_blank', ), 'here'))) - ->setOptions($arc_project_choices); + ->setOptions($arc_project_options); $branch_name_preview = id(new ReleephBranchPreviewView()) ->setLabel(pht('Example Branch')) ->addControl('projectName', $project_name_input) ->addControl('arcProjectID', $arc_project_input) ->addStatic('template', '') ->addStatic('isSymbolic', false); $form = id(new AphrontFormView()) ->setUser($request->getUser()) + ->setFlexible(true) ->appendChild($project_name_input) ->appendChild($arc_project_input) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Trunk')) ->setName('trunkBranch') ->setValue($trunk_branch) ->setError($e_trunk_branch) ->setCaption(pht('The development branch, '. 'from which requests will be picked.'))) ->appendChild($branch_name_preview) ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton('/releeph/project/') ->setValue(pht('Create'))); - $panel = id(new AphrontPanelView()) - ->setHeader(pht('Create Releeph Project')) - ->appendChild($form) - ->setWidth(AphrontPanelView::WIDTH_FORM); + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addCrumb( + id(new PhabricatorCrumbView()) + ->setName(pht('New Project'))); - return $this->buildStandardPageResponse( - array($error_view, $panel), + return $this->buildApplicationPage( + array( + $crumbs, + $error_view, + $form, + ), array( - 'title' => pht('Create New Releeph Project') + 'title' => pht('Create New Project'), + 'dust' => true, + 'device' => true, )); } + + private function loadArcProjects() { + $viewer = $this->getRequest()->getUser(); + + $projects = id(new PhabricatorRepositoryArcanistProjectQuery()) + ->setViewer($viewer) + ->needRepositories(true) + ->execute(); + + $projects = mfilter($projects, 'getRepository'); + $projects = msort($projects, 'getName'); + + return $projects; + } + + private function getArcProjectSelectOptions(array $arc_projects) { + assert_instances_of($arc_projects, 'PhabricatorRepositoryArcanistProject'); + + $repos = mpull($arc_projects, 'getRepository'); + $repos = mpull($repos, null, 'getID'); + + $groups = array(); + foreach ($arc_projects as $arc_project) { + $id = $arc_project->getID(); + $repo_id = $arc_project->getRepository()->getID(); + $groups[$repo_id][$id] = $arc_project->getName(); + } + + $choices = array(); + foreach ($groups as $repo_id => $group) { + $repo_name = $repos[$repo_id]->getName(); + $callsign = $repos[$repo_id]->getCallsign(); + $name = "r{$callsign} ({$repo_name})"; + $choices[$name] = $group; + } + + ksort($choices); + + return $choices; + } + } diff --git a/src/applications/repository/query/PhabricatorRepositoryArcanistProjectQuery.php b/src/applications/repository/query/PhabricatorRepositoryArcanistProjectQuery.php index 1118099e86..9e34bda100 100644 --- a/src/applications/repository/query/PhabricatorRepositoryArcanistProjectQuery.php +++ b/src/applications/repository/query/PhabricatorRepositoryArcanistProjectQuery.php @@ -1,59 +1,84 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } + public function needRepositories($need_repositories) { + $this->needRepositories = $need_repositories; + return $this; + } + protected function loadPage() { $table = new PhabricatorRepositoryArcanistProject(); $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT * FROM %T %Q %Q %Q', $table->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); return $table->loadAllFromArray($data); } + public function willFilterPage(array $projects) { + assert_instances_of($projects, 'PhabricatorRepositoryArcanistProject'); + + if ($this->needRepositories) { + $repository_ids = mpull($projects, 'getRepositoryID'); + $repositories = id(new PhabricatorRepositoryQuery()) + ->setViewer($this->getViewer()) + ->withIDs($repository_ids) + ->execute(); + foreach ($projects as $project) { + $repo = idx($repositories, $project->getRepositoryID()); + $project->attachRepository($repo); + } + } + + return $projects; + } + private function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); if ($this->ids) { $where[] = qsprintf( $conn_r, 'id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn_r, 'phid IN (%Ls)', $this->phids); } $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } } diff --git a/src/applications/repository/storage/PhabricatorRepositoryArcanistProject.php b/src/applications/repository/storage/PhabricatorRepositoryArcanistProject.php index a34a616868..0ce6daafb2 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryArcanistProject.php +++ b/src/applications/repository/storage/PhabricatorRepositoryArcanistProject.php @@ -1,78 +1,90 @@ true, self::CONFIG_TIMESTAMPS => false, self::CONFIG_SERIALIZATION => array( 'symbolIndexLanguages' => self::SERIALIZATION_JSON, 'symbolIndexProjects' => self::SERIALIZATION_JSON, ), ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( PhabricatorRepositoryPHIDTypeArcanistProject::TYPECONST); } + // TODO: Remove. public function loadRepository() { if (!$this->getRepositoryID()) { return null; } return id(new PhabricatorRepository())->load($this->getRepositoryID()); } public function delete() { $this->openTransaction(); queryfx( $this->establishConnection('w'), 'DELETE FROM %T WHERE arcanistProjectID = %d', id(new PhabricatorRepositorySymbol())->getTableName(), $this->getID()); $result = parent::delete(); $this->saveTransaction(); return $result; } + public function getRepository() { + return $this->assertAttached($this->repository); + } + + public function attachRepository(PhabricatorRepository $repository = null) { + $this->repository = $repository; + return $this; + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return PhabricatorPolicies::POLICY_USER; case PhabricatorPolicyCapability::CAN_EDIT: return PhabricatorPolicies::POLICY_ADMIN; } } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return false; } }