From 90ec298b0ecc1734774a6a656171e49fc32606ff Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Tue, 7 Apr 2020 22:02:26 -0400 Subject: [PATCH 01/11] add create pipeline page --- .../public/application/app.tsx | 3 +- .../public/application/components/index.ts | 7 + .../components/pipeline_form/index.ts | 7 + .../pipeline_form/pipeline_form.tsx | 216 ++++++++++++++++++ .../components/pipeline_form/schema.tsx | 87 +++++++ .../public/application/constants/index.ts | 2 + .../public/application/sections/index.ts | 2 + .../sections/pipelines_create/index.ts | 7 + .../pipelines_create/pipelines_create.tsx | 67 ++++++ .../public/application/services/api.ts | 35 ++- .../plugins/ingest_pipelines/public/plugin.ts | 2 +- .../ingest_pipelines/public/shared_imports.ts | 29 +++ 12 files changed, 461 insertions(+), 3 deletions(-) create mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/index.ts create mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/index.ts create mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx create mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/schema.tsx create mode 100644 x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/index.ts create mode 100644 x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/app.tsx b/x-pack/plugins/ingest_pipelines/public/application/app.tsx index 9f994e75256a4..f3c6ccd161f66 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/app.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/app.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { HashRouter, Switch, Route } from 'react-router-dom'; import { BASE_PATH } from '../../common/constants'; -import { PipelinesList } from './sections'; +import { PipelinesList, PipelinesCreate } from './sections'; export const App = () => { return ( @@ -20,5 +20,6 @@ export const App = () => { export const AppWithoutRouter = () => ( + ); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/index.ts new file mode 100644 index 0000000000000..21a2ee30a84e1 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/index.ts @@ -0,0 +1,7 @@ +/* + * 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. + */ + +export { PipelineForm } from './pipeline_form'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/index.ts new file mode 100644 index 0000000000000..21a2ee30a84e1 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/index.ts @@ -0,0 +1,7 @@ +/* + * 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. + */ + +export { PipelineForm } from './pipeline_form'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx new file mode 100644 index 0000000000000..6a7d41ac9e348 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx @@ -0,0 +1,216 @@ +/* + * 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 } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; + +import { + useForm, + Form, + getUseField, + getFormRow, + Field, + FormConfig, + JsonEditorField, +} from '../../../shared_imports'; +import { Pipeline } from '../../../../common/types'; + +import { pipelineFormSchema } from './schema'; + +interface Props { + onSave: (pipeline: any) => void; // todo fix TS + isSaving: boolean; + saveError: any; + defaultValue?: Pipeline; +} + +const fieldsMeta = { + name: { + title: i18n.translate('xpack.ingestPipelines.form.nameTitle', { + defaultMessage: 'Name', + }), + description: i18n.translate('xpack.ingestPipelines.form.nameDescription', { + defaultMessage: 'A unique identifier for this pipeline.', + }), + testSubject: 'nameField', + }, + description: { + title: i18n.translate('xpack.ingestPipelines.form.descriptionFielditle', { + defaultMessage: 'Description', + }), + description: i18n.translate('xpack.ingestPipelines.form.descriptionFieldDescription', { + defaultMessage: 'The description to apply to the pipeline.', + }), + testSubject: 'descriptionField', + }, + processors: { + title: i18n.translate('xpack.ingestPipelines.form.processorsFielditle', { + defaultMessage: 'Processors', + }), + description: i18n.translate('xpack.ingestPipelines.form.processorsFieldDescription', { + defaultMessage: 'The processors to apply to the pipeline.', + }), + ariaLabel: i18n.translate('xpack.ingestPipelines.form.processorsFieldAriaLabel', { + defaultMessage: 'Processors JSON editor', + }), + testSubject: 'processorsField', + }, + onFailure: { + title: i18n.translate('xpack.ingestPipelines.form.onFailureFielditle', { + defaultMessage: 'On failure', + }), + description: i18n.translate('xpack.ingestPipelines.form.onFailureFieldDescription', { + defaultMessage: 'The on-failure processors to apply to the pipeline.', + }), + ariaLabel: i18n.translate('xpack.ingestPipelines.form.onFailureFieldAriaLabel', { + defaultMessage: 'On failure processors JSON editor', + }), + testSubject: 'onFailureField', + }, + version: { + title: i18n.translate('xpack.ingestPipelines.form.versionTitle', { + defaultMessage: 'Version', + }), + description: i18n.translate('xpack.ingestPipelines.form.versionDescription', { + defaultMessage: 'A number that identifies the pipeline to external management systems.', + }), + testSubject: 'versionField', + }, +}; + +const UseField = getUseField({ component: Field }); +const FormRow = getFormRow({ titleTag: 'h3' }); + +export const PipelineForm: React.FunctionComponent = ({ + defaultValue = { + name: '', + description: '', + processors: [], + onFailure: [], + version: '', + }, + onSave, + isSaving, + saveError, +}) => { + const [isFormValid, setIsFormValid] = useState(true); + + const setDataAndValidation: FormConfig['onSubmit'] = (formData, isValid) => { + setIsFormValid(isValid); + + if (isValid) { + onSave(formData); + } + }; + + const { form } = useForm({ + schema: pipelineFormSchema, + defaultValue, + onSubmit: setDataAndValidation, + }); + + const { name, description, version, processors, onFailure } = fieldsMeta; + + return ( + <> + {saveError ? ( + // TODO save error goes here +
+ ) : null} + +
+ {/* Name field */} + + + + + {/* Description */} + + + + + {/* Processors field */} + + + + + {/* On failure field */} + + + + + {/* Version field */} + + + + + + + {/* Form submission */} + + + + + + { + + } + + + + + + + + + + ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/schema.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/schema.tsx new file mode 100644 index 0000000000000..7dd4842edb8fa --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/schema.tsx @@ -0,0 +1,87 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +import { FormSchema, FIELD_TYPES, fieldValidators, fieldFormatters } from '../../../shared_imports'; + +const { emptyField } = fieldValidators; +const { toInt } = fieldFormatters; + +const stringifyJson = (json: { [key: string]: unknown }): string => + Array.isArray(json) ? JSON.stringify(json, null, 2) : '[\n\n]'; + +const parseJson = (jsonString: string) => { + let parsedJSON; + + try { + parsedJSON = JSON.parse(jsonString); + + if (!Array.isArray(parsedJSON)) { + // Convert object to array + parsedJSON = [parsedJSON]; + } + } catch { + parsedJSON = []; + } + + return parsedJSON; +}; + +export const pipelineFormSchema: FormSchema = { + name: { + type: FIELD_TYPES.TEXT, + label: i18n.translate('xpack.ingestPipelines.form.nameFieldLabel', { + defaultMessage: 'Name', + }), + validations: [ + { + validator: emptyField( + i18n.translate('xpack.ingestPipelines.form.pipelineNameRequiredError', { + defaultMessage: 'A pipeline name is required.', + }) + ), + }, + ], + }, + description: { + type: FIELD_TYPES.TEXTAREA, + label: i18n.translate('xpack.ingestPipelines.form.descriptionFieldLabel', { + defaultMessage: 'Description', + }), + validations: [ + { + validator: emptyField( + i18n.translate('xpack.ingestPipelines.form.pipelineDescriptionRequiredError', { + defaultMessage: 'A pipeline description is required.', + }) + ), + }, + ], + }, + processors: { + label: i18n.translate('xpack.ingestPipelines.form.processorsFieldLabel', { + defaultMessage: 'Processors', + }), + serializer: processors => parseJson(processors), + deserializer: processors => stringifyJson(processors), + }, + onFailure: { + label: i18n.translate('xpack.ingestPipelines.form.onFailureFieldLabel', { + defaultMessage: 'On failure (optional)', + }), + // TODO: ES doesn't accept an empty array for on_failure + serializer: onFailureProcessors => parseJson(onFailureProcessors), + deserializer: onFailureProcessors => stringifyJson(onFailureProcessors), + }, + version: { + type: FIELD_TYPES.NUMBER, + label: i18n.translate('xpack.ingestPipelines.form.versionFieldLabel', { + defaultMessage: 'Version (optional)', + }), + formatters: [toInt], + }, +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/constants/index.ts b/x-pack/plugins/ingest_pipelines/public/application/constants/index.ts index 122b2fbd9f382..ae2b285c91c53 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/constants/index.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/constants/index.ts @@ -7,3 +7,5 @@ // UI metric constants export const UIM_APP_NAME = 'ingest_pipelines'; export const UIM_PIPELINES_LIST_LOAD = 'pipelines_list_load'; +export const UIM_PIPELINE_CREATE = 'pipeline_create'; +export const UIM_PIPELINE_UPDATE = 'pipeline_update'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/index.ts b/x-pack/plugins/ingest_pipelines/public/application/sections/index.ts index d750aaba177ba..30935bdd9c9c4 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/sections/index.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/index.ts @@ -5,3 +5,5 @@ */ export { PipelinesList } from './pipelines_list'; + +export { PipelinesCreate } from './pipelines_create'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/index.ts b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/index.ts new file mode 100644 index 0000000000000..374defa869916 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/index.ts @@ -0,0 +1,7 @@ +/* + * 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. + */ + +export { PipelinesCreate } from './pipelines_create'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx new file mode 100644 index 0000000000000..51fc87c4fc61b --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx @@ -0,0 +1,67 @@ +/* + * 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 } from 'react'; +import { RouteComponentProps } from 'react-router-dom'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiPageBody, EuiPageContent, EuiSpacer, EuiTitle } from '@elastic/eui'; + +import { useKibana } from '../../../shared_imports'; +import { PipelineForm } from '../../components'; + +export const PipelinesCreate: React.FunctionComponent = ({ history }) => { + const { services } = useKibana(); + + const [isSaving, setIsSaving] = useState(false); + const [saveError, setSaveError] = useState(null); + + const onSave = async (pipeline: any) => { + // TODO fix TS + setIsSaving(true); + setSaveError(null); + + const { error } = await services.api.createPipeline(pipeline); + + setIsSaving(false); + + if (error) { + setSaveError(error); + return; + } + + // TODO navigate back to list page with flyout open + // history.push(); + }; + + // TODO set breadcrumbs + // useEffect(() => { + // services.setBreadcrumbs([ + // { + // text: i18n.translate('xpack.ingestPipelines.breadcrumbsTitle', { + // defaultMessage: 'Ingest Pipelines', + // }), + // }, + // ]); + // }, [services]); + + return ( + + + +

+ +

+
+ + + + +
+
+ ); +}; 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 71ebb4b25d829..0eb313c1397af 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/services/api.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/services/api.ts @@ -5,15 +5,21 @@ */ import { HttpSetup } from 'src/core/public'; +import { Pipeline } from '../../../common/types'; import { API_BASE_PATH } from '../../../common/constants'; import { UseRequestConfig, + SendRequestConfig, + SendRequestResponse, sendRequest as _sendRequest, useRequest as _useRequest, } from '../../shared_imports'; +import { UiMetricService } from './ui_metric'; +import { UIM_PIPELINE_CREATE } from '../constants'; export class ApiService { private client: HttpSetup | undefined; + private uiMetricService: UiMetricService | undefined; private useRequest(config: UseRequestConfig) { if (!this.client) { @@ -22,8 +28,23 @@ export class ApiService { return _useRequest(this.client, config); } - public setup(httpClient: HttpSetup): void { + private sendRequest(config: SendRequestConfig): Promise { + if (!this.client) { + throw new Error('Api service has not be initialized.'); + } + return _sendRequest(this.client, config); + } + + private trackUiMetric(eventName: string) { + if (!this.uiMetricService) { + throw new Error('UI metric service has not be initialized.'); + } + return this.uiMetricService.trackUiMetric(eventName); + } + + public setup(httpClient: HttpSetup, uiMetricService: UiMetricService): void { this.client = httpClient; + this.uiMetricService = uiMetricService; } public useLoadPipelines() { @@ -32,6 +53,18 @@ export class ApiService { method: 'get', }); } + + public async createPipeline(pipeline: Pipeline) { + const result = await this.sendRequest({ + path: API_BASE_PATH, + method: 'put', + body: JSON.stringify(pipeline), + }); + + this.trackUiMetric(UIM_PIPELINE_CREATE); + + return result; + } } export const apiService = new ApiService(); diff --git a/x-pack/plugins/ingest_pipelines/public/plugin.ts b/x-pack/plugins/ingest_pipelines/public/plugin.ts index 7d8eb73d35a19..5a166588f6236 100644 --- a/x-pack/plugins/ingest_pipelines/public/plugin.ts +++ b/x-pack/plugins/ingest_pipelines/public/plugin.ts @@ -17,8 +17,8 @@ export class IngestPipelinesPlugin implements Plugin { const { http } = coreSetup; // Initialize services - apiService.setup(http); uiMetricService.setup(usageCollection); + apiService.setup(http, uiMetricService); management.sections.getSection('elasticsearch')!.registerApp({ id: PLUGIN_ID, diff --git a/x-pack/plugins/ingest_pipelines/public/shared_imports.ts b/x-pack/plugins/ingest_pipelines/public/shared_imports.ts index 1a278a04adedf..795aeeb6fec79 100644 --- a/x-pack/plugins/ingest_pipelines/public/shared_imports.ts +++ b/x-pack/plugins/ingest_pipelines/public/shared_imports.ts @@ -3,6 +3,8 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { useKibana as _useKibana } from '../../../../src/plugins/kibana_react/public'; +import { AppServices } from './application'; export { SendRequestConfig, @@ -11,3 +13,30 @@ export { sendRequest, useRequest, } from '../../../../src/plugins/es_ui_shared/public/request/np_ready_request'; + +export { + FormSchema, + FIELD_TYPES, + VALIDATION_TYPES, + FieldConfig, + FormConfig, + useForm, + Form, + getUseField, +} from '../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; + +export { + fieldFormatters, + fieldValidators, + serializers, +} from '../../../../src/plugins/es_ui_shared/static/forms/helpers'; + +export { + getFormRow, + Field, + JsonEditorField, +} from '../../../../src/plugins/es_ui_shared/static/forms/components'; + +export { isJSON } from '../../../../src/plugins/es_ui_shared/static/validators/string'; + +export const useKibana = () => _useKibana(); From 21a354f020c3b72522ed27791eda328b1785437c Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Wed, 8 Apr 2020 22:19:02 -0400 Subject: [PATCH 02/11] add breadcrumb service --- .../public/application/index.tsx | 5 +- .../application/mount_management_section.ts | 14 +---- .../pipelines_create/pipelines_create.tsx | 16 ++--- .../pipelines_list/pipelines_list.tsx | 3 +- .../application/services/breadcrumbs.ts | 62 +++++++++++++++++++ .../public/application/services/index.ts | 2 + 6 files changed, 75 insertions(+), 27 deletions(-) create mode 100644 x-pack/plugins/ingest_pipelines/public/application/services/breadcrumbs.ts diff --git a/x-pack/plugins/ingest_pipelines/public/application/index.tsx b/x-pack/plugins/ingest_pipelines/public/application/index.tsx index 752d1ef270128..914a4b3c57e70 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/index.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/index.tsx @@ -6,14 +6,13 @@ import React, { ReactNode } from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { ChromeBreadcrumb } from 'src/core/public'; import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; import { App } from './app'; -import { DocumentationService, UiMetricService, ApiService } from './services'; +import { DocumentationService, UiMetricService, ApiService, BreadcrumbService } from './services'; export interface AppServices { - setBreadcrumbs: (crumbs: ChromeBreadcrumb[]) => void; + breadcrumbs: BreadcrumbService; metric: UiMetricService; documentation: DocumentationService; api: ApiService; diff --git a/x-pack/plugins/ingest_pipelines/public/application/mount_management_section.ts b/x-pack/plugins/ingest_pipelines/public/application/mount_management_section.ts index 29db501488e53..51db91295ed42 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/mount_management_section.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/mount_management_section.ts @@ -5,9 +5,8 @@ */ import { CoreSetup } from 'src/core/public'; import { ManagementAppMountParams } from 'src/plugins/management/public'; -import { i18n } from '@kbn/i18n'; -import { documentationService, uiMetricService, apiService } from './services'; +import { documentationService, uiMetricService, apiService, breadcrumbService } from './services'; import { renderApp } from '.'; export async function mountManagementSection( @@ -22,17 +21,10 @@ export async function mountManagementSection( } = coreStart; documentationService.setup(docLinks); - - setBreadcrumbs([ - { - text: i18n.translate('xpack.ingestPipelines.breadcrumbsTitle', { - defaultMessage: 'Ingest Pipelines', - }), - }, - ]); + breadcrumbService.setup(setBreadcrumbs); const services = { - setBreadcrumbs, + breadcrumbs: breadcrumbService, metric: uiMetricService, documentation: documentationService, api: apiService, diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx index 51fc87c4fc61b..55eeaf5039928 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx @@ -3,7 +3,7 @@ * 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 } from 'react'; +import React, { useState, useEffect } from 'react'; import { RouteComponentProps } from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiPageBody, EuiPageContent, EuiSpacer, EuiTitle } from '@elastic/eui'; @@ -32,19 +32,11 @@ export const PipelinesCreate: React.FunctionComponent = ({ } // TODO navigate back to list page with flyout open - // history.push(); }; - // TODO set breadcrumbs - // useEffect(() => { - // services.setBreadcrumbs([ - // { - // text: i18n.translate('xpack.ingestPipelines.breadcrumbsTitle', { - // defaultMessage: 'Ingest Pipelines', - // }), - // }, - // ]); - // }, [services]); + useEffect(() => { + services.breadcrumbs.setBreadcrumbs('create'); + }, [services]); return ( diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/pipelines_list.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/pipelines_list.tsx index d2ea0f77ebcf3..3c12e28ca5a6f 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/pipelines_list.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/pipelines_list.tsx @@ -27,7 +27,8 @@ export const PipelinesList: React.FunctionComponent = () => { // Track component loaded useEffect(() => { services.metric.trackUiMetric(UIM_PIPELINES_LIST_LOAD); - }, [services.metric]); + services.breadcrumbs.setBreadcrumbs('home'); + }, [services]); return ( diff --git a/x-pack/plugins/ingest_pipelines/public/application/services/breadcrumbs.ts b/x-pack/plugins/ingest_pipelines/public/application/services/breadcrumbs.ts new file mode 100644 index 0000000000000..49885b3d28807 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/services/breadcrumbs.ts @@ -0,0 +1,62 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { BASE_PATH } from '../../../common/constants'; +import { ManagementAppMountParams } from '../../../../../../src/plugins/management/public'; + +type SetBreadcrumbs = ManagementAppMountParams['setBreadcrumbs']; + +export class BreadcrumbService { + private breadcrumbs: { + [key: string]: Array<{ + text: string; + href?: string; + }>; + } = { + home: [], + }; + private setBreadcrumbsHandler?: SetBreadcrumbs; + + public setup(setBreadcrumbsHandler: SetBreadcrumbs): void { + const homeBreadcrumbText = i18n.translate('xpack.ingestPipelines.breadcrumb.pipelinesLabel', { + defaultMessage: 'Ingest Pipelines', + }); + + this.setBreadcrumbsHandler = setBreadcrumbsHandler; + + this.breadcrumbs.home = [ + { + text: homeBreadcrumbText, + }, + ]; + + this.breadcrumbs.create = [ + { + text: homeBreadcrumbText, + href: `#${BASE_PATH}`, + }, + { + text: i18n.translate('xpack.ingestPipelines.breadcrumb.createPipelineLabel', { + defaultMessage: 'Create pipeline', + }), + }, + ]; + } + + public setBreadcrumbs(type: 'create' | 'home'): void { + if (!this.setBreadcrumbsHandler) { + throw new Error('Breadcrumb service has not been initialized'); + } + + const newBreadcrumbs = this.breadcrumbs[type] + ? [...this.breadcrumbs[type]] + : [...this.breadcrumbs.home]; + + this.setBreadcrumbsHandler(newBreadcrumbs); + } +} + +export const breadcrumbService = new BreadcrumbService(); diff --git a/x-pack/plugins/ingest_pipelines/public/application/services/index.ts b/x-pack/plugins/ingest_pipelines/public/application/services/index.ts index 7f69660daefa1..f03a7824f8364 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/services/index.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/services/index.ts @@ -9,3 +9,5 @@ export { documentationService, DocumentationService } from './documentation'; export { uiMetricService, UiMetricService } from './ui_metric'; export { apiService, ApiService } from './api'; + +export { breadcrumbService, BreadcrumbService } from './breadcrumbs'; From 0dda2190ed5dfb36cf5c28f07ae4bdfa46daaeb9 Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Thu, 9 Apr 2020 13:33:00 -0400 Subject: [PATCH 03/11] integrate with list page --- .../pipelines_create/pipelines_create.tsx | 5 ++-- .../sections/pipelines_list/table.tsx | 26 ++++++++++++++++--- .../public/application/services/api.ts | 1 - 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx index 55eeaf5039928..17917698c9f43 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx @@ -8,6 +8,7 @@ import { RouteComponentProps } from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiPageBody, EuiPageContent, EuiSpacer, EuiTitle } from '@elastic/eui'; +import { BASE_PATH } from '../../../../common/constants'; import { useKibana } from '../../../shared_imports'; import { PipelineForm } from '../../components'; @@ -31,7 +32,7 @@ export const PipelinesCreate: React.FunctionComponent = ({ return; } - // TODO navigate back to list page with flyout open + history.push(BASE_PATH); }; useEffect(() => { @@ -44,7 +45,7 @@ export const PipelinesCreate: React.FunctionComponent = ({

diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/table.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/table.tsx index fb79c062c7722..12693435f00be 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/table.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/table.tsx @@ -7,6 +7,7 @@ import React, { FunctionComponent } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiInMemoryTable, EuiLink, EuiButton } from '@elastic/eui'; +import { BASE_PATH } from '../../../../common/constants'; import { Pipeline } from '../../../../common/types'; export interface Props { @@ -27,13 +28,30 @@ export const PipelineTable: FunctionComponent = ({ return ( + toolsRight: [ + {i18n.translate('xpack.ingestPipelines.list.table.reloadButtonLabel', { defaultMessage: 'Reload', })} - - ), + , + + {i18n.translate('xpack.ingestPipelines.list.table.createPipelineButtonLabel', { + defaultMessage: 'Create a pipeline', + })} + , + ], box: { incremental: true, }, 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 33c3ad5fe3fff..4af86a38bb523 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/services/api.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/services/api.ts @@ -7,7 +7,6 @@ import { HttpSetup } from 'src/core/public'; import { Pipeline } from '../../../common/types'; import { API_BASE_PATH } from '../../../common/constants'; -import { Pipeline } from '../../../common/types'; import { UseRequestConfig, SendRequestConfig, From 1e4a8d37343bc23ec0afd7cbe8c08313d61c58e9 Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Thu, 9 Apr 2020 13:35:48 -0400 Subject: [PATCH 04/11] fix typo --- .../public/application/sections/pipelines_list/main.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/main.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/main.tsx index 7fcd819250aa9..655b9cce620b6 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/main.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/main.tsx @@ -37,7 +37,7 @@ export const PipelinesList: React.FunctionComponent = () => { useEffect(() => { services.metric.trackUiMetric(UIM_PIPELINES_LIST_LOAD); services.breadcrumbs.setBreadcrumbs('home'); - }, [services.metric, services.breadcrumb, services.breadcrumbs]); + }, [services.metric, services.breadcrumbs]); const { data, isLoading, error, sendRequest } = services.api.useLoadPipelines(); From ee0248c052b04905981a80b5df6d6ad099e47a96 Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Thu, 9 Apr 2020 22:02:53 -0400 Subject: [PATCH 05/11] add section error and json validation; comment out optional fields for now --- .../public/application/components/index.ts | 2 ++ .../pipeline_form/pipeline_form.tsx | 29 ++++++++++++++----- .../components/pipeline_form/schema.tsx | 27 ++++++++++++++++- .../application/components/section_error.tsx | 29 +++++++++++++++++++ 4 files changed, 79 insertions(+), 8 deletions(-) create mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/section_error.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/index.ts index 21a2ee30a84e1..705dbe54618d6 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/index.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/index.ts @@ -5,3 +5,5 @@ */ export { PipelineForm } from './pipeline_form'; + +export { SectionError } from './section_error'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx index 6a7d41ac9e348..f4c35a26ffab4 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx @@ -19,6 +19,7 @@ import { } from '../../../shared_imports'; import { Pipeline } from '../../../../common/types'; +import { SectionError } from '../section_error'; import { pipelineFormSchema } from './schema'; interface Props { @@ -118,8 +119,19 @@ export const PipelineForm: React.FunctionComponent = ({ return ( <> {saveError ? ( - // TODO save error goes here -
+ <> + + } + error={saveError} + data-test-subj="savePipelineError" + /> + + ) : null}
@@ -139,6 +151,9 @@ export const PipelineForm: React.FunctionComponent = ({ path="description" componentProps={{ ['data-test-subj']: description.testSubject, + euiFieldProps: { + compressed: true, + }, }} /> @@ -158,28 +173,28 @@ export const PipelineForm: React.FunctionComponent = ({ {/* On failure field */} - + {/* - + */} {/* Version field */} - + {/* - + */} diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/schema.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/schema.tsx index 7dd4842edb8fa..139aabc9c00e9 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/schema.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/schema.tsx @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { FormSchema, FIELD_TYPES, fieldValidators, fieldFormatters } from '../../../shared_imports'; -const { emptyField } = fieldValidators; +const { emptyField, isJsonField } = fieldValidators; const { toInt } = fieldFormatters; const stringifyJson = (json: { [key: string]: unknown }): string => @@ -68,6 +68,22 @@ export const pipelineFormSchema: FormSchema = { }), serializer: processors => parseJson(processors), deserializer: processors => stringifyJson(processors), + validations: [ + { + validator: emptyField( + i18n.translate('xpack.ingestPipelines.form.processorsRequiredError', { + defaultMessage: 'Processors are required.', + }) + ), + }, + { + validator: isJsonField( + i18n.translate('xpack.ingestPipelines.form.processorsJsonError', { + defaultMessage: 'The processors JSON is not valid.', + }) + ), + }, + ], }, onFailure: { label: i18n.translate('xpack.ingestPipelines.form.onFailureFieldLabel', { @@ -76,6 +92,15 @@ export const pipelineFormSchema: FormSchema = { // TODO: ES doesn't accept an empty array for on_failure serializer: onFailureProcessors => parseJson(onFailureProcessors), deserializer: onFailureProcessors => stringifyJson(onFailureProcessors), + validations: [ + { + validator: isJsonField( + i18n.translate('xpack.ingestPipelines.form.onFailureProcessorsJsonError', { + defaultMessage: 'The on-failure processors JSON is not valid.', + }) + ), + }, + ], }, version: { type: FIELD_TYPES.NUMBER, diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/section_error.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/section_error.tsx new file mode 100644 index 0000000000000..317da95e24687 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/section_error.tsx @@ -0,0 +1,29 @@ +/* + * 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 { EuiCallOut } from '@elastic/eui'; +import React from 'react'; + +export interface Error { + error: string; + message: string; + statusCode: number; +} + +interface Props { + title: React.ReactNode; + error: Error; +} + +export const SectionError: React.FunctionComponent = ({ title, error, ...rest }) => { + const { message } = error; + + return ( + +

{message}

+
+ ); +}; From b8606688e3e093d9ee0d1e3c77a0bf643b63504d Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Fri, 10 Apr 2020 09:33:46 -0400 Subject: [PATCH 06/11] clean up form layout --- .../pipeline_form/pipeline_form.tsx | 215 +++++++++++------- .../components/pipeline_form/schema.tsx | 2 +- 2 files changed, 128 insertions(+), 89 deletions(-) diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx index f4c35a26ffab4..fcb7ecf3db8e3 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx @@ -6,7 +6,7 @@ import React, { useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; +import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiSwitch } from '@elastic/eui'; import { useForm, @@ -29,60 +29,6 @@ interface Props { defaultValue?: Pipeline; } -const fieldsMeta = { - name: { - title: i18n.translate('xpack.ingestPipelines.form.nameTitle', { - defaultMessage: 'Name', - }), - description: i18n.translate('xpack.ingestPipelines.form.nameDescription', { - defaultMessage: 'A unique identifier for this pipeline.', - }), - testSubject: 'nameField', - }, - description: { - title: i18n.translate('xpack.ingestPipelines.form.descriptionFielditle', { - defaultMessage: 'Description', - }), - description: i18n.translate('xpack.ingestPipelines.form.descriptionFieldDescription', { - defaultMessage: 'The description to apply to the pipeline.', - }), - testSubject: 'descriptionField', - }, - processors: { - title: i18n.translate('xpack.ingestPipelines.form.processorsFielditle', { - defaultMessage: 'Processors', - }), - description: i18n.translate('xpack.ingestPipelines.form.processorsFieldDescription', { - defaultMessage: 'The processors to apply to the pipeline.', - }), - ariaLabel: i18n.translate('xpack.ingestPipelines.form.processorsFieldAriaLabel', { - defaultMessage: 'Processors JSON editor', - }), - testSubject: 'processorsField', - }, - onFailure: { - title: i18n.translate('xpack.ingestPipelines.form.onFailureFielditle', { - defaultMessage: 'On failure', - }), - description: i18n.translate('xpack.ingestPipelines.form.onFailureFieldDescription', { - defaultMessage: 'The on-failure processors to apply to the pipeline.', - }), - ariaLabel: i18n.translate('xpack.ingestPipelines.form.onFailureFieldAriaLabel', { - defaultMessage: 'On failure processors JSON editor', - }), - testSubject: 'onFailureField', - }, - version: { - title: i18n.translate('xpack.ingestPipelines.form.versionTitle', { - defaultMessage: 'Version', - }), - description: i18n.translate('xpack.ingestPipelines.form.versionDescription', { - defaultMessage: 'A number that identifies the pipeline to external management systems.', - }), - testSubject: 'versionField', - }, -}; - const UseField = getUseField({ component: Field }); const FormRow = getFormRow({ titleTag: 'h3' }); @@ -99,6 +45,8 @@ export const PipelineForm: React.FunctionComponent = ({ saveError, }) => { const [isFormValid, setIsFormValid] = useState(true); + const [isVersionVisible, setIsVersionVisible] = useState(false); + const [isOnFailureEditorVisible, setIsOnFailureEditorVisible] = useState(false); const setDataAndValidation: FormConfig['onSubmit'] = (formData, isValid) => { setIsFormValid(isValid); @@ -114,8 +62,6 @@ export const PipelineForm: React.FunctionComponent = ({ onSubmit: setDataAndValidation, }); - const { name, description, version, processors, onFailure } = fieldsMeta; - return ( <> {saveError ? ( @@ -135,22 +81,68 @@ export const PipelineForm: React.FunctionComponent = ({ ) : null} - {/* Name field */} - + {/* Name field with optional version */} + + } + description={ + <> + + + + } + checked={isVersionVisible} + onChange={e => setIsVersionVisible(e.target.checked)} + data-test-subj="versionToggle" + /> + + } + > + + {isVersionVisible && ( + + )} {/* Description */} - + + } + description={ + + } + > = ({ {/* Processors field */} - + + } + description={ + + } + > - {/* On failure field */} - {/* - - */} - - {/* Version field */} - {/* - - */} + {/* On-failure field */} + + } + description={ + <> + + + + } + checked={isOnFailureEditorVisible} + onChange={e => setIsOnFailureEditorVisible(e.target.checked)} + data-test-subj="onFailureToggle" + /> + + } + > + {isOnFailureEditorVisible ? ( + + ) : ( + // requires either children or a field +
+ )} + diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/schema.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/schema.tsx index 139aabc9c00e9..35f562adeb856 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/schema.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/schema.tsx @@ -87,7 +87,7 @@ export const pipelineFormSchema: FormSchema = { }, onFailure: { label: i18n.translate('xpack.ingestPipelines.form.onFailureFieldLabel', { - defaultMessage: 'On failure (optional)', + defaultMessage: 'On-failure processors (optional)', }), // TODO: ES doesn't accept an empty array for on_failure serializer: onFailureProcessors => parseJson(onFailureProcessors), From 4af7814e9aa55729cd00f7f959ea35205310468d Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Fri, 10 Apr 2020 13:39:10 -0400 Subject: [PATCH 07/11] fix validation and clean up copy --- .../pipeline_form/pipeline_form.tsx | 41 ++++++++++++++----- .../components/pipeline_form/schema.tsx | 34 +++++++++++---- .../pipelines_create/pipelines_create.tsx | 2 +- .../application/services/documentation.ts | 8 ++++ .../ingest_pipelines/public/shared_imports.ts | 6 ++- 5 files changed, 71 insertions(+), 20 deletions(-) diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx index fcb7ecf3db8e3..6335e34e460af 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx @@ -6,7 +6,7 @@ import React, { useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiSwitch } from '@elastic/eui'; +import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiSwitch, EuiLink } from '@elastic/eui'; import { useForm, @@ -16,6 +16,7 @@ import { Field, FormConfig, JsonEditorField, + useKibana, } from '../../../shared_imports'; import { Pipeline } from '../../../../common/types'; @@ -36,21 +37,20 @@ export const PipelineForm: React.FunctionComponent = ({ defaultValue = { name: '', description: '', - processors: [], - onFailure: [], + processors: '', + onFailure: '', version: '', }, onSave, isSaving, saveError, }) => { - const [isFormValid, setIsFormValid] = useState(true); + const { services } = useKibana(); + const [isVersionVisible, setIsVersionVisible] = useState(false); const [isOnFailureEditorVisible, setIsOnFailureEditorVisible] = useState(false); const setDataAndValidation: FormConfig['onSubmit'] = (formData, isValid) => { - setIsFormValid(isValid); - if (isValid) { onSave(formData); } @@ -81,7 +81,7 @@ export const PipelineForm: React.FunctionComponent = ({ ) : null} - {/* Name field with optional version */} + {/* Name field with optional version field */} @@ -161,7 +161,16 @@ export const PipelineForm: React.FunctionComponent = ({ description={ + {i18n.translate('xpack.ingestPipelines.form.processorsDocumentionLink', { + defaultMessage: 'Learn more.', + })} + + ), + }} /> } > @@ -195,7 +204,16 @@ export const PipelineForm: React.FunctionComponent = ({ <> + {i18n.translate('xpack.ingestPipelines.form.onFailureDocumentionLink', { + defaultMessage: 'Learn more.', + })} + + ), + }} /> = ({ }} /> ) : ( - // requires either children or a field + // requires children or a field + // For now, we return an empty
if the editor is not visible
)} @@ -248,7 +267,7 @@ export const PipelineForm: React.FunctionComponent = ({ iconType="check" onClick={form.submit} data-test-subj="submitButton" - disabled={isFormValid === false} + disabled={form.isValid === false} isLoading={isSaving} > { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/schema.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/schema.tsx index 35f562adeb856..47db507c0bf37 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/schema.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/schema.tsx @@ -6,7 +6,15 @@ import { i18n } from '@kbn/i18n'; -import { FormSchema, FIELD_TYPES, fieldValidators, fieldFormatters } from '../../../shared_imports'; +import { + FormSchema, + FIELD_TYPES, + fieldValidators, + fieldFormatters, + isJSON, + isEmptyString, + ValidationFuncArg, +} from '../../../shared_imports'; const { emptyField, isJsonField } = fieldValidators; const { toInt } = fieldFormatters; @@ -89,16 +97,28 @@ export const pipelineFormSchema: FormSchema = { label: i18n.translate('xpack.ingestPipelines.form.onFailureFieldLabel', { defaultMessage: 'On-failure processors (optional)', }), - // TODO: ES doesn't accept an empty array for on_failure serializer: onFailureProcessors => parseJson(onFailureProcessors), deserializer: onFailureProcessors => stringifyJson(onFailureProcessors), validations: [ { - validator: isJsonField( - i18n.translate('xpack.ingestPipelines.form.onFailureProcessorsJsonError', { - defaultMessage: 'The on-failure processors JSON is not valid.', - }) - ), + validator: ({ value }: ValidationFuncArg) => { + if (isJSON(value)) { + const parsedJSON = JSON.parse(value); + if (!parsedJSON.length) { + return { + message: 'At least one on-failure processor must be defined.', + }; + } + } else { + if (!isEmptyString(value)) { + return { + message: i18n.translate('xpack.ingestPipelines.form.onFailureProcessorsJsonError', { + defaultMessage: 'The on-failure processors JSON is not valid.', + }), + }; + } + } + }, }, ], }, diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx index 17917698c9f43..182511360c5fc 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx @@ -46,7 +46,7 @@ export const PipelinesCreate: React.FunctionComponent = ({

diff --git a/x-pack/plugins/ingest_pipelines/public/application/services/documentation.ts b/x-pack/plugins/ingest_pipelines/public/application/services/documentation.ts index d968d9762cdf8..78a9764be8e13 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/services/documentation.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/services/documentation.ts @@ -18,6 +18,14 @@ export class DocumentationService { public getIngestNodeUrl() { return `${this.esDocBasePath}/ingest.html`; } + + public getProcessorsUrl() { + return `${this.esDocBasePath}/ingest-processors.html`; + } + + public getHandlingFailureUrl() { + return `${this.esDocBasePath}/handling-failure-in-pipelines.html`; + } } export const documentationService = new DocumentationService(); diff --git a/x-pack/plugins/ingest_pipelines/public/shared_imports.ts b/x-pack/plugins/ingest_pipelines/public/shared_imports.ts index f98b7c785a195..e57e16dd87e72 100644 --- a/x-pack/plugins/ingest_pipelines/public/shared_imports.ts +++ b/x-pack/plugins/ingest_pipelines/public/shared_imports.ts @@ -23,6 +23,7 @@ export { useForm, Form, getUseField, + ValidationFuncArg, } from '../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; export { @@ -37,7 +38,10 @@ export { JsonEditorField, } from '../../../../src/plugins/es_ui_shared/static/forms/components'; -export { isJSON } from '../../../../src/plugins/es_ui_shared/static/validators/string'; +export { + isJSON, + isEmptyString, +} from '../../../../src/plugins/es_ui_shared/static/validators/string'; export { SectionLoading } from '../../../../src/plugins/es_ui_shared/public'; export const useKibana = () => _useKibana(); From 10b9210e968b6c0bee81f0312dc025fc6ed3ca2b Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Fri, 10 Apr 2020 13:55:46 -0400 Subject: [PATCH 08/11] fix todos --- .../components/pipeline_form/pipeline_form.tsx | 8 ++++---- .../sections/pipelines_create/pipelines_create.tsx | 4 ++-- x-pack/plugins/ingest_pipelines/public/shared_imports.ts | 1 + 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx index 6335e34e460af..1ee6665773a2e 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx @@ -24,7 +24,7 @@ import { SectionError } from '../section_error'; import { pipelineFormSchema } from './schema'; interface Props { - onSave: (pipeline: any) => void; // todo fix TS + onSave: (pipeline: Pipeline) => void; isSaving: boolean; saveError: any; defaultValue?: Pipeline; @@ -50,16 +50,16 @@ export const PipelineForm: React.FunctionComponent = ({ const [isVersionVisible, setIsVersionVisible] = useState(false); const [isOnFailureEditorVisible, setIsOnFailureEditorVisible] = useState(false); - const setDataAndValidation: FormConfig['onSubmit'] = (formData, isValid) => { + const handleSave: FormConfig['onSubmit'] = (formData, isValid) => { if (isValid) { - onSave(formData); + onSave(formData as Pipeline); } }; const { form } = useForm({ schema: pipelineFormSchema, defaultValue, - onSubmit: setDataAndValidation, + onSubmit: handleSave, }); return ( diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx index 182511360c5fc..5571bebfbac74 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx @@ -9,6 +9,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiPageBody, EuiPageContent, EuiSpacer, EuiTitle } from '@elastic/eui'; import { BASE_PATH } from '../../../../common/constants'; +import { Pipeline } from '../../../../common/types'; import { useKibana } from '../../../shared_imports'; import { PipelineForm } from '../../components'; @@ -18,8 +19,7 @@ export const PipelinesCreate: React.FunctionComponent = ({ const [isSaving, setIsSaving] = useState(false); const [saveError, setSaveError] = useState(null); - const onSave = async (pipeline: any) => { - // TODO fix TS + const onSave = async (pipeline: Pipeline) => { setIsSaving(true); setSaveError(null); diff --git a/x-pack/plugins/ingest_pipelines/public/shared_imports.ts b/x-pack/plugins/ingest_pipelines/public/shared_imports.ts index e57e16dd87e72..3067d06174ba7 100644 --- a/x-pack/plugins/ingest_pipelines/public/shared_imports.ts +++ b/x-pack/plugins/ingest_pipelines/public/shared_imports.ts @@ -24,6 +24,7 @@ export { Form, getUseField, ValidationFuncArg, + FormData, } from '../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; export { From 6bba3dacf16071f2ce0ac5519bfc2a38e684704f Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Mon, 13 Apr 2020 10:43:01 -0400 Subject: [PATCH 09/11] address review feedback --- .../components/pipeline_form/pipeline_form.tsx | 2 +- .../application/components/pipeline_form/schema.tsx | 8 ++++---- .../sections/pipelines_create/pipelines_create.tsx | 2 +- .../ingest_pipelines/public/application/services/api.ts | 2 +- .../plugins/ingest_pipelines/server/routes/api/create.ts | 2 +- .../apis/management/ingest_pipelines/ingest_pipelines.ts | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx index 1ee6665773a2e..855612c1582e3 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx @@ -204,7 +204,7 @@ export const PipelineForm: React.FunctionComponent = ({ <> diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/schema.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/schema.tsx index 47db507c0bf37..247922d2378dc 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/schema.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/schema.tsx @@ -74,8 +74,8 @@ export const pipelineFormSchema: FormSchema = { label: i18n.translate('xpack.ingestPipelines.form.processorsFieldLabel', { defaultMessage: 'Processors', }), - serializer: processors => parseJson(processors), - deserializer: processors => stringifyJson(processors), + serializer: parseJson, + deserializer: stringifyJson, validations: [ { validator: emptyField( @@ -97,8 +97,8 @@ export const pipelineFormSchema: FormSchema = { label: i18n.translate('xpack.ingestPipelines.form.onFailureFieldLabel', { defaultMessage: 'On-failure processors (optional)', }), - serializer: onFailureProcessors => parseJson(onFailureProcessors), - deserializer: onFailureProcessors => stringifyJson(onFailureProcessors), + serializer: parseJson, + deserializer: stringifyJson, validations: [ { validator: ({ value }: ValidationFuncArg) => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx index 5571bebfbac74..6589d57994dbe 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx @@ -46,7 +46,7 @@ export const PipelinesCreate: React.FunctionComponent = ({

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 4af86a38bb523..92673109b037e 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/services/api.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/services/api.ts @@ -57,7 +57,7 @@ export class ApiService { public async createPipeline(pipeline: Pipeline) { const result = await this.sendRequest({ path: API_BASE_PATH, - method: 'put', + method: 'post', body: JSON.stringify(pipeline), }); diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/create.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/create.ts index 013681fa2f4b7..cad29a2fe555d 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/create.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/create.ts @@ -23,7 +23,7 @@ export const registerCreateRoute = ({ license, lib: { isEsError }, }: RouteDependencies): void => { - router.put( + router.post( { path: API_BASE_PATH, validate: { diff --git a/x-pack/test/api_integration/apis/management/ingest_pipelines/ingest_pipelines.ts b/x-pack/test/api_integration/apis/management/ingest_pipelines/ingest_pipelines.ts index 2b2a64302d839..7c5a97f715869 100644 --- a/x-pack/test/api_integration/apis/management/ingest_pipelines/ingest_pipelines.ts +++ b/x-pack/test/api_integration/apis/management/ingest_pipelines/ingest_pipelines.ts @@ -23,7 +23,7 @@ export default function({ getService }: FtrProviderContext) { it('should create a pipeline', async () => { const { body } = await supertest - .put(API_BASE_PATH) + .post(API_BASE_PATH) .set('kbn-xsrf', 'xxx') .send({ name: PIPELINE_ID, @@ -54,7 +54,7 @@ export default function({ getService }: FtrProviderContext) { it('should not allow creation of an existing pipeline', async () => { const { body } = await supertest - .put(API_BASE_PATH) + .post(API_BASE_PATH) .set('kbn-xsrf', 'xxx') .send({ name: PIPELINE_ID, From ef25c2265b2102dcb04be7e33cabbb9735d49ba7 Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Tue, 14 Apr 2020 12:27:37 -0400 Subject: [PATCH 10/11] address feedback --- .../pipeline_form/pipeline_form.tsx | 9 ++++-- .../components/pipeline_form/schema.tsx | 4 +-- .../sections/pipelines_list/empty_list.tsx | 3 +- .../sections/pipelines_list/main.tsx | 2 +- .../application/services/breadcrumbs.ts | 30 +++++++++---------- 5 files changed, 26 insertions(+), 22 deletions(-) diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx index 855612c1582e3..59f1f659dadea 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx @@ -80,7 +80,12 @@ export const PipelineForm: React.FunctionComponent = ({ ) : null} - + {/* Name field with optional version field */} = ({ iconType="check" onClick={form.submit} data-test-subj="submitButton" - disabled={form.isValid === false} + disabled={form.isSubmitted && form.isValid === false} isLoading={isSaving} > { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/schema.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/schema.tsx index 247922d2378dc..4bc3e6a543206 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/schema.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/schema.tsx @@ -22,8 +22,8 @@ const { toInt } = fieldFormatters; const stringifyJson = (json: { [key: string]: unknown }): string => Array.isArray(json) ? JSON.stringify(json, null, 2) : '[\n\n]'; -const parseJson = (jsonString: string) => { - let parsedJSON; +const parseJson = (jsonString: string): object[] => { + let parsedJSON: any; try { parsedJSON = JSON.parse(jsonString); diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/empty_list.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/empty_list.tsx index c109334168da9..8f380913adb21 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/empty_list.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/empty_list.tsx @@ -6,6 +6,7 @@ import React, { FunctionComponent } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; +import { BASE_PATH } from '../../../../common/constants'; interface Props { onClick: () => void; @@ -22,7 +23,7 @@ export const EmptyList: FunctionComponent = ({ onClick }) => ( } actions={ - + {i18n.translate('xpack.ingestPipelines.list.table.emptyPrompt.createButtonLabel', { defaultMessage: 'Create pipeline', })} diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/main.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/main.tsx index 655b9cce620b6..40972aace12e8 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/main.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/main.tsx @@ -65,7 +65,7 @@ export const PipelinesList: React.FunctionComponent = () => { /> ); } else { - content = {}} />; + content = ; } return ( diff --git a/x-pack/plugins/ingest_pipelines/public/application/services/breadcrumbs.ts b/x-pack/plugins/ingest_pipelines/public/application/services/breadcrumbs.ts index 49885b3d28807..4d3d0d886e999 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/services/breadcrumbs.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/services/breadcrumbs.ts @@ -9,6 +9,10 @@ import { ManagementAppMountParams } from '../../../../../../src/plugins/manageme type SetBreadcrumbs = ManagementAppMountParams['setBreadcrumbs']; +const homeBreadcrumbText = i18n.translate('xpack.ingestPipelines.breadcrumb.pipelinesLabel', { + defaultMessage: 'Ingest Pipelines', +}); + export class BreadcrumbService { private breadcrumbs: { [key: string]: Array<{ @@ -16,24 +20,12 @@ export class BreadcrumbService { href?: string; }>; } = { - home: [], - }; - private setBreadcrumbsHandler?: SetBreadcrumbs; - - public setup(setBreadcrumbsHandler: SetBreadcrumbs): void { - const homeBreadcrumbText = i18n.translate('xpack.ingestPipelines.breadcrumb.pipelinesLabel', { - defaultMessage: 'Ingest Pipelines', - }); - - this.setBreadcrumbsHandler = setBreadcrumbsHandler; - - this.breadcrumbs.home = [ + home: [ { text: homeBreadcrumbText, }, - ]; - - this.breadcrumbs.create = [ + ], + create: [ { text: homeBreadcrumbText, href: `#${BASE_PATH}`, @@ -43,7 +35,13 @@ export class BreadcrumbService { defaultMessage: 'Create pipeline', }), }, - ]; + ], + }; + + private setBreadcrumbsHandler?: SetBreadcrumbs; + + public setup(setBreadcrumbsHandler: SetBreadcrumbs): void { + this.setBreadcrumbsHandler = setBreadcrumbsHandler; } public setBreadcrumbs(type: 'create' | 'home'): void { From cb8185a19ebaa79eb794d7d8e6e868bb0f6f2749 Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Tue, 14 Apr 2020 13:38:43 -0400 Subject: [PATCH 11/11] fix TS --- .../application/sections/pipelines_list/empty_list.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/empty_list.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/empty_list.tsx index 8f380913adb21..eacabb08eced3 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/empty_list.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/empty_list.tsx @@ -8,11 +8,7 @@ import { i18n } from '@kbn/i18n'; import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; import { BASE_PATH } from '../../../../common/constants'; -interface Props { - onClick: () => void; -} - -export const EmptyList: FunctionComponent = ({ onClick }) => ( +export const EmptyList: FunctionComponent = () => (