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

Action trigger feature #25

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

mfendeksilverstripe
Copy link
Collaborator

@mfendeksilverstripe mfendeksilverstripe commented Nov 26, 2019

Action trigger workflow feature

New workflow added which is based on user actions as opposed to model writes.

Features

  • out of the box action handling of basic user actions (page edit form, grid field item edit form)
  • static configuration to alter existing actions and adding new ones
  • runtime overrides which allow altering how snapshots are created
  • snapshots are created only after all model writes are done so snapshot rollback is consistent
  • detailed documentation is in readme

Copy link

@unclecheese unclecheese left a comment

Choose a reason for hiding this comment

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

Awesome work. As I stated in the demo, I really think this puts the module much closer to where it needs to be in order to become a viable long term solution to versioning headaches. I have two primary concerns, other than what's detailed in the comments:

  • Documentation -- I know you wrote a ton of new content, which is great, but I feel like it still requires a lot of context to understand what's going on. I had to read it three times to wrap my head around it, and I'm more familiar with the use cases than most will be. So I think just backing up and thinking really high level about what problems this solves, and working by example is really key. In fact, I would probably prefer that you worked off an imaginary project to show the real business case for it and to connect all the examples into one coherent theme.

  • Coupling -- I'm sure this wasn't your preferred architecture, but it does seem like what used to be a fairly self-contained module is now has all kinds of awareness about different CMS features. You've added a bunch of public APIs that are named specifically after external features, and things like GraphQL Schema are now imported into Snapshot. It may very well have to be that way just because our framework isn't very well decoupled to begin with, but I'd just share that if I were building this from the ground up, the way I would envision that working is with something like a registry that accepts any number of classes of a certain interface that can declare their identifiers. So something like:

Injector:
  Snapshot:
    properties:
      listenerRegistry:
         graphql: %My\GraphQL\Listener

Then, your implementation is $snapshot->fireAction('graphql', array $params), which fails gracefully if nothing in the registry matches graphql, and you destructure the argument signature of those methods so it just takes arbitrary params -- more like an event system. You lose the typing, but I think that's a fair trade off for the value of decoupling.

Does that make sense? Right now it feels like you're really overloading the purpose of extensions. Like I said, you've probably got good reasons for that, but just a gut reaction tells me there's a cleaner approach somewhere.

'`save`': '`Save page`'

This means each time a user saves a page via page edit form a snapshot will be created with a context message `Save page`.

Choose a reason for hiding this comment

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

This still isn't clear to me what save is. I think I need to see the code that is providing this identifier back to the config. I assume that's something like:

class CMSPageSaveAction ... 
{
  public function getActionName(): string { return 'save' }
}

???

I think it's important here to show why a snapshot is created whenever someone saves a page, so it doesn't look so much like magic.


'`save`': '`Save page`'

This means each time a user saves a page via page edit form a snapshot will be created with a context message `Save page`.

Choose a reason for hiding this comment

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

Can you define "context message" here, or just show what the end result looks like in the IUI?


`CustomActionListenerAbstract` is the parent class because this action is a custom mutation

Returning `false` inside `processAction` makes the module fallback to default behaviour.

Choose a reason for hiding this comment

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

What is "default" behaviour at this point?


There are two main cases here:

`1` - snapshot module doesn't have enough information to find origin and is using `event` as a placeholder to fill the gap.

Choose a reason for hiding this comment

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

An example of this would be really helpful, as you've done below.

* @param mixed $controller
* @return Page|null
*/
private function getCurrentPageFromController($controller): ?Page

Choose a reason for hiding this comment

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

I feel like I've written this code so many times. Would be worth considering if it should be a core API. Nothing in here is unique to the concerns of snapshots.

*/
private function getActionType(string $query): ?string
{
$action = explode('(', $query);

Choose a reason for hiding this comment

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

Does this assume the query has arguments?

$url = $request->getHeader('referer');
$url = parse_url($url, PHP_URL_PATH);
$url = ltrim($url, '/');
$page = $this->getCurrentPageFromRequestUrl($url);

Choose a reason for hiding this comment

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

Second time we've done this. Does it belong in a parent class, or maybe in your trait?


if ($owner instanceof Create) {
return static::TYPE_CREATE;
}

Choose a reason for hiding this comment

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

This feels like really awkward coupling between the extension and its owner. It feels like something that should be the concern of the CRUD operation. Since it's static anyway, what if this was just return $owner->config()->snapshot_action_type, and this class defined a default value in private static $snapshot_action_type to ensure the property always existed?

}

return $actions[$identifier];
}

Choose a reason for hiding this comment

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

If this is singleton access only, should it just be static? There's nothing in here that requires an instance.

// no need to add the origin to the list of objects as it's already there
$origin = $owner;
}
} elseif ($origin->ClassName === $owner->ClassName && (int) $origin->ID === (int) $owner->ID) {

Choose a reason for hiding this comment

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

I think you want to compare hashes with SnapshotHasher here.

'graphql_crud_create': 'GraphQL create'
'graphql_crud_delete': 'GraphQL delete'
'graphql_crud_update': 'GraphQL update'

Choose a reason for hiding this comment

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

Do you have plans on how to make these i18n compatible?

---
SilverStripe\Snapshots\Snapshot:
actions:
Copy link
Collaborator Author

@mfendeksilverstripe mfendeksilverstripe Jan 6, 2020

Choose a reason for hiding this comment

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

It's worth considering grouping the actions by listeners. Action names are probably not unique in general.

For example:
Page\CMSMainAction - save
Form\Submission - save

Should the configuration be moved to individual listeners then?

blueo and others added 5 commits January 8, 2020 09:09
…ave-snapshots

SS-8748: Avoid recording useless save actions
Some snapshot items can be information-only
so don't require a publish at the owner.
These items will be excluded from status flag checks
The lister mode was not marking items as WasPublished.
This meant the status flags were always showing as 'modified'
…cation-snapshots

Bugfix/no modification snapshots
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.

4 participants