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

Allow users to "Edit a Copy" when trying to edit a panel they don't own

Summary:
Fixes T5167. When clicking "Edit" on a dashboard panel you don't own, the UI now allows you to make a copy instead.

As a bonus, fixes T5259.

Test Plan: See screenshots.

Reviewers: chad

Reviewed By: chad

Subscribers: epriestley

Maniphest Tasks: T5259, T5167

Differential Revision: https://secure.phabricator.com/D9505
This commit is contained in:
epriestley 2014-06-12 15:14:47 -07:00
parent 6f4ebcb8d9
commit 408d71cdd3
6 changed files with 161 additions and 19 deletions

View file

@ -8,7 +8,7 @@ return array(
'names' => 'names' =>
array( array(
'core.pkg.css' => 'c76b553b', 'core.pkg.css' => 'c76b553b',
'core.pkg.js' => '0627d27e', 'core.pkg.js' => '8335fe3f',
'darkconsole.pkg.js' => 'ca8671ce', 'darkconsole.pkg.js' => 'ca8671ce',
'differential.pkg.css' => '4a93db37', 'differential.pkg.css' => '4a93db37',
'differential.pkg.js' => 'eca39a2c', 'differential.pkg.js' => 'eca39a2c',
@ -51,7 +51,7 @@ return array(
'rsrc/css/application/conpherence/widget-pane.css' => 'bf275a6c', 'rsrc/css/application/conpherence/widget-pane.css' => 'bf275a6c',
'rsrc/css/application/contentsource/content-source-view.css' => '4b8b05d4', 'rsrc/css/application/contentsource/content-source-view.css' => '4b8b05d4',
'rsrc/css/application/countdown/timer.css' => '86b7b0a0', 'rsrc/css/application/countdown/timer.css' => '86b7b0a0',
'rsrc/css/application/dashboard/dashboard.css' => 'f0092e3e', 'rsrc/css/application/dashboard/dashboard.css' => '0594a469',
'rsrc/css/application/diff/inline-comment-summary.css' => '8cfd34e8', 'rsrc/css/application/diff/inline-comment-summary.css' => '8cfd34e8',
'rsrc/css/application/differential/add-comment.css' => 'c478bcaa', 'rsrc/css/application/differential/add-comment.css' => 'c478bcaa',
'rsrc/css/application/differential/changeset-view.css' => 'ff8eacf8', 'rsrc/css/application/differential/changeset-view.css' => 'ff8eacf8',
@ -423,7 +423,7 @@ return array(
'rsrc/js/application/uiexample/notification-example.js' => 'c51a6616', 'rsrc/js/application/uiexample/notification-example.js' => 'c51a6616',
'rsrc/js/core/Busy.js' => '6453c869', 'rsrc/js/core/Busy.js' => '6453c869',
'rsrc/js/core/DragAndDropFileUpload.js' => 'ae6abfba', 'rsrc/js/core/DragAndDropFileUpload.js' => 'ae6abfba',
'rsrc/js/core/DraggableList.js' => '1681c4d4', 'rsrc/js/core/DraggableList.js' => '109e2a87',
'rsrc/js/core/FileUpload.js' => 'a4ae61bf', 'rsrc/js/core/FileUpload.js' => 'a4ae61bf',
'rsrc/js/core/Hovercard.js' => '4f344388', 'rsrc/js/core/Hovercard.js' => '4f344388',
'rsrc/js/core/KeyboardShortcut.js' => '1ae869f2', 'rsrc/js/core/KeyboardShortcut.js' => '1ae869f2',
@ -694,9 +694,9 @@ return array(
'phabricator-core-css' => '40151074', 'phabricator-core-css' => '40151074',
'phabricator-countdown-css' => '86b7b0a0', 'phabricator-countdown-css' => '86b7b0a0',
'phabricator-crumbs-view-css' => '7fbf25b8', 'phabricator-crumbs-view-css' => '7fbf25b8',
'phabricator-dashboard-css' => 'f0092e3e', 'phabricator-dashboard-css' => '0594a469',
'phabricator-drag-and-drop-file-upload' => 'ae6abfba', 'phabricator-drag-and-drop-file-upload' => 'ae6abfba',
'phabricator-draggable-list' => '1681c4d4', 'phabricator-draggable-list' => '109e2a87',
'phabricator-fatal-config-template-css' => '25d446d6', 'phabricator-fatal-config-template-css' => '25d446d6',
'phabricator-feed-css' => 'dd43ce00', 'phabricator-feed-css' => 'dd43ce00',
'phabricator-file-upload' => 'a4ae61bf', 'phabricator-file-upload' => 'a4ae61bf',
@ -913,6 +913,15 @@ return array(
1 => 'javelin-uri', 1 => 'javelin-uri',
2 => 'javelin-install', 2 => 'javelin-install',
), ),
'109e2a87' =>
array(
0 => 'javelin-install',
1 => 'javelin-dom',
2 => 'javelin-stratcom',
3 => 'javelin-util',
4 => 'javelin-vector',
5 => 'javelin-magical-init',
),
'127f2018' => '127f2018' =>
array( array(
0 => 'javelin-behavior', 0 => 'javelin-behavior',
@ -922,15 +931,6 @@ return array(
4 => 'javelin-util', 4 => 'javelin-util',
5 => 'phabricator-shaped-request', 5 => 'phabricator-shaped-request',
), ),
'1681c4d4' =>
array(
0 => 'javelin-install',
1 => 'javelin-dom',
2 => 'javelin-stratcom',
3 => 'javelin-util',
4 => 'javelin-vector',
5 => 'javelin-magical-init',
),
'1693a296' => '1693a296' =>
array( array(
0 => 'javelin-behavior', 0 => 'javelin-behavior',

View file

@ -15,6 +15,10 @@ final class PhabricatorDashboardManageController
$id = $this->id; $id = $this->id;
$dashboard_uri = $this->getApplicationURI('view/'.$id.'/'); $dashboard_uri = $this->getApplicationURI('view/'.$id.'/');
// TODO: This UI should drop a lot of capabilities if the user can't
// edit the dashboard, but we should still let them in for "Install" and
// "View History".
$dashboard = id(new PhabricatorDashboardQuery()) $dashboard = id(new PhabricatorDashboardQuery())
->setViewer($viewer) ->setViewer($viewer)
->withIDs(array($this->id)) ->withIDs(array($this->id))

View file

@ -38,19 +38,45 @@ final class PhabricatorDashboardPanelEditController
if ($this->id) { if ($this->id) {
$is_create = false; $is_create = false;
if ($dashboard) {
$capabilities = array(
PhabricatorPolicyCapability::CAN_VIEW,
);
} else {
$capabilities = array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
$panel = id(new PhabricatorDashboardPanelQuery()) $panel = id(new PhabricatorDashboardPanelQuery())
->setViewer($viewer) ->setViewer($viewer)
->withIDs(array($this->id)) ->withIDs(array($this->id))
->requireCapabilities( ->requireCapabilities($capabilities)
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne(); ->executeOne();
if (!$panel) { if (!$panel) {
return new Aphront404Response(); return new Aphront404Response();
} }
if ($dashboard) {
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$panel,
PhabricatorPolicyCapability::CAN_EDIT);
if (!$can_edit) {
if ($request->isFormPost() && $request->getBool('copy')) {
$panel = $this->copyPanel(
$request,
$dashboard,
$panel);
} else {
return $this->processPanelCloneRequest(
$request,
$dashboard,
$panel);
}
}
}
} else { } else {
$is_create = true; $is_create = true;
@ -161,6 +187,10 @@ final class PhabricatorDashboardPanelEditController
} }
} }
// NOTE: We're setting the submit URI explicitly because we need to edit
// a different panel if we just cloned the original panel.
$submit_uri = $this->getApplicationURI('panel/edit/'.$panel->getID().'/');
$policies = id(new PhabricatorPolicyQuery()) $policies = id(new PhabricatorPolicyQuery())
->setViewer($viewer) ->setViewer($viewer)
->setObject($panel) ->setObject($panel)
@ -168,6 +198,7 @@ final class PhabricatorDashboardPanelEditController
$form = id(new AphrontFormView()) $form = id(new AphrontFormView())
->setUser($viewer) ->setUser($viewer)
->setAction($submit_uri)
->addHiddenInput('edit', true) ->addHiddenInput('edit', true)
->addHiddenInput('dashboardID', $request->getInt('dashboardID')) ->addHiddenInput('dashboardID', $request->getInt('dashboardID'))
->addHiddenInput('column', $request->getInt('column')) ->addHiddenInput('column', $request->getInt('column'))
@ -209,6 +240,7 @@ final class PhabricatorDashboardPanelEditController
if ($request->isAjax()) { if ($request->isAjax()) {
return $this->newDialog() return $this->newDialog()
->setTitle($header) ->setTitle($header)
->setSubmitURI($submit_uri)
->setWidth(AphrontDialogView::WIDTH_FORM) ->setWidth(AphrontDialogView::WIDTH_FORM)
->setValidationException($validation_exception) ->setValidationException($validation_exception)
->appendChild($form->buildLayoutView()) ->appendChild($form->buildLayoutView())
@ -317,4 +349,79 @@ final class PhabricatorDashboardPanelEditController
)); ));
} }
private function processPanelCloneRequest(
AphrontRequest $request,
PhabricatorDashboard $dashboard,
PhabricatorDashboardPanel $panel) {
$viewer = $request->getUser();
$manage_uri = $this->getApplicationURI('manage/'.$dashboard->getID().'/');
return $this->newDialog()
->setTitle(pht('Copy Panel?'))
->addHiddenInput('copy', true)
->addHiddenInput('dashboardID', $request->getInt('dashboardID'))
->addHiddenInput('column', $request->getInt('column'))
->appendParagraph(
pht(
'You do not have permission to edit this dashboard panel, but you '.
'can make a copy and edit that instead. If you choose to copy the '.
'panel, the original will be replaced with the new copy on this '.
'dashboard.'))
->appendParagraph(
pht(
'Do you want to make a copy of this panel?'))
->addCancelButton($manage_uri)
->addSubmitButton(pht('Copy Panel'));
}
private function copyPanel(
AphrontRequest $request,
PhabricatorDashboard $dashboard,
PhabricatorDashboardPanel $panel) {
$viewer = $request->getUser();
$copy = PhabricatorDashboardPanel::initializeNewPanel($viewer);
$copy = PhabricatorDashboardPanel::copyPanel($copy, $panel);
$copy->openTransaction();
$copy->save();
// TODO: This should record a transaction on the panel copy, too.
$xactions = array();
$xactions[] = id(new PhabricatorDashboardTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
->setMetadataValue(
'edge:type',
PhabricatorEdgeConfig::TYPE_DASHBOARD_HAS_PANEL)
->setNewValue(
array(
'+' => array(
$copy->getPHID() => $copy->getPHID(),
),
'-' => array(
$panel->getPHID() => $panel->getPHID(),
),
));
$layout_config = $dashboard->getLayoutConfigObject();
$layout_config->replacePanel($panel->getPHID(), $copy->getPHID());
$dashboard->setLayoutConfigFromObject($layout_config);
$dashboard->save();
$editor = id(new PhabricatorDashboardTransactionEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnMissingFields(true)
->setContinueOnNoEffect(true)
->applyTransactions($dashboard, $xactions);
$copy->saveTransaction();
return $copy;
}
} }

View file

@ -32,6 +32,18 @@ final class PhabricatorDashboardLayoutConfig {
return $this->panelLocations; 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) { public function removePanel($panel_phid) {
$panel_location_grid = $this->getPanelLocations(); $panel_location_grid = $this->getPanelLocations();
foreach ($panel_location_grid as $column => $panel_columns) { foreach ($panel_location_grid as $column => $panel_columns) {

View file

@ -24,6 +24,14 @@ final class PhabricatorDashboardPanel
->setEditPolicy($actor->getPHID()); ->setEditPolicy($actor->getPHID());
} }
public static function copyPanel($dst, $src) {
$dst->name = $src->name;
$dst->panelType = $src->panelType;
$dst->properties = $src->properties;
return $dst;
}
public function getConfiguration() { public function getConfiguration() {
return array( return array(
self::CONFIG_AUX_PHID => true, self::CONFIG_AUX_PHID => true,

View file

@ -151,6 +151,17 @@ JX.install('DraggableList', {
return; return;
} }
if (e.getNode('tag:a')) {
// Never start a drag if we're somewhere inside an <a> tag. This makes
// links unclickable in Firefox.
return;
}
if (JX.Stratcom.pass()) {
// Let other handlers deal with this event before we do.
return;
}
e.kill(); e.kill();
this._dragging = e.getNode(this._sigil); this._dragging = e.getNode(this._sigil);