From 295cdd2f6ddf9daa27e86628b18fa7acb35d0de2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Zen?= Date: Mon, 31 Oct 2016 18:19:51 -0200 Subject: [PATCH] Persistence editorial workflow through own actions & reducer --- src/actions/editorialWorkflow.js | 56 +++++++++++++------ .../editorialWorkflow/EntryPageHOC.js | 30 +++++++--- src/reducers/editorialWorkflow.js | 19 +++++-- 3 files changed, 74 insertions(+), 31 deletions(-) diff --git a/src/actions/editorialWorkflow.js b/src/actions/editorialWorkflow.js index 37a6ac71..0700c150 100644 --- a/src/actions/editorialWorkflow.js +++ b/src/actions/editorialWorkflow.js @@ -1,8 +1,12 @@ import uuid from 'uuid'; +import { actions as notifActions } from 'redux-notifications'; import { BEGIN, COMMIT, REVERT } from 'redux-optimist'; import { currentBackend } from '../backends/backend'; import { getMedia } from '../reducers'; -import { EDITORIAL_WORKFLOW } from '../constants/publishModes'; +import { status, EDITORIAL_WORKFLOW } from '../constants/publishModes'; + +const { notifSend } = notifActions; + /* * Contant Declarations */ @@ -15,12 +19,12 @@ export const UNPUBLISHED_ENTRIES_FAILURE = 'UNPUBLISHED_ENTRIES_FAILURE'; export const UNPUBLISHED_ENTRY_PERSIST_REQUEST = 'UNPUBLISHED_ENTRY_PERSIST_REQUEST'; export const UNPUBLISHED_ENTRY_PERSIST_SUCCESS = 'UNPUBLISHED_ENTRY_PERSIST_SUCCESS'; +export const UNPUBLISHED_ENTRY_PERSIST_FAILURE = 'UNPUBLISHED_ENTRY_PERSIST_FAILURE'; export const UNPUBLISHED_ENTRY_STATUS_CHANGE_REQUEST = 'UNPUBLISHED_ENTRY_STATUS_CHANGE_REQUEST'; export const UNPUBLISHED_ENTRY_STATUS_CHANGE_SUCCESS = 'UNPUBLISHED_ENTRY_STATUS_CHANGE_SUCCESS'; export const UNPUBLISHED_ENTRY_STATUS_CHANGE_FAILURE = 'UNPUBLISHED_ENTRY_STATUS_CHANGE_FAILURE'; - export const UNPUBLISHED_ENTRY_PUBLISH_REQUEST = 'UNPUBLISHED_ENTRY_PUBLISH_REQUEST'; export const UNPUBLISHED_ENTRY_PUBLISH_SUCCESS = 'UNPUBLISHED_ENTRY_PUBLISH_SUCCESS'; export const UNPUBLISHED_ENTRY_PUBLISH_FAILURE = 'UNPUBLISHED_ENTRY_PUBLISH_FAILURE'; @@ -68,24 +72,27 @@ function unpublishedEntriesFailed(error) { } -function unpublishedEntryPersisting(entry) { +function unpublishedEntryPersisting(collection, entry, transactionID) { return { type: UNPUBLISHED_ENTRY_PERSIST_REQUEST, - payload: { entry }, + payload: { collection, entry }, + optimist: { type: BEGIN, id: transactionID }, }; } -function unpublishedEntryPersisted(entry) { +function unpublishedEntryPersisted(collection, entry, transactionID) { return { type: UNPUBLISHED_ENTRY_PERSIST_SUCCESS, - payload: { entry }, + payload: { collection, entry }, + optimist: { type: COMMIT, id: transactionID }, }; } -function unpublishedEntryPersistedFail(error) { +function unpublishedEntryPersistedFail(error, transactionID) { return { - type: UNPUBLISHED_ENTRY_PERSIST_SUCCESS, + type: UNPUBLISHED_ENTRY_PERSIST_FAILURE, payload: { error }, + optimist: { type: REVERT, id: transactionID }, }; } @@ -164,18 +171,33 @@ export function loadUnpublishedEntries() { }; } -export function persistUnpublishedEntry(collection, entry) { +export function persistUnpublishedEntry(collection, entryDraft, existingUnpublishedEntry) { return (dispatch, getState) => { const state = getState(); const backend = currentBackend(state.config); - const MediaProxies = entry && entry.get('mediaFiles').map(path => getMedia(state, path)); - dispatch(unpublishedEntryPersisting(entry)); - backend.persistUnpublishedEntry(state.config, collection, entry, MediaProxies.toJS()).then( - () => { - dispatch(unpublishedEntryPersisted(entry)); - }, - error => dispatch(unpublishedEntryPersistedFail(error)) - ); + const mediaProxies = entryDraft.get('mediaFiles').map(path => getMedia(state, path)); + const entry = entryDraft.get('entry'); + const transactionID = uuid.v4(); + + dispatch(unpublishedEntryPersisting(collection, entry, transactionID)); + const persistAction = existingUnpublishedEntry ? backend.persistUnpublishedEntry : backend.persistEntry; + persistAction.call(backend, state.config, collection, entryDraft, mediaProxies.toJS()) + .then(() => { + dispatch(notifSend({ + message: 'Entry saved', + kind: 'success', + dismissAfter: 4000, + })); + dispatch(unpublishedEntryPersisted(collection, entry, transactionID)); + }) + .catch((error) => { + dispatch(notifSend({ + message: 'Failed to persist entry', + kind: 'danger', + dismissAfter: 4000, + })); + dispatch(unpublishedEntryPersistedFail(error, transactionID)); + }); }; } diff --git a/src/containers/editorialWorkflow/EntryPageHOC.js b/src/containers/editorialWorkflow/EntryPageHOC.js index 486b8779..50ad983a 100644 --- a/src/containers/editorialWorkflow/EntryPageHOC.js +++ b/src/containers/editorialWorkflow/EntryPageHOC.js @@ -1,8 +1,9 @@ import React from 'react'; +import { connect } from 'react-redux'; import { EDITORIAL_WORKFLOW } from '../../constants/publishModes'; import { selectUnpublishedEntry } from '../../reducers'; import { loadUnpublishedEntry, persistUnpublishedEntry } from '../../actions/editorialWorkflow'; -import { connect } from 'react-redux'; + export default function EntryPageHOC(EntryPage) { class EntryPageHOC extends React.Component { @@ -12,11 +13,10 @@ export default function EntryPageHOC(EntryPage) { } function mapStateToProps(state, ownProps) { - const publish_mode = state.config.get('publish_mode'); - const isEditorialWorkflow = (publish_mode === EDITORIAL_WORKFLOW); + const isEditorialWorkflow = (state.config.get('publish_mode') === EDITORIAL_WORKFLOW); const unpublishedEntry = ownProps.route && ownProps.route.unpublishedEntry === true; - const returnObj = {}; + const returnObj = { isEditorialWorkflow }; if (isEditorialWorkflow && unpublishedEntry) { const status = ownProps.params.status; const slug = ownProps.params.slug; @@ -26,22 +26,34 @@ export default function EntryPageHOC(EntryPage) { return returnObj; } - function mapDispatchToProps(dispatch, ownProps) { + function mergeProps(stateProps, dispatchProps, ownProps) { + const { isEditorialWorkflow } = stateProps; + const { dispatch } = dispatchProps; + const unpublishedEntry = ownProps.route && ownProps.route.unpublishedEntry === true; + const status = ownProps.params.status; + const returnObj = {}; + if (unpublishedEntry) { // Overwrite loadEntry to loadUnpublishedEntry - const status = ownProps.params.status; returnObj.loadEntry = (entry, collection, slug) => { dispatch(loadUnpublishedEntry(collection, status, slug)); }; + } + if (isEditorialWorkflow) { + // Overwrite persistEntry to persistUnpublishedEntry returnObj.persistEntry = (collection, entryDraft) => { - dispatch(persistUnpublishedEntry(collection, entryDraft)); + dispatch(persistUnpublishedEntry(collection, entryDraft, unpublishedEntry)); }; } - return returnObj; + + return { + ...ownProps, + ...returnObj, + }; } - return connect(mapStateToProps, mapDispatchToProps)(EntryPageHOC); + return connect(mapStateToProps, null, mergeProps)(EntryPageHOC); } diff --git a/src/reducers/editorialWorkflow.js b/src/reducers/editorialWorkflow.js index 42b883aa..b426d1c0 100644 --- a/src/reducers/editorialWorkflow.js +++ b/src/reducers/editorialWorkflow.js @@ -1,25 +1,25 @@ import { Map, List, fromJS } from 'immutable'; -import { EDITORIAL_WORKFLOW } from '../constants/publishModes'; +import { status, EDITORIAL_WORKFLOW } from '../constants/publishModes'; import { UNPUBLISHED_ENTRY_REQUEST, UNPUBLISHED_ENTRY_SUCCESS, UNPUBLISHED_ENTRIES_REQUEST, UNPUBLISHED_ENTRIES_SUCCESS, + UNPUBLISHED_ENTRY_PERSIST_REQUEST, UNPUBLISHED_ENTRY_STATUS_CHANGE_REQUEST, UNPUBLISHED_ENTRY_PUBLISH_REQUEST, } from '../actions/editorialWorkflow'; import { CONFIG_SUCCESS } from '../actions/config'; const unpublishedEntries = (state = null, action) => { + const publishMode = action.payload && action.payload.publish_mode; switch (action.type) { case CONFIG_SUCCESS: - const publish_mode = action.payload && action.payload.publish_mode; - if (publish_mode === EDITORIAL_WORKFLOW) { + if (publishMode === EDITORIAL_WORKFLOW) { // Editorial workflow state is explicetelly initiated after the config. return Map({ entities: Map(), pages: Map() }); - } else { - return state; } + return state; case UNPUBLISHED_ENTRY_REQUEST: return state.setIn(['entities', `${ action.payload.status }.${ action.payload.slug }`, 'isFetching'], true); @@ -45,6 +45,15 @@ const unpublishedEntries = (state = null, action) => { })); }); + case UNPUBLISHED_ENTRY_PERSIST_REQUEST: + // Update Optimistically + const { collection, entry } = action.payload; + const ownStatus = entry.getIn(['metaData', 'status'], status.first()); + return state.withMutations((map) => { + map.setIn(['entities', `${ ownStatus }.${ entry.get('slug') }`], fromJS(entry)); + map.updateIn(['pages', 'ids'], List(), list => list.push(entry.get('slug'))); + }); + case UNPUBLISHED_ENTRY_STATUS_CHANGE_REQUEST: // Update Optimistically return state.withMutations((map) => {