Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow data Streams enrich policies #200903

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ import React from 'react';
import { act } from 'react-dom/test-utils';

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

Expand Down Expand Up @@ -57,6 +61,7 @@ describe('Create enrich policy', () => {
hasAllPrivileges: true,
missingPrivileges: { cluster: [] },
});
httpRequestsMockHelpers.setGetMatchingDataStreams(getMatchingDataStreams());

await act(async () => {
testBed = await setup(httpSetup);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ export const createTestEnrichPolicy = (name: string, type: EnrichPolicyType) =>
export const getMatchingIndices = () => ({
indices: ['test-1', 'test-2', 'test-3', 'test-4', 'test-5'],
});
export const getMatchingDataStreams = () => ({
dataStreams: ['test-6', 'test-7', 'test-8', 'test-9', 'test-10'],
});

export const getFieldsFromIndices = () => ({
commonFields: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,13 @@ const registerHttpRequestMockHelpers = (
response,
error
);
const setGetMatchingDataStreams = (response?: HttpResponse, error?: ResponseError) =>
mockResponse(
'POST',
`${INTERNAL_API_BASE_PATH}/enrich_policies/get_matching_data_streams`,
response,
error
);

const setGetFieldsFromIndices = (response?: HttpResponse, error?: ResponseError) =>
mockResponse(
Expand Down Expand Up @@ -249,6 +256,7 @@ const registerHttpRequestMockHelpers = (
setGetPrivilegesResponse,
setCreateEnrichPolicy,
setInferenceModels,
setGetMatchingDataStreams,
};
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,18 +94,15 @@ export const configurationFormSchema: FormSchema = {
},

sourceIndices: {
label: i18n.translate('xpack.idxMgmt.enrichPolicyCreate.configurationStep.sourceIndicesLabel', {
defaultMessage: 'Source indices',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maybe we should rename the key of the i18n.translate to be sourceLabel instead of sourceIndicesLabel (same for the error)

label: i18n.translate('xpack.idxMgmt.enrichPolicyCreate.configurationStep.sourceLabel', {
defaultMessage: 'Source',
}),
validations: [
{
validator: fieldValidators.emptyField(
i18n.translate(
'xpack.idxMgmt.enrichPolicyCreate.configurationStep.sourceIndicesRequiredError',
{
defaultMessage: 'At least one source index is required.',
}
)
i18n.translate('xpack.idxMgmt.enrichPolicyCreate.configurationStep.sourceRequiredError', {
defaultMessage: 'At least one source is required.',
})
),
},
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n';
import { uniq, isEmpty } from 'lodash';
import { EuiFormRow, EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui';
import type { EuiComboBoxProps } from '@elastic/eui';
import { getMatchingIndices } from '../../../../services/api';
import { getMatchingDataStreams, getMatchingIndices } from '../../../../services/api';
import type { FieldHook } from '../../../../../shared_imports';
import { getFieldValidityAndErrorMessage } from '../../../../../shared_imports';

Expand All @@ -25,23 +25,76 @@ interface Props {
[key: string]: any;
}

const getIndexOptions = async (patternString: string) => {
const options: IOption[] = [];
interface GetMatchingOptionsParams {
matches: string[];
optionsMessage: string;
noMatchingMessage: string;
}

const i18nTexts = {
indices: {
options: i18n.translate('xpack.idxMgmt.enrichPolicyCreate.indicesSelector.optionsLabel', {
defaultMessage: 'Based on your indices',
}),
noMatches: i18n.translate('xpack.idxMgmt.enrichPolicyCreate.indicesSelector.noMatchingOption', {
defaultMessage: 'No indices match your search criteria.',
}),
},
dataStreams: {
options: i18n.translate(
'xpack.idxMgmt.enrichPolicyCreate.indicesSelector.dataStream.optionsLabel',
{
defaultMessage: 'Based on your data streams',
}
),
noMatches: i18n.translate(
'xpack.idxMgmt.enrichPolicyCreate.indicesSelector.dataStream.noMatchingOption',
{
defaultMessage: 'No data streams match your search criteria.',
}
),
},
sourcePlaceholder: i18n.translate(
'xpack.idxMgmt.enrichPolicyCreate.indicesSelector.placeholder',
{
defaultMessage: 'Select source indices and data streams.',
}
),
};

const getIndexOptions = async (patternString: string) => {
if (!patternString) {
return options;
return [];
}

const { data } = await getMatchingIndices(patternString);
const matchingIndices = data.indices;
const { data: indicesData } = await getMatchingIndices(patternString);
const { data: dataStreamsData } = await getMatchingDataStreams(patternString);

if (matchingIndices.length) {
const matchingOptions = uniq([...matchingIndices]);
const indices = getMatchingOptions({
matches: indicesData.indices,
optionsMessage: i18nTexts.indices.options,
noMatchingMessage: i18nTexts.indices.noMatches,
});
const dataStreams = getMatchingOptions({
matches: dataStreamsData.dataStreams,
optionsMessage: i18nTexts.dataStreams.options,
noMatchingMessage: i18nTexts.dataStreams.noMatches,
});

return [...indices, ...dataStreams];
};

const getMatchingOptions = ({
matches,
optionsMessage,
noMatchingMessage,
}: GetMatchingOptionsParams) => {
const options: IOption[] = [];
if (matches.length) {
const matchingOptions = uniq([...matches]);

options.push({
label: i18n.translate('xpack.idxMgmt.enrichPolicyCreate.indicesSelector.optionsLabel', {
defaultMessage: 'Based on your indices',
}),
label: optionsMessage,
options: matchingOptions
.map((match) => {
return {
Expand All @@ -53,13 +106,10 @@ const getIndexOptions = async (patternString: string) => {
});
} else {
options.push({
label: i18n.translate('xpack.idxMgmt.enrichPolicyCreate.indicesSelector.noMatchingOption', {
defaultMessage: 'No indices match your search criteria.',
}),
label: noMatchingMessage,
options: [],
});
}

return options;
};

Expand All @@ -71,7 +121,6 @@ export const IndicesSelector = ({ field, euiFieldProps, ...rest }: Props) => {
const onSearchChange = useCallback(
async (search: string) => {
const indexPattern = isEmpty(search) ? '*' : search;

setIsIndiciesLoading(true);
setIndexOptions(await getIndexOptions(indexPattern));
setIsIndiciesLoading(false);
Expand All @@ -98,6 +147,7 @@ export const IndicesSelector = ({ field, euiFieldProps, ...rest }: Props) => {
>
<EuiComboBox
async
placeholder={i18nTexts.sourcePlaceholder}
isLoading={isIndiciesLoading}
options={indexOptions}
noSuggestions={!indexOptions.length}
Expand Down
10 changes: 10 additions & 0 deletions x-pack/plugins/index_management/public/application/services/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,16 @@ export async function getMatchingIndices(pattern: string) {
return result;
}

export async function getMatchingDataStreams(pattern: string) {
const result = sendRequest({
path: `${INTERNAL_API_BASE_PATH}/enrich_policies/get_matching_data_streams`,
method: 'post',
body: JSON.stringify({ pattern }),
});

return result;
}

export async function getFieldsFromIndices(indices: string[]) {
const result = sendRequest<FieldFromIndicesRequest>({
path: `${INTERNAL_API_BASE_PATH}/enrich_policies/get_fields_from_indices`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -366,4 +366,33 @@ describe('Enrich policies API', () => {
expect(searchMock).toHaveBeenCalled();
});
});

describe('Get matching indices - POST /api/index_management/enrich_policies/get_matching_data_streams', () => {
const getDataStreamsMock = router.getMockESApiFn('indices.getDataStream');

it('Return matching data streams', async () => {
const mockRequest: RequestMock = {
method: 'post',
path: addInternalBasePath('/enrich_policies/get_matching_data_streams'),
body: {
pattern: 'test',
},
};

getDataStreamsMock.mockResolvedValue({
body: {},
statusCode: 200,
});

const res = await router.runRequest(mockRequest);

expect(res).toEqual({
body: {
dataStreams: [],
},
});

expect(getDataStreamsMock).toHaveBeenCalledWith({ name: '*test*', expand_wildcards: 'open' });
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,15 @@ export async function getIndices(dataClient: IScopedClusterClient, pattern: stri

return indices.buckets ? indices.buckets.map((bucket) => bucket.key) : [];
}
export async function getDataStreams(
dataClient: IScopedClusterClient,
pattern: string,
limit = 10
) {
const response = await dataClient.asCurrentUser.indices.getDataStream({
name: pattern,
expand_wildcards: 'open',
});

return response.data_streams ? response.data_streams.map((dataStream) => dataStream.name) : [];
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@ import { RouteDependencies } from '../../../types';
import { addInternalBasePath } from '..';
import { enrichPoliciesActions } from '../../../lib/enrich_policies';
import { serializeAsESPolicy } from '../../../../common/lib';
import { normalizeFieldsList, getIndices, FieldCapsList, getCommonFields } from './helpers';
import {
normalizeFieldsList,
getIndices,
FieldCapsList,
getCommonFields,
getDataStreams,
} from './helpers';

const validationSchema = schema.object({
policy: schema.object({
Expand Down Expand Up @@ -105,6 +111,33 @@ export function registerCreateRoute({ router, lib: { handleEsError } }: RouteDep
}
}
);
router.post(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a unit test for this route here and an api integration test similar to this one ?

{
path: addInternalBasePath('/enrich_policies/get_matching_data_streams'),
validate: { body: getMatchingIndicesSchema },
},
async (context, request, response) => {
let { pattern } = request.body;
const client = (await context.core).elasticsearch.client as IScopedClusterClient;

// Add wildcards to the search query to match the behavior of the
// index pattern search in the Kibana UI.
if (!pattern.startsWith('*')) {
pattern = `*${pattern}`;
}
if (!pattern.endsWith('*')) {
pattern = `${pattern}*`;
}

try {
const dataStreams = await getDataStreams(client, pattern);

return response.ok({ body: { dataStreams } });
} catch (error) {
return handleEsError({ error, response });
}
}
);

router.post(
{
Expand Down
2 changes: 0 additions & 2 deletions x-pack/plugins/translations/translations/fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -22567,8 +22567,6 @@
"xpack.idxMgmt.enrichPolicyCreate.configurationStep.queryLabel": "Requête (facultative)",
"xpack.idxMgmt.enrichPolicyCreate.configurationStep.rangeOption": "Plage",
"xpack.idxMgmt.enrichPolicyCreate.configurationStep.rangeTypePopOver": "{type} correspond à un nombre, une date ou une plage d'adresses IP.",
"xpack.idxMgmt.enrichPolicyCreate.configurationStep.sourceIndicesLabel": "Index source",
"xpack.idxMgmt.enrichPolicyCreate.configurationStep.sourceIndicesRequiredError": "Au moins un index source est requis.",
"xpack.idxMgmt.enrichPolicyCreate.configurationStep.typeRequiredError": "Une valeur est requise pour le type de politique.",
"xpack.idxMgmt.enrichPolicyCreate.configurationStep.typeTitlePopOver": "Détermine comment faire correspondre les données avec les documents entrants.",
"xpack.idxMgmt.enrichPolicyCreate.configurationStep.uploadFileLink": "Charger un fichier",
Expand Down
2 changes: 0 additions & 2 deletions x-pack/plugins/translations/translations/ja-JP.json
Original file line number Diff line number Diff line change
Expand Up @@ -22538,8 +22538,6 @@
"xpack.idxMgmt.enrichPolicyCreate.configurationStep.queryLabel": "クエリー(任意)",
"xpack.idxMgmt.enrichPolicyCreate.configurationStep.rangeOption": "Range",
"xpack.idxMgmt.enrichPolicyCreate.configurationStep.rangeTypePopOver": "{type}は、番号、日付、またはIPアドレスの範囲と一致します。",
"xpack.idxMgmt.enrichPolicyCreate.configurationStep.sourceIndicesLabel": "ソースインデックス",
"xpack.idxMgmt.enrichPolicyCreate.configurationStep.sourceIndicesRequiredError": "ソースインデックスが最低1つ必要です。",
"xpack.idxMgmt.enrichPolicyCreate.configurationStep.typeRequiredError": "ポリシータイプ値が必要です。",
"xpack.idxMgmt.enrichPolicyCreate.configurationStep.typeTitlePopOver": "どのようにデータを受信ドキュメントに一致させるかを決定します。",
"xpack.idxMgmt.enrichPolicyCreate.configurationStep.uploadFileLink": "ファイルをアップロード",
Expand Down
2 changes: 0 additions & 2 deletions x-pack/plugins/translations/translations/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -22148,8 +22148,6 @@
"xpack.idxMgmt.enrichPolicyCreate.configurationStep.queryLabel": "查询(可选)",
"xpack.idxMgmt.enrichPolicyCreate.configurationStep.rangeOption": "范围",
"xpack.idxMgmt.enrichPolicyCreate.configurationStep.rangeTypePopOver": "{type} 匹配一个数字、日期或 IP 地址范围。",
"xpack.idxMgmt.enrichPolicyCreate.configurationStep.sourceIndicesLabel": "源索引",
"xpack.idxMgmt.enrichPolicyCreate.configurationStep.sourceIndicesRequiredError": "至少需要一个源索引。",
"xpack.idxMgmt.enrichPolicyCreate.configurationStep.typeRequiredError": "策略类型值必填。",
"xpack.idxMgmt.enrichPolicyCreate.configurationStep.typeTitlePopOver": "确定如何将数据匹配到传入文档。",
"xpack.idxMgmt.enrichPolicyCreate.configurationStep.uploadFileLink": "上传文件",
Expand Down
Loading