Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent data loss on save (entries and sections) #2659

Merged
merged 4 commits into from
Jun 14, 2017

Conversation

nitriques
Copy link
Member

We add a timestamp check before saving records.

When saving a section, if the data changes between the render of the
page and the form post, it can lead to data loss if a field was added.
The SQL will crash because it assumes the fields did not change.

When saving and entry, the SQL was ok, but still, it can lead to data
loss.

At both places, the fix provided here makes sure we are editing the
lastest version of the record.

cc @brendo @michael-e

Copy link
Member

@brendo brendo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I'm assuming the position is that this protection will not be enabled for entries saved from Event's? This is purely a backend only feature to help in scenarios where there are multiple authors?

*/
public function __construct($table)
{
assert($table, 'Table cannot be falsy');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't really rely on this for production...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why ? You would prefer to throw ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The behaviour of assert depends on php configuration values. The idea is usually that development code will throw errors, but production instances optimise away the calls.

I'd prefer regular throwing in this scenario.

Copy link
Member Author

@nitriques nitriques Apr 26, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d922795. Thanks!

$defaults = array();
$defaults['creation_date'] = $defaults['modification_date'] = DateTimeObj::get('Y-m-d H:i:s');
$defaults['creation_date_gmt'] = $defaults['modification_date_gmt'] = DateTimeObj::getGMT('Y-m-d H:i:s');
$defaults['author_id'] = '1';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noticed below it's 1, any reason for a string here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think both should become integers.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d1d7ddf

@jonmifsud
Copy link
Member

@nitriques nice touch was looking at this from an extension perspective to provide a locking functionality showing that another user is editing.

From what I can see however (code inspection) there is no way someone can confirm an override. So if I had two tabs open and/or there was another user who saved. But I actually want to save my version what does actually happen?

Secondly if I save and get the error, I'm presuming the post data would be pre-populated as per existing functionality. If so wouldn't saving again have the right time-stamp or is that also in a way maintained through post? Might have missed something as I didn't try it out.

@nitriques
Copy link
Member Author

So I'm assuming the position is that this protection will not be enabled for entries saved from Event's? This is purely a backend only feature to help in scenarios where there are multiple authors?

@brendo Indeed. Right now, the validation is in the publish pages. I did not wanted to break things with this feature. But any entry updated via the frontend will get a new mod_date, so the validation will fail for authors.

But I actually want to save my version what does actually happen?

Would have to start from scratch. I would hope to have time to do some diffs and try to resolve conflicts, but hey. that's hard.

Secondly if I save and get the error, I'm presuming the post data would be pre-populated as per existing functionality.

Exactly. Right now, the save will NEVER work. You have to refresh this page and start from scratch.

This is why it's a PR ;) Let's debate.

@michael-e
Copy link
Member

I understand the reasoning, but I still try to understand the solution.

Does this mean that still two people can open the same entry, but only the first save will be successful? This wouldn't be any better than "the last save wins" (which is what we have now).

Also, I think that any "lock" needs the possibilty to "override" the lock.

Shouldn't a proper solution prevent access to an entry that is edited currently (by another author)? (Also this would need the possibility to override the lock.)

@nitriques
Copy link
Member Author

Does this mean that still two people can open the same entry, but only the first save will be successful?

Yes.

This wouldn't be any better than "the last save wins"

I do not agree. We had calls from client because of this, and it took the activity tracker to prove to the client that this was happening. From a UX point of view, it's better IMHO.

I think that the current error message could be improve, to let the user know more about what happened, like who did the last save and on what time.

@michael-e
Copy link
Member

As I said before, I think that a proper solution should in advance prevent access to an entry that is currently edited (by another author). With your solution, authors will ask "Why have you allowed me to edit an entry if I am not allowed to save my work?". There is no good answer to that.

One way or another, there should be the possibility to override the "lock", i.e. save nevertheless.

@jonmifsud
Copy link
Member

jonmifsud commented Apr 27, 2017 via email

@nitriques
Copy link
Member Author

I think that a proper solution should in advance prevent access to an entry that is currently edited

That's not always possible. Let's say I open the edit page and go make a coffee. I come back, edit and then press save: only then, do the system know that it's trying to edit based on old records.

One way or another, there should be the possibility to override the "lock", i.e. save nevertheless.

Ok this seems to make consensus, I'll do it. Thanks guys!

@nitriques
Copy link
Member Author

Why are we moving away from Symphony::Database() in favor of MySQL

Right now, cleanValue is static, so it should be called with the class name. This needs to change.

As for the UI, I've come up with this:

image

@michael-e
Copy link
Member

"The action requested…" sounds complicated. What about "Saving failed. The entry has been changed by Test on…"?

And instead of "Override? Please understand…" I would prefer "Overwrite these changes?".

This should be decided by a native speaker. @brendo, ah, no, he's Australian… Hmmm… :-)

@michael-e
Copy link
Member

Right now, cleanValue is static, so it should be called with the class name. This needs to change.

I understand. Thanks.

@nitriques
Copy link
Member Author

This should be decided by a native speaker. @brendo, ah, no, he's Australian… Hmmm… :-)

Hahaha. But yeah, I need help with that haha

@nitriques
Copy link
Member Author

I've got override working, and I'm back with the non-existing table problem hahaha

@nitriques
Copy link
Member Author

nitriques commented Apr 27, 2017

1914abc is the fix I've meant to not do, by, instead, creating the timestamp validation. But with overriding in place, it still needed to be done. I hope the fix does not suck as much as I think it sucks, but hey, it is the only one I was able to do it.

@brendo if you could get your blessing regarding the wording... @michael-e and @jonmifsud suggested

Saving failed. The entry has been changed by Test on…

[ ] Don’t care about Joe’s changes? Yes I would much prefer if Joe’s changes never existed!

@nitriques
Copy link
Member Author

@michael-e @jonmifsud @brendo would you mind checking the last commit (fc9adc4). @michael-e I think we need to tell the user at least why Saving "failed" (I prefer aborted since that what's happening)

@@ -1261,7 +1262,9 @@ public function addTimestampError($errorMessage, $existingObject, $options = arr
));
$errdiv->appendChild(new XMLElement('label',
Widget::Input('action[ignore-timestamp]', null, 'checkbox')->generate() . ' ' .
__('Override? Please understand that data loss can occur.')
__("I don't care about %s's changes, pretend it never existed.", array(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be "pretend they…" or "pretend these…"?

Anyway it sounds complicated. What about "I don't care about %s's changes. Save my version."

Copy link
Contributor

@nilshoerrmann nilshoerrmann May 19, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This copy sounds quite rude …

return false;
} else if (isset($_POST['action']['timestamp'])) {
$tv = new TimestampValidator('sections');
if (!$tv->check($section_id, $_POST['action']['timestamp'])) {
$this->_errors['timestamp'] = __('The action requested was made on a earlier version of the record.');
$this->_errors['timestamp'] = __('Saving aborted, data integrity check failed.');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"data integrity check failed" isn't very user-friendly, is it? Can we make this more clear?

@brendo
Copy link
Member

brendo commented May 19, 2017

Mm, I wonder if the in field approach could be refined. At the moment, any 'errors' that occur at a generic level are introduced with a page alert. We have a convention where links can appear as well, and clicking on the link perform actions (usually navigation).

Would it be suitable to instead display a page alert along the lines of:

This entry has been changed by %s %s. _Replace anyway?_ or _View %s's entry_

eg. This entry has been changed by Brendan a few moments ago. Replace anyway? or View Brendan's entry.

I'm not sure if the Brendan's bit translates well though, how does it read in German/French?

This cleans up the interface and doesn't introduce a new field, a pattern that doesn't exist. Items in the main area are always the result of field or extension, never by the core.

@nilshoerrmann
Copy link
Contributor

I like this approach.

I'm not sure if the Brendan's bit translates well though, how does it read in German/French?

It's possible translation-wise: the German equivalent would be "Zeige Brendans Eintrag". But you'll run into trouble with names like mine which should be inserted as "View Nils' entry" and not "View Nils's entry". But it's possible to work that out with good copywriting.

@brendo
Copy link
Member

brendo commented May 19, 2017

But it's possible to work that out with good copywriting.

Yeah I'm wondering if other terms could work:

  • View changed entry
  • View changes (might imply diff like view though?)
  • View new entry
  • View updated entry
  • Show entry

@nilshoerrmann
Copy link
Contributor

View other entry?

@nitriques
Copy link
Member Author

Yes, debate, ideas! I like Brendon's idea. It was my first idea, but it felt wrong with a check box in it...
Let me send some more commits ;)

@brendo
Copy link
Member

brendo commented May 19, 2017

Why does it need a checkbox? Could it be hidden and it's value changed by JS? Or checked by CSS target

@nitriques
Copy link
Member Author

nitriques commented May 24, 2017

Why does it need a checkbox? Could it be hidden and it's value changed by JS? Or checked by CSS target

It does not!!! Your solution is completely valid! I just did not thought of it!

(somehow, I only saw the checkbox option)

@nitriques
Copy link
Member Author

@brendo There is even a bonus side effect of using the alert: We can display it in index pages as well!!!

@nitriques
Copy link
Member Author

How about this :
image

@nilshoerrmann
Copy link
Contributor

nilshoerrmann commented May 27, 2017

What about:

The entry could not be saved due to conflicting changes by X at Y. Dismiss changes? View changes.

@brendo
Copy link
Member

brendo commented May 28, 2017

I like @nilshoerrmann wording, it's succinct. My only worry is that "Dismiss changes" is too soft and doesn't clearly explain that you will essentially delete data. What about "Overwrite changes?" or "Replace changes"?

@nilshoerrmann
Copy link
Contributor

Both suggestions sound just fine.

I had the German word “verwerfen” in mind which translates to “dismiss” or “discard“. So maybe you should just decide, Brendan, because as a native speaker you know best what works in the English language.

My main intention was to shorten Nicolas’ message in order to align the wording of both actions with the rest of the interface.

@brendo
Copy link
Member

brendo commented May 29, 2017

If it's up to me, I'd vote for Replace changes

@nitriques
Copy link
Member Author

Yes I like this too!

image

Is it good to merge ?

@nitriques
Copy link
Member Author

Is it good to merge ?

I meant can I work on finishing this feature since the UI is ok ? :P

@nitriques nitriques mentioned this pull request May 29, 2017
@nitriques nitriques force-pushed the 2.7.x-data-loss branch 2 times, most recently from 845c71a to 82d4b3f Compare May 30, 2017 20:20
@nitriques
Copy link
Member Author

This thing will be an ongoing effort, since it's "complete" (meaning things that are implemented are working), I'll merge soon.

We add a timestamp check before saving records, either in sections
(fields) or entries.

When saving a section, if the data changes between the render of the
page and the form post, it can lead to data loss if a field was added.
The SQL will crash because it assumes the fields did not change.

When saving and entry, the SQL was ok, but still, it can lead to data
loss.

At both places, the fix provided here makes sure we are editing the
lastest version of the record.

Rebased from 81438f6~1...0c8c765
Rebased from 60e930e~1...1a931a4

Fix SQL error when saving a deleted field

This commit makes sure that a request to update a field will not fail if
the field as already been deleted by another user (or the same user in a
different tab/browser).

If the field is found to be non-existant, it is treated as a new field.

Fixes symphonycms#2703
pick 1c84b21

Replace the added fieldset with an alert

@brendo's suggestion is WAY better then the current implementation,
which adds a new UI pattern in the system. Usign links in the alerts
makes it possible to offer the overwrite mode, without gluttering the
UI.

In order to make it possible to submit the form from the alert, a little
of pretty simple javascript was needed.

Rebased from 6f37eaf~1...c652b18
This is needed to properly display the name of the author that created
the conflict.

I first thought that the author_id would get updated on save, but it
turns our it does not: it's the creation author.

So in order to make it backward compatible a modification_author_id is
needed.

pick dff0d7f
This prevents SQL error in case no author can be found (import script by
example). It re-use the default value already established on creation.

Fixes symphonycms#2152

pick 64b2b7f
@nitriques nitriques merged commit af75b3c into symphonycms:2.7.x Jun 14, 2017
@nitriques nitriques deleted the 2.7.x-data-loss branch June 14, 2017 20:43
nitriques added a commit that referenced this pull request Jun 16, 2017
We add a timestamp check before saving records, either in sections
(fields) or entries.

When saving a section, if the data changes between the render of the
page and the form post, it can lead to data loss if a field was added.
The SQL will crash because it assumes the fields did not change.

When saving and entry, the SQL was ok, but still, it can lead to data
loss.

At both places, the fix provided here makes sure we are editing the
lastest version of the record.

Rebased from 81438f6~1...0c8c765
Rebased from 60e930e~1...1a931a4

Fix SQL error when saving a deleted field

This commit makes sure that a request to update a field will not fail if
the field as already been deleted by another user (or the same user in a
different tab/browser).

If the field is found to be non-existant, it is treated as a new field.

Fixes #2703
pick 1c84b21

Replace the added fieldset with an alert

@brendo's suggestion is WAY better then the current implementation,
which adds a new UI pattern in the system. Usign links in the alerts
makes it possible to offer the overwrite mode, without gluttering the
UI.

In order to make it possible to submit the form from the alert, a little
of pretty simple javascript was needed.

Rebased from 6f37eaf~1...c652b18

Picked from 95fb812
nitriques added a commit that referenced this pull request Jun 16, 2017
We add a timestamp check before saving records, either in sections
(fields) or entries.

When saving a section, if the data changes between the render of the
page and the form post, it can lead to data loss if a field was added.
The SQL will crash because it assumes the fields did not change.

When saving and entry, the SQL was ok, but still, it can lead to data
loss.

At both places, the fix provided here makes sure we are editing the
lastest version of the record.

Rebased from 81438f6~1...0c8c765
Rebased from 60e930e~1...1a931a4

Fix SQL error when saving a deleted field

This commit makes sure that a request to update a field will not fail if
the field as already been deleted by another user (or the same user in a
different tab/browser).

If the field is found to be non-existant, it is treated as a new field.

Fixes #2703
pick 1c84b21

Replace the added fieldset with an alert

@brendo's suggestion is WAY better then the current implementation,
which adds a new UI pattern in the system. Usign links in the alerts
makes it possible to offer the overwrite mode, without gluttering the
UI.

In order to make it possible to submit the form from the alert, a little
of pretty simple javascript was needed.

Rebased from 6f37eaf~1...c652b18

Picked from 95fb812
nitriques added a commit that referenced this pull request Oct 9, 2018
next() always returns an entry object or null, never an array.

This problem was introduced in 154c161
Re #2659
nitriques added a commit to DeuxHuitHuit/symphonycms that referenced this pull request Mar 29, 2019
next() always returns an entry object or null, never an array.

This problem was introduced in 154c161
Re symphonycms#2659

Picked from 119f3fe
nitriques added a commit that referenced this pull request Apr 8, 2019
next() always returns an entry object or null, never an array.

This problem was introduced in 154c161
Re #2659

Picked from 119f3fe
Picked from d9992e9
nitriques added a commit to DeuxHuitHuit/symphonycms that referenced this pull request Apr 8, 2019
next() always returns an entry object or null, never an array.

This problem was introduced in 154c161
Re symphonycms#2659

Picked from 119f3fe
Picked from d9992e9
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants