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

Replace old hard-coded URI-based "changes saved" jank with new overgeneralized cookie-based "changes saved" jank

Summary:
Ref T13515. Settings currently has some highly specialized code for rendering "Changes saved." messages. The "saved" state is communicated across a redirect-after-POST by adding `/saved/` to the end of the URI.

This isn't great. It needs a lot of moving pieces, including special accommodations in routing rules. It's user-visible. It has the wrong behavior if you reload the page or navigate directly to the "saved" URI.

Try this scheme, which is also pretty sketchy but seems like an upgrade on the balance:

  - Set a cookie on the redirect which identifies the form we just saved.
  - On page startup: if this cookie exists, save the value and clear it.
  - If the current page started with a cookie identifying the form on the page, treat the page as a "saved" page.

This supports passing a small amount of state across the redirect-after-POST flow, and when you reload the page it doesn't keep the message around. Applications don't need to coordinate it, either. Seems somewhat cleaner?

Test Plan: In Firefox, Safari, and Chrome: saved settings, saw a "Saved changes" banner without any URI junk. Reloaded page, saw banner vanish properly.

Maniphest Tasks: T13515

Differential Revision: https://secure.phabricator.com/D21144
This commit is contained in:
epriestley 2020-04-18 14:46:55 -07:00
parent 3984c14260
commit 7a79131bf2
12 changed files with 128 additions and 28 deletions

View file

@ -3251,6 +3251,7 @@ phutil_register_library_map(array(
'PhabricatorEditEngineMFAInterface' => 'applications/transactions/editengine/PhabricatorEditEngineMFAInterface.php', 'PhabricatorEditEngineMFAInterface' => 'applications/transactions/editengine/PhabricatorEditEngineMFAInterface.php',
'PhabricatorEditEngineNameTransaction' => 'applications/transactions/xaction/PhabricatorEditEngineNameTransaction.php', 'PhabricatorEditEngineNameTransaction' => 'applications/transactions/xaction/PhabricatorEditEngineNameTransaction.php',
'PhabricatorEditEngineOrderTransaction' => 'applications/transactions/xaction/PhabricatorEditEngineOrderTransaction.php', 'PhabricatorEditEngineOrderTransaction' => 'applications/transactions/xaction/PhabricatorEditEngineOrderTransaction.php',
'PhabricatorEditEnginePageState' => 'applications/transactions/editengine/PhabricatorEditEnginePageState.php',
'PhabricatorEditEnginePointsCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEnginePointsCommentAction.php', 'PhabricatorEditEnginePointsCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEnginePointsCommentAction.php',
'PhabricatorEditEnginePreambleTransaction' => 'applications/transactions/xaction/PhabricatorEditEnginePreambleTransaction.php', 'PhabricatorEditEnginePreambleTransaction' => 'applications/transactions/xaction/PhabricatorEditEnginePreambleTransaction.php',
'PhabricatorEditEngineProfileMenuItem' => 'applications/search/menuitem/PhabricatorEditEngineProfileMenuItem.php', 'PhabricatorEditEngineProfileMenuItem' => 'applications/search/menuitem/PhabricatorEditEngineProfileMenuItem.php',
@ -9714,6 +9715,7 @@ phutil_register_library_map(array(
'PhabricatorEditEngineMFAEngine' => 'Phobject', 'PhabricatorEditEngineMFAEngine' => 'Phobject',
'PhabricatorEditEngineNameTransaction' => 'PhabricatorEditEngineTransactionType', 'PhabricatorEditEngineNameTransaction' => 'PhabricatorEditEngineTransactionType',
'PhabricatorEditEngineOrderTransaction' => 'PhabricatorEditEngineTransactionType', 'PhabricatorEditEngineOrderTransaction' => 'PhabricatorEditEngineTransactionType',
'PhabricatorEditEnginePageState' => 'Phobject',
'PhabricatorEditEnginePointsCommentAction' => 'PhabricatorEditEngineCommentAction', 'PhabricatorEditEnginePointsCommentAction' => 'PhabricatorEditEngineCommentAction',
'PhabricatorEditEnginePreambleTransaction' => 'PhabricatorEditEngineTransactionType', 'PhabricatorEditEnginePreambleTransaction' => 'PhabricatorEditEngineTransactionType',
'PhabricatorEditEngineProfileMenuItem' => 'PhabricatorProfileMenuItem', 'PhabricatorEditEngineProfileMenuItem' => 'PhabricatorProfileMenuItem',

View file

@ -30,6 +30,7 @@ final class AphrontRequest extends Phobject {
private $controller; private $controller;
private $uriData = array(); private $uriData = array();
private $cookiePrefix; private $cookiePrefix;
private $submitKey;
public function __construct($host, $path) { public function __construct($host, $path) {
$this->host = $host; $this->host = $host;
@ -914,5 +915,19 @@ final class AphrontRequest extends Phobject {
return $future; return $future;
} }
public function updateEphemeralCookies() {
$submit_cookie = PhabricatorCookies::COOKIE_SUBMIT;
$submit_key = $this->getCookie($submit_cookie);
if (strlen($submit_key)) {
$this->clearCookie($submit_cookie);
$this->submitKey = $submit_key;
}
}
public function getSubmitKey() {
return $this->submitKey;
}
} }

View file

@ -27,6 +27,8 @@ final class AphrontApplicationConfiguration
$request->setApplicationConfiguration($this); $request->setApplicationConfiguration($this);
$request->setCookiePrefix($cookie_prefix); $request->setCookiePrefix($cookie_prefix);
$request->updateEphemeralCookies();
return $request; return $request;
} }

View file

@ -73,7 +73,7 @@ final class PhabricatorAuthApplication extends PhabricatorApplication {
'session/downgrade/' 'session/downgrade/'
=> 'PhabricatorAuthDowngradeSessionController', => 'PhabricatorAuthDowngradeSessionController',
'enroll/' => array( 'enroll/' => array(
'(?:(?P<pageKey>[^/]+)/)?(?:(?P<formSaved>saved)/)?' '(?:(?P<pageKey>[^/]+)/)?'
=> 'PhabricatorAuthNeedsMultiFactorController', => 'PhabricatorAuthNeedsMultiFactorController',
), ),
'sshkey/' => array( 'sshkey/' => array(

View file

@ -63,6 +63,13 @@ final class PhabricatorCookies extends Phobject {
const COOKIE_INVITE = 'invite'; const COOKIE_INVITE = 'invite';
/**
* Stores a workflow completion across a redirect-after-POST following a
* form submission. This can be used to show "Changes Saved" messages.
*/
const COOKIE_SUBMIT = 'phfrm';
/* -( Client ID Cookie )--------------------------------------------------- */ /* -( Client ID Cookie )--------------------------------------------------- */

View file

@ -23,7 +23,7 @@ final class PhabricatorSettingsApplication extends PhabricatorApplication {
} }
public function getRoutes() { public function getRoutes() {
$panel_pattern = '(?:page/(?P<pageKey>[^/]+)/(?:(?P<formSaved>saved)/)?)?'; $panel_pattern = '(?:page/(?P<pageKey>[^/]+)/)?';
return array( return array(
'/settings/' => array( '/settings/' => array(

View file

@ -128,10 +128,6 @@ final class PhabricatorSettingsEditEngine
return PhabricatorPolicies::POLICY_ADMIN; return PhabricatorPolicies::POLICY_ADMIN;
} }
public function getEffectiveObjectEditDoneURI($object) {
return parent::getEffectiveObjectViewURI($object).'saved/';
}
public function getEffectiveObjectEditCancelURI($object) { public function getEffectiveObjectEditCancelURI($object) {
if (!$object->getUser()) { if (!$object->getUser()) {
return '/settings/'; return '/settings/';
@ -253,4 +249,16 @@ final class PhabricatorSettingsEditEngine
return parent::getValidationExceptionShortMessage($ex, $field); return parent::getValidationExceptionShortMessage($ex, $field);
} }
protected function newEditFormHeadContent(
PhabricatorEditEnginePageState $state) {
if ($state->getIsSave()) {
return id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
->appendChild(pht('Changes saved.'));
}
return null;
}
} }

View file

@ -138,7 +138,6 @@ final class PhabricatorEmailPreferencesSettingsPanel
$form_box = id(new PHUIObjectBoxView()) $form_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Email Preferences')) ->setHeaderText(pht('Email Preferences'))
->setFormSaved($request->getStr('saved'))
->setFormErrors($errors) ->setFormErrors($errors)
->setBackground(PHUIObjectBoxView::WHITE_CONFIG) ->setBackground(PHUIObjectBoxView::WHITE_CONFIG)
->setForm($form); ->setForm($form);

View file

@ -206,7 +206,6 @@ final class PhabricatorPasswordSettingsPanel extends PhabricatorSettingsPanel {
$algo_box = $this->newBox(pht('Password Algorithms'), $properties); $algo_box = $this->newBox(pht('Password Algorithms'), $properties);
$form_box = id(new PHUIObjectBoxView()) $form_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Change Password')) ->setHeaderText(pht('Change Password'))
->setFormSaved($request->getStr('saved'))
->setFormErrors($errors) ->setFormErrors($errors)
->setBackground(PHUIObjectBoxView::WHITE_CONFIG) ->setBackground(PHUIObjectBoxView::WHITE_CONFIG)
->setForm($form); ->setForm($form);

View file

@ -1036,9 +1036,13 @@ abstract class PhabricatorEditEngine
$fields = $this->buildEditFields($object); $fields = $this->buildEditFields($object);
$template = $object->getApplicationTransactionTemplate(); $template = $object->getApplicationTransactionTemplate();
$page_state = new PhabricatorEditEnginePageState();
if ($this->getIsCreate()) { if ($this->getIsCreate()) {
$cancel_uri = $this->getObjectCreateCancelURI($object); $cancel_uri = $this->getObjectCreateCancelURI($object);
$submit_button = $this->getObjectCreateButtonText($object); $submit_button = $this->getObjectCreateButtonText($object);
$page_state->setIsCreate(true);
} else { } else {
$cancel_uri = $this->getEffectiveObjectEditCancelURI($object); $cancel_uri = $this->getEffectiveObjectEditCancelURI($object);
$submit_button = $this->getObjectEditButtonText($object); $submit_button = $this->getObjectEditButtonText($object);
@ -1069,6 +1073,8 @@ abstract class PhabricatorEditEngine
$validation_exception = null; $validation_exception = null;
if ($request->isFormOrHisecPost() && $request->getBool('editEngine')) { if ($request->isFormOrHisecPost() && $request->getBool('editEngine')) {
$page_state->setIsSubmit(true);
$submit_fields = $fields; $submit_fields = $fields;
foreach ($submit_fields as $key => $field) { foreach ($submit_fields as $key => $field) {
@ -1154,6 +1160,8 @@ abstract class PhabricatorEditEngine
$field->setControlError($message); $field->setControlError($message);
} }
$page_state->setIsError(true);
} }
} else { } else {
if ($this->getIsCreate()) { if ($this->getIsCreate()) {
@ -1252,6 +1260,17 @@ abstract class PhabricatorEditEngine
$box_header->addActionLink($action_button); $box_header->addActionLink($action_button);
} }
$request_submit_key = $request->getSubmitKey();
$engine_submit_key = $this->getEditEngineSubmitKey();
if ($request_submit_key === $engine_submit_key) {
$page_state->setIsSubmit(true);
$page_state->setIsSave(true);
}
$head = $this->newEditFormHeadContent($page_state);
$tail = $this->newEditFormTailContent($page_state);
$box = id(new PHUIObjectBoxView()) $box = id(new PHUIObjectBoxView())
->setUser($viewer) ->setUser($viewer)
->setHeader($box_header) ->setHeader($box_header)
@ -1259,14 +1278,11 @@ abstract class PhabricatorEditEngine
->setBackground(PHUIObjectBoxView::WHITE_CONFIG) ->setBackground(PHUIObjectBoxView::WHITE_CONFIG)
->appendChild($form); ->appendChild($form);
// This is fairly questionable, but in use by Settings.
if ($request->getURIData('formSaved')) {
$box->setFormSaved(true);
}
$content = array( $content = array(
$head,
$box, $box,
$previews, $previews,
$tail,
); );
$view = new PHUITwoColumnView(); $view = new PHUITwoColumnView();
@ -1291,14 +1307,34 @@ abstract class PhabricatorEditEngine
return $page; return $page;
} }
protected function newEditFormHeadContent(
PhabricatorEditEnginePageState $state) {
return null;
}
protected function newEditFormTailContent(
PhabricatorEditEnginePageState $state) {
return null;
}
protected function newEditResponse( protected function newEditResponse(
AphrontRequest $request, AphrontRequest $request,
$object, $object,
array $xactions) { array $xactions) {
$submit_cookie = PhabricatorCookies::COOKIE_SUBMIT;
$submit_key = $this->getEditEngineSubmitKey();
$request->setTemporaryCookie($submit_cookie, $submit_key);
return id(new AphrontRedirectResponse()) return id(new AphrontRedirectResponse())
->setURI($this->getEffectiveObjectEditDoneURI($object)); ->setURI($this->getEffectiveObjectEditDoneURI($object));
} }
private function getEditEngineSubmitKey() {
return 'edit-engine/'.$this->getEngineKey();
}
private function buildEditForm($object, array $fields) { private function buildEditForm($object, array $fields) {
$viewer = $this->getViewer(); $viewer = $this->getViewer();
$controller = $this->getController(); $controller = $this->getController();

View file

@ -0,0 +1,47 @@
<?php
final class PhabricatorEditEnginePageState
extends Phobject {
private $isCreate;
private $isSubmit;
private $isError;
private $isSave;
public function setIsCreate($is_create) {
$this->isCreate = $is_create;
return $this;
}
public function getIsCreate() {
return $this->isCreate;
}
public function setIsSubmit($is_submit) {
$this->isSubmit = $is_submit;
return $this;
}
public function getIsSubmit() {
return $this->isSubmit;
}
public function setIsError($is_error) {
$this->isError = $is_error;
return $this;
}
public function getIsError() {
return $this->isError;
}
public function setIsSave($is_save) {
$this->isSave = $is_save;
return $this;
}
public function getIsSave() {
return $this->isSave;
}
}

View file

@ -7,7 +7,6 @@ final class PHUIObjectBoxView extends AphrontTagView {
private $background; private $background;
private $tabGroups = array(); private $tabGroups = array();
private $formErrors = null; private $formErrors = null;
private $formSaved = false;
private $infoView; private $infoView;
private $form; private $form;
private $validationException; private $validationException;
@ -75,19 +74,6 @@ final class PHUIObjectBoxView extends AphrontTagView {
return $this; return $this;
} }
public function setFormSaved($saved, $text = null) {
if (!$text) {
$text = pht('Changes saved.');
}
if ($saved) {
$save = id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
->appendChild($text);
$this->formSaved = $save;
}
return $this;
}
public function addTabGroup(PHUITabGroupView $view) { public function addTabGroup(PHUITabGroupView $view) {
$this->tabGroups[] = $view; $this->tabGroups[] = $view;
return $this; return $this;
@ -324,7 +310,6 @@ final class PHUIObjectBoxView extends AphrontTagView {
$header, $header,
$this->infoView, $this->infoView,
$this->formErrors, $this->formErrors,
$this->formSaved,
$exception_errors, $exception_errors,
$this->form, $this->form,
$this->tabGroups, $this->tabGroups,