From 3936e3b7facd9821b9e5887dd4b071b0f8be8580 Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Mon, 21 Sep 2020 10:24:18 -0400 Subject: [PATCH] debug a pipeline with an index document --- .../test_pipeline/test_pipeline_flyout.tsx | 16 +- .../documents_schema.tsx | 24 +++ .../import_document_form.tsx | 174 +++++++++++++++++ .../import_documents_accordion.tsx | 54 +++++ .../tab_documents.tsx | 184 +++++++++++------- .../public/application/services/api.ts | 9 + .../ingest_pipelines/public/shared_imports.ts | 2 + .../server/routes/api/documents.ts | 56 ++++++ .../server/routes/api/index.ts | 2 + .../ingest_pipelines/server/routes/index.ts | 2 + 10 files changed, 438 insertions(+), 85 deletions(-) create mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/import_document_form.tsx create mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/import_documents_accordion.tsx create mode 100644 x-pack/plugins/ingest_pipelines/server/routes/api/documents.ts diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout.tsx index 46271a6bce51c..c643ebf55ef60 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout.tsx @@ -71,19 +71,11 @@ export const TestPipelineFlyout: React.FunctionComponent = ({ } else { // default to "Documents" tab tabContent = ( -
- - + validateAndTestPipeline={validateAndTestPipeline} + isRunningTest={isRunningTest} + /> ); } diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/documents_schema.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/documents_schema.tsx index e8ac223d56ed9..3bd800d3423a1 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/documents_schema.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/documents_schema.tsx @@ -82,6 +82,30 @@ export const documentsSchema: FormSchema = { } }, }, + { + validator: ({ value }: ValidationFuncArg) => { + const parsedJSON = JSON.parse(value); + + const isMissingSourceField = parsedJSON.find((obj: { _source?: object }) => { + if (!obj._source) { + return true; + } + + return false; + }); + + if (isMissingSourceField) { + return { + message: i18n.translate( + 'xpack.ingestPipelines.testPipelineFlyout.documentsForm.sourceFieldRequiredError', + { + defaultMessage: 'Documents require a _source field.', + } + ), + }; + } + }, + }, ], }, }; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/import_document_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/import_document_form.tsx new file mode 100644 index 0000000000000..84f48c9a27486 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/import_document_form.tsx @@ -0,0 +1,174 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState, FunctionComponent } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiFormRow, + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + EuiCallOut, + EuiSpacer, +} from '@elastic/eui'; + +import { + getUseField, + Field, + useKibana, + useForm, + Form, + TextField, + fieldValidators, + FieldConfig, +} from '../../../../../../shared_imports'; + +const UseField = getUseField({ component: Field }); + +const { emptyField } = fieldValidators; + +const i18nTexts = { + importDocumentButton: i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.loadDocuments.importDocButtonLabel', + { + defaultMessage: 'Import', + } + ), + importDocumentErrorMessage: i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.loadDocuments.importDocErrorMessage', + { + defaultMessage: 'Error importing document', + } + ), + indexField: { + fieldLabel: i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.loadDocuments.table.indexColumnLabel', + { + defaultMessage: 'Index', + } + ), + validationMessage: i18n.translate( + 'ingestPipelines.pipelineEditor.loadDocuments.indexRequiredError', + { + defaultMessage: 'An index name is required.', + } + ), + }, + idField: { + fieldLabel: i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.loadDocuments.table.documentIdColumnLabel', + { + defaultMessage: 'Document ID', + } + ), + validationMessage: i18n.translate( + 'ingestPipelines.pipelineEditor.loadDocuments.idRequiredError', + { + defaultMessage: 'A document ID is required.', + } + ), + }, +}; + +const fieldsConfig: Record = { + index: { + label: i18nTexts.indexField.fieldLabel, + validations: [ + { + validator: emptyField(i18nTexts.indexField.validationMessage), + }, + ], + }, + id: { + label: i18nTexts.idField.fieldLabel, + validations: [ + { + validator: emptyField(i18nTexts.idField.validationMessage), + }, + ], + }, +}; + +interface Props { + onAddDocuments: (document: any) => void; +} + +export const ImportDocumentForm: FunctionComponent = ({ onAddDocuments }) => { + const { services } = useKibana(); + + const [isLoadingDocument, setIsLoadingDocument] = useState(false); + const [loadingDocumentError, setLoadingDocumentError] = useState(undefined); + + const { form } = useForm({ defaultValue: { index: '', id: '' } }); + + const submitForm = async (e: React.FormEvent) => { + // e.preventDefault(); + + const { isValid, data } = await form.submit(); + + const { id, index } = data; + + if (isValid) { + setIsLoadingDocument(true); + + const { error, data: document } = await services.api.loadDocument(index, id); + + setIsLoadingDocument(false); + + if (error) { + setLoadingDocumentError(error); + return; + } + + onAddDocuments(document); + form.reset(); + } + }; + + return ( +
+ {loadingDocumentError && ( + <> + +

{loadingDocumentError.message}

+
+ + + + )} + + + + + + + + + + + + + + + {i18nTexts.importDocumentButton} + + + + + + + ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/import_documents_accordion.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/import_documents_accordion.tsx new file mode 100644 index 0000000000000..a8b6ebc6a2657 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/import_documents_accordion.tsx @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FunctionComponent } from 'react'; +import { i18n } from '@kbn/i18n'; + +import { EuiAccordion, EuiText, EuiSpacer } from '@elastic/eui'; + +import { ImportDocumentForm } from './import_document_form'; + +const i18nTexts = { + addDocumentsButton: i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.importDocumentsAccordion.importButtonLabel', + { + defaultMessage: 'Import existing documents', + } + ), + // TODO add link to Discover + contentDescription: i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.importDocumentsAccordion.contentDescriptionText', + { + defaultMessage: + 'Provide the index name and document ID of the indexed document to test. To explore your existing data, use Discover.', + } + ), +}; + +interface Props { + onAddDocuments: (document: any) => void; +} + +export const ImportDocumentsAccordion: FunctionComponent = ({ onAddDocuments }) => { + return ( + + <> + +

{i18nTexts.contentDescription}

+
+ + + + + +
+ ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/tab_documents.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/tab_documents.tsx index b2326644340a7..bdfca3f18f75c 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/tab_documents.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/tab_documents.tsx @@ -4,98 +4,136 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useRef, FunctionComponent, useCallback } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { EuiSpacer, EuiText, EuiButton, EuiLink } from '@elastic/eui'; -import { getUseField, Field, JsonEditorField, useKibana } from '../../../../../../shared_imports'; +import { + getUseField, + Field, + JsonEditorField, + useKibana, + useFormData, + FormHook, + Form, +} from '../../../../../../shared_imports'; + +import { ImportDocumentsAccordion } from './import_documents_accordion'; const UseField = getUseField({ component: Field }); interface Props { validateAndTestPipeline: () => Promise; isRunningTest: boolean; - isSubmitButtonDisabled: boolean; + form: FormHook; } -export const DocumentsTab: React.FunctionComponent = ({ +export const DocumentsTab: FunctionComponent = ({ validateAndTestPipeline, - isSubmitButtonDisabled, isRunningTest, + form, }) => { const { services } = useKibana(); + const documentsStateRef = useRef([]); + + const [, formatData] = useFormData({ form }); + + const onAddDocumentHandler = useCallback( + (document) => { + const existingDocuments = [...documentsStateRef.current, document]; + documentsStateRef.current = existingDocuments; + + const { documents } = formatData(); + + form.reset({ defaultValue: { documents: [...existingDocuments, ...documents] } }); + }, + [form, formatData] + ); + return ( -
- -

- - {i18n.translate( - 'xpack.ingestPipelines.testPipelineFlyout.documentsTab.simulateDocumentionLink', - { - defaultMessage: 'Learn more.', - } - )} - +

+
+ +

+ + {i18n.translate( + 'xpack.ingestPipelines.testPipelineFlyout.documentsTab.simulateDocumentionLink', + { + defaultMessage: 'Learn more.', + } + )} + + ), + }} + /> +

+
+ + + + + + + + {/* Documents editor */} + -

- - - - - {/* Documents editor */} - - - - - - {isRunningTest ? ( - - ) : ( - - )} - -
+ }, + }} + /> + + + + + {isRunningTest ? ( + + ) : ( + + )} + +
+ ); }; diff --git a/x-pack/plugins/ingest_pipelines/public/application/services/api.ts b/x-pack/plugins/ingest_pipelines/public/application/services/api.ts index 552e0ed0c41b2..2d6ab0477a603 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/services/api.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/services/api.ts @@ -120,6 +120,15 @@ export class ApiService { return result; } + + public async loadDocument(index: string, id: string) { + const result = await this.sendRequest({ + path: `${API_BASE_PATH}/documents/${encodeURIComponent(index)}/${encodeURIComponent(id)}`, + method: 'get', + }); + + return result; + } } export const apiService = new ApiService(); diff --git a/x-pack/plugins/ingest_pipelines/public/shared_imports.ts b/x-pack/plugins/ingest_pipelines/public/shared_imports.ts index abdbdf2140400..4424ec65f2294 100644 --- a/x-pack/plugins/ingest_pipelines/public/shared_imports.ts +++ b/x-pack/plugins/ingest_pipelines/public/shared_imports.ts @@ -45,6 +45,7 @@ export { getFieldValidityAndErrorMessage, ValidationFunc, ValidationConfig, + useFormData, } from '../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; export { @@ -63,6 +64,7 @@ export { NumericField, SelectField, CheckBoxField, + TextField, } from '../../../../src/plugins/es_ui_shared/static/forms/components'; export { diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/documents.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/documents.ts new file mode 100644 index 0000000000000..1f19112e069d5 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/documents.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { schema } from '@kbn/config-schema'; + +import { API_BASE_PATH } from '../../../common/constants'; +import { RouteDependencies } from '../../types'; + +const paramsSchema = schema.object({ + index: schema.string(), + id: schema.string(), +}); + +export const registerDocumentsRoute = ({ + router, + license, + lib: { isEsError }, +}: RouteDependencies): void => { + router.get( + { + path: `${API_BASE_PATH}/documents/{index}/{id}`, + validate: { + params: paramsSchema, + }, + }, + license.guardApiRoute(async (ctx, req, res) => { + const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; + const { index, id } = req.params; + + try { + const document = await callAsCurrentUser('get', { index, id }); + + const { _id, _index, _source } = document; + + return res.ok({ + body: { + _id, + _index, + _source, + }, + }); + } catch (error) { + if (isEsError(error)) { + return res.customError({ + statusCode: error.statusCode, + body: error, + }); + } + + return res.internalError({ body: error }); + } + }) + ); +}; diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/index.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/index.ts index 58a4bf5617659..7c0ab19917d1f 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/index.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/index.ts @@ -15,3 +15,5 @@ export { registerPrivilegesRoute } from './privileges'; export { registerDeleteRoute } from './delete'; export { registerSimulateRoute } from './simulate'; + +export { registerDocumentsRoute } from './documents'; diff --git a/x-pack/plugins/ingest_pipelines/server/routes/index.ts b/x-pack/plugins/ingest_pipelines/server/routes/index.ts index f703a460143f4..5e80be4388b25 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/index.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/index.ts @@ -13,6 +13,7 @@ import { registerPrivilegesRoute, registerDeleteRoute, registerSimulateRoute, + registerDocumentsRoute, } from './api'; export class ApiRoutes { @@ -23,5 +24,6 @@ export class ApiRoutes { registerPrivilegesRoute(dependencies); registerDeleteRoute(dependencies); registerSimulateRoute(dependencies); + registerDocumentsRoute(dependencies); } }