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

Editorial workflow dependency tracking #243

Closed

Conversation

Benaiah
Copy link
Contributor

@Benaiah Benaiah commented Feb 23, 2017

- Summary

Dependency tracking for unpublished entries: closes #192.

- Test plan

  • Existing test suite passes.
  • State actions manually tested by dispatching appropriate actions.
  • Dependency resolution and publishing of multiple entries manually tested on benaiah/netlify-cms-test-repo.
  • pre-commit hooks skipped since they complain about code already in master.

- Description for the changelog

  • Add dependency tracking and multiple-entry publishing.

- A picture of a cute animal (not mandatory but encouraged)

image

@Benaiah Benaiah force-pushed the editorial-workflow-dependency-tracking branch from 10747bc to 071e956 Compare February 23, 2017 20:22
@@ -76,6 +77,15 @@ const unpublishedEntries = (state = null, action) => {
// Update Optimistically
return state.deleteIn(['entities', `${ action.payload.collection }.${ action.payload.slug }`]);

case UNPUBLISHED_ENTRY_REGISTER_DEPENDENCY:
return state.setIn([
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is currently storing the dependencies in entry.data.dependencies, where it is then grabbed in persistUnpublishedEntry, added to options, and stored in metadata in editorialWorkflowGit. Should this be saving directly to entry.metaData instead?

@Benaiah Benaiah force-pushed the editorial-workflow-dependency-tracking branch 6 times, most recently from 8735aaa to 24d5dfc Compare February 24, 2017 21:45
@@ -1,58 +1,29 @@
backend:
name: test-repo
Copy link
Contributor

Choose a reason for hiding this comment

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

please, do not push changes to this file

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is temporary, it'll be rebased out before this leaves WIP.

};
}

function unpublishedEntryDependenciesRequest (collection, slug) {
Copy link
Contributor

Choose a reason for hiding this comment

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

please, remove space between method name and parameters.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will do (ditto to those below).

};
}

function unpublishedEntryDependenciesSuccess (collection, slug, dependencies) {
Copy link
Contributor

Choose a reason for hiding this comment

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

please, remove space between method name and parameters.

};
}

function unpublishedEntryDependenciesError (collection, slug, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

please, remove space between method name and parameters.


export const registerUnpublishedEntryDependency = unpublishedEntryRegisterDependency;

export function getUnpublishedEntryDependencies (collection, slug) {
Copy link
Contributor

Choose a reason for hiding this comment

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

please, remove space between method name and parameters.

"dependencies",
];

const getEventualDependencies = (paths, loadedDeps, state) =>
Copy link
Contributor

Choose a reason for hiding this comment

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

why is this function defined inside the parent function? please, extract.

Copy link
Contributor Author

@Benaiah Benaiah Feb 24, 2017

Choose a reason for hiding this comment

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

This function is only useful within getUnpublishedEntryDependencies - I didn't want it exposed to the outer scope since it's very specific to the functionality of traverse. I'm fine with extracting it, that was just the reasoning behind nesting it with traverse.

.then(() => path);
});

const traverse = (collectedDeps, path) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

why is this function defined inside the parent function? please, extract.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This function closes over getState, which is why it was defined here. I can hoist it and make getState an argument, though.

};

// Begin traversal
return traverse(new Immutable.List(), `${ collection }.${ slug }`)
Copy link
Contributor

@calavera calavera Feb 24, 2017

Choose a reason for hiding this comment

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

If you use a Map rather than a List, you'll avoid the O(N) lookup per iteration in the traverse call.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, thanks!

@Benaiah Benaiah force-pushed the editorial-workflow-dependency-tracking branch from 24d5dfc to 9faf2ed Compare February 25, 2017 00:21
@Benaiah
Copy link
Contributor Author

Benaiah commented Feb 25, 2017

@calavera I've addressed your comments on editorialWorkflow.js - thanks for the review!

Copy link
Contributor

@biilmann biilmann left a comment

Choose a reason for hiding this comment

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

Finally got some time to go through this. Still want to see if I can find some time to dig a little more into the traverse function - it feels like it could be cleaned up a bit, but I would have to spend a bit more understanding it to get a closer look.

Apart from some minor things, the main issue I see is keeping the dependency state in in entry.data.dependencies, when entry.data should be the actual data that will be stored in git when persisting the entry.

Apart from that a widget would also need to be able to remove or change a dependency - and I'm wondering if there's any good way to handle cases with a list of relation widgets (what happens when a list item is removed).

const state = getState();

if (collectedDeps.get(path) === true) {
return new Promise(resolve => resolve(collectedDeps));
Copy link
Contributor

Choose a reason for hiding this comment

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

return Promise.resolve(collectedDeps) is cleaner

if (!state.editorialWorkflow.hasIn(depsPath) ||
state.editorialWorkflow.getIn(depsPath).size === 0) {
// This path has no dependencies
return new Promise(resolve => resolve(newDeps));
Copy link
Contributor

Choose a reason for hiding this comment

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

return Promise.resolve(newDeps)

@@ -73,6 +74,10 @@ const entryDraftReducer = (state = Map(), action) => {
case REMOVE_ASSET:
return state.update('mediaFiles', list => list.filterNot(path => path === action.payload));

case UNPUBLISHED_ENTRY_REGISTER_DEPENDENCY:
return state.setIn(['entry', 'data', 'dependencies', action.payload.field],
Copy link
Contributor

Choose a reason for hiding this comment

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

The entry data is what gets persisted in the repository - we don't want to add "dependencies" to the frontmatter of the entry and the name could easily class with a field.

Either we should store them in something like entry.dependencies or do like the assets and have dependencies in the state for entryDraft.

Copy link
Contributor Author

@Benaiah Benaiah Feb 27, 2017

Choose a reason for hiding this comment

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

Ah, thanks for clarifying that. We definitely don't want this clashing with frontmatter. I'll also change the code in src/backends/backend.js/persistEntry to pass the dependencies somewhere other than parsedData, for the same reason (perhaps in the options object).

Touching on that last point, I was originally passing the deps into the backend in persistUnpublishedEntry, but it seemed that it wasn't getting called when creating a new unpublished entry, so dependencies that were created when the entry was first created weren't being saved. Is this behavior of persistUnpublishedEntry intentional or a bug?

return React.createElement(controlComponent, {
field,
value,
metadata,
onChange,
onAddAsset,
onRemoveAsset,
registerDependency,
Copy link
Contributor

Choose a reason for hiding this comment

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

seems like onAddDependency and onRemoveDependency would be more in line with the other callback functions in terms of naming (and it should be possible to remove a dependency as well as adding one...)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, that's a good point re: naming. See my comment below for more on the removal and updating.

@Benaiah
Copy link
Contributor Author

Benaiah commented Feb 27, 2017

@biilmann updating a dependency already works (in the case of a 1-to-1 field->dependency relationship; there isn't any functionality to handle a 1-to-many field->dependencies relationship). Removing a dependency is unimplemented - perhaps this should just be triggered whenever a relation field changes (other than selecting a suggestion)?

@Benaiah Benaiah force-pushed the editorial-workflow-dependency-tracking branch from 9faf2ed to abb067c Compare February 28, 2017 00:57
@Benaiah
Copy link
Contributor Author

Benaiah commented Feb 28, 2017

@biilmann I've pushed changes that address your comments. I've moved the dependency storage to entry.dependencies, renamed the registerDependency prop to onAddDependency, added an onRemoveDependency prop (this is currently a noop), and changed the immediately-resolving promises to use Promise.resolve.

I'm working on hooking this into the publishUnpublishedEntry action, and I noticed something - if I do the simplest implementation, the traversal doesn't work as the current entry is optimistically removed from the editorialWorkflow store on the publish request. I could either grab the dependencies from the entry object itself, which would be a bit messier as traverse would be implemented two different ways (once for the root entry, once for the rest); or I could only fire the publish request after the dependency check. Thoughts?

@calavera
Copy link
Contributor

calavera commented Mar 1, 2017

or I could only fire the publish request after the dependency check. Thoughts?

I think firing the publish request when the we're sure everything is correct might be more expected. @biilmann wdyt?

@Benaiah Benaiah force-pushed the editorial-workflow-dependency-tracking branch 4 times, most recently from 1dc91cc to eca9c97 Compare March 6, 2017 18:54
@Benaiah
Copy link
Contributor Author

Benaiah commented Mar 7, 2017

@calavera @biilmann I've pushed an updated version with all the functionality, including dependency removal. It currently uses window.confirm for UI - I think building out a custom modal interface should wait until the UI stuff has settled down a bit, and the search issue I've mentioned previously (unpublished dependencies don't show up in relation widget suggestions) means you can't register dependencies yet from the UI anyway.

@Benaiah Benaiah force-pushed the editorial-workflow-dependency-tracking branch 2 times, most recently from ea55a23 to 291eb13 Compare March 9, 2017 22:11
@Benaiah
Copy link
Contributor Author

Benaiah commented Mar 9, 2017

@calavera @biilmann I've clarified traverse a bit more by pulling the promise array reduction into its own function, reducePromises. The function seems pretty out-of-place in src/actions/editorialWorkflow.js though - what do you think about moving it somewhere, perhaps lib/promiseHelper.js?

I've also rebased this onto the latest master.

@Benaiah Benaiah force-pushed the editorial-workflow-dependency-tracking branch from 291eb13 to 65595a6 Compare March 11, 2017 01:00
@Benaiah
Copy link
Contributor Author

Benaiah commented Mar 11, 2017

@calavera @biilmann I've removed the temporary config.yml commit that I was using for testing.

@calavera
Copy link
Contributor

LGTM, but unfortunately, there are some conflicts.

@Benaiah
Copy link
Contributor Author

Benaiah commented Mar 11, 2017

@calavera I'll get it rebased again.

@Benaiah Benaiah force-pushed the editorial-workflow-dependency-tracking branch from 65595a6 to a1a8096 Compare March 11, 2017 22:53
@Benaiah
Copy link
Contributor Author

Benaiah commented Mar 11, 2017

Blech, tried to git in the morning and ended up accidentally squashing two commits. It looks like all the changes are still there, but the changes ended up in two commits instead of the four I had before. It is rebased onto the current master now, though.

- New actions, `UNPUBLISHED_ENTRY_REGISTER_DEPENDENCY` and
  `UNPUBLISHED_ENTRY_UNREGISTER_DEPENDENCY`, in
  `src/actions/editorialWorkflow.js`, with exported action creators.

- New reducers case in `src/reducers/entryDraft.js`
  for (un)registering dependencies to the current `entryDraft.entry`.

- `persistEntry` in `src/backends/backend.js` pulls dependency data
  out of `entryDraft` and saves it to metadata.

- New widget control props, `onAddDependency` and `onRemoveDependency`
  passed down to widget controls from `EntryPageHOC`. These call
  `(un)registerUnpublishedEntryDependency`.

- `RelationControl` calls `onAddDependency` when selecting a suggestion.
- New action constants in `src/actions/editorialWorkflow.js` -
  `UNPUBLISHED_ENTRY_DEPENDENCIES_REQUEST`,
  `UNPUBLISHED_ENTRY_DEPENDENCIES_SUCCESS`, and
  `UNPUBLISHED_ENTRY_DEPENDENCIES_FAILURE`, with exported thunk action
  creators.

- `loadUnpublishedEntry` returns its promise so it can be used to load
  dependencies.

- Add new thunk action `getUnpublishedEntryDependencies` to resolve
  the dependencies of a given entry.
- New action constants in `src/editorialWorkflow.js` -
  `UNPUBLISHED_ENTRIES_PUBLISH_REQUEST`,
  `UNPUBLISHED_ENTRIES_PUBLISH_SUCCESS`, and
  `UNPUBLISHED_ENTRIES_PUBLISH_FAILURE`, with exported thunk action
  creators.

- New backend and GitHub API function `publishUnpublishedEntries`.

- New reducer case in `src/reducers/editorialWorkflow.js` which
  updates editorialWorkflow state when publishing multiple entries.
@Benaiah Benaiah force-pushed the editorial-workflow-dependency-tracking branch from a1a8096 to d73a80d Compare March 11, 2017 23:35
@Benaiah
Copy link
Contributor Author

Benaiah commented Mar 11, 2017

@calavera, @biilmann OK, got that fixed. It's now rebased onto current master with the correct history (thanks, git reflog).

@Benaiah Benaiah changed the title [WIP] Editorial workflow dependency tracking Editorial workflow dependency tracking Mar 13, 2017
@Benaiah Benaiah force-pushed the editorial-workflow-dependency-tracking branch from d73a80d to f74b719 Compare March 14, 2017 18:44
@Benaiah
Copy link
Contributor Author

Benaiah commented Aug 11, 2017

I'm closing this PR for now, since it's quite old and not ready for release. I'll reopen this once work starts on it again, but I don't want anyone mistakenly reviewing what I have here since it needs to be rebased and I'm not currently working on it.

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.

Dependency system for relation fields & editorial workflow.
3 participants