Skip to content

Commit

Permalink
[Index Management] Add support for enrich policies (elastic#164080)
Browse files Browse the repository at this point in the history
  • Loading branch information
sabarasaba authored Sep 20, 2023
1 parent e02c874 commit d1608f0
Show file tree
Hide file tree
Showing 65 changed files with 4,664 additions and 28 deletions.
3 changes: 3 additions & 0 deletions packages/kbn-doc-links/src/get_doc_links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,9 @@ export const getDocLinks = ({ kibanaBranch }: GetDocLinkOptions): DocLinks => {
asyncSearch: `${ELASTICSEARCH_DOCS}async-search-intro.html`,
dataStreams: `${ELASTICSEARCH_DOCS}data-streams.html`,
deprecationLogging: `${ELASTICSEARCH_DOCS}logging.html#deprecation-logging`,
createEnrichPolicy: `${ELASTICSEARCH_DOCS}put-enrich-policy-api.html`,
matchAllQuery: `${ELASTICSEARCH_DOCS}query-dsl-match-all-query.html`,
enrichPolicies: `${ELASTICSEARCH_DOCS}ingest-enriching-data.html#enrich-policy`,
createIndex: `${ELASTICSEARCH_DOCS}indices-create-index.html`,
frozenIndices: `${ELASTICSEARCH_DOCS}frozen-indices.html`,
gettingStarted: `${ELASTICSEARCH_DOCS}getting-started.html`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ function JsonEditorComp<T extends object = { [key: string]: any }>({
defaultValue,
codeEditorProps,
error: propsError,
...rest
}: Props<T>) {
const {
content,
Expand Down Expand Up @@ -82,6 +83,7 @@ function JsonEditorComp<T extends object = { [key: string]: any }>({
isInvalid={typeof error === 'string'}
error={error}
fullWidth
{...rest}
>
<CodeEditor
languageId="json"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* 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 { act } from 'react-dom/test-utils';

import { registerTestBed, TestBed, AsyncTestBedConfig } from '@kbn/test-jest-helpers';
import { HttpSetup } from '@kbn/core/public';
import { EnrichPolicyCreate } from '../../../public/application/sections/enrich_policy_create';
import { indexManagementStore } from '../../../public/application/store';
import { WithAppDependencies, services, TestSubjects } from '../helpers';

const testBedConfig: AsyncTestBedConfig = {
store: () => indexManagementStore(services as any),
memoryRouter: {
initialEntries: [`/enrich_policies/create`],
componentRoutePath: `/:section(enrich_policies)/create`,
},
doMountAsync: true,
};

export interface CreateEnrichPoliciesTestBed extends TestBed<TestSubjects> {
actions: {
clickNextButton: () => Promise<void>;
clickBackButton: () => Promise<void>;
clickRequestTab: () => Promise<void>;
clickCreatePolicy: () => Promise<void>;
completeConfigurationStep: ({ indices }: { indices?: string }) => Promise<void>;
completeFieldsSelectionStep: () => Promise<void>;
isOnConfigurationStep: () => boolean;
isOnFieldSelectionStep: () => boolean;
isOnCreateStep: () => boolean;
};
}

export const setup = async (
httpSetup: HttpSetup,
overridingDependencies: any = {}
): Promise<CreateEnrichPoliciesTestBed> => {
const initTestBed = registerTestBed(
WithAppDependencies(EnrichPolicyCreate, httpSetup, overridingDependencies),
testBedConfig
);
const testBed = await initTestBed();

/**
* User Actions
*/
const isOnConfigurationStep = () => testBed.exists('configurationForm');
const isOnFieldSelectionStep = () => testBed.exists('fieldSelectionForm');
const isOnCreateStep = () => testBed.exists('creationStep');
const clickNextButton = async () => {
await act(async () => {
testBed.find('nextButton').simulate('click');
});

testBed.component.update();
};
const clickBackButton = async () => {
await act(async () => {
testBed.find('backButton').simulate('click');
});

testBed.component.update();
};
const clickCreatePolicy = async (executeAfter?: boolean) => {
await act(async () => {
testBed.find(executeAfter ? 'createAndExecuteButton' : 'createButton').simulate('click');
});

testBed.component.update();
};

const clickRequestTab = async () => {
await act(async () => {
testBed.find('requestTab').simulate('click');
});

testBed.component.update();
};

const completeConfigurationStep = async ({ indices }: { indices?: string }) => {
const { form } = testBed;

form.setInputValue('policyNameField.input', 'test_policy');
form.setSelectValue('policyTypeField', 'match');
form.setSelectValue('policySourceIndicesField', indices ?? 'test-1');

await clickNextButton();
};

const completeFieldsSelectionStep = async () => {
const { form } = testBed;

form.setSelectValue('matchField', 'name');
form.setSelectValue('enrichFields', 'email');

await clickNextButton();
};

return {
...testBed,
actions: {
clickNextButton,
clickBackButton,
clickRequestTab,
clickCreatePolicy,
completeConfigurationStep,
completeFieldsSelectionStep,
isOnConfigurationStep,
isOnFieldSelectionStep,
isOnCreateStep,
},
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
/*
* 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 React from 'react';
import { act } from 'react-dom/test-utils';

import { setupEnvironment } from '../helpers';
import { getMatchingIndices, getFieldsFromIndices } from '../helpers/fixtures';
import { CreateEnrichPoliciesTestBed, setup } from './create_enrich_policy.helpers';
import { getESPolicyCreationApiCall } from '../../../common/lib';

jest.mock('@kbn/kibana-react-plugin/public', () => {
const original = jest.requireActual('@kbn/kibana-react-plugin/public');
return {
...original,
// Mocking CodeEditor, which uses React Monaco under the hood
CodeEditor: (props: any) => (
<input
data-test-subj={props['data-test-subj'] || 'mockCodeEditor'}
data-currentvalue={props.value}
onChange={(e: any) => {
props.onChange(e.jsonContent);
}}
/>
),
};
});

jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
...original,
// Mock EuiComboBox as a simple input instead so that its easier to test
EuiComboBox: (props: any) => (
<input
data-test-subj={props['data-test-subj'] || 'mockEuiCombobox'}
data-currentvalue={props.value}
onChange={(e: any) => {
props.onChange(e.target.value.split(', '));
}}
/>
),
};
});

describe('Create enrich policy', () => {
const { httpSetup, httpRequestsMockHelpers } = setupEnvironment();
let testBed: CreateEnrichPoliciesTestBed;

beforeEach(async () => {
httpRequestsMockHelpers.setGetMatchingIndices(getMatchingIndices());

await act(async () => {
testBed = await setup(httpSetup);
});

testBed.component.update();
});

test('Has header and docs link', async () => {
const { exists, component } = testBed;
component.update();

expect(exists('createEnrichPolicyHeaderContent')).toBe(true);
expect(exists('createEnrichPolicyDocumentationLink')).toBe(true);
});

describe('Configuration step', () => {
it('Fields have helpers', async () => {
const { exists } = testBed;

expect(exists('typePopoverIcon')).toBe(true);
expect(exists('uploadFileLink')).toBe(true);
expect(exists('matchAllQueryLink')).toBe(true);
});

it('shows validation errors if form isnt filled', async () => {
await testBed.actions.clickNextButton();

expect(testBed.form.getErrorsMessages()).toHaveLength(3);
});

it('Allows to submit the form when fields are filled', async () => {
const { actions } = testBed;

await testBed.actions.completeConfigurationStep({});

expect(actions.isOnFieldSelectionStep()).toBe(true);
});
});

describe('Fields selection step', () => {
beforeEach(async () => {
httpRequestsMockHelpers.setGetFieldsFromIndices(getFieldsFromIndices());

await act(async () => {
testBed = await setup(httpSetup);
});

testBed.component.update();

await testBed.actions.completeConfigurationStep({});
});

it('shows validation errors if form isnt filled', async () => {
await testBed.actions.clickNextButton();

expect(testBed.form.getErrorsMessages()).toHaveLength(2);
});

it('Allows to submit the form when fields are filled', async () => {
const { form, actions } = testBed;

form.setSelectValue('matchField', 'name');
form.setSelectValue('enrichFields', 'email');

await testBed.actions.clickNextButton();

expect(actions.isOnCreateStep()).toBe(true);
});

it('When no common fields are returned it shows an error callout', async () => {
httpRequestsMockHelpers.setGetFieldsFromIndices({
commonFields: [],
indices: [],
});

await act(async () => {
testBed = await setup(httpSetup);
});

testBed.component.update();

await testBed.actions.completeConfigurationStep({ indices: 'test-1, test-2' });

expect(testBed.exists('noCommonFieldsError')).toBe(true);
});
});

describe('Creation step', () => {
beforeEach(async () => {
httpRequestsMockHelpers.setGetFieldsFromIndices(getFieldsFromIndices());

await act(async () => {
testBed = await setup(httpSetup);
});

testBed.component.update();

await testBed.actions.completeConfigurationStep({});
await testBed.actions.completeFieldsSelectionStep();
});

it('Shows CTAs for creating the policy', async () => {
const { exists } = testBed;

expect(exists('createButton')).toBe(true);
expect(exists('createAndExecuteButton')).toBe(true);
});

it('Shows policy summary and request', async () => {
const { find } = testBed;

expect(find('enrichPolicySummaryList').text()).toContain('test_policy');

await testBed.actions.clickRequestTab();

expect(find('requestBody').text()).toContain(getESPolicyCreationApiCall('test_policy'));
});

it('Shows error message when creating the policy fails', async () => {
const { exists, actions } = testBed;
const error = {
statusCode: 400,
error: 'Bad Request',
message: 'something went wrong...',
};

httpRequestsMockHelpers.setCreateEnrichPolicy(undefined, error);

await actions.clickCreatePolicy();

expect(exists('errorWhenCreatingCallout')).toBe(true);
});
});

it('Can navigate back and forth with next/back buttons', async () => {
httpRequestsMockHelpers.setGetFieldsFromIndices(getFieldsFromIndices());

await act(async () => {
testBed = await setup(httpSetup);
});

const { component, actions } = testBed;
component.update();

// Navigate to create step
await actions.completeConfigurationStep({});
await actions.completeFieldsSelectionStep();

// Clicking back button should take us to fields selection step
await actions.clickBackButton();
expect(actions.isOnFieldSelectionStep()).toBe(true);

// Clicking back button should take us to configuration step
await actions.clickBackButton();
expect(actions.isOnConfigurationStep()).toBe(true);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* 2.0.
*/

import type { EnrichPolicyType } from '@elastic/elasticsearch/lib/api/types';

export const indexSettings = {
settings: { index: { number_of_shards: '1' } },
defaults: { index: { flush_after_merge: '512mb' } },
Expand Down Expand Up @@ -45,3 +47,31 @@ export const indexStats = {
},
},
};

export const createTestEnrichPolicy = (name: string, type: EnrichPolicyType) => ({
name,
type,
sourceIndices: ['users'],
matchField: 'email',
enrichFields: ['first_name', 'last_name', 'city'],
query: {
match_all: {},
},
});

export const getMatchingIndices = () => ({
indices: ['test-1', 'test-2', 'test-3', 'test-4', 'test-5'],
});

export const getFieldsFromIndices = () => ({
commonFields: [],
indices: [
{
index: 'test-1',
fields: [
{ name: 'first_name', type: 'keyword', normalizedType: 'keyword' },
{ name: 'age', type: 'long', normalizedType: 'number' },
],
},
],
});
Loading

0 comments on commit d1608f0

Please sign in to comment.