Skip to content

Aztec: Implements basic state machine for editor state#6527

Merged
astralbodies merged 30 commits intodevelopfrom
issue/6422-post-status-state-machine
Feb 2, 2017
Merged

Aztec: Implements basic state machine for editor state#6527
astralbodies merged 30 commits intodevelopfrom
issue/6422-post-status-state-machine

Conversation

@astralbodies
Copy link
Contributor

@astralbodies astralbodies commented Jan 31, 2017

Closes #6422

This adds the Publish button (previously called Post) to the Aztec editor. Instead of using a series of hacky methods to determine the state of the button it extracts the logic into clearly defined units of state that is unit testable. Other variables that affect state of the editor are also in the state context.

This is partially based off of @frosty's PostEditorSaveAction enum and WPPostViewController extension but made to be more abstract.

The main things this solves are:

  • Publish button title.
  • Publish button state (enabled/disabled).
  • What text is shown during the publish process (Publishing...).

What it can/should solve but isn't part of this PR:

  • Is Post-Post shown?
  • Is there a secondary publish button and what does it say? (publish now, save draft, etc)
  • Anything else that's related to the state of the editor that shouldn't turn into a pile of crazy If statements.

Some other TODOs that aren't part of this PR:

  • Submit for Review is a lot of text to fit on the button label. Maybe just Submit? How are we handling it in cases where the button text is just too long in other languages?
  • Submit for Review is also not currently supported in the hybrid editor. May need to validate how this all works. Currently the Aztec editor supports it but we're not setting the Bool properly if the user has or doesn't have the permission to publish.
  • Update the state context when the post is being published to disable the publish button.
  • Add some more unit tests to make sure all state switches are accounted for.

How to Test

Make sure the publish button shows the right text and is enabled in these scenarios:

  1. New post, no content. (Publish, disabled)
  2. New post, with content. (Publish, enabled)
  3. New post, set to draft. (Save)
  4. New post, future dated. (Schedule)
  5. New post, past dated. (Publish)
  6. Existing published post. (Update)
  7. Existing scheduled post. (Update)
  8. Existing draft post. (Save)

There are a bunch of other scenarios but you get the point.

Needs review: @frosty, @jleandroperez

@astralbodies astralbodies added this to the Aztec v1 milestone Jan 31, 2017
@astralbodies astralbodies changed the title Implements basic state machine for editor state Aztec: Implements basic state machine for editor state Jan 31, 2017
Copy link
Contributor

@jleandroperez jleandroperez left a comment

Choose a reason for hiding this comment

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

Beautiful code!! Adding just a few comments. About to begin testing on the actual device. Zero blockers so far!

originalPostStatus = PostStatus(rawValue: postStatus) ?? .draft
} else {
originalPostStatus = .draft
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Nipticky // feel free to disregard!!

Potential candidate for a helper method:

enum PostStatus {
   static func fromPost(post: Post) -> PostStatus {
 //...
   }
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Could be a good idea – defaulting to .draft. Could also be used in observeValue(forKey:...)


super.init(nibName: nil, bundle: nil)

addObservers(toPost: post)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's safe to remove this addObservers, since it's already being done in the didSet.

Unless i'm missing something!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jleandroperez didSet isn't called when an init method sets the properties before super is called. Once super is called then didSet is called every future time the property is set. Stabby!!

fileprivate var isBeingPublished = false {
didSet {
if isBeingPublished != oldValue {
publishActionAllowed = hasContent && !isBeingPublished
Copy link
Contributor

Choose a reason for hiding this comment

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

Still thinking if this snippet deserves a method its own method, or not (refreshActionAllowedState). It's just a line... perhaps i'm overthinking this one!

}

var publishButtonText: String {
return editorState.action().publishActionLabel
Copy link
Contributor

Choose a reason for hiding this comment

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

action.publishActionLabel

}

var publishVerbText: String {
return editorState.action().publishingActionLabel
Copy link
Contributor

@jleandroperez jleandroperez Jan 31, 2017

Choose a reason for hiding this comment

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

action.publishingActionLabel (nipticky, sorry!)

}

var isPostPostShown: Bool {
return editorState.action().isPostPostShown
Copy link
Contributor

Choose a reason for hiding this comment

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

action.isPostPostShown (sorry, nipticky++)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed - I'll turn action() into a protocol property.

@jleandroperez
Copy link
Contributor

jleandroperez commented Feb 1, 2017

Note 1:

Drafts are displaying the Publish Action (vs Update in the Hybrid editor):

  1. In a testing blog, create a Draft
  2. Enable Aztec
  3. Edit the Post. The top right action will read Publish

Is this expected?

Update: Possibly related to #6534

@jleandroperez
Copy link
Contributor

jleandroperez commented Feb 1, 2017

Note 2:

We've got an overlap between navigationBar Action Button, and the Aztec Title. We should either cap the Blog Title's Width, or indicate somehow else that we're in Aztec. (I'd vote for both perhaps!)

simulator screen shot feb 1 2017 11 57 51

@jleandroperez
Copy link
Contributor

jleandroperez commented Feb 1, 2017

Note 3:

The post's setter observer is not refreshing the PostEditorStateContext. I suspect we should be re-instantiating the PostEditorStateContext's instance, whenever the Post changes.

IE: From Revision to Original.

@jleandroperez
Copy link
Contributor

jleandroperez commented Feb 1, 2017

Note 4:

Post's Status Context didChangeAction delegate method (which is in charge of setting the Publish Button's Title) is not getting called with the initial value.

We could either mimic KVO's "Post Initial Value" flag, or just add that to the refreshInterface helper?

@frosty
Copy link
Contributor

frosty commented Feb 1, 2017

A few notes from putting this through its paces:

  • When writing a new post, the Publish button is only enabled when text is added to the body of the post, not the title. On the hybrid editor, typing characters into the title field enables the button. We should probably do the same in Aztec? (you could save a post with only a title)
  • If I set a new post to Scheduled (setting a publish date), the button changes to Schedule (as expected). If I change the post back to Published (by tapping Publish Immediately), the button title stays as Schedule. In fact, even if I change the post status to Draft the title is still Schedule. Not the case with the hybrid editor.
  • I'm seeing the same as @jleandroperez – existing posts are all showing Publish for me. I tested with existing drafts, scheduled posts, and published posts. The button says Publish for all of them.
  • The existing button says "Post" not "Publish". Are we changing this?

Taking a look through the code now... :)

Copy link
Contributor

@frosty frosty left a comment

Choose a reason for hiding this comment

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

This is looking great, @astralbodies! I've left some code comments, and it seems like @jleandroperez has everything else covered :)

}

func testContextChangedNewPostToDraft() {
context = PostEditorStateContext(originalPostStatus: .draft, userCanPublish: true, delegate: self)
Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't a new post created with publish status, not draft?

Copy link
Contributor

Choose a reason for hiding this comment

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

(although note that if you change this, we already have a publish -> draft test below.

Copy link
Contributor

Choose a reason for hiding this comment

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

Just tested with the existing editor. Posts begin in the publish status. If you change it to draft, the button shows Save.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I fixed this with a check-in - new posts will pass in a nil originalPostStatus now since it's not on the server. I was trying to match logic in Calypso which makes every post a draft when creating it which is something we should eventually emulate. 😄

Copy link
Contributor

Choose a reason for hiding this comment

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

I definitely agree we should start every post off as a draft!

func testContextDraftPost() {
context = PostEditorStateContext(originalPostStatus: .draft, userCanPublish: true, delegate: self)

XCTAssertEqual(PostEditorAction.publish, context.action, "should return Publish if the post is a draft")
Copy link
Contributor

Choose a reason for hiding this comment

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

The hybrid editor shows Update if the post is a draft.

XCTAssertEqual(PostEditorAction.submitForReview, context.action, "should return 'Submit for Review' if the post is a draft and user can't publish")
}


Copy link
Contributor

Choose a reason for hiding this comment

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

Should we add another test ensuring that a post originally in the scheduled status, if we don't change the date at all, shows an update action?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added unit tests to cover these scenarios.


// Missing test: should return false if not dirty and has no content

func testPublishDisabledNoContent() {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is very similar to the first test (testContextNoContentPublishButtonDisabled). Perhaps we should name them more differently or more consistently, and perhaps group them together.

}
}

var isPostPostShown: Bool {
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like this isn't used yet and I might be misunderstanding its use, but with the hybrid editor we also show post-post for scheduled posts.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's not currently used yet - will be in another PR.


fileprivate var hasContent = false {
didSet {
publishActionAllowed = hasContent && !isBeingPublished
Copy link
Contributor

Choose a reason for hiding this comment

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

I know it's only one line, but we could extract this to a private updatePublishActionAllowed func and call it from both didSets?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yup I'll make that change. 👍

}

func updated(postStatus: PostStatus, context: PostEditorStateContext) -> PostEditorActionState {
return self
Copy link
Contributor

Choose a reason for hiding this comment

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

Presumably this is why I can't seem to get the button out of Schedule state once I've entered it?

fileprivate extension PostEditorActionState {
func isFutureDated(_ date: Date) -> Bool {
let oneMinute: TimeInterval = 60.0
let dateOneMinuteFromNow = Date(timeInterval: oneMinute, since: Date())
Copy link
Contributor

Choose a reason for hiding this comment

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

I would instead use Calendar.current.date(byAdding: .minute, value: 1, to: date)

Or you could potentially use Calendar.current.compare(Date(), to: date, toGranularity: .minute) to compare them instead.

}

internal func context(_ context: PostEditorStateContext, didChangeAction: PostEditorAction) {
publishButton.title = context.publishButtonText
Copy link
Contributor

Choose a reason for hiding this comment

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

In both of these delegate methods, is there a reason you're going via the context instead of using the passed in action / bool?

@astralbodies
Copy link
Contributor Author

Responding to @frosty's main thread comments:

A few notes from putting this through its paces:

When writing a new post, the Publish button is only enabled when text is added to the body of the post, not the title. On the hybrid editor, typing characters into the title field enables the button. We should probably do the same in Aztec? (you could save a post with only a title)

Done. Had to add a target/action for the editing changed event on the textview to watch for this. The postTitle property on the post isn't updated until editing is done.

If I set a new post to Scheduled (setting a publish date), the button changes to Schedule (as expected). If I change the post back to Published (by tapping Publish Immediately), the button title stays as Schedule. In fact, even if I change the post status to Draft the title is still Schedule. Not the case with the hybrid editor.

Fixed.

I'm seeing the same as @jleandroperez – existing posts are all showing Publish for me. I tested with existing drafts, scheduled posts, and published posts. The button says Publish for all of them.

Fixed.

The existing button says "Post" not "Publish". Are we changing this?

I'm forcing us to match what Calypso says. I think @mattmiklic agreed when I asked about it. 😄

@astralbodies
Copy link
Contributor Author

@jleandroperez Here are responses to your notes:

Note 1:
Drafts are displaying the Publish Action (vs Update in the Hybrid editor):

Fixed.

Note 2:
We've got an overlap between navigationBar Action Button, and the Aztec Title. We should either cap the Blog Title's Width, or indicate somehow else that we're in Aztec. (I'd vote for both perhaps!)

We talked about this and removing the navbar title makes the most sense. It's getting in the way and we'll mark the editor as being Aztec is some other way like slightly changing the title bar color or something. Will address in a different PR.

Note 3:
The post's setter observer is not refreshing the PostEditorStateContext. I suspect we should be re-instantiating the PostEditorStateContext's instance, whenever the Post changes.

For now I moved post revision creation to happen before the UI is set up. I think it makes sense we do the revision operation first.

Note 4:
Post's Status Context didChangeAction delegate method (which is in charge of setting the Publish Button's Title) is not getting called with the initial value.

I think letting the context emit the current state is a good idea. (There's a similar feature in RxSwift ... just sayin...) For now I'll leave it as there are a few things I want to add to the context (like the secondary publish button and post-post functionality) and I'll add this in.

@astralbodies
Copy link
Contributor Author

@jleandroperez @frosty I think this is ready for another pass.

@mattmiklic
Copy link
Member

mattmiklic commented Feb 1, 2017

I'm forcing us to match what Calypso says. I think @mattmiklic agreed when I asked about it. 😄

💯. Post in the iOS editor is an outlier; both Calypso and wp-android use Publish.

@frosty
Copy link
Contributor

frosty commented Feb 2, 2017

@astralbodies Changes look good! 👍 for all the comments you've added. Fixes the issues I had, and the tests all pass.

:shipit: from me, after the conflicts are fixed!

# Conflicts:
#	WordPress/Classes/ViewRelated/Post/AztecPostViewController.swift
@jleandroperez
Copy link
Contributor

Beautiful code @astralbodies, all looking good!!

:shipit: !!!

@astralbodies
Copy link
Contributor Author

Thank you both so much for the time to review!

@astralbodies astralbodies merged commit cde4de0 into develop Feb 2, 2017
@astralbodies astralbodies deleted the issue/6422-post-status-state-machine branch February 2, 2017 21:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants