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:
parent
6f4ebcb8d9
commit
408d71cdd3
6 changed files with 161 additions and 19 deletions
|
@ -8,7 +8,7 @@ return array(
|
|||
'names' =>
|
||||
array(
|
||||
'core.pkg.css' => 'c76b553b',
|
||||
'core.pkg.js' => '0627d27e',
|
||||
'core.pkg.js' => '8335fe3f',
|
||||
'darkconsole.pkg.js' => 'ca8671ce',
|
||||
'differential.pkg.css' => '4a93db37',
|
||||
'differential.pkg.js' => 'eca39a2c',
|
||||
|
@ -51,7 +51,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' => 'f0092e3e',
|
||||
'rsrc/css/application/dashboard/dashboard.css' => '0594a469',
|
||||
'rsrc/css/application/diff/inline-comment-summary.css' => '8cfd34e8',
|
||||
'rsrc/css/application/differential/add-comment.css' => 'c478bcaa',
|
||||
'rsrc/css/application/differential/changeset-view.css' => 'ff8eacf8',
|
||||
|
@ -423,7 +423,7 @@ return array(
|
|||
'rsrc/js/application/uiexample/notification-example.js' => 'c51a6616',
|
||||
'rsrc/js/core/Busy.js' => '6453c869',
|
||||
'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/Hovercard.js' => '4f344388',
|
||||
'rsrc/js/core/KeyboardShortcut.js' => '1ae869f2',
|
||||
|
@ -694,9 +694,9 @@ return array(
|
|||
'phabricator-core-css' => '40151074',
|
||||
'phabricator-countdown-css' => '86b7b0a0',
|
||||
'phabricator-crumbs-view-css' => '7fbf25b8',
|
||||
'phabricator-dashboard-css' => 'f0092e3e',
|
||||
'phabricator-dashboard-css' => '0594a469',
|
||||
'phabricator-drag-and-drop-file-upload' => 'ae6abfba',
|
||||
'phabricator-draggable-list' => '1681c4d4',
|
||||
'phabricator-draggable-list' => '109e2a87',
|
||||
'phabricator-fatal-config-template-css' => '25d446d6',
|
||||
'phabricator-feed-css' => 'dd43ce00',
|
||||
'phabricator-file-upload' => 'a4ae61bf',
|
||||
|
@ -913,6 +913,15 @@ return array(
|
|||
1 => 'javelin-uri',
|
||||
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' =>
|
||||
array(
|
||||
0 => 'javelin-behavior',
|
||||
|
@ -922,15 +931,6 @@ return array(
|
|||
4 => 'javelin-util',
|
||||
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' =>
|
||||
array(
|
||||
0 => 'javelin-behavior',
|
||||
|
|
|
@ -15,6 +15,10 @@ final class PhabricatorDashboardManageController
|
|||
$id = $this->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())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($this->id))
|
||||
|
|
|
@ -38,19 +38,45 @@ final class PhabricatorDashboardPanelEditController
|
|||
if ($this->id) {
|
||||
$is_create = false;
|
||||
|
||||
if ($dashboard) {
|
||||
$capabilities = array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
);
|
||||
} else {
|
||||
$capabilities = array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
);
|
||||
}
|
||||
|
||||
$panel = id(new PhabricatorDashboardPanelQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($this->id))
|
||||
->requireCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
))
|
||||
->requireCapabilities($capabilities)
|
||||
->executeOne();
|
||||
if (!$panel) {
|
||||
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 {
|
||||
$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())
|
||||
->setViewer($viewer)
|
||||
->setObject($panel)
|
||||
|
@ -168,6 +198,7 @@ final class PhabricatorDashboardPanelEditController
|
|||
|
||||
$form = id(new AphrontFormView())
|
||||
->setUser($viewer)
|
||||
->setAction($submit_uri)
|
||||
->addHiddenInput('edit', true)
|
||||
->addHiddenInput('dashboardID', $request->getInt('dashboardID'))
|
||||
->addHiddenInput('column', $request->getInt('column'))
|
||||
|
@ -209,6 +240,7 @@ final class PhabricatorDashboardPanelEditController
|
|||
if ($request->isAjax()) {
|
||||
return $this->newDialog()
|
||||
->setTitle($header)
|
||||
->setSubmitURI($submit_uri)
|
||||
->setWidth(AphrontDialogView::WIDTH_FORM)
|
||||
->setValidationException($validation_exception)
|
||||
->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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -32,6 +32,18 @@ final class PhabricatorDashboardLayoutConfig {
|
|||
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) {
|
||||
|
|
|
@ -24,6 +24,14 @@ final class PhabricatorDashboardPanel
|
|||
->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() {
|
||||
return array(
|
||||
self::CONFIG_AUX_PHID => true,
|
||||
|
|
|
@ -151,6 +151,17 @@ JX.install('DraggableList', {
|
|||
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();
|
||||
|
||||
this._dragging = e.getNode(this._sigil);
|
||||
|
|
Loading…
Reference in a new issue