fix: call createDraftFromEntry after entry is loaded instead in Editor (#3418)

* fix: call createDraftFromEntry after entry is loaded instead in Editor
This commit is contained in:
Erez Rokah 2020-03-16 20:48:49 +01:00 committed by GitHub
parent c9b82551ed
commit 2409323dba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 41 additions and 77 deletions

View File

@ -64,7 +64,7 @@ describe('editorialWorkflow actions', () => {
return store.dispatch(actions.loadUnpublishedEntry(collection, slug)).then(() => { return store.dispatch(actions.loadUnpublishedEntry(collection, slug)).then(() => {
const actions = store.getActions(); const actions = store.getActions();
expect(actions).toHaveLength(3); expect(actions).toHaveLength(4);
expect(actions[0]).toEqual({ expect(actions[0]).toEqual({
type: 'UNPUBLISHED_ENTRY_REQUEST', type: 'UNPUBLISHED_ENTRY_REQUEST',
payload: { payload: {
@ -80,6 +80,12 @@ describe('editorialWorkflow actions', () => {
entry: { ...entry, mediaFiles: [{ file: { name: 'name' }, id: '1', draft: true }] }, entry: { ...entry, mediaFiles: [{ file: { name: 'name' }, id: '1', draft: true }] },
}, },
}); });
expect(actions[3]).toEqual({
type: 'DRAFT_CREATE_FROM_ENTRY',
payload: {
entry,
},
});
}); });
}); });
}); });
@ -111,7 +117,7 @@ describe('editorialWorkflow actions', () => {
return store.dispatch(actions.publishUnpublishedEntry('posts', slug)).then(() => { return store.dispatch(actions.publishUnpublishedEntry('posts', slug)).then(() => {
const actions = store.getActions(); const actions = store.getActions();
expect(actions).toHaveLength(6); expect(actions).toHaveLength(7);
expect(actions[0]).toEqual({ expect(actions[0]).toEqual({
type: 'UNPUBLISHED_ENTRY_PUBLISH_REQUEST', type: 'UNPUBLISHED_ENTRY_PUBLISH_REQUEST',
@ -155,6 +161,12 @@ describe('editorialWorkflow actions', () => {
collection: 'posts', collection: 'posts',
}, },
}); });
expect(actions[6]).toEqual({
type: 'DRAFT_CREATE_FROM_ENTRY',
payload: {
entry,
},
});
}); });
}); });

View File

@ -15,7 +15,7 @@ import {
import { selectFields } from '../reducers/collections'; import { selectFields } from '../reducers/collections';
import { EDITORIAL_WORKFLOW, status, Status } from '../constants/publishModes'; import { EDITORIAL_WORKFLOW, status, Status } from '../constants/publishModes';
import { EDITORIAL_WORKFLOW_ERROR } from 'netlify-cms-lib-util'; import { EDITORIAL_WORKFLOW_ERROR } from 'netlify-cms-lib-util';
import { loadEntry, entryDeleted, getMediaAssets } from './entries'; import { loadEntry, entryDeleted, getMediaAssets, createDraftFromEntry } from './entries';
import { createAssetProxy } from '../valueObjects/AssetProxy'; import { createAssetProxy } from '../valueObjects/AssetProxy';
import { addAssets } from './media'; import { addAssets } from './media';
import { loadMedia } from './mediaLibrary'; import { loadMedia } from './mediaLibrary';
@ -290,6 +290,7 @@ export function loadUnpublishedEntry(collection: Collection, slug: string) {
} }
dispatch(unpublishedEntryLoaded(collection, { ...entry, mediaFiles })); dispatch(unpublishedEntryLoaded(collection, { ...entry, mediaFiles }));
dispatch(createDraftFromEntry(entry));
} catch (error) { } catch (error) {
if (error.name === EDITORIAL_WORKFLOW_ERROR && error.notUnderEditorialWorkflow) { if (error.name === EDITORIAL_WORKFLOW_ERROR && error.notUnderEditorialWorkflow) {
dispatch(unpublishedEntryRedirected(collection, slug)); dispatch(unpublishedEntryRedirected(collection, slug));

View File

@ -201,10 +201,10 @@ export function emptyDraftCreated(entry: EntryValue) {
/* /*
* Exported simple Action Creators * Exported simple Action Creators
*/ */
export function createDraftFromEntry(entry: EntryMap, metadata?: Map<string, unknown>) { export function createDraftFromEntry(entry: EntryValue) {
return { return {
type: DRAFT_CREATE_FROM_ENTRY, type: DRAFT_CREATE_FROM_ENTRY,
payload: { entry, metadata }, payload: { entry },
}; };
} }
@ -339,12 +339,12 @@ export function loadEntry(collection: Collection, slug: string) {
const backend = currentBackend(state.config); const backend = currentBackend(state.config);
await waitForMediaLibraryToLoad(dispatch, getState()); await waitForMediaLibraryToLoad(dispatch, getState());
dispatch(entryLoading(collection, slug)); dispatch(entryLoading(collection, slug));
return backend
.getEntry(getState(), collection, slug) try {
.then((loadedEntry: EntryValue) => { const loadedEntry = await backend.getEntry(getState(), collection, slug);
return dispatch(entryLoaded(collection, loadedEntry)); dispatch(entryLoaded(collection, loadedEntry));
}) dispatch(createDraftFromEntry(loadedEntry));
.catch((error: Error) => { } catch (error) {
console.error(error); console.error(error);
dispatch( dispatch(
notifSend({ notifSend({
@ -357,7 +357,7 @@ export function loadEntry(collection: Collection, slug: string) {
}), }),
); );
dispatch(entryLoadError(error, collection, slug)); dispatch(entryLoadError(error, collection, slug));
}); }
}; };
} }

View File

@ -10,7 +10,6 @@ import { logoutUser } from 'Actions/auth';
import { import {
loadEntry, loadEntry,
loadEntries, loadEntries,
createDraftFromEntry,
createDraftDuplicateFromEntry, createDraftDuplicateFromEntry,
createEmptyDraft, createEmptyDraft,
discardDraft, discardDraft,
@ -30,7 +29,6 @@ import {
deleteUnpublishedEntry, deleteUnpublishedEntry,
} from 'Actions/editorialWorkflow'; } from 'Actions/editorialWorkflow';
import { loadDeployPreview } from 'Actions/deploys'; import { loadDeployPreview } from 'Actions/deploys';
import { deserializeValues } from 'Lib/serializeEntryValues';
import { selectEntry, selectUnpublishedEntry, selectDeployPreview } from 'Reducers'; import { selectEntry, selectUnpublishedEntry, selectDeployPreview } from 'Reducers';
import { selectFields } from 'Reducers/collections'; import { selectFields } from 'Reducers/collections';
import { status, EDITORIAL_WORKFLOW } from 'Constants/publishModes'; import { status, EDITORIAL_WORKFLOW } from 'Constants/publishModes';
@ -48,7 +46,6 @@ export class Editor extends React.Component {
changeDraftField: PropTypes.func.isRequired, changeDraftField: PropTypes.func.isRequired,
changeDraftFieldValidation: PropTypes.func.isRequired, changeDraftFieldValidation: PropTypes.func.isRequired,
collection: ImmutablePropTypes.map.isRequired, collection: ImmutablePropTypes.map.isRequired,
createDraftFromEntry: PropTypes.func.isRequired,
createDraftDuplicateFromEntry: PropTypes.func.isRequired, createDraftDuplicateFromEntry: PropTypes.func.isRequired,
createEmptyDraft: PropTypes.func.isRequired, createEmptyDraft: PropTypes.func.isRequired,
discardDraft: PropTypes.func.isRequired, discardDraft: PropTypes.func.isRequired,
@ -198,18 +195,9 @@ export class Editor extends React.Component {
if (prevProps.entry === this.props.entry) return; if (prevProps.entry === this.props.entry) return;
const { entry, newEntry, fields, collection } = this.props; const { newEntry, collection } = this.props;
if (entry && !entry.get('isFetching') && !entry.get('error')) { if (newEntry) {
/**
* Deserialize entry values for widgets with registered serializers before
* creating the entry draft.
*/
const values = deserializeValues(entry.get('data'), fields);
const deserializedEntry = entry.set('data', values);
const fieldsMetaData = this.props.entryDraft && this.props.entryDraft.get('fieldsMetaData');
this.createDraft(deserializedEntry, fieldsMetaData);
} else if (newEntry) {
prevProps.createEmptyDraft(collection, this.props.location.search); prevProps.createEmptyDraft(collection, this.props.location.search);
} }
} }
@ -224,10 +212,6 @@ export class Editor extends React.Component {
this.props.persistLocalBackup(entry, collection); this.props.persistLocalBackup(entry, collection);
}, 2000); }, 2000);
createDraft = (entry, metadata) => {
if (entry) this.props.createDraftFromEntry(entry, metadata);
};
handleChangeDraftField = (field, value, metadata) => { handleChangeDraftField = (field, value, metadata) => {
const entries = [this.props.unPublishedEntry, this.props.publishedEntry].filter(Boolean); const entries = [this.props.unPublishedEntry, this.props.publishedEntry].filter(Boolean);
this.props.changeDraftField(field, value, metadata, entries); this.props.changeDraftField(field, value, metadata, entries);
@ -509,7 +493,6 @@ const mapDispatchToProps = {
retrieveLocalBackup, retrieveLocalBackup,
persistLocalBackup, persistLocalBackup,
deleteLocalBackup, deleteLocalBackup,
createDraftFromEntry,
createDraftDuplicateFromEntry, createDraftDuplicateFromEntry,
createEmptyDraft, createEmptyDraft,
discardDraft, discardDraft,

View File

@ -25,7 +25,6 @@ describe('Editor', () => {
changeDraftField: jest.fn(), changeDraftField: jest.fn(),
changeDraftFieldValidation: jest.fn(), changeDraftFieldValidation: jest.fn(),
collection: fromJS({ name: 'posts' }), collection: fromJS({ name: 'posts' }),
createDraftFromEntry: jest.fn(),
createDraftDuplicateFromEntry: jest.fn(), createDraftDuplicateFromEntry: jest.fn(),
createEmptyDraft: jest.fn(), createEmptyDraft: jest.fn(),
discardDraft: jest.fn(), discardDraft: jest.fn(),
@ -215,32 +214,4 @@ describe('Editor', () => {
props.collection, props.collection,
); );
}); });
it('should create draft from entry when done fetching', () => {
const { rerender } = render(
<Editor
{...props}
entryDraft={fromJS({ entry: {} })}
entry={fromJS({ isFetching: false, mediaFiles: [] })}
/>,
);
jest.clearAllMocks();
rerender(
<Editor
{...props}
entryDraft={fromJS({
entry: {},
fieldsMetaData: {},
})}
entry={fromJS({ isFetching: false, mediaFiles: [{ id: '1' }], data: {} })}
/>,
);
expect(props.createDraftFromEntry).toHaveBeenCalledTimes(1);
expect(props.createDraftFromEntry).toHaveBeenCalledWith(
fromJS({ isFetching: false, data: {}, mediaFiles: [{ id: '1' }] }),
fromJS({}),
);
});
}); });

View File

@ -36,12 +36,9 @@ const entryDraftReducer = (state = Map(), action) => {
case DRAFT_CREATE_FROM_ENTRY: case DRAFT_CREATE_FROM_ENTRY:
// Existing Entry // Existing Entry
return state.withMutations(state => { return state.withMutations(state => {
state.set('entry', action.payload.entry); state.set('entry', fromJS(action.payload.entry));
state.setIn(['entry', 'newRecord'], false); state.setIn(['entry', 'newRecord'], false);
// An existing entry may already have metadata. If we surfed away and back to its state.set('fieldsMetaData', Map());
// editor page, the metadata will have been fetched already, so we shouldn't
// clear it as to not break relation lists.
state.set('fieldsMetaData', action.payload.metadata || Map());
state.set('fieldsErrors', Map()); state.set('fieldsErrors', Map());
state.set('hasChanged', false); state.set('hasChanged', false);
state.set('key', uuid()); state.set('key', uuid());