Persistence editorial workflow through own actions & reducer

This commit is contained in:
Cássio Zen 2016-10-31 18:19:51 -02:00
parent dd71b59e9e
commit 295cdd2f6d
3 changed files with 74 additions and 31 deletions

View File

@ -1,8 +1,12 @@
import uuid from 'uuid'; import uuid from 'uuid';
import { actions as notifActions } from 'redux-notifications';
import { BEGIN, COMMIT, REVERT } from 'redux-optimist'; import { BEGIN, COMMIT, REVERT } from 'redux-optimist';
import { currentBackend } from '../backends/backend'; import { currentBackend } from '../backends/backend';
import { getMedia } from '../reducers'; import { getMedia } from '../reducers';
import { EDITORIAL_WORKFLOW } from '../constants/publishModes'; import { status, EDITORIAL_WORKFLOW } from '../constants/publishModes';
const { notifSend } = notifActions;
/* /*
* Contant Declarations * 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_REQUEST = 'UNPUBLISHED_ENTRY_PERSIST_REQUEST';
export const UNPUBLISHED_ENTRY_PERSIST_SUCCESS = 'UNPUBLISHED_ENTRY_PERSIST_SUCCESS'; 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_REQUEST = 'UNPUBLISHED_ENTRY_STATUS_CHANGE_REQUEST';
export const UNPUBLISHED_ENTRY_STATUS_CHANGE_SUCCESS = 'UNPUBLISHED_ENTRY_STATUS_CHANGE_SUCCESS'; 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_STATUS_CHANGE_FAILURE = 'UNPUBLISHED_ENTRY_STATUS_CHANGE_FAILURE';
export const UNPUBLISHED_ENTRY_PUBLISH_REQUEST = 'UNPUBLISHED_ENTRY_PUBLISH_REQUEST'; 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_SUCCESS = 'UNPUBLISHED_ENTRY_PUBLISH_SUCCESS';
export const UNPUBLISHED_ENTRY_PUBLISH_FAILURE = 'UNPUBLISHED_ENTRY_PUBLISH_FAILURE'; 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 { return {
type: UNPUBLISHED_ENTRY_PERSIST_REQUEST, type: UNPUBLISHED_ENTRY_PERSIST_REQUEST,
payload: { entry }, payload: { collection, entry },
optimist: { type: BEGIN, id: transactionID },
}; };
} }
function unpublishedEntryPersisted(entry) { function unpublishedEntryPersisted(collection, entry, transactionID) {
return { return {
type: UNPUBLISHED_ENTRY_PERSIST_SUCCESS, type: UNPUBLISHED_ENTRY_PERSIST_SUCCESS,
payload: { entry }, payload: { collection, entry },
optimist: { type: COMMIT, id: transactionID },
}; };
} }
function unpublishedEntryPersistedFail(error) { function unpublishedEntryPersistedFail(error, transactionID) {
return { return {
type: UNPUBLISHED_ENTRY_PERSIST_SUCCESS, type: UNPUBLISHED_ENTRY_PERSIST_FAILURE,
payload: { error }, 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) => { return (dispatch, getState) => {
const state = getState(); const state = getState();
const backend = currentBackend(state.config); const backend = currentBackend(state.config);
const MediaProxies = entry && entry.get('mediaFiles').map(path => getMedia(state, path)); const mediaProxies = entryDraft.get('mediaFiles').map(path => getMedia(state, path));
dispatch(unpublishedEntryPersisting(entry)); const entry = entryDraft.get('entry');
backend.persistUnpublishedEntry(state.config, collection, entry, MediaProxies.toJS()).then( const transactionID = uuid.v4();
() => {
dispatch(unpublishedEntryPersisted(entry)); dispatch(unpublishedEntryPersisting(collection, entry, transactionID));
}, const persistAction = existingUnpublishedEntry ? backend.persistUnpublishedEntry : backend.persistEntry;
error => dispatch(unpublishedEntryPersistedFail(error)) 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));
});
}; };
} }

View File

@ -1,8 +1,9 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux';
import { EDITORIAL_WORKFLOW } from '../../constants/publishModes'; import { EDITORIAL_WORKFLOW } from '../../constants/publishModes';
import { selectUnpublishedEntry } from '../../reducers'; import { selectUnpublishedEntry } from '../../reducers';
import { loadUnpublishedEntry, persistUnpublishedEntry } from '../../actions/editorialWorkflow'; import { loadUnpublishedEntry, persistUnpublishedEntry } from '../../actions/editorialWorkflow';
import { connect } from 'react-redux';
export default function EntryPageHOC(EntryPage) { export default function EntryPageHOC(EntryPage) {
class EntryPageHOC extends React.Component { class EntryPageHOC extends React.Component {
@ -12,11 +13,10 @@ export default function EntryPageHOC(EntryPage) {
} }
function mapStateToProps(state, ownProps) { function mapStateToProps(state, ownProps) {
const publish_mode = state.config.get('publish_mode'); const isEditorialWorkflow = (state.config.get('publish_mode') === EDITORIAL_WORKFLOW);
const isEditorialWorkflow = (publish_mode === EDITORIAL_WORKFLOW);
const unpublishedEntry = ownProps.route && ownProps.route.unpublishedEntry === true; const unpublishedEntry = ownProps.route && ownProps.route.unpublishedEntry === true;
const returnObj = {}; const returnObj = { isEditorialWorkflow };
if (isEditorialWorkflow && unpublishedEntry) { if (isEditorialWorkflow && unpublishedEntry) {
const status = ownProps.params.status; const status = ownProps.params.status;
const slug = ownProps.params.slug; const slug = ownProps.params.slug;
@ -26,22 +26,34 @@ export default function EntryPageHOC(EntryPage) {
return returnObj; 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 unpublishedEntry = ownProps.route && ownProps.route.unpublishedEntry === true;
const status = ownProps.params.status;
const returnObj = {}; const returnObj = {};
if (unpublishedEntry) { if (unpublishedEntry) {
// Overwrite loadEntry to loadUnpublishedEntry // Overwrite loadEntry to loadUnpublishedEntry
const status = ownProps.params.status;
returnObj.loadEntry = (entry, collection, slug) => { returnObj.loadEntry = (entry, collection, slug) => {
dispatch(loadUnpublishedEntry(collection, status, slug)); dispatch(loadUnpublishedEntry(collection, status, slug));
}; };
}
if (isEditorialWorkflow) {
// Overwrite persistEntry to persistUnpublishedEntry
returnObj.persistEntry = (collection, entryDraft) => { 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);
} }

View File

@ -1,25 +1,25 @@
import { Map, List, fromJS } from 'immutable'; import { Map, List, fromJS } from 'immutable';
import { EDITORIAL_WORKFLOW } from '../constants/publishModes'; import { status, EDITORIAL_WORKFLOW } from '../constants/publishModes';
import { import {
UNPUBLISHED_ENTRY_REQUEST, UNPUBLISHED_ENTRY_REQUEST,
UNPUBLISHED_ENTRY_SUCCESS, UNPUBLISHED_ENTRY_SUCCESS,
UNPUBLISHED_ENTRIES_REQUEST, UNPUBLISHED_ENTRIES_REQUEST,
UNPUBLISHED_ENTRIES_SUCCESS, UNPUBLISHED_ENTRIES_SUCCESS,
UNPUBLISHED_ENTRY_PERSIST_REQUEST,
UNPUBLISHED_ENTRY_STATUS_CHANGE_REQUEST, UNPUBLISHED_ENTRY_STATUS_CHANGE_REQUEST,
UNPUBLISHED_ENTRY_PUBLISH_REQUEST, UNPUBLISHED_ENTRY_PUBLISH_REQUEST,
} from '../actions/editorialWorkflow'; } from '../actions/editorialWorkflow';
import { CONFIG_SUCCESS } from '../actions/config'; import { CONFIG_SUCCESS } from '../actions/config';
const unpublishedEntries = (state = null, action) => { const unpublishedEntries = (state = null, action) => {
const publishMode = action.payload && action.payload.publish_mode;
switch (action.type) { switch (action.type) {
case CONFIG_SUCCESS: case CONFIG_SUCCESS:
const publish_mode = action.payload && action.payload.publish_mode; if (publishMode === EDITORIAL_WORKFLOW) {
if (publish_mode === EDITORIAL_WORKFLOW) {
// Editorial workflow state is explicetelly initiated after the config. // Editorial workflow state is explicetelly initiated after the config.
return Map({ entities: Map(), pages: Map() }); return Map({ entities: Map(), pages: Map() });
} else {
return state;
} }
return state;
case UNPUBLISHED_ENTRY_REQUEST: case UNPUBLISHED_ENTRY_REQUEST:
return state.setIn(['entities', `${ action.payload.status }.${ action.payload.slug }`, 'isFetching'], true); 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: case UNPUBLISHED_ENTRY_STATUS_CHANGE_REQUEST:
// Update Optimistically // Update Optimistically
return state.withMutations((map) => { return state.withMutations((map) => {