mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-22 14:52:41 +01:00
Allow Spaces to be archived
Summary: Ref T8377. This adds a standard disable/enable feature to Spaces, with a couple of twists: - You can't create new stuff in an archived space, and you can't move stuff into an archived space. - We don't show results from an archived space by default in ApplicationSearch queries. You can still find these objects if you explicitly search for "Spaces: <the archived space>". So this is a "put it in a box in the attic" sort of operation, but that seems fairly nice/reasonable. Test Plan: - Archived and activated spaces. - Used ApplicationSearch, which omitted archived objects by default but allowed searches for them, specifically, to succeed. - Tried to create objects into an archived space (this is not allowed). - Edited objects in an archived space (this is OK). Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T8377 Differential Revision: https://secure.phabricator.com/D13238
This commit is contained in:
parent
a06618f879
commit
88e7cd158f
18 changed files with 309 additions and 38 deletions
2
resources/sql/autopatches/20150610.spaces.3.archive.sql
Normal file
2
resources/sql/autopatches/20150610.spaces.3.archive.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_spaces.spaces_namespace
|
||||
ADD isArchived BOOL NOT NULL;
|
|
@ -2577,6 +2577,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorSortTableUIExample' => 'applications/uiexample/examples/PhabricatorSortTableUIExample.php',
|
||||
'PhabricatorSourceCodeView' => 'view/layout/PhabricatorSourceCodeView.php',
|
||||
'PhabricatorSpacesApplication' => 'applications/spaces/application/PhabricatorSpacesApplication.php',
|
||||
'PhabricatorSpacesArchiveController' => 'applications/spaces/controller/PhabricatorSpacesArchiveController.php',
|
||||
'PhabricatorSpacesCapabilityCreateSpaces' => 'applications/spaces/capability/PhabricatorSpacesCapabilityCreateSpaces.php',
|
||||
'PhabricatorSpacesCapabilityDefaultEdit' => 'applications/spaces/capability/PhabricatorSpacesCapabilityDefaultEdit.php',
|
||||
'PhabricatorSpacesCapabilityDefaultView' => 'applications/spaces/capability/PhabricatorSpacesCapabilityDefaultView.php',
|
||||
|
@ -6089,6 +6090,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorSortTableUIExample' => 'PhabricatorUIExample',
|
||||
'PhabricatorSourceCodeView' => 'AphrontView',
|
||||
'PhabricatorSpacesApplication' => 'PhabricatorApplication',
|
||||
'PhabricatorSpacesArchiveController' => 'PhabricatorSpacesController',
|
||||
'PhabricatorSpacesCapabilityCreateSpaces' => 'PhabricatorPolicyCapability',
|
||||
'PhabricatorSpacesCapabilityDefaultEdit' => 'PhabricatorPolicyCapability',
|
||||
'PhabricatorSpacesCapabilityDefaultView' => 'PhabricatorPolicyCapability',
|
||||
|
|
|
@ -759,7 +759,7 @@ final class PhabricatorUser
|
|||
// for now just use the global space if one exists.
|
||||
|
||||
// If the viewer has access to the default space, use that.
|
||||
$spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces($this);
|
||||
$spaces = PhabricatorSpacesNamespaceQuery::getViewerActiveSpaces($this);
|
||||
foreach ($spaces as $space) {
|
||||
if ($space->getIsDefaultNamespace()) {
|
||||
return $space->getPHID();
|
||||
|
|
|
@ -149,6 +149,10 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject {
|
|||
if ($object instanceof PhabricatorSpacesInterface) {
|
||||
if (!empty($map['spacePHIDs'])) {
|
||||
$query->withSpacePHIDs($map['spacePHIDs']);
|
||||
} else {
|
||||
// If the user doesn't search for objects in specific spaces, we
|
||||
// default to "all active spaces you have permission to view".
|
||||
$query->withSpaceIsArchived(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -38,6 +38,15 @@ final class PhabricatorSpacesApplication extends PhabricatorApplication {
|
|||
return true;
|
||||
}
|
||||
|
||||
public function getHelpDocumentationArticles(PhabricatorUser $viewer) {
|
||||
return array(
|
||||
array(
|
||||
'name' => pht('Spaces User Guide'),
|
||||
'href' => PhabricatorEnv::getDoclink('Spaces User Guide'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public function getRemarkupRules() {
|
||||
return array(
|
||||
new PhabricatorSpacesRemarkupRule(),
|
||||
|
@ -51,6 +60,8 @@ final class PhabricatorSpacesApplication extends PhabricatorApplication {
|
|||
'(?:query/(?P<queryKey>[^/]+)/)?' => 'PhabricatorSpacesListController',
|
||||
'create/' => 'PhabricatorSpacesEditController',
|
||||
'edit/(?:(?P<id>\d+)/)?' => 'PhabricatorSpacesEditController',
|
||||
'(?P<action>activate|archive)/(?P<id>\d+)/'
|
||||
=> 'PhabricatorSpacesArchiveController',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorSpacesArchiveController
|
||||
extends PhabricatorSpacesController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getUser();
|
||||
|
||||
$space = id(new PhabricatorSpacesNamespaceQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($request->getURIData('id')))
|
||||
->requireCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
))
|
||||
->executeOne();
|
||||
if (!$space) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$is_archive = ($request->getURIData('action') == 'archive');
|
||||
$cancel_uri = '/'.$space->getMonogram();
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
$type_archive = PhabricatorSpacesNamespaceTransaction::TYPE_ARCHIVE;
|
||||
|
||||
$xactions = array();
|
||||
$xactions[] = id(new PhabricatorSpacesNamespaceTransaction())
|
||||
->setTransactionType($type_archive)
|
||||
->setNewValue($is_archive ? 1 : 0);
|
||||
|
||||
$editor = id(new PhabricatorSpacesNamespaceEditor())
|
||||
->setActor($viewer)
|
||||
->setContinueOnNoEffect(true)
|
||||
->setContinueOnMissingFields(true)
|
||||
->setContentSourceFromRequest($request);
|
||||
|
||||
$editor->applyTransactions($space, $xactions);
|
||||
|
||||
return id(new AphrontRedirectResponse())->setURI($cancel_uri);
|
||||
}
|
||||
|
||||
$body = array();
|
||||
if ($is_archive) {
|
||||
$title = pht('Archive Space: %s', $space->getNamespaceName());
|
||||
$body[] = pht(
|
||||
'If you archive this Space, you will no longer be able to create '.
|
||||
'new objects inside it.');
|
||||
$body[] = pht(
|
||||
'Existing objects in this Space will be hidden from query results '.
|
||||
'by default.');
|
||||
$button = pht('Archive Space');
|
||||
} else {
|
||||
$title = pht('Activate Space: %s', $space->getNamespaceName());
|
||||
$body[] = pht(
|
||||
'If you activate this space, you will be able to create objects '.
|
||||
'inside it again.');
|
||||
$body[] = pht(
|
||||
'Existing objects will no longer be hidden from query results.');
|
||||
$button = pht('Activate Space');
|
||||
}
|
||||
|
||||
|
||||
$dialog = $this->newDialog()
|
||||
->setTitle($title)
|
||||
->addCancelButton($cancel_uri)
|
||||
->addSubmitButton($button);
|
||||
|
||||
foreach ($body as $paragraph) {
|
||||
$dialog->appendParagraph($paragraph);
|
||||
}
|
||||
|
||||
return $dialog;
|
||||
}
|
||||
}
|
|
@ -37,6 +37,12 @@ final class PhabricatorSpacesViewController
|
|||
->setHeader($space->getNamespaceName())
|
||||
->setPolicyObject($space);
|
||||
|
||||
if ($space->getIsArchived()) {
|
||||
$header->setStatus('fa-ban', 'red', pht('Archived'));
|
||||
} else {
|
||||
$header->setStatus('fa-check', 'bluegrey', pht('Active'));
|
||||
}
|
||||
|
||||
$box = id(new PHUIObjectBoxView())
|
||||
->setHeader($header)
|
||||
->addPropertyList($property_list);
|
||||
|
@ -112,6 +118,26 @@ final class PhabricatorSpacesViewController
|
|||
->setWorkflow(!$can_edit)
|
||||
->setDisabled(!$can_edit));
|
||||
|
||||
$id = $space->getID();
|
||||
|
||||
if ($space->getIsArchived()) {
|
||||
$list->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setName(pht('Activate Space'))
|
||||
->setIcon('fa-check')
|
||||
->setHref($this->getApplicationURI("activate/{$id}/"))
|
||||
->setDisabled(!$can_edit)
|
||||
->setWorkflow(true));
|
||||
} else {
|
||||
$list->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setName(pht('Archive Space'))
|
||||
->setIcon('fa-ban')
|
||||
->setHref($this->getApplicationURI("archive/{$id}/"))
|
||||
->setDisabled(!$can_edit)
|
||||
->setWorkflow(true));
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ final class PhabricatorSpacesNamespaceEditor
|
|||
$types[] = PhabricatorSpacesNamespaceTransaction::TYPE_NAME;
|
||||
$types[] = PhabricatorSpacesNamespaceTransaction::TYPE_DESCRIPTION;
|
||||
$types[] = PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT;
|
||||
$types[] = PhabricatorSpacesNamespaceTransaction::TYPE_ARCHIVE;
|
||||
|
||||
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
|
||||
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
|
||||
|
@ -40,6 +41,8 @@ final class PhabricatorSpacesNamespaceEditor
|
|||
return null;
|
||||
}
|
||||
return $object->getDescription();
|
||||
case PhabricatorSpacesNamespaceTransaction::TYPE_ARCHIVE:
|
||||
return $object->getIsArchived();
|
||||
case PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT:
|
||||
return $object->getIsDefaultNamespace() ? 1 : null;
|
||||
case PhabricatorTransactions::TYPE_VIEW_POLICY:
|
||||
|
@ -61,6 +64,8 @@ final class PhabricatorSpacesNamespaceEditor
|
|||
case PhabricatorTransactions::TYPE_VIEW_POLICY:
|
||||
case PhabricatorTransactions::TYPE_EDIT_POLICY:
|
||||
return $xaction->getNewValue();
|
||||
case PhabricatorSpacesNamespaceTransaction::TYPE_ARCHIVE:
|
||||
return $xaction->getNewValue() ? 1 : 0;
|
||||
case PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT:
|
||||
return $xaction->getNewValue() ? 1 : null;
|
||||
}
|
||||
|
@ -84,6 +89,9 @@ final class PhabricatorSpacesNamespaceEditor
|
|||
case PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT:
|
||||
$object->setIsDefaultNamespace($new ? 1 : null);
|
||||
return;
|
||||
case PhabricatorSpacesNamespaceTransaction::TYPE_ARCHIVE:
|
||||
$object->setIsArchived($new ? 1 : 0);
|
||||
return;
|
||||
case PhabricatorTransactions::TYPE_VIEW_POLICY:
|
||||
$object->setViewPolicy($new);
|
||||
return;
|
||||
|
@ -103,6 +111,7 @@ final class PhabricatorSpacesNamespaceEditor
|
|||
case PhabricatorSpacesNamespaceTransaction::TYPE_NAME:
|
||||
case PhabricatorSpacesNamespaceTransaction::TYPE_DESCRIPTION:
|
||||
case PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT:
|
||||
case PhabricatorSpacesNamespaceTransaction::TYPE_ARCHIVE:
|
||||
case PhabricatorTransactions::TYPE_VIEW_POLICY:
|
||||
case PhabricatorTransactions::TYPE_EDIT_POLICY:
|
||||
return;
|
||||
|
@ -135,6 +144,20 @@ final class PhabricatorSpacesNamespaceEditor
|
|||
$errors[] = $error;
|
||||
}
|
||||
break;
|
||||
case PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT:
|
||||
if (!$this->getIsNewObject()) {
|
||||
foreach ($xactions as $xaction) {
|
||||
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
||||
$type,
|
||||
pht('Invalid'),
|
||||
pht(
|
||||
'Only the first space created can be the default space, and '.
|
||||
'it must remain the default space evermore.'),
|
||||
$xaction);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return $errors;
|
||||
|
|
|
@ -39,6 +39,10 @@ final class PhabricatorSpacesNamespacePHIDType
|
|||
$handle->setName($name);
|
||||
$handle->setFullName(pht('%s %s', $monogram, $name));
|
||||
$handle->setURI('/'.$monogram);
|
||||
|
||||
if ($namespace->getIsArchived()) {
|
||||
$handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ final class PhabricatorSpacesNamespaceQuery
|
|||
private $ids;
|
||||
private $phids;
|
||||
private $isDefaultNamespace;
|
||||
private $isArchived;
|
||||
|
||||
public function withIDs(array $ids) {
|
||||
$this->ids = $ids;
|
||||
|
@ -25,38 +26,32 @@ final class PhabricatorSpacesNamespaceQuery
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function withIsArchived($archived) {
|
||||
$this->isArchived = $archived;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getQueryApplicationClass() {
|
||||
return 'PhabricatorSpacesApplication';
|
||||
}
|
||||
|
||||
protected function loadPage() {
|
||||
$table = new PhabricatorSpacesNamespace();
|
||||
$conn_r = $table->establishConnection('r');
|
||||
|
||||
$rows = 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($rows);
|
||||
return $this->loadStandardPage(new PhabricatorSpacesNamespace());
|
||||
}
|
||||
|
||||
protected function buildWhereClause(AphrontDatabaseConnection $conn_r) {
|
||||
$where = array();
|
||||
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
|
||||
$where = parent::buildWhereClauseParts($conn);
|
||||
|
||||
if ($this->ids !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
$conn,
|
||||
'id IN (%Ld)',
|
||||
$this->ids);
|
||||
}
|
||||
|
||||
if ($this->phids !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
$conn,
|
||||
'phid IN (%Ls)',
|
||||
$this->phids);
|
||||
}
|
||||
|
@ -64,17 +59,23 @@ final class PhabricatorSpacesNamespaceQuery
|
|||
if ($this->isDefaultNamespace !== null) {
|
||||
if ($this->isDefaultNamespace) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
$conn,
|
||||
'isDefaultNamespace = 1');
|
||||
} else {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
$conn,
|
||||
'isDefaultNamespace IS NULL');
|
||||
}
|
||||
}
|
||||
|
||||
$where[] = $this->buildPagingClause($conn_r);
|
||||
return $this->formatWhereClause($where);
|
||||
if ($this->isArchived !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'isArchived = %d',
|
||||
(int)$this->isArchived);
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
public static function destroySpacesCache() {
|
||||
|
@ -156,6 +157,21 @@ final class PhabricatorSpacesNamespaceQuery
|
|||
return $result;
|
||||
}
|
||||
|
||||
|
||||
public static function getViewerActiveSpaces(PhabricatorUser $viewer) {
|
||||
$spaces = self::getViewerSpaces($viewer);
|
||||
|
||||
foreach ($spaces as $key => $space) {
|
||||
if ($space->getIsArchived()) {
|
||||
unset($spaces[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
return $spaces;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get the Space PHID for an object, if one exists.
|
||||
*
|
||||
|
|
|
@ -11,28 +11,39 @@ final class PhabricatorSpacesNamespaceSearchEngine
|
|||
return pht('Spaces');
|
||||
}
|
||||
|
||||
public function buildSavedQueryFromRequest(AphrontRequest $request) {
|
||||
$saved = new PhabricatorSavedQuery();
|
||||
|
||||
return $saved;
|
||||
public function newQuery() {
|
||||
return new PhabricatorSpacesNamespaceQuery();
|
||||
}
|
||||
|
||||
public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
|
||||
$query = id(new PhabricatorSpacesNamespaceQuery());
|
||||
public function buildCustomSearchFields() {
|
||||
return array(
|
||||
id(new PhabricatorSearchThreeStateField())
|
||||
->setLabel(pht('Active'))
|
||||
->setKey('active')
|
||||
->setOptions(
|
||||
pht('(Show All)'),
|
||||
pht('Show Only Active Spaces'),
|
||||
pht('Hide Active Spaces')),
|
||||
);
|
||||
}
|
||||
|
||||
public function buildQueryFromParameters(array $map) {
|
||||
$query = $this->newQuery();
|
||||
|
||||
if ($map['active']) {
|
||||
$query->withIsArchived(!$map['active']);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function buildSearchForm(
|
||||
AphrontFormView $form,
|
||||
PhabricatorSavedQuery $saved_query) {}
|
||||
|
||||
protected function getURI($path) {
|
||||
return '/spaces/'.$path;
|
||||
}
|
||||
|
||||
protected function getBuiltinQueryNames() {
|
||||
$names = array(
|
||||
'active' => pht('Active Spaces'),
|
||||
'all' => pht('All Spaces'),
|
||||
);
|
||||
|
||||
|
@ -40,11 +51,12 @@ final class PhabricatorSpacesNamespaceSearchEngine
|
|||
}
|
||||
|
||||
public function buildSavedQueryFromBuiltin($query_key) {
|
||||
|
||||
$query = $this->newSavedQuery();
|
||||
$query->setQueryKey($query_key);
|
||||
|
||||
switch ($query_key) {
|
||||
case 'active':
|
||||
return $query->setParameter('active', true);
|
||||
case 'all':
|
||||
return $query;
|
||||
}
|
||||
|
@ -72,6 +84,10 @@ final class PhabricatorSpacesNamespaceSearchEngine
|
|||
$item->addIcon('fa-certificate', pht('Default Space'));
|
||||
}
|
||||
|
||||
if ($space->getIsArchived()) {
|
||||
$item->setDisabled(true);
|
||||
}
|
||||
|
||||
$list->addItem($item);
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ final class PhabricatorSpacesNamespace
|
|||
protected $editPolicy;
|
||||
protected $isDefaultNamespace;
|
||||
protected $description;
|
||||
protected $isArchived;
|
||||
|
||||
public static function initializeNewNamespace(PhabricatorUser $actor) {
|
||||
$app = id(new PhabricatorApplicationQuery())
|
||||
|
@ -28,7 +29,8 @@ final class PhabricatorSpacesNamespace
|
|||
->setIsDefaultNamespace(null)
|
||||
->setViewPolicy($view_policy)
|
||||
->setEditPolicy($edit_policy)
|
||||
->setDescription('');
|
||||
->setDescription('')
|
||||
->setIsArchived(0);
|
||||
}
|
||||
|
||||
protected function getConfiguration() {
|
||||
|
@ -38,6 +40,7 @@ final class PhabricatorSpacesNamespace
|
|||
'namespaceName' => 'text255',
|
||||
'isDefaultNamespace' => 'bool?',
|
||||
'description' => 'text',
|
||||
'isArchived' => 'bool',
|
||||
),
|
||||
self::CONFIG_KEY_SCHEMA => array(
|
||||
'key_default' => array(
|
||||
|
|
|
@ -6,6 +6,7 @@ final class PhabricatorSpacesNamespaceTransaction
|
|||
const TYPE_NAME = 'spaces:name';
|
||||
const TYPE_DEFAULT = 'spaces:default';
|
||||
const TYPE_DESCRIPTION = 'spaces:description';
|
||||
const TYPE_ARCHIVE = 'spaces:archive';
|
||||
|
||||
public function getApplicationName() {
|
||||
return 'spaces';
|
||||
|
@ -78,6 +79,16 @@ final class PhabricatorSpacesNamespaceTransaction
|
|||
return pht(
|
||||
'%s made this the default space.',
|
||||
$this->renderHandleLink($author_phid));
|
||||
case self::TYPE_ARCHIVE:
|
||||
if ($new) {
|
||||
return pht(
|
||||
'%s archived this space.',
|
||||
$this->renderHandleLink($author_phid));
|
||||
} else {
|
||||
return pht(
|
||||
'%s activated this space.',
|
||||
$this->renderHandleLink($author_phid));
|
||||
}
|
||||
}
|
||||
|
||||
return parent::getTitle();
|
||||
|
|
|
@ -21,9 +21,20 @@ final class PhabricatorSpacesNamespaceDatasource
|
|||
$spaces = $this->executeQuery($query);
|
||||
$results = array();
|
||||
foreach ($spaces as $space) {
|
||||
$results[] = id(new PhabricatorTypeaheadResult())
|
||||
->setName($space->getNamespaceName())
|
||||
$full_name = pht(
|
||||
'%s %s',
|
||||
$space->getMonogram(),
|
||||
$space->getNamespaceName());
|
||||
|
||||
$result = id(new PhabricatorTypeaheadResult())
|
||||
->setName($full_name)
|
||||
->setPHID($space->getPHID());
|
||||
|
||||
if ($space->getIsArchived()) {
|
||||
$result->setClosed(pht('Archived'));
|
||||
}
|
||||
|
||||
$results[] = $result;
|
||||
}
|
||||
|
||||
return $this->filterResultsAgainstTokens($results);
|
||||
|
|
|
@ -310,6 +310,7 @@ abstract class PhabricatorApplicationTransactionEditor
|
|||
$space_phid = $default_space->getPHID();
|
||||
}
|
||||
}
|
||||
|
||||
return $space_phid;
|
||||
case PhabricatorTransactions::TYPE_EDGE:
|
||||
$edge_type = $xaction->getMetadataValue('edge:type');
|
||||
|
@ -2011,6 +2012,8 @@ abstract class PhabricatorApplicationTransactionEditor
|
|||
|
||||
$has_spaces = PhabricatorSpacesNamespaceQuery::getViewerSpacesExist($actor);
|
||||
$actor_spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces($actor);
|
||||
$active_spaces = PhabricatorSpacesNamespaceQuery::getViewerActiveSpaces(
|
||||
$actor);
|
||||
foreach ($xactions as $xaction) {
|
||||
$space_phid = $xaction->getNewValue();
|
||||
|
||||
|
@ -2040,6 +2043,23 @@ abstract class PhabricatorApplicationTransactionEditor
|
|||
'You can not shift this object in the selected space, because '.
|
||||
'the space does not exist or you do not have access to it.'),
|
||||
$xaction);
|
||||
} else if (empty($active_spaces[$space_phid])) {
|
||||
|
||||
// It's OK to edit objects in an archived space, so just move on if
|
||||
// we aren't adjusting the value.
|
||||
$old_space_phid = $this->getTransactionOldValue($object, $xaction);
|
||||
if ($space_phid == $old_space_phid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
||||
$transaction_type,
|
||||
pht('Archived'),
|
||||
pht(
|
||||
'You can not shift this object into the selected space, because '.
|
||||
'the space is archived. Objects can not be created inside (or '.
|
||||
'moved into) archived spaces.'),
|
||||
$xaction);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
27
src/docs/user/userguide/spaces.diviner
Normal file
27
src/docs/user/userguide/spaces.diviner
Normal file
|
@ -0,0 +1,27 @@
|
|||
@title Spaces User Guide
|
||||
@group userguide
|
||||
|
||||
Guide to the Spaces application.
|
||||
|
||||
Overview
|
||||
========
|
||||
|
||||
IMPORTANT: Spaces is a prototype application.
|
||||
|
||||
Archiving Spaces
|
||||
================
|
||||
|
||||
If you no longer need a Space, you can archive it by choosing
|
||||
{nav Archive Space} from the detail view. This hides the space and all the
|
||||
objects in it without deleting any data.
|
||||
|
||||
New objects can't be created into archived spaces, and existing objects can't
|
||||
be shifted into archived spaces. The UI won't give you options to choose
|
||||
these spaces when creating or editing objects.
|
||||
|
||||
Additionally, objects (like tasks) in archived spaces won't be shown in most
|
||||
search result lists by default. If you need to find objects in an archived
|
||||
space, use the `Spaces` constraint to specifically search for objects in that
|
||||
space.
|
||||
|
||||
You can reactivate a space later by choosing {nav Activate Space}.
|
|
@ -25,6 +25,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
|
|||
private $edgeLogicConstraints = array();
|
||||
private $edgeLogicConstraintsAreValid = false;
|
||||
private $spacePHIDs;
|
||||
private $spaceIsArchived;
|
||||
|
||||
protected function getPageCursors(array $page) {
|
||||
return array(
|
||||
|
@ -1722,6 +1723,11 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function withSpaceIsArchived($archived) {
|
||||
$this->spaceIsArchived = $archived;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constrain the query to include only results in valid Spaces.
|
||||
|
@ -1760,6 +1766,11 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
|
|||
$viewer_spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces(
|
||||
$viewer);
|
||||
foreach ($viewer_spaces as $viewer_space) {
|
||||
if ($this->spaceIsArchived !== null) {
|
||||
if ($viewer_space->getIsArchived() != $this->spaceIsArchived) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$phid = $viewer_space->getPHID();
|
||||
$space_phids[$phid] = $phid;
|
||||
if ($viewer_space->getIsDefaultNamespace()) {
|
||||
|
|
|
@ -265,7 +265,7 @@ final class AphrontFormPolicyControl extends AphrontFormControl {
|
|||
|
||||
$select = AphrontFormSelectControl::renderSelectTag(
|
||||
$space_phid,
|
||||
$this->getSpaceOptions(),
|
||||
$this->getSpaceOptions($space_phid),
|
||||
array(
|
||||
'name' => 'spacePHID',
|
||||
));
|
||||
|
@ -273,12 +273,20 @@ final class AphrontFormPolicyControl extends AphrontFormControl {
|
|||
return $select;
|
||||
}
|
||||
|
||||
protected function getSpaceOptions() {
|
||||
protected function getSpaceOptions($space_phid) {
|
||||
$viewer = $this->getUser();
|
||||
$viewer_spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces($viewer);
|
||||
|
||||
$map = array();
|
||||
foreach ($viewer_spaces as $space) {
|
||||
|
||||
// Skip archived spaces, unless the object is already in that space.
|
||||
if ($space->getIsArchived()) {
|
||||
if ($space->getPHID() != $space_phid) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$map[$space->getPHID()] = pht(
|
||||
'Space %s: %s',
|
||||
$space->getMonogram(),
|
||||
|
|
Loading…
Reference in a new issue