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

Update "add panel" and "remove panel" Dashboard flows to the new panel storage format

Summary:
Depends on D20407. Ref T13272. This updates the "add panel" (which has two flavors: "add existing" and "create new") and "remove panel" flows to work with the new duplicate-friendly storage format.

  - We now modify panels by "panelKey", not by panel PHID, so one dashboard may have multiple copies of the same panel and we can still figure out what's going on.
  - We now work with "contextPHID", not "dashboardID", to make some flows with tab panels (or other nested panels in the future) easier.

The only major remaining flow is the Javascript "move panels around with drag-and-drop" flow.

Test Plan:
  - Added panels to a dashboard with "Create New Panel".
  - Added panels to a dashboard with "Add Existing Panel".
  - Removed panels from a dashboard.
  - Added and removed duplicate panels, got a correctly-functioning dashboard that didn't care about duplicates.

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13272

Differential Revision: https://secure.phabricator.com/D20408
This commit is contained in:
epriestley 2019-04-12 10:39:11 -07:00
parent a3c43c473b
commit 82c46f4b93
18 changed files with 574 additions and 413 deletions

View file

@ -10,7 +10,7 @@ return array(
'conpherence.pkg.css' => '3c8a0668',
'conpherence.pkg.js' => '020aebcf',
'core.pkg.css' => '294e365c',
'core.pkg.js' => '794952ae',
'core.pkg.js' => '69247edd',
'differential.pkg.css' => '8d8360fb',
'differential.pkg.js' => '67e02996',
'diffusion.pkg.css' => '42c75c37',
@ -371,7 +371,7 @@ return array(
'rsrc/js/application/conpherence/behavior-toggle-widget.js' => '8f959ad0',
'rsrc/js/application/countdown/timer.js' => '6a162524',
'rsrc/js/application/daemon/behavior-bulk-job-reload.js' => '3829a3cf',
'rsrc/js/application/dashboard/behavior-dashboard-async-panel.js' => '09ecf50c',
'rsrc/js/application/dashboard/behavior-dashboard-async-panel.js' => 'a871fe00',
'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '076bd092',
'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '1e413dc9',
'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => '0116d3e8',
@ -594,7 +594,7 @@ return array(
'javelin-behavior-conpherence-search' => '91befbcc',
'javelin-behavior-countdown-timer' => '6a162524',
'javelin-behavior-dark-console' => 'f39d968b',
'javelin-behavior-dashboard-async-panel' => '09ecf50c',
'javelin-behavior-dashboard-async-panel' => 'a871fe00',
'javelin-behavior-dashboard-move-panels' => '076bd092',
'javelin-behavior-dashboard-query-panel-select' => '1e413dc9',
'javelin-behavior-dashboard-tab-panel' => '0116d3e8',
@ -982,11 +982,6 @@ return array(
'herald-rule-editor',
'javelin-behavior',
),
'09ecf50c' => array(
'javelin-behavior',
'javelin-dom',
'javelin-workflow',
),
'0ad8d31f' => array(
'javelin-behavior',
'javelin-stratcom',
@ -1794,6 +1789,11 @@ return array(
'javelin-install',
'javelin-dom',
),
'a871fe00' => array(
'javelin-behavior',
'javelin-dom',
'javelin-workflow',
),
'a9942052' => array(
'javelin-behavior',
'javelin-dom',

View file

@ -2906,7 +2906,7 @@ phutil_register_library_map(array(
'PhabricatorDarkConsoleTabSetting' => 'applications/settings/setting/PhabricatorDarkConsoleTabSetting.php',
'PhabricatorDarkConsoleVisibleSetting' => 'applications/settings/setting/PhabricatorDarkConsoleVisibleSetting.php',
'PhabricatorDashboard' => 'applications/dashboard/storage/PhabricatorDashboard.php',
'PhabricatorDashboardAddPanelController' => 'applications/dashboard/controller/PhabricatorDashboardAddPanelController.php',
'PhabricatorDashboardAdjustController' => 'applications/dashboard/controller/dashboard/PhabricatorDashboardAdjustController.php',
'PhabricatorDashboardApplication' => 'applications/dashboard/application/PhabricatorDashboardApplication.php',
'PhabricatorDashboardApplicationInstallWorkflow' => 'applications/dashboard/install/PhabricatorDashboardApplicationInstallWorkflow.php',
'PhabricatorDashboardArchiveController' => 'applications/dashboard/controller/dashboard/PhabricatorDashboardArchiveController.php',
@ -2927,7 +2927,6 @@ phutil_register_library_map(array(
'PhabricatorDashboardInstall' => 'applications/dashboard/storage/PhabricatorDashboardInstall.php',
'PhabricatorDashboardInstallController' => 'applications/dashboard/controller/dashboard/PhabricatorDashboardInstallController.php',
'PhabricatorDashboardInstallWorkflow' => 'applications/dashboard/install/PhabricatorDashboardInstallWorkflow.php',
'PhabricatorDashboardLayoutConfig' => 'applications/dashboard/layoutconfig/PhabricatorDashboardLayoutConfig.php',
'PhabricatorDashboardLayoutMode' => 'applications/dashboard/layoutconfig/PhabricatorDashboardLayoutMode.php',
'PhabricatorDashboardLayoutTransaction' => 'applications/dashboard/xaction/dashboard/PhabricatorDashboardLayoutTransaction.php',
'PhabricatorDashboardListController' => 'applications/dashboard/controller/PhabricatorDashboardListController.php',
@ -2964,6 +2963,7 @@ phutil_register_library_map(array(
'PhabricatorDashboardPanelType' => 'applications/dashboard/paneltype/PhabricatorDashboardPanelType.php',
'PhabricatorDashboardPanelUsedByObjectEdgeType' => 'applications/search/edge/PhabricatorDashboardPanelUsedByObjectEdgeType.php',
'PhabricatorDashboardPanelViewController' => 'applications/dashboard/controller/panel/PhabricatorDashboardPanelViewController.php',
'PhabricatorDashboardPanelsTransaction' => 'applications/dashboard/xaction/dashboard/PhabricatorDashboardPanelsTransaction.php',
'PhabricatorDashboardPortal' => 'applications/dashboard/storage/PhabricatorDashboardPortal.php',
'PhabricatorDashboardPortalController' => 'applications/dashboard/controller/portal/PhabricatorDashboardPortalController.php',
'PhabricatorDashboardPortalDatasource' => 'applications/dashboard/typeahead/PhabricatorDashboardPortalDatasource.php',
@ -2998,7 +2998,6 @@ phutil_register_library_map(array(
'PhabricatorDashboardQueryPanelQueryTransaction' => 'applications/dashboard/xaction/panel/PhabricatorDashboardQueryPanelQueryTransaction.php',
'PhabricatorDashboardQueryPanelType' => 'applications/dashboard/paneltype/PhabricatorDashboardQueryPanelType.php',
'PhabricatorDashboardRemarkupRule' => 'applications/dashboard/remarkup/PhabricatorDashboardRemarkupRule.php',
'PhabricatorDashboardRemovePanelController' => 'applications/dashboard/controller/PhabricatorDashboardRemovePanelController.php',
'PhabricatorDashboardRenderingEngine' => 'applications/dashboard/engine/PhabricatorDashboardRenderingEngine.php',
'PhabricatorDashboardSchemaSpec' => 'applications/dashboard/storage/PhabricatorDashboardSchemaSpec.php',
'PhabricatorDashboardSearchEngine' => 'applications/dashboard/query/PhabricatorDashboardSearchEngine.php',
@ -8906,7 +8905,7 @@ phutil_register_library_map(array(
'PhabricatorNgramsInterface',
'PhabricatorDashboardPanelContainerInterface',
),
'PhabricatorDashboardAddPanelController' => 'PhabricatorDashboardController',
'PhabricatorDashboardAdjustController' => 'PhabricatorDashboardController',
'PhabricatorDashboardApplication' => 'PhabricatorApplication',
'PhabricatorDashboardApplicationInstallWorkflow' => 'PhabricatorDashboardInstallWorkflow',
'PhabricatorDashboardArchiveController' => 'PhabricatorDashboardController',
@ -8927,7 +8926,6 @@ phutil_register_library_map(array(
'PhabricatorDashboardInstall' => 'PhabricatorDashboardDAO',
'PhabricatorDashboardInstallController' => 'PhabricatorDashboardController',
'PhabricatorDashboardInstallWorkflow' => 'Phobject',
'PhabricatorDashboardLayoutConfig' => 'Phobject',
'PhabricatorDashboardLayoutMode' => 'Phobject',
'PhabricatorDashboardLayoutTransaction' => 'PhabricatorDashboardTransactionType',
'PhabricatorDashboardListController' => 'PhabricatorDashboardController',
@ -8971,6 +8969,7 @@ phutil_register_library_map(array(
'PhabricatorDashboardPanelType' => 'Phobject',
'PhabricatorDashboardPanelUsedByObjectEdgeType' => 'PhabricatorEdgeType',
'PhabricatorDashboardPanelViewController' => 'PhabricatorDashboardController',
'PhabricatorDashboardPanelsTransaction' => 'PhabricatorDashboardTransactionType',
'PhabricatorDashboardPortal' => array(
'PhabricatorDashboardDAO',
'PhabricatorApplicationTransactionInterface',
@ -9013,7 +9012,6 @@ phutil_register_library_map(array(
'PhabricatorDashboardQueryPanelQueryTransaction' => 'PhabricatorDashboardPanelPropertyTransaction',
'PhabricatorDashboardQueryPanelType' => 'PhabricatorDashboardPanelType',
'PhabricatorDashboardRemarkupRule' => 'PhabricatorObjectRemarkupRule',
'PhabricatorDashboardRemovePanelController' => 'PhabricatorDashboardController',
'PhabricatorDashboardRenderingEngine' => 'Phobject',
'PhabricatorDashboardSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhabricatorDashboardSearchEngine' => 'PhabricatorApplicationSearchEngine',

View file

@ -389,7 +389,7 @@ final class PhabricatorAuthSessionEngine extends Phobject {
* appropriate for one-time checks.
*
* @param PhabricatorUser User whose session needs to be in high security.
* @param AphrontReqeust Current request.
* @param AphrontRequest Current request.
* @param string URI to return the user to if they cancel.
* @return PhabricatorAuthHighSecurityToken Security token.
* @task hisec
@ -421,7 +421,7 @@ final class PhabricatorAuthSessionEngine extends Phobject {
* use @{method:requireHighSecurityToken}.
*
* @param PhabricatorUser User whose session needs to be in high security.
* @param AphrontReqeust Current request.
* @param AphrontRequest Current request.
* @param string URI to return the user to if they cancel.
* @param bool True to jump partial sessions directly into high
* security instead of just upgrading them to full

View file

@ -48,10 +48,9 @@ final class PhabricatorDashboardApplication extends PhabricatorApplication {
'(?:(?P<modeKey>[^/]+)/)?)?' =>
'PhabricatorDashboardInstallController',
'console/' => 'PhabricatorDashboardConsoleController',
'addpanel/(?P<id>\d+)/' => 'PhabricatorDashboardAddPanelController',
'movepanel/(?P<id>\d+)/' => 'PhabricatorDashboardMovePanelController',
'removepanel/(?P<id>\d+)/'
=> 'PhabricatorDashboardRemovePanelController',
'adjust/(?P<op>remove|add)/'
=> 'PhabricatorDashboardAdjustController',
'panel/' => array(
'install/(?P<engineKey>[^/]+)/(?:(?P<queryKey>[^/]+)/)?' =>
'PhabricatorDashboardQueryPanelInstallController',

View file

@ -1,103 +0,0 @@
<?php
final class PhabricatorDashboardAddPanelController
extends PhabricatorDashboardController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
$dashboard = id(new PhabricatorDashboardQuery())
->setViewer($viewer)
->withIDs(array($id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$dashboard) {
return new Aphront404Response();
}
$redirect_uri = $this->getApplicationURI(
'arrange/'.$dashboard->getID().'/');
$v_panel = head($request->getArr('panel'));
$e_panel = true;
$errors = array();
if ($request->isFormPost()) {
if (strlen($v_panel)) {
$panel = id(new PhabricatorDashboardPanelQuery())
->setViewer($viewer)
->withIDs(array($v_panel))
->executeOne();
if (!$panel) {
$errors[] = pht('Not a valid panel.');
$e_panel = pht('Invalid');
}
$on_dashboard = $dashboard->getPanels();
$on_ids = mpull($on_dashboard, null, 'getID');
if (array_key_exists($v_panel, $on_ids)) {
$p_name = $panel->getName();
$errors[] = pht('Panel "%s" already exists on dashboard.', $p_name);
$e_panel = pht('Invalid');
}
} else {
$errors[] = pht('Select a panel to add.');
$e_panel = pht('Required');
}
if (!$errors) {
PhabricatorDashboardTransactionEditor::addPanelToDashboard(
$viewer,
PhabricatorContentSource::newFromRequest($request),
$panel,
$dashboard,
$request->getInt('column', 0));
return id(new AphrontRedirectResponse())->setURI($redirect_uri);
}
}
$panels = id(new PhabricatorDashboardPanelQuery())
->setViewer($viewer)
->withArchived(false)
->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);
}
$form = id(new AphrontFormView())
->setUser($viewer)
->addHiddenInput('column', $request->getInt('column'))
->appendRemarkupInstructions(
pht('Choose a panel to add to this dashboard:'))
->appendChild(
id(new AphrontFormTokenizerControl())
->setUser($this->getViewer())
->setDatasource(new PhabricatorDashboardPanelDatasource())
->setLimit(1)
->setName('panel')
->setLabel(pht('Panel')));
return $this->newDialog()
->setTitle(pht('Add Panel'))
->setErrors($errors)
->appendChild($form->buildLayoutView())
->addCancelButton($redirect_uri)
->addSubmitButton(pht('Add Panel'));
}
}

View file

@ -1,77 +0,0 @@
<?php
final class PhabricatorDashboardRemovePanelController
extends PhabricatorDashboardController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
$dashboard = id(new PhabricatorDashboardQuery())
->setViewer($viewer)
->withIDs(array($id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$dashboard) {
return new Aphront404Response();
}
// NOTE: If you can edit a dashboard, you can remove panels from it even
// if you don't have permission to see them or they aren't valid. We only
// require that the panel be present on the dashboard.
$v_panel = $request->getStr('panelPHID');
$panel_on_dashboard = false;
$layout = $dashboard->getLayoutConfigObject();
$columns = $layout->getPanelLocations();
foreach ($columns as $column) {
foreach ($column as $column_panel_phid) {
if ($column_panel_phid == $v_panel) {
$panel_on_dashboard = true;
break;
}
}
}
if (!$panel_on_dashboard) {
return new Aphront404Response();
}
$redirect_uri = $dashboard->getURI();
$layout_config = $dashboard->getLayoutConfigObject();
if ($request->isFormPost()) {
$xactions = array();
$layout_config->removePanel($v_panel);
$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);
}
$form = id(new AphrontFormView())
->setUser($viewer)
->addHiddenInput('confirm', true)
->addHiddenInput('panelPHID', $v_panel)
->appendChild(pht('Are you sure you want to remove this panel?'));
return $this->newDialog()
->setTitle(pht('Remove Panel'))
->appendChild($form->buildLayoutView())
->addCancelButton($redirect_uri)
->addSubmitButton(pht('Remove Panel'));
}
}

View file

@ -0,0 +1,202 @@
<?php
final class PhabricatorDashboardAdjustController
extends PhabricatorDashboardController {
private $contextPHID;
private $panelKey;
private $columnKey;
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$context_phid = $request->getStr('contextPHID');
$dashboard = id(new PhabricatorDashboardQuery())
->setViewer($viewer)
->withPHIDs(array($context_phid))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$dashboard) {
return new Aphront404Response();
}
$this->contextPHID = $context_phid;
$done_uri = $dashboard->getURI();
$ref_list = $dashboard->getPanelRefList();
$panel_ref = null;
$panel_key = $request->getStr('panelKey');
if (strlen($panel_key)) {
$panel_ref = $ref_list->getPanelRef($panel_key);
if (!$panel_ref) {
return new Aphront404Response();
}
$this->panelKey = $panel_key;
} else {
$panel_ref = null;
}
$column_key = $request->getStr('columnKey');
if (strlen($column_key)) {
$columns = $ref_list->getColumns();
if (!isset($columns[$column_key])) {
return new Aphront404Response();
}
$this->columnKey = $column_key;
}
switch ($request->getURIData('op')) {
case 'add':
return $this->handleAddRequest($dashboard, $done_uri);
case 'remove':
if (!$panel_ref) {
return new Aphront404Response();
}
return $this->handleRemoveRequest($dashboard, $panel_ref, $done_uri);
}
}
private function handleAddRequest(
PhabricatorDashboard $dashboard,
$done_uri) {
$request = $this->getRequest();
$viewer = $this->getViewer();
$errors = array();
$panel_phid = null;
$e_panel = true;
if ($request->isFormPost()) {
$panel_phid = head($request->getArr('panelPHIDs'));
if (!$panel_phid) {
$errors[] = pht('You must choose a panel to add to the dashboard.');
$e_panel = pht('Required');
} else {
$panel = id(new PhabricatorDashboardPanelQuery())
->setViewer($viewer)
->withPHIDs(array($panel_phid))
->executeOne();
if (!$panel) {
$errors[] = pht('You must choose a valid panel.');
$e_panel = pht('Invalid');
}
}
if (!$errors) {
$xactions = array();
$ref_list = clone $dashboard->getPanelRefList();
$ref_list->newPanelRef($panel, $this->columnKey);
$new_panels = $ref_list->toDictionary();
$xactions[] = $dashboard->getApplicationTransactionTemplate()
->setTransactionType(
PhabricatorDashboardPanelsTransaction::TRANSACTIONTYPE)
->setNewValue($new_panels);
$editor = $dashboard->getApplicationTransactionEditor()
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true);
$editor->applyTransactions($dashboard, $xactions);
return id(new AphrontRedirectResponse())->setURI($done_uri);
}
}
if ($panel_phid) {
$panel_phids = array($panel_phid);
} else {
$panel_phids = array();
}
$form = id(new AphrontFormView())
->setViewer($viewer)
->appendRemarkupInstructions(
pht('Choose a panel to add to this dashboard:'))
->appendControl(
id(new AphrontFormTokenizerControl())
->setDatasource(new PhabricatorDashboardPanelDatasource())
->setLimit(1)
->setName('panelPHIDs')
->setLabel(pht('Panel'))
->setError($e_panel)
->setValue($panel_phids));
return $this->newEditDialog()
->setTitle(pht('Add Panel'))
->setWidth(AphrontDialogView::WIDTH_FORM)
->setErrors($errors)
->appendForm($form)
->addCancelButton($done_uri)
->addSubmitButton(pht('Add Panel'));
}
private function handleRemoveRequest(
PhabricatorDashboard $dashboard,
PhabricatorDashboardPanelRef $panel_ref,
$done_uri) {
$request = $this->getRequest();
$viewer = $this->getViewer();
// NOTE: If you can edit a dashboard, you can remove panels from it even
// if you don't have permission to see them or they aren't valid. We only
// require that the panel be present on the dashboard.
if ($request->isFormPost()) {
$xactions = array();
$ref_list = clone $dashboard->getPanelRefList();
$ref_list->removePanelRef($panel_ref);
$new_panels = $ref_list->toDictionary();
$xactions[] = $dashboard->getApplicationTransactionTemplate()
->setTransactionType(
PhabricatorDashboardPanelsTransaction::TRANSACTIONTYPE)
->setNewValue($new_panels);
$editor = $dashboard->getApplicationTransactionEditor()
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true);
$editor->applyTransactions($dashboard, $xactions);
return id(new AphrontRedirectResponse())->setURI($done_uri);
}
$panel_phid = $panel_ref->getPanelPHID();
$handles = $viewer->loadHandles(array($panel_phid));
$handle = $handles[$panel_phid];
$message = pht(
'Remove panel %s from dashboard %s?',
phutil_tag('strong', array(), $handle->getFullName()),
phutil_tag('strong', array(), $dashboard->getName()));
return $this->newEditDialog()
->setTitle(pht('Remove Dashboard Panel'))
->appendParagraph($message)
->addCancelButton($done_uri)
->addSubmitButton(pht('Remove Panel'));
}
private function newEditDialog() {
return $this->newDialog()
->addHiddenInput('contextPHID', $this->contextPHID)
->addHiddenInput('panelKey', $this->panelKey)
->addHiddenInput('columnKey', $this->columnKey);
}
}

View file

@ -9,37 +9,43 @@ final class PhabricatorDashboardPanelEditController
$engine = id(new PhabricatorDashboardPanelEditEngine())
->setController($this);
// We can create or edit a panel in the context of a dashboard. If we
// started on a dashboard, we want to return to that dashboard when we're
// done editing.
$dashboard_id = $request->getStr('dashboardID');
if (strlen($dashboard_id)) {
$dashboard = id(new PhabricatorDashboardQuery())
// We can create or edit a panel in the context of a dashboard or
// container panel, like a tab panel. If we started this flow on some
// container object, we want to return to that container when we're done
// editing.
$context_phid = $request->getStr('contextPHID');
if (strlen($context_phid)) {
$context = id(new PhabricatorObjectQuery())
->setViewer($viewer)
->withIDs(array($dashboard_id))
->withPHIDs(array($context_phid))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$dashboard) {
if (!$context) {
return new Aphront404Response();
}
if (!($context instanceof PhabricatorDashboardPanelContainerInterface)) {
return new Aphront404Response();
}
$engine
->setDashboard($dashboard)
->addContextParameter('dashboardID', $dashboard_id);
->setContextObject($context)
->addContextParameter('contextPHID', $context_phid);
} else {
$dashboard = null;
$context = null;
}
$id = $request->getURIData('id');
if (!$id) {
$column_id = $request->getStr('columnID');
$column_key = $request->getStr('columnKey');
if ($dashboard) {
$cancel_uri = $dashboard->getURI();
if ($context) {
$cancel_uri = $context->getURI();
} else {
$cancel_uri = $this->getApplicationURI('panel/');
}
@ -52,9 +58,9 @@ final class PhabricatorDashboardPanelEditController
$engine
->addContextParameter('panelType', $panel_type)
->addContextParameter('columnID', $column_id)
->addContextParameter('columnKey', $column_key)
->setPanelType($panel_type)
->setColumnID($column_id);
->setColumnKey($column_key);
}
return $engine->buildResponse();

View file

@ -31,14 +31,27 @@ final class PhabricatorDashboardPanelRenderController
$parent_phids = array();
}
$rendered_panel = id(new PhabricatorDashboardPanelRenderingEngine())
$engine = id(new PhabricatorDashboardPanelRenderingEngine())
->setViewer($viewer)
->setPanel($panel)
->setPanelPHID($panel->getPHID())
->setParentPanelPHIDs($parent_phids)
->setHeaderMode($request->getStr('headerMode'))
->setDashboardID($request->getInt('dashboardID'))
->renderPanel();
->setPanelKey($request->getStr('panelKey'));
$context_phid = $request->getStr('contextPHID');
if ($context_phid) {
$context = id(new PhabricatorObjectQuery())
->setViewer($viewer)
->withPHIDs(array($context_phid))
->executeOne();
if (!$context) {
return new Aphront404Response();
}
$engine->setContextObject($context);
}
$rendered_panel = $engine->renderPanel();
if ($request->isAjax()) {
return id(new AphrontAjaxResponse())

View file

@ -6,8 +6,8 @@ final class PhabricatorDashboardPanelEditEngine
const ENGINECONST = 'dashboard.panel';
private $panelType;
private $dashboard;
private $columnID;
private $contextObject;
private $columnKey;
public function setPanelType($panel_type) {
$this->panelType = $panel_type;
@ -18,22 +18,22 @@ final class PhabricatorDashboardPanelEditEngine
return $this->panelType;
}
public function setDashboard(PhabricatorDashboard $dashboard) {
$this->dashboard = $dashboard;
public function setContextObject($context) {
$this->contextObject = $context;
return $this;
}
public function getDashboard() {
return $this->dashboard;
public function getContextObject() {
return $this->contextObject;
}
public function setColumnID($column_id) {
$this->columnID = $column_id;
public function setColumnKey($column_key) {
$this->columnKey = $column_key;
return $this;
}
public function getColumnID() {
return $this->columnID;
public function getColumnKey() {
return $this->columnKey;
}
public function isEngineConfigurable() {
@ -84,27 +84,27 @@ final class PhabricatorDashboardPanelEditEngine
}
protected function getObjectCreateCancelURI($object) {
$dashboard = $this->getDashboard();
if ($dashboard) {
return $dashboard->getURI();
$context = $this->getContextObject();
if ($context) {
return $context->getURI();
}
return parent::getObjectCreateCancelURI($object);
}
public function getEffectiveObjectEditDoneURI($object) {
$dashboard = $this->getDashboard();
if ($dashboard) {
return $dashboard->getURI();
$context = $this->getContextObject();
if ($context) {
return $context->getURI();
}
return parent::getEffectiveObjectEditDoneURI($object);
}
protected function getObjectEditCancelURI($object) {
$dashboard = $this->getDashboard();
if ($dashboard) {
return $dashboard->getURI();
$context = $this->getContextObject();
if ($context) {
return $context->getURI();
}
return parent::getObjectEditCancelURI($object);
@ -131,18 +131,34 @@ final class PhabricatorDashboardPanelEditEngine
}
protected function didApplyTransactions($object, array $xactions) {
$dashboard = $this->getDashboard();
if ($dashboard) {
$context = $this->getContextObject();
if ($context instanceof PhabricatorDashboard) {
$viewer = $this->getViewer();
$controller = $this->getController();
$request = $controller->getRequest();
PhabricatorDashboardTransactionEditor::addPanelToDashboard(
$viewer,
PhabricatorContentSource::newFromRequest($request),
$object,
$dashboard,
(int)$this->getColumnID());
$dashboard = $context;
$xactions = array();
$ref_list = clone $dashboard->getPanelRefList();
$ref_list->newPanelRef($object, $this->getColumnKey());
$new_panels = $ref_list->toDictionary();
$xactions[] = $dashboard->getApplicationTransactionTemplate()
->setTransactionType(
PhabricatorDashboardPanelsTransaction::TRANSACTIONTYPE)
->setNewValue($new_panels);
$editor = $dashboard->getApplicationTransactionEditor()
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true);
$editor->applyTransactions($dashboard, $xactions);
}
}

View file

@ -12,11 +12,11 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject {
private $enableAsyncRendering;
private $parentPanelPHIDs;
private $headerMode = self::HEADER_MODE_NORMAL;
private $dashboardID;
private $movable = true;
private $panelHandle;
private $editMode;
private $contextObject;
private $panelKey;
public function setContextObject($object) {
$this->contextObject = $object;
@ -27,13 +27,13 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject {
return $this->contextObject;
}
public function setDashboardID($id) {
$this->dashboardID = $id;
public function setPanelKey($panel_key) {
$this->panelKey = $panel_key;
return $this;
}
public function getDashboardID() {
return $this->dashboardID;
public function getPanelKey() {
return $this->panelKey;
}
public function setHeaderMode($header_mode) {
@ -182,10 +182,10 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject {
private function renderAsyncPanel() {
$context_phid = $this->getContextPHID();
$panel = $this->getPanel();
$panel_id = celerity_generate_unique_node_id();
$dashboard_id = $this->getDashboardID();
Javelin::initBehavior(
'dashboard-async-panel',
@ -193,7 +193,8 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject {
'panelID' => $panel_id,
'parentPanelPHIDs' => $this->getParentPanelPHIDs(),
'headerMode' => $this->getHeaderMode(),
'dashboardID' => $dashboard_id,
'contextPHID' => $context_phid,
'panelKey' => $this->getPanelKey(),
'uri' => '/dashboard/panel/render/'.$panel->getID().'/',
));
@ -322,7 +323,7 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject {
$viewer = $this->getViewer();
$panel = $this->getPanel();
$dashboard_id = $this->getDashboardID();
$context_phid = $this->getContextPHID();
$actions = array();
@ -330,15 +331,15 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject {
$panel_id = $panel->getID();
$edit_uri = "/dashboard/panel/edit/{$panel_id}/";
$edit_uri = new PhutilURI($edit_uri);
if ($dashboard_id) {
$edit_uri->replaceQueryParam('dashboardID', $dashboard_id);
}
$params = array(
'contextPHID' => $context_phid,
);
$edit_uri = new PhutilURI($edit_uri, $params);
$actions[] = id(new PhabricatorActionView())
->setIcon('fa-pencil')
->setName(pht('Edit Panel'))
->setHref((string)$edit_uri);
->setHref($edit_uri);
$actions[] = id(new PhabricatorActionView())
->setIcon('fa-window-maximize')
@ -346,16 +347,19 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject {
->setHref($panel->getURI());
}
if ($dashboard_id) {
if ($context_phid) {
$panel_phid = $this->getPanelPHID();
$remove_uri = "/dashboard/removepanel/{$dashboard_id}/";
$remove_uri = id(new PhutilURI($remove_uri))
->replaceQueryParam('panelPHID', $panel_phid);
$remove_uri = urisprintf('/dashboard/adjust/remove/');
$params = array(
'contextPHID' => $context_phid,
'panelKey' => $this->getPanelKey(),
);
$remove_uri = new PhutilURI($remove_uri, $params);
$actions[] = id(new PhabricatorActionView())
->setIcon('fa-times')
->setHref((string)$remove_uri)
->setHref($remove_uri)
->setName(pht('Remove Panel'))
->setWorkflow(true);
}
@ -415,5 +419,14 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject {
}
}
private function getContextPHID() {
$context = $this->getContextObject();
if ($context) {
return $context->getPHID();
}
return null;
}
}

View file

@ -73,9 +73,9 @@ final class PhabricatorDashboardRenderingEngine extends Phobject {
$panel_engine = id(new PhabricatorDashboardPanelRenderingEngine())
->setViewer($viewer)
->setDashboardID($dashboard->getID())
->setEnableAsyncRendering(true)
->setContextObject($dashboard)
->setPanelKey($panel_ref->getPanelKey())
->setPanelPHID($panel_phid)
->setParentPanelPHIDs(array())
->setHeaderMode($h_mode)
@ -94,14 +94,20 @@ final class PhabricatorDashboardRenderingEngine extends Phobject {
if ($is_editable) {
$column_views[] = $this->renderAddPanelPlaceHolder();
$column_views[] = $this->renderAddPanelUI($column->getColumnKey());
$column_views[] = $this->renderAddPanelUI($column);
}
$sigil = 'dashboard-column';
$metadata = array(
'columnKey' => $column->getColumnKey(),
);
$result->addColumn(
$column_views,
implode(' ', $column_classes),
$sigil = 'dashboard-column',
$metadata = array('columnID' => $column));
$sigil,
$metadata);
}
if ($is_editable) {
@ -133,15 +139,17 @@ final class PhabricatorDashboardRenderingEngine extends Phobject {
pht('This column does not have any panels yet.'));
}
private function renderAddPanelUI($column) {
$dashboard_id = $this->dashboard->getID();
private function renderAddPanelUI(PhabricatorDashboardColumn $column) {
$dashboard = $this->getDashboard();
$column_key = $column->getColumnKey();
$create_uri = id(new PhutilURI('/dashboard/panel/edit/'))
->replaceQueryParam('dashboardID', $dashboard_id)
->replaceQueryParam('columnID', $column);
->replaceQueryParam('contextPHID', $dashboard->getPHID())
->replaceQueryParam('columnKey', $column_key);
$add_uri = id(new PhutilURI('/dashboard/addpanel/'.$dashboard_id.'/'))
->replaceQueryParam('columnID', $column);
$add_uri = id(new PhutilURI('/dashboard/adjust/add/'))
->replaceQueryParam('contextPHID', $dashboard->getPHID())
->replaceQueryParam('columnKey', $column_key);
$create_button = id(new PHUIButtonView())
->setTag('a')

View file

@ -1,128 +0,0 @@
<?php
final class PhabricatorDashboardLayoutConfig extends Phobject {
const MODE_FULL = 'layout-mode-full';
const MODE_HALF_AND_HALF = 'layout-mode-half-and-half';
const MODE_THIRD_AND_THIRDS = 'layout-mode-third-and-thirds';
const MODE_THIRDS_AND_THIRD = 'layout-mode-thirds-and-third';
private $layoutMode = self::MODE_FULL;
private $panelLocations = array();
public function setLayoutMode($mode) {
$this->layoutMode = $mode;
return $this;
}
public function getLayoutMode() {
return $this->layoutMode;
}
public function setPanelLocation($which_column, $panel_phid) {
$this->panelLocations[$which_column][] = $panel_phid;
return $this;
}
public function setPanelLocations(array $locations) {
$this->panelLocations = $locations;
return $this;
}
public function getPanelLocations() {
return $this->panelLocations;
}
public function replacePanel($old_phid, $new_phid) {
$locations = $this->getPanelLocations();
foreach ($locations as $column => $panel_phids) {
foreach ($panel_phids as $key => $panel_phid) {
if ($panel_phid == $old_phid) {
$locations[$column][$key] = $new_phid;
}
}
}
return $this->setPanelLocations($locations);
}
public function removePanel($panel_phid) {
$panel_location_grid = $this->getPanelLocations();
foreach ($panel_location_grid as $column => $panel_columns) {
$found_old_column = array_search($panel_phid, $panel_columns);
if ($found_old_column !== false) {
$new_panel_columns = $panel_columns;
array_splice(
$new_panel_columns,
$found_old_column,
1,
array());
$panel_location_grid[$column] = $new_panel_columns;
break;
}
}
$this->setPanelLocations($panel_location_grid);
}
public function getDefaultPanelLocations() {
switch ($this->getLayoutMode()) {
case self::MODE_HALF_AND_HALF:
case self::MODE_THIRD_AND_THIRDS:
case self::MODE_THIRDS_AND_THIRD:
$locations = array(array(), array());
break;
case self::MODE_FULL:
default:
$locations = array(array());
break;
}
return $locations;
}
public function getColumnClass($column_index, $grippable = false) {
switch ($this->getLayoutMode()) {
case self::MODE_HALF_AND_HALF:
$class = 'half';
break;
case self::MODE_THIRD_AND_THIRDS:
if ($column_index) {
$class = 'thirds';
} else {
$class = 'third';
}
break;
case self::MODE_THIRDS_AND_THIRD:
if ($column_index) {
$class = 'third';
} else {
$class = 'thirds';
}
break;
case self::MODE_FULL:
default:
$class = null;
break;
}
if ($grippable) {
$class .= ' grippable';
}
return $class;
}
public static function newFromDictionary(array $dict) {
$layout_config = id(new PhabricatorDashboardLayoutConfig())
->setLayoutMode(idx($dict, 'layoutMode', self::MODE_FULL));
$layout_config->setPanelLocations(idx(
$dict,
'panelLocations',
$layout_config->getDefaultPanelLocations()));
return $layout_config;
}
public function toDictionary() {
return array(
'layoutMode' => $this->getLayoutMode(),
'panelLocations' => $this->getPanelLocations(),
);
}
}

View file

@ -34,4 +34,12 @@ final class PhabricatorDashboardPanelRef
return $this->panelKey;
}
public function toDictionary() {
return array(
'panelKey' => $this->getPanelKey(),
'panelPHID' => $this->getPanelPHID(),
'columnKey' => $this->getColumnKey(),
);
}
}

View file

@ -73,4 +73,47 @@ final class PhabricatorDashboardPanelRefList
return $this->refs;
}
public function getPanelRef($panel_key) {
foreach ($this->getPanelRefs() as $ref) {
if ($ref->getPanelKey() === $panel_key) {
return $ref;
}
}
return null;
}
public function toDictionary() {
return array_values(mpull($this->getPanelRefs(), 'toDictionary'));
}
public function newPanelRef(PhabricatorDashboardPanel $panel, $column_key) {
$ref = id(new PhabricatorDashboardPanelRef())
->setPanelKey($this->newPanelKey())
->setPanelPHID($panel->getPHID())
->setColumnKey($column_key);
$this->refs[] = $ref;
return $ref;
}
public function removePanelRef(PhabricatorDashboardPanelRef $target) {
foreach ($this->refs as $key => $ref) {
if ($ref->getPanelKey() !== $target->getPanelKey()) {
continue;
}
unset($this->refs[$key]);
return $ref;
}
return null;
}
private function newPanelKey() {
return Filesystem::readRandomCharacters(8);
}
}

View file

@ -24,9 +24,6 @@ final class PhabricatorDashboard extends PhabricatorDashboardDAO
const STATUS_ACTIVE = 'active';
const STATUS_ARCHIVED = 'archived';
private $panels = self::ATTACHABLE;
private $edgeProjectPHIDs = self::ATTACHABLE;
private $panelRefList;
public static function initializeNewDashboard(PhabricatorUser $actor) {
@ -36,8 +33,7 @@ final class PhabricatorDashboard extends PhabricatorDashboardDAO
->setViewPolicy(PhabricatorPolicies::getMostOpenPolicy())
->setEditPolicy($actor->getPHID())
->setStatus(self::STATUS_ACTIVE)
->setAuthorPHID($actor->getPHID())
->attachPanels(array());
->setAuthorPHID($actor->getPHID());
}
public static function getStatusNameMap() {
@ -62,9 +58,8 @@ final class PhabricatorDashboard extends PhabricatorDashboardDAO
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorDashboardDashboardPHIDType::TYPECONST);
public function getPHIDType() {
return PhabricatorDashboardDashboardPHIDType::TYPECONST;
}
public function getRawLayoutMode() {
@ -75,11 +70,18 @@ final class PhabricatorDashboard extends PhabricatorDashboardDAO
public function setRawLayoutMode($mode) {
$config = $this->getRawLayoutConfig();
$config['layoutMode'] = $mode;
return $this->setRawLayoutConfig($config);
}
// If a cached panel ref list exists, clear it.
$this->panelRefList = null;
public function getRawPanels() {
$config = $this->getRawLayoutConfig();
return idx($config, 'panels');
}
return $this->setLayoutConfig($config);
public function setRawPanels(array $panels) {
$config = $this->getRawLayoutConfig();
$config['panels'] = $panels;
return $this->setRawLayoutConfig($config);
}
private function getRawLayoutConfig() {
@ -92,6 +94,13 @@ final class PhabricatorDashboard extends PhabricatorDashboardDAO
return $config;
}
private function setRawLayoutConfig(array $config) {
// If a cached panel ref list exists, clear it.
$this->panelRefList = null;
return $this->setLayoutConfig($config);
}
public function isArchived() {
return ($this->getStatus() == self::STATUS_ARCHIVED);
}

View file

@ -0,0 +1,153 @@
<?php
final class PhabricatorDashboardPanelsTransaction
extends PhabricatorDashboardTransactionType {
const TRANSACTIONTYPE = 'panels';
public function generateOldValue($object) {
return $object->getRawPanels();
}
public function applyInternalEffects($object, $value) {
$object->setRawPanels($value);
}
public function getTitle() {
return pht(
'%s changed the panels on this dashboard.',
$this->renderAuthor());
}
public function validateTransactions($object, array $xactions) {
$actor = $this->getActor();
$errors = array();
$ref_list = $object->getPanelRefList();
$columns = $ref_list->getColumns();
$old_phids = $object->getPanelPHIDs();
$old_phids = array_fuse($old_phids);
foreach ($xactions as $xaction) {
$new_value = $xaction->getNewValue();
if (!is_array($new_value)) {
$errors[] = $this->newInvalidError(
pht('Panels must be a list of panel specifications.'),
$xaction);
continue;
}
if (!phutil_is_natural_list($new_value)) {
$errors[] = $this->newInvalidError(
pht('Panels must be a list, not a map.'),
$xaction);
continue;
}
$new_phids = array();
$seen_keys = array();
foreach ($new_value as $idx => $spec) {
if (!is_array($spec)) {
$errors[] = $this->newInvalidError(
pht(
'Each panel specification must be a map of panel attributes. '.
'Panel specification at index "%s" is "%s".',
$idx,
phutil_describe_type($spec)),
$xaction);
continue;
}
try {
PhutilTypeSpec::checkMap(
$spec,
array(
'panelPHID' => 'string',
'columnKey' => 'string',
'panelKey' => 'string',
));
} catch (PhutilTypeCheckException $ex) {
$errors[] = $this->newInvalidError(
pht(
'Panel specification at index "%s" is invalid: %s',
$idx,
$ex->getMessage()),
$xaction);
continue;
}
$panel_key = $spec['panelKey'];
if (!strlen($panel_key)) {
$errors[] = $this->newInvalidError(
pht(
'Panel specification at index "%s" has bad panel key "%s". '.
'Panel keys must be nonempty.',
$idx,
$panel_key),
$xaction);
continue;
}
if (isset($seen_keys[$panel_key])) {
$errors[] = $this->newInvalidError(
pht(
'Panel specification at index "%s" has duplicate panel key '.
'"%s". Each panel must have a unique panel key.',
$idx,
$panel_key),
$xaction);
continue;
}
$seen_keys[$panel_key] = true;
$panel_phid = $spec['panelPHID'];
$new_phids[] = $panel_phid;
$column_key = $spec['columnKey'];
if (!isset($columns[$column_key])) {
$errors[] = $this->newInvalidError(
pht(
'Panel specification at index "%s" has bad column key "%s", '.
'valid column keys are: %s.',
$idx,
$column_key,
implode(', ', array_keys($columns))),
$xaction);
continue;
}
}
$new_phids = array_fuse($new_phids);
$add_phids = array_diff_key($new_phids, $old_phids);
if ($add_phids) {
$panels = id(new PhabricatorDashboardPanelQuery())
->setViewer($actor)
->withPHIDs($add_phids)
->execute();
$panels = mpull($panels, null, 'getPHID');
foreach ($add_phids as $add_phid) {
$panel = idx($panels, $add_phid);
if (!$panel) {
$errors[] = $this->newInvalidError(
pht(
'Panel specification adds panel "%s", but this is not a '.
'valid panel or not a visible panel. You can only add '.
'valid panels which you have permission to see to a dashboard.',
$add_phid));
continue;
}
}
}
}
return $errors;
}
}

View file

@ -12,7 +12,8 @@ JX.behavior('dashboard-async-panel', function(config) {
var data = {
parentPanelPHIDs: config.parentPanelPHIDs.join(','),
headerMode: config.headerMode,
dashboardID: config.dashboardID
contextPHID: config.contextPHID,
panelKey: config.panelKey
};
new JX.Workflow(config.uri)