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

Upgrade to latest slate #554

Merged
merged 23 commits into from Aug 18, 2018
Merged

Upgrade to latest slate #554

merged 23 commits into from Aug 18, 2018

Conversation

PeterKottas
Copy link
Collaborator

This work is not ready yet.
I am just opening PR for convenience so that you can see my changes and have a chance to react.
This of course has to do with #553
Right now I went with creating slate-next. The idea is to either at the end delete old slate and rename this back to slate. That is of course if we manage to make it work with old documents.

@PeterKottas
Copy link
Collaborator Author

Ok this version appears to be working quite well. @arekkas please give it a go when you have a chance. Of course there's some more work to do (consider with to do with the "slate"&"slate-next" situation) and fix the example content but other than that, it appears to be working.
Also the fucking sign-off is not working again. I think it's vs-code doing something to it. The fix you suggested in #552 doesn't work either

@PeterKottas
Copy link
Collaborator Author

Complete list of changes in the underlying serialized data

  • kind is renamed to object
  • ranges is renamed to leaves
  • whole content is wrapped in additional document node

I think we could make it backwards compatible by hacking around with https://github.com/PeterKottas/editor/blob/master/packages/plugins/content/slate-next/src/hooks.js#L130
Namely we could update plugin version, check in that place if it's the old version, and if so, just perform these actions on the serialized document. Not that importFromHtml way of storing data doesn't suffer from this problem.

Signed-off-by: Peter Kottas <petokottas@gmail.com>
Signed-off-by: Peter Kottas <petokottas@gmail.com>
Signed-off-by: Peter Kottas <petokottas@gmail.com>
@aeneasr
Copy link
Collaborator

aeneasr commented Aug 13, 2018

Do you think it would be possible to migrate those changes to the slate plugin itself and offer a way for migrations? We do have a way to check for plugin versions iirc for this specific use case.

@PeterKottas
Copy link
Collaborator Author

Yeah absolutely, it's already implemented in #557 ;)

@PeterKottas
Copy link
Collaborator Author

Just need to wait for that branch to merge, then merge it into this and make the slate-next -> slate renaming, it should then work perfectly with old data. I've also added some unit tests for migrations

@aeneasr
Copy link
Collaborator

aeneasr commented Aug 13, 2018

Ok I see, merging unstable/dev work into master is not a good practice so I created a new branch https://github.com/ory/editor/tree/slate-next where both PRs can be merged into (please change base of both) - then review it and then finally merge into master. Sound good?

@PeterKottas
Copy link
Collaborator Author

Will I be able to commit directly to it?

@PeterKottas
Copy link
Collaborator Author

If not I can merge my 2 together and PR against that new branch. I just wanted to keep those separate as the migrations are fairly self contained thing

@aeneasr
Copy link
Collaborator

aeneasr commented Aug 13, 2018

You don't have write access to this repository at the moment but if you keep up this amazing work you should! If you rebase both PRs against that branch I can merge them right now and you can create another PR against that new branch with the merged changes to make your latest changes. Alternatively you can merge your two branches on your fork and create a new PR. Whatever you like

@aeneasr
Copy link
Collaborator

aeneasr commented Aug 13, 2018

I can also merge the two PRs on the new branch and you fork it and work on that base

@PeterKottas
Copy link
Collaborator Author

Cheers man, yeah I am in a rush to add CMS to our system and as we love ory editor, we have decided to give some love back ;) For now, I'll be happy to merge my 2 together, that will be straight forward. It however makes sense to me to keep the PR for migrations alive, just for your convenience. That way, you can easily see (and discuss) in separation how I implemented migrations. Sounds good?

@aeneasr
Copy link
Collaborator

aeneasr commented Aug 13, 2018

Sound good! I won't be able to review until end of the week though. The PRs are rather large and it's hard to see what actually changed in this one as it's a completely new code base.

@PeterKottas
Copy link
Collaborator Author

Yeah I know and understand. Don't worry about it. Just as soon as possible will do ;) Thanks for letting me know about time schedule

@PeterKottas
Copy link
Collaborator Author

From now on, this branch contains work on migrations.

Signed-off-by: Peter Kottas <petokottas@gmail.com>
Signed-off-by: Peter Kottas <petokottas@gmail.com>
Signed-off-by: Peter Kottas <petokottas@gmail.com>
Signed-off-by: Peter Kottas <petokottas@gmail.com>
nodes: [
{
object: "block",
type: "paragraph",
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is it intentional that this is "paragraph" and not P?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

nope that's an error on my side, fixed

import { Migration } from 'ory-editor-core/lib/service/plugin/classes';
import rename from 'deep-rename-keys';
const migration = new Migration({
version: '0.0.2',
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think it would be better to define the version ranges here:

new Migration({
  toVersion: '0.0.2',
  fromVersionRange: '~0.0.1' // just an example,
  migrate: state => state
})

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is actually not how it works right now, it's little bit complicated like always with migrations. I don't even believe there's right way to do this.
Basically right now, you as an author should provide all migrations necessary to migrate from older to newer.
These are sorted by version. That means that if I define migrations for
0.0.1
0.0.3
0.0.6
My plugin is version 0.0.6 for example.
My content is version 0.0.2
Then it would apply migration 0.0.3 and 0.0.6
If content was 0.0.0
It would use all 3 migrations.
If I explained this properly, it should make sense that the "fromVersionRange" is not necessary. It's implicitly based on all migrations provided.

Copy link
Collaborator

Choose a reason for hiding this comment

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

That's not how semver works though, that's how database migrations work. There you have a counter (e.g. migration_count) which increases by one when you apply a migrations. It looks like you're doing this here too.

Semver, or more specifically library releases, work differently. 1.0.0 might be compatible with 0.8.0 but not 0.9.0 because 0.9.0 could a later release than 1.0.0 (think maintenance release). If you were to provide migrations for all versions, you'd have to retroactively upgrade the versions which is not possible as you can't tag twice in git (without breaking serious stuff). That's why we have to constrain the versions a certain migration applies to. Chaining migrations is more challenging here for the developer writing the plugin, but it is doable. From the API standpoint though, it is more concise and compatible with semver or more generally npm releases/git tags.

@@ -24,7 +24,6 @@
import React from 'react'
import ListIcon from '@material-ui/icons/List'
import OrderedListIcon from '@material-ui/icons/FormatListNumbered'
import createListPlugin from 'slate-edit-list'
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is slate-edit-list not compatible with the latest slate or what is the reason for removing it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I had to modify this plugin by borrowing the implementation from official docs. They didn't use it there and I didn't find anything it did. Also I think it might have been incompatible. But tbh I've change so many things, I don't exactly remember. I've tested lists and they seem to work. Not sure if there's some functionality missing

Copy link
Collaborator

Choose a reason for hiding this comment

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

The plugin has key bindings and validators (see https://github.com/GitbookIO/slate-edit-list#features). Those are definitely missing!


class Paragraph extends Component {
shouldComponentUpdate(nextProps) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

A particular reason for removing shouldComponentUpdate?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The way react decides if to update component is actually shallowEqual so it seemed redundant to do it twice. Unless there's something I am missing.

Copy link
Collaborator

Choose a reason for hiding this comment

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

You're right, not sure what the point of it was.

const align = this.props.node.data.get('align')
return (
<p {...attributes} style={{ textAlign: align }}>
<Placeholder
className="ory-plugins-content-slate-paragraph-placeholder"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Particular reason for not keeping the classname?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

They removed the placeholder from slate. The placeholder is now provided as parameter to the editor.

Copy link
Collaborator

@aeneasr aeneasr left a comment

Choose a reason for hiding this comment

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

I reviewed the whole PR now. Looks very solid. The biggest issue I see is how migrations are working at the moment. Migrations will be a huge and defining part of the API and should be thought through. I added some suggestions as to how a migration could look like. Open for suggestions!

Signed-off-by: Peter Kottas <petokottas@gmail.com>
@PeterKottas
Copy link
Collaborator Author

Yeah I know about suggestions. That's why I initially wanted to keep it as separate PR, to make it easier to modify stuff and reason about things.

Ok here's my 2 cents about migrations. Bear in mind it's very complex topic like you pointed out and it's kind of hard for me to explain. I'll do my best.

In the context of this library, I don't consider migrations to be "key" feature. Think of it this way, you have thousands of people using this lib, and so far it wasn't there at all. I am not saying this to force you to accept wrong solution to the problem, just pointing out it will probably not be used by too many people.
Migrations here are definitely not something like in entity framework where you end having hundreds of migrations for one project.
Here I assume 1 migration (if that) will be more than enough for most plugins. It's important to point out that even when plugins change quite a lot, it's not exactly hard to make them backwards compatible.
I think the slate example was fairly extreme case.
That's why I designed it the way I did.
The "version" in migration merely says to what version it upgrades.
The "fromVersion" (or version range) here is not necessary as we are always expecting all of the migrations to be there. (coincidentally entity framework migrations works exactly the same)
For a known content version, you just take all migrations where semver > than content version. Up to the version of plugin that you use. And that's it.
It's actually easier than I though :D

@PeterKottas
Copy link
Collaborator Author

Also on a side-note, I've implemented other feedback from PR (most of it definitely pretty straight forward compared to the migration talks)

Signed-off-by: Peter Kottas <petokottas@gmail.com>
@aeneasr
Copy link
Collaborator

aeneasr commented Aug 16, 2018

In the context of this library, I don't consider migrations to be "key" feature. Think of it this way, you have thousands of people using this lib, and so far it wasn't there at all. I am not saying this to force you to accept wrong solution to the problem, just pointing out it will probably not be used by too many people.

I disagree. Migrations of the document model are as important as migrations of a SQL Schema. If we significantly break the document model without a way for migrating to a newer version, it will be a terrible developer experience. The system was not in place because there was no need in the core library, but versioning existed from the beginning due to this.

Versioning for libraries is, unfortunately, more complex due to the way typical libraries are versioned (semver). This allows for high complexity when releases are not sequential (e.g. 0.9.0 released after 1.0.0 but 0.8.0 released before 1.0.0).

If you don't want to take time to think about this - which would be totally acceptable - I'll free up some time to do so.

@PeterKottas
Copy link
Collaborator Author

(e.g. 0.9.0 released after 1.0.0 but 0.8.0 released before 1.0.0) is a very good point. Yeah I sort of agree with you. For now, I am tempted to leave it for another PR if that would work for you. The only thing I am afraid of right now is to wait a long time to merge this. If we can in fact merge it, I'd be happy to work with you on improving the migrations after this goes in.

@aeneasr
Copy link
Collaborator

aeneasr commented Aug 17, 2018

The only thing I am afraid of right now is to wait a long time to merge this. If we can in fact merge it, I'd be happy to work with you on improving the migrations after this goes in.

I created a 0.4.0 branch where we can merge this as work in progress. Once the changes are done we'll push it to master and tag it v0.4.0 . That way it's easier to work on this branch and also use it in npm. Deal?

@aeneasr aeneasr changed the base branch from master to 0.4.0 August 17, 2018 08:26
@PeterKottas
Copy link
Collaborator Author

Yeah sure, that works for me. So will you just merge this now, release it and create new PR against master? Did I get it right? The only problem then will be I won't be able to commit directly to it. But I can PR against it if that's preferable.

@PeterKottas
Copy link
Collaborator Author

PeterKottas commented Aug 17, 2018

In the meantime, I think I might have cracked the migrations and do it properly this way.

Let's assume that all migrations necessary to migrate to current version are present in code. This is an important assumption and it should become guideline for developers. Let's call it assumption A

For the argument sake, let's create this example situation.

Existing plugin versions in order of releases

0.1.0 -> 0.2.0 -> 0.3.0 -> 1.0.0 -> 0.4.0 -> 1.1.0

We would then add the param you proposed so the migration object could look like:

export type Migration = {
    toVersion: string, // SEMVER, single version
    fromVersionRange: string // SEMVER range
    migrate: (state: Object) => Object
}

Sidenote, once/if we have a proper typesystem in place, we could also keep all state types and provide 2 generic arguments

export type Migration<FromState, ToState> = {
    toVersion: string, // SEMVER, single version
    fromVersionRange: string // SEMVER range
    migrate: (state: FromState) => ToState
}

Which would imho be pretty cool

Let's now create the migrations for our example

[
  {
    toVersion: '0.3.0',
    fromVersionRange: '0.1.0 – 0.2' // >=0.1.0 to < 0.3.0 according to https://devhints.io/semver
  },
  {
    toVersion: '1.0.0',
    fromVersionRange: '~0.3.0' // >=0.3.0 to < 0.4.0 according to https://devhints.io/semver
  },
  {
    toVersion: '0.4.0',
    fromVersionRange: '~1.0.0' // >=1.0.0 to < 1.1.0 according to https://devhints.io/semver
  },
  {
    toVersion: '1.1.0',
    fromVersionRange: '~0.4.0' // >=0.4.0 to < 0.5.0 according to https://devhints.io/semver
  }
]

Let's do this for content with version 0.1.0 and plugin version 1.1.0

Pseudo code

**currentDataVersion** = '0.1.0'
**pluginVersion** = '1.1.0'
while **currentDataVersion** != **pluginVersion**
  **migration** = Find migration that upgrades from **currentDataVersion** based on **fromVersionRange**
  **state** = apply **migration**
  **currentDataVersion** = **migration**.**toVersion**

Based on A we can guarantee that after going through that while loop, even if we don't end up with exactly the same version as our pluginVersion, it should still mean that we have the correct data.

And that's pretty much it. Not complicated at all and if we agree on this, I can implement it later on today.

@aeneasr
Copy link
Collaborator

aeneasr commented Aug 17, 2018

Yeah sure, that works for me. So will you just merge this now, release it and create new PR against master? Did I get it right? The only problem then will be I won't be able to commit directly to it. But I can PR against it if that's preferable.

I will do another review pass, then merge

And that's pretty much it. Not complicated at all and if we agree on this, I can implement it later on today.

Code example looks good. I think that's the way to do it!

@PeterKottas
Copy link
Collaborator Author

PeterKottas commented Aug 17, 2018

I was trying to fix the lists, but weirdly, I noticed it doesn't work properly on the master either. Check out https://editor.ory.am/?utm_source=github&utm_medium=header&utm_campaign=editor, select one of the existing lists and notice that the button for list is not active. If you click it, it will wrap it once again.

The reason for this is it's wrapped in additional paragraph. Not sure how that happened. Do you ahve any insight?

If you create a new one, it works as expected, it's only the ones baked in content.js

If you check out the html, you'll notice the extra paragraph

@aeneasr
Copy link
Collaborator

aeneasr commented Aug 17, 2018 via email

Signed-off-by: Peter Kottas <petokottas@gmail.com>
Signed-off-by: Peter Kottas <petokottas@gmail.com>
@PeterKottas
Copy link
Collaborator Author

I am not exactly sure how you suppress build errors like this one. What method you prefer/use?

@aeneasr
Copy link
Collaborator

aeneasr commented Aug 18, 2018

We can ignore that for now!

@aeneasr aeneasr merged commit 913fc7b into react-page:0.4.0 Aug 18, 2018
@PeterKottas
Copy link
Collaborator Author

Ok, fair enough ;)

aeneasr pushed a commit that referenced this pull request Aug 22, 2018
Signed-off-by: Peter Kottas <petokottas@gmail.com>
@PeterKottas
Copy link
Collaborator Author

Hey mate, is this not released yet? I am thinking maybe the flow errors broke the release pipeline?

@aeneasr
Copy link
Collaborator

aeneasr commented Aug 25, 2018

Yeah the lerna upgrade broke it, fixed now

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants