[Index Management] Update editable index settings for Serverless (#168884)

## Summary

Fixes https://github.com/elastic/kibana/issues/165895

This PR limits which index settings are displayed on the index details
page, "Settings" tab in the edit mode. On serverless only a handful of
index settings will be editable by the user. The UI only prevents
displaying some index settings, but it's still possible for the user to
type in a setting that can't be edited. That is the case on dedicated as
well.

### How to test
1. Start Serverless ES and Kibana
2. Navigate to Index Management and create a test index
3. Click on the index name and on the details page click the tab
"Settings"
4. Toggle the "Edit mode" switch and verify that only editable settings
are displayed.

#### Screenshot
<img width="527" alt="Screenshot 2023-10-16 at 20 25 49"
src="e6678cca-3494-4c63-ae66-ace9c823d12d">
This commit is contained in:
Yulia Čech 2023-10-18 15:22:54 +02:00 committed by GitHub
parent c58238d989
commit 11b1bc77a6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 108 additions and 36 deletions

View file

@ -42,6 +42,8 @@ xpack.index_management.enableIndexActions: false
xpack.index_management.enableLegacyTemplates: false
# Disable index stats information from Index Management UI
xpack.index_management.enableIndexStats: false
# Only limited index settings can be edited
xpack.index_management.editableIndexSettings: limited
# Keep deeplinks visible so that they are shown in the sidenav
dev_tools.deeplinks.navLinkStatus: visible

View file

@ -266,6 +266,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
'xpack.index_management.enableIndexActions (any)',
'xpack.index_management.enableLegacyTemplates (any)',
'xpack.index_management.enableIndexStats (any)',
'xpack.index_management.editableIndexSettings (any)',
'xpack.infra.sources.default.fields.message (array)',
/**
* Feature flags bellow are conditional based on traditional/serverless offering

View file

@ -82,7 +82,7 @@ const appDependencies = {
enableLegacyTemplates: true,
enableIndexActions: true,
enableIndexStats: true,
enableIndexDetailsPage: false,
editableIndexSettings: 'all',
},
} as any;

View file

@ -17,7 +17,8 @@ import {
IndexManagementBreadcrumb,
} from '../../../public/application/services/breadcrumbs';
import {
testIndexEditableSettings,
testIndexEditableSettingsAll,
testIndexEditableSettingsLimited,
testIndexMappings,
testIndexMock,
testIndexName,
@ -429,13 +430,30 @@ describe('<IndexDetailsPage />', () => {
await testBed.actions.settings.clickEditModeSwitch();
});
it('displays the editable settings (flattened and filtered)', () => {
it('displays all editable settings (flattened and filtered)', () => {
const editorContent = testBed.actions.settings.getCodeEditorContent();
expect(editorContent).toEqual(JSON.stringify(testIndexEditableSettings, null, 2));
expect(editorContent).toEqual(JSON.stringify(testIndexEditableSettingsAll, null, 2));
});
it('displays limited editable settings (flattened and filtered)', async () => {
await act(async () => {
testBed = await setup({
httpSetup,
dependencies: {
config: { editableIndexSettings: 'limited' },
},
});
});
testBed.component.update();
await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Settings);
await testBed.actions.settings.clickEditModeSwitch();
const editorContent = testBed.actions.settings.getCodeEditorContent();
expect(editorContent).toEqual(JSON.stringify(testIndexEditableSettingsLimited, null, 2));
});
it('updates the settings', async () => {
const updatedSettings = { ...testIndexEditableSettings, 'index.priority': '2' };
const updatedSettings = { ...testIndexEditableSettingsAll, 'index.priority': '2' };
await testBed.actions.settings.updateCodeEditorContent(JSON.stringify(updatedSettings));
await testBed.actions.settings.saveSettings();
expect(httpSetup.put).toHaveBeenLastCalledWith(
@ -452,7 +470,7 @@ describe('<IndexDetailsPage />', () => {
it('reloads the settings after an update', async () => {
const numberOfRequests = 2;
expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests);
const updatedSettings = { ...testIndexEditableSettings, 'index.priority': '2' };
const updatedSettings = { ...testIndexEditableSettingsAll, 'index.priority': '2' };
await testBed.actions.settings.updateCodeEditorContent(JSON.stringify(updatedSettings));
await testBed.actions.settings.saveSettings();
expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests + 1);
@ -463,11 +481,11 @@ describe('<IndexDetailsPage />', () => {
});
it('resets the changes in the editor', async () => {
const updatedSettings = { ...testIndexEditableSettings, 'index.priority': '2' };
const updatedSettings = { ...testIndexEditableSettingsAll, 'index.priority': '2' };
await testBed.actions.settings.updateCodeEditorContent(JSON.stringify(updatedSettings));
await testBed.actions.settings.resetChanges();
const editorContent = testBed.actions.settings.getCodeEditorContent();
expect(editorContent).toEqual(JSON.stringify(testIndexEditableSettings, null, 2));
expect(editorContent).toEqual(JSON.stringify(testIndexEditableSettingsAll, null, 2));
});
});
});
@ -664,7 +682,7 @@ describe('<IndexDetailsPage />', () => {
it('updates settings with the encoded index name', async () => {
await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Settings);
await testBed.actions.settings.clickEditModeSwitch();
const updatedSettings = { ...testIndexEditableSettings, 'index.priority': '2' };
const updatedSettings = { ...testIndexEditableSettingsAll, 'index.priority': '2' };
await testBed.actions.settings.updateCodeEditorContent(JSON.stringify(updatedSettings));
await testBed.actions.settings.saveSettings();
expect(httpSetup.put).toHaveBeenLastCalledWith(

View file

@ -67,11 +67,14 @@ export const testIndexSettings = {
},
},
};
export const testIndexEditableSettings = {
export const testIndexEditableSettingsAll = {
'index.priority': '1',
'index.query.default_field': ['*'],
'index.routing.allocation.include._tier_preference': 'data_content',
};
export const testIndexEditableSettingsLimited = {
'index.query.default_field': ['*'],
};
// Mocking partial index stats response
export const testIndexStats = {

View file

@ -53,6 +53,7 @@ export interface AppDependencies {
enableIndexActions: boolean;
enableLegacyTemplates: boolean;
enableIndexStats: boolean;
editableIndexSettings: 'all' | 'limited';
};
history: ScopedHistory;
setBreadcrumbs: ManagementAppMountParams['setBreadcrumbs'];

View file

@ -22,7 +22,25 @@ export const readOnlySettings = [
'index.routing_partition_size',
'index.store.type',
];
export const settingsToDisplay = [
export const limitedEditableSettings = [
'index.blocks.write',
'index.blocks.read',
'index.blocks.read_only',
'index.default_pipeline',
'index.lifecycle.origination_date',
'index.final_pipeline',
'index.query.default_field',
'index.refresh_interval',
'index.mapping.ignore_malformed',
'index.mapping.total_fields.limit',
'index.merge.policy.deletes_pct_allowed',
'index.merge.policy.max_merge_at_once',
'index.merge.policy.expunge_deletes_allowed',
'index.merge.policy.floor_segment',
];
export const defaultsToDisplay = [
'index.number_of_replicas',
'index.blocks.read_only_allow_delete',
'index.codec',

View file

@ -54,9 +54,7 @@ export async function mountManagementSection({
extensionsService,
isFleetEnabled,
kibanaVersion,
enableIndexActions = true,
enableLegacyTemplates = true,
enableIndexStats = true,
config,
cloud,
}: {
coreSetup: CoreSetup<StartDependencies>;
@ -65,9 +63,7 @@ export async function mountManagementSection({
extensionsService: ExtensionsService;
isFleetEnabled: boolean;
kibanaVersion: SemVer;
enableIndexActions?: boolean;
enableLegacyTemplates?: boolean;
enableIndexStats?: boolean;
config: AppDependencies['config'];
cloud?: CloudSetup;
}) {
const { element, setBreadcrumbs, history, theme$ } = params;
@ -114,11 +110,7 @@ export async function mountManagementSection({
uiMetricService,
extensionsService,
},
config: {
enableIndexActions,
enableLegacyTemplates,
enableIndexStats,
},
config,
history,
setBreadcrumbs,
uiSettings,

View file

@ -30,22 +30,39 @@ import { Error } from '../../../../../shared_imports';
import { documentationService, updateIndexSettings } from '../../../../services';
import { notificationService } from '../../../../services/notification';
import { flattenObject } from '../../../../lib/flatten_object';
import { readOnlySettings, settingsToDisplay } from '../../../../lib/edit_settings';
import {
readOnlySettings,
defaultsToDisplay,
limitedEditableSettings,
} from '../../../../lib/edit_settings';
import { AppDependencies, useAppContext } from '../../../../app_context';
const getEditableSettings = (
data: Props['data'],
isIndexOpen: boolean
): { originalSettings: Record<string, any>; settingsString: string } => {
const getEditableSettings = ({
data,
isIndexOpen,
editableIndexSettings,
}: {
data: Props['data'];
isIndexOpen: boolean;
editableIndexSettings: AppDependencies['config']['editableIndexSettings'];
}): { originalSettings: Record<string, any>; settingsString: string } => {
const { defaults, settings } = data;
// settings user has actually set
const flattenedSettings = flattenObject(settings);
// settings with their defaults
const flattenedDefaults = flattenObject(defaults);
const filteredDefaults = _.pick(flattenedDefaults, settingsToDisplay);
const newSettings = { ...filteredDefaults, ...flattenedSettings };
// store these to be used as autocomplete values later
readOnlySettings.forEach((e) => delete newSettings[e]);
// can't change codec on open index
const filteredDefaults = _.pick(flattenedDefaults, defaultsToDisplay);
let newSettings = { ...filteredDefaults, ...flattenedSettings };
if (editableIndexSettings === 'limited') {
// only pick limited settings
newSettings = _.pick(newSettings, limitedEditableSettings);
} else {
// remove read only settings
readOnlySettings.forEach((e) => delete newSettings[e]);
}
// can't change codec on an open index
if (isIndexOpen) {
delete newSettings['index.codec'];
}
@ -67,12 +84,19 @@ export const DetailsPageSettingsContent: FunctionComponent<Props> = ({
reloadIndexSettings,
}) => {
const [isEditMode, setIsEditMode] = useState(false);
const {
config: { editableIndexSettings },
} = useAppContext();
const onEditModeChange = (event: EuiSwitchEvent) => {
setUpdateError(null);
setIsEditMode(event.target.checked);
};
const { originalSettings, settingsString } = getEditableSettings(data, isIndexOpen);
const { originalSettings, settingsString } = getEditableSettings({
data,
isIndexOpen,
editableIndexSettings,
});
const [editableSettings, setEditableSettings] = useState(settingsString);
const [isLoading, setIsLoading] = useState(false);
const [updateError, setUpdateError] = useState<Error | null>(null);

View file

@ -41,11 +41,18 @@ export class IndexMgmtUIPlugin {
enableIndexActions,
enableLegacyTemplates,
enableIndexStats,
editableIndexSettings,
} = this.ctx.config.get<ClientConfigType>();
if (isIndexManagementUiEnabled) {
const { fleet, usageCollection, management, cloud } = plugins;
const kibanaVersion = new SemVer(this.ctx.env.packageInfo.version);
const config = {
enableIndexActions: enableIndexActions ?? true,
enableLegacyTemplates: enableLegacyTemplates ?? true,
enableIndexStats: enableIndexStats ?? true,
editableIndexSettings: editableIndexSettings ?? 'all',
};
management.sections.section.data.registerApp({
id: PLUGIN.id,
title: i18n.translate('xpack.idxMgmt.appTitle', { defaultMessage: 'Index Management' }),
@ -59,9 +66,7 @@ export class IndexMgmtUIPlugin {
extensionsService: this.extensionsService,
isFleetEnabled: Boolean(fleet),
kibanaVersion,
enableIndexActions,
enableLegacyTemplates,
enableIndexStats,
config,
cloud,
});
},

View file

@ -35,4 +35,5 @@ export interface ClientConfigType {
enableIndexActions?: boolean;
enableLegacyTemplates?: boolean;
enableIndexStats?: boolean;
editableIndexSettings?: 'all' | 'limited';
}

View file

@ -41,6 +41,12 @@ const schemaLatest = schema.object(
// We take this approach in order to have a central place (serverless.yml) for serverless config across Kibana
serverless: schema.boolean({ defaultValue: true }),
}),
editableIndexSettings: offeringBasedSchema({
// on serverless only a limited set of index settings can be edited
serverless: schema.oneOf([schema.literal('all'), schema.literal('limited')], {
defaultValue: 'all',
}),
}),
},
{ defaultValue: undefined }
);
@ -51,6 +57,7 @@ const configLatest: PluginConfigDescriptor<IndexManagementConfig> = {
enableIndexActions: true,
enableLegacyTemplates: true,
enableIndexStats: true,
editableIndexSettings: true,
},
schema: schemaLatest,
deprecations: ({ unused }) => [unused('dev.enableIndexDetailsPage', { level: 'warning' })],