Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: mutate config in normalizeConfig and handleLocalBackend fns #4846

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {

jest.spyOn(console, 'log').mockImplementation(() => {});
jest.spyOn(console, 'warn').mockImplementation(() => {});
jest.mock('coreSrc/backend', () => {
jest.mock('../../backend', () => {
return {
resolveBackend: jest.fn(() => ({ isGitBackend: jest.fn(() => true) })),
};
Expand Down Expand Up @@ -452,8 +452,8 @@ describe('config', () => {
test('should convert camel case to snake case', () => {
expect(
applyDefaults(
normalizeConfig(
fromJS({
fromJS(
normalizeConfig({
collections: [
{
sortableFields: ['title'],
Expand Down Expand Up @@ -922,7 +922,7 @@ describe('config', () => {
window.location = { hostname: 'localhost' };
global.fetch = jest.fn().mockRejectedValue(new Error());

const config = fromJS({ local_backend: true, backend: { name: 'github' } });
const config = { local_backend: true, backend: { name: 'github' } };
const actual = await handleLocalBackend(config);

expect(actual).toEqual(config);
Expand Down
192 changes: 101 additions & 91 deletions packages/netlify-cms-core/src/actions/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,32 @@ import yaml from 'yaml';
import { Map, fromJS } from 'immutable';
import deepmerge from 'deepmerge';
import { trimStart, trim, get, isPlainObject, isEmpty } from 'lodash';
import * as publishModes from 'Constants/publishModes';
import { validateConfig } from 'Constants/configSchema';
import { SIMPLE as SIMPLE_PUBLISH_MODE } from '../constants/publishModes';
import { validateConfig } from '../constants/configSchema';
import { selectDefaultSortableFields, traverseFields } from '../reducers/collections';
import { getIntegrations, selectIntegration } from '../reducers/integrations';
import { resolveBackend } from 'coreSrc/backend';
import { resolveBackend } from '../backend';
import { I18N, I18N_FIELD, I18N_STRUCTURE } from '../lib/i18n';

export const CONFIG_REQUEST = 'CONFIG_REQUEST';
export const CONFIG_SUCCESS = 'CONFIG_SUCCESS';
export const CONFIG_FAILURE = 'CONFIG_FAILURE';

const traverseFieldsJS = (fields, updater) => {
return fields.map(field => {
let newField = updater(field);
if (newField.fields) {
newField = { ...newField, fields: traverseFieldsJS(newField.fields, updater) };
} else if (newField.field) {
newField = { ...newField, field: traverseFieldsJS([newField.field], updater)[0] };
} else if (newField.types) {
newField = { ...newField, types: traverseFieldsJS(newField.types, updater) };
}

return newField;
});
};

const getConfigUrl = () => {
const validTypes = { 'text/yaml': 'yaml', 'application/x-yaml': 'yaml' };
const configLinkEl = document.querySelector('link[rel="cms-config-url"]');
Expand All @@ -32,31 +47,30 @@ const setDefaultPublicFolder = map => {
return map;
};

const setSnakeCaseConfig = field => {
// Mapping between existing camelCase and its snake_case counterpart
const widgetKeyMap = {
dateFormat: 'date_format',
timeFormat: 'time_format',
pickerUtc: 'picker_utc',
editorComponents: 'editor_components',
valueType: 'value_type',
valueField: 'value_field',
searchFields: 'search_fields',
displayFields: 'display_fields',
optionsLength: 'options_length',
};
// Mapping between existing camelCase and its snake_case counterpart
const WIDGET_KEY_MAP = {
dateFormat: 'date_format',
timeFormat: 'time_format',
pickerUtc: 'picker_utc',
editorComponents: 'editor_components',
valueType: 'value_type',
valueField: 'value_field',
searchFields: 'search_fields',
displayFields: 'display_fields',
optionsLength: 'options_length',
};

Object.entries(widgetKeyMap).forEach(([camel, snake]) => {
if (field.has(camel)) {
field = field.set(snake, field.get(camel));
console.warn(
`Field ${field.get(
'name',
)} is using a deprecated configuration '${camel}'. Please use '${snake}'`,
);
}
const setSnakeCaseConfig = field => {
const deprecatedKeys = Object.keys(WIDGET_KEY_MAP).filter(camel => camel in field);
const snakeValues = deprecatedKeys.map(camel => {
const snake = WIDGET_KEY_MAP[camel];
console.warn(
`Field ${field.name} is using a deprecated configuration '${camel}'. Please use '${snake}'`,
);
return { [snake]: field[camel] };
});
return field;

return Object.assign({}, field, ...snakeValues);
};

const setI18nField = field => {
Expand Down Expand Up @@ -141,7 +155,7 @@ const setViewPatternsDefaults = (key, collection) => {
};

const defaults = {
publish_mode: publishModes.SIMPLE,
publish_mode: SIMPLE_PUBLISH_MODE,
};

const hasIntegration = (config, collection) => {
Expand All @@ -151,45 +165,38 @@ const hasIntegration = (config, collection) => {
};

export function normalizeConfig(config) {
return Map(config).withMutations(map => {
map.set(
'collections',
map.get('collections').map(collection => {
const folder = collection.get('folder');
if (folder) {
collection = collection.set(
'fields',
traverseFields(collection.get('fields'), setSnakeCaseConfig),
);
}

const files = collection.get('files');
if (files) {
collection = collection.set(
'files',
files.map(file => {
file = file.set('fields', traverseFields(file.get('fields'), setSnakeCaseConfig));
return file;
}),
);
}

if (collection.has('sortableFields')) {
collection = collection
.set('sortable_fields', collection.get('sortableFields'))
.delete('sortableFields');

console.warn(
`Collection ${collection.get(
'name',
)} is using a deprecated configuration 'sortableFields'. Please use 'sortable_fields'`,
);
}

return collection;
}),
);
const { collections = [] } = config;

const normalizedCollections = collections.map(collection => {
const { fields, files } = collection;

let normalizedCollection = collection;
if (fields) {
const normalizedFields = traverseFieldsJS(fields, setSnakeCaseConfig);
normalizedCollection = { ...normalizedCollection, fields: normalizedFields };
}

if (files) {
const normalizedFiles = files.map(file => {
const normalizedFileFields = traverseFieldsJS(file.fields, setSnakeCaseConfig);
return { ...file, fields: normalizedFileFields };
});
normalizedCollection = { ...normalizedCollection, files: normalizedFiles };
}

if (normalizedCollection.sortableFields) {
const { sortableFields, ...rest } = normalizedCollection;
normalizedCollection = { ...rest, sortable_fields: sortableFields };

console.warn(
`Collection ${collection.name} is using a deprecated configuration 'sortableFields'. Please use 'sortable_fields'`,
);
}

return normalizedCollection;
});

return { ...config, collections: normalizedCollections };
}

export function applyDefaults(config) {
Expand Down Expand Up @@ -383,36 +390,38 @@ export async function detectProxyServer(localBackend) {
return {};
}

export async function handleLocalBackend(originalConfig) {
if (!originalConfig.local_backend) {
return originalConfig;
const getPublishMode = (config, publishModes, backendType) => {
if (config.publish_mode && publishModes && !publishModes.includes(config.publish_mode)) {
const newPublishMode = publishModes[0];
console.log(
`'${config.publish_mode}' is not supported by '${backendType}' backend, switching to '${newPublishMode}'`,
);
return newPublishMode;
}

const { proxyUrl, publish_modes, type } = await detectProxyServer(originalConfig.local_backend);
return config.publish_mode;
};

if (!proxyUrl) {
return originalConfig;
export const handleLocalBackend = async config => {
if (!config.local_backend) {
return config;
}

let mergedConfig = deepmerge(originalConfig, {
backend: { name: 'proxy', proxy_url: proxyUrl },
});
const { proxyUrl, publish_modes: publishModes, type: backendType } = await detectProxyServer(
config.local_backend,
);

if (
mergedConfig.publish_mode &&
publish_modes &&
!publish_modes.includes(mergedConfig.publish_mode)
) {
const newPublishMode = publish_modes[0];
mergedConfig = deepmerge(mergedConfig, {
publish_mode: newPublishMode,
});
console.log(
`'${mergedConfig.publish_mode}' is not supported by '${type}' backend, switching to '${newPublishMode}'`,
);
if (!proxyUrl) {
return config;
}
return mergedConfig;
}

const publishMode = getPublishMode(config, publishModes, backendType);
return {
...config,
...(publishMode && { publish_mode: publishMode }),
backend: { ...config.backend, name: 'proxy', proxy_url: proxyUrl },
};
};

export function loadConfig(manualConfig = {}, onLoad) {
if (window.CMS_CONFIG) {
Expand All @@ -430,13 +439,14 @@ export function loadConfig(manualConfig = {}, onLoad) {
: await getConfigYaml(configUrl, hasManualConfig);

// Merge manual config into the config.yml one
let mergedConfig = deepmerge(configYaml, manualConfig);
const mergedConfig = deepmerge(configYaml, manualConfig);

validateConfig(mergedConfig);

mergedConfig = await handleLocalBackend(mergedConfig);
const withLocalBackend = await handleLocalBackend(mergedConfig);
const normalizedConfig = normalizeConfig(withLocalBackend);

const config = applyDefaults(normalizeConfig(fromJS(mergedConfig)));
const config = applyDefaults(fromJS(normalizedConfig));

dispatch(configLoaded(config));

Expand Down