1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-26 08:42:41 +01:00

Add skeleton for Portals, a collection of dashboards and other resources

Summary:
Ref T13275. Today, you can build a custom page on the home page, on project pages, and in your favorites menu.

PHI374 would approximately like to build a completely standalone custom page, and this generally seems like a reasonable capability which we should support, and which should be easy to support if the "custom menu" stuff is built right.

In the near future, I'm planning to shore up some of the outstanding issues with profile menus and then build charts (which will have a big dashboard/panel component), so adding Portals now should let me double up on a lot of the testing and maybe make some of it a bit easier.

Test Plan:
Viewed the list of portals, created a new portal. Everything is currently a pure skeleton with no unique behavior.

Here's a glorious portal page:

{F6321846}

Reviewers: amckinley

Reviewed By: amckinley

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T13275

Differential Revision: https://secure.phabricator.com/D20348
This commit is contained in:
epriestley 2019-03-29 09:46:17 -07:00
parent 3e05ff2e99
commit 90df4b2bd1
20 changed files with 701 additions and 0 deletions

View file

@ -0,0 +1,11 @@
CREATE TABLE {$NAMESPACE}_dashboard.dashboard_portal (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
phid VARBINARY(64) NOT NULL,
name VARCHAR(255) NOT NULL COLLATE {$COLLATE_TEXT},
status VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT},
viewPolicy VARBINARY(64) NOT NULL,
editPolicy VARBINARY(64) NOT NULL,
properties LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT},
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE {$COLLATE_TEXT};

View file

@ -0,0 +1,19 @@
CREATE TABLE {$NAMESPACE}_dashboard.dashboard_portaltransaction (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
phid VARBINARY(64) NOT NULL,
authorPHID VARBINARY(64) NOT NULL,
objectPHID VARBINARY(64) NOT NULL,
viewPolicy VARBINARY(64) NOT NULL,
editPolicy VARBINARY(64) NOT NULL,
commentPHID VARBINARY(64) DEFAULT NULL,
commentVersion INT UNSIGNED NOT NULL,
transactionType VARCHAR(32) NOT NULL,
oldValue LONGTEXT NOT NULL,
newValue LONGTEXT NOT NULL,
contentSource LONGTEXT NOT NULL,
metadata LONGTEXT NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE {$COLLATE_TEXT};

View file

@ -2946,6 +2946,22 @@ phutil_register_library_map(array(
'PhabricatorDashboardPanelTransactionQuery' => 'applications/dashboard/query/PhabricatorDashboardPanelTransactionQuery.php', 'PhabricatorDashboardPanelTransactionQuery' => 'applications/dashboard/query/PhabricatorDashboardPanelTransactionQuery.php',
'PhabricatorDashboardPanelType' => 'applications/dashboard/paneltype/PhabricatorDashboardPanelType.php', 'PhabricatorDashboardPanelType' => 'applications/dashboard/paneltype/PhabricatorDashboardPanelType.php',
'PhabricatorDashboardPanelViewController' => 'applications/dashboard/controller/PhabricatorDashboardPanelViewController.php', 'PhabricatorDashboardPanelViewController' => 'applications/dashboard/controller/PhabricatorDashboardPanelViewController.php',
'PhabricatorDashboardPortal' => 'applications/dashboard/storage/PhabricatorDashboardPortal.php',
'PhabricatorDashboardPortalController' => 'applications/dashboard/controller/portal/PhabricatorDashboardPortalController.php',
'PhabricatorDashboardPortalEditConduitAPIMethod' => 'applications/dashboard/conduit/PhabricatorDashboardPortalEditConduitAPIMethod.php',
'PhabricatorDashboardPortalEditController' => 'applications/dashboard/controller/portal/PhabricatorDashboardPortalEditController.php',
'PhabricatorDashboardPortalEditEngine' => 'applications/dashboard/editor/PhabricatorDashboardPortalEditEngine.php',
'PhabricatorDashboardPortalEditor' => 'applications/dashboard/editor/PhabricatorDashboardPortalEditor.php',
'PhabricatorDashboardPortalListController' => 'applications/dashboard/controller/portal/PhabricatorDashboardPortalListController.php',
'PhabricatorDashboardPortalNameTransaction' => 'applications/dashboard/xaction/portal/PhabricatorDashboardPortalNameTransaction.php',
'PhabricatorDashboardPortalPHIDType' => 'applications/dashboard/phid/PhabricatorDashboardPortalPHIDType.php',
'PhabricatorDashboardPortalQuery' => 'applications/dashboard/query/PhabricatorDashboardPortalQuery.php',
'PhabricatorDashboardPortalSearchConduitAPIMethod' => 'applications/dashboard/conduit/PhabricatorDashboardPortalSearchConduitAPIMethod.php',
'PhabricatorDashboardPortalSearchEngine' => 'applications/dashboard/query/PhabricatorDashboardPortalSearchEngine.php',
'PhabricatorDashboardPortalStatus' => 'applications/dashboard/constants/PhabricatorDashboardPortalStatus.php',
'PhabricatorDashboardPortalTransaction' => 'applications/dashboard/storage/PhabricatorDashboardPortalTransaction.php',
'PhabricatorDashboardPortalTransactionType' => 'applications/dashboard/xaction/portal/PhabricatorDashboardPortalTransactionType.php',
'PhabricatorDashboardPortalViewController' => 'applications/dashboard/controller/portal/PhabricatorDashboardPortalViewController.php',
'PhabricatorDashboardProfileController' => 'applications/dashboard/controller/PhabricatorDashboardProfileController.php', 'PhabricatorDashboardProfileController' => 'applications/dashboard/controller/PhabricatorDashboardProfileController.php',
'PhabricatorDashboardProfileMenuItem' => 'applications/search/menuitem/PhabricatorDashboardProfileMenuItem.php', 'PhabricatorDashboardProfileMenuItem' => 'applications/search/menuitem/PhabricatorDashboardProfileMenuItem.php',
'PhabricatorDashboardQuery' => 'applications/dashboard/query/PhabricatorDashboardQuery.php', 'PhabricatorDashboardQuery' => 'applications/dashboard/query/PhabricatorDashboardQuery.php',
@ -8896,6 +8912,27 @@ phutil_register_library_map(array(
'PhabricatorDashboardPanelTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorDashboardPanelTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorDashboardPanelType' => 'Phobject', 'PhabricatorDashboardPanelType' => 'Phobject',
'PhabricatorDashboardPanelViewController' => 'PhabricatorDashboardController', 'PhabricatorDashboardPanelViewController' => 'PhabricatorDashboardController',
'PhabricatorDashboardPortal' => array(
'PhabricatorDashboardDAO',
'PhabricatorApplicationTransactionInterface',
'PhabricatorPolicyInterface',
'PhabricatorDestructibleInterface',
),
'PhabricatorDashboardPortalController' => 'PhabricatorDashboardController',
'PhabricatorDashboardPortalEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'PhabricatorDashboardPortalEditController' => 'PhabricatorDashboardPortalController',
'PhabricatorDashboardPortalEditEngine' => 'PhabricatorEditEngine',
'PhabricatorDashboardPortalEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorDashboardPortalListController' => 'PhabricatorDashboardPortalController',
'PhabricatorDashboardPortalNameTransaction' => 'PhabricatorDashboardPortalTransactionType',
'PhabricatorDashboardPortalPHIDType' => 'PhabricatorPHIDType',
'PhabricatorDashboardPortalQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorDashboardPortalSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'PhabricatorDashboardPortalSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorDashboardPortalStatus' => 'Phobject',
'PhabricatorDashboardPortalTransaction' => 'PhabricatorModularTransaction',
'PhabricatorDashboardPortalTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorDashboardPortalViewController' => 'PhabricatorDashboardPortalController',
'PhabricatorDashboardProfileController' => 'PhabricatorController', 'PhabricatorDashboardProfileController' => 'PhabricatorController',
'PhabricatorDashboardProfileMenuItem' => 'PhabricatorProfileMenuItem', 'PhabricatorDashboardProfileMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorDashboardQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorDashboardQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',

View file

@ -57,6 +57,14 @@ final class PhabricatorDashboardApplication extends PhabricatorApplication {
=> 'PhabricatorDashboardPanelArchiveController', => 'PhabricatorDashboardPanelArchiveController',
), ),
), ),
'/portal/' => array(
$this->getQueryRoutePattern() =>
'PhabricatorDashboardPortalListController',
$this->getEditRoutePattern('edit/') =>
'PhabricatorDashboardPortalEditController',
'view/(?P<id>\d)/' =>
'PhabricatorDashboardPortalViewController',
),
); );
} }

View file

@ -0,0 +1,19 @@
<?php
final class PhabricatorDashboardPortalEditConduitAPIMethod
extends PhabricatorEditEngineAPIMethod {
public function getAPIMethodName() {
return 'portal.edit';
}
public function newEditEngine() {
return new PhabricatorDashboardPortalEditEngine();
}
public function getMethodSummary() {
return pht(
'Apply transactions to create a new portal or edit an existing one.');
}
}

View file

@ -0,0 +1,18 @@
<?php
final class PhabricatorDashboardPortalSearchConduitAPIMethod
extends PhabricatorSearchEngineAPIMethod {
public function getAPIMethodName() {
return 'portal.search';
}
public function newSearchEngine() {
return new PhabricatorDashboardPortalSearchEngine();
}
public function getMethodSummary() {
return pht('Read information about portals.');
}
}

View file

@ -0,0 +1,9 @@
<?php
final class PhabricatorDashboardPortalStatus
extends Phobject {
const STATUS_ACTIVE = 'active';
const STATUS_ARCHIVED = 'archived';
}

View file

@ -0,0 +1,14 @@
<?php
abstract class PhabricatorDashboardPortalController
extends PhabricatorDashboardController {
protected function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Portals'), '/portal/');
return $crumbs;
}
}

View file

@ -0,0 +1,12 @@
<?php
final class PhabricatorDashboardPortalEditController
extends PhabricatorDashboardPortalController {
public function handleRequest(AphrontRequest $request) {
return id(new PhabricatorDashboardPortalEditEngine())
->setController($this)
->buildResponse();
}
}

View file

@ -0,0 +1,26 @@
<?php
final class PhabricatorDashboardPortalListController
extends PhabricatorDashboardPortalController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
return id(new PhabricatorDashboardPortalSearchEngine())
->setController($this)
->buildResponse();
}
protected function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs();
id(new PhabricatorDashboardPortalEditEngine())
->setViewer($this->getViewer())
->addActionToCrumbs($crumbs);
return $crumbs;
}
}

View file

@ -0,0 +1,34 @@
<?php
final class PhabricatorDashboardPortalViewController
extends PhabricatorDashboardPortalController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$id = $request->getURIData('id');
$portal = id(new PhabricatorDashboardPortalQuery())
->setViewer($viewer)
->withIDs(array($id))
->executeOne();
if (!$portal) {
return new Aphront404Response();
}
$content = $portal->getObjectName();
return $this->newPage()
->setTitle(
array(
pht('Portal'),
$portal->getName(),
))
->setPageObjectPHIDs(array($portal->getPHID()))
->appendChild($content);
}
}

View file

@ -0,0 +1,83 @@
<?php
final class PhabricatorDashboardPortalEditEngine
extends PhabricatorEditEngine {
const ENGINECONST = 'portal';
public function isEngineConfigurable() {
return false;
}
public function getEngineName() {
return pht('Portals');
}
public function getSummaryHeader() {
return pht('Edit Portals');
}
public function getSummaryText() {
return pht('This engine is used to modify portals.');
}
public function getEngineApplicationClass() {
return 'PhabricatorDashboardApplication';
}
protected function newEditableObject() {
return PhabricatorDashboardPortal::initializeNewPortal();
}
protected function newObjectQuery() {
return new PhabricatorDashboardPortalQuery();
}
protected function getObjectCreateTitleText($object) {
return pht('Create Portal');
}
protected function getObjectCreateButtonText($object) {
return pht('Create Portal');
}
protected function getObjectEditTitleText($object) {
return pht('Edit Portal: %s', $object->getName());
}
protected function getObjectEditShortText($object) {
return pht('Edit Portal');
}
protected function getObjectCreateShortText() {
return pht('Create Portal');
}
protected function getObjectName() {
return pht('Portal');
}
protected function getObjectViewURI($object) {
return $object->getURI();
}
protected function getEditorURI() {
return '/portal/edit/';
}
protected function buildCustomEditFields($object) {
return array(
id(new PhabricatorTextEditField())
->setKey('name')
->setLabel(pht('Name'))
->setDescription(pht('Name of the portal.'))
->setConduitDescription(pht('Rename the portal.'))
->setConduitTypeDescription(pht('New portal name.'))
->setTransactionType(
PhabricatorDashboardPortalNameTransaction::TRANSACTIONTYPE)
->setIsRequired(true)
->setValue($object->getName()),
);
}
}

View file

@ -0,0 +1,31 @@
<?php
final class PhabricatorDashboardPortalEditor
extends PhabricatorApplicationTransactionEditor {
public function getEditorApplicationClass() {
return 'PhabricatorDashboardApplication';
}
public function getEditorObjectsDescription() {
return pht('Portals');
}
public function getCreateObjectTitle($author, $object) {
return pht('%s created this portal.', $author);
}
public function getCreateObjectTitleForFeed($author, $object) {
return pht('%s created %s.', $author, $object);
}
public function getTransactionTypes() {
$types = parent::getTransactionTypes();
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
return $types;
}
}

View file

@ -0,0 +1,42 @@
<?php
final class PhabricatorDashboardPortalPHIDType
extends PhabricatorPHIDType {
const TYPECONST = 'PRTL';
public function getTypeName() {
return pht('Portal');
}
public function newObject() {
return new PhabricatorDashboardPortal();
}
public function getPHIDTypeApplicationClass() {
return 'PhabricatorDashboardApplication';
}
protected function buildQueryForObjects(
PhabricatorObjectQuery $query,
array $phids) {
return id(new PhabricatorDashboardPortalQuery())
->withPHIDs($phids);
}
public function loadHandles(
PhabricatorHandleQuery $query,
array $handles,
array $objects) {
foreach ($handles as $phid => $handle) {
$portal = $objects[$phid];
$handle
->setName($portal->getName())
->setURI($portal->getURI());
}
}
}

View file

@ -0,0 +1,64 @@
<?php
final class PhabricatorDashboardPortalQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $statuses;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withStatuses(array $statuses) {
$this->statuses = $statuses;
return $this;
}
public function newResultObject() {
return new PhabricatorDashboardPortal();
}
protected function loadPage() {
return $this->loadStandardPage($this->newResultObject());
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->statuses !== null) {
$where[] = qsprintf(
$conn,
'status IN (%Ls)',
$this->statuses);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorDashboardApplication';
}
}

View file

@ -0,0 +1,78 @@
<?php
final class PhabricatorDashboardPortalSearchEngine
extends PhabricatorApplicationSearchEngine {
public function getResultTypeDescription() {
return pht('Portals');
}
public function getApplicationClassName() {
return 'PhabricatorDashboardApplication';
}
public function newQuery() {
return new PhabricatorDashboardPortalQuery();
}
protected function buildQueryFromParameters(array $map) {
$query = $this->newQuery();
return $query;
}
protected function buildCustomSearchFields() {
return array();
}
protected function getURI($path) {
return '/portal/'.$path;
}
protected function getBuiltinQueryNames() {
$names = array();
$names['all'] = pht('All Portals');
return $names;
}
public function buildSavedQueryFromBuiltin($query_key) {
$query = $this->newSavedQuery();
$query->setQueryKey($query_key);
$viewer = $this->requireViewer();
switch ($query_key) {
case 'all':
return $query;
}
return parent::buildSavedQueryFromBuiltin($query_key);
}
protected function renderResultList(
array $portals,
PhabricatorSavedQuery $query,
array $handles) {
assert_instances_of($portals, 'PhabricatorDashboardPortal');
$viewer = $this->requireViewer();
$list = new PHUIObjectItemListView();
$list->setUser($viewer);
foreach ($portals as $portal) {
$item = id(new PHUIObjectItemView())
->setObjectName($portal->getObjectName())
->setHeader($portal->getName())
->setHref($portal->getURI())
->setObject($portal);
$list->addItem($item);
}
return id(new PhabricatorApplicationSearchResultView())
->setObjectList($list)
->setNoDataString(pht('No portals found.'));
}
}

View file

@ -0,0 +1,104 @@
<?php
final class PhabricatorDashboardPortal
extends PhabricatorDashboardDAO
implements
PhabricatorApplicationTransactionInterface,
PhabricatorPolicyInterface,
PhabricatorDestructibleInterface {
protected $name;
protected $viewPolicy;
protected $editPolicy;
protected $status;
protected $properties = array();
public static function initializeNewPortal() {
return id(new self())
->setName('')
->setViewPolicy(PhabricatorPolicies::getMostOpenPolicy())
->setEditPolicy(PhabricatorPolicies::POLICY_USER)
->setStatus(PhabricatorDashboardPortalStatus::STATUS_ACTIVE);
}
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'properties' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'text255',
'status' => 'text32',
),
) + parent::getConfiguration();
}
public function getPHIDType() {
return PhabricatorDashboardPortalPHIDType::TYPECONST;
}
public function getPortalProperty($key, $default = null) {
return idx($this->properties, $key, $default);
}
public function setPortalProperty($key, $value) {
$this->properties[$key] = $value;
return $this;
}
public function getObjectName() {
return pht('Portal %d', $this->getID());
}
public function getURI() {
return '/portal/view/'.$this->getID().'/';
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {
return new PhabricatorDashboardPortalEditor();
}
public function getApplicationTransactionTemplate() {
return new PhabricatorDashboardPortalTransaction();
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return $this->getViewPolicy();
case PhabricatorPolicyCapability::CAN_EDIT:
return $this->getEditPolicy();
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return false;
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
$this->delete();
}
}

View file

@ -0,0 +1,18 @@
<?php
final class PhabricatorDashboardPortalTransaction
extends PhabricatorModularTransaction {
public function getApplicationName() {
return 'dashboard';
}
public function getApplicationTransactionType() {
return PhabricatorDashboardPortalPHIDType::TYPECONST;
}
public function getBaseTransactionClass() {
return 'PhabricatorDashboardPortalTransactionType';
}
}

View file

@ -0,0 +1,70 @@
<?php
final class PhabricatorDashboardPortalNameTransaction
extends PhabricatorDashboardPortalTransactionType {
const TRANSACTIONTYPE = 'name';
public function generateOldValue($object) {
return $object->getName();
}
public function applyInternalEffects($object, $value) {
$object->setName($value);
}
public function getTitle() {
$old = $this->getOldValue();
$new = $this->getNewValue();
return pht(
'%s renamed this portal from %s to %s.',
$this->renderAuthor(),
$this->renderOldValue(),
$this->renderNewValue());
}
public function validateTransactions($object, array $xactions) {
$errors = array();
$max_length = $object->getColumnMaximumByteLength('name');
foreach ($xactions as $xaction) {
$new = $xaction->getNewValue();
if (!strlen($new)) {
$errors[] = $this->newInvalidError(
pht('Portals must have a title.'),
$xaction);
continue;
}
if (strlen($new) > $max_length) {
$errors[] = $this->newInvalidError(
pht(
'Portal names must not be longer than %s characters.',
$max_length));
continue;
}
}
if (!$errors) {
if ($this->isEmptyTextTransaction($object->getName(), $xactions)) {
$errors[] = $this->newRequiredError(
pht('Portals must have a title.'));
}
}
return $errors;
}
public function getTransactionTypeForConduit($xaction) {
return 'name';
}
public function getFieldValuesForConduit($xaction, $data) {
return array(
'old' => $xaction->getOldValue(),
'new' => $xaction->getNewValue(),
);
}
}

View file

@ -0,0 +1,4 @@
<?php
abstract class PhabricatorDashboardPortalTransactionType
extends PhabricatorModularTransactionType {}