Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15664499
D13988.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
21 KB
Referenced Files
None
Subscribers
None
D13988.diff
View Options
diff --git a/resources/sql/autopatches/20150823.nuance.queue.1.sql b/resources/sql/autopatches/20150823.nuance.queue.1.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20150823.nuance.queue.1.sql
@@ -0,0 +1 @@
+DROP TABLE {$NAMESPACE}_nuance.nuance_queueitem;
diff --git a/resources/sql/autopatches/20150823.nuance.queue.2.sql b/resources/sql/autopatches/20150823.nuance.queue.2.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20150823.nuance.queue.2.sql
@@ -0,0 +1,2 @@
+ALTER TABLE {$NAMESPACE}_nuance.nuance_item
+ ADD queuePHID VARBINARY(64) NOT NULL;
diff --git a/resources/sql/autopatches/20150823.nuance.queue.3.sql b/resources/sql/autopatches/20150823.nuance.queue.3.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20150823.nuance.queue.3.sql
@@ -0,0 +1,2 @@
+ALTER TABLE {$NAMESPACE}_nuance.nuance_source
+ ADD defaultQueuePHID VARBINARY(64) NOT NULL;
diff --git a/resources/sql/autopatches/20150823.nuance.queue.4.sql b/resources/sql/autopatches/20150823.nuance.queue.4.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20150823.nuance.queue.4.sql
@@ -0,0 +1,2 @@
+ALTER TABLE {$NAMESPACE}_nuance.nuance_item
+ DROP dateNuanced;
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
@@ -1288,9 +1288,9 @@
'NuancePhabricatorFormSourceDefinition' => 'applications/nuance/source/NuancePhabricatorFormSourceDefinition.php',
'NuanceQuery' => 'applications/nuance/query/NuanceQuery.php',
'NuanceQueue' => 'applications/nuance/storage/NuanceQueue.php',
+ 'NuanceQueueDatasource' => 'applications/nuance/typeahead/NuanceQueueDatasource.php',
'NuanceQueueEditController' => 'applications/nuance/controller/NuanceQueueEditController.php',
'NuanceQueueEditor' => 'applications/nuance/editor/NuanceQueueEditor.php',
- 'NuanceQueueItem' => 'applications/nuance/storage/NuanceQueueItem.php',
'NuanceQueueListController' => 'applications/nuance/controller/NuanceQueueListController.php',
'NuanceQueuePHIDType' => 'applications/nuance/phid/NuanceQueuePHIDType.php',
'NuanceQueueQuery' => 'applications/nuance/query/NuanceQueueQuery.php',
@@ -5080,9 +5080,9 @@
'PhabricatorPolicyInterface',
'PhabricatorApplicationTransactionInterface',
),
+ 'NuanceQueueDatasource' => 'PhabricatorTypeaheadDatasource',
'NuanceQueueEditController' => 'NuanceController',
'NuanceQueueEditor' => 'PhabricatorApplicationTransactionEditor',
- 'NuanceQueueItem' => 'NuanceDAO',
'NuanceQueueListController' => 'NuanceController',
'NuanceQueuePHIDType' => 'PhabricatorPHIDType',
'NuanceQueueQuery' => 'NuanceQuery',
diff --git a/src/applications/nuance/controller/NuanceItemEditController.php b/src/applications/nuance/controller/NuanceItemEditController.php
--- a/src/applications/nuance/controller/NuanceItemEditController.php
+++ b/src/applications/nuance/controller/NuanceItemEditController.php
@@ -33,10 +33,17 @@
->setHeaderText($title)
->addPropertyList($properties);
+ $timeline = $this->buildTransactionTimeline(
+ $item,
+ new NuanceItemTransactionQuery());
+
+ $timeline->setShouldTerminate(true);
+
return $this->buildApplicationPage(
array(
$crumbs,
$box,
+ $timeline,
),
array(
'title' => $title,
@@ -62,6 +69,10 @@
pht('Source'),
$viewer->renderHandle($item->getSourcePHID()));
+ $properties->addProperty(
+ pht('Queue'),
+ $viewer->renderHandle($item->getQueuePHID()));
+
$source = $item->getSource();
$definition = $source->requireDefinition();
diff --git a/src/applications/nuance/controller/NuanceSourceViewController.php b/src/applications/nuance/controller/NuanceSourceViewController.php
--- a/src/applications/nuance/controller/NuanceSourceViewController.php
+++ b/src/applications/nuance/controller/NuanceSourceViewController.php
@@ -13,7 +13,7 @@
return new Aphront404Response();
}
- $source_phid = $source->getPHID();
+ $source_id = $source->getID();
$timeline = $this->buildTransactionTimeline(
$source,
@@ -34,10 +34,29 @@
$crumbs->addTextCrumb($title);
+
+ $can_edit = PhabricatorPolicyFilter::hasCapability(
+ $viewer,
+ $source,
+ PhabricatorPolicyCapability::CAN_EDIT);
+
+ $routing_list = id(new PHUIPropertyListView())
+ ->addProperty(
+ pht('Default Queue'),
+ $viewer->renderHandle($source->getDefaultQueuePHID()));
+
+ $routing_header = id(new PHUIHeaderView())
+ ->setHeader(pht('Routing Rules'));
+
+ $routing = id(new PHUIObjectBoxView())
+ ->setHeader($routing_header)
+ ->addPropertyList($routing_list);
+
return $this->buildApplicationPage(
array(
$crumbs,
$box,
+ $routing,
$timeline,
),
array(
diff --git a/src/applications/nuance/editor/NuanceItemEditor.php b/src/applications/nuance/editor/NuanceItemEditor.php
--- a/src/applications/nuance/editor/NuanceItemEditor.php
+++ b/src/applications/nuance/editor/NuanceItemEditor.php
@@ -18,6 +18,7 @@
$types[] = NuanceItemTransaction::TYPE_SOURCE;
$types[] = NuanceItemTransaction::TYPE_REQUESTOR;
$types[] = NuanceItemTransaction::TYPE_PROPERTY;
+ $types[] = NuanceItemTransaction::TYPE_QUEUE;
$types[] = PhabricatorTransactions::TYPE_EDGE;
$types[] = PhabricatorTransactions::TYPE_COMMENT;
@@ -38,6 +39,8 @@
return $object->getSourcePHID();
case NuanceItemTransaction::TYPE_OWNER:
return $object->getOwnerPHID();
+ case NuanceItemTransaction::TYPE_QUEUE:
+ return $object->getQueuePHID();
case NuanceItemTransaction::TYPE_PROPERTY:
$key = $xaction->getMetadataValue(
NuanceItemTransaction::PROPERTY_KEY);
@@ -56,6 +59,7 @@
case NuanceItemTransaction::TYPE_SOURCE:
case NuanceItemTransaction::TYPE_OWNER:
case NuanceItemTransaction::TYPE_PROPERTY:
+ case NuanceItemTransaction::TYPE_QUEUE:
return $xaction->getNewValue();
}
@@ -76,6 +80,9 @@
case NuanceItemTransaction::TYPE_OWNER:
$object->setOwnerPHID($xaction->getNewValue());
break;
+ case NuanceItemTransaction::TYPE_QUEUE:
+ $object->setQueuePHID($xaction->getNewValue());
+ break;
case NuanceItemTransaction::TYPE_PROPERTY:
$key = $xaction->getMetadataValue(
NuanceItemTransaction::PROPERTY_KEY);
@@ -93,6 +100,7 @@
case NuanceItemTransaction::TYPE_SOURCE:
case NuanceItemTransaction::TYPE_OWNER:
case NuanceItemTransaction::TYPE_PROPERTY:
+ case NuanceItemTransaction::TYPE_QUEUE:
return;
}
diff --git a/src/applications/nuance/editor/NuanceSourceEditor.php b/src/applications/nuance/editor/NuanceSourceEditor.php
--- a/src/applications/nuance/editor/NuanceSourceEditor.php
+++ b/src/applications/nuance/editor/NuanceSourceEditor.php
@@ -15,6 +15,7 @@
$types = parent::getTransactionTypes();
$types[] = NuanceSourceTransaction::TYPE_NAME;
+ $types[] = NuanceSourceTransaction::TYPE_DEFAULT_QUEUE;
$types[] = PhabricatorTransactions::TYPE_EDGE;
$types[] = PhabricatorTransactions::TYPE_COMMENT;
@@ -31,6 +32,8 @@
switch ($xaction->getTransactionType()) {
case NuanceSourceTransaction::TYPE_NAME:
return $object->getName();
+ case NuanceSourceTransaction::TYPE_DEFAULT_QUEUE:
+ return $object->getDefaultQueuePHID();
}
return parent::getCustomTransactionOldValue($object, $xaction);
@@ -42,6 +45,7 @@
switch ($xaction->getTransactionType()) {
case NuanceSourceTransaction::TYPE_NAME:
+ case NuanceSourceTransaction::TYPE_DEFAULT_QUEUE:
return $xaction->getNewValue();
}
@@ -56,6 +60,9 @@
case NuanceSourceTransaction::TYPE_NAME:
$object->setName($xaction->getNewValue());
break;
+ case NuanceSourceTransaction::TYPE_DEFAULT_QUEUE:
+ $object->setDefaultQueuePHID($xaction->getNewValue());
+ break;
}
}
@@ -65,6 +72,7 @@
switch ($xaction->getTransactionType()) {
case NuanceSourceTransaction::TYPE_NAME:
+ case NuanceSourceTransaction::TYPE_DEFAULT_QUEUE:
return;
}
@@ -95,6 +103,19 @@
$errors[] = $error;
}
break;
+ case NuanceSourceTransaction::TYPE_DEFAULT_QUEUE:
+ foreach ($xactions as $xaction) {
+ if (!$xaction->getNewValue()) {
+ $error = new PhabricatorApplicationTransactionValidationError(
+ $type,
+ pht('Required'),
+ pht('Sources must have a default queue.'),
+ $xaction);
+ $error->setIsMissingFieldError(true);
+ $errors[] = $error;
+ }
+ }
+ break;
}
return $errors;
diff --git a/src/applications/nuance/source/NuanceSourceDefinition.php b/src/applications/nuance/source/NuanceSourceDefinition.php
--- a/src/applications/nuance/source/NuanceSourceDefinition.php
+++ b/src/applications/nuance/source/NuanceSourceDefinition.php
@@ -169,25 +169,39 @@
$form = $this->augmentEditForm($form, $ex);
+ $default_phid = $source->getDefaultQueuePHID();
+ if ($default_phid) {
+ $default_queues = array($default_phid);
+ } else {
+ $default_queues = array();
+ }
+
$form
+ ->appendControl(
+ id(new AphrontFormTokenizerControl())
+ ->setLabel(pht('Default Queue'))
+ ->setName('defaultQueuePHIDs')
+ ->setLimit(1)
+ ->setDatasource(new NuanceQueueDatasource())
+ ->setValue($default_queues))
->appendChild(
id(new AphrontFormPolicyControl())
- ->setUser($user)
- ->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
- ->setPolicyObject($source)
- ->setPolicies($policies)
- ->setName('viewPolicy'))
+ ->setUser($user)
+ ->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
+ ->setPolicyObject($source)
+ ->setPolicies($policies)
+ ->setName('viewPolicy'))
->appendChild(
id(new AphrontFormPolicyControl())
- ->setUser($user)
- ->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
- ->setPolicyObject($source)
- ->setPolicies($policies)
- ->setName('editPolicy'))
+ ->setUser($user)
+ ->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
+ ->setPolicyObject($source)
+ ->setPolicies($policies)
+ ->setName('editPolicy'))
->appendChild(
id(new AphrontFormSubmitControl())
- ->addCancelButton($source->getURI())
- ->setValue(pht('Save')));
+ ->addCancelButton($source->getURI())
+ ->setValue(pht('Save')));
return $form;
}
@@ -213,13 +227,19 @@
$transactions[] = id(new NuanceSourceTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY)
->setNewValue($request->getStr('editPolicy'));
+
$transactions[] = id(new NuanceSourceTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY)
->setNewValue($request->getStr('viewPolicy'));
- $transactions[] = id(new NuanceSourceTransaction())
+
+ $transactions[] = id(new NuanceSourceTransaction())
->setTransactionType(NuanceSourceTransaction::TYPE_NAME)
->setNewvalue($request->getStr('name'));
+ $transactions[] = id(new NuanceSourceTransaction())
+ ->setTransactionType(NuanceSourceTransaction::TYPE_DEFAULT_QUEUE)
+ ->setNewvalue(head($request->getArr('defaultQueuePHIDs')));
+
return $transactions;
}
@@ -251,6 +271,12 @@
->setTransactionType(NuanceItemTransaction::TYPE_REQUESTOR)
->setNewValue($requestor->getPHID());
+ // TODO: Eventually, apply real routing rules. For now, just put everything
+ // in the default queue for the source.
+ $xactions[] = id(new NuanceItemTransaction())
+ ->setTransactionType(NuanceItemTransaction::TYPE_QUEUE)
+ ->setNewValue($source->getDefaultQueuePHID());
+
foreach ($properties as $key => $property) {
$xactions[] = id(new NuanceItemTransaction())
->setTransactionType(NuanceItemTransaction::TYPE_PROPERTY)
diff --git a/src/applications/nuance/storage/NuanceItem.php b/src/applications/nuance/storage/NuanceItem.php
--- a/src/applications/nuance/storage/NuanceItem.php
+++ b/src/applications/nuance/storage/NuanceItem.php
@@ -17,13 +17,12 @@
protected $sourceLabel;
protected $data = array();
protected $mailKey;
- protected $dateNuanced;
+ protected $queuePHID;
private $source = self::ATTACHABLE;
public static function initializeNewItem() {
return id(new NuanceItem())
- ->setDateNuanced(time())
->setStatus(self::STATUS_OPEN);
}
@@ -38,17 +37,19 @@
'sourceLabel' => 'text255?',
'status' => 'uint32',
'mailKey' => 'bytes20',
- 'dateNuanced' => 'epoch',
),
self::CONFIG_KEY_SCHEMA => array(
'key_source' => array(
- 'columns' => array('sourcePHID', 'status', 'dateNuanced', 'id'),
+ 'columns' => array('sourcePHID', 'status'),
),
'key_owner' => array(
- 'columns' => array('ownerPHID', 'status', 'dateNuanced', 'id'),
+ 'columns' => array('ownerPHID', 'status'),
),
- 'key_contacter' => array(
- 'columns' => array('requestorPHID', 'status', 'dateNuanced', 'id'),
+ 'key_requestor' => array(
+ 'columns' => array('requestorPHID', 'status'),
+ ),
+ 'key_queue' => array(
+ 'columns' => array('queuePHID', 'status'),
),
),
) + parent::getConfiguration();
@@ -144,7 +145,6 @@
'sourceLabel' => $this->getSourceLabel(),
'dateCreated' => $this->getDateCreated(),
'dateModified' => $this->getDateModified(),
- 'dateNuanced' => $this->getDateNuanced(),
);
}
diff --git a/src/applications/nuance/storage/NuanceItemTransaction.php b/src/applications/nuance/storage/NuanceItemTransaction.php
--- a/src/applications/nuance/storage/NuanceItemTransaction.php
+++ b/src/applications/nuance/storage/NuanceItemTransaction.php
@@ -9,6 +9,7 @@
const TYPE_REQUESTOR = 'nuance.item.requestor';
const TYPE_SOURCE = 'nuance.item.source';
const TYPE_PROPERTY = 'nuance.item.property';
+ const TYPE_QUEUE = 'nuance.item.queue';
public function getApplicationTransactionType() {
return NuanceItemPHIDType::TYPECONST;
@@ -18,4 +19,55 @@
return new NuanceItemTransactionComment();
}
+ public function shouldHide() {
+ $old = $this->getOldValue();
+ $type = $this->getTransactionType();
+
+ switch ($type) {
+ case self::TYPE_REQUESTOR:
+ case self::TYPE_SOURCE:
+ return ($old === null);
+ }
+
+ return parent::shouldHide();
+ }
+
+ public function getRequiredHandlePHIDs() {
+ $old = $this->getOldValue();
+ $new = $this->getNewValue();
+ $type = $this->getTransactionType();
+
+ $phids = parent::getRequiredHandlePHIDs();
+ switch ($type) {
+ case self::TYPE_QUEUE:
+ if ($old) {
+ $phids[] = $old;
+ }
+ if ($new) {
+ $phids[] = $new;
+ }
+ break;
+ }
+
+ return $phids;
+ }
+
+ public function getTitle() {
+ $old = $this->getOldValue();
+ $new = $this->getNewValue();
+ $type = $this->getTransactionType();
+
+ $author_phid = $this->getAuthorPHID();
+
+ switch ($type) {
+ case self::TYPE_QUEUE:
+ return pht(
+ '%s routed this item to the %s queue.',
+ $this->renderHandleLink($author_phid),
+ $this->renderHandleLink($new));
+ }
+
+ return parent::getTitle();
+ }
+
}
diff --git a/src/applications/nuance/storage/NuanceQueueItem.php b/src/applications/nuance/storage/NuanceQueueItem.php
deleted file mode 100644
--- a/src/applications/nuance/storage/NuanceQueueItem.php
+++ /dev/null
@@ -1,34 +0,0 @@
-<?php
-
-final class NuanceQueueItem
- extends NuanceDAO {
-
- protected $queuePHID;
- protected $itemPHID;
- protected $itemStatus;
- protected $itemDateNuanced;
-
- protected function getConfiguration() {
- return array(
- self::CONFIG_COLUMN_SCHEMA => array(
- 'itemStatus' => 'uint32',
- 'itemDateNuanced' => 'epoch',
- ),
- self::CONFIG_KEY_SCHEMA => array(
- 'key_one_per_queue' => array(
- 'columns' => array('itemPHID', 'queuePHID'),
- 'unique' => true,
- ),
- 'key_queue' => array(
- 'columns' => array(
- 'queuePHID',
- 'itemStatus',
- 'itemDateNuanced',
- 'id',
- ),
- ),
- ),
- ) + parent::getConfiguration();
- }
-
-}
diff --git a/src/applications/nuance/storage/NuanceQueueTransaction.php b/src/applications/nuance/storage/NuanceQueueTransaction.php
--- a/src/applications/nuance/storage/NuanceQueueTransaction.php
+++ b/src/applications/nuance/storage/NuanceQueueTransaction.php
@@ -12,4 +12,34 @@
return new NuanceQueueTransactionComment();
}
+ public function shouldHide() {
+ $old = $this->getOldValue();
+ $type = $this->getTransactionType();
+
+ switch ($type) {
+ case self::TYPE_NAME:
+ return ($old === null);
+ }
+
+ return parent::shouldHide();
+ }
+
+ public function getTitle() {
+ $old = $this->getOldValue();
+ $new = $this->getNewValue();
+ $type = $this->getTransactionType();
+
+ $author_phid = $this->getAuthorPHID();
+
+ switch ($type) {
+ case self::TYPE_NAME:
+ return pht(
+ '%s renamed this queue from "%s" to "%s".',
+ $this->renderHandleLink($author_phid),
+ $old,
+ $new);
+ }
+
+ return parent::getTitle();
+ }
}
diff --git a/src/applications/nuance/storage/NuanceSource.php b/src/applications/nuance/storage/NuanceSource.php
--- a/src/applications/nuance/storage/NuanceSource.php
+++ b/src/applications/nuance/storage/NuanceSource.php
@@ -11,6 +11,7 @@
protected $mailKey;
protected $viewPolicy;
protected $editPolicy;
+ protected $defaultQueuePHID;
private $definition;
diff --git a/src/applications/nuance/storage/NuanceSourceTransaction.php b/src/applications/nuance/storage/NuanceSourceTransaction.php
--- a/src/applications/nuance/storage/NuanceSourceTransaction.php
+++ b/src/applications/nuance/storage/NuanceSourceTransaction.php
@@ -3,7 +3,8 @@
final class NuanceSourceTransaction
extends NuanceTransaction {
- const TYPE_NAME = 'name-source';
+ const TYPE_NAME = 'source.name';
+ const TYPE_DEFAULT_QUEUE = 'source.queue.default';
public function getApplicationTransactionType() {
return NuanceSourcePHIDType::TYPECONST;
@@ -13,27 +14,63 @@
return new NuanceSourceTransactionComment();
}
- public function getTitle() {
+ public function shouldHide() {
$old = $this->getOldValue();
$new = $this->getNewValue();
- $author_phid = $this->getAuthorPHID();
+ $type = $this->getTransactionType();
- switch ($this->getTransactionType()) {
+ switch ($type) {
+ case self::TYPE_DEFAULT_QUEUE:
+ return !$old;
case self::TYPE_NAME:
- if ($old === null) {
- return pht(
- '%s created this source.',
- $this->renderHandleLink($author_phid));
- } else {
- return pht(
- '%s renamed this source from "%s" to "%s".',
- $this->renderHandleLink($author_phid),
- $old,
- $new);
+ return ($old === null);
+ }
+
+ return parent::shouldHide();
+ }
+
+ public function getRequiredHandlePHIDs() {
+ $old = $this->getOldValue();
+ $new = $this->getNewValue();
+ $type = $this->getTransactionType();
+
+ $phids = parent::getRequiredHandlePHIDs();
+ switch ($type) {
+ case self::TYPE_DEFAULT_QUEUE:
+ if ($old) {
+ $phids[] = $old;
+ }
+ if ($new) {
+ $phids[] = $new;
}
break;
}
+ return $phids;
+ }
+
+ public function getTitle() {
+ $old = $this->getOldValue();
+ $new = $this->getNewValue();
+ $type = $this->getTransactionType();
+ $author_phid = $this->getAuthorPHID();
+
+ switch ($type) {
+ case self::TYPE_DEFAULT_QUEUE:
+ return pht(
+ '%s changed the default queue from %s to %s.',
+ $this->renderHandleLink($author_phid),
+ $this->renderHandleLink($old),
+ $this->renderHandleLink($new));
+ case self::TYPE_NAME:
+ return pht(
+ '%s renamed this source from "%s" to "%s".',
+ $this->renderHandleLink($author_phid),
+ $old,
+ $new);
+ }
+
+ return parent::getTitle();
}
}
diff --git a/src/applications/nuance/typeahead/NuanceQueueDatasource.php b/src/applications/nuance/typeahead/NuanceQueueDatasource.php
new file mode 100644
--- /dev/null
+++ b/src/applications/nuance/typeahead/NuanceQueueDatasource.php
@@ -0,0 +1,38 @@
+<?php
+
+final class NuanceQueueDatasource
+ extends PhabricatorTypeaheadDatasource {
+
+ public function getBrowseTitle() {
+ return pht('Browse Queues');
+ }
+
+ public function getPlaceholderText() {
+ return pht('Type a queue name...');
+ }
+
+ public function getDatasourceApplicationClass() {
+ return 'PhabricatorNuanceApplication';
+ }
+
+ public function loadResults() {
+ $viewer = $this->getViewer();
+ $raw_query = $this->getRawQuery();
+
+ $results = array();
+
+ // TODO: Make this use real typeahead logic.
+ $query = new NuanceQueueQuery();
+ $queues = $this->executeQuery($query);
+
+ foreach ($queues as $queue) {
+ $results[] = id(new PhabricatorTypeaheadResult())
+ ->setName($queue->getName())
+ ->setURI('/nuance/queue/'.$queue->getID().'/')
+ ->setPHID($queue->getPHID());
+ }
+
+ return $this->filterResultsAgainstTokens($results);
+ }
+
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mon, May 26, 1:08 AM (19 h, 15 m)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
8026063
Default Alt Text
D13988.diff (21 KB)
Attached To
Mode
D13988: Add very basic routing to Nuance
Attached
Detach File
Event Timeline
Log In to Comment