diff --git a/src/actions/editorialWorkflow.js b/src/actions/editorialWorkflow.js index 93a22d2252dc..a7e8dfc98e86 100644 --- a/src/actions/editorialWorkflow.js +++ b/src/actions/editorialWorkflow.js @@ -1,4 +1,5 @@ import uuid from 'uuid'; +import Immutable from 'immutable'; import { actions as notifActions } from 'redux-notifications'; import { closeEntry } from './editor'; import { BEGIN, COMMIT, REVERT } from 'redux-optimist'; @@ -35,6 +36,10 @@ export const UNPUBLISHED_ENTRY_PUBLISH_FAILURE = 'UNPUBLISHED_ENTRY_PUBLISH_FAIL export const UNPUBLISHED_ENTRY_REGISTER_DEPENDENCY = 'UNPUBLISHED_ENTRY_REGISTER_DEPENDENCY'; +export const UNPUBLISHED_ENTRY_DEPENDENCIES_REQUEST = 'UNPUBLISHED_ENTRY_DEPENDENCIES_REQUEST'; +export const UNPUBLISHED_ENTRY_DEPENDENCIES_SUCCESS = 'UNPUBLISHED_ENTRY_DEPENDENCIES_SUCCESS'; +export const UNPUBLISHED_ENTRY_DEPENDENCIES_FAILURE = 'UNPUBLISHED_ENTRY_DEPENDENCIES_FAILURE'; + /* * Simple Action Creators (Internal) */ @@ -185,7 +190,28 @@ function unpublishedEntryPublishError(collection, slug, transactionID) { function unpublishedEntryRegisterDependency(field, collection, slug) { return { type: UNPUBLISHED_ENTRY_REGISTER_DEPENDENCY, - payload: {field, collection, slug}, + payload: { field, collection, slug }, + }; +} + +function unpublishedEntryDependenciesRequest(collection, slug) { + return { + type: UNPUBLISHED_ENTRY_DEPENDENCIES_REQUEST, + payload: { collection, slug }, + }; +} + +function unpublishedEntryDependenciesSuccess(collection, slug, dependencies) { + return { + type: UNPUBLISHED_ENTRY_DEPENDENCIES_SUCCESS, + payload: { collection, slug, dependencies }, + }; +} + +function unpublishedEntryDependenciesError(collection, slug, error) { + return { + type: UNPUBLISHED_ENTRY_DEPENDENCIES_FAILURE, + payload: { collection, slug, error }, }; } @@ -193,14 +219,12 @@ function unpublishedEntryRegisterDependency(field, collection, slug) { * Exported Thunk Action Creators */ -export const registerUnpublishedEntryDependency = unpublishedEntryRegisterDependency; - export function loadUnpublishedEntry(collection, slug) { return (dispatch, getState) => { const state = getState(); const backend = currentBackend(state.config); dispatch(unpublishedEntryLoading(collection, slug)); - backend.unpublishedEntry(collection, slug) + return backend.unpublishedEntry(collection, slug) .then(entry => dispatch(unpublishedEntryLoaded(collection, entry))) .catch((error) => { if (error instanceof EditorialWorkflowError && error.notUnderEditorialWorkflow) { @@ -302,3 +326,66 @@ export function publishUnpublishedEntry(collection, slug) { }); }; } + +export const registerUnpublishedEntryDependency = unpublishedEntryRegisterDependency; + +const getDepsPath = dep => [ + "entities", + dep, + "metaData", + "dependencies", +]; + +const getEventualDependencies = (paths, loadedDeps, state, dispatch) => + paths.filter(path => !loadedDeps.includes(path)).map((path) => { + const [pathCollectionName, pathSlug] = path.split("."); + const pathCollection = state.collections.get(pathCollectionName); + // Wait for the entry to load + return dispatch(loadUnpublishedEntry(pathCollection, pathSlug)) + // Return the path at the end so we can use it in .thens later + .then(() => path); + }); + +const traverse = (collectedDeps, path, getState, dispatch) => { + const state = getState(); + + if (collectedDeps.get(path) === true) { + return Promise.resolve(collectedDeps); + } + + const newDeps = collectedDeps.set(path, true); + const depsPath = getDepsPath(path); + + if (!state.editorialWorkflow.hasIn(depsPath) || + state.editorialWorkflow.getIn(depsPath).size === 0) { + // This path has no dependencies + return Promise.resolve(newDeps); + } + + const theseDependencies = state.editorialWorkflow.getIn(depsPath); + + const eventualDeps = getEventualDependencies(theseDependencies, collectedDeps, state, dispatch); + + // This is a manual version of Bluebird's Promise.reduce + return eventualDeps.reduce((depsPromise, depPromise) => + Promise.all([depsPromise, depPromise]).then( + ([deps, dep]) => traverse(deps, dep, getState, dispatch)), + Promise.resolve(newDeps)); +}; + +export function getUnpublishedEntryDependencies(collection, slug) { + return (dispatch, getState) => { + dispatch(unpublishedEntryDependenciesRequest(collection, slug)); + + // Begin traversal + return traverse(new Immutable.Map(), `${ collection }.${ slug }`, getState, dispatch) + .then((dependencyMap) => { + const state = getState(); + const dependencies = dependencyMap.keySeq().toList(); + const filteredDependencies = dependencies.filter( + dep => state.editorialWorkflow.hasIn(["entities", dep])); + return dispatch(unpublishedEntryDependenciesSuccess(collection, slug, filteredDependencies)); + }) + .catch(err => dispatch(unpublishedEntryDependenciesError(collection, slug, err))); + }; +}