Do not mutate config in place during deprecations (#99629)
* refactor config deprecations to avoid config mutations * remove dynamic access keys in core deprecations. * refactor custom config deprecations to match the new signature * improve config deprecations fixtures for nested keys * add a test for xpack.banner config deprecations * key --> path, add a test for invalid command
This commit is contained in:
parent
df752023a0
commit
5fde16c2b3
|
@ -36,10 +36,9 @@ describe('applyDeprecations', () => {
|
|||
const addDeprecation = jest.fn();
|
||||
const createAddDeprecation = jest.fn().mockReturnValue(addDeprecation);
|
||||
const initialConfig = { foo: 'bar', deprecated: 'deprecated' };
|
||||
const alteredConfig = { foo: 'bar' };
|
||||
|
||||
const handlerA = jest.fn().mockReturnValue(alteredConfig);
|
||||
const handlerB = jest.fn().mockImplementation((conf) => conf);
|
||||
const handlerA = jest.fn().mockReturnValue({ unset: [{ path: 'deprecated' }] });
|
||||
const handlerB = jest.fn().mockReturnValue(undefined);
|
||||
|
||||
applyDeprecations(
|
||||
initialConfig,
|
||||
|
@ -47,8 +46,6 @@ describe('applyDeprecations', () => {
|
|||
createAddDeprecation
|
||||
);
|
||||
|
||||
expect(handlerA).toHaveBeenCalledWith(initialConfig, 'pathA', addDeprecation);
|
||||
expect(handlerB).toHaveBeenCalledWith(alteredConfig, 'pathB', addDeprecation);
|
||||
expect(createAddDeprecation).toBeCalledTimes(2);
|
||||
expect(createAddDeprecation).toHaveBeenNthCalledWith(1, 'pathA');
|
||||
expect(createAddDeprecation).toHaveBeenNthCalledWith(2, 'pathB');
|
||||
|
@ -60,8 +57,15 @@ describe('applyDeprecations', () => {
|
|||
const initialConfig = { foo: 'bar', deprecated: 'deprecated' };
|
||||
const alteredConfig = { foo: 'bar' };
|
||||
|
||||
const handlerA = jest.fn().mockReturnValue(alteredConfig);
|
||||
const handlerB = jest.fn().mockImplementation((conf) => conf);
|
||||
const configs: Array<{ fn: string; config: Record<string, any> }> = [];
|
||||
const handlerA = jest.fn().mockImplementation((config) => {
|
||||
// the first argument is mutated between calls, we store a copy of it
|
||||
configs.push({ fn: 'handlerA', config: { ...config } });
|
||||
return { unset: [{ path: 'deprecated' }] };
|
||||
});
|
||||
const handlerB = jest.fn().mockImplementation((config) => {
|
||||
configs.push({ fn: 'handlerB', config: { ...config } });
|
||||
});
|
||||
|
||||
applyDeprecations(
|
||||
initialConfig,
|
||||
|
@ -69,8 +73,10 @@ describe('applyDeprecations', () => {
|
|||
createAddDeprecation
|
||||
);
|
||||
|
||||
expect(handlerA).toHaveBeenCalledWith(initialConfig, 'pathA', addDeprecation);
|
||||
expect(handlerB).toHaveBeenCalledWith(alteredConfig, 'pathB', addDeprecation);
|
||||
expect(configs).toEqual([
|
||||
{ fn: 'handlerA', config: initialConfig },
|
||||
{ fn: 'handlerB', config: alteredConfig },
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns the migrated config', () => {
|
||||
|
@ -94,4 +100,40 @@ describe('applyDeprecations', () => {
|
|||
expect(initialConfig).toEqual({ foo: 'bar', deprecated: 'deprecated' });
|
||||
expect(migrated).toEqual({ foo: 'bar' });
|
||||
});
|
||||
|
||||
it('ignores a command for unknown path', () => {
|
||||
const addDeprecation = jest.fn();
|
||||
const createAddDeprecation = jest.fn().mockReturnValue(addDeprecation);
|
||||
const initialConfig = { foo: 'bar', deprecated: 'deprecated' };
|
||||
|
||||
const handler = jest.fn().mockImplementation((config) => {
|
||||
return { unset: [{ path: 'unknown' }] };
|
||||
});
|
||||
|
||||
const migrated = applyDeprecations(
|
||||
initialConfig,
|
||||
[wrapHandler(handler, 'pathA')],
|
||||
createAddDeprecation
|
||||
);
|
||||
|
||||
expect(migrated).toEqual(initialConfig);
|
||||
});
|
||||
|
||||
it('ignores an unknown command', () => {
|
||||
const addDeprecation = jest.fn();
|
||||
const createAddDeprecation = jest.fn().mockReturnValue(addDeprecation);
|
||||
const initialConfig = { foo: 'bar', deprecated: 'deprecated' };
|
||||
|
||||
const handler = jest.fn().mockImplementation((config) => {
|
||||
return { rewrite: [{ path: 'foo' }] };
|
||||
});
|
||||
|
||||
const migrated = applyDeprecations(
|
||||
initialConfig,
|
||||
[wrapHandler(handler, 'pathA')],
|
||||
createAddDeprecation
|
||||
);
|
||||
|
||||
expect(migrated).toEqual(initialConfig);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { cloneDeep, unset } from 'lodash';
|
||||
import { set } from '@elastic/safer-lodash-set';
|
||||
import { ConfigDeprecationWithContext, AddConfigDeprecation } from './types';
|
||||
|
||||
const noopAddDeprecationFactory: () => AddConfigDeprecation = () => () => undefined;
|
||||
|
@ -22,9 +23,21 @@ export const applyDeprecations = (
|
|||
deprecations: ConfigDeprecationWithContext[],
|
||||
createAddDeprecation: (pluginId: string) => AddConfigDeprecation = noopAddDeprecationFactory
|
||||
) => {
|
||||
let processed = cloneDeep(config);
|
||||
const result = cloneDeep(config);
|
||||
deprecations.forEach(({ deprecation, path }) => {
|
||||
processed = deprecation(processed, path, createAddDeprecation(path));
|
||||
const commands = deprecation(result, path, createAddDeprecation(path));
|
||||
if (commands) {
|
||||
if (commands.set) {
|
||||
commands.set.forEach(function ({ path: commandPath, value }) {
|
||||
set(result, commandPath, value);
|
||||
});
|
||||
}
|
||||
if (commands.unset) {
|
||||
commands.unset.forEach(function ({ path: commandPath }) {
|
||||
unset(result, commandPath);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
return processed;
|
||||
return result;
|
||||
};
|
||||
|
|
|
@ -29,15 +29,15 @@ describe('DeprecationFactory', () => {
|
|||
property: 'value',
|
||||
},
|
||||
};
|
||||
const processed = rename('deprecated', 'renamed')(rawConfig, 'myplugin', addDeprecation);
|
||||
expect(processed).toEqual({
|
||||
myplugin: {
|
||||
renamed: 'toberenamed',
|
||||
valid: 'valid',
|
||||
},
|
||||
someOtherPlugin: {
|
||||
property: 'value',
|
||||
},
|
||||
const commands = rename('deprecated', 'renamed')(rawConfig, 'myplugin', addDeprecation);
|
||||
expect(commands).toEqual({
|
||||
set: [
|
||||
{
|
||||
path: 'myplugin.renamed',
|
||||
value: 'toberenamed',
|
||||
},
|
||||
],
|
||||
unset: [{ path: 'myplugin.deprecated' }],
|
||||
});
|
||||
expect(addDeprecation.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
|
@ -64,16 +64,8 @@ describe('DeprecationFactory', () => {
|
|||
property: 'value',
|
||||
},
|
||||
};
|
||||
const processed = rename('deprecated', 'new')(rawConfig, 'myplugin', addDeprecation);
|
||||
expect(processed).toEqual({
|
||||
myplugin: {
|
||||
new: 'new',
|
||||
valid: 'valid',
|
||||
},
|
||||
someOtherPlugin: {
|
||||
property: 'value',
|
||||
},
|
||||
});
|
||||
const commands = rename('deprecated', 'new')(rawConfig, 'myplugin', addDeprecation);
|
||||
expect(commands).toBeUndefined();
|
||||
expect(addDeprecation).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
it('handles nested keys', () => {
|
||||
|
@ -88,22 +80,19 @@ describe('DeprecationFactory', () => {
|
|||
property: 'value',
|
||||
},
|
||||
};
|
||||
const processed = rename('oldsection.deprecated', 'newsection.renamed')(
|
||||
const commands = rename('oldsection.deprecated', 'newsection.renamed')(
|
||||
rawConfig,
|
||||
'myplugin',
|
||||
addDeprecation
|
||||
);
|
||||
expect(processed).toEqual({
|
||||
myplugin: {
|
||||
oldsection: {},
|
||||
newsection: {
|
||||
renamed: 'toberenamed',
|
||||
expect(commands).toEqual({
|
||||
set: [
|
||||
{
|
||||
path: 'myplugin.newsection.renamed',
|
||||
value: 'toberenamed',
|
||||
},
|
||||
valid: 'valid',
|
||||
},
|
||||
someOtherPlugin: {
|
||||
property: 'value',
|
||||
},
|
||||
],
|
||||
unset: [{ path: 'myplugin.oldsection.deprecated' }],
|
||||
});
|
||||
expect(addDeprecation.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
|
@ -127,11 +116,9 @@ describe('DeprecationFactory', () => {
|
|||
renamed: 'renamed',
|
||||
},
|
||||
};
|
||||
const processed = rename('deprecated', 'renamed')(rawConfig, 'myplugin', addDeprecation);
|
||||
expect(processed).toEqual({
|
||||
myplugin: {
|
||||
renamed: 'renamed',
|
||||
},
|
||||
const commands = rename('deprecated', 'renamed')(rawConfig, 'myplugin', addDeprecation);
|
||||
expect(commands).toEqual({
|
||||
unset: [{ path: 'myplugin.deprecated' }],
|
||||
});
|
||||
expect(addDeprecation.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
|
@ -162,19 +149,19 @@ describe('DeprecationFactory', () => {
|
|||
property: 'value',
|
||||
},
|
||||
};
|
||||
const processed = renameFromRoot('myplugin.deprecated', 'myplugin.renamed')(
|
||||
const commands = renameFromRoot('myplugin.deprecated', 'myplugin.renamed')(
|
||||
rawConfig,
|
||||
'does-not-matter',
|
||||
addDeprecation
|
||||
);
|
||||
expect(processed).toEqual({
|
||||
myplugin: {
|
||||
renamed: 'toberenamed',
|
||||
valid: 'valid',
|
||||
},
|
||||
someOtherPlugin: {
|
||||
property: 'value',
|
||||
},
|
||||
expect(commands).toEqual({
|
||||
set: [
|
||||
{
|
||||
path: 'myplugin.renamed',
|
||||
value: 'toberenamed',
|
||||
},
|
||||
],
|
||||
unset: [{ path: 'myplugin.deprecated' }],
|
||||
});
|
||||
expect(addDeprecation.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
|
@ -202,19 +189,19 @@ describe('DeprecationFactory', () => {
|
|||
property: 'value',
|
||||
},
|
||||
};
|
||||
const processed = renameFromRoot('oldplugin.deprecated', 'newplugin.renamed')(
|
||||
const commands = renameFromRoot('oldplugin.deprecated', 'newplugin.renamed')(
|
||||
rawConfig,
|
||||
'does-not-matter',
|
||||
addDeprecation
|
||||
);
|
||||
expect(processed).toEqual({
|
||||
oldplugin: {
|
||||
valid: 'valid',
|
||||
},
|
||||
newplugin: {
|
||||
renamed: 'toberenamed',
|
||||
property: 'value',
|
||||
},
|
||||
expect(commands).toEqual({
|
||||
set: [
|
||||
{
|
||||
path: 'newplugin.renamed',
|
||||
value: 'toberenamed',
|
||||
},
|
||||
],
|
||||
unset: [{ path: 'oldplugin.deprecated' }],
|
||||
});
|
||||
expect(addDeprecation.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
|
@ -242,20 +229,12 @@ describe('DeprecationFactory', () => {
|
|||
property: 'value',
|
||||
},
|
||||
};
|
||||
const processed = renameFromRoot('myplugin.deprecated', 'myplugin.new')(
|
||||
const commands = renameFromRoot('myplugin.deprecated', 'myplugin.new')(
|
||||
rawConfig,
|
||||
'does-not-matter',
|
||||
addDeprecation
|
||||
);
|
||||
expect(processed).toEqual({
|
||||
myplugin: {
|
||||
new: 'new',
|
||||
valid: 'valid',
|
||||
},
|
||||
someOtherPlugin: {
|
||||
property: 'value',
|
||||
},
|
||||
});
|
||||
expect(commands).toBeUndefined();
|
||||
expect(addDeprecation).toBeCalledTimes(0);
|
||||
});
|
||||
|
||||
|
@ -266,15 +245,13 @@ describe('DeprecationFactory', () => {
|
|||
renamed: 'renamed',
|
||||
},
|
||||
};
|
||||
const processed = renameFromRoot('myplugin.deprecated', 'myplugin.renamed')(
|
||||
const commands = renameFromRoot('myplugin.deprecated', 'myplugin.renamed')(
|
||||
rawConfig,
|
||||
'does-not-matter',
|
||||
addDeprecation
|
||||
);
|
||||
expect(processed).toEqual({
|
||||
myplugin: {
|
||||
renamed: 'renamed',
|
||||
},
|
||||
expect(commands).toEqual({
|
||||
unset: [{ path: 'myplugin.deprecated' }],
|
||||
});
|
||||
|
||||
expect(addDeprecation.mock.calls).toMatchInlineSnapshot(`
|
||||
|
@ -306,14 +283,9 @@ describe('DeprecationFactory', () => {
|
|||
property: 'value',
|
||||
},
|
||||
};
|
||||
const processed = unused('deprecated')(rawConfig, 'myplugin', addDeprecation);
|
||||
expect(processed).toEqual({
|
||||
myplugin: {
|
||||
valid: 'valid',
|
||||
},
|
||||
someOtherPlugin: {
|
||||
property: 'value',
|
||||
},
|
||||
const commands = unused('deprecated')(rawConfig, 'myplugin', addDeprecation);
|
||||
expect(commands).toEqual({
|
||||
unset: [{ path: 'myplugin.deprecated' }],
|
||||
});
|
||||
expect(addDeprecation.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
|
@ -343,17 +315,10 @@ describe('DeprecationFactory', () => {
|
|||
property: 'value',
|
||||
},
|
||||
};
|
||||
const processed = unused('section.deprecated')(rawConfig, 'myplugin', addDeprecation);
|
||||
expect(processed).toEqual({
|
||||
myplugin: {
|
||||
valid: 'valid',
|
||||
section: {},
|
||||
},
|
||||
someOtherPlugin: {
|
||||
property: 'value',
|
||||
},
|
||||
const commands = unused('section.deprecated')(rawConfig, 'myplugin', addDeprecation);
|
||||
expect(commands).toEqual({
|
||||
unset: [{ path: 'myplugin.section.deprecated' }],
|
||||
});
|
||||
|
||||
expect(addDeprecation.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
|
@ -379,15 +344,8 @@ describe('DeprecationFactory', () => {
|
|||
property: 'value',
|
||||
},
|
||||
};
|
||||
const processed = unused('deprecated')(rawConfig, 'myplugin', addDeprecation);
|
||||
expect(processed).toEqual({
|
||||
myplugin: {
|
||||
valid: 'valid',
|
||||
},
|
||||
someOtherPlugin: {
|
||||
property: 'value',
|
||||
},
|
||||
});
|
||||
const commands = unused('deprecated')(rawConfig, 'myplugin', addDeprecation);
|
||||
expect(commands).toBeUndefined();
|
||||
expect(addDeprecation).toBeCalledTimes(0);
|
||||
});
|
||||
});
|
||||
|
@ -403,20 +361,14 @@ describe('DeprecationFactory', () => {
|
|||
property: 'value',
|
||||
},
|
||||
};
|
||||
const processed = unusedFromRoot('myplugin.deprecated')(
|
||||
const commands = unusedFromRoot('myplugin.deprecated')(
|
||||
rawConfig,
|
||||
'does-not-matter',
|
||||
addDeprecation
|
||||
);
|
||||
expect(processed).toEqual({
|
||||
myplugin: {
|
||||
valid: 'valid',
|
||||
},
|
||||
someOtherPlugin: {
|
||||
property: 'value',
|
||||
},
|
||||
expect(commands).toEqual({
|
||||
unset: [{ path: 'myplugin.deprecated' }],
|
||||
});
|
||||
|
||||
expect(addDeprecation.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
|
@ -442,19 +394,12 @@ describe('DeprecationFactory', () => {
|
|||
property: 'value',
|
||||
},
|
||||
};
|
||||
const processed = unusedFromRoot('myplugin.deprecated')(
|
||||
const commands = unusedFromRoot('myplugin.deprecated')(
|
||||
rawConfig,
|
||||
'does-not-matter',
|
||||
addDeprecation
|
||||
);
|
||||
expect(processed).toEqual({
|
||||
myplugin: {
|
||||
valid: 'valid',
|
||||
},
|
||||
someOtherPlugin: {
|
||||
property: 'value',
|
||||
},
|
||||
});
|
||||
expect(commands).toBeUndefined();
|
||||
expect(addDeprecation).toBeCalledTimes(0);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,13 +7,12 @@
|
|||
*/
|
||||
|
||||
import { get } from 'lodash';
|
||||
import { set } from '@elastic/safer-lodash-set';
|
||||
import { unset } from '@kbn/std';
|
||||
import {
|
||||
ConfigDeprecation,
|
||||
AddConfigDeprecation,
|
||||
ConfigDeprecationFactory,
|
||||
DeprecatedConfigDetails,
|
||||
ConfigDeprecationCommand,
|
||||
} from './types';
|
||||
|
||||
const _rename = (
|
||||
|
@ -23,20 +22,16 @@ const _rename = (
|
|||
oldKey: string,
|
||||
newKey: string,
|
||||
details?: Partial<DeprecatedConfigDetails>
|
||||
) => {
|
||||
): void | ConfigDeprecationCommand => {
|
||||
const fullOldPath = getPath(rootPath, oldKey);
|
||||
const oldValue = get(config, fullOldPath);
|
||||
if (oldValue === undefined) {
|
||||
return config;
|
||||
return;
|
||||
}
|
||||
|
||||
unset(config, fullOldPath);
|
||||
|
||||
const fullNewPath = getPath(rootPath, newKey);
|
||||
const newValue = get(config, fullNewPath);
|
||||
if (newValue === undefined) {
|
||||
set(config, fullNewPath, oldValue);
|
||||
|
||||
addDeprecation({
|
||||
message: `"${fullOldPath}" is deprecated and has been replaced by "${fullNewPath}"`,
|
||||
correctiveActions: {
|
||||
|
@ -46,6 +41,10 @@ const _rename = (
|
|||
},
|
||||
...details,
|
||||
});
|
||||
return {
|
||||
set: [{ path: fullNewPath, value: oldValue }],
|
||||
unset: [{ path: fullOldPath }],
|
||||
};
|
||||
} else {
|
||||
addDeprecation({
|
||||
message: `"${fullOldPath}" is deprecated and has been replaced by "${fullNewPath}". However both key are present, ignoring "${fullOldPath}"`,
|
||||
|
@ -59,7 +58,9 @@ const _rename = (
|
|||
});
|
||||
}
|
||||
|
||||
return config;
|
||||
return {
|
||||
unset: [{ path: fullOldPath }],
|
||||
};
|
||||
};
|
||||
|
||||
const _unused = (
|
||||
|
@ -68,12 +69,11 @@ const _unused = (
|
|||
addDeprecation: AddConfigDeprecation,
|
||||
unusedKey: string,
|
||||
details?: Partial<DeprecatedConfigDetails>
|
||||
) => {
|
||||
): void | ConfigDeprecationCommand => {
|
||||
const fullPath = getPath(rootPath, unusedKey);
|
||||
if (get(config, fullPath) === undefined) {
|
||||
return config;
|
||||
return;
|
||||
}
|
||||
unset(config, fullPath);
|
||||
addDeprecation({
|
||||
message: `${fullPath} is deprecated and is no longer used`,
|
||||
correctiveActions: {
|
||||
|
@ -83,7 +83,9 @@ const _unused = (
|
|||
},
|
||||
...details,
|
||||
});
|
||||
return config;
|
||||
return {
|
||||
unset: [{ path: fullPath }],
|
||||
};
|
||||
};
|
||||
|
||||
const rename = (
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
export type {
|
||||
ConfigDeprecation,
|
||||
ConfigDeprecationCommand,
|
||||
ConfigDeprecationWithContext,
|
||||
ConfigDeprecationFactory,
|
||||
AddConfigDeprecation,
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { RecursiveReadonly } from '@kbn/utility-types';
|
||||
/**
|
||||
* Config deprecation hook used when invoking a {@link ConfigDeprecation}
|
||||
*
|
||||
|
@ -41,14 +41,29 @@ export interface DeprecatedConfigDetails {
|
|||
* @remarks
|
||||
* This should only be manually implemented if {@link ConfigDeprecationFactory} does not provide the proper helpers for a specific
|
||||
* deprecation need.
|
||||
* @param config must not be mutated, return {@link ConfigDeprecationCommand} to change config shape.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const provider: ConfigDeprecation = (config, path) => ({ unset: [{ key: 'path.to.key' }] })
|
||||
* ```
|
||||
* @internal
|
||||
*/
|
||||
export type ConfigDeprecation = (
|
||||
config: RecursiveReadonly<Record<string, any>>,
|
||||
fromPath: string,
|
||||
addDeprecation: AddConfigDeprecation
|
||||
) => void | ConfigDeprecationCommand;
|
||||
|
||||
/**
|
||||
* Outcome of deprecation operation. Allows mutating config values in a declarative way.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type ConfigDeprecation = (
|
||||
config: Record<string, any>,
|
||||
fromPath: string,
|
||||
addDeprecation: AddConfigDeprecation
|
||||
) => Record<string, any>;
|
||||
export interface ConfigDeprecationCommand {
|
||||
set?: Array<{ path: string; value: any }>;
|
||||
unset?: Array<{ path: string }>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A provider that should returns a list of {@link ConfigDeprecation}.
|
||||
|
@ -60,7 +75,7 @@ export type ConfigDeprecation = (
|
|||
* const provider: ConfigDeprecationProvider = ({ rename, unused }) => [
|
||||
* rename('oldKey', 'newKey'),
|
||||
* unused('deprecatedKey'),
|
||||
* myCustomDeprecation,
|
||||
* (config, path) => ({ unset: [{ key: 'path.to.key' }] })
|
||||
* ]
|
||||
* ```
|
||||
*
|
||||
|
|
|
@ -12,6 +12,7 @@ export type {
|
|||
ConfigDeprecationProvider,
|
||||
ConfigDeprecationWithContext,
|
||||
ConfigDeprecation,
|
||||
ConfigDeprecationCommand,
|
||||
} from './deprecation';
|
||||
|
||||
export { applyDeprecations, configDeprecationFactory } from './deprecation';
|
||||
|
|
|
@ -6,29 +6,26 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { has, get } from 'lodash';
|
||||
import { ConfigDeprecationProvider, ConfigDeprecation } from '@kbn/config';
|
||||
|
||||
const configPathDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => {
|
||||
if (has(process.env, 'CONFIG_PATH')) {
|
||||
if (process.env?.CONFIG_PATH) {
|
||||
addDeprecation({
|
||||
message: `Environment variable CONFIG_PATH is deprecated. It has been replaced with KBN_PATH_CONF pointing to a config folder`,
|
||||
});
|
||||
}
|
||||
return settings;
|
||||
};
|
||||
|
||||
const dataPathDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => {
|
||||
if (has(process.env, 'DATA_PATH')) {
|
||||
if (process.env?.DATA_PATH) {
|
||||
addDeprecation({
|
||||
message: `Environment variable "DATA_PATH" will be removed. It has been replaced with kibana.yml setting "path.data"`,
|
||||
});
|
||||
}
|
||||
return settings;
|
||||
};
|
||||
|
||||
const rewriteBasePathDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => {
|
||||
if (has(settings, 'server.basePath') && !has(settings, 'server.rewriteBasePath')) {
|
||||
if (settings.server?.basePath && !settings.server?.rewriteBasePath) {
|
||||
addDeprecation({
|
||||
message:
|
||||
'You should set server.basePath along with server.rewriteBasePath. Starting in 7.0, Kibana ' +
|
||||
|
@ -37,20 +34,19 @@ const rewriteBasePathDeprecation: ConfigDeprecation = (settings, fromPath, addDe
|
|||
'current behavior and silence this warning.',
|
||||
});
|
||||
}
|
||||
return settings;
|
||||
};
|
||||
|
||||
const rewriteCorsSettings: ConfigDeprecation = (settings, fromPath, addDeprecation) => {
|
||||
const corsSettings = get(settings, 'server.cors');
|
||||
if (typeof get(settings, 'server.cors') === 'boolean') {
|
||||
const corsSettings = settings.server?.cors;
|
||||
if (typeof corsSettings === 'boolean') {
|
||||
addDeprecation({
|
||||
message: '"server.cors" is deprecated and has been replaced by "server.cors.enabled"',
|
||||
});
|
||||
settings.server.cors = {
|
||||
enabled: corsSettings,
|
||||
|
||||
return {
|
||||
set: [{ path: 'server.cors', value: { enabled: corsSettings } }],
|
||||
};
|
||||
}
|
||||
return settings;
|
||||
};
|
||||
|
||||
const cspRulesDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => {
|
||||
|
@ -59,7 +55,7 @@ const cspRulesDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecati
|
|||
const SELF_POLICIES = Object.freeze(['script-src', 'style-src']);
|
||||
const SELF_STRING = `'self'`;
|
||||
|
||||
const rules: string[] = get(settings, 'csp.rules');
|
||||
const rules: string[] = settings.csp?.rules;
|
||||
if (rules) {
|
||||
const parsed = new Map(
|
||||
rules.map((ruleStr) => {
|
||||
|
@ -68,34 +64,39 @@ const cspRulesDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecati
|
|||
})
|
||||
);
|
||||
|
||||
settings.csp.rules = [...parsed].map(([policy, sourceList]) => {
|
||||
if (sourceList.find((source) => source.includes(NONCE_STRING))) {
|
||||
addDeprecation({
|
||||
message: `csp.rules no longer supports the {nonce} syntax. Replacing with 'self' in ${policy}`,
|
||||
});
|
||||
sourceList = sourceList.filter((source) => !source.includes(NONCE_STRING));
|
||||
return {
|
||||
set: [
|
||||
{
|
||||
path: 'csp.rules',
|
||||
value: [...parsed].map(([policy, sourceList]) => {
|
||||
if (sourceList.find((source) => source.includes(NONCE_STRING))) {
|
||||
addDeprecation({
|
||||
message: `csp.rules no longer supports the {nonce} syntax. Replacing with 'self' in ${policy}`,
|
||||
});
|
||||
sourceList = sourceList.filter((source) => !source.includes(NONCE_STRING));
|
||||
|
||||
// Add 'self' if not present
|
||||
if (!sourceList.find((source) => source.includes(SELF_STRING))) {
|
||||
sourceList.push(SELF_STRING);
|
||||
}
|
||||
}
|
||||
// Add 'self' if not present
|
||||
if (!sourceList.find((source) => source.includes(SELF_STRING))) {
|
||||
sourceList.push(SELF_STRING);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
SELF_POLICIES.includes(policy) &&
|
||||
!sourceList.find((source) => source.includes(SELF_STRING))
|
||||
) {
|
||||
addDeprecation({
|
||||
message: `csp.rules must contain the 'self' source. Automatically adding to ${policy}.`,
|
||||
});
|
||||
sourceList.push(SELF_STRING);
|
||||
}
|
||||
if (
|
||||
SELF_POLICIES.includes(policy) &&
|
||||
!sourceList.find((source) => source.includes(SELF_STRING))
|
||||
) {
|
||||
addDeprecation({
|
||||
message: `csp.rules must contain the 'self' source. Automatically adding to ${policy}.`,
|
||||
});
|
||||
sourceList.push(SELF_STRING);
|
||||
}
|
||||
|
||||
return `${policy} ${sourceList.join(' ')}`.trim();
|
||||
});
|
||||
return `${policy} ${sourceList.join(' ')}`.trim();
|
||||
}),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
return settings;
|
||||
};
|
||||
|
||||
const mapManifestServiceUrlDeprecation: ConfigDeprecation = (
|
||||
|
@ -103,7 +104,7 @@ const mapManifestServiceUrlDeprecation: ConfigDeprecation = (
|
|||
fromPath,
|
||||
addDeprecation
|
||||
) => {
|
||||
if (has(settings, 'map.manifestServiceUrl')) {
|
||||
if (settings.map?.manifestServiceUrl) {
|
||||
addDeprecation({
|
||||
message:
|
||||
'You should no longer use the map.manifestServiceUrl setting in kibana.yml to configure the location ' +
|
||||
|
@ -112,11 +113,10 @@ const mapManifestServiceUrlDeprecation: ConfigDeprecation = (
|
|||
'modified for use in production environments.',
|
||||
});
|
||||
}
|
||||
return settings;
|
||||
};
|
||||
|
||||
const opsLoggingEventDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => {
|
||||
if (has(settings, 'logging.events.ops')) {
|
||||
if (settings.logging?.events?.ops) {
|
||||
addDeprecation({
|
||||
documentationUrl:
|
||||
'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingevents',
|
||||
|
@ -127,11 +127,10 @@ const opsLoggingEventDeprecation: ConfigDeprecation = (settings, fromPath, addDe
|
|||
'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx',
|
||||
});
|
||||
}
|
||||
return settings;
|
||||
};
|
||||
|
||||
const requestLoggingEventDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => {
|
||||
if (has(settings, 'logging.events.request') || has(settings, 'logging.events.response')) {
|
||||
if (settings.logging?.events?.request || settings.logging?.events?.response) {
|
||||
addDeprecation({
|
||||
documentationUrl:
|
||||
'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingevents',
|
||||
|
@ -142,11 +141,10 @@ const requestLoggingEventDeprecation: ConfigDeprecation = (settings, fromPath, a
|
|||
'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx',
|
||||
});
|
||||
}
|
||||
return settings;
|
||||
};
|
||||
|
||||
const timezoneLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => {
|
||||
if (has(settings, 'logging.timezone')) {
|
||||
if (settings.logging?.timezone) {
|
||||
addDeprecation({
|
||||
documentationUrl:
|
||||
'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingtimezone',
|
||||
|
@ -157,11 +155,10 @@ const timezoneLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDe
|
|||
'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx',
|
||||
});
|
||||
}
|
||||
return settings;
|
||||
};
|
||||
|
||||
const destLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => {
|
||||
if (has(settings, 'logging.dest')) {
|
||||
if (settings.logging?.dest) {
|
||||
addDeprecation({
|
||||
documentationUrl:
|
||||
'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingdest',
|
||||
|
@ -172,11 +169,10 @@ const destLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDeprec
|
|||
'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx',
|
||||
});
|
||||
}
|
||||
return settings;
|
||||
};
|
||||
|
||||
const quietLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => {
|
||||
if (has(settings, 'logging.quiet')) {
|
||||
if (settings.logging?.quiet) {
|
||||
addDeprecation({
|
||||
documentationUrl:
|
||||
'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingquiet',
|
||||
|
@ -185,11 +181,10 @@ const quietLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDepre
|
|||
'in 8.0. Moving forward, you can use "logging.root.level:error" in your logging configuration. ',
|
||||
});
|
||||
}
|
||||
return settings;
|
||||
};
|
||||
|
||||
const silentLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => {
|
||||
if (has(settings, 'logging.silent')) {
|
||||
if (settings.logging?.silent) {
|
||||
addDeprecation({
|
||||
documentationUrl:
|
||||
'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingsilent',
|
||||
|
@ -198,11 +193,10 @@ const silentLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDepr
|
|||
'in 8.0. Moving forward, you can use "logging.root.level:off" in your logging configuration. ',
|
||||
});
|
||||
}
|
||||
return settings;
|
||||
};
|
||||
|
||||
const verboseLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => {
|
||||
if (has(settings, 'logging.verbose')) {
|
||||
if (settings.logging?.verbose) {
|
||||
addDeprecation({
|
||||
documentationUrl:
|
||||
'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingverbose',
|
||||
|
@ -211,7 +205,6 @@ const verboseLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDep
|
|||
'in 8.0. Moving forward, you can use "logging.root.level:all" in your logging configuration. ',
|
||||
});
|
||||
}
|
||||
return settings;
|
||||
};
|
||||
|
||||
const jsonLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => {
|
||||
|
@ -219,7 +212,7 @@ const jsonLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDeprec
|
|||
// the dev CLI code in src/dev/cli_dev_mode/using_server_process.ts manually
|
||||
// specifies `--logging.json=false`. Since it's executed in a child process, the
|
||||
// ` legacyLoggingConfigSchema` returns `true` for the TTY check on `process.stdout.isTTY`
|
||||
if (has(settings, 'logging.json') && settings.env !== 'development') {
|
||||
if (settings.logging?.json && settings.env !== 'development') {
|
||||
addDeprecation({
|
||||
documentationUrl:
|
||||
'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx',
|
||||
|
@ -232,11 +225,10 @@ const jsonLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDeprec
|
|||
'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx',
|
||||
});
|
||||
}
|
||||
return settings;
|
||||
};
|
||||
|
||||
const logRotateDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => {
|
||||
if (has(settings, 'logging.rotate')) {
|
||||
if (settings.logging?.rotate) {
|
||||
addDeprecation({
|
||||
documentationUrl:
|
||||
'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#rolling-file-appender',
|
||||
|
@ -247,11 +239,10 @@ const logRotateDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecat
|
|||
'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#rolling-file-appender',
|
||||
});
|
||||
}
|
||||
return settings;
|
||||
};
|
||||
|
||||
const logEventsLogDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => {
|
||||
if (has(settings, 'logging.events.log')) {
|
||||
if (settings.logging?.events?.log) {
|
||||
addDeprecation({
|
||||
documentationUrl:
|
||||
'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingevents',
|
||||
|
@ -260,11 +251,10 @@ const logEventsLogDeprecation: ConfigDeprecation = (settings, fromPath, addDepre
|
|||
'in 8.0. Moving forward, log levels can be customized on a per-logger basis using the new logging configuration. ',
|
||||
});
|
||||
}
|
||||
return settings;
|
||||
};
|
||||
|
||||
const logEventsErrorDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => {
|
||||
if (has(settings, 'logging.events.error')) {
|
||||
if (settings.logging?.events?.error) {
|
||||
addDeprecation({
|
||||
documentationUrl:
|
||||
'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingevents',
|
||||
|
@ -273,18 +263,16 @@ const logEventsErrorDeprecation: ConfigDeprecation = (settings, fromPath, addDep
|
|||
'in 8.0. Moving forward, you can use "logging.root.level: error" in your logging configuration. ',
|
||||
});
|
||||
}
|
||||
return settings;
|
||||
};
|
||||
|
||||
const logFilterDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => {
|
||||
if (has(settings, 'logging.filter')) {
|
||||
if (settings.logging?.filter) {
|
||||
addDeprecation({
|
||||
documentationUrl:
|
||||
'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingfilter',
|
||||
message: '"logging.filter" has been deprecated and will be removed in 8.0.',
|
||||
});
|
||||
}
|
||||
return settings;
|
||||
};
|
||||
|
||||
export const coreDeprecationProvider: ConfigDeprecationProvider = ({ rename, unusedFromRoot }) => [
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import { set } from '@elastic/safer-lodash-set';
|
||||
import type { ConfigDeprecationProvider } from '@kbn/config';
|
||||
import { configDeprecationFactory, applyDeprecations } from '@kbn/config';
|
||||
|
||||
|
@ -38,7 +39,7 @@ export const getDeprecationsFor = ({
|
|||
settings?: Record<string, any>;
|
||||
path: string;
|
||||
}) => {
|
||||
return collectDeprecations(provider, { [path]: settings }, path);
|
||||
return collectDeprecations(provider, set({}, path, settings), path);
|
||||
};
|
||||
|
||||
export const getDeprecationsForGlobalSettings = ({
|
||||
|
|
|
@ -147,7 +147,7 @@ const deprecations: ConfigDeprecationProvider = () => [
|
|||
(settings, fromPath, addDeprecation) => {
|
||||
const es = settings[fromPath];
|
||||
if (!es) {
|
||||
return settings;
|
||||
return;
|
||||
}
|
||||
if (es.username === 'elastic') {
|
||||
addDeprecation({
|
||||
|
@ -171,7 +171,7 @@ const deprecations: ConfigDeprecationProvider = () => [
|
|||
message: `Setting [${fromPath}.logQueries] is deprecated and no longer used. You should set the log level to "debug" for the "elasticsearch.queries" context in "logging.loggers" or use "logging.verbose: true".`,
|
||||
});
|
||||
}
|
||||
return settings;
|
||||
return;
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { AddConfigDeprecation, CoreSetup, CoreStart, PluginConfigDescriptor } from 'kibana/server';
|
||||
import type { CoreSetup, CoreStart, PluginConfigDescriptor } from 'kibana/server';
|
||||
import { get } from 'lodash';
|
||||
|
||||
import { configSchema, ConfigSchema } from '../config';
|
||||
|
@ -19,16 +19,12 @@ export const config: PluginConfigDescriptor<ConfigSchema> = {
|
|||
deprecations: ({ renameFromRoot }) => [
|
||||
// TODO: Remove deprecation once defaultAppId is deleted
|
||||
renameFromRoot('kibana.defaultAppId', 'kibana_legacy.defaultAppId', { silent: true }),
|
||||
(
|
||||
completeConfig: Record<string, any>,
|
||||
rootPath: string,
|
||||
addDeprecation: AddConfigDeprecation
|
||||
) => {
|
||||
(completeConfig, rootPath, addDeprecation) => {
|
||||
if (
|
||||
get(completeConfig, 'kibana.defaultAppId') === undefined &&
|
||||
get(completeConfig, 'kibana_legacy.defaultAppId') === undefined
|
||||
) {
|
||||
return completeConfig;
|
||||
return;
|
||||
}
|
||||
addDeprecation({
|
||||
message: `kibana.defaultAppId is deprecated and will be removed in 8.0. Please use the \`defaultRoute\` advanced setting instead`,
|
||||
|
@ -40,7 +36,6 @@ export const config: PluginConfigDescriptor<ConfigSchema> = {
|
|||
],
|
||||
},
|
||||
});
|
||||
return completeConfig;
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
38
x-pack/plugins/banners/server/config.test.ts
Normal file
38
x-pack/plugins/banners/server/config.test.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { config } from './config';
|
||||
import { getDeprecationsFor } from '../../../../src/core/server/test_utils';
|
||||
|
||||
function applyDeprecations(settings?: Record<string, any>) {
|
||||
return getDeprecationsFor({ provider: config.deprecations!, settings, path: 'xpack.banners' });
|
||||
}
|
||||
|
||||
describe('deprecations', () => {
|
||||
it('replaces xpack.banners.placement from "header" to "top"', () => {
|
||||
const { migrated } = applyDeprecations({
|
||||
placement: 'header',
|
||||
});
|
||||
expect(migrated.xpack.banners.placement).toBe('top');
|
||||
});
|
||||
it('logs a warning message about xpack.banners.placement renaming', () => {
|
||||
const { messages } = applyDeprecations({
|
||||
placement: 'header',
|
||||
});
|
||||
expect(messages).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"The \`header\` value for xpack.banners.placement has been replaced by \`top\`",
|
||||
]
|
||||
`);
|
||||
});
|
||||
it('do not rename other placement values', () => {
|
||||
const { migrated, messages } = applyDeprecations({
|
||||
placement: 'disabled',
|
||||
});
|
||||
expect(migrated.xpack.banners.placement).toBe('disabled');
|
||||
expect(messages.length).toBe(0);
|
||||
});
|
||||
});
|
|
@ -46,10 +46,10 @@ export const config: PluginConfigDescriptor<BannersConfigType> = {
|
|||
addDeprecation({
|
||||
message: 'The `header` value for xpack.banners.placement has been replaced by `top`',
|
||||
});
|
||||
pluginConfig.placement = 'top';
|
||||
return {
|
||||
set: [{ path: `${fromPath}.placement`, value: 'top' }],
|
||||
};
|
||||
}
|
||||
|
||||
return rootConfig;
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -39,8 +39,6 @@ export const config: PluginConfigDescriptor<ReportingConfigType> = {
|
|||
`through feature controls in Management > Security > Roles`,
|
||||
});
|
||||
}
|
||||
|
||||
return settings;
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -29,8 +29,6 @@ export const securityConfigDeprecationProvider: ConfigDeprecationProvider = ({
|
|||
'Defining `xpack.security.authc.providers` as an array of provider types is deprecated. Use extended `object` format instead.',
|
||||
});
|
||||
}
|
||||
|
||||
return settings;
|
||||
},
|
||||
(settings, fromPath, addDeprecation) => {
|
||||
const hasProviderType = (providerType: string) => {
|
||||
|
@ -50,7 +48,6 @@ export const securityConfigDeprecationProvider: ConfigDeprecationProvider = ({
|
|||
'Enabling both `basic` and `token` authentication providers in `xpack.security.authc.providers` is deprecated. Login page will only use `token` provider.',
|
||||
});
|
||||
}
|
||||
return settings;
|
||||
},
|
||||
(settings, fromPath, addDeprecation) => {
|
||||
const samlProviders = (settings?.xpack?.security?.authc?.providers?.saml ?? {}) as Record<
|
||||
|
@ -63,8 +60,6 @@ export const securityConfigDeprecationProvider: ConfigDeprecationProvider = ({
|
|||
'`xpack.security.authc.providers.saml.<provider-name>.maxRedirectURLSize` is deprecated and is no longer used',
|
||||
});
|
||||
}
|
||||
|
||||
return settings;
|
||||
},
|
||||
(settings, fromPath, addDeprecation) => {
|
||||
if (settings?.xpack?.security?.enabled === false) {
|
||||
|
@ -74,6 +69,5 @@ export const securityConfigDeprecationProvider: ConfigDeprecationProvider = ({
|
|||
'To turn off security features, disable them in Elasticsearch instead.',
|
||||
});
|
||||
}
|
||||
return settings;
|
||||
},
|
||||
];
|
||||
|
|
|
@ -30,7 +30,6 @@ const disabledDeprecation: ConfigDeprecation = (config, fromPath, addDeprecation
|
|||
message: `Disabling the Spaces plugin (xpack.spaces.enabled) will not be supported in the next major version (8.0)`,
|
||||
});
|
||||
}
|
||||
return config;
|
||||
};
|
||||
|
||||
export const spacesConfigDeprecationProvider: ConfigDeprecationProvider = () => {
|
||||
|
|
|
@ -45,7 +45,6 @@ export const config: PluginConfigDescriptor<TaskManagerConfig> = {
|
|||
message: `setting "${fromPath}.max_workers" (${taskManager?.max_workers}) greater than ${MAX_WORKERS_LIMIT} is deprecated. Values greater than ${MAX_WORKERS_LIMIT} will not be supported starting in 8.0.`,
|
||||
});
|
||||
}
|
||||
return settings;
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue