diff --git a/src/applications/project/controller/PhabricatorProjectBoardImportController.php b/src/applications/project/controller/PhabricatorProjectBoardImportController.php index c344bc0af0..67bddaaa52 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardImportController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardImportController.php @@ -1,98 +1,113 @@ 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); + $project_id = $project->getID(); + $board_uri = $this->getApplicationURI("board/{$project_id}/"); + + // See PHI1025. We only want to prevent the import if the board already has + // real columns. If it has proxy columns (for example, for milestones) you + // can still import columns from another board. $columns = id(new PhabricatorProjectColumnQuery()) ->setViewer($viewer) ->withProjectPHIDs(array($project->getPHID())) + ->withIsProxyColumn(false) ->execute(); if ($columns) { - return new Aphront400Response(); + return $this->newDialog() + ->setTitle(pht('Workboard Already Has Columns')) + ->appendParagraph( + pht( + 'You can not import columns into this workboard because it '. + 'already has columns. You can only import into an empty '. + 'workboard.')) + ->addCancelButton($board_uri); } - $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)) + ->withIsProxyColumn(false) ->execute(); if (!$import_columns) { - return new Aphront400Response(); + return $this->newDialog() + ->setTitle(pht('Source Workboard Has No Columns')) + ->appendParagraph( + pht( + 'You can not import columns from that workboard because it has '. + 'no importable columns.')) + ->addCancelButton($board_uri); } $table = id(new PhabricatorProjectColumn()) ->openTransaction(); foreach ($import_columns as $import_column) { if ($import_column->isHidden()) { continue; } - if ($import_column->getProxy()) { - continue; - } $new_column = PhabricatorProjectColumn::initializeNewColumn($viewer) ->setSequence($import_column->getSequence()) ->setProjectPHID($project->getPHID()) ->setName($import_column->getName()) ->setProperties($import_column->getProperties()) ->save(); } $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) ->setTransactionType( PhabricatorProjectWorkboardTransaction::TRANSACTIONTYPE) ->setNewValue(1); id(new PhabricatorProjectTransactionEditor()) ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true) ->setContinueOnMissingFields(true) ->applyTransactions($project, $xactions); $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/query/PhabricatorProjectColumnQuery.php b/src/applications/project/query/PhabricatorProjectColumnQuery.php index 13f2f52a43..441c33e8cb 100644 --- a/src/applications/project/query/PhabricatorProjectColumnQuery.php +++ b/src/applications/project/query/PhabricatorProjectColumnQuery.php @@ -1,166 +1,180 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withProjectPHIDs(array $project_phids) { $this->projectPHIDs = $project_phids; return $this; } public function withProxyPHIDs(array $proxy_phids) { $this->proxyPHIDs = $proxy_phids; return $this; } public function withStatuses(array $status) { $this->statuses = $status; return $this; } + public function withIsProxyColumn($is_proxy) { + $this->isProxyColumn = $is_proxy; + return $this; + } + public function newResultObject() { return new PhabricatorProjectColumn(); } protected function loadPage() { return $this->loadStandardPage($this->newResultObject()); } protected function willFilterPage(array $page) { $projects = array(); $project_phids = array_filter(mpull($page, 'getProjectPHID')); if ($project_phids) { $projects = id(new PhabricatorProjectQuery()) ->setParentQuery($this) ->setViewer($this->getViewer()) ->withPHIDs($project_phids) ->execute(); $projects = mpull($projects, null, 'getPHID'); } foreach ($page as $key => $column) { $phid = $column->getProjectPHID(); $project = idx($projects, $phid); if (!$project) { $this->didRejectResult($page[$key]); unset($page[$key]); continue; } $column->attachProject($project); } $proxy_phids = array_filter(mpull($page, 'getProjectPHID')); return $page; } protected function didFilterPage(array $page) { $proxy_phids = array(); foreach ($page as $column) { $proxy_phid = $column->getProxyPHID(); if ($proxy_phid !== null) { $proxy_phids[$proxy_phid] = $proxy_phid; } } if ($proxy_phids) { $proxies = id(new PhabricatorObjectQuery()) ->setParentQuery($this) ->setViewer($this->getViewer()) ->withPHIDs($proxy_phids) ->execute(); $proxies = mpull($proxies, null, 'getPHID'); } else { $proxies = array(); } foreach ($page as $key => $column) { $proxy_phid = $column->getProxyPHID(); if ($proxy_phid !== null) { $proxy = idx($proxies, $proxy_phid); // Only attach valid proxies, so we don't end up getting surprised if // an install somehow gets junk into their database. if (!($proxy instanceof PhabricatorColumnProxyInterface)) { $proxy = null; } if (!$proxy) { $this->didRejectResult($column); unset($page[$key]); continue; } } else { $proxy = null; } $column->attachProxy($proxy); } return $page; } protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { $where = parent::buildWhereClauseParts($conn); if ($this->ids !== null) { $where[] = qsprintf( $conn, 'id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( $conn, 'phid IN (%Ls)', $this->phids); } if ($this->projectPHIDs !== null) { $where[] = qsprintf( $conn, 'projectPHID IN (%Ls)', $this->projectPHIDs); } if ($this->proxyPHIDs !== null) { $where[] = qsprintf( $conn, 'proxyPHID IN (%Ls)', $this->proxyPHIDs); } if ($this->statuses !== null) { $where[] = qsprintf( $conn, 'status IN (%Ld)', $this->statuses); } + if ($this->isProxyColumn !== null) { + if ($this->isProxyColumn) { + $where[] = qsprintf($conn, 'proxyPHID IS NOT NULL'); + } else { + $where[] = qsprintf($conn, 'proxyPHID IS NULL'); + } + } + return $where; } public function getQueryApplicationClass() { return 'PhabricatorProjectApplication'; } } diff --git a/src/applications/project/typeahead/PhabricatorProjectDatasource.php b/src/applications/project/typeahead/PhabricatorProjectDatasource.php index e5b24335cf..5b999a997f 100644 --- a/src/applications/project/typeahead/PhabricatorProjectDatasource.php +++ b/src/applications/project/typeahead/PhabricatorProjectDatasource.php @@ -1,156 +1,157 @@ getViewer(); $raw_query = $this->getRawQuery(); // Allow users to type "#qa" or "qa" to find "Quality Assurance". $raw_query = ltrim($raw_query, '#'); $tokens = self::tokenizeString($raw_query); $query = id(new PhabricatorProjectQuery()) ->needImages(true) ->needSlugs(true) ->setOrderVector(array('-status', 'id')); if ($this->getPhase() == self::PHASE_PREFIX) { $prefix = $this->getPrefixQuery(); $query->withNamePrefixes(array($prefix)); } else if ($tokens) { $query->withNameTokens($tokens); } // If this is for policy selection, prevent users from using milestones. $for_policy = $this->getParameter('policy'); if ($for_policy) { $query->withIsMilestone(false); } $for_autocomplete = $this->getParameter('autocomplete'); $projs = $this->executeQuery($query); $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)) + ->withIsProxyColumn(false) ->execute(); $has_cols = mgroup($columns, 'getProjectPHID'); } else { $has_cols = array_fill_keys(array_keys($projs), true); } $is_browse = $this->getIsBrowse(); if ($is_browse && $projs) { // TODO: This is a little ad-hoc, but we don't currently have // infrastructure for bulk querying custom fields efficiently. $table = new PhabricatorProjectCustomFieldStorage(); $descriptions = $table->loadAllWhere( 'objectPHID IN (%Ls) AND fieldIndex = %s', array_keys($projs), PhabricatorHash::digestForIndex('std:project:internal:description')); $descriptions = mpull($descriptions, 'getFieldValue', 'getObjectPHID'); } else { $descriptions = array(); } $results = array(); foreach ($projs as $proj) { $phid = $proj->getPHID(); if (!isset($has_cols[$phid])) { 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'); } $all_strings = array(); // NOTE: We list the project's name first because results will be // sorted into prefix vs content phases incorrectly if we don't: it // will look like "Parent (Milestone)" matched "Parent" as a prefix, // but it did not. $all_strings[] = $proj->getName(); if ($proj->isMilestone()) { $all_strings[] = $proj->getParentProject()->getName(); } foreach ($proj->getSlugs() as $project_slug) { $all_strings[] = $project_slug->getSlug(); } $all_strings = implode("\n", $all_strings); $proj_result = id(new PhabricatorTypeaheadResult()) ->setName($all_strings) ->setDisplayName($proj->getDisplayName()) ->setDisplayType($proj->getDisplayIconName()) ->setURI($proj->getURI()) ->setPHID($phid) ->setIcon($proj->getDisplayIconIcon()) ->setColor($proj->getColor()) ->setPriorityType('proj') ->setClosed($closed); if (strlen($slug)) { $proj_result->setAutocomplete('#'.$slug); } $proj_result->setImageURI($proj->getProfileImageURI()); if ($is_browse) { $proj_result->addAttribute($proj->getDisplayIconName()); $description = idx($descriptions, $phid); if (strlen($description)) { $summary = PhabricatorMarkupEngine::summarizeSentence($description); $proj_result->addAttribute($summary); } } $results[] = $proj_result; } return $results; } }