[Index Templates] Fix edit form when there are missing component templates (#187766)

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

## Summary

This PR fixes the following issues in the Index template edit form:

For an index template that is composed of a nonexistent component
template and has a `ignore_missing_component_templates` property:
1. when we edit the index template and pass through the "Component
template" steps, the nonexistent component template is removed.
2. when we edit the index template and go directly to the "Review" step,
the `ignore_missing_templates` field is removed from the request.
Therefore, when we try to save the template, it fails with an error as
it contains a missing component template and doesn't have a
`ignore_missing_templates` property.

**How to test:**
1. Start Es and Kibana
2. Create a test index template composed of a nonexistent component
template:
```
PUT _index_template/test
{
  "index_patterns": [
    "test-*"  
  ], 
  "composed_of": [
    "mytesttemplate"
  ],
  "ignore_missing_component_templates": [
    "mytesttemplate"
  ]
}
```
3. Go to Index Management -> Index Templates and start editing the
created template.
4. Go directly to the Review step and verify that the request includes
both the `composed_of` and the `ignore_missing_component_templates`
fields as they were initially. Verify that saving the form doesn't
result in an error.
5. Start editing the template again and this time go to the Component
Template step. Verify that the non-existent step is displayed in the
list. Go to the "Review" step and verify that the request contains the
correct `composed_of` and `ignore_missing_component_templates` fields.
6. You can also try the above steps with some of the auto-created index
templates that are composed of non-existent component templates (e.g.
`logs`, `logs-apm.app@template`, `logs-apm.error@template` - they all
are composed of `*@custom` component templates which don't exist).
This commit is contained in:
Elena Stoeva 2024-07-10 17:30:08 +01:00 committed by GitHub
parent ee7c047653
commit 462ac5c2a4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 121 additions and 3 deletions

View file

@ -12,7 +12,13 @@ import * as fixtures from '../../../test/fixtures';
import { API_BASE_PATH } from '../../../common/constants';
import { setupEnvironment, kibanaVersion } from '../helpers';
import { TEMPLATE_NAME, SETTINGS, ALIASES, MAPPINGS as DEFAULT_MAPPING } from './constants';
import {
TEMPLATE_NAME,
SETTINGS,
ALIASES,
MAPPINGS as DEFAULT_MAPPING,
INDEX_PATTERNS,
} from './constants';
import { setup } from './template_edit.helpers';
import { TemplateFormTestBed } from './template_form.helpers';
@ -26,6 +32,22 @@ const MAPPING = {
},
},
};
const NONEXISTENT_COMPONENT_TEMPLATE = {
name: 'component_template@custom',
hasMappings: false,
hasAliases: false,
hasSettings: false,
usedBy: [],
};
const EXISTING_COMPONENT_TEMPLATE = {
name: 'test_component_template',
hasMappings: true,
hasAliases: false,
hasSettings: false,
usedBy: [],
isManaged: false,
};
jest.mock('@kbn/code-editor', () => {
const original = jest.requireActual('@kbn/code-editor');
@ -70,6 +92,7 @@ describe('<TemplateEdit />', () => {
beforeAll(() => {
jest.useFakeTimers({ legacyFakeTimers: true });
httpRequestsMockHelpers.setLoadComponentTemplatesResponse([]);
httpRequestsMockHelpers.setLoadComponentTemplatesResponse([EXISTING_COMPONENT_TEMPLATE]);
});
afterAll(() => {
@ -296,6 +319,84 @@ describe('<TemplateEdit />', () => {
});
});
describe('when composed of a nonexistent component template', () => {
const templateToEdit = fixtures.getTemplate({
name: TEMPLATE_NAME,
indexPatterns: INDEX_PATTERNS,
composedOf: [NONEXISTENT_COMPONENT_TEMPLATE.name],
ignoreMissingComponentTemplates: [NONEXISTENT_COMPONENT_TEMPLATE.name],
});
beforeAll(() => {
httpRequestsMockHelpers.setLoadTemplateResponse('my_template', templateToEdit);
});
beforeEach(async () => {
await act(async () => {
testBed = await setup(httpSetup);
});
testBed.component.update();
});
it('the nonexistent component template should be selected in the Component templates selector', async () => {
const { actions, exists } = testBed;
// Complete step 1: Logistics
await actions.completeStepOne();
jest.advanceTimersByTime(0); // advance timers to allow the form to validate
// Should be at the Component templates step
expect(exists('stepComponents')).toBe(true);
const {
actions: {
componentTemplates: { getComponentTemplatesSelected },
},
} = testBed;
expect(exists('componentTemplatesSelection.emptyPrompt')).toBe(false);
expect(getComponentTemplatesSelected()).toEqual([NONEXISTENT_COMPONENT_TEMPLATE.name]);
});
it('the composedOf and ignoreMissingComponentTemplates fields should be included in the final payload', async () => {
const { component, actions, find } = testBed;
// Complete step 1: Logistics
await actions.completeStepOne();
// Complete step 2: Component templates
await actions.completeStepTwo();
// Complete step 3: Index settings
await actions.completeStepThree();
// Complete step 4: Mappings
await actions.completeStepFour();
// Complete step 5: Aliases
await actions.completeStepFive();
expect(find('stepTitle').text()).toEqual(`Review details for '${TEMPLATE_NAME}'`);
await act(async () => {
actions.clickNextButton();
});
component.update();
expect(httpSetup.put).toHaveBeenLastCalledWith(
`${API_BASE_PATH}/index_templates/${TEMPLATE_NAME}`,
expect.objectContaining({
body: JSON.stringify({
name: TEMPLATE_NAME,
indexPatterns: INDEX_PATTERNS,
version: templateToEdit.version,
allowAutoCreate: templateToEdit.allowAutoCreate,
_kbnMeta: templateToEdit._kbnMeta,
composedOf: [NONEXISTENT_COMPONENT_TEMPLATE.name],
template: {},
ignoreMissingComponentTemplates: [NONEXISTENT_COMPONENT_TEMPLATE.name],
}),
})
);
});
});
if (kibanaVersion.major < 8) {
describe('legacy index templates', () => {
const legacyTemplateToEdit = fixtures.getTemplate({

View file

@ -87,8 +87,20 @@ export const ComponentTemplatesSelector = ({
.map((name) => components.find((comp) => comp.name === name))
.filter(Boolean) as ComponentTemplateListItem[];
setComponentsSelected(nextComponentsSelected);
onChange(nextComponentsSelected.map(({ name }) => name));
// Add the non-existing templates from the "defaultValue" prop
const missingDefaultComponents: ComponentTemplateListItem[] = defaultValue
.filter((name) => !components.find((comp) => comp.name === name))
.map((name) => ({
name,
usedBy: [],
hasMappings: false,
hasAliases: false,
hasSettings: false,
isManaged: false,
}));
setComponentsSelected([...nextComponentsSelected, ...missingDefaultComponents]);
onChange([...nextComponentsSelected, ...missingDefaultComponents].map(({ name }) => name));
isInitialized.current = true;
} else {
onChange(componentsSelected.map(({ name }) => name));

View file

@ -218,6 +218,7 @@ export const TemplateForm = ({
? serializeAsESLifecycle(wizardData.logistics.lifecycle)
: undefined,
},
ignoreMissingComponentTemplates: initialTemplate.ignoreMissingComponentTemplates,
};
return cleanupTemplateObject(outputTemplate as TemplateDeserialized);

View file

@ -67,6 +67,8 @@ export const getTemplate = ({
indexPatterns = [],
template: { settings, aliases, mappings } = {},
dataStream,
composedOf,
ignoreMissingComponentTemplates,
hasDatastream = false,
isLegacy = false,
type = 'default',
@ -98,6 +100,8 @@ export const getTemplate = ({
hasDatastream: dataStream !== undefined ? true : hasDatastream,
isLegacy,
},
composedOf,
ignoreMissingComponentTemplates,
};
return indexTemplate;