1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-18 19:40:55 +01:00

Create dashboard panels inline on dashboards

Summary:
Fixes T5308.

  - Allows you to create a panel directly on a dashboard.
  - Also, include existing panels with a select instead of a text field. This won't scale as well but should be fine for now, and is way easier to use.

Test Plan: See comment.

Reviewers: chad

Reviewed By: chad

Subscribers: epriestley

Maniphest Tasks: T5308

Differential Revision: https://secure.phabricator.com/D9501
This commit is contained in:
epriestley 2014-06-12 13:22:27 -07:00
parent b8bc0aa2b0
commit 6f4ebcb8d9
9 changed files with 243 additions and 173 deletions

View file

@ -1488,7 +1488,6 @@ phutil_register_library_map(array(
'PhabricatorDashboardPHIDTypePanel' => 'applications/dashboard/phid/PhabricatorDashboardPHIDTypePanel.php', 'PhabricatorDashboardPHIDTypePanel' => 'applications/dashboard/phid/PhabricatorDashboardPHIDTypePanel.php',
'PhabricatorDashboardPanel' => 'applications/dashboard/storage/PhabricatorDashboardPanel.php', 'PhabricatorDashboardPanel' => 'applications/dashboard/storage/PhabricatorDashboardPanel.php',
'PhabricatorDashboardPanelCoreCustomField' => 'applications/dashboard/customfield/PhabricatorDashboardPanelCoreCustomField.php', 'PhabricatorDashboardPanelCoreCustomField' => 'applications/dashboard/customfield/PhabricatorDashboardPanelCoreCustomField.php',
'PhabricatorDashboardPanelCreateController' => 'applications/dashboard/controller/PhabricatorDashboardPanelCreateController.php',
'PhabricatorDashboardPanelCustomField' => 'applications/dashboard/customfield/PhabricatorDashboardPanelCustomField.php', 'PhabricatorDashboardPanelCustomField' => 'applications/dashboard/customfield/PhabricatorDashboardPanelCustomField.php',
'PhabricatorDashboardPanelEditController' => 'applications/dashboard/controller/PhabricatorDashboardPanelEditController.php', 'PhabricatorDashboardPanelEditController' => 'applications/dashboard/controller/PhabricatorDashboardPanelEditController.php',
'PhabricatorDashboardPanelListController' => 'applications/dashboard/controller/PhabricatorDashboardPanelListController.php', 'PhabricatorDashboardPanelListController' => 'applications/dashboard/controller/PhabricatorDashboardPanelListController.php',
@ -4313,7 +4312,6 @@ phutil_register_library_map(array(
0 => 'PhabricatorDashboardPanelCustomField', 0 => 'PhabricatorDashboardPanelCustomField',
1 => 'PhabricatorStandardCustomFieldInterface', 1 => 'PhabricatorStandardCustomFieldInterface',
), ),
'PhabricatorDashboardPanelCreateController' => 'PhabricatorDashboardController',
'PhabricatorDashboardPanelCustomField' => 'PhabricatorCustomField', 'PhabricatorDashboardPanelCustomField' => 'PhabricatorCustomField',
'PhabricatorDashboardPanelEditController' => 'PhabricatorDashboardController', 'PhabricatorDashboardPanelEditController' => 'PhabricatorDashboardController',
'PhabricatorDashboardPanelListController' => 'PhabricatorDashboardController', 'PhabricatorDashboardPanelListController' => 'PhabricatorDashboardController',

View file

@ -34,7 +34,7 @@ final class PhabricatorApplicationDashboard extends PhabricatorApplication {
'panel/' => array( 'panel/' => array(
'(?:query/(?P<queryKey>[^/]+)/)?' '(?:query/(?P<queryKey>[^/]+)/)?'
=> 'PhabricatorDashboardPanelListController', => 'PhabricatorDashboardPanelListController',
'create/' => 'PhabricatorDashboardPanelCreateController', 'create/' => 'PhabricatorDashboardPanelEditController',
'edit/(?:(?P<id>\d+)/)?' => 'PhabricatorDashboardPanelEditController', 'edit/(?:(?P<id>\d+)/)?' => 'PhabricatorDashboardPanelEditController',
'render/(?P<id>\d+)/' => 'PhabricatorDashboardPanelRenderController', 'render/(?P<id>\d+)/' => 'PhabricatorDashboardPanelRenderController',
), ),

View file

@ -26,82 +26,74 @@ final class PhabricatorDashboardAddPanelController
return new Aphront404Response(); return new Aphront404Response();
} }
$redirect_uri = $this->getApplicationURI( $redirect_uri = $this->getApplicationURI('manage/'.$dashboard->getID().'/');
'manage/'.$dashboard->getID().'/');
$layout_config = $dashboard->getLayoutConfigObject();
$v_panel = $request->getStr('panel'); $v_panel = $request->getStr('panel');
$e_panel = true; $e_panel = true;
$errors = array(); $errors = array();
if ($request->isFormPost()) { if ($request->isFormPost()) {
if (strlen($v_panel)) { if (strlen($v_panel)) {
$panel = id(new PhabricatorObjectQuery()) $panel = id(new PhabricatorDashboardPanelQuery())
->setViewer($viewer) ->setViewer($viewer)
->withNames(array($v_panel)) ->withIDs(array($v_panel))
->withTypes(array(PhabricatorDashboardPHIDTypePanel::TYPECONST))
->executeOne(); ->executeOne();
if (!$panel) { if (!$panel) {
$errors[] = pht('No such panel!'); $errors[] = pht('No such panel!');
$e_panel = pht('Invalid'); $e_panel = pht('Invalid');
} }
} else { } else {
$errors[] = pht('Name a panel to add.'); $errors[] = pht('Select a panel to add.');
$e_panel = pht('Required'); $e_panel = pht('Required');
} }
if (!$errors) { if (!$errors) {
$xactions = array(); PhabricatorDashboardTransactionEditor::addPanelToDashboard(
$xactions[] = id(new PhabricatorDashboardTransaction()) $viewer,
->setTransactionType(PhabricatorTransactions::TYPE_EDGE) PhabricatorContentSource::newFromRequest($request),
->setMetadataValue( $panel,
'edge:type', $dashboard,
PhabricatorEdgeConfig::TYPE_DASHBOARD_HAS_PANEL) $request->getInt('column', 0));
->setNewValue(
array(
'+' => array(
$panel->getPHID() => $panel->getPHID(),
),
));
$layout_config->setPanelLocation(
$request->getInt('column', 0),
$panel->getPHID());
$dashboard->setLayoutConfigFromObject($layout_config);
$editor = id(new PhabricatorDashboardTransactionEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnMissingFields(true)
->setContinueOnNoEffect(true)
->applyTransactions($dashboard, $xactions);
return id(new AphrontRedirectResponse())->setURI($redirect_uri); return id(new AphrontRedirectResponse())->setURI($redirect_uri);
} }
} }
$panels = id(new PhabricatorDashboardPanelQuery())
->setViewer($viewer)
->execute();
if (!$panels) {
return $this->newDialog()
->setTitle(pht('No Panels Exist Yet'))
->appendParagraph(
pht(
'You have not created any dashboard panels yet, so you can not '.
'add an existing panel.'))
->appendParagraph(
pht('Instead, add a new panel.'))
->addCancelButton($redirect_uri);
}
$panel_options = array();
foreach ($panels as $panel) {
$panel_options[$panel->getID()] = pht(
'%s %s',
$panel->getMonogram(),
$panel->getName());
}
$form = id(new AphrontFormView()) $form = id(new AphrontFormView())
->setUser($viewer) ->setUser($viewer)
->addHiddenInput('src', $request->getStr('src', 'edit')) ->addHiddenInput('column', $request->getInt('column'))
->appendRemarkupInstructions( ->appendRemarkupInstructions(
pht('Enter a panel monogram like `W123`.')) pht('Choose a panel to add to this dashboard:'))
->appendChild( ->appendChild(
id(new AphrontFormTextControl()) id(new AphrontFormSelectControl())
->setName('panel') ->setName('panel')
->setLabel(pht('Panel')) ->setLabel(pht('Panel'))
->setValue($v_panel) ->setValue($v_panel)
->setError($e_panel)); ->setError($e_panel)
->setOptions($panel_options));
if ($layout_config->isMultiColumnLayout()) {
$form
->appendRemarkupInstructions(
pht('Choose which column the panel should reside in.'))
->appendChild(
id(new AphrontFormSelectControl())
->setName('column')
->setLabel(pht('Column'))
->setOptions($layout_config->getColumnSelectOptions())
->setValue($request->getInt('column')));
}
return $this->newDialog() return $this->newDialog()
->setTitle(pht('Add Panel')) ->setTitle(pht('Add Panel'))

View file

@ -1,75 +0,0 @@
<?php
final class PhabricatorDashboardPanelCreateController
extends PhabricatorDashboardController {
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$types = PhabricatorDashboardPanelType::getAllPanelTypes();
$v_type = null;
$errors = array();
if ($request->isFormPost()) {
$v_type = $request->getStr('type');
if (!isset($types[$v_type])) {
$errors[] = pht('You must select a type of panel to create.');
}
if (!$errors) {
return id(new AphrontRedirectResponse())->setURI(
$this->getApplicationURI('panel/edit/?type='.$v_type));
}
}
$cancel_uri = $this->getApplicationURI('panel/');
if (!$v_type) {
$v_type = key($types);
}
$panel_types = id(new AphrontFormRadioButtonControl())
->setName('type')
->setValue($v_type);
foreach ($types as $key => $type) {
$panel_types->addButton(
$key,
$type->getPanelTypeName(),
$type->getPanelTypeDescription());
}
$form = id(new AphrontFormView())
->setUser($viewer)
->appendChild($panel_types)
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Continue'))
->addCancelButton($cancel_uri));
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(
pht('Panels'),
$this->getApplicationURI('panel/'));
$crumbs->addTextCrumb(pht('New Panel'));
$title = pht('Create Dashboard Panel');
$box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->setFormErrors($errors)
->setForm($form);
return $this->buildApplicationPage(
array(
$crumbs,
$box,
),
array(
'title' => $title,
'device' => true,
));
}
}

View file

@ -13,6 +13,28 @@ final class PhabricatorDashboardPanelEditController
$request = $this->getRequest(); $request = $this->getRequest();
$viewer = $request->getUser(); $viewer = $request->getUser();
// If the user is trying to create a panel directly on a dashboard, make
// sure they have permission to see and edit the dashboard.
$dashboard_id = $request->getInt('dashboardID');
$dashboard = null;
if ($dashboard_id) {
$dashboard = id(new PhabricatorDashboardQuery())
->setViewer($viewer)
->withIDs(array($dashboard_id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$dashboard) {
return new Aphront404Response();
}
$manage_uri = $this->getApplicationURI('manage/'.$dashboard_id.'/');
}
if ($this->id) { if ($this->id) {
$is_create = false; $is_create = false;
@ -33,11 +55,10 @@ final class PhabricatorDashboardPanelEditController
$is_create = true; $is_create = true;
$panel = PhabricatorDashboardPanel::initializeNewPanel($viewer); $panel = PhabricatorDashboardPanel::initializeNewPanel($viewer);
$types = PhabricatorDashboardPanelType::getAllPanelTypes(); $types = PhabricatorDashboardPanelType::getAllPanelTypes();
$type = $request->getStr('type'); $type = $request->getStr('type');
if (empty($types[$type])) { if (empty($types[$type])) {
return new Aphront404Response(); return $this->processPanelTypeRequest($request);
} }
$panel->setPanelType($type); $panel->setPanelType($type);
@ -47,12 +68,20 @@ final class PhabricatorDashboardPanelEditController
$title = pht('New Panel'); $title = pht('New Panel');
$header = pht('Create New Panel'); $header = pht('Create New Panel');
$button = pht('Create Panel'); $button = pht('Create Panel');
$cancel_uri = $this->getApplicationURI('panel/'); if ($dashboard) {
$cancel_uri = $manage_uri;
} else {
$cancel_uri = $this->getApplicationURI('panel/');
}
} else { } else {
$title = pht('Edit %s', $panel->getMonogram()); $title = pht('Edit %s', $panel->getMonogram());
$header = pht('Edit %s %s', $panel->getMonogram(), $panel->getName()); $header = pht('Edit %s %s', $panel->getMonogram(), $panel->getName());
$button = pht('Save Panel'); $button = pht('Save Panel');
$cancel_uri = $this->getPanelRedirectURI($panel); if ($dashboard) {
$cancel_uri = $manage_uri;
} else {
$cancel_uri = '/'.$panel->getMonogram();
}
} }
$v_name = $panel->getName(); $v_name = $panel->getName();
@ -67,7 +96,10 @@ final class PhabricatorDashboardPanelEditController
$validation_exception = null; $validation_exception = null;
if ($request->isFormPost()) { // NOTE: We require 'edit' to distinguish between the "Choose a Type"
// and "Create a Panel" dialogs.
if ($request->isFormPost() && $request->getBool('edit')) {
$v_name = $request->getStr('name'); $v_name = $request->getStr('name');
$v_view_policy = $request->getStr('viewPolicy'); $v_view_policy = $request->getStr('viewPolicy');
$v_edit_policy = $request->getStr('editPolicy'); $v_edit_policy = $request->getStr('editPolicy');
@ -102,8 +134,23 @@ final class PhabricatorDashboardPanelEditController
->setContentSourceFromRequest($request) ->setContentSourceFromRequest($request)
->applyTransactions($panel, $xactions); ->applyTransactions($panel, $xactions);
return id(new AphrontRedirectResponse()) // If we're creating a panel directly on a dashboard, add it now.
->setURI($this->getPanelRedirectURI($panel)); if ($dashboard) {
PhabricatorDashboardTransactionEditor::addPanelToDashboard(
$viewer,
PhabricatorContentSource::newFromRequest($request),
$panel,
$dashboard,
$request->getInt('column', 0));
}
if ($dashboard) {
$done_uri = $manage_uri;
} else {
$done_uri = '/'.$panel->getMonogram();
}
return id(new AphrontRedirectResponse())->setURI($done_uri);
} catch (PhabricatorApplicationTransactionValidationException $ex) { } catch (PhabricatorApplicationTransactionValidationException $ex) {
$validation_exception = $ex; $validation_exception = $ex;
@ -121,7 +168,9 @@ final class PhabricatorDashboardPanelEditController
$form = id(new AphrontFormView()) $form = id(new AphrontFormView())
->setUser($viewer) ->setUser($viewer)
->addHiddenInput('edit', true)
->addHiddenInput('dashboardID', $request->getInt('dashboardID')) ->addHiddenInput('dashboardID', $request->getInt('dashboardID'))
->addHiddenInput('column', $request->getInt('column'))
->appendChild( ->appendChild(
id(new AphrontFormTextControl()) id(new AphrontFormTextControl())
->setLabel(pht('Name')) ->setLabel(pht('Name'))
@ -189,16 +238,83 @@ final class PhabricatorDashboardPanelEditController
)); ));
} }
private function getPanelRedirectURI(PhabricatorDashboardPanel $panel) { private function processPanelTypeRequest(AphrontRequest $request) {
$request = $this->getRequest(); $viewer = $request->getUser();
$dashboard_id = $request->getInt('dashboardID');
if ($dashboard_id) { $types = PhabricatorDashboardPanelType::getAllPanelTypes();
$uri = $this->getApplicationURI('manage/'.$dashboard_id.'/');
} else { $v_type = null;
$uri = '/'.$panel->getMonogram(); $errors = array();
if ($request->isFormPost()) {
$v_type = $request->getStr('type');
if (!isset($types[$v_type])) {
$errors[] = pht('You must select a type of panel to create.');
}
} }
return $uri; $cancel_uri = $this->getApplicationURI('panel/');
if (!$v_type) {
$v_type = key($types);
}
$panel_types = id(new AphrontFormRadioButtonControl())
->setName('type')
->setValue($v_type);
foreach ($types as $key => $type) {
$panel_types->addButton(
$key,
$type->getPanelTypeName(),
$type->getPanelTypeDescription());
}
$form = id(new AphrontFormView())
->setUser($viewer)
->addHiddenInput('dashboardID', $request->getInt('dashboardID'))
->addHiddenInput('column', $request->getInt('column'))
->appendRemarkupInstructions(
pht(
'Choose the type of dashboard panel to create:'))
->appendChild($panel_types);
if ($request->isAjax()) {
return $this->newDialog()
->setTitle(pht('Add New Panel'))
->setWidth(AphrontDialogView::WIDTH_FORM)
->setErrors($errors)
->appendChild($form->buildLayoutView())
->addCancelbutton($cancel_uri)
->addSubmitButton(pht('Continue'));
} else {
$form->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Continue'))
->addCancelButton($cancel_uri));
}
$title = pht('Create Dashboard Panel');
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(
pht('Panels'),
$this->getApplicationURI('panel/'));
$crumbs->addTextCrumb(pht('New Panel'));
$box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->setFormErrors($errors)
->setForm($form);
return $this->buildApplicationPage(
array(
$crumbs,
$box,
),
array(
'title' => $title,
'device' => true,
));
} }
} }

View file

@ -136,9 +136,14 @@ final class PhabricatorDashboardPanelViewController
PhabricatorEdgeConfig::TYPE_PANEL_HAS_DASHBOARD); PhabricatorEdgeConfig::TYPE_PANEL_HAS_DASHBOARD);
$this->loadHandles($dashboard_phids); $this->loadHandles($dashboard_phids);
$does_not_appear = pht(
'This panel does not appear on any dashboards.');
$properties->addProperty( $properties->addProperty(
pht('Appears On'), pht('Appears On'),
$this->renderHandlesForPHIDs($dashboard_phids)); $dashboard_phids
? $this->renderHandlesForPHIDs($dashboard_phids)
: phutil_tag('em', array(), $does_not_appear));
return $properties; return $properties;
} }

View file

@ -3,6 +3,38 @@
final class PhabricatorDashboardTransactionEditor final class PhabricatorDashboardTransactionEditor
extends PhabricatorApplicationTransactionEditor { extends PhabricatorApplicationTransactionEditor {
public static function addPanelToDashboard(
PhabricatorUser $actor,
PhabricatorContentSource $content_source,
PhabricatorDashboardPanel $panel,
PhabricatorDashboard $dashboard,
$column) {
$xactions = array();
$xactions[] = id(new PhabricatorDashboardTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
->setMetadataValue(
'edge:type',
PhabricatorEdgeConfig::TYPE_DASHBOARD_HAS_PANEL)
->setNewValue(
array(
'+' => array(
$panel->getPHID() => $panel->getPHID(),
),
));
$layout_config = $dashboard->getLayoutConfigObject();
$layout_config->setPanelLocation($column, $panel->getPHID());
$dashboard->setLayoutConfigFromObject($layout_config);
$editor = id(new PhabricatorDashboardTransactionEditor())
->setActor($actor)
->setContentSource($content_source)
->setContinueOnMissingFields(true)
->setContinueOnNoEffect(true)
->applyTransactions($dashboard, $xactions);
}
public function getTransactionTypes() { public function getTransactionTypes() {
$types = parent::getTransactionTypes(); $types = parent::getTransactionTypes();

View file

@ -86,45 +86,53 @@ final class PhabricatorDashboardRenderingEngine extends Phobject {
} }
private function renderAddPanelPlaceHolder($column) { private function renderAddPanelPlaceHolder($column) {
$uri = $this->getAddPanelURI($column);
$dashboard = $this->dashboard; $dashboard = $this->dashboard;
$panels = $dashboard->getPanels(); $panels = $dashboard->getPanels();
$layout_config = $dashboard->getLayoutConfigObject();
if ($layout_config->isMultiColumnLayout() && count($panels)) {
$text = pht('Drag a panel here or click to add a panel.');
} else {
$text = pht('Click to add a panel.');
}
return javelin_tag( return javelin_tag(
'a', 'span',
array( array(
'sigil' => 'workflow', 'sigil' => 'workflow',
'class' => 'drag-ghost dashboard-panel-placeholder', 'class' => 'drag-ghost dashboard-panel-placeholder',
'href' => (string) $uri), ),
$text); pht('This column does not have any panels yet.'));
} }
private function renderAddPanelUI($column) { private function renderAddPanelUI($column) {
$uri = $this->getAddPanelURI($column); $dashboard_id = $this->dashboard->getID();
return id(new PHUIButtonView()) $create_uri = id(new PhutilURI('/dashboard/panel/create/'))
->setQueryParam('dashboardID', $dashboard_id)
->setQueryParam('column', $column);
$add_uri = id(new PhutilURI('/dashboard/addpanel/'.$dashboard_id.'/'))
->setQueryParam('column', $column);
$create_button = id(new PHUIButtonView())
->setTag('a') ->setTag('a')
->setHref((string) $uri) ->setHref($create_uri)
->setWorkflow(true) ->setWorkflow(true)
->setColor(PHUIButtonView::GREY) ->setColor(PHUIButtonView::GREY)
->setIcon(id(new PHUIIconView()) ->setText(pht('Create Panel'))
->setIconFont('fa-plus')) ->addClass(PHUI::MARGIN_MEDIUM);
->setText(pht('Add Panel'))
->addClass(PHUI::MARGIN_LARGE);
}
private function getAddPanelURI($column) { $add_button = id(new PHUIButtonView())
$dashboard = $this->dashboard; ->setTag('a')
$uri = id(new PhutilURI('/dashboard/addpanel/'.$dashboard->getID().'/')) ->setHref($add_uri)
->setQueryParam('column', $column) ->setWorkflow(true)
->setQueryParam('src', 'arrange'); ->setColor(PHUIButtonView::GREY)
return $uri; ->setText(pht('Add Existing Panel'))
->addClass(PHUI::MARGIN_MEDIUM);
return phutil_tag(
'div',
array(
'style' => 'text-align: center;',
),
array(
$create_button,
$add_button,
));
} }
} }

View file

@ -50,18 +50,12 @@
.aphront-multi-column-fluid .aphront-multi-column-fluid
.aphront-multi-column-column.dashboard-column-empty .aphront-multi-column-column.dashboard-column-empty
.dashboard-panel-placeholder { .dashboard-panel-placeholder {
color: {$greytext};
display: block; display: block;
padding: 24px; padding: 24px;
margin: 16px 16px 0px 16px; margin: 16px 16px 0px 16px;
}
.aphront-multi-column-fluid
.aphront-multi-column-column.dashboard-column-empty
.dashboard-panel-placeholder:hover {
text-decoration: none; text-decoration: none;
border: 1px {$greyborder} dashed; border: 1px {$greyborder} dashed;
color: {$darkgreytext}; color: {$greytext};
} }
.aphront-multi-column-fluid .aphront-multi-column-fluid