Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14013299
D13159.id31806.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
22 KB
Referenced Files
None
Subscribers
None
D13159.id31806.diff
View Options
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -2558,6 +2558,7 @@
'PhabricatorSpacesCapabilityCreateSpaces' => 'applications/spaces/capability/PhabricatorSpacesCapabilityCreateSpaces.php',
'PhabricatorSpacesCapabilityDefaultEdit' => 'applications/spaces/capability/PhabricatorSpacesCapabilityDefaultEdit.php',
'PhabricatorSpacesCapabilityDefaultView' => 'applications/spaces/capability/PhabricatorSpacesCapabilityDefaultView.php',
+ 'PhabricatorSpacesControl' => 'applications/spaces/view/PhabricatorSpacesControl.php',
'PhabricatorSpacesController' => 'applications/spaces/controller/PhabricatorSpacesController.php',
'PhabricatorSpacesDAO' => 'applications/spaces/storage/PhabricatorSpacesDAO.php',
'PhabricatorSpacesEditController' => 'applications/spaces/controller/PhabricatorSpacesEditController.php',
@@ -6037,6 +6038,7 @@
'PhabricatorSpacesCapabilityCreateSpaces' => 'PhabricatorPolicyCapability',
'PhabricatorSpacesCapabilityDefaultEdit' => 'PhabricatorPolicyCapability',
'PhabricatorSpacesCapabilityDefaultView' => 'PhabricatorPolicyCapability',
+ 'PhabricatorSpacesControl' => 'AphrontFormControl',
'PhabricatorSpacesController' => 'PhabricatorController',
'PhabricatorSpacesDAO' => 'PhabricatorLiskDAO',
'PhabricatorSpacesEditController' => 'PhabricatorSpacesController',
diff --git a/src/applications/paste/controller/PhabricatorPasteEditController.php b/src/applications/paste/controller/PhabricatorPasteEditController.php
--- a/src/applications/paste/controller/PhabricatorPasteEditController.php
+++ b/src/applications/paste/controller/PhabricatorPasteEditController.php
@@ -57,13 +57,12 @@
}
}
- $text = null;
- $e_text = true;
- $errors = array();
+ $v_space = $paste->getSpacePHID();
if ($is_create && $parent) {
$v_title = pht('Fork of %s', $parent->getFullName());
$v_language = $parent->getLanguage();
$v_text = $parent->getRawContent();
+ $v_space = $parent->getSpacePHID();
} else {
$v_title = $paste->getTitle();
$v_language = $paste->getLanguage();
@@ -81,68 +80,64 @@
$v_projects = array_reverse($v_projects);
}
+ $validation_exception = null;
if ($request->isFormPost()) {
$xactions = array();
$v_text = $request->getStr('text');
- if (!strlen($v_text)) {
- $e_text = pht('Required');
- $errors[] = pht('The paste may not be blank.');
- } else {
- $e_text = null;
- }
-
$v_title = $request->getStr('title');
$v_language = $request->getStr('language');
$v_view_policy = $request->getStr('can_view');
$v_edit_policy = $request->getStr('can_edit');
$v_projects = $request->getArr('projects');
+ $v_space = $request->getStr('spacePHID');
// NOTE: The author is the only editor and can always view the paste,
// so it's impossible for them to choose an invalid policy.
- if (!$errors) {
- if ($is_create || ($v_text !== $paste->getRawContent())) {
- $file = PhabricatorPasteEditor::initializeFileForPaste(
- $user,
- $v_title,
- $v_text);
-
- $xactions[] = id(new PhabricatorPasteTransaction())
- ->setTransactionType(PhabricatorPasteTransaction::TYPE_CONTENT)
- ->setNewValue($file->getPHID());
- }
+ if ($is_create || ($v_text !== $paste->getRawContent())) {
+ $file = PhabricatorPasteEditor::initializeFileForPaste(
+ $user,
+ $v_title,
+ $v_text);
$xactions[] = id(new PhabricatorPasteTransaction())
- ->setTransactionType(PhabricatorPasteTransaction::TYPE_TITLE)
- ->setNewValue($v_title);
- $xactions[] = id(new PhabricatorPasteTransaction())
- ->setTransactionType(PhabricatorPasteTransaction::TYPE_LANGUAGE)
- ->setNewValue($v_language);
- $xactions[] = id(new PhabricatorPasteTransaction())
- ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY)
- ->setNewValue($v_view_policy);
- $xactions[] = id(new PhabricatorPasteTransaction())
- ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY)
- ->setNewValue($v_edit_policy);
+ ->setTransactionType(PhabricatorPasteTransaction::TYPE_CONTENT)
+ ->setNewValue($file->getPHID());
+ }
- $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
- $xactions[] = id(new PhabricatorPasteTransaction())
- ->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
- ->setMetadataValue('edge:type', $proj_edge_type)
- ->setNewValue(array('=' => array_fuse($v_projects)));
-
- $editor = id(new PhabricatorPasteEditor())
- ->setActor($user)
- ->setContentSourceFromRequest($request)
- ->setContinueOnNoEffect(true);
+ $xactions[] = id(new PhabricatorPasteTransaction())
+ ->setTransactionType(PhabricatorPasteTransaction::TYPE_TITLE)
+ ->setNewValue($v_title);
+ $xactions[] = id(new PhabricatorPasteTransaction())
+ ->setTransactionType(PhabricatorPasteTransaction::TYPE_LANGUAGE)
+ ->setNewValue($v_language);
+ $xactions[] = id(new PhabricatorPasteTransaction())
+ ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY)
+ ->setNewValue($v_view_policy);
+ $xactions[] = id(new PhabricatorPasteTransaction())
+ ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY)
+ ->setNewValue($v_edit_policy);
+ $xactions[] = id(new PhabricatorPasteTransaction())
+ ->setTransactionType(PhabricatorTransactions::TYPE_SPACE)
+ ->setNewValue($v_space);
+
+ $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
+ $xactions[] = id(new PhabricatorPasteTransaction())
+ ->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
+ ->setMetadataValue('edge:type', $proj_edge_type)
+ ->setNewValue(array('=' => array_fuse($v_projects)));
+
+ $editor = id(new PhabricatorPasteEditor())
+ ->setActor($user)
+ ->setContentSourceFromRequest($request)
+ ->setContinueOnNoEffect(true);
+
+ try {
$xactions = $editor->applyTransactions($paste, $xactions);
return id(new AphrontRedirectResponse())->setURI($paste->getURI());
- } else {
- // make sure we update policy so its correctly populated to what
- // the user chose
- $paste->setViewPolicy($v_view_policy);
- $paste->setEditPolicy($v_edit_policy);
+ } catch (PhabricatorApplicationTransactionValidationException $ex) {
+ $validation_exception = $ex;
}
}
@@ -172,12 +167,19 @@
->setObject($paste)
->execute();
+ $form->appendControl(
+ id(new PhabricatorSpacesControl())
+ ->setObject($paste)
+ ->setValue($v_space)
+ ->setName('spacePHID'));
+
$form->appendChild(
id(new AphrontFormPolicyControl())
->setUser($user)
->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
->setPolicyObject($paste)
->setPolicies($policies)
+ ->setValue($v_view_policy)
->setName('can_view'));
$form->appendChild(
@@ -186,6 +188,7 @@
->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
->setPolicyObject($paste)
->setPolicies($policies)
+ ->setValue($v_edit_policy)
->setName('can_edit'));
$form->appendControl(
@@ -199,7 +202,6 @@
->appendChild(
id(new AphrontFormTextAreaControl())
->setLabel(pht('Text'))
- ->setError($e_text)
->setValue($v_text)
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL)
->setCustomClass('PhabricatorMonospaced')
@@ -222,9 +224,12 @@
$form_box = id(new PHUIObjectBoxView())
->setHeaderText($title)
- ->setFormErrors($errors)
->setForm($form);
+ if ($validation_exception) {
+ $form_box->setValidationException($validation_exception);
+ }
+
$crumbs = $this->buildApplicationCrumbs($this->buildSideNavView());
if (!$is_create) {
$crumbs->addTextCrumb('P'.$paste->getID(), '/P'.$paste->getID());
diff --git a/src/applications/paste/storage/PhabricatorPaste.php b/src/applications/paste/storage/PhabricatorPaste.php
--- a/src/applications/paste/storage/PhabricatorPaste.php
+++ b/src/applications/paste/storage/PhabricatorPaste.php
@@ -42,7 +42,11 @@
}
public function getURI() {
- return '/P'.$this->getID();
+ return '/'.$this->getMonogram();
+ }
+
+ public function getMonogram() {
+ return 'P'.$this->getID();
}
protected function getConfiguration() {
diff --git a/src/applications/people/storage/PhabricatorUser.php b/src/applications/people/storage/PhabricatorUser.php
--- a/src/applications/people/storage/PhabricatorUser.php
+++ b/src/applications/people/storage/PhabricatorUser.php
@@ -750,6 +750,20 @@
$email->getUserPHID());
}
+ public function getDefaultSpacePHID() {
+ // TODO: We might let the user switch which space they're "in" later on;
+ // for now just use the global space if one exists.
+
+ $spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces($this);
+ foreach ($spaces as $space) {
+ if ($space->getIsDefaultNamespace()) {
+ return $space->getPHID();
+ }
+ }
+
+ return null;
+ }
+
/**
* Grant a user a source of authority, to let them bypass policy checks they
diff --git a/src/applications/spaces/phid/PhabricatorSpacesNamespacePHIDType.php b/src/applications/spaces/phid/PhabricatorSpacesNamespacePHIDType.php
--- a/src/applications/spaces/phid/PhabricatorSpacesNamespacePHIDType.php
+++ b/src/applications/spaces/phid/PhabricatorSpacesNamespacePHIDType.php
@@ -28,7 +28,10 @@
foreach ($handles as $phid => $handle) {
$namespace = $objects[$phid];
+ $monogram = $namespace->getMonogram();
+
$handle->setName($namespace->getNamespaceName());
+ $handle->setURI('/'.$monogram);
}
}
diff --git a/src/applications/spaces/view/PhabricatorSpacesControl.php b/src/applications/spaces/view/PhabricatorSpacesControl.php
new file mode 100644
--- /dev/null
+++ b/src/applications/spaces/view/PhabricatorSpacesControl.php
@@ -0,0 +1,49 @@
+<?php
+
+final class PhabricatorSpacesControl extends AphrontFormControl {
+
+ private $object;
+
+ protected function shouldRender() {
+ // Render this control only if some Spaces exist.
+ return PhabricatorSpacesNamespaceQuery::getAllSpaces();
+ }
+
+ public function setObject(PhabricatorSpacesInterface $object) {
+ $this->object = $object;
+ return $this;
+ }
+
+ protected function getCustomControlClass() {
+ return '';
+ }
+
+ protected function getOptions() {
+ $viewer = $this->getUser();
+ $viewer_spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces($viewer);
+
+ $map = mpull($viewer_spaces, 'getNamespaceName', 'getPHID');
+ asort($map);
+
+ return $map;
+ }
+
+ public function renderInput() {
+ $viewer = $this->getUser();
+
+ $this->setLabel(pht('Space'));
+
+ $value = $this->getValue();
+ if ($value === null) {
+ $value = $viewer->getDefaultSpacePHID();
+ }
+
+ return AphrontFormSelectControl::renderSelectTag(
+ $value,
+ $this->getOptions(),
+ array(
+ 'name' => $this->getName(),
+ ));
+ }
+
+}
diff --git a/src/applications/transactions/constants/PhabricatorTransactions.php b/src/applications/transactions/constants/PhabricatorTransactions.php
--- a/src/applications/transactions/constants/PhabricatorTransactions.php
+++ b/src/applications/transactions/constants/PhabricatorTransactions.php
@@ -12,6 +12,7 @@
const TYPE_BUILDABLE = 'harbormaster:buildable';
const TYPE_TOKEN = 'token:give';
const TYPE_INLINESTATE = 'core:inlinestate';
+ const TYPE_SPACE = 'core:space';
const COLOR_RED = 'red';
const COLOR_ORANGE = 'orange';
diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
--- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
+++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
@@ -264,6 +264,10 @@
$types[] = PhabricatorTransactions::TYPE_EDGE;
}
+ if ($this->object instanceof PhabricatorSpacesInterface) {
+ $types[] = PhabricatorTransactions::TYPE_SPACE;
+ }
+
return $types;
}
@@ -292,6 +296,21 @@
return $object->getEditPolicy();
case PhabricatorTransactions::TYPE_JOIN_POLICY:
return $object->getJoinPolicy();
+ case PhabricatorTransactions::TYPE_SPACE:
+ $space_phid = $object->getSpacePHID();
+ if ($space_phid === null) {
+ if ($this->getIsNewObject()) {
+ // In this case, just return `null` so we know this is the initial
+ // transaction and it should be hidden.
+ return null;
+ }
+
+ $default_space = PhabricatorSpacesNamespaceQuery::getDefaultSpace();
+ if ($default_space) {
+ $space_phid = $default_space->getPHID();
+ }
+ }
+ return $space_phid;
case PhabricatorTransactions::TYPE_EDGE:
$edge_type = $xaction->getMetadataValue('edge:type');
if (!$edge_type) {
@@ -337,7 +356,16 @@
case PhabricatorTransactions::TYPE_BUILDABLE:
case PhabricatorTransactions::TYPE_TOKEN:
case PhabricatorTransactions::TYPE_INLINESTATE:
- return $xaction->getNewValue();
+ case PhabricatorTransactions::TYPE_SPACE:
+ $space_phid = $xaction->getNewValue();
+ if (!strlen($space_phid)) {
+ // If an install has no Spaces, we might end up with the empty string
+ // here instead of a strict `null`. Just make this work like callers
+ // might reasonably expect.
+ return null;
+ } else {
+ return $space_phid;
+ }
case PhabricatorTransactions::TYPE_EDGE:
return $this->getEdgeTransactionNewValue($xaction);
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
@@ -437,6 +465,7 @@
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
case PhabricatorTransactions::TYPE_INLINESTATE:
case PhabricatorTransactions::TYPE_EDGE:
+ case PhabricatorTransactions::TYPE_SPACE:
case PhabricatorTransactions::TYPE_COMMENT:
return $this->applyBuiltinInternalTransaction($object, $xaction);
}
@@ -485,6 +514,7 @@
case PhabricatorTransactions::TYPE_EDIT_POLICY:
case PhabricatorTransactions::TYPE_JOIN_POLICY:
case PhabricatorTransactions::TYPE_INLINESTATE:
+ case PhabricatorTransactions::TYPE_SPACE:
case PhabricatorTransactions::TYPE_COMMENT:
return $this->applyBuiltinExternalTransaction($object, $xaction);
}
@@ -537,6 +567,9 @@
case PhabricatorTransactions::TYPE_JOIN_POLICY:
$object->setJoinPolicy($xaction->getNewValue());
break;
+ case PhabricatorTransactions::TYPE_SPACE:
+ $object->setSpacePHID($xaction->getNewValue());
+ break;
}
}
@@ -1190,18 +1223,9 @@
PhabricatorPolicyCapability::CAN_VIEW);
break;
case PhabricatorTransactions::TYPE_VIEW_POLICY:
- PhabricatorPolicyFilter::requireCapability(
- $actor,
- $object,
- PhabricatorPolicyCapability::CAN_EDIT);
- break;
case PhabricatorTransactions::TYPE_EDIT_POLICY:
- PhabricatorPolicyFilter::requireCapability(
- $actor,
- $object,
- PhabricatorPolicyCapability::CAN_EDIT);
- break;
case PhabricatorTransactions::TYPE_JOIN_POLICY:
+ case PhabricatorTransactions::TYPE_SPACE:
PhabricatorPolicyFilter::requireCapability(
$actor,
$object,
@@ -1882,6 +1906,12 @@
$type,
PhabricatorPolicyCapability::CAN_EDIT);
break;
+ case PhabricatorTransactions::TYPE_SPACE:
+ $errors[] = $this->validateSpaceTransactions(
+ $object,
+ $xactions,
+ $type);
+ break;
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$groups = array();
foreach ($xactions as $xaction) {
@@ -1968,6 +1998,52 @@
return $errors;
}
+
+ private function validateSpaceTransactions(
+ PhabricatorLiskDAO $object,
+ array $xactions,
+ $transaction_type) {
+ $errors = array();
+
+ $all_spaces = PhabricatorSpacesNamespaceQuery::getAllSpaces();
+ $viewer_spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces(
+ $this->getActor());
+ foreach ($xactions as $xaction) {
+ $space_phid = $xaction->getNewValue();
+
+ if ($space_phid === null) {
+ if (!$all_spaces) {
+ // The install doesn't have any spaces, so this is fine.
+ continue;
+ }
+
+ // The install has some spaces, so every object needs to be put
+ // in a valid space.
+ $errors[] = new PhabricatorApplicationTransactionValidationError(
+ $transaction_type,
+ pht('Invalid'),
+ pht('You must choose a space for this object.'),
+ $xaction);
+ continue;
+ }
+
+ // If the PHID isn't `null`, it needs to be a valid space that the
+ // viewer can see.
+ if (empty($viewer_spaces[$space_phid])) {
+ $errors[] = new PhabricatorApplicationTransactionValidationError(
+ $transaction_type,
+ pht('Invalid'),
+ pht(
+ 'You can not put this object in the selected space, because '.
+ 'the space does not exist or you do not have access to it.'),
+ $xaction);
+ }
+ }
+
+ return $errors;
+ }
+
+
protected function adjustObjectForPolicyChecks(
PhabricatorLiskDAO $object,
array $xactions) {
diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php
--- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php
+++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php
@@ -250,6 +250,14 @@
$phids[] = array($new);
}
break;
+ case PhabricatorTransactions::TYPE_SPACE:
+ if ($old) {
+ $phids[] = array($old);
+ }
+ if ($new) {
+ $phids[] = array($new);
+ }
+ break;
case PhabricatorTransactions::TYPE_TOKEN:
break;
case PhabricatorTransactions::TYPE_BUILDABLE:
@@ -369,6 +377,8 @@
return 'fa-wrench';
case PhabricatorTransactions::TYPE_TOKEN:
return 'fa-trophy';
+ case PhabricatorTransactions::TYPE_SPACE:
+ return 'fa-compass';
}
return 'fa-pencil';
@@ -438,6 +448,7 @@
case PhabricatorTransactions::TYPE_VIEW_POLICY:
case PhabricatorTransactions::TYPE_EDIT_POLICY:
case PhabricatorTransactions::TYPE_JOIN_POLICY:
+ case PhabricatorTransactions::TYPE_SPACE:
if ($this->getOldValue() === null) {
return true;
} else {
@@ -597,6 +608,8 @@
return pht(
'All users are already subscribed to this %s.',
$this->getApplicationObjectTypeName());
+ case PhabricatorTransactions::TYPE_SPACE:
+ return pht('This object is already in that space.');
case PhabricatorTransactions::TYPE_EDGE:
return pht('Edges already exist; transaction has no effect.');
}
@@ -636,6 +649,12 @@
$this->getApplicationObjectTypeName(),
$this->renderPolicyName($old, 'old'),
$this->renderPolicyName($new, 'new'));
+ case PhabricatorTransactions::TYPE_SPACE:
+ return pht(
+ '%s shifted this object from the %s space to the %s space.',
+ $this->renderHandleLink($author_phid),
+ $this->renderHandleLink($old),
+ $this->renderHandleLink($new));
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
$add = array_diff($new, $old);
$rem = array_diff($old, $new);
@@ -821,6 +840,13 @@
'%s updated subscribers of %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
+ case PhabricatorTransactions::TYPE_SPACE:
+ return pht(
+ '%s shifted %s from the %s space to the %s space.',
+ $this->renderHandleLink($author_phid),
+ $this->renderHandleLink($object_phid),
+ $this->renderHandleLink($old),
+ $this->renderHandleLink($new));
case PhabricatorTransactions::TYPE_EDGE:
$new = ipull($new, 'dst');
$old = ipull($old, 'dst');
diff --git a/src/view/phui/PHUIHeaderView.php b/src/view/phui/PHUIHeaderView.php
--- a/src/view/phui/PHUIHeaderView.php
+++ b/src/view/phui/PHUIHeaderView.php
@@ -174,6 +174,20 @@
$header = array();
+ $header[] = $this->renderObjectSpaceInformation();
+
+ if ($this->objectName) {
+ $header[] = array(
+ phutil_tag(
+ 'a',
+ array(
+ 'href' => '/'.$this->objectName,
+ ),
+ $this->objectName),
+ ' ',
+ );
+ }
+
if ($this->actionLinks) {
$actions = array();
foreach ($this->actionLinks as $button) {
@@ -200,18 +214,6 @@
}
$header[] = $this->header;
- if ($this->objectName) {
- array_unshift(
- $header,
- phutil_tag(
- 'a',
- array(
- 'href' => '/'.$this->objectName,
- ),
- $this->objectName),
- ' ');
- }
-
if ($this->tags) {
$header[] = ' ';
$header[] = phutil_tag(
@@ -268,9 +270,9 @@
}
private function renderPolicyProperty(PhabricatorPolicyInterface $object) {
- $policies = PhabricatorPolicyQuery::loadPolicies(
- $this->getUser(),
- $object);
+ $viewer = $this->getUser();
+
+ $policies = PhabricatorPolicyQuery::loadPolicies($viewer, $object);
$view_capability = PhabricatorPolicyCapability::CAN_VIEW;
$policy = idx($policies, $view_capability);
@@ -294,4 +296,36 @@
return array($icon, $link);
}
+
+ private function renderObjectSpaceInformation() {
+ $viewer = $this->getUser();
+
+ $object = $this->policyObject;
+ if (!$object) {
+ return;
+ }
+
+ if (!($object instanceof PhabricatorSpacesInterface)) {
+ return;
+ }
+
+ $space_phid = $object->getSpacePHID();
+ if ($space_phid === null) {
+ $default_space = PhabricatorSpacesNamespaceQuery::getDefaultSpace();
+ if ($default_space) {
+ $space_phid = $default_space->getPHID();
+ }
+ }
+
+ if ($space_phid === null) {
+ return;
+ }
+
+ return array(
+ $viewer->renderHandle($space_phid),
+ ' | ',
+ );
+ }
+
+
}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sun, Nov 3, 4:03 AM (2 w, 1 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6757472
Default Alt Text
D13159.id31806.diff (22 KB)
Attached To
Mode
D13159: Support Spaces transactions
Attached
Detach File
Event Timeline
Log In to Comment