Skip to content

Commit

Permalink
FIX save changes before calling publish mutation
Browse files Browse the repository at this point in the history
  • Loading branch information
Dylan Wagstaff committed Oct 25, 2018
1 parent 87a71e3 commit 26b7ed0
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 43 deletions.
2 changes: 1 addition & 1 deletion client/dist/js/bundle.js

Large diffs are not rendered by default.

159 changes: 119 additions & 40 deletions client/src/components/ElementActions/PublishAction.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,53 +4,117 @@ import { compose } from 'redux';
import AbstractAction from 'components/ElementActions/AbstractAction';
import publishBlockMutation from 'state/editor/publishBlockMutation';
import i18n from 'i18n';
import backend from 'lib/Backend';
import { connect } from 'react-redux';
import { loadElementSchemaValue } from 'state/editor/loadElementSchemaValue';
import { loadElementFormStateName } from 'state/editor/loadElementFormStateName';

/**
* Show a toast message reporting whether publication of Element was successful
*
* @param {string} elementType E.g. "Content" - human friendly element type (not PHP FQCN)
* @param {string} title Title of the element, or a false value if unset (e.g. undefined)
* @param {bool} success Show a success message (true), or an error message (false)
*/
const reportPublicationStatus = (elementType, title, success) => {
const noTitle = i18n.inject(
i18n._t('ElementHeader.NOTITLE', 'Untitled {elementType} block'),
{ elementType }
);
const successMessage = i18n.inject(
i18n._t('UnpublishAction.SUCCESS_NOTIFICATION', 'Published \'{title}\' successfully'),
{ title: title || noTitle }
);
const errorMessage = i18n.inject(
i18n._t('UnpublishAction.ERROR_NOTIFICATION', 'Error publishing \'{title}\''),
{ title: title || noTitle }
);
window.jQuery.noticeAdd({
text: success ? successMessage : errorMessage,
stay: false,
type: success ? 'success' : 'error',
});
};

/**
* Post updated Element data to save it
*
* @param {number} id Element ID
* @param {object} formData Information to be saved
* @param {string} securityId Security ID for form submission
*/
const performSaveForElementWithFormData = (id, formData, securityId) => {
const saveEndpoint = backend.createEndpointFetcher({
url: loadElementSchemaValue('saveUrl', id),
method: loadElementSchemaValue('saveMethod'),
payloadFormat: loadElementSchemaValue('payloadFormat'),
defaultData: {
SecurityID: securityId
},
});

// Perform save & get new version number to publish
return saveEndpoint(formData)
.then(() => window.ss.apolloClient.queryManager.reFetchObservableQueries())
.then((input) => {
const preview = window.jQuery('.cms-preview');
preview.entwine('ss.preview')._loadUrl(preview.find('iframe').attr('src'));
return input;
})
.then((newPageData) => {
const newElementData = newPageData[0] && newPageData[0]
.data
.readOnePage
.ElementalAreaIfExists
.Elements
.edges
.find((elementData) => elementData.node.ID === id);
return newElementData && newElementData.node.Version;
});
};

/**
* Detect if there has been an input change to the Element's data
* which will have to be saved before publishing
*
* @param {number} id Element ID to check for form input changes
*/
const elementDataHasChanged = (id) => id && false;

/**
* Adds the elemental menu action to publish a draft/modified block
*/
const PublishAction = (MenuComponent) => (props) => {
const { element, actions: { handlePublishBlock } } = props;
const { element } = props;

const handleClick = (event) => {
event.stopPropagation();
const { jQuery: $ } = window;
const noTitle = i18n.inject(
i18n._t(
'ElementHeader.NOTITLE',
'Untitled {type} block'
),
{ type: element.BlockSchema.type }
);

if (handlePublishBlock) {
handlePublishBlock(element.ID, 'DRAFT', 'LIVE', element.Version)
.then(() => {
const preview = $('.cms-preview');
preview.entwine('ss.preview')._loadUrl(preview.find('iframe').attr('src'));

$.noticeAdd({
text: i18n.inject(
i18n._t(
'UnpublishAction.SUCCESS_NOTIFICATION',
'Published \'{title}\' successfully'),
{ title: element.Title || noTitle }
),
stay: false,
type: 'success'
});
})
.catch(() => {
$.noticeAdd({
text: i18n.inject(
i18n._t(
'UnpublishAction.ERROR_NOTIFICATION',
'Error publishing \'{title}\''),
{ title: element.Title || noTitle }
),
stay: false,
type: 'error'
});
});

const {
element: {
ID: id,
Title: title,
Version: version,
BlockSchema: { type: elementType }
},
securityId,
formData,
actions: { handlePublishBlock }
} = props;

const formDirty = elementDataHasChanged(id);
let actionFlow = new Promise((resolve) => resolve(version));

// Edits have been made to the form. Peform a "Save & Publish"
if (formDirty) {
actionFlow = performSaveForElementWithFormData(id, formData, securityId);
}

// Perform publish. Data is assumed to be up to date
actionFlow
.then((versionToPublish) => handlePublishBlock(id, 'DRAFT', 'LIVE', versionToPublish))
.then(() => reportPublicationStatus(elementType, title, true))
.catch(() => reportPublicationStatus(elementType, title, false));
};

const newProps = {
Expand All @@ -69,6 +133,21 @@ const PublishAction = (MenuComponent) => (props) => {
);
};

function mapStateToProps(state, ownProps) {
const formName = loadElementFormStateName(ownProps.element.ID);

let formData = null;

if (state.form.formState.element && state.form.formState.element[formName]) {
formData = state.form.formState.element[formName].values;
}

return {
formData,
securityId: state.config.SecurityID,
};
}

export { PublishAction as Component };

export default compose(publishBlockMutation, PublishAction);
export default compose(publishBlockMutation, connect(mapStateToProps), PublishAction);
10 changes: 8 additions & 2 deletions client/src/components/ElementActions/tests/PublishAction-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ Enzyme.configure({ adapter: new Adapter() });

describe('PublishAction', () => {
let wrapper = null;
const mockMutation = jest.fn(() => new Promise((resolve) => { resolve(); }));
let mockMutation = null;
const WrappedComponent = (props) => <div>{props.children}</div>;
const ActionComponent = PublishAction(WrappedComponent);
const jQuery = jest.fn();
window.jQuery = jQuery;

beforeEach(() => {
mockMutation = jest.fn(() => new Promise((resolve) => resolve()));
wrapper = mount(
<ActionComponent
title="My abstract action"
Expand Down Expand Up @@ -47,8 +48,13 @@ describe('PublishAction', () => {
expect(wrapper.find('button').hasClass('element-editor__actions-publish')).toBe(true);
});

it('publishes from draft to live', () => {
it('publishes from draft to live', async () => {
wrapper.find('button').simulate('click');

// The click handler does not return a promise, but it does USE one.
// We need to await the promise resolution cycle before asserting.
await new Promise((resolve) => resolve());

expect(mockMutation).toHaveBeenCalledWith(123, 'DRAFT', 'LIVE', 234);
});

Expand Down

0 comments on commit 26b7ed0

Please sign in to comment.