mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Index Management] Add validation of index settings in template form (#208419)
Addresses https://github.com/elastic/kibana/issues/207350 Follow-up to https://github.com/elastic/kibana/pull/207413 ## Summary This PR adds validation to the index settings step in the template creation flow so that the `index.number_of_shards` setting can only be set to 1 if the Lookup index is selected. <img width="900" alt="Screenshot 2025-01-28 at 15 33 59" src="https://github.com/user-attachments/assets/a867fc6d-460d-4ab6-86b2-2ec54ac7203f" /> How to test: 1. Go to Index Management -> Index templates and start creating a template 2. In the Logistics step, select Lookup index mode 3. In the index settings step, add the `index.number_of_shards` setting and verify that only the values `1` and `null` are allowed. 4. Change the index mode and verify that for all other index modes, there is no restriction on this index setting. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
c7e62fc01b
commit
af553b531a
4 changed files with 57 additions and 11 deletions
|
@ -8,7 +8,7 @@
|
|||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
|
||||
import { API_BASE_PATH } from '../../../common/constants';
|
||||
import { API_BASE_PATH, LOOKUP_INDEX_MODE } from '../../../common/constants';
|
||||
import { setupEnvironment } from '../helpers';
|
||||
|
||||
import {
|
||||
|
@ -298,7 +298,11 @@ describe('<TemplateCreate />', () => {
|
|||
beforeEach(async () => {
|
||||
const { actions } = testBed;
|
||||
// Logistics
|
||||
await actions.completeStepOne({ name: TEMPLATE_NAME, indexPatterns: ['index1'] });
|
||||
await actions.completeStepOne({
|
||||
name: TEMPLATE_NAME,
|
||||
indexPatterns: ['index1'],
|
||||
indexMode: LOOKUP_INDEX_MODE,
|
||||
});
|
||||
// Component templates
|
||||
await actions.completeStepTwo();
|
||||
});
|
||||
|
@ -315,7 +319,7 @@ describe('<TemplateCreate />', () => {
|
|||
|
||||
expect(exists('indexModeCallout')).toBe(true);
|
||||
expect(find('indexModeCallout').text()).toContain(
|
||||
'The index.mode setting has been set to Standard within the Logistics step.'
|
||||
'The index.mode setting has been set to Lookup within the Logistics step.'
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -326,6 +330,17 @@ describe('<TemplateCreate />', () => {
|
|||
|
||||
expect(form.getErrorsMessages()).toContain('Invalid JSON format.');
|
||||
});
|
||||
|
||||
it('should not allow setting number_of_shards to a value different from 1 for Lookup index mode', async () => {
|
||||
// The Lookup index mode was already selected in the first (Logistics) step
|
||||
const { form, actions } = testBed;
|
||||
|
||||
await actions.completeStepThree('{ "index.number_of_shards": 2 }');
|
||||
|
||||
expect(form.getErrorsMessages()).toContain(
|
||||
'Number of shards for lookup index mode can only be 1 or unset.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mappings (step 4)', () => {
|
||||
|
|
|
@ -33,7 +33,7 @@ export type DataStreamIndexFromEs = IndicesDataStreamIndex;
|
|||
|
||||
export type Health = 'green' | 'yellow' | 'red';
|
||||
|
||||
export type IndexMode = 'standard' | 'logsdb' | 'time_series';
|
||||
export type IndexMode = 'standard' | 'logsdb' | 'time_series' | 'lookup';
|
||||
|
||||
export interface EnhancedDataStreamFromEs extends IndicesDataStream {
|
||||
global_max_retention?: string;
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
|
@ -22,7 +22,8 @@ import {
|
|||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { CodeEditor } from '@kbn/code-editor';
|
||||
|
||||
import { Forms } from '../../../../../shared_imports';
|
||||
import { LOOKUP_INDEX_MODE } from '../../../../../../common/constants';
|
||||
import { Forms, isJSON } from '../../../../../shared_imports';
|
||||
import { useJsonStep } from './use_json_step';
|
||||
import { documentationService } from '../../../mappings_editor/shared_imports';
|
||||
import { indexModeLabels } from '../../../../lib/index_mode_labels';
|
||||
|
@ -35,12 +36,38 @@ interface Props {
|
|||
indexMode?: IndexMode;
|
||||
}
|
||||
|
||||
// The value of the number_of_shards setting that is allowed for lookup index mode
|
||||
const NUMBER_OF_SHARDS_LOOKUP_MODE = 1;
|
||||
|
||||
export const StepSettings: React.FunctionComponent<Props> = React.memo(
|
||||
({ defaultValue = {}, onChange, esDocsBase, indexMode }) => {
|
||||
const { navigateToStep } = Forms.useFormWizardContext();
|
||||
const customValidate = useCallback(
|
||||
(json: string) => {
|
||||
if (!isJSON(json)) return null;
|
||||
const settings = JSON.parse(json);
|
||||
const numberOfShardsValue =
|
||||
settings['index.number_of_shards'] ?? settings?.index?.number_of_shards;
|
||||
if (
|
||||
numberOfShardsValue != null &&
|
||||
indexMode === LOOKUP_INDEX_MODE &&
|
||||
(isNaN(numberOfShardsValue) || numberOfShardsValue !== NUMBER_OF_SHARDS_LOOKUP_MODE)
|
||||
) {
|
||||
return i18n.translate(
|
||||
'xpack.idxMgmt.formWizard.stepSettings.validations.lookupIndexModeNumberOfShardsError',
|
||||
{
|
||||
defaultMessage: 'Number of shards for lookup index mode can only be 1 or unset.',
|
||||
}
|
||||
);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
[indexMode]
|
||||
);
|
||||
const { jsonContent, setJsonContent, error } = useJsonStep({
|
||||
defaultValue,
|
||||
onChange,
|
||||
customValidate,
|
||||
});
|
||||
|
||||
return (
|
||||
|
|
|
@ -13,29 +13,33 @@ import { isJSON, Forms } from '../../../../../shared_imports';
|
|||
interface Parameters {
|
||||
onChange: (content: Forms.Content) => void;
|
||||
defaultValue?: object;
|
||||
customValidate?: (json: string) => string | null;
|
||||
}
|
||||
|
||||
const stringifyJson = (json: any) =>
|
||||
Object.keys(json).length ? JSON.stringify(json, null, 2) : '{\n\n}';
|
||||
|
||||
export const useJsonStep = ({ defaultValue, onChange }: Parameters) => {
|
||||
export const useJsonStep = ({ defaultValue, onChange, customValidate }: Parameters) => {
|
||||
const [jsonContent, setJsonContent] = useState<string>(stringifyJson(defaultValue ?? {}));
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const validateContent = useCallback(() => {
|
||||
// We allow empty string as it will be converted to "{}""
|
||||
const isValid = jsonContent.trim() === '' ? true : isJSON(jsonContent);
|
||||
if (!isValid) {
|
||||
const isValidJson = jsonContent.trim() === '' ? true : isJSON(jsonContent);
|
||||
const customValidationError = customValidate ? customValidate(jsonContent) : null;
|
||||
if (!isValidJson) {
|
||||
setError(
|
||||
i18n.translate('xpack.idxMgmt.validators.string.invalidJSONError', {
|
||||
defaultMessage: 'Invalid JSON format.',
|
||||
})
|
||||
);
|
||||
} else if (customValidationError) {
|
||||
setError(customValidationError);
|
||||
} else {
|
||||
setError(null);
|
||||
}
|
||||
return isValid;
|
||||
}, [jsonContent]);
|
||||
return isValidJson && !customValidationError;
|
||||
}, [customValidate, jsonContent]);
|
||||
|
||||
useEffect(() => {
|
||||
const isValid = validateContent();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue