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

Rebuild Dashboards on EditEngine: v1 Major Jank Edition

Summary:
Depends on D20383. Ref T13272. Fixes T12363. See PHI997. This gets the edit flows for tab panels functional again. They aren't //nice//, and a lot of the workflows are fairly janky: for example, most of them end up with you on the tab panel's page, which isn't useful if you started on a dashboard page.

However, these flows were extremely janky before anyway (see T12363) and I suspect this is a net improvement even though it's a bit of a mess. I anticipate cleaning this up bit-by-bit in future diffs.

Test Plan: {F6366372}

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13272, T12363

Differential Revision: https://secure.phabricator.com/D20384
This commit is contained in:
epriestley 2019-04-08 10:29:26 -07:00
parent fb19310631
commit a35fda2019
10 changed files with 519 additions and 28 deletions

View file

@ -9,7 +9,7 @@ return array(
'names' => array(
'conpherence.pkg.css' => '3c8a0668',
'conpherence.pkg.js' => '020aebcf',
'core.pkg.css' => '2d4810eb',
'core.pkg.css' => '671b9fae',
'core.pkg.js' => 'c783d8f6',
'differential.pkg.css' => '8d8360fb',
'differential.pkg.js' => '67e02996',
@ -134,7 +134,7 @@ return array(
'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '490e2e2e',
'rsrc/css/phui/object-item/phui-oi-list-view.css' => 'f14f2422',
'rsrc/css/phui/object-item/phui-oi-simple-ui.css' => '6a30fa46',
'rsrc/css/phui/phui-action-list.css' => 'c4972757',
'rsrc/css/phui/phui-action-list.css' => 'c34af376',
'rsrc/css/phui/phui-action-panel.css' => '6c386cbf',
'rsrc/css/phui/phui-badge.css' => '666e25ad',
'rsrc/css/phui/phui-basic-nav-view.css' => '56ebd66d',
@ -757,7 +757,7 @@ return array(
'path-typeahead' => 'ad486db3',
'people-picture-menu-item-css' => 'fe8e07cf',
'people-profile-css' => '2ea2daa1',
'phabricator-action-list-view-css' => 'c4972757',
'phabricator-action-list-view-css' => 'c34af376',
'phabricator-busy' => '5202e831',
'phabricator-chatlog-css' => 'abdc76ee',
'phabricator-content-source-view-css' => 'cdf0d579',

View file

@ -2942,6 +2942,7 @@ phutil_register_library_map(array(
'PhabricatorDashboardPanelRenderingEngine' => 'applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php',
'PhabricatorDashboardPanelSearchEngine' => 'applications/dashboard/query/PhabricatorDashboardPanelSearchEngine.php',
'PhabricatorDashboardPanelStatusTransaction' => 'applications/dashboard/xaction/panel/PhabricatorDashboardPanelStatusTransaction.php',
'PhabricatorDashboardPanelTabsController' => 'applications/dashboard/controller/panel/PhabricatorDashboardPanelTabsController.php',
'PhabricatorDashboardPanelTransaction' => 'applications/dashboard/storage/PhabricatorDashboardPanelTransaction.php',
'PhabricatorDashboardPanelTransactionEditor' => 'applications/dashboard/editor/PhabricatorDashboardPanelTransactionEditor.php',
'PhabricatorDashboardPanelTransactionQuery' => 'applications/dashboard/query/PhabricatorDashboardPanelTransactionQuery.php',
@ -2984,6 +2985,7 @@ phutil_register_library_map(array(
'PhabricatorDashboardRenderingEngine' => 'applications/dashboard/engine/PhabricatorDashboardRenderingEngine.php',
'PhabricatorDashboardSchemaSpec' => 'applications/dashboard/storage/PhabricatorDashboardSchemaSpec.php',
'PhabricatorDashboardSearchEngine' => 'applications/dashboard/query/PhabricatorDashboardSearchEngine.php',
'PhabricatorDashboardTabsPanelTabsTransaction' => 'applications/dashboard/xaction/panel/PhabricatorDashboardTabsPanelTabsTransaction.php',
'PhabricatorDashboardTabsPanelType' => 'applications/dashboard/paneltype/PhabricatorDashboardTabsPanelType.php',
'PhabricatorDashboardTextPanelTextTransaction' => 'applications/dashboard/xaction/panel/PhabricatorDashboardTextPanelTextTransaction.php',
'PhabricatorDashboardTextPanelType' => 'applications/dashboard/paneltype/PhabricatorDashboardTextPanelType.php',
@ -8920,6 +8922,7 @@ phutil_register_library_map(array(
'PhabricatorDashboardPanelRenderingEngine' => 'Phobject',
'PhabricatorDashboardPanelSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorDashboardPanelStatusTransaction' => 'PhabricatorDashboardPanelTransactionType',
'PhabricatorDashboardPanelTabsController' => 'PhabricatorDashboardController',
'PhabricatorDashboardPanelTransaction' => 'PhabricatorModularTransaction',
'PhabricatorDashboardPanelTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorDashboardPanelTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
@ -8967,6 +8970,7 @@ phutil_register_library_map(array(
'PhabricatorDashboardRenderingEngine' => 'Phobject',
'PhabricatorDashboardSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhabricatorDashboardSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorDashboardTabsPanelTabsTransaction' => 'PhabricatorDashboardPanelPropertyTransaction',
'PhabricatorDashboardTabsPanelType' => 'PhabricatorDashboardPanelType',
'PhabricatorDashboardTextPanelTextTransaction' => 'PhabricatorDashboardPanelPropertyTransaction',
'PhabricatorDashboardTextPanelType' => 'PhabricatorDashboardPanelType',

View file

@ -62,6 +62,8 @@ final class PhabricatorDashboardApplication extends PhabricatorApplication {
'render/(?P<id>\d+)/' => 'PhabricatorDashboardPanelRenderController',
'archive/(?P<id>\d+)/'
=> 'PhabricatorDashboardPanelArchiveController',
'tabs/(?P<id>\d+)/(?P<op>add|move|remove|rename)/'
=> 'PhabricatorDashboardPanelTabsController',
),
),
'/portal/' => array(

View file

@ -0,0 +1,295 @@
<?php
final class PhabricatorDashboardPanelTabsController
extends PhabricatorDashboardController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$panel = id(new PhabricatorDashboardPanelQuery())
->setViewer($viewer)
->withIDs(array($request->getURIData('id')))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$panel) {
return new Aphront404Response();
}
$tabs_type = id(new PhabricatorDashboardTabsPanelType())
->getPanelTypeKey();
// This controller may only be used to edit tab panels.
$panel_type = $panel->getPanelType();
if ($panel_type !== $tabs_type) {
return new Aphront404Response();
}
$op = $request->getURIData('op');
$after = $request->getStr('after');
if (!strlen($after)) {
$after = null;
}
$target = $request->getStr('target');
if (!strlen($target)) {
$target = null;
}
$impl = $panel->getImplementation();
$config = $impl->getPanelConfiguration($panel);
$cancel_uri = $panel->getURI();
if ($after !== null) {
$found = false;
foreach ($config as $key => $spec) {
if ((string)$key === $after) {
$found = true;
break;
}
}
if (!$found) {
return $this->newDialog()
->setTitle(pht('Adjacent Tab Not Found'))
->appendParagraph(
pht(
'Adjacent tab ("%s") was not found on this panel. It may have '.
'been removed.',
$after))
->addCancelButton($cancel_uri);
}
}
if ($target !== null) {
$found = false;
foreach ($config as $key => $spec) {
if ((string)$key === $target) {
$found = true;
break;
}
}
if (!$found) {
return $this->newDialog()
->setTitle(pht('Target Tab Not Found'))
->appendParagraph(
pht(
'Target tab ("%s") was not found on this panel. It may have '.
'been removed.',
$target))
->addCancelButton($cancel_uri);
}
}
switch ($op) {
case 'add':
return $this->handleAddOperation($panel, $after, $cancel_uri);
case 'remove':
return $this->handleRemoveOperation($panel, $target, $cancel_uri);
case 'move':
break;
case 'rename':
return $this->handleRenameOperation($panel, $target, $cancel_uri);
}
}
private function handleAddOperation(
PhabricatorDashboardPanel $panel,
$after,
$cancel_uri) {
$request = $this->getRequest();
$viewer = $this->getViewer();
$panel_phid = null;
$errors = array();
if ($request->isFormPost()) {
$panel_phid = $request->getArr('panelPHID');
$panel_phid = head($panel_phid);
$add_panel = id(new PhabricatorDashboardPanelQuery())
->setViewer($viewer)
->withPHIDs(array($panel_phid))
->executeOne();
if (!$add_panel) {
$errors[] = pht('You must select a valid panel.');
}
if (!$errors) {
$add_panel_config = array(
'name' => null,
'panelID' => $add_panel->getID(),
);
$add_panel_key = Filesystem::readRandomCharacters(12);
$impl = $panel->getImplementation();
$old_config = $impl->getPanelConfiguration($panel);
$new_config = array();
if ($after === null) {
$new_config = $old_config;
$new_config[] = $add_panel_config;
} else {
foreach ($old_config as $key => $value) {
$new_config[$key] = $value;
if ((string)$key === $after) {
$new_config[$add_panel_key] = $add_panel_config;
}
}
}
$xactions = array();
$xactions[] = $panel->getApplicationTransactionTemplate()
->setTransactionType(
PhabricatorDashboardTabsPanelTabsTransaction::TRANSACTIONTYPE)
->setNewValue($new_config);
$editor = id(new PhabricatorDashboardPanelTransactionEditor())
->setContentSourceFromRequest($request)
->setActor($viewer)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true);
$editor->applyTransactions($panel, $xactions);
return id(new AphrontRedirectResponse())->setURI($cancel_uri);
}
}
if ($panel_phid) {
$v_panel = array($panel_phid);
} else {
$v_panel = array();
}
$form = id(new AphrontFormView())
->setViewer($viewer)
->appendControl(
id(new AphrontFormTokenizerControl())
->setDatasource(new PhabricatorDashboardPanelDatasource())
->setLimit(1)
->setName('panelPHID')
->setLabel(pht('Panel'))
->setValue($v_panel));
return $this->newDialog()
->setTitle(pht('Choose Dashboard Panel'))
->setErrors($errors)
->setWidth(AphrontDialogView::WIDTH_FORM)
->addHiddenInput('after', $after)
->appendForm($form)
->addCancelButton($cancel_uri)
->addSubmitButton(pht('Add Panel'));
}
private function handleRemoveOperation(
PhabricatorDashboardPanel $panel,
$target,
$cancel_uri) {
$request = $this->getRequest();
$viewer = $this->getViewer();
$panel_phid = null;
$errors = array();
if ($request->isFormPost()) {
$impl = $panel->getImplementation();
$old_config = $impl->getPanelConfiguration($panel);
$new_config = $this->removePanel($old_config, $target);
$this->writePanelConfig($panel, $new_config);
return id(new AphrontRedirectResponse())->setURI($cancel_uri);
}
return $this->newDialog()
->setTitle(pht('Remove tab?'))
->addHiddenInput('target', $target)
->appendParagraph(pht('Really remove this tab?'))
->addCancelButton($cancel_uri)
->addSubmitButton(pht('Remove Tab'));
}
private function handleRenameOperation(
PhabricatorDashboardPanel $panel,
$target,
$cancel_uri) {
$request = $this->getRequest();
$viewer = $this->getViewer();
$impl = $panel->getImplementation();
$old_config = $impl->getPanelConfiguration($panel);
$spec = $old_config[$target];
$name = idx($spec, 'name');
if ($request->isFormPost()) {
$name = $request->getStr('name');
$new_config = $this->renamePanel($old_config, $target, $name);
$this->writePanelConfig($panel, $new_config);
return id(new AphrontRedirectResponse())->setURI($cancel_uri);
}
$form = id(new AphrontFormView())
->setViewer($viewer)
->appendControl(
id(new AphrontFormTextControl())
->setValue($name)
->setName('name')
->setLabel(pht('Tab Name')));
return $this->newDialog()
->setTitle(pht('Rename Panel'))
->addHiddenInput('target', $target)
->appendForm($form)
->addCancelButton($cancel_uri)
->addSubmitButton(pht('Rename Tab'));
}
private function writePanelConfig(
PhabricatorDashboardPanel $panel,
array $config) {
$request = $this->getRequest();
$viewer = $this->getViewer();
$xactions = array();
$xactions[] = $panel->getApplicationTransactionTemplate()
->setTransactionType(
PhabricatorDashboardTabsPanelTabsTransaction::TRANSACTIONTYPE)
->setNewValue($config);
$editor = id(new PhabricatorDashboardPanelTransactionEditor())
->setContentSourceFromRequest($request)
->setActor($viewer)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true);
return $editor->applyTransactions($panel, $xactions);
}
private function removePanel(array $config, $target) {
$result = array();
foreach ($config as $key => $panel_spec) {
if ((string)$key === $target) {
continue;
}
$result[$key] = $panel_spec;
}
return $result;
}
private function renamePanel(array $config, $target, $name) {
$config[$target]['name'] = $name;
return $config;
}
}

View file

@ -43,6 +43,10 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject {
return $this->panelHandle;
}
public function isEditMode() {
return ($this->getHeaderMode() === self::HEADER_MODE_EDIT);
}
/**
* Allow the engine to render the panel via Ajax.
*/

View file

@ -20,7 +20,6 @@ final class PhabricatorDashboardTabsPanelType
}
protected function newEditEngineFields(PhabricatorDashboardPanel $panel) {
// TODO: Restore this using EditEngine instead of CustomField.
return array();
}
@ -29,37 +28,37 @@ final class PhabricatorDashboardTabsPanelType
return false;
}
public function getPanelConfiguration(PhabricatorDashboardPanel $panel) {
$config = $panel->getProperty('config');
if (!is_array($config)) {
// NOTE: The older version of this panel stored raw JSON.
try {
$config = phutil_json_decode($config);
} catch (PhutilJSONParserException $ex) {
$config = array();
}
}
return $config;
}
public function renderPanelContent(
PhabricatorUser $viewer,
PhabricatorDashboardPanel $panel,
PhabricatorDashboardPanelRenderingEngine $engine) {
$config = $panel->getProperty('config');
if (!is_array($config)) {
// NOTE: The older version of this panel stored raw JSON.
$config = phutil_json_decode($config);
}
$is_edit = $engine->isEditMode();
$config = $this->getPanelConfiguration($panel);
$list = id(new PHUIListView())
->setType(PHUIListView::NAVBAR_LIST);
$selected = 0;
$node_ids = array();
foreach ($config as $idx => $tab_spec) {
$node_ids[$idx] = celerity_generate_unique_node_id();
}
foreach ($config as $idx => $tab_spec) {
$list->addMenuItem(
id(new PHUIListItemView())
->setHref('#')
->setSelected($idx == $selected)
->addSigil('dashboard-tab-panel-tab')
->setMetadata(array('idx' => $idx))
->setName(idx($tab_spec, 'name', pht('Nameless Tab'))));
}
$ids = ipull($config, 'panelID');
if ($ids) {
$panels = id(new PhabricatorDashboardPanelQuery())
@ -70,6 +69,135 @@ final class PhabricatorDashboardTabsPanelType
$panels = array();
}
$id = $panel->getID();
$add_uri = urisprintf('/dashboard/panel/tabs/%d/add/', $id);
$add_uri = new PhutilURI($add_uri);
$remove_uri = urisprintf('/dashboard/panel/tabs/%d/remove/', $id);
$remove_uri = new PhutilURI($remove_uri);
$rename_uri = urisprintf('/dashboard/panel/tabs/%d/rename/', $id);
$rename_uri = new PhutilURI($rename_uri);
$selected = 0;
$last_idx = null;
foreach ($config as $idx => $tab_spec) {
$panel_id = idx($tab_spec, 'panelID');
$subpanel = idx($panels, $panel_id);
$name = idx($tab_spec, 'name');
if (!strlen($name)) {
if ($subpanel) {
$name = $subpanel->getName();
}
}
if (!strlen($name)) {
$name = pht('Unnamed Tab');
}
$tab_view = id(new PHUIListItemView())
->setHref('#')
->setSelected($idx == $selected)
->addSigil('dashboard-tab-panel-tab')
->setMetadata(array('idx' => $idx))
->setName($name);
if ($is_edit) {
$dropdown_menu = id(new PhabricatorActionListView())
->setViewer($viewer);
$remove_tab_uri = id(clone $remove_uri)
->replaceQueryParam('target', $idx);
$rename_tab_uri = id(clone $rename_uri)
->replaceQueryParam('target', $idx);
if ($subpanel) {
$details_uri = $subpanel->getURI();
} else {
$details_uri = null;
}
$edit_uri = urisprintf(
'/dashboard/panel/edit/%d/',
$panel_id);
if ($subpanel) {
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$subpanel,
PhabricatorPolicyCapability::CAN_EDIT);
} else {
$can_edit = false;
}
$dropdown_menu->addAction(
id(new PhabricatorActionView())
->setName(pht('Rename Tab'))
->setIcon('fa-pencil')
->setHref($rename_tab_uri)
->setWorkflow(true));
$dropdown_menu->addAction(
id(new PhabricatorActionView())
->setName(pht('Remove Tab'))
->setIcon('fa-times')
->setHref($remove_tab_uri)
->setWorkflow(true));
$dropdown_menu->addAction(
id(new PhabricatorActionView())
->setType(PhabricatorActionView::TYPE_DIVIDER));
$dropdown_menu->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Panel'))
->setIcon('fa-pencil')
->setHref($edit_uri)
->setWorkflow(true)
->setDisabled(!$can_edit));
$dropdown_menu->addAction(
id(new PhabricatorActionView())
->setName(pht('View Panel Details'))
->setIcon('fa-window-maximize')
->setHref($details_uri)
->setDisabled(!$subpanel));
$tab_view->setDropdownMenu($dropdown_menu);
}
$list->addMenuItem($tab_view);
$last_idx = $idx;
}
if ($is_edit) {
$actions = id(new PhabricatorActionListView())
->setViewer($viewer);
$add_last_uri = clone $add_uri;
if ($last_idx) {
$add_last_uri->replaceQueryParam('after', $last_idx);
}
$actions->addAction(
id(new PhabricatorActionView())
->setName(pht('Add Existing Panel'))
->setIcon('fa-window-maximize')
->setHref($add_last_uri)
->setWorkflow(true));
$list->addMenuItem(
id(new PHUIListItemView())
->setHref('#')
->setSelected(false)
->setName(pht('Add Tab...'))
->setDropdownMenu($actions));
}
$parent_phids = $engine->getParentPanelPHIDs();
$parent_phids[] = $panel->getPHID();
@ -83,15 +211,15 @@ final class PhabricatorDashboardTabsPanelType
$no_headers = PhabricatorDashboardPanelRenderingEngine::HEADER_MODE_NONE;
foreach ($config as $idx => $tab_spec) {
$panel_id = idx($tab_spec, 'panelID');
$panel = idx($panels, $panel_id);
$subpanel = idx($panels, $panel_id);
if ($panel) {
if ($subpanel) {
$panel_content = id(new PhabricatorDashboardPanelRenderingEngine())
->setViewer($viewer)
->setEnableAsyncRendering(true)
->setParentPanelPHIDs($parent_phids)
->setPanel($panel)
->setPanelPHID($panel->getPHID())
->setPanel($subpanel)
->setPanelPHID($subpanel->getPHID())
->setHeaderMode($no_headers)
->setMovable(false)
->renderPanel();
@ -108,6 +236,28 @@ final class PhabricatorDashboardTabsPanelType
$panel_content);
}
if (!$content) {
if ($is_edit) {
$message = pht(
'This tab panel does not have any tabs yet. Use "Add Tab" to '.
'create or place a tab.');
} else {
$message = pht(
'This tab panel does not have any tabs yet.');
}
$content = id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_NODATA)
->setErrors(
array(
$message,
));
$content = id(new PHUIBoxView())
->addClass('mlt mlb')
->appendChild($content);
}
Javelin::initBehavior('dashboard-tab-panel');
return javelin_tag(

View file

@ -48,13 +48,13 @@ final class PhabricatorDashboardPanelDatasource
$type_text = nonempty($panel->getPanelType(), pht('Unknown Type'));
$icon = 'fa-question';
}
$id = $panel->getID();
$phid = $panel->getPHID();
$monogram = $panel->getMonogram();
$properties = $panel->getProperties();
$result = id(new PhabricatorTypeaheadResult())
->setName($monogram.' '.$panel->getName())
->setPHID($id)
->setPHID($phid)
->setIcon($icon)
->addAttribute($type_text);
@ -66,7 +66,7 @@ final class PhabricatorDashboardPanelDatasource
$result->setClosed(pht('Archived'));
}
$results[$id] = $result;
$results[$phid] = $result;
}
return $results;

View file

@ -0,0 +1,12 @@
<?php
final class PhabricatorDashboardTabsPanelTabsTransaction
extends PhabricatorDashboardPanelPropertyTransaction {
const TRANSACTIONTYPE = 'tabs.tabs';
protected function getPropertyKey() {
return 'config';
}
}

View file

@ -35,6 +35,7 @@ final class PHUIListItemView extends AphrontTagView {
private $actionIconHref;
private $count;
private $rel;
private $hasDropdown;
public function setOpenInNewWindow($open_in_new_window) {
$this->openInNewWindow = $open_in_new_window;
@ -68,6 +69,7 @@ final class PHUIListItemView extends AphrontTagView {
$this->addSigil('phui-dropdown-menu');
$this->setMetadata($actions->getDropdownMenuMetadata());
$this->hasDropdown = true;
return $this;
}
@ -235,6 +237,10 @@ final class PHUIListItemView extends AphrontTagView {
$classes[] = 'phui-list-item-has-action-icon';
}
if ($this->hasDropdown) {
$classes[] = 'dropdown';
}
return array(
'class' => implode(' ', $classes),
);
@ -363,6 +369,12 @@ final class PHUIListItemView extends AphrontTagView {
$this->count);
}
if ($this->hasDropdown) {
$caret = phutil_tag('span', array('class' => 'caret'), '');
} else {
$caret = null;
}
$icons = $this->getIcons();
$list_item = javelin_tag(
@ -381,6 +393,7 @@ final class PHUIListItemView extends AphrontTagView {
$icons,
$this->renderChildren(),
$name,
$caret,
$count,
));

View file

@ -213,3 +213,14 @@
.phabricator-action-view-item .phui-icon-view {
color: {$sky};
}
.phui-list-item-view.dropdown .phui-list-item-href {
padding-right: 28px;
}
.phui-list-item-view .caret {
position: absolute;
top: 6px;
right: 12px;
border-top: 7px solid {$greytext};
}