diff --git a/src/cli_plugin/install/kibana.js b/src/cli_plugin/install/kibana.js
index 675cc180a9d94..7cc18f55ab73b 100644
--- a/src/cli_plugin/install/kibana.js
+++ b/src/cli_plugin/install/kibana.js
@@ -42,8 +42,7 @@ export async function rebuildCache(settings, logger) {
'--env.name=production',
'--optimize.useBundleCache=false',
'--server.autoListen=false',
- '--plugins.initialize=false',
- '--uiSettings.enabled=false'
+ '--plugins.initialize=false'
];
const proc = execa(process.execPath, kibanaArgs, {
diff --git a/src/core_plugins/kibana/public/management/sections/settings/__snapshots__/advanced_settings.test.js.snap b/src/core_plugins/kibana/public/management/sections/settings/__snapshots__/advanced_settings.test.js.snap
index bfde665a6e5dc..c72d3e2f64c58 100644
--- a/src/core_plugins/kibana/public/management/sections/settings/__snapshots__/advanced_settings.test.js.snap
+++ b/src/core_plugins/kibana/public/management/sections/settings/__snapshots__/advanced_settings.test.js.snap
@@ -76,7 +76,7 @@ exports[`AdvancedSettings should render normally 1`] = `
categoryCounts={
Object {
"elasticsearch": 2,
- "general": 7,
+ "general": 11,
}
}
clear={[Function]}
@@ -96,6 +96,7 @@ exports[`AdvancedSettings should render normally 1`] = `
"description": "Description for Test array setting",
"displayName": "Test array setting",
"isCustom": undefined,
+ "isOverridden": false,
"name": "test:array:setting",
"options": undefined,
"readonly": false,
@@ -111,6 +112,7 @@ exports[`AdvancedSettings should render normally 1`] = `
"description": "Description for Test boolean setting",
"displayName": "Test boolean setting",
"isCustom": undefined,
+ "isOverridden": false,
"name": "test:boolean:setting",
"options": undefined,
"readonly": false,
@@ -128,6 +130,7 @@ exports[`AdvancedSettings should render normally 1`] = `
"description": "Description for Test custom string setting",
"displayName": "Test custom string setting",
"isCustom": undefined,
+ "isOverridden": false,
"name": "test:customstring:setting",
"options": undefined,
"readonly": false,
@@ -143,12 +146,83 @@ exports[`AdvancedSettings should render normally 1`] = `
"description": "Description for Test image setting",
"displayName": "Test image setting",
"isCustom": undefined,
+ "isOverridden": false,
"name": "test:image:setting",
"options": undefined,
"readonly": false,
"type": "image",
"value": undefined,
},
+ Object {
+ "ariaName": "test is overridden json",
+ "category": Array [
+ "general",
+ ],
+ "defVal": "{
+ \\"foo\\": \\"bar\\"
+}",
+ "description": "Description for overridden json",
+ "displayName": "An overridden json",
+ "isCustom": undefined,
+ "isOverridden": true,
+ "name": "test:isOverridden:json",
+ "options": undefined,
+ "readonly": false,
+ "type": "json",
+ "value": undefined,
+ },
+ Object {
+ "ariaName": "test is overridden number",
+ "category": Array [
+ "general",
+ ],
+ "defVal": 1234,
+ "description": "Description for overridden number",
+ "displayName": "An overridden number",
+ "isCustom": undefined,
+ "isOverridden": true,
+ "name": "test:isOverridden:number",
+ "options": undefined,
+ "readonly": false,
+ "type": "number",
+ "value": undefined,
+ },
+ Object {
+ "ariaName": "test is overridden select",
+ "category": Array [
+ "general",
+ ],
+ "defVal": "orange",
+ "description": "Description for overridden select setting",
+ "displayName": "Test overridden select setting",
+ "isCustom": undefined,
+ "isOverridden": true,
+ "name": "test:isOverridden:select",
+ "options": Array [
+ "apple",
+ "orange",
+ "banana",
+ ],
+ "readonly": false,
+ "type": "select",
+ "value": undefined,
+ },
+ Object {
+ "ariaName": "test is overridden string",
+ "category": Array [
+ "general",
+ ],
+ "defVal": "foo",
+ "description": "Description for overridden string",
+ "displayName": "An overridden string",
+ "isCustom": undefined,
+ "isOverridden": true,
+ "name": "test:isOverridden:string",
+ "options": undefined,
+ "readonly": false,
+ "type": "string",
+ "value": undefined,
+ },
Object {
"ariaName": "test json setting",
"category": Array [
@@ -158,6 +232,7 @@ exports[`AdvancedSettings should render normally 1`] = `
"description": "Description for Test json setting",
"displayName": "Test json setting",
"isCustom": undefined,
+ "isOverridden": false,
"name": "test:json:setting",
"options": undefined,
"readonly": false,
@@ -173,6 +248,7 @@ exports[`AdvancedSettings should render normally 1`] = `
"description": "Description for Test markdown setting",
"displayName": "Test markdown setting",
"isCustom": undefined,
+ "isOverridden": false,
"name": "test:markdown:setting",
"options": undefined,
"readonly": false,
@@ -188,6 +264,7 @@ exports[`AdvancedSettings should render normally 1`] = `
"description": "Description for Test number setting",
"displayName": "Test number setting",
"isCustom": undefined,
+ "isOverridden": false,
"name": "test:number:setting",
"options": undefined,
"readonly": false,
@@ -203,6 +280,7 @@ exports[`AdvancedSettings should render normally 1`] = `
"description": "Description for Test select setting",
"displayName": "Test select setting",
"isCustom": undefined,
+ "isOverridden": false,
"name": "test:select:setting",
"options": Array [
"apple",
@@ -222,6 +300,7 @@ exports[`AdvancedSettings should render normally 1`] = `
"description": "Description for Test string setting",
"displayName": "Test string setting",
"isCustom": undefined,
+ "isOverridden": false,
"name": "test:string:setting",
"options": undefined,
"readonly": false,
@@ -329,7 +408,7 @@ exports[`AdvancedSettings should render specific setting if given setting key 1`
categoryCounts={
Object {
"elasticsearch": 2,
- "general": 7,
+ "general": 11,
}
}
clear={[Function]}
@@ -347,6 +426,7 @@ exports[`AdvancedSettings should render specific setting if given setting key 1`
"description": "Description for Test string setting",
"displayName": "Test string setting",
"isCustom": undefined,
+ "isOverridden": false,
"name": "test:string:setting",
"options": undefined,
"readonly": false,
diff --git a/src/core_plugins/kibana/public/management/sections/settings/advanced_settings.js b/src/core_plugins/kibana/public/management/sections/settings/advanced_settings.js
index 7899bbd2b0681..9f607a0e0577f 100644
--- a/src/core_plugins/kibana/public/management/sections/settings/advanced_settings.js
+++ b/src/core_plugins/kibana/public/management/sections/settings/advanced_settings.js
@@ -90,6 +90,7 @@ export class AdvancedSettings extends Component {
name: setting[0],
value: setting[1].userValue,
isCustom: config.isCustom(setting[0]),
+ isOverridden: config.isOverridden(setting[0]),
});
})
.filter((c) => !c.readonly)
diff --git a/src/core_plugins/kibana/public/management/sections/settings/advanced_settings.test.js b/src/core_plugins/kibana/public/management/sections/settings/advanced_settings.test.js
index 113f23b6b6f4a..23b873d2e08de 100644
--- a/src/core_plugins/kibana/public/management/sections/settings/advanced_settings.test.js
+++ b/src/core_plugins/kibana/public/management/sections/settings/advanced_settings.test.js
@@ -19,6 +19,7 @@
import React from 'react';
import { shallow } from 'enzyme';
+import dedent from 'dedent';
import { AdvancedSettings } from './advanced_settings';
@@ -44,6 +45,7 @@ const config = {
set: () => {},
remove: () => {},
isCustom: (setting) => setting.isCustom,
+ isOverridden: (key) => Boolean(config.getAll()[key].isOverridden),
getAll: () => {
return {
'test:array:setting': {
@@ -109,6 +111,39 @@ const config = {
type: 'string',
isCustom: true,
},
+ 'test:isOverridden:string': {
+ isOverridden: true,
+ value: 'foo',
+ name: 'An overridden string',
+ description: 'Description for overridden string',
+ type: 'string',
+ },
+ 'test:isOverridden:number': {
+ isOverridden: true,
+ value: 1234,
+ name: 'An overridden number',
+ description: 'Description for overridden number',
+ type: 'number',
+ },
+ 'test:isOverridden:json': {
+ isOverridden: true,
+ value: dedent`
+ {
+ "foo": "bar"
+ }
+ `,
+ name: 'An overridden json',
+ description: 'Description for overridden json',
+ type: 'json',
+ },
+ 'test:isOverridden:select': {
+ isOverridden: true,
+ value: 'orange',
+ name: 'Test overridden select setting',
+ description: 'Description for overridden select setting',
+ type: 'select',
+ options: ['apple', 'orange', 'banana'],
+ },
};
}
};
diff --git a/src/core_plugins/kibana/public/management/sections/settings/components/field/__snapshots__/field.test.js.snap b/src/core_plugins/kibana/public/management/sections/settings/components/field/__snapshots__/field.test.js.snap
index 7f3a8dd7f8242..881e228173b31 100644
--- a/src/core_plugins/kibana/public/management/sections/settings/components/field/__snapshots__/field.test.js.snap
+++ b/src/core_plugins/kibana/public/management/sections/settings/components/field/__snapshots__/field.test.js.snap
@@ -1,5 +1,106 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`Field for array setting should render as read only with help text if overridden 1`] = `
+
+
+
+
+
+
+
+
+ Default:
+
+ default_value
+
+
+
+
+
+ }
+ fullWidth={false}
+ gutterSize="l"
+ idAria="array:test:setting-aria"
+ title={
+
+ Array test setting
+
+
+ }
+ titleSize="xs"
+ >
+
+ This setting is overriden by the Kibana server and can not be changed.
+
+ }
+ isInvalid={false}
+ label={
+
+ array:test:setting
+
+ }
+ >
+
+
+
+
+
+
+`;
+
exports[`Field for array setting should render custom setting icon if it is custom 1`] = `
`;
+exports[`Field for boolean setting should render as read only with help text if overridden 1`] = `
+
+
+
+
+
+
+
+
+ Default:
+
+ true
+
+
+
+
+
+ }
+ fullWidth={false}
+ gutterSize="l"
+ idAria="boolean:test:setting-aria"
+ title={
+
+ Boolean test setting
+
+
+ }
+ titleSize="xs"
+ >
+
+ This setting is overriden by the Kibana server and can not be changed.
+
+ }
+ isInvalid={false}
+ label={
+
+ boolean:test:setting
+
+ }
+ >
+
+
+
+
+
+
+`;
+
exports[`Field for boolean setting should render custom setting icon if it is custom 1`] = `
`;
-exports[`Field for image setting should render custom setting icon if it is custom 1`] = `
+exports[`Field for image setting should render as read only with help text if overridden 1`] = `
+
+
+
+
+ Default:
+
+ null
+
+
+
+
}
fullWidth={false}
@@ -568,12 +784,7 @@ exports[`Field for image setting should render custom setting icon if it is cust
title={
Image test setting
-
+
}
titleSize="xs"
@@ -587,7 +798,14 @@ exports[`Field for image setting should render custom setting icon if it is cust
error={null}
fullWidth={false}
hasEmptyLabelSpace={false}
- helpText={null}
+ helpText={
+
+ This setting is overriden by the Kibana server and can not be changed.
+
+ }
isInvalid={false}
label={
}
>
-
@@ -616,7 +832,7 @@ exports[`Field for image setting should render custom setting icon if it is cust
`;
-exports[`Field for image setting should render default value if there is no user value set 1`] = `
+exports[`Field for image setting should render custom setting icon if it is custom 1`] = `
Image test setting
-
+
}
titleSize="xs"
@@ -693,7 +914,7 @@ exports[`Field for image setting should render default value if there is no user
`;
-exports[`Field for image setting should render user value if there is user value is set 1`] = `
+exports[`Field for image setting should render default value if there is no user value set 1`] = `
-
-
-
-
- Default:
-
- null
-
-
-
-
}
fullWidth={false}
@@ -757,7 +962,100 @@ exports[`Field for image setting should render user value if there is user value
error={null}
fullWidth={false}
hasEmptyLabelSpace={false}
- helpText={
+ helpText={null}
+ isInvalid={false}
+ label={
+
+ image:test:setting
+
+ }
+ >
+
+
+
+
+
+
+`;
+
+exports[`Field for image setting should render user value if there is user value is set 1`] = `
+
+
+
+
+
+
+
+
+ Default:
+
+ null
+
+
+
+
+
+ }
+ fullWidth={false}
+ gutterSize="l"
+ idAria="image:test:setting-aria"
+ title={
+
+ Image test setting
+
+
+ }
+ titleSize="xs"
+ >
+
`;
+exports[`Field for json setting should render as read only with help text if overridden 1`] = `
+
+
+
+
+
+
+
+
+ Default:
+
+ {}
+
+
+
+
+
+ }
+ fullWidth={false}
+ gutterSize="l"
+ idAria="json:test:setting-aria"
+ title={
+
+ Json test setting
+
+
+ }
+ titleSize="xs"
+ >
+
+ This setting is overriden by the Kibana server and can not be changed.
+
+ }
+ isInvalid={false}
+ label={
+
+ json:test:setting
+
+ }
+ >
+
+
+
+
+
+
+
+
+`;
+
exports[`Field for json setting should render custom setting icon if it is custom 1`] = `
`;
-exports[`Field for markdown setting should render custom setting icon if it is custom 1`] = `
+exports[`Field for markdown setting should render as read only with help text if overridden 1`] = `
+
+
+
+
+ Default:
+
+ null
+
+
+
+
}
fullWidth={false}
@@ -1198,12 +1636,7 @@ exports[`Field for markdown setting should render custom setting icon if it is c
title={
Markdown test setting
-
+
}
titleSize="xs"
@@ -1217,7 +1650,14 @@ exports[`Field for markdown setting should render custom setting icon if it is c
error={null}
fullWidth={false}
hasEmptyLabelSpace={false}
- helpText={null}
+ helpText={
+
+ This setting is overriden by the Kibana server and can not be changed.
+
+ }
isInvalid={false}
label={
@@ -1262,7 +1703,7 @@ exports[`Field for markdown setting should render custom setting icon if it is c
`;
-exports[`Field for markdown setting should render default value if there is no user value set 1`] = `
+exports[`Field for markdown setting should render custom setting icon if it is custom 1`] = `
Markdown test setting
-
+
}
titleSize="xs"
@@ -1330,6 +1776,7 @@ exports[`Field for markdown setting should render default value if there is no u
}
}
height="auto"
+ isReadOnly={false}
maxLines={30}
minLines={6}
mode="markdown"
@@ -1355,7 +1802,7 @@ exports[`Field for markdown setting should render default value if there is no u
`;
-exports[`Field for markdown setting should render user value if there is user value is set 1`] = `
+exports[`Field for markdown setting should render default value if there is no user value set 1`] = `
-
-
-
+ }
+ fullWidth={false}
+ gutterSize="l"
+ idAria="markdown:test:setting-aria"
+ title={
+
+ Markdown test setting
+
+
+ }
+ titleSize="xs"
+ >
+
+ markdown:test:setting
+
+ }
+ >
+
+
+
+
+
+
+
+
+`;
+
+exports[`Field for markdown setting should render user value if there is user value is set 1`] = `
+
+
+
+
+
+
+
Default:
@@ -1454,6 +1995,7 @@ exports[`Field for markdown setting should render user value if there is user va
}
}
height="auto"
+ isReadOnly={false}
maxLines={30}
minLines={6}
mode="markdown"
@@ -1479,6 +2021,107 @@ exports[`Field for markdown setting should render user value if there is user va
`;
+exports[`Field for number setting should render as read only with help text if overridden 1`] = `
+
+
+
+
+
+
+
+
+ Default:
+
+ 5
+
+
+
+
+
+ }
+ fullWidth={false}
+ gutterSize="l"
+ idAria="number:test:setting-aria"
+ title={
+
+ Number test setting
+
+
+ }
+ titleSize="xs"
+ >
+
+ This setting is overriden by the Kibana server and can not be changed.
+
+ }
+ isInvalid={false}
+ label={
+
+ number:test:setting
+
+ }
+ >
+
+
+
+
+
+
+`;
+
exports[`Field for number setting should render custom setting icon if it is custom 1`] = `
`;
+exports[`Field for select setting should render as read only with help text if overridden 1`] = `
+
+
+
+
+
+
+
+
+ Default:
+
+ orange
+
+
+
+
+
+ }
+ fullWidth={false}
+ gutterSize="l"
+ idAria="select:test:setting-aria"
+ title={
+
+ Select test setting
+
+
+ }
+ titleSize="xs"
+ >
+
+ This setting is overriden by the Kibana server and can not be changed.
+
+ }
+ isInvalid={false}
+ label={
+
+ select:test:setting
+
+ }
+ >
+
+
+
+
+
+
+`;
+
exports[`Field for select setting should render custom setting icon if it is custom 1`] = `
`;
+exports[`Field for string setting should render as read only with help text if overridden 1`] = `
+
+
+
+
+
+
+
+
+ Default:
+
+ null
+
+
+
+
+
+ }
+ fullWidth={false}
+ gutterSize="l"
+ idAria="string:test:setting-aria"
+ title={
+
+ String test setting
+
+
+ }
+ titleSize="xs"
+ >
+
+ This setting is overriden by the Kibana server and can not be changed.
+
+ }
+ isInvalid={false}
+ label={
+
+ string:test:setting
+
+ }
+ >
+
+
+
+
+
+
+`;
+
exports[`Field for string setting should render custom setting icon if it is custom 1`] = `
@@ -346,6 +346,7 @@ export class Field extends PureComponent {
height="auto"
minLines={6}
maxLines={30}
+ isReadOnly={isOverridden}
setOptions={{
showLineNumbers: false,
tabSize: 2,
@@ -369,7 +370,7 @@ export class Field extends PureComponent {
} else {
return (
{ this.changeImageForm = input; }}
@@ -387,7 +388,7 @@ export class Field extends PureComponent {
})}
onChange={this.onFieldChange}
isLoading={loading}
- disabled={loading}
+ disabled={loading || isOverridden}
onKeyDown={this.onFieldKeyDown}
data-test-subj={`advancedSetting-editField-${name}`}
/>
@@ -398,7 +399,7 @@ export class Field extends PureComponent {
value={unsavedValue}
onChange={this.onFieldChange}
isLoading={loading}
- disabled={loading}
+ disabled={loading || isOverridden}
onKeyDown={this.onFieldKeyDown}
data-test-subj={`advancedSetting-editField-${name}`}
/>
@@ -409,7 +410,7 @@ export class Field extends PureComponent {
value={unsavedValue}
onChange={this.onFieldChange}
isLoading={loading}
- disabled={loading}
+ disabled={loading || isOverridden}
onKeyDown={this.onFieldKeyDown}
data-test-subj={`advancedSetting-editField-${name}`}
/>
@@ -426,6 +427,14 @@ export class Field extends PureComponent {
}
renderHelpText(setting) {
+ if (setting.isOverridden) {
+ return (
+
+ This setting is overriden by the Kibana server and can not be changed.
+
+ );
+ }
+
const defaultLink = this.renderResetToDefaultLink(setting);
const imageLink = this.renderChangeImageLink(setting);
@@ -538,9 +547,12 @@ export class Field extends PureComponent {
renderActions(setting) {
const { ariaName, name } = setting;
const { loading, isInvalid, changeImage, savedValue, unsavedValue } = this.state;
- if(savedValue === unsavedValue && !changeImage) {
+ const isDisabled = loading || setting.isOverridden;
+
+ if (savedValue === unsavedValue && !changeImage) {
return;
}
+
return (
@@ -549,7 +561,7 @@ export class Field extends PureComponent {
fill
aria-label={`Save ${ariaName}`}
onClick={this.saveEdit}
- disabled={loading || isInvalid}
+ disabled={isDisabled || isInvalid}
data-test-subj={`advancedSetting-saveEditField-${name}`}
>
Save
@@ -559,7 +571,7 @@ export class Field extends PureComponent {
changeImage ? this.cancelChangeImage() : this.cancelEdit()}
- disabled={loading}
+ disabled={isDisabled}
data-test-subj={`advancedSetting-cancelEditField-${name}`}
>
Cancel
diff --git a/src/core_plugins/kibana/public/management/sections/settings/components/field/field.test.js b/src/core_plugins/kibana/public/management/sections/settings/components/field/field.test.js
index f49fc88b0e5cb..901d63af2f3e9 100644
--- a/src/core_plugins/kibana/public/management/sections/settings/components/field/field.test.js
+++ b/src/core_plugins/kibana/public/management/sections/settings/components/field/field.test.js
@@ -42,6 +42,7 @@ const settings = {
value: undefined,
defVal: ['default_value'],
isCustom: false,
+ isOverridden: false,
options: null,
},
boolean: {
@@ -53,6 +54,7 @@ const settings = {
value: undefined,
defVal: true,
isCustom: false,
+ isOverridden: false,
options: null,
},
image: {
@@ -64,6 +66,7 @@ const settings = {
value: undefined,
defVal: null,
isCustom: false,
+ isOverridden: false,
options: {
maxSize: {
length: 1000,
@@ -81,6 +84,7 @@ const settings = {
value: '{"foo": "bar"}',
defVal: '{}',
isCustom: false,
+ isOverridden: false,
options: null,
},
markdown: {
@@ -92,6 +96,7 @@ const settings = {
value: undefined,
defVal: '',
isCustom: false,
+ isOverridden: false,
options: null,
},
number: {
@@ -103,6 +108,7 @@ const settings = {
value: undefined,
defVal: 5,
isCustom: false,
+ isOverridden: false,
options: null,
},
select: {
@@ -114,6 +120,7 @@ const settings = {
value: undefined,
defVal: 'orange',
isCustom: false,
+ isOverridden: false,
options: ['apple', 'orange', 'banana'],
},
string: {
@@ -125,6 +132,7 @@ const settings = {
value: undefined,
defVal: null,
isCustom: false,
+ isOverridden: false,
options: null,
},
};
@@ -158,6 +166,22 @@ describe('Field', () => {
expect(component).toMatchSnapshot();
});
+ it('should render as read only with help text if overridden', async () => {
+ const component = shallow(
+
+ );
+
+ expect(component).toMatchSnapshot();
+ });
+
it('should render user value if there is user value is set', async () => {
const component = shallow(
Joi.object({
}).default(),
}).default(),
+ uiSettings: Joi.object().keys({
+ overrides: Joi.object().unknown(true).default()
+ }).default(),
+
logging: Joi.object().keys({
silent: Joi.boolean().default(false),
diff --git a/src/ui/public/config/config.js b/src/ui/public/config/config.js
index ee46adf5de3c1..a4f668529d475 100644
--- a/src/ui/public/config/config.js
+++ b/src/ui/public/config/config.js
@@ -38,6 +38,7 @@ module.service(`config`, function ($rootScope, Promise) {
this.isDeclared = (...args) => uiSettings.isDeclared(...args);
this.isDefault = (...args) => uiSettings.isDefault(...args);
this.isCustom = (...args) => uiSettings.isCustom(...args);
+ this.isOverridden = (...args) => uiSettings.isOverridden(...args);
// modify remove() to use angular Promises
this.remove = (key) => (
diff --git a/src/ui/ui_settings/__tests__/ui_settings_mixin_integration.js b/src/ui/ui_settings/__tests__/ui_settings_mixin_integration.js
index 44d633b069395..aa4e20d2c0041 100644
--- a/src/ui/ui_settings/__tests__/ui_settings_mixin_integration.js
+++ b/src/ui/ui_settings/__tests__/ui_settings_mixin_integration.js
@@ -34,13 +34,13 @@ import { uiSettingsMixin } from '../ui_settings_mixin';
describe('uiSettingsMixin()', () => {
const sandbox = sinon.createSandbox();
- async function setup(options = {}) {
- const {
- enabled = true
- } = options;
-
+ async function setup() {
const config = await Config.withDefaultSchema({
- uiSettings: { enabled }
+ uiSettings: {
+ overrides: {
+ foo: 'bar'
+ }
+ }
});
// maps of decorations passed to `server.decorate()`
@@ -111,6 +111,9 @@ describe('uiSettingsMixin()', () => {
sinon.assert.calledOnce(uiSettingsServiceFactory);
sinon.assert.calledWithExactly(uiSettingsServiceFactory, server, {
foo: 'bar',
+ overrides: {
+ foo: 'bar'
+ },
getDefaults: sinon.match.func,
});
});
diff --git a/src/ui/ui_settings/__tests__/ui_settings_service.js b/src/ui/ui_settings/__tests__/ui_settings_service.js
index 46757dc690baf..2f8e82880dd02 100644
--- a/src/ui/ui_settings/__tests__/ui_settings_service.js
+++ b/src/ui/ui_settings/__tests__/ui_settings_service.js
@@ -17,7 +17,6 @@
* under the License.
*/
-import { isEqual } from 'lodash';
import expect from 'expect.js';
import { errors as esErrors } from 'elasticsearch';
import Chance from 'chance';
@@ -42,6 +41,7 @@ describe('ui settings', () => {
const {
getDefaults,
defaults = {},
+ overrides,
esDocSource = {},
savedObjectsClient = createObjectsClientStub(TYPE, ID, esDocSource)
} = options;
@@ -52,6 +52,7 @@ describe('ui settings', () => {
buildNum: BUILD_NUM,
getDefaults: getDefaults || (() => defaults),
savedObjectsClient,
+ overrides,
});
const createOrUpgradeSavedConfig = sandbox.stub(createOrUpgradeSavedConfigNS, 'createOrUpgradeSavedConfig');
@@ -67,21 +68,6 @@ describe('ui settings', () => {
afterEach(() => sandbox.restore());
- describe('overview', () => {
- it('has expected api surface', () => {
- const { uiSettings } = setup();
- expect(uiSettings).to.have.property('get').a('function');
- expect(uiSettings).to.have.property('getAll').a('function');
- expect(uiSettings).to.have.property('getDefaults').a('function');
- expect(uiSettings).to.have.property('getRaw').a('function');
- expect(uiSettings).to.have.property('getUserProvided').a('function');
- expect(uiSettings).to.have.property('remove').a('function');
- expect(uiSettings).to.have.property('removeMany').a('function');
- expect(uiSettings).to.have.property('set').a('function');
- expect(uiSettings).to.have.property('setMany').a('function');
- });
- });
-
describe('#setMany()', () => {
it('returns a promise', () => {
const { uiSettings } = setup();
@@ -127,6 +113,23 @@ describe('ui settings', () => {
sinon.assert.calledTwice(savedObjectsClient.update);
sinon.assert.calledOnce(createOrUpgradeSavedConfig);
});
+
+ it('throws an error if any key is overridden', async () => {
+ const { uiSettings } = setup({
+ overrides: {
+ foo: 'bar'
+ }
+ });
+
+ try {
+ await uiSettings.setMany({
+ bar: 'box',
+ foo: 'baz'
+ });
+ } catch (error) {
+ expect(error.message).to.be('Unable to update "foo" because it is overridden');
+ }
+ });
});
describe('#set()', () => {
@@ -140,6 +143,20 @@ describe('ui settings', () => {
await uiSettings.set('one', 'value');
savedObjectsClient.assertUpdateQuery({ one: 'value' });
});
+
+ it('throws an error if the key is overridden', async () => {
+ const { uiSettings } = setup({
+ overrides: {
+ foo: 'bar'
+ }
+ });
+
+ try {
+ await uiSettings.set('foo', 'baz');
+ } catch (error) {
+ expect(error.message).to.be('Unable to update "foo" because it is overridden');
+ }
+ });
});
describe('#remove()', () => {
@@ -153,6 +170,20 @@ describe('ui settings', () => {
await uiSettings.remove('one');
savedObjectsClient.assertUpdateQuery({ one: null });
});
+
+ it('throws an error if the key is overridden', async () => {
+ const { uiSettings } = setup({
+ overrides: {
+ foo: 'bar'
+ }
+ });
+
+ try {
+ await uiSettings.remove('foo');
+ } catch (error) {
+ expect(error.message).to.be('Unable to update "foo" because it is overridden');
+ }
+ });
});
describe('#removeMany()', () => {
@@ -172,6 +203,20 @@ describe('ui settings', () => {
await uiSettings.removeMany(['one', 'two', 'three']);
savedObjectsClient.assertUpdateQuery({ one: null, two: null, three: null });
});
+
+ it('throws an error if any key is overridden', async () => {
+ const { uiSettings } = setup({
+ overrides: {
+ foo: 'bar'
+ }
+ });
+
+ try {
+ await uiSettings.setMany(['bar', 'foo']);
+ } catch (error) {
+ expect(error.message).to.be('Unable to update "foo" because it is overridden');
+ }
+ });
});
describe('#getDefaults()', () => {
@@ -210,18 +255,25 @@ describe('ui settings', () => {
const esDocSource = { user: 'customized' };
const { uiSettings } = setup({ esDocSource });
const result = await uiSettings.getUserProvided();
- expect(isEqual(result, {
- user: { userValue: 'customized' }
- })).to.equal(true);
+ expect(result).to.eql({
+ user: {
+ userValue: 'customized',
+ }
+ });
});
it('ignores null user configuration (because default values)', async () => {
const esDocSource = { user: 'customized', usingDefault: null, something: 'else' };
const { uiSettings } = setup({ esDocSource });
const result = await uiSettings.getUserProvided();
- expect(isEqual(result, {
- user: { userValue: 'customized' }, something: { userValue: 'else' }
- })).to.equal(true);
+ expect(result).to.eql({
+ user: {
+ userValue: 'customized'
+ },
+ something: {
+ userValue: 'else'
+ }
+ });
});
it('returns an empty object on 404 responses', async () => {
@@ -291,6 +343,27 @@ describe('ui settings', () => {
expect(err).to.be(expectedUnexpectedError);
}
});
+
+ it('includes overridden values for overridden keys', async () => {
+ const esDocSource = {
+ user: 'customized'
+ };
+
+ const overrides = {
+ foo: 'bar'
+ };
+
+ const { uiSettings } = setup({ esDocSource, overrides });
+ expect(await uiSettings.getUserProvided()).to.eql({
+ user: {
+ userValue: 'customized',
+ },
+ foo: {
+ userValue: 'bar',
+ isOverridden: true,
+ },
+ });
+ });
});
describe('#getRaw()', () => {
@@ -324,6 +397,24 @@ describe('ui settings', () => {
},
});
});
+
+ it('includes the values for overridden keys', async () => {
+ const esDocSource = { foo: 'bar' };
+ const defaults = { key: { value: chance.word() } };
+ const overrides = { foo: true };
+ const { uiSettings } = setup({ esDocSource, defaults, overrides });
+ const result = await uiSettings.getRaw();
+
+ expect(result).to.eql({
+ foo: {
+ userValue: true,
+ isOverridden: true,
+ },
+ key: {
+ value: defaults.key.value,
+ },
+ });
+ });
});
describe('#getAll()', () => {
@@ -361,6 +452,29 @@ describe('ui settings', () => {
bar: 'user-provided',
});
});
+
+ it('includes the values for overridden keys', async () => {
+ const esDocSource = {
+ foo: 'user-override',
+ bar: 'user-provided',
+ };
+
+ const defaults = {
+ foo: {
+ value: 'default'
+ },
+ };
+
+ const overrides = {
+ foo: 'bax'
+ };
+
+ const { uiSettings } = setup({ esDocSource, defaults, overrides });
+ expect(await uiSettings.getAll()).to.eql({
+ foo: 'bax',
+ bar: 'user-provided',
+ });
+ });
});
describe('#get()', () => {
@@ -392,5 +506,61 @@ describe('ui settings', () => {
const result = await uiSettings.get('dateFormat');
expect(result).to.equal('YYYY-MM-DD');
});
+
+ it('returns the overridden value for an overrided key', async () => {
+ const esDocSource = { dateFormat: 'YYYY-MM-DD' };
+ const overrides = { dateFormat: 'foo' };
+ const { uiSettings } = setup({ esDocSource, overrides });
+ expect(await uiSettings.get('dateFormat')).to.be('foo');
+ });
+
+ it('returns the default value for an override with value null', async () => {
+ const esDocSource = { dateFormat: 'YYYY-MM-DD' };
+ const overrides = { dateFormat: null };
+ const defaults = { dateFormat: { value: 'foo' } };
+ const { uiSettings } = setup({ esDocSource, overrides, defaults });
+ expect(await uiSettings.get('dateFormat')).to.be('foo');
+ });
+
+ it('returns the overridden value if the document does not exist', async () => {
+ const overrides = { dateFormat: 'foo' };
+ const { uiSettings, savedObjectsClient } = setup({ overrides });
+ savedObjectsClient.get.throws(savedObjectsClientErrors.createGenericNotFoundError());
+ expect(await uiSettings.get('dateFormat')).to.be('foo');
+ });
+ });
+
+ describe('#isOverridden()', () => {
+ it('returns false if no overrides defined', () => {
+ const { uiSettings } = setup();
+ expect(uiSettings.isOverridden('foo')).to.be(false);
+ });
+ it('returns false if overrides defined but key is not included', () => {
+ const { uiSettings } = setup({ overrides: { foo: true, bar: true } });
+ expect(uiSettings.isOverridden('baz')).to.be(false);
+ });
+ it('returns false for object prototype properties', () => {
+ const { uiSettings } = setup({ overrides: { foo: true, bar: true } });
+ expect(uiSettings.isOverridden('hasOwnProperty')).to.be(false);
+ });
+ it('returns true if overrides defined and key is overridden', () => {
+ const { uiSettings } = setup({ overrides: { foo: true, bar: true } });
+ expect(uiSettings.isOverridden('bar')).to.be(true);
+ });
+ });
+
+ describe('#assertUpdateAllowed()', () => {
+ it('returns false if no overrides defined', () => {
+ const { uiSettings } = setup();
+ expect(uiSettings.assertUpdateAllowed('foo')).to.be(undefined);
+ });
+ it('throws 400 Boom error when keys is overridden', () => {
+ const { uiSettings } = setup({ overrides: { foo: true } });
+ expect(() => uiSettings.assertUpdateAllowed('foo')).to.throwError(error => {
+ expect(error).to.have.property('message', 'Unable to update "foo" because it is overridden');
+ expect(error).to.have.property('isBoom', true);
+ expect(error.output).to.have.property('statusCode', 400);
+ });
+ });
});
});
diff --git a/src/ui/ui_settings/public/__snapshots__/ui_settings_client.test.js.snap b/src/ui/ui_settings/public/__snapshots__/ui_settings_client.test.js.snap
index 84000c0913ed5..8915553b36bf1 100644
--- a/src/ui/ui_settings/public/__snapshots__/ui_settings_client.test.js.snap
+++ b/src/ui/ui_settings/public/__snapshots__/ui_settings_client.test.js.snap
@@ -20,6 +20,8 @@ You can use \`config.get(\\"throwableProperty\\", defaultValue)\`, which will ju
\`defaultValue\` when the key is unrecognized."
`;
+exports[`#overrideLocalDefault #assertUpdateAllowed() throws error when keys is overridden 1`] = `"Unable to update \\"foo\\" because its value is overridden by the Kibana server"`;
+
exports[`#overrideLocalDefault key has no user value calls subscriber with new and previous value: single subscriber call 1`] = `
Array [
Array [
@@ -95,6 +97,10 @@ Object {
}
`;
+exports[`#remove throws an error if key is overridden 1`] = `"Unable to update \\"bar\\" because its value is overridden by the Kibana server"`;
+
+exports[`#set throws an error if key is overridden 1`] = `"Unable to update \\"foo\\" because its value is overridden by the Kibana server"`;
+
exports[`#subscribe calls handler with { key, newValue, oldValue } when config changes 1`] = `
Array [
Array [
diff --git a/src/ui/ui_settings/public/ui_settings_client.js b/src/ui/ui_settings/public/ui_settings_client.js
index f479ab37262cc..60505065143d7 100644
--- a/src/ui/ui_settings/public/ui_settings_client.js
+++ b/src/ui/ui_settings/public/ui_settings_client.js
@@ -102,6 +102,16 @@ You can use \`config.get("${key}", defaultValue)\`, which will just return
return this.isDeclared(key) && !('value' in this._cache[key]);
}
+ isOverridden(key) {
+ return this.isDeclared(key) && Boolean(this._cache[key].isOverridden);
+ }
+
+ assertUpdateAllowed(key) {
+ if (this.isOverridden(key)) {
+ throw new Error(`Unable to update "${key}" because its value is overridden by the Kibana server`);
+ }
+ }
+
overrideLocalDefault(key, newDefault) {
// capture the previous value
const prevDefault = this._defaults[key]
@@ -137,6 +147,8 @@ You can use \`config.get("${key}", defaultValue)\`, which will just return
}
async _update(key, value) {
+ this.assertUpdateAllowed(key);
+
const declared = this.isDeclared(key);
const defaults = this._defaults;
@@ -151,7 +163,7 @@ You can use \`config.get("${key}", defaultValue)\`, which will just return
}
const initialVal = declared ? this.get(key) : undefined;
- this._setLocally(key, newVal, initialVal);
+ this._setLocally(key, newVal);
try {
const { settings } = await this._api.batchSet(key, newVal);
@@ -165,6 +177,8 @@ You can use \`config.get("${key}", defaultValue)\`, which will just return
}
_setLocally(key, newValue) {
+ this.assertUpdateAllowed(key);
+
if (!this.isDeclared(key)) {
this._cache[key] = {};
}
diff --git a/src/ui/ui_settings/public/ui_settings_client.test.js b/src/ui/ui_settings/public/ui_settings_client.test.js
index 6fda16da1096f..f41c9bc0018ca 100644
--- a/src/ui/ui_settings/public/ui_settings_client.test.js
+++ b/src/ui/ui_settings/public/ui_settings_client.test.js
@@ -123,6 +123,18 @@ describe('#set', () => {
await expect(config.set('foo', 'bar')).resolves.toBe(false);
});
+
+ it('throws an error if key is overridden', async () => {
+ const { config } = setup({
+ initialSettings: {
+ foo: {
+ isOverridden: true,
+ value: 'bar'
+ }
+ }
+ });
+ await expect(config.set('foo', true)).rejects.toThrowErrorMatchingSnapshot();
+ });
});
describe('#remove', () => {
@@ -140,6 +152,18 @@ describe('#remove', () => {
await expect(config.remove('dateFormat')).resolves.toBe(false);
});
+
+ it('throws an error if key is overridden', async () => {
+ const { config } = setup({
+ initialSettings: {
+ bar: {
+ isOverridden: true,
+ userValue: true
+ }
+ }
+ });
+ await expect(config.remove('bar')).rejects.toThrowErrorMatchingSnapshot();
+ });
});
describe('#isDeclared', () => {
@@ -293,4 +317,61 @@ describe('#overrideLocalDefault', () => {
expect(config.getAll()).toMatchSnapshot('getAll after override');
});
});
+
+ describe('#isOverridden()', () => {
+ it('returns false if key is unknown', () => {
+ const { config } = setup();
+ expect(config.isOverridden('foo')).toBe(false);
+ });
+ it('returns false if key is no overridden', () => {
+ const { config } = setup({
+ initialSettings: {
+ foo: {
+ userValue: 1
+ },
+ bar: {
+ isOverridden: true,
+ userValue: 2
+ }
+ }
+ });
+ expect(config.isOverridden('foo')).toBe(false);
+ });
+ it('returns true when key is overridden', () => {
+ const { config } = setup({
+ initialSettings: {
+ foo: {
+ userValue: 1
+ },
+ bar: {
+ isOverridden: true,
+ userValue: 2
+ },
+ }
+ });
+ expect(config.isOverridden('bar')).toBe(true);
+ });
+ it('returns false for object prototype properties', () => {
+ const { config } = setup();
+ expect(config.isOverridden('hasOwnProperty')).toBe(false);
+ });
+ });
+
+ describe('#assertUpdateAllowed()', () => {
+ it('returns false if no settings defined', () => {
+ const { config } = setup();
+ expect(config.assertUpdateAllowed('foo')).toBe(undefined);
+ });
+ it('throws error when keys is overridden', () => {
+ const { config } = setup({
+ initialSettings: {
+ foo: {
+ isOverridden: true,
+ userValue: 'bar'
+ }
+ }
+ });
+ expect(() => config.assertUpdateAllowed('foo')).toThrowErrorMatchingSnapshot();
+ });
+ });
});
diff --git a/src/ui/ui_settings/routes/__tests__/doc_exists.js b/src/ui/ui_settings/routes/__tests__/doc_exists.js
index 7cc1f8b542354..2c03ab66e4983 100644
--- a/src/ui/ui_settings/routes/__tests__/doc_exists.js
+++ b/src/ui/ui_settings/routes/__tests__/doc_exists.js
@@ -82,7 +82,11 @@ export function docExistsSuite() {
},
defaultIndex: {
userValue: defaultIndex
- }
+ },
+ foo: {
+ userValue: 'bar',
+ isOverridden: true
+ },
}
});
});
@@ -109,10 +113,33 @@ export function docExistsSuite() {
},
defaultIndex: {
userValue: defaultIndex
- }
+ },
+ foo: {
+ userValue: 'bar',
+ isOverridden: true
+ },
}
});
});
+
+ it('returns a 400 if trying to set overridden value', async () => {
+ const { kbnServer } = await setup();
+
+ const { statusCode, result } = await kbnServer.inject({
+ method: 'POST',
+ url: '/api/kibana/settings/foo',
+ payload: {
+ value: 'baz'
+ }
+ });
+
+ expect(statusCode).to.be(400);
+ assertSinonMatch(result, {
+ error: 'Bad Request',
+ message: 'Unable to update "foo" because it is overridden',
+ statusCode: 400
+ });
+ });
});
describe('setMany route', () => {
@@ -138,9 +165,34 @@ export function docExistsSuite() {
},
defaultIndex: {
userValue: defaultIndex
+ },
+ foo: {
+ userValue: 'bar',
+ isOverridden: true
+ },
+ }
+ });
+ });
+
+ it('returns a 400 if trying to set overridden value', async () => {
+ const { kbnServer } = await setup();
+
+ const { statusCode, result } = await kbnServer.inject({
+ method: 'POST',
+ url: '/api/kibana/settings',
+ payload: {
+ changes: {
+ foo: 'baz'
}
}
});
+
+ expect(statusCode).to.be(400);
+ assertSinonMatch(result, {
+ error: 'Bad Request',
+ message: 'Unable to update "foo" because it is overridden',
+ statusCode: 400
+ });
});
});
@@ -164,9 +216,28 @@ export function docExistsSuite() {
settings: {
buildNum: {
userValue: sinon.match.number
- }
+ },
+ foo: {
+ userValue: 'bar',
+ isOverridden: true
+ },
}
});
});
+ it('returns a 400 if deleting overridden value', async () => {
+ const { kbnServer } = await setup();
+
+ const { statusCode, result } = await kbnServer.inject({
+ method: 'DELETE',
+ url: '/api/kibana/settings/foo'
+ });
+
+ expect(statusCode).to.be(400);
+ assertSinonMatch(result, {
+ error: 'Bad Request',
+ message: 'Unable to update "foo" because it is overridden',
+ statusCode: 400
+ });
+ });
});
}
diff --git a/src/ui/ui_settings/routes/__tests__/doc_missing.js b/src/ui/ui_settings/routes/__tests__/doc_missing.js
index 4fd39649a5004..d3afefcc0932a 100644
--- a/src/ui/ui_settings/routes/__tests__/doc_missing.js
+++ b/src/ui/ui_settings/routes/__tests__/doc_missing.js
@@ -48,7 +48,7 @@ export function docMissingSuite() {
});
describe('get route', () => {
- it('creates doc, returns a 200 with no settings', async () => {
+ it('creates doc, returns a 200 with only overridden settings', async () => {
const { kbnServer } = getServices();
const { statusCode, result } = await kbnServer.inject({
@@ -58,7 +58,12 @@ export function docMissingSuite() {
expect(statusCode).to.be(200);
assertSinonMatch(result, {
- settings: {}
+ settings: {
+ foo: {
+ userValue: 'bar',
+ isOverridden: true
+ }
+ }
});
});
});
@@ -82,6 +87,10 @@ export function docMissingSuite() {
},
defaultIndex: {
userValue: defaultIndex
+ },
+ foo: {
+ userValue: 'bar',
+ isOverridden: true
}
}
});
@@ -109,6 +118,10 @@ export function docMissingSuite() {
},
defaultIndex: {
userValue: defaultIndex
+ },
+ foo: {
+ userValue: 'bar',
+ isOverridden: true
}
}
});
@@ -129,6 +142,10 @@ export function docMissingSuite() {
settings: {
buildNum: {
userValue: sinon.match.number
+ },
+ foo: {
+ userValue: 'bar',
+ isOverridden: true
}
}
});
diff --git a/src/ui/ui_settings/routes/__tests__/index.js b/src/ui/ui_settings/routes/__tests__/index.js
index ca4a9e589de9b..7479996576c6a 100644
--- a/src/ui/ui_settings/routes/__tests__/index.js
+++ b/src/ui/ui_settings/routes/__tests__/index.js
@@ -27,7 +27,6 @@ import { docMissingSuite } from './doc_missing';
import { indexMissingSuite } from './index_missing';
describe('uiSettings/routes', function () {
-
/**
* The "doc missing" and "index missing" tests verify how the uiSettings
* API behaves in between healthChecks, so they interact with the healthCheck
diff --git a/src/ui/ui_settings/routes/__tests__/index_missing.js b/src/ui/ui_settings/routes/__tests__/index_missing.js
index 838cc9b48b524..c8487d03b363b 100644
--- a/src/ui/ui_settings/routes/__tests__/index_missing.js
+++ b/src/ui/ui_settings/routes/__tests__/index_missing.js
@@ -59,7 +59,7 @@ export function indexMissingSuite() {
}
describe('get route', () => {
- it('returns a 200 and with empty values', async () => {
+ it('returns a 200 and with just overridden values', async () => {
const { kbnServer } = await setup();
const { statusCode, result } = await kbnServer.inject({
@@ -68,7 +68,14 @@ export function indexMissingSuite() {
});
expect(statusCode).to.be(200);
- expect(result).to.eql({ settings: {} });
+ expect(result).to.eql({
+ settings: {
+ foo: {
+ userValue: 'bar',
+ isOverridden: true
+ }
+ }
+ });
});
});
@@ -93,6 +100,10 @@ export function indexMissingSuite() {
},
defaultIndex: {
userValue: defaultIndex
+ },
+ foo: {
+ userValue: 'bar',
+ isOverridden: true
}
}
});
@@ -122,6 +133,10 @@ export function indexMissingSuite() {
},
defaultIndex: {
userValue: defaultIndex
+ },
+ foo: {
+ userValue: 'bar',
+ isOverridden: true
}
}
});
@@ -144,6 +159,10 @@ export function indexMissingSuite() {
settings: {
buildNum: {
userValue: sinon.match.number
+ },
+ foo: {
+ userValue: 'bar',
+ isOverridden: true
}
}
});
diff --git a/src/ui/ui_settings/routes/__tests__/lib/servers.js b/src/ui/ui_settings/routes/__tests__/lib/servers.js
index 38ccd172cf5a2..825c5c85ca7d5 100644
--- a/src/ui/ui_settings/routes/__tests__/lib/servers.js
+++ b/src/ui/ui_settings/routes/__tests__/lib/servers.js
@@ -39,7 +39,13 @@ export async function startServers() {
log.indent(-4);
await es.start();
- kbnServer = kbnTestServer.createServerWithCorePlugins();
+ kbnServer = kbnTestServer.createServerWithCorePlugins({
+ uiSettings: {
+ overrides: {
+ foo: 'bar',
+ }
+ }
+ });
await kbnServer.ready();
await kbnServer.server.plugins.elasticsearch.waitUntilReady();
}
diff --git a/src/ui/ui_settings/ui_settings_mixin.js b/src/ui/ui_settings/ui_settings_mixin.js
index 6ea81df2c229b..9b958efc97848 100644
--- a/src/ui/ui_settings/ui_settings_mixin.js
+++ b/src/ui/ui_settings/ui_settings_mixin.js
@@ -30,10 +30,12 @@ export function uiSettingsMixin(kbnServer, server) {
const getDefaults = () => (
kbnServer.uiExports.uiSettingDefaults
);
+ const overrides = kbnServer.config.get('uiSettings.overrides');
server.decorate('server', 'uiSettingsServiceFactory', (options = {}) => {
return uiSettingsServiceFactory(server, {
getDefaults,
+ overrides,
...options
});
});
@@ -41,6 +43,7 @@ export function uiSettingsMixin(kbnServer, server) {
server.addMemoizedFactoryToRequest('getUiSettingsService', request => {
return getUiSettingsServiceForRequest(server, request, {
getDefaults,
+ overrides,
});
});
diff --git a/src/ui/ui_settings/ui_settings_service.js b/src/ui/ui_settings/ui_settings_service.js
index bf9b12133999a..6234884fdb881 100644
--- a/src/ui/ui_settings/ui_settings_service.js
+++ b/src/ui/ui_settings/ui_settings_service.js
@@ -18,14 +18,9 @@
*/
import { defaultsDeep } from 'lodash';
-import { createOrUpgradeSavedConfig } from './create_or_upgrade_saved_config';
+import Boom from 'boom';
-function hydrateUserSettings(userSettings) {
- return Object.keys(userSettings)
- .map(key => ({ key, userValue: userSettings[key] }))
- .filter(({ userValue }) => userValue !== null)
- .reduce((acc, { key, userValue }) => ({ ...acc, [key]: { userValue } }), {});
-}
+import { createOrUpgradeSavedConfig } from './create_or_upgrade_saved_config';
/**
* Service that provides access to the UiSettings stored in elasticsearch.
@@ -53,6 +48,7 @@ export class UiSettingsService {
getDefaults = () => ({}),
// function that accepts log messages in the same format as server.log
log = () => {},
+ overrides = {},
} = options;
this._type = type;
@@ -60,6 +56,7 @@ export class UiSettingsService {
this._buildNum = buildNum;
this._savedObjectsClient = savedObjectsClient;
this._getDefaults = getDefaults;
+ this._overrides = overrides;
this._log = log;
}
@@ -91,7 +88,26 @@ export class UiSettingsService {
}
async getUserProvided(options) {
- return hydrateUserSettings(await this._read(options));
+ const userProvided = {};
+
+ // write the userValue for each key stored in the saved object that is not overridden
+ for (const [key, userValue] of Object.entries(await this._read(options))) {
+ if (userValue !== null && !this.isOverridden(key)) {
+ userProvided[key] = {
+ userValue
+ };
+ }
+ }
+
+ // write all overridden keys, dropping the userValue is override is null and
+ // adding keys for overrides that are not in saved object
+ for (const [key, userValue] of Object.entries(this._overrides)) {
+ userProvided[key] = userValue === null
+ ? { isOverridden: true }
+ : { isOverridden: true, userValue };
+ }
+
+ return userProvided;
}
async setMany(changes) {
@@ -114,7 +130,21 @@ export class UiSettingsService {
await this.setMany(changes);
}
+ isOverridden(key) {
+ return this._overrides.hasOwnProperty(key);
+ }
+
+ assertUpdateAllowed(key) {
+ if (this.isOverridden(key)) {
+ throw Boom.badRequest(`Unable to update "${key}" because it is overridden`);
+ }
+ }
+
async _write({ changes, autoCreateOrUpgradeIfMissing = true }) {
+ for (const key of Object.keys(changes)) {
+ this.assertUpdateAllowed(key);
+ }
+
try {
await this._savedObjectsClient.update(this._type, this._id, changes);
} catch (error) {
diff --git a/src/ui/ui_settings/ui_settings_service_factory.js b/src/ui/ui_settings/ui_settings_service_factory.js
index 4b9859abf6bdb..595c8a2d904d5 100644
--- a/src/ui/ui_settings/ui_settings_service_factory.js
+++ b/src/ui/ui_settings/ui_settings_service_factory.js
@@ -37,6 +37,7 @@ export function uiSettingsServiceFactory(server, options) {
const {
savedObjectsClient,
getDefaults,
+ overrides,
} = options;
return new UiSettingsService({
@@ -45,6 +46,7 @@ export function uiSettingsServiceFactory(server, options) {
buildNum: config.get('pkg.buildNum'),
savedObjectsClient,
getDefaults,
+ overrides,
log: (...args) => server.log(...args),
});
}
diff --git a/src/ui/ui_settings/ui_settings_service_for_request.js b/src/ui/ui_settings/ui_settings_service_for_request.js
index 58dcbe22b4b0c..422c9cc14f833 100644
--- a/src/ui/ui_settings/ui_settings_service_for_request.js
+++ b/src/ui/ui_settings/ui_settings_service_for_request.js
@@ -34,11 +34,13 @@ import { uiSettingsServiceFactory } from './ui_settings_service_factory';
*/
export function getUiSettingsServiceForRequest(server, request, options = {}) {
const {
- getDefaults
+ getDefaults,
+ overrides,
} = options;
const uiSettingsService = uiSettingsServiceFactory(server, {
getDefaults,
+ overrides,
savedObjectsClient: request.getSavedObjectsClient()
});
diff --git a/test/common/services/kibana_server/ui_settings.js b/test/common/services/kibana_server/ui_settings.js
index 8971a012f64b9..ff318fa5eeb16 100644
--- a/test/common/services/kibana_server/ui_settings.js
+++ b/test/common/services/kibana_server/ui_settings.js
@@ -66,7 +66,9 @@ export class KibanaServerUiSettings {
const { payload } = await this._wreck.get('/api/kibana/settings');
for (const key of Object.keys(payload.settings)) {
- await this._wreck.delete(`/api/kibana/settings/${key}`);
+ if (!payload.settings[key].isOverridden) {
+ await this._wreck.delete(`/api/kibana/settings/${key}`);
+ }
}
this._log.debug('replacing kibana config doc: %j', doc);