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

Dashboards - add remove functionality

Summary: To get there, upgrade "headerless" to "headerMode". Add a new removepanel controller. Fixes T5084.

Test Plan: removed some panels to much success

Reviewers: chad, epriestley

Reviewed By: epriestley

Subscribers: epriestley, Korvin

Maniphest Tasks: T5078, T5084

Differential Revision: https://secure.phabricator.com/D9156
This commit is contained in:
Bob Trahan 2014-05-19 14:04:26 -07:00
parent 9cb4047134
commit d9058d7f3f
17 changed files with 327 additions and 99 deletions

View file

@ -52,7 +52,7 @@ return array(
'rsrc/css/application/conpherence/widget-pane.css' => 'bf275a6c',
'rsrc/css/application/contentsource/content-source-view.css' => '4b8b05d4',
'rsrc/css/application/countdown/timer.css' => '86b7b0a0',
'rsrc/css/application/dashboard/dashboard.css' => '2b41640b',
'rsrc/css/application/dashboard/dashboard.css' => 'fbf815b5',
'rsrc/css/application/diff/inline-comment-summary.css' => '8cfd34e8',
'rsrc/css/application/differential/add-comment.css' => 'c478bcaa',
'rsrc/css/application/differential/changeset-view.css' => 'c45747f0',
@ -355,7 +355,7 @@ return array(
'rsrc/js/application/conpherence/behavior-pontificate.js' => '53f6f2dd',
'rsrc/js/application/conpherence/behavior-widget-pane.js' => '40b1ff90',
'rsrc/js/application/countdown/timer.js' => '889c96f3',
'rsrc/js/application/dashboard/behavior-dashboard-async-panel.js' => 'f1375ea5',
'rsrc/js/application/dashboard/behavior-dashboard-async-panel.js' => '469c0d9e',
'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => 'fa187a68',
'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => 'f2441746',
'rsrc/js/application/differential/behavior-add-reviewers-and-ccs.js' => '533a187b',
@ -550,7 +550,7 @@ return array(
'javelin-behavior-conpherence-widget-pane' => '40b1ff90',
'javelin-behavior-countdown-timer' => '889c96f3',
'javelin-behavior-dark-console' => 'e9fdb5e5',
'javelin-behavior-dashboard-async-panel' => 'f1375ea5',
'javelin-behavior-dashboard-async-panel' => '469c0d9e',
'javelin-behavior-dashboard-move-panels' => 'fa187a68',
'javelin-behavior-device' => '03d6ed07',
'javelin-behavior-differential-add-reviewers-and-ccs' => '533a187b',
@ -698,7 +698,7 @@ return array(
'phabricator-core-css' => '40151074',
'phabricator-countdown-css' => '86b7b0a0',
'phabricator-crumbs-view-css' => '6a23399c',
'phabricator-dashboard-css' => '2b41640b',
'phabricator-dashboard-css' => 'fbf815b5',
'phabricator-drag-and-drop-file-upload' => 'ae6abfba',
'phabricator-draggable-list' => '1681c4d4',
'phabricator-fatal-config-template-css' => '25d446d6',
@ -1129,6 +1129,12 @@ return array(
1 => 'javelin-stratcom',
2 => 'javelin-dom',
),
'469c0d9e' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
2 => 'javelin-workflow',
),
'46efd18e' =>
array(
0 => 'multirow-row-manager',
@ -1254,6 +1260,13 @@ return array(
2 => 'javelin-util',
3 => 'phabricator-shaped-request',
),
'62e18640' =>
array(
0 => 'javelin-install',
1 => 'javelin-util',
2 => 'javelin-dom',
3 => 'javelin-typeahead-normalizer',
),
'6453c869' =>
array(
0 => 'javelin-install',
@ -1322,13 +1335,6 @@ return array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
),
'62e18640' =>
array(
0 => 'javelin-install',
1 => 'javelin-util',
2 => 'javelin-dom',
3 => 'javelin-typeahead-normalizer',
),
'76f4ebed' =>
array(
0 => 'javelin-install',
@ -1961,12 +1967,6 @@ return array(
0 => 'javelin-install',
1 => 'javelin-util',
),
'f1375ea5' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
2 => 'javelin-workflow',
),
'f2441746' =>
array(
0 => 'javelin-dom',

View file

@ -1487,6 +1487,7 @@ phutil_register_library_map(array(
'PhabricatorDashboardPanelTypeText' => 'applications/dashboard/paneltype/PhabricatorDashboardPanelTypeText.php',
'PhabricatorDashboardPanelViewController' => 'applications/dashboard/controller/PhabricatorDashboardPanelViewController.php',
'PhabricatorDashboardQuery' => 'applications/dashboard/query/PhabricatorDashboardQuery.php',
'PhabricatorDashboardRemovePanelController' => 'applications/dashboard/controller/PhabricatorDashboardRemovePanelController.php',
'PhabricatorDashboardRenderingEngine' => 'applications/dashboard/engine/PhabricatorDashboardRenderingEngine.php',
'PhabricatorDashboardSearchEngine' => 'applications/dashboard/query/PhabricatorDashboardSearchEngine.php',
'PhabricatorDashboardTransaction' => 'applications/dashboard/storage/PhabricatorDashboardTransaction.php',
@ -4278,6 +4279,7 @@ phutil_register_library_map(array(
'PhabricatorDashboardPanelTypeText' => 'PhabricatorDashboardPanelType',
'PhabricatorDashboardPanelViewController' => 'PhabricatorDashboardController',
'PhabricatorDashboardQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorDashboardRemovePanelController' => 'PhabricatorDashboardController',
'PhabricatorDashboardRenderingEngine' => 'Phobject',
'PhabricatorDashboardSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorDashboardTransaction' => 'PhabricatorApplicationTransaction',

View file

@ -26,6 +26,8 @@ final class PhabricatorApplicationDashboard extends PhabricatorApplication {
'edit/(?:(?P<id>\d+)/)?' => 'PhabricatorDashboardEditController',
'addpanel/(?P<id>\d+)/' => 'PhabricatorDashboardAddPanelController',
'movepanel/(?P<id>\d+)/' => 'PhabricatorDashboardMovePanelController',
'removepanel/(?P<id>\d+)/'
=> 'PhabricatorDashboardRemovePanelController',
'panel/' => array(
'(?:query/(?P<queryKey>[^/]+)/)?'
=> 'PhabricatorDashboardPanelListController',

View file

@ -68,12 +68,10 @@ final class PhabricatorDashboardAddPanelController
),
));
if ($layout_config->isMultiColumnLayout()) {
$layout_config->setPanelLocation(
$request->getInt('column'),
$panel->getPHID());
$dashboard->setLayoutConfigFromObject($layout_config);
}
$layout_config->setPanelLocation(
$request->getInt('column', 0),
$panel->getPHID());
$dashboard->setLayoutConfigFromObject($layout_config);
$editor = id(new PhabricatorDashboardTransactionEditor())
->setActor($viewer)

View file

@ -38,21 +38,9 @@ final class PhabricatorDashboardMovePanelController
}
$layout_config = $dashboard->getLayoutConfigObject();
$layout_config->removePanel($panel_phid);
$panel_location_grid = $layout_config->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;
}
}
$panel_columns = idx($panel_location_grid, $column_id, array());
if ($panel_columns) {
$insert_at = 0;

View file

@ -52,7 +52,7 @@ final class PhabricatorDashboardPanelEditController
$title = pht('Edit %s', $panel->getMonogram());
$header = pht('Edit %s %s', $panel->getMonogram(), $panel->getName());
$button = pht('Save Panel');
$cancel_uri = '/'.$panel->getMonogram();
$cancel_uri = $this->getPanelRedirectURI($panel);
}
$v_name = $panel->getName();
@ -89,7 +89,7 @@ final class PhabricatorDashboardPanelEditController
->applyTransactions($panel, $xactions);
return id(new AphrontRedirectResponse())
->setURI('/'.$panel->getMonogram());
->setURI($this->getPanelRedirectURI($panel));
} catch (PhabricatorApplicationTransactionValidationException $ex) {
$validation_exception = $ex;
@ -144,4 +144,16 @@ final class PhabricatorDashboardPanelEditController
));
}
private function getPanelRedirectURI(PhabricatorDashboardPanel $panel) {
$request = $this->getRequest();
$dashboard_id = $request->getInt('dashboardID');
if ($dashboard_id) {
$uri = $this->getApplicationURI('arrange/'.$dashboard_id.'/');
} else {
$uri = '/'.$panel->getMonogram();
}
return $uri;
}
}

View file

@ -37,7 +37,8 @@ final class PhabricatorDashboardPanelRenderController
->setViewer($viewer)
->setPanel($panel)
->setParentPanelPHIDs($parent_phids)
->setHeaderless($request->getBool('headerless'))
->setHeaderMode($request->getStr('headerMode'))
->setDashboardID($request->getInt('dashboardID'))
->renderPanel();
if ($request->isAjax()) {

View file

@ -0,0 +1,82 @@
<?php
final class PhabricatorDashboardRemovePanelController
extends PhabricatorDashboardController {
private $id;
public function willProcessRequest(array $data) {
$this->id = idx($data, 'id');
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$dashboard = id(new PhabricatorDashboardQuery())
->setViewer($viewer)
->withIDs(array($this->id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$dashboard) {
return new Aphront404Response();
}
$v_panel = $request->getStr('panelPHID');
$panel = id(new PhabricatorDashboardPanelQuery())
->setViewer($viewer)
->withPHIDs(array($v_panel))
->executeOne();
if (!$panel) {
return new Aphront404Response();
}
$redirect_uri = $this->getApplicationURI(
'arrange/'.$dashboard->getID().'/');
$layout_config = $dashboard->getLayoutConfigObject();
if ($request->isFormPost()) {
$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->removePanel($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);
}
$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 %s', $panel->getMonogram()))
->appendChild($form->buildLayoutView())
->addCancelButton($redirect_uri)
->addSubmitButton(pht('Remove Panel'));
}
}

View file

@ -2,19 +2,33 @@
final class PhabricatorDashboardPanelRenderingEngine extends Phobject {
const HEADER_MODE_NORMAL = 'normal';
const HEADER_MODE_NONE = 'none';
const HEADER_MODE_EDIT = 'edit';
private $panel;
private $viewer;
private $enableAsyncRendering;
private $parentPanelPHIDs;
private $headerless;
private $headerMode = self::HEADER_MODE_NORMAL;
private $dashboardID;
public function setHeaderless($headerless) {
$this->headerless = $headerless;
public function setDashboardID($id) {
$this->dashboardID = $id;
return $this;
}
public function getHeaderless() {
return $this->headerless;
public function getDashboardID() {
return $this->dashboardID;
}
public function setHeaderMode($header_mode) {
$this->headerMode = $header_mode;
return $this;
}
public function getHeaderMode() {
return $this->headerMode;
}
/**
@ -39,14 +53,22 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject {
return $this;
}
public function getViewer() {
return $this->viewer;
}
public function setPanel(PhabricatorDashboardPanel $panel) {
$this->panel = $panel;
return $this;
}
public function getPanel() {
return $this->panel;
}
public function renderPanel() {
$panel = $this->panel;
$viewer = $this->viewer;
$panel = $this->getPanel();
$viewer = $this->getViewer();
if (!$panel) {
return $this->renderErrorPanel(
@ -69,11 +91,11 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject {
if ($this->enableAsyncRendering) {
if ($panel_type->shouldRenderAsync()) {
return $this->renderAsyncPanel($panel);
return $this->renderAsyncPanel();
}
}
return $panel_type->renderPanel($viewer, $panel, $this);
return $this->renderNormalPanel($viewer, $panel, $this);
} catch (Exception $ex) {
return $this->renderErrorPanel(
$panel->getName(),
@ -84,47 +106,146 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject {
}
}
private function renderErrorPanel($title, $body) {
if ($this->getHeaderless()) {
return id(new AphrontErrorView())
->setErrors(array($body));
} else {
return id(new PHUIObjectBoxView())
->setHeaderText($title)
->setFormErrors(array($body));
}
private function renderNormalPanel() {
$panel = $this->getPanel();
$panel_type = $panel->getImplementation();
$content = $panel_type->renderPanelContent(
$this->getViewer(),
$panel,
$this);
$header = $this->renderPanelHeader();
return $this->renderPanelDiv(
$content,
$header);
}
private function renderAsyncPanel(PhabricatorDashboardPanel $panel) {
private function renderAsyncPanel() {
$panel = $this->getPanel();
$panel_id = celerity_generate_unique_node_id();
$dashboard_id = $this->getDashboardID();
Javelin::initBehavior(
'dashboard-async-panel',
array(
'panelID' => $panel_id,
'parentPanelPHIDs' => $this->getParentPanelPHIDs(),
'headerless' => $this->getHeaderless(),
'headerMode' => $this->getHeaderMode(),
'dashboardID' => $dashboard_id,
'uri' => '/dashboard/panel/render/'.$panel->getID().'/',
));
$content = pht('Loading...');
$header = $this->renderPanelHeader();
$content = id(new PHUIPropertyListView())
->addTextContent(pht('Loading...'));
if ($this->headerless) {
return phutil_tag(
'div',
array(
'id' => $panel_id,
),
$content);
} else {
return id(new PHUIObjectBoxView())
->addSigil('dashboard-panel')
->setMetadata(array(
'objectPHID' => $panel->getPHID()))
->setHeaderText($panel->getName())
->setID($panel_id)
->appendChild($content);
return $this->renderPanelDiv(
$content,
$header,
$panel_id);
}
private function renderErrorPanel($title, $body) {
switch ($this->getHeaderMode()) {
case self::HEADER_MODE_NONE:
$header = null;
break;
case self::HEADER_MODE_EDIT:
$header = id(new PhabricatorActionHeaderView())
->setHeaderTitle($title)
->setHeaderColor(PhabricatorActionHeaderView::HEADER_RED);
$header = $this->addPanelHeaderActions($header);
break;
case self::HEADER_MODE_NORMAL:
default:
$header = id(new PhabricatorActionHeaderView())
->setHeaderTitle($title)
->setHeaderColor(PhabricatorActionHeaderView::HEADER_RED);
break;
}
return $this->renderPanelDiv(
id(new AphrontErrorView())
->appendChild($body),
$header);
}
private function renderPanelDiv(
$content,
$header = null,
$id = null) {
$panel = $this->getPanel();
if (!$id) {
$id = celerity_generate_unique_node_id();
}
return javelin_tag(
'div',
array(
'id' => $id,
'sigil' => 'dashboard-panel',
'meta' => array(
'objectPHID' => $panel->getPHID()),
'class' => 'dashboard-panel'),
array(
$header,
$content));
}
private function renderPanelHeader() {
$panel = $this->getPanel();
switch ($this->getHeaderMode()) {
case self::HEADER_MODE_NONE:
$header = null;
break;
case self::HEADER_MODE_EDIT:
$header = id(new PhabricatorActionHeaderView())
->setHeaderTitle($panel->getName())
->setHeaderColor(PhabricatorActionHeaderView::HEADER_GREY);
$header = $this->addPanelHeaderActions($header);
break;
case self::HEADER_MODE_NORMAL:
default:
$header = id(new PhabricatorActionHeaderView())
->setHeaderTitle($panel->getName())
->setHeaderColor(PhabricatorActionHeaderView::HEADER_GREY);
break;
}
return $header;
}
private function addPanelHeaderActions(
PhabricatorActionHeaderView $header) {
$panel = $this->getPanel();
$dashboard_id = $this->getDashboardID();
$edit_uri = id(new PhutilURI(
'/dashboard/panel/edit/'.$panel->getID().'/'));
if ($dashboard_id) {
$edit_uri->setQueryParam('dashboardID', $dashboard_id);
}
$action_edit = id(new PHUIIconView())
->setSpriteSheet(PHUIIconView::SPRITE_ACTIONS)
->setSpriteIcon('settings-grey')
->setHref((string) $edit_uri);
$header->addAction($action_edit);
if ($dashboard_id) {
$uri = id(new PhutilURI(
'/dashboard/removepanel/'.$dashboard_id.'/'))
->setQueryParam('panelPHID', $panel->getPHID());
$action_remove = id(new PHUIIconView())
->setSpriteSheet(PHUIIconView::SPRITE_ACTIONS)
->setSpriteIcon('close-grey')
->setHref((string) $uri)
->setWorkflow(true);
$header->addAction($action_remove);
}
return $header;
}
/**

View file

@ -34,6 +34,11 @@ final class PhabricatorDashboardRenderingEngine extends Phobject {
->setID($dashboard_id)
->setFluidlayout(true);
if ($this->arrangeMode) {
$h_mode = PhabricatorDashboardPanelRenderingEngine::HEADER_MODE_EDIT;
} else {
$h_mode = PhabricatorDashboardPanelRenderingEngine::HEADER_MODE_NORMAL;
}
foreach ($panel_grid_locations as $column => $panel_column_locations) {
$panel_phids = $panel_column_locations;
$column_panels = array_select_keys($panels, $panel_phids);
@ -42,8 +47,10 @@ final class PhabricatorDashboardRenderingEngine extends Phobject {
$column_result[] = id(new PhabricatorDashboardPanelRenderingEngine())
->setViewer($viewer)
->setPanel($panel)
->setDashboardID($dashboard->getID())
->setEnableAsyncRendering(true)
->setParentPanelPHIDs(array())
->setHeaderMode($h_mode)
->renderPanel();
}
$column_class = $layout_config->getColumnClass(

View file

@ -32,6 +32,24 @@ final class PhabricatorDashboardLayoutConfig {
return $this->panelLocations;
}
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:

View file

@ -40,26 +40,9 @@ abstract class PhabricatorDashboardPanelType extends Phobject {
return $types;
}
public function renderPanel(
PhabricatorUser $viewer,
PhabricatorDashboardPanel $panel,
PhabricatorDashboardPanelRenderingEngine $engine) {
$content = $this->renderPanelContent($viewer, $panel, $engine);
if ($engine->getHeaderless()) {
return $content;
}
return id(new PHUIObjectBoxView())
->addSigil('dashboard-panel')
->setMetadata(array(
'objectPHID' => $panel->getPHID()))
->setHeaderText($panel->getName())
->appendChild($content);
}
protected function renderPanelContent(
public function renderPanelContent(
PhabricatorUser $viewer,
PhabricatorDashboardPanel $panel,
PhabricatorDashboardPanelRenderingEngine $engine) {

View file

@ -30,7 +30,7 @@ final class PhabricatorDashboardPanelTypeQuery
);
}
protected function renderPanelContent(
public function renderPanelContent(
PhabricatorUser $viewer,
PhabricatorDashboardPanel $panel,
PhabricatorDashboardPanelRenderingEngine $engine) {

View file

@ -25,7 +25,7 @@ final class PhabricatorDashboardPanelTypeTabs
);
}
protected function renderPanelContent(
public function renderPanelContent(
PhabricatorUser $viewer,
PhabricatorDashboardPanel $panel,
PhabricatorDashboardPanelRenderingEngine $engine) {
@ -81,6 +81,7 @@ final class PhabricatorDashboardPanelTypeTabs
$parent_phids[] = $panel->getPHID();
$content = array();
$no_headers = PhabricatorDashboardPanelRenderingEngine::HEADER_MODE_NONE;
foreach ($config as $idx => $tab_spec) {
$panel_id = idx($tab_spec, 'panelID');
$panel = idx($panels, $panel_id);
@ -91,7 +92,7 @@ final class PhabricatorDashboardPanelTypeTabs
->setEnableAsyncRendering(true)
->setParentPanelPHIDs($parent_phids)
->setPanel($panel)
->setHeaderless(true)
->setHeaderMode($no_headers)
->renderPanel();
} else {
$panel_content = pht('(Invalid Panel)');

View file

@ -26,7 +26,7 @@ final class PhabricatorDashboardPanelTypeText
);
}
protected function renderPanelContent(
public function renderPanelContent(
PhabricatorUser $viewer,
PhabricatorDashboardPanel $panel,
PhabricatorDashboardPanelRenderingEngine $engine) {

View file

@ -17,9 +17,15 @@
width: 66.66%;
}
.aphront-multi-column-fluid
.aphront-multi-column-column-outer
.aphront-multi-column-column .dashboard-panel {
margin: 16px 16px 0px 16px;
}
.aphront-multi-column-fluid
.aphront-multi-column-column-outer.grippable
.aphront-multi-column-column .phui-object-box {
.aphront-multi-column-column .dashboard-panel {
cursor: move;
}
@ -57,3 +63,9 @@
.dashboard-panel-placeholder {
display: none;
}
.aphront-multi-column-fluid
.aphront-multi-column-column-outer
.aphront-multi-column-column .aphront-error-view {
margin: 0;
}

View file

@ -11,7 +11,8 @@ JX.behavior('dashboard-async-panel', function(config) {
var data = {
parentPanelPHIDs: config.parentPanelPHIDs.join(','),
headerless: config.headerless ? 1 : 0
headerMode: config.headerMode,
dashboardID: config.dashboardID
};
new JX.Workflow(config.uri)