mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-22 23:02:42 +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',
|
'PhabricatorSortTableUIExample' => 'applications/uiexample/examples/PhabricatorSortTableUIExample.php',
|
||||||
'PhabricatorSourceCodeView' => 'view/layout/PhabricatorSourceCodeView.php',
|
'PhabricatorSourceCodeView' => 'view/layout/PhabricatorSourceCodeView.php',
|
||||||
'PhabricatorSpacesApplication' => 'applications/spaces/application/PhabricatorSpacesApplication.php',
|
'PhabricatorSpacesApplication' => 'applications/spaces/application/PhabricatorSpacesApplication.php',
|
||||||
|
'PhabricatorSpacesArchiveController' => 'applications/spaces/controller/PhabricatorSpacesArchiveController.php',
|
||||||
'PhabricatorSpacesCapabilityCreateSpaces' => 'applications/spaces/capability/PhabricatorSpacesCapabilityCreateSpaces.php',
|
'PhabricatorSpacesCapabilityCreateSpaces' => 'applications/spaces/capability/PhabricatorSpacesCapabilityCreateSpaces.php',
|
||||||
'PhabricatorSpacesCapabilityDefaultEdit' => 'applications/spaces/capability/PhabricatorSpacesCapabilityDefaultEdit.php',
|
'PhabricatorSpacesCapabilityDefaultEdit' => 'applications/spaces/capability/PhabricatorSpacesCapabilityDefaultEdit.php',
|
||||||
'PhabricatorSpacesCapabilityDefaultView' => 'applications/spaces/capability/PhabricatorSpacesCapabilityDefaultView.php',
|
'PhabricatorSpacesCapabilityDefaultView' => 'applications/spaces/capability/PhabricatorSpacesCapabilityDefaultView.php',
|
||||||
|
@ -6089,6 +6090,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorSortTableUIExample' => 'PhabricatorUIExample',
|
'PhabricatorSortTableUIExample' => 'PhabricatorUIExample',
|
||||||
'PhabricatorSourceCodeView' => 'AphrontView',
|
'PhabricatorSourceCodeView' => 'AphrontView',
|
||||||
'PhabricatorSpacesApplication' => 'PhabricatorApplication',
|
'PhabricatorSpacesApplication' => 'PhabricatorApplication',
|
||||||
|
'PhabricatorSpacesArchiveController' => 'PhabricatorSpacesController',
|
||||||
'PhabricatorSpacesCapabilityCreateSpaces' => 'PhabricatorPolicyCapability',
|
'PhabricatorSpacesCapabilityCreateSpaces' => 'PhabricatorPolicyCapability',
|
||||||
'PhabricatorSpacesCapabilityDefaultEdit' => 'PhabricatorPolicyCapability',
|
'PhabricatorSpacesCapabilityDefaultEdit' => 'PhabricatorPolicyCapability',
|
||||||
'PhabricatorSpacesCapabilityDefaultView' => 'PhabricatorPolicyCapability',
|
'PhabricatorSpacesCapabilityDefaultView' => 'PhabricatorPolicyCapability',
|
||||||
|
|
|
@ -759,7 +759,7 @@ final class PhabricatorUser
|
||||||
// for now just use the global space if one exists.
|
// for now just use the global space if one exists.
|
||||||
|
|
||||||
// If the viewer has access to the default space, use that.
|
// If the viewer has access to the default space, use that.
|
||||||
$spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces($this);
|
$spaces = PhabricatorSpacesNamespaceQuery::getViewerActiveSpaces($this);
|
||||||
foreach ($spaces as $space) {
|
foreach ($spaces as $space) {
|
||||||
if ($space->getIsDefaultNamespace()) {
|
if ($space->getIsDefaultNamespace()) {
|
||||||
return $space->getPHID();
|
return $space->getPHID();
|
||||||
|
|
|
@ -149,6 +149,10 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject {
|
||||||
if ($object instanceof PhabricatorSpacesInterface) {
|
if ($object instanceof PhabricatorSpacesInterface) {
|
||||||
if (!empty($map['spacePHIDs'])) {
|
if (!empty($map['spacePHIDs'])) {
|
||||||
$query->withSpacePHIDs($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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getHelpDocumentationArticles(PhabricatorUser $viewer) {
|
||||||
|
return array(
|
||||||
|
array(
|
||||||
|
'name' => pht('Spaces User Guide'),
|
||||||
|
'href' => PhabricatorEnv::getDoclink('Spaces User Guide'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function getRemarkupRules() {
|
public function getRemarkupRules() {
|
||||||
return array(
|
return array(
|
||||||
new PhabricatorSpacesRemarkupRule(),
|
new PhabricatorSpacesRemarkupRule(),
|
||||||
|
@ -51,6 +60,8 @@ final class PhabricatorSpacesApplication extends PhabricatorApplication {
|
||||||
'(?:query/(?P<queryKey>[^/]+)/)?' => 'PhabricatorSpacesListController',
|
'(?:query/(?P<queryKey>[^/]+)/)?' => 'PhabricatorSpacesListController',
|
||||||
'create/' => 'PhabricatorSpacesEditController',
|
'create/' => 'PhabricatorSpacesEditController',
|
||||||
'edit/(?:(?P<id>\d+)/)?' => '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())
|
->setHeader($space->getNamespaceName())
|
||||||
->setPolicyObject($space);
|
->setPolicyObject($space);
|
||||||
|
|
||||||
|
if ($space->getIsArchived()) {
|
||||||
|
$header->setStatus('fa-ban', 'red', pht('Archived'));
|
||||||
|
} else {
|
||||||
|
$header->setStatus('fa-check', 'bluegrey', pht('Active'));
|
||||||
|
}
|
||||||
|
|
||||||
$box = id(new PHUIObjectBoxView())
|
$box = id(new PHUIObjectBoxView())
|
||||||
->setHeader($header)
|
->setHeader($header)
|
||||||
->addPropertyList($property_list);
|
->addPropertyList($property_list);
|
||||||
|
@ -112,6 +118,26 @@ final class PhabricatorSpacesViewController
|
||||||
->setWorkflow(!$can_edit)
|
->setWorkflow(!$can_edit)
|
||||||
->setDisabled(!$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;
|
return $list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ final class PhabricatorSpacesNamespaceEditor
|
||||||
$types[] = PhabricatorSpacesNamespaceTransaction::TYPE_NAME;
|
$types[] = PhabricatorSpacesNamespaceTransaction::TYPE_NAME;
|
||||||
$types[] = PhabricatorSpacesNamespaceTransaction::TYPE_DESCRIPTION;
|
$types[] = PhabricatorSpacesNamespaceTransaction::TYPE_DESCRIPTION;
|
||||||
$types[] = PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT;
|
$types[] = PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT;
|
||||||
|
$types[] = PhabricatorSpacesNamespaceTransaction::TYPE_ARCHIVE;
|
||||||
|
|
||||||
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
|
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
|
||||||
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
|
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
|
||||||
|
@ -40,6 +41,8 @@ final class PhabricatorSpacesNamespaceEditor
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return $object->getDescription();
|
return $object->getDescription();
|
||||||
|
case PhabricatorSpacesNamespaceTransaction::TYPE_ARCHIVE:
|
||||||
|
return $object->getIsArchived();
|
||||||
case PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT:
|
case PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT:
|
||||||
return $object->getIsDefaultNamespace() ? 1 : null;
|
return $object->getIsDefaultNamespace() ? 1 : null;
|
||||||
case PhabricatorTransactions::TYPE_VIEW_POLICY:
|
case PhabricatorTransactions::TYPE_VIEW_POLICY:
|
||||||
|
@ -61,6 +64,8 @@ final class PhabricatorSpacesNamespaceEditor
|
||||||
case PhabricatorTransactions::TYPE_VIEW_POLICY:
|
case PhabricatorTransactions::TYPE_VIEW_POLICY:
|
||||||
case PhabricatorTransactions::TYPE_EDIT_POLICY:
|
case PhabricatorTransactions::TYPE_EDIT_POLICY:
|
||||||
return $xaction->getNewValue();
|
return $xaction->getNewValue();
|
||||||
|
case PhabricatorSpacesNamespaceTransaction::TYPE_ARCHIVE:
|
||||||
|
return $xaction->getNewValue() ? 1 : 0;
|
||||||
case PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT:
|
case PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT:
|
||||||
return $xaction->getNewValue() ? 1 : null;
|
return $xaction->getNewValue() ? 1 : null;
|
||||||
}
|
}
|
||||||
|
@ -84,6 +89,9 @@ final class PhabricatorSpacesNamespaceEditor
|
||||||
case PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT:
|
case PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT:
|
||||||
$object->setIsDefaultNamespace($new ? 1 : null);
|
$object->setIsDefaultNamespace($new ? 1 : null);
|
||||||
return;
|
return;
|
||||||
|
case PhabricatorSpacesNamespaceTransaction::TYPE_ARCHIVE:
|
||||||
|
$object->setIsArchived($new ? 1 : 0);
|
||||||
|
return;
|
||||||
case PhabricatorTransactions::TYPE_VIEW_POLICY:
|
case PhabricatorTransactions::TYPE_VIEW_POLICY:
|
||||||
$object->setViewPolicy($new);
|
$object->setViewPolicy($new);
|
||||||
return;
|
return;
|
||||||
|
@ -103,6 +111,7 @@ final class PhabricatorSpacesNamespaceEditor
|
||||||
case PhabricatorSpacesNamespaceTransaction::TYPE_NAME:
|
case PhabricatorSpacesNamespaceTransaction::TYPE_NAME:
|
||||||
case PhabricatorSpacesNamespaceTransaction::TYPE_DESCRIPTION:
|
case PhabricatorSpacesNamespaceTransaction::TYPE_DESCRIPTION:
|
||||||
case PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT:
|
case PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT:
|
||||||
|
case PhabricatorSpacesNamespaceTransaction::TYPE_ARCHIVE:
|
||||||
case PhabricatorTransactions::TYPE_VIEW_POLICY:
|
case PhabricatorTransactions::TYPE_VIEW_POLICY:
|
||||||
case PhabricatorTransactions::TYPE_EDIT_POLICY:
|
case PhabricatorTransactions::TYPE_EDIT_POLICY:
|
||||||
return;
|
return;
|
||||||
|
@ -135,6 +144,20 @@ final class PhabricatorSpacesNamespaceEditor
|
||||||
$errors[] = $error;
|
$errors[] = $error;
|
||||||
}
|
}
|
||||||
break;
|
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;
|
return $errors;
|
||||||
|
|
|
@ -39,6 +39,10 @@ final class PhabricatorSpacesNamespacePHIDType
|
||||||
$handle->setName($name);
|
$handle->setName($name);
|
||||||
$handle->setFullName(pht('%s %s', $monogram, $name));
|
$handle->setFullName(pht('%s %s', $monogram, $name));
|
||||||
$handle->setURI('/'.$monogram);
|
$handle->setURI('/'.$monogram);
|
||||||
|
|
||||||
|
if ($namespace->getIsArchived()) {
|
||||||
|
$handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ final class PhabricatorSpacesNamespaceQuery
|
||||||
private $ids;
|
private $ids;
|
||||||
private $phids;
|
private $phids;
|
||||||
private $isDefaultNamespace;
|
private $isDefaultNamespace;
|
||||||
|
private $isArchived;
|
||||||
|
|
||||||
public function withIDs(array $ids) {
|
public function withIDs(array $ids) {
|
||||||
$this->ids = $ids;
|
$this->ids = $ids;
|
||||||
|
@ -25,38 +26,32 @@ final class PhabricatorSpacesNamespaceQuery
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function withIsArchived($archived) {
|
||||||
|
$this->isArchived = $archived;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function getQueryApplicationClass() {
|
public function getQueryApplicationClass() {
|
||||||
return 'PhabricatorSpacesApplication';
|
return 'PhabricatorSpacesApplication';
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function loadPage() {
|
protected function loadPage() {
|
||||||
$table = new PhabricatorSpacesNamespace();
|
return $this->loadStandardPage(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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function buildWhereClause(AphrontDatabaseConnection $conn_r) {
|
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
|
||||||
$where = array();
|
$where = parent::buildWhereClauseParts($conn);
|
||||||
|
|
||||||
if ($this->ids !== null) {
|
if ($this->ids !== null) {
|
||||||
$where[] = qsprintf(
|
$where[] = qsprintf(
|
||||||
$conn_r,
|
$conn,
|
||||||
'id IN (%Ld)',
|
'id IN (%Ld)',
|
||||||
$this->ids);
|
$this->ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->phids !== null) {
|
if ($this->phids !== null) {
|
||||||
$where[] = qsprintf(
|
$where[] = qsprintf(
|
||||||
$conn_r,
|
$conn,
|
||||||
'phid IN (%Ls)',
|
'phid IN (%Ls)',
|
||||||
$this->phids);
|
$this->phids);
|
||||||
}
|
}
|
||||||
|
@ -64,17 +59,23 @@ final class PhabricatorSpacesNamespaceQuery
|
||||||
if ($this->isDefaultNamespace !== null) {
|
if ($this->isDefaultNamespace !== null) {
|
||||||
if ($this->isDefaultNamespace) {
|
if ($this->isDefaultNamespace) {
|
||||||
$where[] = qsprintf(
|
$where[] = qsprintf(
|
||||||
$conn_r,
|
$conn,
|
||||||
'isDefaultNamespace = 1');
|
'isDefaultNamespace = 1');
|
||||||
} else {
|
} else {
|
||||||
$where[] = qsprintf(
|
$where[] = qsprintf(
|
||||||
$conn_r,
|
$conn,
|
||||||
'isDefaultNamespace IS NULL');
|
'isDefaultNamespace IS NULL');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$where[] = $this->buildPagingClause($conn_r);
|
if ($this->isArchived !== null) {
|
||||||
return $this->formatWhereClause($where);
|
$where[] = qsprintf(
|
||||||
|
$conn,
|
||||||
|
'isArchived = %d',
|
||||||
|
(int)$this->isArchived);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $where;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function destroySpacesCache() {
|
public static function destroySpacesCache() {
|
||||||
|
@ -156,6 +157,21 @@ final class PhabricatorSpacesNamespaceQuery
|
||||||
return $result;
|
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.
|
* Get the Space PHID for an object, if one exists.
|
||||||
*
|
*
|
||||||
|
|
|
@ -11,28 +11,39 @@ final class PhabricatorSpacesNamespaceSearchEngine
|
||||||
return pht('Spaces');
|
return pht('Spaces');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function buildSavedQueryFromRequest(AphrontRequest $request) {
|
public function newQuery() {
|
||||||
$saved = new PhabricatorSavedQuery();
|
return new PhabricatorSpacesNamespaceQuery();
|
||||||
|
|
||||||
return $saved;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
|
public function buildCustomSearchFields() {
|
||||||
$query = id(new PhabricatorSpacesNamespaceQuery());
|
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;
|
return $query;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function buildSearchForm(
|
|
||||||
AphrontFormView $form,
|
|
||||||
PhabricatorSavedQuery $saved_query) {}
|
|
||||||
|
|
||||||
protected function getURI($path) {
|
protected function getURI($path) {
|
||||||
return '/spaces/'.$path;
|
return '/spaces/'.$path;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getBuiltinQueryNames() {
|
protected function getBuiltinQueryNames() {
|
||||||
$names = array(
|
$names = array(
|
||||||
|
'active' => pht('Active Spaces'),
|
||||||
'all' => pht('All Spaces'),
|
'all' => pht('All Spaces'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -40,11 +51,12 @@ final class PhabricatorSpacesNamespaceSearchEngine
|
||||||
}
|
}
|
||||||
|
|
||||||
public function buildSavedQueryFromBuiltin($query_key) {
|
public function buildSavedQueryFromBuiltin($query_key) {
|
||||||
|
|
||||||
$query = $this->newSavedQuery();
|
$query = $this->newSavedQuery();
|
||||||
$query->setQueryKey($query_key);
|
$query->setQueryKey($query_key);
|
||||||
|
|
||||||
switch ($query_key) {
|
switch ($query_key) {
|
||||||
|
case 'active':
|
||||||
|
return $query->setParameter('active', true);
|
||||||
case 'all':
|
case 'all':
|
||||||
return $query;
|
return $query;
|
||||||
}
|
}
|
||||||
|
@ -72,6 +84,10 @@ final class PhabricatorSpacesNamespaceSearchEngine
|
||||||
$item->addIcon('fa-certificate', pht('Default Space'));
|
$item->addIcon('fa-certificate', pht('Default Space'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($space->getIsArchived()) {
|
||||||
|
$item->setDisabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
$list->addItem($item);
|
$list->addItem($item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ final class PhabricatorSpacesNamespace
|
||||||
protected $editPolicy;
|
protected $editPolicy;
|
||||||
protected $isDefaultNamespace;
|
protected $isDefaultNamespace;
|
||||||
protected $description;
|
protected $description;
|
||||||
|
protected $isArchived;
|
||||||
|
|
||||||
public static function initializeNewNamespace(PhabricatorUser $actor) {
|
public static function initializeNewNamespace(PhabricatorUser $actor) {
|
||||||
$app = id(new PhabricatorApplicationQuery())
|
$app = id(new PhabricatorApplicationQuery())
|
||||||
|
@ -28,7 +29,8 @@ final class PhabricatorSpacesNamespace
|
||||||
->setIsDefaultNamespace(null)
|
->setIsDefaultNamespace(null)
|
||||||
->setViewPolicy($view_policy)
|
->setViewPolicy($view_policy)
|
||||||
->setEditPolicy($edit_policy)
|
->setEditPolicy($edit_policy)
|
||||||
->setDescription('');
|
->setDescription('')
|
||||||
|
->setIsArchived(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getConfiguration() {
|
protected function getConfiguration() {
|
||||||
|
@ -38,6 +40,7 @@ final class PhabricatorSpacesNamespace
|
||||||
'namespaceName' => 'text255',
|
'namespaceName' => 'text255',
|
||||||
'isDefaultNamespace' => 'bool?',
|
'isDefaultNamespace' => 'bool?',
|
||||||
'description' => 'text',
|
'description' => 'text',
|
||||||
|
'isArchived' => 'bool',
|
||||||
),
|
),
|
||||||
self::CONFIG_KEY_SCHEMA => array(
|
self::CONFIG_KEY_SCHEMA => array(
|
||||||
'key_default' => array(
|
'key_default' => array(
|
||||||
|
|
|
@ -6,6 +6,7 @@ final class PhabricatorSpacesNamespaceTransaction
|
||||||
const TYPE_NAME = 'spaces:name';
|
const TYPE_NAME = 'spaces:name';
|
||||||
const TYPE_DEFAULT = 'spaces:default';
|
const TYPE_DEFAULT = 'spaces:default';
|
||||||
const TYPE_DESCRIPTION = 'spaces:description';
|
const TYPE_DESCRIPTION = 'spaces:description';
|
||||||
|
const TYPE_ARCHIVE = 'spaces:archive';
|
||||||
|
|
||||||
public function getApplicationName() {
|
public function getApplicationName() {
|
||||||
return 'spaces';
|
return 'spaces';
|
||||||
|
@ -78,6 +79,16 @@ final class PhabricatorSpacesNamespaceTransaction
|
||||||
return pht(
|
return pht(
|
||||||
'%s made this the default space.',
|
'%s made this the default space.',
|
||||||
$this->renderHandleLink($author_phid));
|
$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();
|
return parent::getTitle();
|
||||||
|
|
|
@ -21,9 +21,20 @@ final class PhabricatorSpacesNamespaceDatasource
|
||||||
$spaces = $this->executeQuery($query);
|
$spaces = $this->executeQuery($query);
|
||||||
$results = array();
|
$results = array();
|
||||||
foreach ($spaces as $space) {
|
foreach ($spaces as $space) {
|
||||||
$results[] = id(new PhabricatorTypeaheadResult())
|
$full_name = pht(
|
||||||
->setName($space->getNamespaceName())
|
'%s %s',
|
||||||
|
$space->getMonogram(),
|
||||||
|
$space->getNamespaceName());
|
||||||
|
|
||||||
|
$result = id(new PhabricatorTypeaheadResult())
|
||||||
|
->setName($full_name)
|
||||||
->setPHID($space->getPHID());
|
->setPHID($space->getPHID());
|
||||||
|
|
||||||
|
if ($space->getIsArchived()) {
|
||||||
|
$result->setClosed(pht('Archived'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$results[] = $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->filterResultsAgainstTokens($results);
|
return $this->filterResultsAgainstTokens($results);
|
||||||
|
|
|
@ -310,6 +310,7 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
$space_phid = $default_space->getPHID();
|
$space_phid = $default_space->getPHID();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $space_phid;
|
return $space_phid;
|
||||||
case PhabricatorTransactions::TYPE_EDGE:
|
case PhabricatorTransactions::TYPE_EDGE:
|
||||||
$edge_type = $xaction->getMetadataValue('edge:type');
|
$edge_type = $xaction->getMetadataValue('edge:type');
|
||||||
|
@ -2011,6 +2012,8 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
|
|
||||||
$has_spaces = PhabricatorSpacesNamespaceQuery::getViewerSpacesExist($actor);
|
$has_spaces = PhabricatorSpacesNamespaceQuery::getViewerSpacesExist($actor);
|
||||||
$actor_spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces($actor);
|
$actor_spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces($actor);
|
||||||
|
$active_spaces = PhabricatorSpacesNamespaceQuery::getViewerActiveSpaces(
|
||||||
|
$actor);
|
||||||
foreach ($xactions as $xaction) {
|
foreach ($xactions as $xaction) {
|
||||||
$space_phid = $xaction->getNewValue();
|
$space_phid = $xaction->getNewValue();
|
||||||
|
|
||||||
|
@ -2040,6 +2043,23 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
'You can not shift this object in the selected space, because '.
|
'You can not shift this object in the selected space, because '.
|
||||||
'the space does not exist or you do not have access to it.'),
|
'the space does not exist or you do not have access to it.'),
|
||||||
$xaction);
|
$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 $edgeLogicConstraints = array();
|
||||||
private $edgeLogicConstraintsAreValid = false;
|
private $edgeLogicConstraintsAreValid = false;
|
||||||
private $spacePHIDs;
|
private $spacePHIDs;
|
||||||
|
private $spaceIsArchived;
|
||||||
|
|
||||||
protected function getPageCursors(array $page) {
|
protected function getPageCursors(array $page) {
|
||||||
return array(
|
return array(
|
||||||
|
@ -1722,6 +1723,11 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function withSpaceIsArchived($archived) {
|
||||||
|
$this->spaceIsArchived = $archived;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constrain the query to include only results in valid Spaces.
|
* Constrain the query to include only results in valid Spaces.
|
||||||
|
@ -1760,6 +1766,11 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
|
||||||
$viewer_spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces(
|
$viewer_spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces(
|
||||||
$viewer);
|
$viewer);
|
||||||
foreach ($viewer_spaces as $viewer_space) {
|
foreach ($viewer_spaces as $viewer_space) {
|
||||||
|
if ($this->spaceIsArchived !== null) {
|
||||||
|
if ($viewer_space->getIsArchived() != $this->spaceIsArchived) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
$phid = $viewer_space->getPHID();
|
$phid = $viewer_space->getPHID();
|
||||||
$space_phids[$phid] = $phid;
|
$space_phids[$phid] = $phid;
|
||||||
if ($viewer_space->getIsDefaultNamespace()) {
|
if ($viewer_space->getIsDefaultNamespace()) {
|
||||||
|
|
|
@ -265,7 +265,7 @@ final class AphrontFormPolicyControl extends AphrontFormControl {
|
||||||
|
|
||||||
$select = AphrontFormSelectControl::renderSelectTag(
|
$select = AphrontFormSelectControl::renderSelectTag(
|
||||||
$space_phid,
|
$space_phid,
|
||||||
$this->getSpaceOptions(),
|
$this->getSpaceOptions($space_phid),
|
||||||
array(
|
array(
|
||||||
'name' => 'spacePHID',
|
'name' => 'spacePHID',
|
||||||
));
|
));
|
||||||
|
@ -273,12 +273,20 @@ final class AphrontFormPolicyControl extends AphrontFormControl {
|
||||||
return $select;
|
return $select;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getSpaceOptions() {
|
protected function getSpaceOptions($space_phid) {
|
||||||
$viewer = $this->getUser();
|
$viewer = $this->getUser();
|
||||||
$viewer_spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces($viewer);
|
$viewer_spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces($viewer);
|
||||||
|
|
||||||
$map = array();
|
$map = array();
|
||||||
foreach ($viewer_spaces as $space) {
|
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(
|
$map[$space->getPHID()] = pht(
|
||||||
'Space %s: %s',
|
'Space %s: %s',
|
||||||
$space->getMonogram(),
|
$space->getMonogram(),
|
||||||
|
|
Loading…
Reference in a new issue