fix: don't use getAsset for draft entries (#3403)

This commit is contained in:
Erez Rokah 2020-03-22 16:53:06 +02:00 committed by GitHub
parent e92ba412d8
commit 45a1654404
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 102 additions and 120 deletions

View File

@ -349,7 +349,7 @@ describe('Backend', () => {
init: jest.fn(() => implementation), init: jest.fn(() => implementation),
unpublishedEntry: jest.fn().mockResolvedValue(unpublishedEntryResult), unpublishedEntry: jest.fn().mockResolvedValue(unpublishedEntryResult),
}; };
const config = Map({}); const config = Map({ media_folder: 'static/images' });
const backend = new Backend(implementation, { config, backendName: 'github' }); const backend = new Backend(implementation, { config, backendName: 'github' });
@ -357,9 +357,15 @@ describe('Backend', () => {
name: 'posts', name: 'posts',
}); });
const state = {
config,
integrations: Map({}),
mediaLibrary: Map({}),
};
const slug = 'slug'; const slug = 'slug';
const result = await backend.unpublishedEntry(collection, slug); const result = await backend.unpublishedEntry(state, collection, slug);
expect(result).toEqual({ expect(result).toEqual({
collection: 'posts', collection: 'posts',
slug: '', slug: '',
@ -370,7 +376,7 @@ describe('Backend', () => {
label: null, label: null,
metaData: {}, metaData: {},
isModification: true, isModification: true,
mediaFiles: [{ id: '1' }], mediaFiles: [{ id: '1', draft: true }],
}); });
}); });
}); });

View File

@ -38,7 +38,7 @@ describe('editorialWorkflow actions', () => {
const { createAssetProxy } = require('ValueObjects/AssetProxy'); const { createAssetProxy } = require('ValueObjects/AssetProxy');
const assetProxy = { name: 'name', path: 'path' }; const assetProxy = { name: 'name', path: 'path' };
const entry = { mediaFiles: [{ file: { name: 'name' }, id: '1' }] }; const entry = { mediaFiles: [{ file: { name: 'name' }, id: '1', draft: true }] };
const backend = { const backend = {
unpublishedEntry: jest.fn().mockResolvedValue(entry), unpublishedEntry: jest.fn().mockResolvedValue(entry),
}; };

View File

@ -8,15 +8,9 @@ import {
} from '../entries'; } from '../entries';
import configureMockStore from 'redux-mock-store'; import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk'; import thunk from 'redux-thunk';
import AssetProxy from '../../valueObjects/AssetProxy';
jest.mock('coreSrc/backend'); jest.mock('coreSrc/backend');
jest.mock('../media', () => {
const media = jest.requireActual('../media');
return {
...media,
getAsset: jest.fn(),
};
});
jest.mock('netlify-cms-lib-util'); jest.mock('netlify-cms-lib-util');
jest.mock('../mediaLibrary'); jest.mock('../mediaLibrary');
@ -25,6 +19,13 @@ const mockStore = configureMockStore(middlewares);
describe('entries', () => { describe('entries', () => {
describe('createEmptyDraft', () => { describe('createEmptyDraft', () => {
const { currentBackend } = require('coreSrc/backend');
const backend = {
processEntry: jest.fn((_state, _collection, entry) => Promise.resolve(entry)),
};
currentBackend.mockReturnValue(backend);
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
}); });
@ -45,7 +46,7 @@ describe('entries', () => {
data: {}, data: {},
isModification: null, isModification: null,
label: null, label: null,
mediaFiles: fromJS([]), mediaFiles: [],
metaData: null, metaData: null,
partial: false, partial: false,
path: '', path: '',
@ -74,7 +75,7 @@ describe('entries', () => {
data: { title: 'title', boolean: true }, data: { title: 'title', boolean: true },
isModification: null, isModification: null,
label: null, label: null,
mediaFiles: fromJS([]), mediaFiles: [],
metaData: null, metaData: null,
partial: false, partial: false,
path: '', path: '',
@ -105,7 +106,7 @@ describe('entries', () => {
data: { title: '<script>alert('hello')</script>' }, data: { title: '<script>alert('hello')</script>' },
isModification: null, isModification: null,
label: null, label: null,
mediaFiles: fromJS([]), mediaFiles: [],
metaData: null, metaData: null,
partial: false, partial: false,
path: '', path: '',
@ -286,20 +287,11 @@ describe('entries', () => {
jest.clearAllMocks(); jest.clearAllMocks();
}); });
it('should map mediaFiles to assets', async () => { it('should map mediaFiles to assets', () => {
const { getAsset } = require('../media');
const mediaFiles = fromJS([{ path: 'path1' }, { path: 'path2', draft: true }]); const mediaFiles = fromJS([{ path: 'path1' }, { path: 'path2', draft: true }]);
const asset = { path: 'path1' };
getAsset.mockReturnValue(() => asset);
const collection = Map();
const entry = Map({ mediaFiles }); const entry = Map({ mediaFiles });
await expect(getMediaAssets({ entry, collection })).resolves.toEqual([asset]); expect(getMediaAssets({ entry })).toEqual([new AssetProxy({ path: 'path2' })]);
expect(getAsset).toHaveBeenCalledTimes(1);
expect(getAsset).toHaveBeenCalledWith({ collection, path: 'path2', entry });
}); });
}); });
}); });

View File

@ -271,25 +271,20 @@ export function loadUnpublishedEntry(collection: Collection, slug: string) {
dispatch(unpublishedEntryLoading(collection, slug)); dispatch(unpublishedEntryLoading(collection, slug));
try { try {
const entry = (await backend.unpublishedEntry(collection, slug)) as EntryValue; const entry = (await backend.unpublishedEntry(state, collection, slug)) as EntryValue;
const assetProxies = await Promise.all( const assetProxies = await Promise.all(
entry.mediaFiles.map(({ url, file, path }) => entry.mediaFiles
createAssetProxy({ .filter(file => file.draft)
path, .map(({ url, file, path }) =>
url, createAssetProxy({
file, path,
}), url,
), file,
}),
),
); );
dispatch(addAssets(assetProxies)); dispatch(addAssets(assetProxies));
dispatch(unpublishedEntryLoaded(collection, entry));
let mediaFiles: MediaFile[] = entry.mediaFiles.map(file => ({ ...file, draft: true }));
if (!collection.has('media_folder')) {
const libraryFiles = getState().mediaLibrary.get('files') || [];
mediaFiles = mediaFiles.concat(libraryFiles);
}
dispatch(unpublishedEntryLoaded(collection, { ...entry, mediaFiles }));
dispatch(createDraftFromEntry(entry)); dispatch(createDraftFromEntry(entry));
} catch (error) { } catch (error) {
if (error.name === EDITORIAL_WORKFLOW_ERROR && error.notUnderEditorialWorkflow) { if (error.name === EDITORIAL_WORKFLOW_ERROR && error.notUnderEditorialWorkflow) {
@ -375,10 +370,7 @@ export function persistUnpublishedEntry(collection: Collection, existingUnpublis
const backend = currentBackend(state.config); const backend = currentBackend(state.config);
const transactionID = uuid(); const transactionID = uuid();
const entry = entryDraft.get('entry'); const entry = entryDraft.get('entry');
const assetProxies = await getMediaAssets({ const assetProxies = getMediaAssets({
getState,
dispatch,
collection,
entry, entry,
}); });

View File

@ -12,9 +12,9 @@ import { createEntry, EntryValue } from '../valueObjects/Entry';
import AssetProxy, { createAssetProxy } from '../valueObjects/AssetProxy'; import AssetProxy, { createAssetProxy } from '../valueObjects/AssetProxy';
import ValidationErrorTypes from '../constants/validationErrorTypes'; import ValidationErrorTypes from '../constants/validationErrorTypes';
import { addAssets, getAsset } from './media'; import { addAssets, getAsset } from './media';
import { Collection, EntryMap, MediaFile, State, EntryFields, EntryField } from '../types/redux'; import { Collection, EntryMap, State, EntryFields, EntryField } from '../types/redux';
import { ThunkDispatch } from 'redux-thunk'; import { ThunkDispatch } from 'redux-thunk';
import { AnyAction, Dispatch } from 'redux'; import { AnyAction } from 'redux';
import { waitForMediaLibraryToLoad, loadMedia } from './mediaLibrary'; import { waitForMediaLibraryToLoad, loadMedia } from './mediaLibrary';
import { waitUntil } from './waitUntil'; import { waitUntil } from './waitUntil';
@ -524,13 +524,18 @@ export function createEmptyDraft(collection: Collection, search: string) {
const fields = collection.get('fields', List()); const fields = collection.get('fields', List());
const dataFields = createEmptyDraftData(fields); const dataFields = createEmptyDraftData(fields);
let mediaFiles = [] as MediaFile[]; const state = getState();
const backend = currentBackend(state.config);
if (!collection.has('media_folder')) { if (!collection.has('media_folder')) {
await waitForMediaLibraryToLoad(dispatch, getState()); await waitForMediaLibraryToLoad(dispatch, getState());
mediaFiles = getState().mediaLibrary.get('files');
} }
const newEntry = createEntry(collection.get('name'), '', '', { data: dataFields, mediaFiles }); let newEntry = createEntry(collection.get('name'), '', '', {
data: dataFields,
mediaFiles: [],
});
newEntry = await backend.processEntry(state, collection, newEntry);
dispatch(emptyDraftCreated(newEntry)); dispatch(emptyDraftCreated(newEntry));
}; };
} }
@ -592,28 +597,13 @@ export function createEmptyDraftData(fields: EntryFields, withNameKey = true) {
); );
} }
export async function getMediaAssets({ export function getMediaAssets({ entry }: { entry: EntryMap }) {
getState,
dispatch,
collection,
entry,
}: {
getState: () => State;
collection: Collection;
entry: EntryMap;
dispatch: Dispatch;
}) {
const filesArray = entry.get('mediaFiles').toArray(); const filesArray = entry.get('mediaFiles').toArray();
const assets = await Promise.all( const assets = filesArray
filesArray .filter(file => file.get('draft'))
.filter(file => file.get('draft')) .map(file =>
.map(file => createAssetProxy({ path: file.get('path'), file: file.get('file'), url: file.get('url') }),
getAsset({ collection, entry, path: file.get('path'), field: file.get('field') })( );
dispatch,
getState,
),
),
);
return assets; return assets;
} }
@ -648,10 +638,7 @@ export function persistEntry(collection: Collection) {
const backend = currentBackend(state.config); const backend = currentBackend(state.config);
const entry = entryDraft.get('entry'); const entry = entryDraft.get('entry');
const assetProxies = await getMediaAssets({ const assetProxies = getMediaAssets({
getState,
dispatch,
collection,
entry, entry,
}); });

View File

@ -198,6 +198,7 @@ function createMediaFileFromAsset({
name: basename(assetProxy.path), name: basename(assetProxy.path),
displayURL: assetProxy.url, displayURL: assetProxy.url,
draft, draft,
file,
size: file.size, size: file.size,
url: assetProxy.url, url: assetProxy.url,
path: assetProxy.path, path: assetProxy.path,

View File

@ -494,27 +494,16 @@ export class Backend {
const path = selectEntryPath(collection, slug) as string; const path = selectEntryPath(collection, slug) as string;
const label = selectFileEntryLabel(collection, slug); const label = selectFileEntryLabel(collection, slug);
const integration = selectIntegration(state.integrations, null, 'assetStore');
const loadedEntry = await this.implementation.getEntry(path); const loadedEntry = await this.implementation.getEntry(path);
const entry = createEntry(collection.get('name'), slug, loadedEntry.file.path, { let entry = createEntry(collection.get('name'), slug, loadedEntry.file.path, {
raw: loadedEntry.data, raw: loadedEntry.data,
label, label,
mediaFiles: [], mediaFiles: [],
}); });
const entryWithFormat = this.entryWithFormat(collection)(entry); entry = this.entryWithFormat(collection)(entry);
const mediaFolders = selectMediaFolders(state, collection, fromJS(entryWithFormat)); entry = await this.processEntry(state, collection, entry);
if (mediaFolders.length > 0 && !integration) { return entry;
entry.mediaFiles = [];
for (const folder of mediaFolders) {
entry.mediaFiles = [...entry.mediaFiles, ...(await this.implementation.getMedia(folder))];
}
} else {
entry.mediaFiles = state.mediaLibrary.get('files') || [];
}
return entryWithFormat;
} }
getMedia() { getMedia() {
@ -536,9 +525,9 @@ export class Backend {
return Promise.reject(err); return Promise.reject(err);
} }
entryWithFormat(collectionOrEntity: unknown) { entryWithFormat(collection: Collection) {
return (entry: EntryValue): EntryValue => { return (entry: EntryValue): EntryValue => {
const format = resolveFormat(collectionOrEntity, entry); const format = resolveFormat(collection, entry);
if (entry && entry.raw !== undefined) { if (entry && entry.raw !== undefined) {
const data = (format && attempt(format.fromFile.bind(format, entry.raw))) || {}; const data = (format && attempt(format.fromFile.bind(format, entry.raw))) || {};
if (isError(data)) console.error(data); if (isError(data)) console.error(data);
@ -579,18 +568,37 @@ export class Backend {
})); }));
} }
unpublishedEntry(collection: Collection, slug: string) { async processEntry(state: State, collection: Collection, entry: EntryValue) {
return this.implementation!.unpublishedEntry!(collection.get('name') as string, slug) const integration = selectIntegration(state.integrations, null, 'assetStore');
.then(loadedEntry => { const mediaFolders = selectMediaFolders(state, collection, fromJS(entry));
const entry = createEntry(collection.get('name'), loadedEntry.slug, loadedEntry.file.path, { if (mediaFolders.length > 0 && !integration) {
raw: loadedEntry.data, const files = await Promise.all(
isModification: loadedEntry.isModification, mediaFolders.map(folder => this.implementation.getMedia(folder)),
metaData: loadedEntry.metaData, );
mediaFiles: loadedEntry.mediaFiles, entry.mediaFiles = entry.mediaFiles.concat(...files);
}); } else {
return entry; entry.mediaFiles = entry.mediaFiles.concat(state.mediaLibrary.get('files') || []);
}) }
.then(this.entryWithFormat(collection));
return entry;
}
async unpublishedEntry(state: State, collection: Collection, slug: string) {
const loadedEntry = await this.implementation!.unpublishedEntry!(
collection.get('name') as string,
slug,
);
let entry = createEntry(collection.get('name'), loadedEntry.slug, loadedEntry.file.path, {
raw: loadedEntry.data,
isModification: loadedEntry.isModification,
metaData: loadedEntry.metaData,
mediaFiles: loadedEntry.mediaFiles?.map(file => ({ ...file, draft: true })) || [],
});
entry = this.entryWithFormat(collection)(entry);
entry = await this.processEntry(state, collection, entry);
return entry;
} }
/** /**

View File

@ -40,15 +40,15 @@ const formatByName = (name, customDelimiter) =>
'yaml-frontmatter': frontmatterYAML(customDelimiter), 'yaml-frontmatter': frontmatterYAML(customDelimiter),
}[name]); }[name]);
export function resolveFormat(collectionOrEntity, entry) { export function resolveFormat(collection, entry) {
// Check for custom delimiter // Check for custom delimiter
const frontmatter_delimiter = collectionOrEntity.get('frontmatter_delimiter'); const frontmatter_delimiter = collection.get('frontmatter_delimiter');
const customDelimiter = List.isList(frontmatter_delimiter) const customDelimiter = List.isList(frontmatter_delimiter)
? frontmatter_delimiter.toArray() ? frontmatter_delimiter.toArray()
: frontmatter_delimiter; : frontmatter_delimiter;
// If the format is specified in the collection, use that format. // If the format is specified in the collection, use that format.
const formatSpecification = collectionOrEntity.get('format'); const formatSpecification = collection.get('format');
if (formatSpecification) { if (formatSpecification) {
return formatByName(formatSpecification, customDelimiter); return formatByName(formatSpecification, customDelimiter);
} }
@ -62,7 +62,7 @@ export function resolveFormat(collectionOrEntity, entry) {
// If creating a new file, and an `extension` is specified in the // If creating a new file, and an `extension` is specified in the
// collection config, infer the format from that extension. // collection config, infer the format from that extension.
const extension = collectionOrEntity.get('extension'); const extension = collection.get('extension');
if (extension) { if (extension) {
return get(extensionFormatters, extension); return get(extensionFormatters, extension);
} }

View File

@ -142,7 +142,7 @@ describe('entries', () => {
folder: 'src/docs/getting-started', folder: 'src/docs/getting-started',
media_folder: '/static/images/docs/getting-started', media_folder: '/static/images/docs/getting-started',
}), }),
fromJS({ path: 'src/docs/getting-started/with-github.md' }), fromJS({}),
undefined, undefined,
), ),
).toEqual('static/images/docs/getting-started'); ).toEqual('static/images/docs/getting-started');
@ -184,7 +184,6 @@ describe('entries', () => {
}; };
const entry = fromJS({ const entry = fromJS({
path: 'src/docs/extending/overview.md',
data: { title: 'Overview' }, data: { title: 'Overview' },
}); });
const collection = fromJS({ const collection = fromJS({

View File

@ -351,18 +351,15 @@ export const selectMediaFolder = (
const customFolder = hasCustomFolder(name, collection, entryMap?.get('slug'), field); const customFolder = hasCustomFolder(name, collection, entryMap?.get('slug'), field);
if (customFolder) { if (customFolder) {
const entryPath = entryMap?.get('path'); const folder = evaluateFolder(name, config, collection!, entryMap, field);
if (entryPath) { if (folder.startsWith('/')) {
const entryDir = dirname(entryPath);
const folder = evaluateFolder(name, config, collection!, entryMap, field);
// return absolute paths as is // return absolute paths as is
if (folder.startsWith('/')) { mediaFolder = join(folder);
mediaFolder = join(folder);
} else {
mediaFolder = join(entryDir, folder as string);
}
} else { } else {
mediaFolder = join(collection!.get('folder') as string, DRAFT_MEDIA_FILES); const entryPath = entryMap?.get('path');
mediaFolder = entryPath
? join(dirname(entryPath), folder)
: join(collection!.get('folder') as string, DRAFT_MEDIA_FILES);
} }
} }