static-cms/src/actions/editorialWorkflow.js

404 lines
12 KiB
JavaScript

import uuid from 'uuid/v4';
import { actions as notifActions } from 'redux-notifications';
import { BEGIN, COMMIT, REVERT } from 'redux-optimist';
import { serializeValues } from 'Lib/serializeEntryValues';
import { currentBackend } from 'Backends/backend';
import { getAsset } from 'Reducers';
import { selectFields } from 'Reducers/collections';
import { status, EDITORIAL_WORKFLOW } from 'Constants/publishModes';
import { EditorialWorkflowError } from "ValueObjects/errors";
import { loadEntry } from './entries';
import ValidationErrorTypes from 'Constants/validationErrorTypes';
const { notifSend } = notifActions;
/*
* Contant Declarations
*/
export const UNPUBLISHED_ENTRY_REQUEST = 'UNPUBLISHED_ENTRY_REQUEST';
export const UNPUBLISHED_ENTRY_SUCCESS = 'UNPUBLISHED_ENTRY_SUCCESS';
export const UNPUBLISHED_ENTRY_REDIRECT = 'UNPUBLISHED_ENTRY_REDIRECT';
export const UNPUBLISHED_ENTRIES_REQUEST = 'UNPUBLISHED_ENTRIES_REQUEST';
export const UNPUBLISHED_ENTRIES_SUCCESS = 'UNPUBLISHED_ENTRIES_SUCCESS';
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';
export const UNPUBLISHED_ENTRY_DELETE_REQUEST = 'UNPUBLISHED_ENTRY_DELETE_REQUEST';
export const UNPUBLISHED_ENTRY_DELETE_SUCCESS = 'UNPUBLISHED_ENTRY_DELETE_SUCCESS';
export const UNPUBLISHED_ENTRY_DELETE_FAILURE = 'UNPUBLISHED_ENTRY_DELETE_FAILURE';
/*
* Simple Action Creators (Internal)
*/
function unpublishedEntryLoading(collection, slug) {
return {
type: UNPUBLISHED_ENTRY_REQUEST,
payload: {
collection: collection.get('name'),
slug,
},
};
}
function unpublishedEntryLoaded(collection, entry) {
return {
type: UNPUBLISHED_ENTRY_SUCCESS,
payload: {
collection: collection.get('name'),
entry,
},
};
}
function unpublishedEntryRedirected(collection, slug) {
return {
type: UNPUBLISHED_ENTRY_REDIRECT,
payload: {
collection: collection.get('name'),
slug,
},
};
}
function unpublishedEntriesLoading() {
return {
type: UNPUBLISHED_ENTRIES_REQUEST,
};
}
function unpublishedEntriesLoaded(entries, pagination) {
return {
type: UNPUBLISHED_ENTRIES_SUCCESS,
payload: {
entries,
pages: pagination,
},
};
}
function unpublishedEntriesFailed(error) {
return {
type: UNPUBLISHED_ENTRIES_FAILURE,
error: 'Failed to load entries',
payload: error,
};
}
function unpublishedEntryPersisting(collection, entry, transactionID) {
return {
type: UNPUBLISHED_ENTRY_PERSIST_REQUEST,
payload: {
collection: collection.get('name'),
entry,
},
optimist: { type: BEGIN, id: transactionID },
};
}
function unpublishedEntryPersisted(collection, entry, transactionID, slug) {
return {
type: UNPUBLISHED_ENTRY_PERSIST_SUCCESS,
payload: {
collection: collection.get('name'),
entry,
slug,
},
optimist: { type: COMMIT, id: transactionID },
};
}
function unpublishedEntryPersistedFail(error, transactionID) {
return {
type: UNPUBLISHED_ENTRY_PERSIST_FAILURE,
payload: { error },
optimist: { type: REVERT, id: transactionID },
error,
};
}
function unpublishedEntryStatusChangeRequest(collection, slug, oldStatus, newStatus, transactionID) {
return {
type: UNPUBLISHED_ENTRY_STATUS_CHANGE_REQUEST,
payload: {
collection,
slug,
oldStatus,
newStatus,
},
optimist: { type: BEGIN, id: transactionID },
};
}
function unpublishedEntryStatusChangePersisted(collection, slug, oldStatus, newStatus, transactionID) {
return {
type: UNPUBLISHED_ENTRY_STATUS_CHANGE_SUCCESS,
payload: {
collection,
slug,
oldStatus,
newStatus,
},
optimist: { type: COMMIT, id: transactionID },
};
}
function unpublishedEntryStatusChangeError(collection, slug, transactionID) {
return {
type: UNPUBLISHED_ENTRY_STATUS_CHANGE_FAILURE,
payload: { collection, slug },
optimist: { type: REVERT, id: transactionID },
};
}
function unpublishedEntryPublishRequest(collection, slug, transactionID) {
return {
type: UNPUBLISHED_ENTRY_PUBLISH_REQUEST,
payload: { collection, slug },
optimist: { type: BEGIN, id: transactionID },
};
}
function unpublishedEntryPublished(collection, slug, transactionID) {
return {
type: UNPUBLISHED_ENTRY_PUBLISH_SUCCESS,
payload: { collection, slug },
optimist: { type: COMMIT, id: transactionID },
};
}
function unpublishedEntryPublishError(collection, slug, transactionID) {
return {
type: UNPUBLISHED_ENTRY_PUBLISH_FAILURE,
payload: { collection, slug },
optimist: { type: REVERT, id: transactionID },
};
}
function unpublishedEntryDeleteRequest(collection, slug, transactionID) {
return {
type: UNPUBLISHED_ENTRY_DELETE_REQUEST,
payload: { collection, slug },
optimist: { type: BEGIN, id: transactionID },
};
}
function unpublishedEntryDeleted(collection, slug, transactionID) {
return {
type: UNPUBLISHED_ENTRY_DELETE_SUCCESS,
payload: { collection, slug },
optimist: { type: COMMIT, id: transactionID },
};
}
function unpublishedEntryDeleteError(collection, slug, transactionID) {
return {
type: UNPUBLISHED_ENTRY_DELETE_FAILURE,
payload: { collection, slug },
optimist: { type: REVERT, id: transactionID },
};
}
/*
* Exported Thunk Action Creators
*/
export function loadUnpublishedEntry(collection, slug) {
return (dispatch, getState) => {
const state = getState();
const backend = currentBackend(state.config);
dispatch(unpublishedEntryLoading(collection, slug));
backend.unpublishedEntry(collection, slug)
.then(entry => dispatch(unpublishedEntryLoaded(collection, entry)))
.catch((error) => {
if (error instanceof EditorialWorkflowError && error.notUnderEditorialWorkflow) {
dispatch(unpublishedEntryRedirected(collection, slug));
dispatch(loadEntry(collection, slug));
} else {
dispatch(notifSend({
message: `Error loading entry: ${ error }`,
kind: 'danger',
dismissAfter: 8000,
}));
}
});
};
}
export function loadUnpublishedEntries(collections) {
return (dispatch, getState) => {
const state = getState();
if (state.config.get('publish_mode') !== EDITORIAL_WORKFLOW) return;
const backend = currentBackend(state.config);
dispatch(unpublishedEntriesLoading());
backend.unpublishedEntries(collections)
.then(response => dispatch(unpublishedEntriesLoaded(response.entries, response.pagination)))
.catch(error => {
dispatch(notifSend({
message: `Error loading entries: ${ error }`,
kind: 'danger',
dismissAfter: 8000,
}));
dispatch(unpublishedEntriesFailed(error));
Promise.reject(error)
});
};
}
export function persistUnpublishedEntry(collection, existingUnpublishedEntry) {
return async (dispatch, getState) => {
const state = getState();
const entryDraft = state.entryDraft;
const fieldsErrors = entryDraft.get('fieldsErrors');
// Early return if draft contains validation errors
if (!fieldsErrors.isEmpty()) {
const hasPresenceErrors = fieldsErrors
.some(errors => errors.some(error => error.type && error.type === ValidationErrorTypes.PRESENCE));
if (hasPresenceErrors) {
dispatch(notifSend({
message: 'Oops, you\'ve missed a required field. Please complete before saving.',
kind: 'danger',
dismissAfter: 8000,
}));
}
return Promise.reject()
}
const backend = currentBackend(state.config);
const transactionID = uuid();
const assetProxies = entryDraft.get('mediaFiles').map(path => getAsset(state, path));
const entry = entryDraft.get('entry');
/**
* Serialize the values of any fields with registered serializers, and
* update the entry and entryDraft with the serialized values.
*/
const fields = selectFields(collection, entry.get('slug'));
const serializedData = serializeValues(entryDraft.getIn(['entry', 'data']), fields);
const serializedEntry = entry.set('data', serializedData);
const serializedEntryDraft = entryDraft.set('entry', serializedEntry);
dispatch(unpublishedEntryPersisting(collection, serializedEntry, transactionID));
const persistAction = existingUnpublishedEntry ? backend.persistUnpublishedEntry : backend.persistEntry;
const persistCallArgs = [
backend,
state.config,
collection,
serializedEntryDraft,
assetProxies.toJS(),
state.integrations,
];
try {
const newSlug = await persistAction.call(...persistCallArgs);
dispatch(notifSend({
message: 'Entry saved',
kind: 'success',
dismissAfter: 4000,
}));
dispatch(unpublishedEntryPersisted(collection, serializedEntry, transactionID, newSlug));
}
catch(error) {
dispatch(notifSend({
message: `Failed to persist entry: ${ error }`,
kind: 'danger',
dismissAfter: 8000,
}));
return Promise.reject(dispatch(unpublishedEntryPersistedFail(error, transactionID)));
}
};
}
export function updateUnpublishedEntryStatus(collection, slug, oldStatus, newStatus) {
return (dispatch, getState) => {
const state = getState();
const backend = currentBackend(state.config);
const transactionID = uuid();
dispatch(unpublishedEntryStatusChangeRequest(collection, slug, oldStatus, newStatus, transactionID));
backend.updateUnpublishedEntryStatus(collection, slug, newStatus)
.then(() => {
dispatch(notifSend({
message: 'Entry status updated',
kind: 'success',
dismissAfter: 4000,
}));
dispatch(unpublishedEntryStatusChangePersisted(collection, slug, oldStatus, newStatus, transactionID));
})
.catch((error) => {
dispatch(notifSend({
message: `Failed to update status: ${ error }`,
kind: 'danger',
dismissAfter: 8000,
}));
dispatch(unpublishedEntryStatusChangeError(collection, slug, transactionID));
});
};
}
export function deleteUnpublishedEntry(collection, slug) {
return (dispatch, getState) => {
const state = getState();
const backend = currentBackend(state.config);
const transactionID = uuid();
dispatch(unpublishedEntryDeleteRequest(collection, slug, transactionID));
return backend.deleteUnpublishedEntry(collection, slug)
.then(() => {
dispatch(notifSend({
message: 'Unpublished changes deleted',
kind: 'success',
dismissAfter: 4000,
}));
dispatch(unpublishedEntryDeleted(collection, slug, transactionID));
})
.catch((error) => {
dispatch(notifSend({
message: `Failed to delete unpublished changes: ${ error }`,
kind: 'danger',
dismissAfter: 8000,
}));
dispatch(unpublishedEntryDeleteError(collection, slug, transactionID));
});
};
}
export function publishUnpublishedEntry(collection, slug) {
return (dispatch, getState) => {
const state = getState();
const backend = currentBackend(state.config);
const transactionID = uuid();
dispatch(unpublishedEntryPublishRequest(collection, slug, transactionID));
return backend.publishUnpublishedEntry(collection, slug)
.then(() => {
dispatch(notifSend({
message: 'Entry published',
kind: 'success',
dismissAfter: 4000,
}));
dispatch(unpublishedEntryPublished(collection, slug, transactionID));
})
.catch((error) => {
dispatch(notifSend({
message: `Failed to publish: ${ error }`,
kind: 'danger',
dismissAfter: 8000,
}));
dispatch(unpublishedEntryPublishError(collection, slug, transactionID));
});
};
}