From c604eb9e63dcb7fe89b25bb4bc3b4a1b23e221d2 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Mon, 13 Apr 2020 08:00:15 -0600 Subject: [PATCH 01/11] [Maps] fix regression in loading left join fields (#63325) Co-authored-by: Elastic Machine --- .../maps/public/connected_components/layer_panel/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/index.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/index.js index b3ad3d1df68ce..256f52112ba97 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/index.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/index.js @@ -12,7 +12,7 @@ import { fitToLayerExtent, updateSourceProp } from '../../actions/map_actions'; function mapStateToProps(state = {}) { const selectedLayer = getSelectedLayer(state); return { - key: selectedLayer ? selectedLayer.getId() : '', + key: selectedLayer ? `${selectedLayer.getId()}${selectedLayer.isJoinable()}` : '', selectedLayer, }; } From 8cef9457c1f4d07bdf19fbc4679768f261699311 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Mon, 13 Apr 2020 10:39:40 -0400 Subject: [PATCH 02/11] [Ingest] Update Create datasource UI to fit in one page (#62858) --- .../enrollment_instructions/shell/index.tsx | 2 +- .../ingest_manager/components/header.tsx | 37 +- .../components/confirm_modal.tsx | 72 ++++ .../components/index.ts | 1 + .../components/layout.tsx | 175 +++++---- .../components/navigation.tsx | 85 ----- .../create_datasource_page/constants.ts | 7 - .../create_datasource_page/index.tsx | 348 +++++++++--------- .../step_configure_datasource.tsx | 188 +--------- .../step_define_datasource.tsx | 165 +++++++++ .../create_datasource_page/step_review.tsx | 202 ---------- .../step_select_config.tsx | 78 +--- .../step_select_package.tsx | 54 +-- .../agent_enrollment_flyout/instructions.tsx | 2 +- .../sections/fleet/agent_list_page/index.tsx | 4 +- .../translations/translations/ja-JP.json | 20 - .../translations/translations/zh-CN.json | 20 - 17 files changed, 550 insertions(+), 910 deletions(-) create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/confirm_modal.tsx delete mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/navigation.tsx create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_define_datasource.tsx delete mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_review.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/enrollment_instructions/shell/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/enrollment_instructions/shell/index.tsx index e6990927b926e..cb65e31fb74b5 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/enrollment_instructions/shell/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/enrollment_instructions/shell/index.tsx @@ -43,7 +43,7 @@ export const ShellEnrollmentInstructions: React.FunctionComponent = ({ // apiKey.api_key // } sh -c "$(curl ${kibanaUrl}/api/ingest_manager/fleet/install/${currentPlatform})"`; - const quickInstallInstructions = `./agent enroll ${kibanaUrl} ${apiKey.api_key}`; + const quickInstallInstructions = `./elastic-agent enroll ${kibanaUrl} ${apiKey.api_key}`; return ( <> diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/header.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/header.tsx index e1f29fdbeb323..1aab6d901a992 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/header.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/header.tsx @@ -7,14 +7,15 @@ import React, { memo } from 'react'; import styled from 'styled-components'; import { EuiFlexGroup, EuiFlexItem, EuiTabs, EuiTab, EuiSpacer } from '@elastic/eui'; import { Props as EuiTabProps } from '@elastic/eui/src/components/tabs/tab'; +import { EuiFlexItemProps } from '@elastic/eui/src/components/flex/flex_item'; const Container = styled.div` border-bottom: ${props => props.theme.eui.euiBorderThin}; background-color: ${props => props.theme.eui.euiPageBackgroundColor}; `; -const Wrapper = styled.div` - max-width: 1200px; +const Wrapper = styled.div<{ maxWidth?: number }>` + max-width: ${props => props.maxWidth || 1200}px; margin-left: auto; margin-right: auto; padding-top: ${props => props.theme.eui.paddingSizes.xl}; @@ -30,22 +31,36 @@ const Tabs = styled(EuiTabs)` `; export interface HeaderProps { + restrictHeaderWidth?: number; leftColumn?: JSX.Element; rightColumn?: JSX.Element; + rightColumnGrow?: EuiFlexItemProps['grow']; tabs?: EuiTabProps[]; } -const HeaderColumns: React.FC> = memo(({ leftColumn, rightColumn }) => ( - - {leftColumn ? {leftColumn} : null} - {rightColumn ? {rightColumn} : null} - -)); +const HeaderColumns: React.FC> = memo( + ({ leftColumn, rightColumn, rightColumnGrow }) => ( + + {leftColumn ? {leftColumn} : null} + {rightColumn ? {rightColumn} : null} + + ) +); -export const Header: React.FC = ({ leftColumn, rightColumn, tabs }) => ( +export const Header: React.FC = ({ + leftColumn, + rightColumn, + rightColumnGrow, + tabs, + restrictHeaderWidth, +}) => ( - - + + {tabs ? ( diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/confirm_modal.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/confirm_modal.tsx new file mode 100644 index 0000000000000..aa7eab8f5be8d --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/confirm_modal.tsx @@ -0,0 +1,72 @@ +/* + * 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 from 'react'; +import { EuiCallOut, EuiOverlayMask, EuiConfirmModal, EuiSpacer } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import { AgentConfig } from '../../../../types'; + +export const ConfirmCreateDatasourceModal: React.FunctionComponent<{ + onConfirm: () => void; + onCancel: () => void; + agentCount: number; + agentConfig: AgentConfig; +}> = ({ onConfirm, onCancel, agentCount, agentConfig }) => { + return ( + + + } + onCancel={onCancel} + onConfirm={onConfirm} + cancelButtonText={ + + } + confirmButtonText={ + + } + buttonColor="primary" + > + + {agentConfig.name}, + }} + /> + + + + + + ); +}; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/index.ts index 3bfca75668911..aa564690a6092 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/index.ts @@ -5,4 +5,5 @@ */ export { CreateDatasourcePageLayout } from './layout'; export { DatasourceInputPanel } from './datasource_input_panel'; +export { ConfirmCreateDatasourceModal } from './confirm_modal'; export { DatasourceInputVarField } from './datasource_input_var_field'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/layout.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/layout.tsx index dd242f366e8c0..73a7ba8ec119d 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/layout.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/layout.tsx @@ -19,108 +19,107 @@ import { WithHeaderLayout } from '../../../../layouts'; import { AgentConfig, PackageInfo } from '../../../../types'; import { PackageIcon } from '../../../../components/package_icon'; import { CreateDatasourceFrom, CreateDatasourceStep } from '../types'; -import { CreateDatasourceStepsNavigation } from './navigation'; export const CreateDatasourcePageLayout: React.FunctionComponent<{ from: CreateDatasourceFrom; basePath: string; cancelUrl: string; maxStep: CreateDatasourceStep | ''; - currentStep: CreateDatasourceStep; agentConfig?: AgentConfig; packageInfo?: PackageInfo; - restrictWidth?: number; -}> = ({ - from, - basePath, - cancelUrl, - maxStep, - currentStep, - agentConfig, - packageInfo, - restrictWidth, - children, -}) => { - return ( - - - +}> = ({ from, basePath, cancelUrl, maxStep, agentConfig, packageInfo, children }) => { + const leftColumn = ( + + + + + + + + +

+ +

+
+
+ + + + {from === 'config' ? ( + + ) : ( + + )} + + +
+ ); + const rightColumn = ( + + + + {agentConfig && from === 'config' ? ( + + + + + + {agentConfig?.name || '-'} + + + ) : null} + {packageInfo && from === 'package' ? ( + + -
-
- - -

- -

-
-
- - - - {agentConfig || from === 'config' ? ( + + + - - - - - - {agentConfig?.name || '-'} - - + - ) : null} - {packageInfo || from === 'package' ? ( - - - - - - - - - - - {packageInfo?.title || packageInfo?.name || '-'} - - - - + {packageInfo?.title || packageInfo?.name || '-'} - ) : null} - - -
- } - rightColumn={ - - } + + + + ) : null} + + + ); + + const maxWidth = 770; + return ( + {children} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/navigation.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/navigation.tsx deleted file mode 100644 index 7dae981e65c30..0000000000000 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/navigation.tsx +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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 from 'react'; -import styled from 'styled-components'; -import { useHistory } from 'react-router-dom'; -import { i18n } from '@kbn/i18n'; -import { EuiStepsHorizontal } from '@elastic/eui'; -import { CreateDatasourceFrom, CreateDatasourceStep } from '../types'; -import { WeightedCreateDatasourceSteps, CREATE_DATASOURCE_STEP_PATHS } from '../constants'; - -const StepsHorizontal = styled(EuiStepsHorizontal)` - background: none; -`; - -export const CreateDatasourceStepsNavigation: React.FunctionComponent<{ - from: CreateDatasourceFrom; - basePath: string; - maxStep: CreateDatasourceStep | ''; - currentStep: CreateDatasourceStep; -}> = ({ from, basePath, maxStep, currentStep }) => { - const history = useHistory(); - - const steps = [ - from === 'config' - ? { - title: i18n.translate('xpack.ingestManager.createDatasource.stepSelectPackageLabel', { - defaultMessage: 'Select integration', - }), - isSelected: currentStep === 'selectPackage', - isComplete: - WeightedCreateDatasourceSteps.indexOf('selectPackage') <= - WeightedCreateDatasourceSteps.indexOf(maxStep), - onClick: () => { - history.push(`${basePath}${CREATE_DATASOURCE_STEP_PATHS.selectPackage}`); - }, - } - : { - title: i18n.translate('xpack.ingestManager.createDatasource.stepSelectConfigLabel', { - defaultMessage: 'Select configuration', - }), - isSelected: currentStep === 'selectConfig', - isComplete: - WeightedCreateDatasourceSteps.indexOf('selectConfig') <= - WeightedCreateDatasourceSteps.indexOf(maxStep), - onClick: () => { - history.push(`${basePath}${CREATE_DATASOURCE_STEP_PATHS.selectConfig}`); - }, - }, - { - title: i18n.translate('xpack.ingestManager.createDatasource.stepConfigureDatasourceLabel', { - defaultMessage: 'Configure data source', - }), - isSelected: currentStep === 'configure', - isComplete: - WeightedCreateDatasourceSteps.indexOf('configure') <= - WeightedCreateDatasourceSteps.indexOf(maxStep), - disabled: - WeightedCreateDatasourceSteps.indexOf(maxStep) < - WeightedCreateDatasourceSteps.indexOf('configure') - 1, - onClick: () => { - history.push(`${basePath}${CREATE_DATASOURCE_STEP_PATHS.configure}`); - }, - }, - { - title: i18n.translate('xpack.ingestManager.createDatasource.stepReviewLabel', { - defaultMessage: 'Review', - }), - isSelected: currentStep === 'review', - isComplete: - WeightedCreateDatasourceSteps.indexOf('review') <= - WeightedCreateDatasourceSteps.indexOf(maxStep), - disabled: - WeightedCreateDatasourceSteps.indexOf(maxStep) < - WeightedCreateDatasourceSteps.indexOf('review') - 1, - onClick: () => { - history.push(`${basePath}${CREATE_DATASOURCE_STEP_PATHS.review}`); - }, - }, - ]; - - return ; -}; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/constants.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/constants.ts index eea18179560a1..49223a8eb4531 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/constants.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/constants.ts @@ -9,10 +9,3 @@ export const WeightedCreateDatasourceSteps = [ 'configure', 'review', ]; - -export const CREATE_DATASOURCE_STEP_PATHS = { - selectConfig: '/select-config', - selectPackage: '/select-package', - configure: '/configure', - review: '/review', -}; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/index.tsx index 461bb750ca6f5..1ad579d591b21 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/index.tsx @@ -3,45 +3,74 @@ * 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 { - useRouteMatch, - HashRouter as Router, - Switch, - Route, - Redirect, - useHistory, -} from 'react-router-dom'; +import React, { useState, useEffect } from 'react'; +import { useRouteMatch, useHistory } from 'react-router-dom'; +import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiButtonEmpty } from '@elastic/eui'; +import { + EuiButtonEmpty, + EuiButton, + EuiSteps, + EuiBottomBar, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, +} from '@elastic/eui'; +import { EuiStepProps } from '@elastic/eui/src/components/steps/step'; import { AGENT_CONFIG_DETAILS_PATH } from '../../../constants'; import { AgentConfig, PackageInfo, NewDatasource } from '../../../types'; -import { useLink, sendCreateDatasource } from '../../../hooks'; +import { + useLink, + sendCreateDatasource, + useCore, + useConfig, + sendGetAgentStatus, +} from '../../../hooks'; import { useLinks as useEPMLinks } from '../../epm/hooks'; -import { CreateDatasourcePageLayout } from './components'; +import { CreateDatasourcePageLayout, ConfirmCreateDatasourceModal } from './components'; import { CreateDatasourceFrom, CreateDatasourceStep } from './types'; -import { CREATE_DATASOURCE_STEP_PATHS } from './constants'; -import { DatasourceValidationResults, validateDatasource } from './services'; +import { DatasourceValidationResults, validateDatasource, validationHasErrors } from './services'; import { StepSelectPackage } from './step_select_package'; import { StepSelectConfig } from './step_select_config'; import { StepConfigureDatasource } from './step_configure_datasource'; -import { StepReviewDatasource } from './step_review'; + +import { StepDefineDatasource } from './step_define_datasource'; export const CreateDatasourcePage: React.FunctionComponent = () => { + const { notifications } = useCore(); + const { + fleet: { enabled: isFleetEnabled }, + } = useConfig(); const { params: { configId, pkgkey }, - path: matchPath, url: basePath, } = useRouteMatch(); const history = useHistory(); const from: CreateDatasourceFrom = configId ? 'config' : 'package'; const [maxStep, setMaxStep] = useState(''); - const [isSaving, setIsSaving] = useState(false); // Agent config and package info states const [agentConfig, setAgentConfig] = useState(); const [packageInfo, setPackageInfo] = useState(); + const agentConfigId = agentConfig?.id; + // Retrieve agent count + useEffect(() => { + const getAgentCount = async () => { + if (agentConfigId) { + const { data } = await sendGetAgentStatus({ configId: agentConfigId }); + if (data?.results.total) { + setAgentCount(data.results.total); + } + } + }; + + if (isFleetEnabled && agentConfigId) { + getAgentCount(); + } + }, [agentConfigId, isFleetEnabled]); + const [agentCount, setAgentCount] = useState(0); + // New datasource state const [datasource, setDatasource] = useState({ name: '', @@ -60,6 +89,7 @@ export const CreateDatasourcePage: React.FunctionComponent = () => { if (updatedPackageInfo) { setPackageInfo(updatedPackageInfo); } else { + setFormState('INVALID'); setPackageInfo(undefined); setMaxStep(''); } @@ -73,6 +103,7 @@ export const CreateDatasourcePage: React.FunctionComponent = () => { if (updatedAgentConfig) { setAgentConfig(updatedAgentConfig); } else { + setFormState('INVALID'); setAgentConfig(undefined); setMaxStep(''); } @@ -81,6 +112,8 @@ export const CreateDatasourcePage: React.FunctionComponent = () => { console.debug('Agent config updated', updatedAgentConfig); }; + const hasErrors = validationResults ? validationHasErrors(validationResults) : false; + // Update datasource method const updateDatasource = (updatedFields: Partial) => { const newDatasource = { @@ -88,9 +121,18 @@ export const CreateDatasourcePage: React.FunctionComponent = () => { ...updatedFields, }; setDatasource(newDatasource); + // eslint-disable-next-line no-console console.debug('Datasource updated', newDatasource); - updateDatasourceValidation(newDatasource); + const newValidationResults = updateDatasourceValidation(newDatasource); + const hasPackage = newDatasource.package; + const hasValidationErrors = newValidationResults + ? validationHasErrors(newValidationResults) + : false; + const hasAgentConfig = newDatasource.config_id && newDatasource.config_id !== ''; + if (hasPackage && hasAgentConfig && !hasValidationErrors) { + setFormState('VALID'); + } }; const updateDatasourceValidation = (newDatasource?: NewDatasource) => { @@ -99,6 +141,8 @@ export const CreateDatasourcePage: React.FunctionComponent = () => { setValidationResults(newValidationResult); // eslint-disable-next-line no-console console.debug('Datasource validation results', newValidationResult); + + return newValidationResult; } }; @@ -112,34 +156,37 @@ export const CreateDatasourcePage: React.FunctionComponent = () => { }); const cancelUrl = from === 'config' ? CONFIG_URL : PACKAGE_URL; - // Redirect to first step - const redirectToFirstStep = - from === 'config' ? ( - - ) : ( - - ); - - // Url to first and second steps - const SELECT_PACKAGE_URL = useLink(`${basePath}${CREATE_DATASOURCE_STEP_PATHS.selectPackage}`); - const SELECT_CONFIG_URL = useLink(`${basePath}${CREATE_DATASOURCE_STEP_PATHS.selectConfig}`); - const CONFIGURE_DATASOURCE_URL = useLink(`${basePath}${CREATE_DATASOURCE_STEP_PATHS.configure}`); - const firstStepUrl = from === 'config' ? SELECT_PACKAGE_URL : SELECT_CONFIG_URL; - const secondStepUrl = CONFIGURE_DATASOURCE_URL; - - // Redirect to second step - const redirectToSecondStep = ( - - ); - // Save datasource + const [formState, setFormState] = useState< + 'VALID' | 'INVALID' | 'CONFIRM' | 'LOADING' | 'SUBMITTED' + >('INVALID'); const saveDatasource = async () => { - setIsSaving(true); + setFormState('LOADING'); const result = await sendCreateDatasource(datasource); - setIsSaving(false); + setFormState('SUBMITTED'); return result; }; + const onSubmit = async () => { + if (formState === 'VALID' && hasErrors) { + setFormState('INVALID'); + return; + } + if (agentCount !== 0 && formState !== 'CONFIRM') { + setFormState('CONFIRM'); + return; + } + const { error } = await saveDatasource(); + if (!error) { + history.push(`${AGENT_CONFIG_DETAILS_PATH}${agentConfig ? agentConfig.id : configId}`); + } else { + notifications.toasts.addError(error, { + title: 'Error', + }); + setFormState('VALID'); + } + }; + const layoutProps = { from, basePath, @@ -147,135 +194,108 @@ export const CreateDatasourcePage: React.FunctionComponent = () => { maxStep, agentConfig, packageInfo, - restrictWidth: 770, }; - return ( - - - {/* Redirect to first step from `/` */} - {from === 'config' ? ( - + ), + } + : { + title: i18n.translate('xpack.ingestManager.createDatasource.stepSelectPackageTitle', { + defaultMessage: 'Select an integration', + }), + children: ( + + ), + }, + { + title: i18n.translate('xpack.ingestManager.createDatasource.stepDefineDatasourceTitle', { + defaultMessage: 'Define your data source', + }), + status: !packageInfo || !agentConfig ? 'disabled' : undefined, + children: + agentConfig && packageInfo ? ( + - ) : ( - - )} - - {/* First step, either render select package or select config depending on entry */} - {from === 'config' ? ( - - - { - setMaxStep('selectPackage'); - history.push(`${basePath}${CREATE_DATASOURCE_STEP_PATHS.configure}`); - }} - /> - - - ) : ( - - - { - setMaxStep('selectConfig'); - history.push(`${basePath}${CREATE_DATASOURCE_STEP_PATHS.configure}`); - }} - /> - - - )} - - {/* Second step to configure data source, redirect to first step if agent config */} - {/* or package info isn't defined (i.e. after full page reload) */} - - - {!agentConfig || !packageInfo ? ( - redirectToFirstStep - ) : ( - - {from === 'config' ? ( - - ) : ( - - )} - - } - cancelUrl={cancelUrl} - onNext={() => { - setMaxStep('configure'); - history.push(`${basePath}${CREATE_DATASOURCE_STEP_PATHS.review}`); - }} + ) : null, + }, + ]; + return ( + + {formState === 'CONFIRM' && agentConfig && ( + setFormState('VALID')} + /> + )} + + + + + + + - )} - - - - {/* Third step to review, redirect to second step if data source name is missing */} - {/* (i.e. after full page reload) */} - - - {!agentConfig || !datasource.name ? ( - redirectToSecondStep - ) : ( - - - - } - onSubmit={async () => { - const { error } = await saveDatasource(); - if (!error) { - history.push( - `${AGENT_CONFIG_DETAILS_PATH}${agentConfig ? agentConfig.id : configId}` - ); - } else { - // TODO: Handle save datasource error - } - }} + + + + + - )} - - - - + + + + + ); }; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_configure_datasource.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_configure_datasource.tsx index 105d6c66a5704..843891b63ca01 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_configure_datasource.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_configure_datasource.tsx @@ -3,23 +3,18 @@ * 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, { useEffect, useState, Fragment } from 'react'; -import { i18n } from '@kbn/i18n'; +import React, { useEffect } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { - EuiSteps, EuiPanel, EuiFlexGroup, EuiFlexItem, - EuiFormRow, - EuiButtonEmpty, EuiSpacer, EuiEmptyPrompt, EuiText, - EuiButton, - EuiComboBox, EuiCallOut, } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { AgentConfig, PackageInfo, @@ -30,7 +25,7 @@ import { import { Loading } from '../../../components'; import { packageToConfigDatasourceInputs } from '../../../services'; import { DatasourceValidationResults, validationHasErrors } from './services'; -import { DatasourceInputPanel, DatasourceInputVarField } from './components'; +import { DatasourceInputPanel } from './components'; export const StepConfigureDatasource: React.FunctionComponent<{ agentConfig: AgentConfig; @@ -38,24 +33,17 @@ export const StepConfigureDatasource: React.FunctionComponent<{ datasource: NewDatasource; updateDatasource: (fields: Partial) => void; validationResults: DatasourceValidationResults; - backLink: JSX.Element; - cancelUrl: string; - onNext: () => void; + submitAttempted: boolean; }> = ({ agentConfig, packageInfo, datasource, updateDatasource, validationResults, - backLink, - cancelUrl, - onNext, + submitAttempted, }) => { // Form show/hide states - const [isShowingAdvancedDefine, setIsShowingAdvancedDefine] = useState(false); - // Form submit state - const [submitAttempted, setSubmitAttempted] = useState(false); const hasErrors = validationResults ? validationHasErrors(validationResults) : false; // Update datasource's package and config info @@ -95,107 +83,6 @@ export const StepConfigureDatasource: React.FunctionComponent<{ } }, [datasource.package, datasource.config_id, agentConfig, packageInfo, updateDatasource]); - // Step A, define datasource - const renderDefineDatasource = () => ( - - - - { - updateDatasource({ - name: newValue, - }); - }} - errors={validationResults!.name} - forceShowErrors={submitAttempted} - /> - - - { - updateDatasource({ - description: newValue, - }); - }} - errors={validationResults!.description} - forceShowErrors={submitAttempted} - /> - - - - setIsShowingAdvancedDefine(!isShowingAdvancedDefine)} - > - - - {/* Todo: Populate list of existing namespaces */} - {isShowingAdvancedDefine ? ( - - - - - - } - > - { - updateDatasource({ - namespace: newNamespace, - }); - }} - onChange={(newNamespaces: Array<{ label: string }>) => { - updateDatasource({ - namespace: newNamespaces.length ? newNamespaces[0].label : '', - }); - }} - /> - - - - - - ) : null} - - ); - // Step B, configure inputs (and their streams) // Assume packages only export one datasource for now const renderConfigureInputs = () => @@ -252,41 +139,10 @@ export const StepConfigureDatasource: React.FunctionComponent<{ return validationResults ? ( - - - - - {backLink} - - - - - - - + {renderConfigureInputs()} {hasErrors && submitAttempted ? ( + ) : null} - - - - - - - - - { - setSubmitAttempted(true); - if (!hasErrors) { - onNext(); - } - }} - > - - - - - ) : ( diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_define_datasource.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_define_datasource.tsx new file mode 100644 index 0000000000000..792389381eaf0 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_define_datasource.tsx @@ -0,0 +1,165 @@ +/* + * 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, { useEffect, useState, Fragment } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiFlexGrid, + EuiFlexItem, + EuiFormRow, + EuiFieldText, + EuiButtonEmpty, + EuiSpacer, + EuiText, + EuiComboBox, +} from '@elastic/eui'; +import { AgentConfig, PackageInfo, Datasource, NewDatasource } from '../../../types'; +import { packageToConfigDatasourceInputs } from '../../../services'; + +export const StepDefineDatasource: React.FunctionComponent<{ + agentConfig: AgentConfig; + packageInfo: PackageInfo; + datasource: NewDatasource; + updateDatasource: (fields: Partial) => void; +}> = ({ agentConfig, packageInfo, datasource, updateDatasource }) => { + // Form show/hide states + const [isShowingAdvancedDefine, setIsShowingAdvancedDefine] = useState(false); + + // Update datasource's package and config info + useEffect(() => { + const dsPackage = datasource.package; + const currentPkgKey = dsPackage ? `${dsPackage.name}-${dsPackage.version}` : ''; + const pkgKey = `${packageInfo.name}-${packageInfo.version}`; + + // If package has changed, create shell datasource with input&stream values based on package info + if (currentPkgKey !== pkgKey) { + // Existing datasources on the agent config using the package name, retrieve highest number appended to datasource name + const dsPackageNamePattern = new RegExp(`${packageInfo.name}-(\\d+)`); + const dsWithMatchingNames = (agentConfig.datasources as Datasource[]) + .filter(ds => Boolean(ds.name.match(dsPackageNamePattern))) + .map(ds => parseInt(ds.name.match(dsPackageNamePattern)![1], 10)) + .sort(); + + updateDatasource({ + name: `${packageInfo.name}-${ + dsWithMatchingNames.length ? dsWithMatchingNames[dsWithMatchingNames.length - 1] + 1 : 1 + }`, + package: { + name: packageInfo.name, + title: packageInfo.title, + version: packageInfo.version, + }, + inputs: packageToConfigDatasourceInputs(packageInfo), + }); + } + + // If agent config has changed, update datasource's config ID and namespace + if (datasource.config_id !== agentConfig.id) { + updateDatasource({ + config_id: agentConfig.id, + namespace: agentConfig.namespace, + }); + } + }, [datasource.package, datasource.config_id, agentConfig, packageInfo, updateDatasource]); + + return ( + <> + + + + } + > + + updateDatasource({ + name: e.target.value, + }) + } + /> + + + + + } + labelAppend={ + + + + } + > + + updateDatasource({ + description: e.target.value, + }) + } + /> + + + + + setIsShowingAdvancedDefine(!isShowingAdvancedDefine)} + > + + + {/* Todo: Populate list of existing namespaces */} + {isShowingAdvancedDefine ? ( + + + + + + } + > + { + updateDatasource({ + namespace: newNamespace, + }); + }} + onChange={(newNamespaces: Array<{ label: string }>) => { + updateDatasource({ + namespace: newNamespaces.length ? newNamespaces[0].label : '', + }); + }} + /> + + + + + ) : null} + + ); +}; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_review.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_review.tsx deleted file mode 100644 index 20af5954c1436..0000000000000 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_review.tsx +++ /dev/null @@ -1,202 +0,0 @@ -/* - * 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, { Fragment, useState, useEffect } from 'react'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { - EuiFlexGroup, - EuiFlexItem, - EuiButtonEmpty, - EuiButton, - EuiTitle, - EuiCallOut, - EuiText, - EuiCheckbox, - EuiTabbedContent, - EuiCodeBlock, - EuiSpacer, -} from '@elastic/eui'; -import { dump } from 'js-yaml'; -import { NewDatasource, AgentConfig } from '../../../types'; -import { useConfig, sendGetAgentStatus } from '../../../hooks'; -import { storedDatasourceToAgentDatasource } from '../../../services'; - -const KEYS_TO_SINK = ['inputs', 'streams']; - -export const StepReviewDatasource: React.FunctionComponent<{ - agentConfig: AgentConfig; - datasource: NewDatasource; - backLink: JSX.Element; - cancelUrl: string; - onSubmit: () => void; - isSubmitLoading: boolean; -}> = ({ agentConfig, datasource, backLink, cancelUrl, onSubmit, isSubmitLoading }) => { - // Agent count info states - const [agentCount, setAgentCount] = useState(0); - const [agentCountChecked, setAgentCountChecked] = useState(false); - - // Config information - const { - fleet: { enabled: isFleetEnabled }, - } = useConfig(); - - // Retrieve agent count - useEffect(() => { - const getAgentCount = async () => { - const { data } = await sendGetAgentStatus({ configId: agentConfig.id }); - if (data?.results.total) { - setAgentCount(data.results.total); - } - }; - - if (isFleetEnabled) { - getAgentCount(); - } - }, [agentConfig.id, isFleetEnabled]); - - const showAgentDisclaimer = isFleetEnabled && agentCount; - const fullAgentDatasource = storedDatasourceToAgentDatasource(datasource); - - return ( - - - - - -

- -

-
-
- {backLink} -
-
- - {/* Agents affected warning */} - {showAgentDisclaimer ? ( - - - } - > - -

- {agentConfig.name}, - }} - /> -

-
-
-
- ) : null} - - {/* Preview and YAML view */} - {/* TODO: Implement preview tab */} - - - - - {dump(fullAgentDatasource, { - sortKeys: (a: string, b: string) => { - // Make YAML output prettier by sinking certain fields - if (KEYS_TO_SINK.indexOf(a) > -1) { - return 1; - } - if (KEYS_TO_SINK.indexOf(b) > -1) { - return -1; - } - return 0; - }, - })} - - - ), - }, - ]} - /> - - - {/* Confirm agents affected */} - {showAgentDisclaimer ? ( - - - - -

- -

-
-
- - - } - checked={agentCountChecked} - onChange={e => setAgentCountChecked(e.target.checked)} - /> - -
-
- ) : null} - - - - - - - - - - onSubmit()} - isLoading={isSubmitLoading} - disabled={isSubmitLoading || Boolean(showAgentDisclaimer && !agentCountChecked)} - > - - - - - -
- ); -}; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_select_config.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_select_config.tsx index 2ddfc170069a1..6cbe56e628903 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_select_config.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_select_config.tsx @@ -6,19 +6,8 @@ import React, { useEffect, useState, Fragment } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { - EuiButtonEmpty, - EuiButton, - EuiFlexGroup, - EuiFlexItem, - EuiTitle, - EuiSelectable, - EuiSpacer, - EuiTextColor, -} from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiSelectable, EuiSpacer, EuiTextColor } from '@elastic/eui'; import { Error } from '../../../components'; -import { AGENT_CONFIG_PATH } from '../../../constants'; -import { useCapabilities, useLink } from '../../../hooks'; import { AgentConfig, PackageInfo, GetAgentConfigsResponseItem } from '../../../types'; import { useGetPackageInfoByKey, useGetAgentConfigs, sendGetOneAgentConfig } from '../../../hooks'; @@ -27,20 +16,13 @@ export const StepSelectConfig: React.FunctionComponent<{ updatePackageInfo: (packageInfo: PackageInfo | undefined) => void; agentConfig: AgentConfig | undefined; updateAgentConfig: (config: AgentConfig | undefined) => void; - cancelUrl: string; - onNext: () => void; -}> = ({ pkgkey, updatePackageInfo, agentConfig, updateAgentConfig, cancelUrl, onNext }) => { - const hasWriteCapabilites = useCapabilities().write; +}> = ({ pkgkey, updatePackageInfo, agentConfig, updateAgentConfig }) => { // Selected config state const [selectedConfigId, setSelectedConfigId] = useState( agentConfig ? agentConfig.id : undefined ); - const [selectedConfigLoading, setSelectedConfigLoading] = useState(false); const [selectedConfigError, setSelectedConfigError] = useState(); - // Todo: replace with create agent config flyout - const CREATE_NEW_CONFIG_URI = useLink(AGENT_CONFIG_PATH); - // Fetch package info const { data: packageInfoData, error: packageInfoError } = useGetPackageInfoByKey(pkgkey); @@ -70,9 +52,7 @@ export const StepSelectConfig: React.FunctionComponent<{ useEffect(() => { const fetchAgentConfigInfo = async () => { if (selectedConfigId) { - setSelectedConfigLoading(true); const { data, error } = await sendGetOneAgentConfig(selectedConfigId); - setSelectedConfigLoading(false); if (error) { setSelectedConfigError(error); updateAgentConfig(undefined); @@ -122,33 +102,6 @@ export const StepSelectConfig: React.FunctionComponent<{ return ( - - - - -

- -

-
-
- - - - - -
-
) : null} - - - - - - - - - onNext()} - > - - - - -
); }; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_select_package.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_select_package.tsx index 496e1d3c0fd7b..8dabb3bc98110 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_select_package.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_select_package.tsx @@ -6,15 +6,7 @@ import React, { useEffect, useState, Fragment } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { - EuiButtonEmpty, - EuiButton, - EuiFlexGroup, - EuiFlexItem, - EuiTitle, - EuiSelectable, - EuiSpacer, -} from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiSelectable, EuiSpacer } from '@elastic/eui'; import { Error } from '../../../components'; import { AgentConfig, PackageInfo } from '../../../types'; import { useGetOneAgentConfig, useGetPackages, sendGetPackageInfoByKey } from '../../../hooks'; @@ -25,14 +17,11 @@ export const StepSelectPackage: React.FunctionComponent<{ updateAgentConfig: (config: AgentConfig | undefined) => void; packageInfo?: PackageInfo; updatePackageInfo: (packageInfo: PackageInfo | undefined) => void; - cancelUrl: string; - onNext: () => void; -}> = ({ agentConfigId, updateAgentConfig, packageInfo, updatePackageInfo, cancelUrl, onNext }) => { +}> = ({ agentConfigId, updateAgentConfig, packageInfo, updatePackageInfo }) => { // Selected package state const [selectedPkgKey, setSelectedPkgKey] = useState( packageInfo ? `${packageInfo.name}-${packageInfo.version}` : undefined ); - const [selectedPkgLoading, setSelectedPkgLoading] = useState(false); const [selectedPkgError, setSelectedPkgError] = useState(); // Fetch agent config info @@ -57,9 +46,7 @@ export const StepSelectPackage: React.FunctionComponent<{ useEffect(() => { const fetchPackageInfo = async () => { if (selectedPkgKey) { - setSelectedPkgLoading(true); const { data, error } = await sendGetPackageInfoByKey(selectedPkgKey); - setSelectedPkgLoading(false); if (error) { setSelectedPkgError(error); updatePackageInfo(undefined); @@ -109,16 +96,6 @@ export const StepSelectPackage: React.FunctionComponent<{ return ( - - -

- -

-
-
) : null} - - - - - - - - - onNext()} - > - - - - -
); }; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/components/agent_enrollment_flyout/instructions.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/components/agent_enrollment_flyout/instructions.tsx index 1bc20c2baf660..a0244c601cd96 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/components/agent_enrollment_flyout/instructions.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/components/agent_enrollment_flyout/instructions.tsx @@ -86,7 +86,7 @@ export const EnrollmentInstructions: React.FunctionComponent = ({ selecte steps={[ { title: i18n.translate('xpack.ingestManager.agentEnrollment.stepSetupAgents', { - defaultMessage: 'Setup Beats agent', + defaultMessage: 'Setup Elastic agent', }), children: ( = () => {

} @@ -373,7 +373,7 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { setIsEnrollmentFlyoutOpen(true)}> ) : null diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index e579830bad203..3cebde793a085 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -8283,7 +8283,6 @@ "xpack.ingestManager.agentConfigForm.systemMonitoringFieldLabel": "オプション", "xpack.ingestManager.agentConfigForm.systemMonitoringText": "システムメトリックを収集", "xpack.ingestManager.agentConfigForm.systemMonitoringTooltipText": "このオプションを有効にすると、システムメトリックと情報を収集するデータソースで構成をブートストラップできます。", - "xpack.ingestManager.agentConfigInfo.yamlTabName": "YAML", "xpack.ingestManager.agentConfigList.actionsColumnTitle": "アクション", "xpack.ingestManager.agentConfigList.actionsMenuText": "開く", "xpack.ingestManager.agentConfigList.addButton": "エージェント構成を作成", @@ -8421,21 +8420,14 @@ "xpack.ingestManager.createAgentConfig.flyoutTitleDescription": "エージェント構成は、エージェントのグループ全体にわたる設定を管理する目的で使用されます。エージェント構成にデータソースを追加すると、エージェントで収集するデータを指定できます。エージェント構成の編集時には、フリートを使用して、指定したエージェントのグループに更新をデプロイできます。", "xpack.ingestManager.createAgentConfig.submitButtonLabel": "エージェント構成を作成", "xpack.ingestManager.createAgentConfig.successNotificationTitle": "エージェント構成「{name}」を作成しました", - "xpack.ingestManager.createDatasource.addDatasourceButtonText": "データソースに構成を追加", "xpack.ingestManager.createDatasource.agentConfigurationNameLabel": "構成", "xpack.ingestManager.createDatasource.cancelLinkText": "キャンセル", - "xpack.ingestManager.createDatasource.changeConfigLinkText": "構成を変更", - "xpack.ingestManager.createDatasource.changePackageLinkText": "パッケージを変更", - "xpack.ingestManager.createDatasource.continueButtonText": "続行", - "xpack.ingestManager.createDatasource.editDatasourceLinkText": "データソースを編集", "xpack.ingestManager.createDatasource.packageNameLabel": "パッケージ", "xpack.ingestManager.createDatasource.pageTitle": "データソースを作成", "xpack.ingestManager.createDatasource.stepConfigure.advancedOptionsToggleLinkText": "高度なオプション", - "xpack.ingestManager.createDatasource.stepConfigure.chooseDataTitle": "収集したいデータを選択してください", "xpack.ingestManager.createDatasource.stepConfigure.datasourceDescriptionInputLabel": "説明", "xpack.ingestManager.createDatasource.stepConfigure.datasourceNameInputLabel": "データソース名", "xpack.ingestManager.createDatasource.stepConfigure.datasourceNamespaceInputLabel": "名前空間", - "xpack.ingestManager.createDatasource.stepConfigure.defineDatasourceTitle": "データソースを定義する", "xpack.ingestManager.createDatasource.stepConfigure.hideStreamsAriaLabel": "{type} ストリームを隠す", "xpack.ingestManager.createDatasource.stepConfigure.inputSettingsDescription": "次の設定はすべてのストリームに適用されます。", "xpack.ingestManager.createDatasource.stepConfigure.inputSettingsTitle": "設定", @@ -8444,27 +8436,15 @@ "xpack.ingestManager.createDatasource.stepConfigure.showStreamsAriaLabel": "{type} ストリームを表示", "xpack.ingestManager.createDatasource.stepConfigure.streamsEnabledCountText": "{count} / {total, plural, one {# ストリーム} other {# ストリーム}}が有効です", "xpack.ingestManager.createDatasource.stepConfigure.toggleAdvancedOptionsButtonText": "高度なオプション", - "xpack.ingestManager.createDatasource.stepConfigureDatasourceLabel": "構成データソース", - "xpack.ingestManager.createDatasource.stepReview.agentsAffectedCalloutText": "選択されたエージェント構成 {configName} が一部のエージェントで既に使用されていることをフリートが検出しました。このアクションの結果として、フリートはこの構成に登録されているすべてのエージェントを更新します。", - "xpack.ingestManager.createDatasource.stepReview.agentsAffectedCalloutTitle": "このアクションは {count, plural, one {# エージェント} other {# エージェント}}に影響します", - "xpack.ingestManager.createDatasource.stepReview.confirmAgentDisclaimerText": "このアクションによって、この構成に登録されているすべてのエージェントが更新されることを理解しています。", - "xpack.ingestManager.createDatasource.stepReview.confirmAgentDisclaimerTitle": "意思決定を確認", - "xpack.ingestManager.createDatasource.stepReview.reviewTitle": "変更の見直し", - "xpack.ingestManager.createDatasource.stepReviewLabel": "見直し", "xpack.ingestManager.createDatasource.StepSelectConfig.agentConfigAgentsCountText": "{count, plural, one {# エージェント} other {# エージェント}}", - "xpack.ingestManager.createDatasource.StepSelectConfig.createNewConfigButtonText": "新しい構成を作成", "xpack.ingestManager.createDatasource.StepSelectConfig.errorLoadingAgentConfigsTitle": "エージェント構成の読み込みエラー", "xpack.ingestManager.createDatasource.StepSelectConfig.errorLoadingPackageTitle": "パッケージ情報の読み込みエラー", "xpack.ingestManager.createDatasource.StepSelectConfig.errorLoadingSelectedAgentConfigTitle": "選択したエージェント構成の読み込みエラー", "xpack.ingestManager.createDatasource.StepSelectConfig.filterAgentConfigsInputPlaceholder": "エージェント構成の検索", - "xpack.ingestManager.createDatasource.StepSelectConfig.selectAgentConfigTitle": "エージェント構成を選択する", - "xpack.ingestManager.createDatasource.stepSelectConfigLabel": "構成を選択", "xpack.ingestManager.createDatasource.stepSelectPackage.errorLoadingConfigTitle": "エージェント構成情報の読み込みエラー", "xpack.ingestManager.createDatasource.stepSelectPackage.errorLoadingPackagesTitle": "パッケージの読み込みエラー", "xpack.ingestManager.createDatasource.stepSelectPackage.errorLoadingSelectedPackageTitle": "選択したパッケージの読み込みエラー", "xpack.ingestManager.createDatasource.stepSelectPackage.filterPackagesInputPlaceholder": "パッケージの検索", - "xpack.ingestManager.createDatasource.stepSelectPackage.selectPackageTitle": "パッケージを選択する", - "xpack.ingestManager.createDatasource.stepSelectPackageLabel": "パッケージを選択", "xpack.ingestManager.deleteAgentConfigs.confirmModal.affectedAgentsMessage": "{agentsCount, plural, one {# エージェントを} other {# エージェントを}}{agentConfigsCount, plural, one {このエージェント構成に} other {これらのエージェント構成に}}割り当てました。 {agentsCount, plural, one {このエージェント} other {これらのエージェント}}の登録が解除されます。", "xpack.ingestManager.deleteAgentConfigs.confirmModal.cancelButtonLabel": "キャンセル", "xpack.ingestManager.deleteAgentConfigs.confirmModal.confirmAndReassignButtonLabel": "{agentConfigsCount, plural, one {エージェント構成} other {エージェント構成}} and unenroll {agentsCount, plural, one {エージェント} other {エージェント}} を削除", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 75f48fb11823a..1daf66117e948 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -8286,7 +8286,6 @@ "xpack.ingestManager.agentConfigForm.systemMonitoringFieldLabel": "可选", "xpack.ingestManager.agentConfigForm.systemMonitoringText": "收集系统指标", "xpack.ingestManager.agentConfigForm.systemMonitoringTooltipText": "启用此选项可使用收集系统指标和信息的数据源启动您的配置。", - "xpack.ingestManager.agentConfigInfo.yamlTabName": "YAML", "xpack.ingestManager.agentConfigList.actionsColumnTitle": "操作", "xpack.ingestManager.agentConfigList.actionsMenuText": "打开", "xpack.ingestManager.agentConfigList.addButton": "创建代理配置", @@ -8424,21 +8423,14 @@ "xpack.ingestManager.createAgentConfig.flyoutTitleDescription": "代理配置用于管理整个代理组的设置。您可以将数据源添加到代理配置以指定代理收集的数据。编辑代理配置时,可以使用 Fleet 将更新部署到指定的代理组。", "xpack.ingestManager.createAgentConfig.submitButtonLabel": "创建代理配置", "xpack.ingestManager.createAgentConfig.successNotificationTitle": "代理配置“{name}”已创建", - "xpack.ingestManager.createDatasource.addDatasourceButtonText": "将数据源添加到配置", "xpack.ingestManager.createDatasource.agentConfigurationNameLabel": "配置", "xpack.ingestManager.createDatasource.cancelLinkText": "取消", - "xpack.ingestManager.createDatasource.changeConfigLinkText": "更改配置", - "xpack.ingestManager.createDatasource.changePackageLinkText": "更改软件包", - "xpack.ingestManager.createDatasource.continueButtonText": "继续", - "xpack.ingestManager.createDatasource.editDatasourceLinkText": "编辑数据源", "xpack.ingestManager.createDatasource.packageNameLabel": "软件包", "xpack.ingestManager.createDatasource.pageTitle": "创建数据源", "xpack.ingestManager.createDatasource.stepConfigure.advancedOptionsToggleLinkText": "高级选项", - "xpack.ingestManager.createDatasource.stepConfigure.chooseDataTitle": "选择要收集的数据", "xpack.ingestManager.createDatasource.stepConfigure.datasourceDescriptionInputLabel": "描述", "xpack.ingestManager.createDatasource.stepConfigure.datasourceNameInputLabel": "数据源名称", "xpack.ingestManager.createDatasource.stepConfigure.datasourceNamespaceInputLabel": "命名空间", - "xpack.ingestManager.createDatasource.stepConfigure.defineDatasourceTitle": "定义您的数据源", "xpack.ingestManager.createDatasource.stepConfigure.hideStreamsAriaLabel": "隐藏 {type} 流", "xpack.ingestManager.createDatasource.stepConfigure.inputSettingsDescription": "以下设置适用于所有流。", "xpack.ingestManager.createDatasource.stepConfigure.inputSettingsTitle": "设置", @@ -8447,27 +8439,15 @@ "xpack.ingestManager.createDatasource.stepConfigure.showStreamsAriaLabel": "显示 {type} 流", "xpack.ingestManager.createDatasource.stepConfigure.streamsEnabledCountText": "{count} / {total, plural, one {# 个流} other {# 个流}}已启用", "xpack.ingestManager.createDatasource.stepConfigure.toggleAdvancedOptionsButtonText": "高级选项", - "xpack.ingestManager.createDatasource.stepConfigureDatasourceLabel": "配置数据源", - "xpack.ingestManager.createDatasource.stepReview.agentsAffectedCalloutText": "Fleet 检测到所选代理配置 {configName} 已由您的部分代理使用。此操作的结果是,Fleet 将更新用此配置进行注册的所有代理。", - "xpack.ingestManager.createDatasource.stepReview.agentsAffectedCalloutTitle": "此操作将影响 {count, plural, one {# 个代理} other {# 个代理}}", - "xpack.ingestManager.createDatasource.stepReview.confirmAgentDisclaimerText": "我理解此操作将更新注册到此配置的所有代理。", - "xpack.ingestManager.createDatasource.stepReview.confirmAgentDisclaimerTitle": "确认您的决定", - "xpack.ingestManager.createDatasource.stepReview.reviewTitle": "复查更改", - "xpack.ingestManager.createDatasource.stepReviewLabel": "复查", "xpack.ingestManager.createDatasource.StepSelectConfig.agentConfigAgentsCountText": "{count, plural, one {# 个代理} other {# 个代理}}", - "xpack.ingestManager.createDatasource.StepSelectConfig.createNewConfigButtonText": "创建新配置", "xpack.ingestManager.createDatasource.StepSelectConfig.errorLoadingAgentConfigsTitle": "加载代理配置时出错", "xpack.ingestManager.createDatasource.StepSelectConfig.errorLoadingPackageTitle": "加载软件包信息时出错", "xpack.ingestManager.createDatasource.StepSelectConfig.errorLoadingSelectedAgentConfigTitle": "加载选定代理配置时出错", "xpack.ingestManager.createDatasource.StepSelectConfig.filterAgentConfigsInputPlaceholder": "搜索代理配置", - "xpack.ingestManager.createDatasource.StepSelectConfig.selectAgentConfigTitle": "选择代理配置", - "xpack.ingestManager.createDatasource.stepSelectConfigLabel": "选择配置", "xpack.ingestManager.createDatasource.stepSelectPackage.errorLoadingConfigTitle": "加载代理配置信息时出错", "xpack.ingestManager.createDatasource.stepSelectPackage.errorLoadingPackagesTitle": "加载软件包时出错", "xpack.ingestManager.createDatasource.stepSelectPackage.errorLoadingSelectedPackageTitle": "加载选定软件包时出错", "xpack.ingestManager.createDatasource.stepSelectPackage.filterPackagesInputPlaceholder": "搜索软件包", - "xpack.ingestManager.createDatasource.stepSelectPackage.selectPackageTitle": "选择软件包", - "xpack.ingestManager.createDatasource.stepSelectPackageLabel": "选择软件包", "xpack.ingestManager.deleteAgentConfigs.confirmModal.affectedAgentsMessage": "{agentsCount, plural, one {# 个代理} other {# 个代理}}已分配{agentConfigsCount, plural, one {给此代理配置} other {给这些代理配置}}。将取消注册{agentsCount, plural, one {此代理} other {这些代理}}。", "xpack.ingestManager.deleteAgentConfigs.confirmModal.cancelButtonLabel": "取消", "xpack.ingestManager.deleteAgentConfigs.confirmModal.confirmAndReassignButtonLabel": "删除{agentConfigsCount, plural, one {代理配置} other {代理配置}}并取消注册{agentsCount, plural, one {代理} other {代理}}", From d00c90510d11e4575a3453b3437221b341a7478f Mon Sep 17 00:00:00 2001 From: Robert Austin Date: Mon, 13 Apr 2020 11:33:14 -0400 Subject: [PATCH 03/11] Endpoint: Move files, add README, replace implicit `any` with stricter types. Reorganize types. (#63262) * Removed `FIXME` comments and references to private repos. Please use Github issues to track work * Added a README describing the modules in `applications/endpoint` * Removed dead code * Moved `AppRoot` component to its own module * Moved `applications/endpoint/services` under `store` * Moved some exported types to `applications/endpoint/types` * Moved all React code to `view` * Added types in some places that were implicitly `any` * Moved `PageId` type from common directory (where it could be shared with the server) to the public directory --- .../plugins/endpoint/common/generate_data.ts | 1 - x-pack/plugins/endpoint/common/types.ts | 5 -- .../public/applications/endpoint/README.md | 28 +++++++++ .../endpoint/components/truncate_text.ts | 13 ---- .../public/applications/endpoint/index.tsx | 51 +--------------- .../endpoint/store/alerts/index.ts | 1 - .../store/policy_details/middleware.ts | 14 +++-- .../endpoint/store/policy_list/middleware.ts | 4 +- .../policy_list}/services/ingest.test.ts | 2 +- .../policy_list}/services/ingest.ts | 32 +++------- .../endpoint/store/routing/action.ts | 4 +- .../public/applications/endpoint/types.ts | 27 ++++++++- .../applications/endpoint/view/app_root.tsx | 59 +++++++++++++++++++ .../__snapshots__/link_to_app.test.tsx.snap | 0 .../__snapshots__/page_view.test.tsx.snap | 0 .../components/header_navigation.tsx} | 7 ++- .../components/link_to_app.test.tsx | 17 ++++-- .../{ => view}/components/link_to_app.tsx | 2 +- .../{ => view}/components/page_view.test.tsx | 2 +- .../{ => view}/components/page_view.tsx | 0 .../use_navigate_to_app_event_handler.ts | 2 +- .../endpoint/view/hosts/details.tsx | 2 +- .../endpoint/view/hosts/index.test.tsx | 1 - .../endpoint/view/policy/policy_details.tsx | 5 +- .../endpoint/view/policy/policy_list.tsx | 4 +- .../applications/endpoint/view/use_page_id.ts | 2 +- 26 files changed, 161 insertions(+), 124 deletions(-) create mode 100644 x-pack/plugins/endpoint/public/applications/endpoint/README.md delete mode 100644 x-pack/plugins/endpoint/public/applications/endpoint/components/truncate_text.ts rename x-pack/plugins/endpoint/public/applications/endpoint/{ => store/policy_list}/services/ingest.test.ts (95%) rename x-pack/plugins/endpoint/public/applications/endpoint/{ => store/policy_list}/services/ingest.ts (77%) create mode 100644 x-pack/plugins/endpoint/public/applications/endpoint/view/app_root.tsx rename x-pack/plugins/endpoint/public/applications/endpoint/{ => view}/components/__snapshots__/link_to_app.test.tsx.snap (100%) rename x-pack/plugins/endpoint/public/applications/endpoint/{ => view}/components/__snapshots__/page_view.test.tsx.snap (100%) rename x-pack/plugins/endpoint/public/applications/endpoint/{components/header_nav.tsx => view/components/header_navigation.tsx} (90%) rename x-pack/plugins/endpoint/public/applications/endpoint/{ => view}/components/link_to_app.test.tsx (86%) rename x-pack/plugins/endpoint/public/applications/endpoint/{ => view}/components/link_to_app.tsx (96%) rename x-pack/plugins/endpoint/public/applications/endpoint/{ => view}/components/page_view.test.tsx (96%) rename x-pack/plugins/endpoint/public/applications/endpoint/{ => view}/components/page_view.tsx (100%) rename x-pack/plugins/endpoint/public/applications/endpoint/{ => view}/hooks/use_navigate_to_app_event_handler.ts (96%) diff --git a/x-pack/plugins/endpoint/common/generate_data.ts b/x-pack/plugins/endpoint/common/generate_data.ts index 7c24bd9d77148..3f783d90e577d 100644 --- a/x-pack/plugins/endpoint/common/generate_data.ts +++ b/x-pack/plugins/endpoint/common/generate_data.ts @@ -7,7 +7,6 @@ import uuid from 'uuid'; import seedrandom from 'seedrandom'; import { AlertEvent, EndpointEvent, HostMetadata, OSFields, HostFields } from './types'; -// FIXME: move types/model to top-level // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { PolicyData } from '../public/applications/endpoint/types'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths diff --git a/x-pack/plugins/endpoint/common/types.ts b/x-pack/plugins/endpoint/common/types.ts index a614526d92a3f..403ca9832e191 100644 --- a/x-pack/plugins/endpoint/common/types.ts +++ b/x-pack/plugins/endpoint/common/types.ts @@ -375,11 +375,6 @@ export interface EndpointEvent { export type ResolverEvent = EndpointEvent | LegacyEndpointEvent; -/** - * The PageId type is used for the payload when firing userNavigatedToPage actions - */ -export type PageId = 'alertsPage' | 'managementPage' | 'policyListPage'; - /** * Takes a @kbn/config-schema 'schema' type and returns a type that represents valid inputs. * Similar to `TypeOf`, but allows strings as input for `schema.number()` (which is inline diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/README.md b/x-pack/plugins/endpoint/public/applications/endpoint/README.md new file mode 100644 index 0000000000000..25bfd615d1d2c --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/README.md @@ -0,0 +1,28 @@ +# Endpoint application +This application provides the user interface for the Elastic Endpoint + +# Architecture +The application consists of a _view_ written in React and a _model_ written in Redux. + +# Modules +We structure the modules to match the architecture. `view` contains the _view_ (all React) code. `store` contains the _model_. + +This section covers the conventions of each top level module. + +# `mocks` +This contains helper code for unit tests. + +## `models` +This contains domain models. By convention, each submodule here contains methods for a single type. Domain model classes would also live here. + +## `store` +This contains the _model_ of the application. All Redux and Redux middleware code (including API interactions) happen here. This module also contains the types and interfaces defining Redux actions. Each action type or interface should be commented and if it has fields, each field should be commented. Comments should be of `tsdoc` style. + +## `view` +This contains the code which renders elements to the DOM. All React code goes here. + +## `index.tsx` +This exports `renderApp` which instantiates the React view with the _model_. + +## `types.ts` +This contains the types and interfaces. All `export`ed types or interfaces (except ones defining Redux actions, which live in `store`) should be here. Each type or interface should have a `tsdoc` style comment. Interfaces should have `tsdoc` comments on each field and types which have fields should do the same. diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/components/truncate_text.ts b/x-pack/plugins/endpoint/public/applications/endpoint/components/truncate_text.ts deleted file mode 100644 index 83f4bc1e79317..0000000000000 --- a/x-pack/plugins/endpoint/public/applications/endpoint/components/truncate_text.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * 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 styled from 'styled-components'; - -export const TruncateText = styled.div` - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; -`; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/index.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/index.tsx index 82ac95160519c..a1999c056bf59 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/index.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/index.tsx @@ -6,19 +6,10 @@ import * as React from 'react'; import ReactDOM from 'react-dom'; -import { CoreStart, AppMountParameters, ScopedHistory } from 'kibana/public'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { Route, Switch } from 'react-router-dom'; -import { Store } from 'redux'; +import { CoreStart, AppMountParameters } from 'kibana/public'; import { EndpointPluginStartDependencies } from '../../plugin'; import { appStoreFactory } from './store'; -import { AlertIndex } from './view/alerts'; -import { HostList } from './view/hosts'; -import { PolicyList } from './view/policy'; -import { PolicyDetails } from './view/policy'; -import { HeaderNavigation } from './components/header_nav'; -import { AppRootProvider } from './view/app_root_provider'; -import { Setup } from './view/setup'; +import { AppRoot } from './view/app_root'; /** * This module will be loaded asynchronously to reduce the bundle size of your plugin's main bundle. @@ -37,41 +28,3 @@ export function renderApp( ReactDOM.unmountComponentAtNode(element); }; } - -interface RouterProps { - history: ScopedHistory; - store: Store; - coreStart: CoreStart; - depsStart: EndpointPluginStartDependencies; -} - -const AppRoot: React.FunctionComponent = React.memo( - ({ history, store, coreStart, depsStart }) => { - return ( - - - - - ( -

- -

- )} - /> - - - - - ( - - )} - /> -
-
- ); - } -); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/index.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/index.ts index f63910a1c305e..5545218d9abd6 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/index.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/index.ts @@ -6,4 +6,3 @@ export { alertListReducer } from './reducer'; export { AlertAction } from './action'; -export * from '../../types'; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/middleware.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/middleware.ts index 18248e272aada..a00ce255cbac4 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/middleware.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/middleware.ts @@ -4,15 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import { MiddlewareFactory, PolicyData, PolicyDetailsState } from '../../types'; +import { + MiddlewareFactory, + PolicyData, + PolicyDetailsState, + UpdateDatasourceResponse, +} from '../../types'; import { policyIdFromParams, isOnPolicyDetailsPage, policyDetails } from './selectors'; +import { generatePolicy } from '../../models/policy'; import { sendGetDatasource, sendGetFleetAgentStatusForConfig, sendPutDatasource, - UpdateDatasourceResponse, -} from '../../services/ingest'; -import { generatePolicy } from '../../models/policy'; +} from '../policy_list/services/ingest'; export const policyDetailsMiddlewareFactory: MiddlewareFactory = coreStart => { const http = coreStart.http; @@ -35,7 +39,6 @@ export const policyDetailsMiddlewareFactory: MiddlewareFactory = coreStart => { const http = coreStart.http; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/services/ingest.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/services/ingest.test.ts similarity index 95% rename from x-pack/plugins/endpoint/public/applications/endpoint/services/ingest.test.ts rename to x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/services/ingest.test.ts index a2c1dfbe09a48..c2865d36c95f2 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/services/ingest.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/services/ingest.test.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { httpServiceMock } from '../../../../../../../src/core/public/mocks'; import { sendGetDatasource, sendGetEndpointSpecificDatasources } from './ingest'; +import { httpServiceMock } from '../../../../../../../../../src/core/public/mocks'; describe('ingest service', () => { let http: ReturnType; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/services/ingest.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/services/ingest.ts similarity index 77% rename from x-pack/plugins/endpoint/public/applications/endpoint/services/ingest.ts rename to x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/services/ingest.ts index 583ebc55d896b..16c885f26f0a4 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/services/ingest.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/services/ingest.ts @@ -6,37 +6,21 @@ import { HttpFetchOptions, HttpStart } from 'kibana/public'; import { - CreateDatasourceResponse, - GetAgentStatusResponse, GetDatasourcesRequest, -} from '../../../../../ingest_manager/common/types/rest_spec'; -import { NewPolicyData, PolicyData } from '../types'; + GetAgentStatusResponse, +} from '../../../../../../../ingest_manager/common'; +import { + NewPolicyData, + GetDatasourcesResponse, + GetDatasourceResponse, + UpdateDatasourceResponse, +} from '../../../types'; const INGEST_API_ROOT = `/api/ingest_manager`; const INGEST_API_DATASOURCES = `${INGEST_API_ROOT}/datasources`; const INGEST_API_FLEET = `${INGEST_API_ROOT}/fleet`; const INGEST_API_FLEET_AGENT_STATUS = `${INGEST_API_FLEET}/agent-status`; -// FIXME: Import from ingest after - https://github.com/elastic/kibana/issues/60677 -export interface GetDatasourcesResponse { - items: PolicyData[]; - total: number; - page: number; - perPage: number; - success: boolean; -} - -// FIXME: Import from Ingest after - https://github.com/elastic/kibana/issues/60677 -export interface GetDatasourceResponse { - item: PolicyData; - success: boolean; -} - -// FIXME: Import from Ingest after - https://github.com/elastic/kibana/issues/60677 -export type UpdateDatasourceResponse = CreateDatasourceResponse & { - item: PolicyData; -}; - /** * Retrieves a list of endpoint specific datasources (those created with a `package.name` of * `endpoint`) from Ingest diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/routing/action.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/routing/action.ts index c7e9970e58c30..f22272bc68233 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/routing/action.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/routing/action.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PageId, Immutable } from '../../../../../common/types'; -import { EndpointAppLocation } from '../alerts'; +import { Immutable } from '../../../../../common/types'; +import { EndpointAppLocation, PageId } from '../../types'; interface UserNavigatedToPage { readonly type: 'userNavigatedToPage'; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts index dda50847169e7..f9ad8f6708f6b 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts @@ -18,7 +18,10 @@ import { EndpointPluginStartDependencies } from '../../plugin'; import { AppAction } from './store/action'; import { CoreStart } from '../../../../../../src/core/public'; import { Datasource, NewDatasource } from '../../../../ingest_manager/common/types/models'; -import { GetAgentStatusResponse } from '../../../../ingest_manager/common/types/rest_spec'; +import { + GetAgentStatusResponse, + CreateDatasourceResponse, +} from '../../../../ingest_manager/common/types/rest_spec'; export { AppAction }; export type MiddlewareFactory = ( @@ -317,3 +320,25 @@ export interface AlertingIndexUIQueryParams { date_range?: string; filters?: string; } + +export interface GetDatasourcesResponse { + items: PolicyData[]; + total: number; + page: number; + perPage: number; + success: boolean; +} + +export interface GetDatasourceResponse { + item: PolicyData; + success: boolean; +} + +export type UpdateDatasourceResponse = CreateDatasourceResponse & { + item: PolicyData; +}; + +/** + * The PageId type is used for the payload when firing userNavigatedToPage actions + */ +export type PageId = 'alertsPage' | 'managementPage' | 'policyListPage'; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/app_root.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/app_root.tsx new file mode 100644 index 0000000000000..f9634c63deefb --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/app_root.tsx @@ -0,0 +1,59 @@ +/* + * 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 * as React from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { Route, Switch } from 'react-router-dom'; +import { Store } from 'redux'; +import { AlertIndex } from './alerts'; +import { HostList } from './hosts'; +import { PolicyList } from './policy'; +import { PolicyDetails } from './policy'; +import { HeaderNavigation } from './components/header_navigation'; +import { AppRootProvider } from './app_root_provider'; +import { Setup } from './setup'; +import { EndpointPluginStartDependencies } from '../../../plugin'; +import { ScopedHistory, CoreStart } from '../../../../../../../src/core/public'; + +interface RouterProps { + history: ScopedHistory; + store: Store; + coreStart: CoreStart; + depsStart: EndpointPluginStartDependencies; +} + +/** + * The root of the Endpoint application react view. + */ +export const AppRoot: React.FunctionComponent = React.memo( + ({ history, store, coreStart, depsStart }) => { + return ( + + + + + ( +

+ +

+ )} + /> + + + + + ( + + )} + /> +
+
+ ); + } +); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/components/__snapshots__/link_to_app.test.tsx.snap b/x-pack/plugins/endpoint/public/applications/endpoint/view/components/__snapshots__/link_to_app.test.tsx.snap similarity index 100% rename from x-pack/plugins/endpoint/public/applications/endpoint/components/__snapshots__/link_to_app.test.tsx.snap rename to x-pack/plugins/endpoint/public/applications/endpoint/view/components/__snapshots__/link_to_app.test.tsx.snap diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/components/__snapshots__/page_view.test.tsx.snap b/x-pack/plugins/endpoint/public/applications/endpoint/view/components/__snapshots__/page_view.test.tsx.snap similarity index 100% rename from x-pack/plugins/endpoint/public/applications/endpoint/components/__snapshots__/page_view.test.tsx.snap rename to x-pack/plugins/endpoint/public/applications/endpoint/view/components/__snapshots__/page_view.test.tsx.snap diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/components/header_nav.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/components/header_navigation.tsx similarity index 90% rename from x-pack/plugins/endpoint/public/applications/endpoint/components/header_nav.tsx rename to x-pack/plugins/endpoint/public/applications/endpoint/view/components/header_navigation.tsx index 3fb06d6b4a56e..6c294d9c86548 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/components/header_nav.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/components/header_navigation.tsx @@ -8,15 +8,16 @@ import React, { MouseEvent, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiTabs, EuiTab } from '@elastic/eui'; import { useHistory, useLocation } from 'react-router-dom'; -import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; +import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; +import { Immutable } from '../../../../../common/types'; -export interface NavTabs { +interface NavTabs { name: string; id: string; href: string; } -export const navTabs: NavTabs[] = [ +const navTabs: Immutable = [ { id: 'home', name: i18n.translate('xpack.endpoint.headerNav.home', { diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/components/link_to_app.test.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/components/link_to_app.test.tsx similarity index 86% rename from x-pack/plugins/endpoint/public/applications/endpoint/components/link_to_app.test.tsx rename to x-pack/plugins/endpoint/public/applications/endpoint/view/components/link_to_app.test.tsx index 902c215434aac..d0a8f9690dafb 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/components/link_to_app.test.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/components/link_to_app.test.tsx @@ -7,9 +7,14 @@ import React from 'react'; import { mount } from 'enzyme'; import { LinkToApp } from './link_to_app'; -import { KibanaContextProvider } from '../../../../../../../src/plugins/kibana_react/public'; -import { coreMock } from '../../../../../../../src/core/public/mocks'; import { CoreStart } from 'kibana/public'; +import { KibanaContextProvider } from '../../../../../../../../src/plugins/kibana_react/public'; +import { coreMock } from '../../../../../../../../src/core/public/mocks'; + +type LinkToAppOnClickMock = jest.Mock< + Return, + [React.MouseEvent] +>; describe('LinkToApp component', () => { let fakeCoreStart: jest.Mocked; @@ -38,7 +43,8 @@ describe('LinkToApp component', () => { ).toMatchSnapshot(); }); it('should support onClick prop', () => { - const spyOnClickHandler = jest.fn(); + // Take `_event` (even though it is not used) so that `jest.fn` will have a type that expects to be called with an event + const spyOnClickHandler: LinkToAppOnClickMock = jest.fn(_event => {}); const renderResult = render( link @@ -91,7 +97,8 @@ describe('LinkToApp component', () => { }); }); it('should still preventDefault if onClick callback throws', () => { - const spyOnClickHandler = jest.fn(ev => { + // Take `_event` (even though it is not used) so that `jest.fn` will have a type that expects to be called with an event + const spyOnClickHandler: LinkToAppOnClickMock = jest.fn(_event => { throw new Error('test'); }); const renderResult = render( @@ -104,7 +111,7 @@ describe('LinkToApp component', () => { expect(clickEventArg.isDefaultPrevented()).toBe(true); }); it('should not navigate if onClick callback prevents defalut', () => { - const spyOnClickHandler = jest.fn(ev => { + const spyOnClickHandler: LinkToAppOnClickMock = jest.fn(ev => { ev.preventDefault(); }); const renderResult = render( diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/components/link_to_app.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/components/link_to_app.tsx similarity index 96% rename from x-pack/plugins/endpoint/public/applications/endpoint/components/link_to_app.tsx rename to x-pack/plugins/endpoint/public/applications/endpoint/view/components/link_to_app.tsx index 858dac864b58a..6a3cf5e78f4bf 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/components/link_to_app.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/components/link_to_app.tsx @@ -9,7 +9,7 @@ import { EuiLink } from '@elastic/eui'; import { EuiLinkProps } from '@elastic/eui'; import { useNavigateToAppEventHandler } from '../hooks/use_navigate_to_app_event_handler'; -export type LinkToAppProps = EuiLinkProps & { +type LinkToAppProps = EuiLinkProps & { /** the app id - normally the value of the `id` in that plugin's `kibana.json` */ appId: string; /** Any app specific path (route) */ diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/components/page_view.test.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/components/page_view.test.tsx similarity index 96% rename from x-pack/plugins/endpoint/public/applications/endpoint/components/page_view.test.tsx rename to x-pack/plugins/endpoint/public/applications/endpoint/view/components/page_view.test.tsx index 0d4d26737d355..4007477a088fa 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/components/page_view.test.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/components/page_view.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { mount } from 'enzyme'; import { PageView } from './page_view'; -import { EuiThemeProvider } from '../../../../../../legacy/common/eui_styled_components'; +import { EuiThemeProvider } from '../../../../../../../legacy/common/eui_styled_components'; describe('PageView component', () => { const render = (ui: Parameters[0]) => diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/components/page_view.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/components/page_view.tsx similarity index 100% rename from x-pack/plugins/endpoint/public/applications/endpoint/components/page_view.tsx rename to x-pack/plugins/endpoint/public/applications/endpoint/view/components/page_view.tsx diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/hooks/use_navigate_to_app_event_handler.ts b/x-pack/plugins/endpoint/public/applications/endpoint/view/hooks/use_navigate_to_app_event_handler.ts similarity index 96% rename from x-pack/plugins/endpoint/public/applications/endpoint/hooks/use_navigate_to_app_event_handler.ts rename to x-pack/plugins/endpoint/public/applications/endpoint/view/hooks/use_navigate_to_app_event_handler.ts index 5fbfa5e0e58a8..ec9a8691c481e 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/hooks/use_navigate_to_app_event_handler.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hooks/use_navigate_to_app_event_handler.ts @@ -6,7 +6,7 @@ import { MouseEventHandler, useCallback } from 'react'; import { ApplicationStart } from 'kibana/public'; -import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; +import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; type NavigateToAppHandlerProps = Parameters; type EventHandlerCallback = MouseEventHandler; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details.tsx index 90829f7ad4cbe..f51349b24933a 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details.tsx @@ -28,7 +28,7 @@ import { useHostListSelector } from './hooks'; import { urlFromQueryParams } from './url_from_query_params'; import { FormattedDateAndTime } from '../formatted_date_time'; import { uiQueryParams, detailsData, detailsError } from './../../store/hosts/selectors'; -import { LinkToApp } from '../../components/link_to_app'; +import { LinkToApp } from '../components/link_to_app'; const HostIds = styled(EuiListGroupItem)` margin-top: 0; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx index c3ff41268e3db..d2d0ad40b025f 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx @@ -151,7 +151,6 @@ describe('when on the hosts page', () => { }); it('should navigate to logs without full page refresh', async () => { - // FIXME: this is not working :( expect(coreStart.application.navigateToApp.mock.calls).toHaveLength(1); }); }); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_details.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_details.tsx index 1e723e32615eb..267077da6598c 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_details.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_details.tsx @@ -29,7 +29,7 @@ import { isLoading, apiError, } from '../../store/policy_details/selectors'; -import { PageView, PageViewHeaderTitle } from '../../components/page_view'; +import { PageView, PageViewHeaderTitle } from '../components/page_view'; import { AppAction } from '../../types'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; import { AgentsSummary } from './agents_summary'; @@ -82,7 +82,7 @@ export const PolicyDetails = React.memo(() => { } }, [notifications.toasts, policyItem, policyName, policyUpdateStatus]); - const handleBackToListOnClick = useCallback( + const handleBackToListOnClick: React.MouseEventHandler = useCallback( ev => { ev.preventDefault(); history.push(`/policy`); @@ -161,7 +161,6 @@ export const PolicyDetails = React.memo(() => { fill={true} iconType="save" data-test-subj="policyDetailsSaveButton" - // FIXME: need to disable if User has no write permissions to ingest - see: https://github.com/elastic/endpoint-app-team/issues/296 onClick={handleSaveOnClick} isLoading={isPolicyLoading} > diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_list.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_list.tsx index 5ee1539ce9788..06ba74aa46732 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_list.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_list.tsx @@ -23,8 +23,8 @@ import { usePolicyListSelector } from './policy_hooks'; import { PolicyListAction } from '../../store/policy_list'; import { PolicyData } from '../../types'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; -import { PageView } from '../../components/page_view'; -import { LinkToApp } from '../../components/link_to_app'; +import { PageView } from '../components/page_view'; +import { LinkToApp } from '../components/link_to_app'; interface TableChangeCallbackArguments { page: { index: number; size: number }; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/use_page_id.ts b/x-pack/plugins/endpoint/public/applications/endpoint/view/use_page_id.ts index 49c39064c8d9a..85ed8a39fb386 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/use_page_id.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/use_page_id.ts @@ -6,8 +6,8 @@ import { useEffect } from 'react'; import { useDispatch } from 'react-redux'; -import { PageId } from '../../../../common/types'; import { RoutingAction } from '../store/routing'; +import { PageId } from '../types'; /** * Dispatches a 'userNavigatedToPage' action with the given 'pageId' as the action payload. From d5fc944b4bccdd987f05e5fcdcc5d8289eecfd92 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Mon, 13 Apr 2020 18:59:48 +0300 Subject: [PATCH 04/11] Index pattern management UI -> TypeScript (source_filters_table) (#62925) * [WIP] Index pattern management UI -> TypeScript (source_filters_table) --- .../source_filters_table.test.tsx.snap} | 73 ++------ .../add_filter.test.tsx.snap} | 0 .../components/add_filter/add_filter.js | 73 -------- ...add_filter.test.js => add_filter.test.tsx} | 21 +-- .../components/add_filter/add_filter.tsx | 63 +++++++ .../add_filter/{index.js => index.ts} | 0 .../confirmation_modal.test.tsx.snap | 37 ++++ .../confirmation_modal.test.tsx | 37 ++++ .../confirmation_modal/confirmation_modal.tsx | 76 ++++++++ .../components/confirmation_modal/index.ts | 20 +++ .../header.test.tsx.snap} | 4 +- .../header.test.js => header.test.tsx} | 2 +- .../header/{header.js => header.tsx} | 5 +- .../components/header/{index.js => index.ts} | 0 .../source_filters_table/components/index.ts | 23 +++ .../table.test.tsx.snap} | 37 ++-- .../components/table/{index.js => index.ts} | 0 .../table.test.js => table.test.tsx} | 146 ++++++++------- .../components/table/{table.js => table.tsx} | 169 ++++++++++-------- .../{index.js => index.ts} | 0 ....test.js => source_filters_table.test.tsx} | 82 +++++---- ...ters_table.js => source_filters_table.tsx} | 138 ++++++-------- .../source_filters_table/types.ts | 24 +++ 23 files changed, 617 insertions(+), 413 deletions(-) rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/{__jest__/__snapshots__/source_filters_table.test.js.snap => __snapshots__/source_filters_table.test.tsx.snap} (82%) rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/{__jest__/__snapshots__/add_filter.test.js.snap => __snapshots__/add_filter.test.tsx.snap} (100%) delete mode 100644 src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/add_filter.js rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/{__jest__/add_filter.test.js => add_filter.test.tsx} (66%) create mode 100644 src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/add_filter.tsx rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/{index.js => index.ts} (100%) create mode 100644 src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/confirmation_modal/__snapshots__/confirmation_modal.test.tsx.snap create mode 100644 src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/confirmation_modal/confirmation_modal.test.tsx create mode 100644 src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/confirmation_modal/confirmation_modal.tsx create mode 100644 src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/confirmation_modal/index.ts rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/{__jest__/__snapshots__/header.test.js.snap => __snapshots__/header.test.tsx.snap} (98%) rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/{__jest__/header.test.js => header.test.tsx} (95%) rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/{header.js => header.tsx} (99%) rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/{index.js => index.ts} (100%) create mode 100644 src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/index.ts rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/{__jest__/__snapshots__/table.test.js.snap => __snapshots__/table.test.tsx.snap} (77%) rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/{index.js => index.ts} (100%) rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/{__jest__/table.test.js => table.test.tsx} (69%) rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/{table.js => table.tsx} (54%) rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/{index.js => index.ts} (100%) rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/{__jest__/source_filters_table.test.js => source_filters_table.test.tsx} (64%) rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/{source_filters_table.js => source_filters_table.tsx} (56%) create mode 100644 src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/types.ts diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/__jest__/__snapshots__/source_filters_table.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/__snapshots__/source_filters_table.test.tsx.snap similarity index 82% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/__jest__/__snapshots__/source_filters_table.test.js.snap rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/__snapshots__/source_filters_table.test.tsx.snap index 52fccb8607a83..a7b73624c4665 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/__jest__/__snapshots__/source_filters_table.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/__snapshots__/source_filters_table.test.tsx.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`SourceFiltersTable should add a filter 1`] = ` -
+
-
+ `; exports[`SourceFiltersTable should filter based on the query bar 1`] = ` -
+
-
+ `; exports[`SourceFiltersTable should remove a filter 1`] = ` -
+
-
+ `; exports[`SourceFiltersTable should render normally 1`] = ` -
+
-
+ `; exports[`SourceFiltersTable should should a loading indicator when saving 1`] = ` -
+
-
+ `; exports[`SourceFiltersTable should show a delete modal 1`] = ` -
+
- - - } - confirmButtonText={ - - } - onCancel={[Function]} - onConfirm={[Function]} - title={ - - } - /> - -
+ + `; exports[`SourceFiltersTable should update a filter 1`] = ` -
+
-
+ `; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/__jest__/__snapshots__/add_filter.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/__snapshots__/add_filter.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/__jest__/__snapshots__/add_filter.test.js.snap rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/__snapshots__/add_filter.test.tsx.snap diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/add_filter.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/add_filter.js deleted file mode 100644 index 2124b76b3a915..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/add_filter.js +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; - -import { EuiFlexGroup, EuiFlexItem, EuiFieldText, EuiButton } from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; - -export class AddFilter extends Component { - static propTypes = { - onAddFilter: PropTypes.func.isRequired, - }; - - constructor(props) { - super(props); - this.state = { - filter: '', - }; - } - - onAddFilter = () => { - this.props.onAddFilter(this.state.filter); - this.setState({ filter: '' }); - }; - - render() { - const { filter } = this.state; - const placeholder = i18n.translate('kbn.management.editIndexPattern.sourcePlaceholder', { - defaultMessage: - "source filter, accepts wildcards (e.g., `user*` to filter fields starting with 'user')", - }); - - return ( - - - this.setState({ filter: e.target.value.trim() })} - placeholder={placeholder} - /> - - - - - - - - ); - } -} diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/__jest__/add_filter.test.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/add_filter.test.tsx similarity index 66% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/__jest__/add_filter.test.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/add_filter.test.tsx index 915d9490db045..1ebaa3eaf89f8 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/__jest__/add_filter.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/add_filter.test.tsx @@ -18,33 +18,30 @@ */ import React from 'react'; -import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers'; +import { shallow } from 'enzyme'; -import { AddFilter } from '../add_filter'; +import { AddFilter } from './add_filter'; describe('AddFilter', () => { - it('should render normally', async () => { - const component = shallowWithI18nProvider( {}} />); + test('should render normally', () => { + const component = shallow( {}} />); expect(component).toMatchSnapshot(); }); - it('should allow adding a filter', async () => { + test('should allow adding a filter', async () => { const onAddFilter = jest.fn(); - const component = shallowWithI18nProvider(); + const component = shallow(); - // Set a value in the input field - component.setState({ filter: 'tim*' }); - - // Click the button + component.find('EuiFieldText').simulate('change', { target: { value: 'tim*' } }); component.find('EuiButton').simulate('click'); component.update(); expect(onAddFilter).toBeCalledWith('tim*'); }); - it('should ignore strings with just spaces', async () => { - const component = shallowWithI18nProvider( {}} />); + test('should ignore strings with just spaces', () => { + const component = shallow( {}} />); // Set a value in the input field component.find('EuiFieldText').simulate('keypress', ' '); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/add_filter.tsx b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/add_filter.tsx new file mode 100644 index 0000000000000..d0f397637de33 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/add_filter.tsx @@ -0,0 +1,63 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { useState, useCallback } from 'react'; + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiFlexGroup, EuiFlexItem, EuiFieldText, EuiButton } from '@elastic/eui'; + +interface AddFilterProps { + onAddFilter: (filter: string) => void; +} + +const sourcePlaceholder = i18n.translate('kbn.management.editIndexPattern.sourcePlaceholder', { + defaultMessage: + "source filter, accepts wildcards (e.g., `user*` to filter fields starting with 'user')", +}); + +export const AddFilter = ({ onAddFilter }: AddFilterProps) => { + const [filter, setFilter] = useState(''); + + const onAddButtonClick = useCallback(() => { + onAddFilter(filter); + setFilter(''); + }, [filter, onAddFilter]); + + return ( + + + setFilter(e.target.value.trim())} + placeholder={sourcePlaceholder} + /> + + + + + + + + ); +}; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/index.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/index.ts diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/confirmation_modal/__snapshots__/confirmation_modal.test.tsx.snap b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/confirmation_modal/__snapshots__/confirmation_modal.test.tsx.snap new file mode 100644 index 0000000000000..62376b498d887 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/confirmation_modal/__snapshots__/confirmation_modal.test.tsx.snap @@ -0,0 +1,37 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Header should render normally 1`] = ` + + + } + confirmButtonText={ + + } + defaultFocusedButton="confirm" + onCancel={[Function]} + onConfirm={[Function]} + title={ + + } + /> + +`; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/confirmation_modal/confirmation_modal.test.tsx b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/confirmation_modal/confirmation_modal.test.tsx new file mode 100644 index 0000000000000..ac7237095e4b3 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/confirmation_modal/confirmation_modal.test.tsx @@ -0,0 +1,37 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { DeleteFilterConfirmationModal } from './confirmation_modal'; + +describe('Header', () => { + test('should render normally', () => { + const component = shallow( + {}} + onDeleteFilter={() => {}} + /> + ); + + expect(component).toMatchSnapshot(); + }); +}); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/confirmation_modal/confirmation_modal.tsx b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/confirmation_modal/confirmation_modal.tsx new file mode 100644 index 0000000000000..dabfb6d8f275a --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/confirmation_modal/confirmation_modal.tsx @@ -0,0 +1,76 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import PropTypes from 'prop-types'; + +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiOverlayMask, EuiConfirmModal, EUI_MODAL_CONFIRM_BUTTON } from '@elastic/eui'; + +interface DeleteFilterConfirmationModalProps { + filterToDeleteValue: string; + onCancelConfirmationModal: ( + event?: React.KeyboardEvent | React.MouseEvent + ) => void; + onDeleteFilter: (event: React.MouseEvent) => void; +} + +export const DeleteFilterConfirmationModal = ({ + filterToDeleteValue, + onCancelConfirmationModal, + onDeleteFilter, +}: DeleteFilterConfirmationModalProps) => { + return ( + + + } + onCancel={onCancelConfirmationModal} + onConfirm={onDeleteFilter} + cancelButtonText={ + + } + buttonColor="danger" + confirmButtonText={ + + } + defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON} + /> + + ); +}; + +DeleteFilterConfirmationModal.propTypes = { + filterToDeleteValue: PropTypes.string.isRequired, + onCancelConfirmationModal: PropTypes.func.isRequired, + onDeleteFilter: PropTypes.func.isRequired, +}; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/confirmation_modal/index.ts b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/confirmation_modal/index.ts new file mode 100644 index 0000000000000..e48e38b7c3dcb --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/confirmation_modal/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { DeleteFilterConfirmationModal } from './confirmation_modal'; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/__jest__/__snapshots__/header.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/__snapshots__/header.test.tsx.snap similarity index 98% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/__jest__/__snapshots__/header.test.js.snap rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/__snapshots__/header.test.tsx.snap index a5be141a18c89..cde0de79caacd 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/__jest__/__snapshots__/header.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/__snapshots__/header.test.tsx.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Header should render normally 1`] = ` -
+ @@ -32,5 +32,5 @@ exports[`Header should render normally 1`] = ` -
+ `; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/__jest__/header.test.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/header.test.tsx similarity index 95% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/__jest__/header.test.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/header.test.tsx index 058bf99fe26fa..869bdeb55cf02 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/__jest__/header.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/header.test.tsx @@ -23,7 +23,7 @@ import { shallow } from 'enzyme'; import { Header } from '../header'; describe('Header', () => { - it('should render normally', async () => { + test('should render normally', () => { const component = shallow(
); expect(component).toMatchSnapshot(); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/header.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/header.tsx similarity index 99% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/header.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/header.tsx index 8822ca6236250..7b37f75043dd5 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/header.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/header.tsx @@ -20,11 +20,10 @@ import React from 'react'; import { EuiTitle, EuiText, EuiSpacer } from '@elastic/eui'; - import { FormattedMessage } from '@kbn/i18n/react'; export const Header = () => ( -
+ <>

(

-

+ ); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/index.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/header/index.ts diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/index.ts b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/index.ts new file mode 100644 index 0000000000000..87ac13ad15f50 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/index.ts @@ -0,0 +1,23 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { AddFilter } from './add_filter'; +export { DeleteFilterConfirmationModal } from './confirmation_modal'; +export { Header } from './header'; +export { Table } from './table'; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/__jest__/__snapshots__/table.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/__snapshots__/table.test.tsx.snap similarity index 77% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/__jest__/__snapshots__/table.test.js.snap rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/__snapshots__/table.test.tsx.snap index a0853b8628cc9..c70d0871bb854 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/__jest__/__snapshots__/table.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/__snapshots__/table.test.tsx.snap @@ -3,14 +3,15 @@ exports[`Table editing should show a save button 1`] = `
@@ -18,27 +19,20 @@ exports[`Table editing should show a save button 1`] = ` `; exports[`Table editing should show an input field 1`] = ` - - - - - + + tim* + `; exports[`Table editing should update the matches dynamically as input value is changed 1`] = `
- - time, value - + + +
`; @@ -79,6 +73,7 @@ exports[`Table should render normally 1`] = ` items={ Array [ Object { + "clientId": "", "value": "tim*", }, ] diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/index.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/index.ts diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/__jest__/table.test.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/table.test.tsx similarity index 69% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/__jest__/table.test.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/table.test.tsx index 7fba1fcfe4876..4705ecd2d1685 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/__jest__/table.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/table.test.tsx @@ -17,25 +17,40 @@ * under the License. */ -import React from 'react'; -import { shallow } from 'enzyme'; -import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers'; - -import { Table } from '../table'; -import { keyCodes } from '@elastic/eui'; - -const indexPattern = {}; -const items = [{ value: 'tim*' }]; +import React, { ReactElement } from 'react'; +import { shallow, ShallowWrapper } from 'enzyme'; + +import { Table, TableProps, TableState } from './table'; +import { EuiTableFieldDataColumnType, keyCodes } from '@elastic/eui'; +import { IIndexPattern } from '../../../../../../../../../../../plugins/data/public'; +import { SourceFiltersTableFilter } from '../../types'; + +const indexPattern = {} as IIndexPattern; +const items: SourceFiltersTableFilter[] = [{ value: 'tim*', clientId: '' }]; + +const getIndexPatternMock = (mockedFields: any = {}) => ({ ...mockedFields } as IIndexPattern); + +const getTableColumnRender = ( + component: ShallowWrapper, + index: number = 0 +) => { + const columns = component.prop>>( + 'columns' + ); + return { + render: columns[index].render as (...args: any) => ReactElement, + }; +}; describe('Table', () => { - it('should render normally', async () => { - const component = shallowWithI18nProvider( + test('should render normally', () => { + const component = shallow( {}} fieldWildcardMatcher={() => {}} - saveFilter={() => {}} + saveFilter={() => undefined} isSaving={true} /> ); @@ -43,31 +58,33 @@ describe('Table', () => { expect(component).toMatchSnapshot(); }); - it('should render filter matches', async () => { - const component = shallowWithI18nProvider( + test('should render filter matches', () => { + const component = shallow
(
[{ name: 'time' }, { name: 'value' }], - }} + })} items={items} deleteFilter={() => {}} - fieldWildcardMatcher={filter => field => field.includes(filter[0])} - saveFilter={() => {}} + fieldWildcardMatcher={(filter: string) => (field: string) => field.includes(filter[0])} + saveFilter={() => undefined} isSaving={false} /> ); - const matchesTableCell = shallow(component.prop('columns')[1].render('tim', { clientId: 1 })); + const matchesTableCell = shallow( + getTableColumnRender(component, 1).render('tim', { clientId: 1 }) + ); expect(matchesTableCell).toMatchSnapshot(); }); describe('editing', () => { const saveFilter = jest.fn(); - const clientId = 1; - let component; + const clientId = '1'; + let component: ShallowWrapper; beforeEach(() => { - component = shallowWithI18nProvider( + component = shallow
(
{ ); }); - it('should show an input field', () => { + test('should show an input field', () => { // Start the editing process + const editingComponent = shallow( // Wrap in a div because: https://github.com/airbnb/enzyme/issues/1213 -
{component.prop('columns')[2].render({ clientId, value: 'tim*' })}
+
{getTableColumnRender(component, 2).render({ clientId, value: 'tim*' })}
); editingComponent .find('EuiButtonIcon') @@ -92,19 +110,19 @@ describe('Table', () => { // Ensure the state change propagates component.update(); - // Ensure the table cell switches to an input - const filterNameTableCell = shallow( - component.prop('columns')[0].render('tim*', { clientId }) - ); + const cell = getTableColumnRender(component).render('tim*', { clientId }); + const filterNameTableCell = shallow(cell); + expect(filterNameTableCell).toMatchSnapshot(); }); - it('should show a save button', () => { + test('should show a save button', () => { // Start the editing process const editingComponent = shallow( // Fixes: Invariant Violation: ReactShallowRenderer render(): Shallow rendering works only with custom components, but the provided element type was `symbol`. -
{component.prop('columns')[2].render({ clientId, value: 'tim*' })}
+
{getTableColumnRender(component, 2).render({ clientId, value: 'tim*' })}
); + editingComponent .find('EuiButtonIcon') .at(1) @@ -116,22 +134,20 @@ describe('Table', () => { // Verify save button const saveTableCell = shallow( // Fixes Invariant Violation: ReactShallowRenderer render(): Shallow rendering works only with custom components, but the provided element type was `symbol`. -
{component.prop('columns')[2].render({ clientId, value: 'tim*' })}
+
{getTableColumnRender(component, 2).render({ clientId, value: 'tim*' })}
); expect(saveTableCell).toMatchSnapshot(); }); - it('should update the matches dynamically as input value is changed', () => { - const localComponent = shallowWithI18nProvider( + test('should update the matches dynamically as input value is changed', () => { + const localComponent = shallow(
[{ name: 'time' }, { name: 'value' }], - }} + })} items={items} deleteFilter={() => {}} - fieldWildcardMatcher={query => () => { - return query.includes('time*'); - }} + fieldWildcardMatcher={(query: string) => () => query.includes('time*')} saveFilter={saveFilter} isSaving={false} /> @@ -142,6 +158,7 @@ describe('Table', () => { // Fixes: Invariant Violation: ReactShallowRenderer render(): Shallow rendering works only with custom components, but the provided element type was `symbol`.
{localComponent.prop('columns')[2].render({ clientId, value: 'tim*' })}
); + editingComponent .find('EuiButtonIcon') .at(1) @@ -161,7 +178,7 @@ describe('Table', () => { expect(matchesTableCell).toMatchSnapshot(); }); - it('should exit on save', () => { + test('should exit on save', () => { // Change the value to something else component.setState({ editingFilterId: clientId, @@ -171,34 +188,37 @@ describe('Table', () => { // Click the save button const editingComponent = shallow( // Fixes Invariant Violation: ReactShallowRenderer render(): Shallow rendering works only with custom components, but the provided element type was `symbol`. -
{component.prop('columns')[2].render({ clientId, value: 'tim*' })}
+
{getTableColumnRender(component, 2).render({ clientId, value: 'tim*' })}
); + editingComponent .find('EuiButtonIcon') .at(0) .simulate('click'); + editingComponent.update(); + // Ensure we call saveFilter properly expect(saveFilter).toBeCalledWith({ - filterId: clientId, - newFilterValue: 'ti*', + clientId, + value: 'ti*', }); // Ensure the state is properly reset - expect(component.state('editingFilterId')).toBe(null); + expect(component.state('editingFilterId')).toBe(''); }); }); - it('should allow deletes', () => { + test('should allow deletes', () => { const deleteFilter = jest.fn(); - const component = shallowWithI18nProvider( + const component = shallow(
{}} - saveFilter={() => {}} + saveFilter={() => undefined} isSaving={false} /> ); @@ -210,16 +230,15 @@ describe('Table', () => { ); deleteCellComponent .find('EuiButtonIcon') - .at(0) + .at(1) .simulate('click'); expect(deleteFilter).toBeCalled(); }); - it('should save when in edit mode and the enter key is pressed', () => { + test('should save when in edit mode and the enter key is pressed', () => { const saveFilter = jest.fn(); - const clientId = 1; - const component = shallowWithI18nProvider( + const component = shallow(
{ // Start the editing process const editingComponent = shallow( // Fixes Invariant Violation: ReactShallowRenderer render(): Shallow rendering works only with custom components, but the provided element type was `symbol`. -
{component.prop('columns')[2].render({ clientId, value: 'tim*' })}
+
{component.prop('columns')[2].render({ clientId: 1, value: 'tim*' })}
); editingComponent .find('EuiButtonIcon') - .at(1) + .at(0) .simulate('click'); - // Ensure the state change propagates + component.update(); // Get the rendered input cell const filterNameTableCell = shallow( // Fixes Invariant Violation: ReactShallowRenderer render(): Shallow rendering works only with custom components, but the provided element type was `symbol`. -
{component.prop('columns')[0].render('tim*', { clientId })}
+
{component.prop('columns')[0].render('tim*', { clientId: 1 })}
); // Press the enter key @@ -253,14 +272,13 @@ describe('Table', () => { expect(saveFilter).toBeCalled(); // It should reset - expect(component.state('editingFilterId')).toBe(null); + expect(component.state('editingFilterId')).toBe(''); }); - it('should cancel when in edit mode and the esc key is pressed', () => { + test('should cancel when in edit mode and the esc key is pressed', () => { const saveFilter = jest.fn(); - const clientId = 1; - const component = shallowWithI18nProvider( + const component = shallow(
{ // Start the editing process const editingComponent = shallow( // Fixes Invariant Violation: ReactShallowRenderer render(): Shallow rendering works only with custom components, but the provided element type was `symbol`. -
{component.prop('columns')[2].render({ clientId, value: 'tim*' })}
+
{component.prop('columns')[2].render({ clientId: 1, value: 'tim*' })}
); + editingComponent .find('EuiButtonIcon') - .at(1) + .at(0) .simulate('click'); + // Ensure the state change propagates component.update(); // Get the rendered input cell const filterNameTableCell = shallow( // Fixes Invariant Violation: ReactShallowRenderer render(): Shallow rendering works only with custom components, but the provided element type was `symbol`. -
{component.prop('columns')[0].render('tim*', { clientId })}
+
{component.prop('columns')[0].render('tim*', { clientId: 1 })}
); - // Press the enter key + // Press the ESCAPE key filterNameTableCell.find('EuiFieldText').simulate('keydown', { keyCode: keyCodes.ESCAPE }); expect(saveFilter).not.toBeCalled(); // It should reset - expect(component.state('editingFilterId')).toBe(null); + expect(component.state('editingFilterId')).toBe(''); }); }); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/table.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/table.tsx similarity index 54% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/table.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/table.tsx index f16663e1cd41a..db2b74bbc9824 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/table.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/table.tsx @@ -17,48 +17,94 @@ * under the License. */ -import React, { Component, Fragment } from 'react'; -import PropTypes from 'prop-types'; +import React, { Component } from 'react'; import { + keyCodes, + EuiBasicTableColumn, EuiInMemoryTable, EuiFieldText, EuiButtonIcon, - keyCodes, RIGHT_ALIGNMENT, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { SourceFiltersTableFilter } from '../../types'; -export class Table extends Component { - static propTypes = { - indexPattern: PropTypes.object.isRequired, - items: PropTypes.array.isRequired, - deleteFilter: PropTypes.func.isRequired, - fieldWildcardMatcher: PropTypes.func.isRequired, - saveFilter: PropTypes.func.isRequired, - isSaving: PropTypes.bool.isRequired, - }; +import { IIndexPattern } from '../../../../../../../../../../../plugins/data/public'; + +const filterHeader = i18n.translate('kbn.management.editIndexPattern.source.table.filterHeader', { + defaultMessage: 'Filter', +}); + +const filterDescription = i18n.translate( + 'kbn.management.editIndexPattern.source.table.filterDescription', + { defaultMessage: 'Filter name' } +); + +const matchesHeader = i18n.translate('kbn.management.editIndexPattern.source.table.matchesHeader', { + defaultMessage: 'Matches', +}); + +const matchesDescription = i18n.translate( + 'kbn.management.editIndexPattern.source.table.matchesDescription', + { defaultMessage: 'Language used for the field' } +); + +const editAria = i18n.translate('kbn.management.editIndexPattern.source.table.editAria', { + defaultMessage: 'Edit', +}); + +const saveAria = i18n.translate('kbn.management.editIndexPattern.source.table.saveAria', { + defaultMessage: 'Save', +}); + +const deleteAria = i18n.translate('kbn.management.editIndexPattern.source.table.deleteAria', { + defaultMessage: 'Delete', +}); + +const cancelAria = i18n.translate('kbn.management.editIndexPattern.source.table.cancelAria', { + defaultMessage: 'Cancel', +}); + +export interface TableProps { + indexPattern: IIndexPattern; + items: SourceFiltersTableFilter[]; + deleteFilter: Function; + fieldWildcardMatcher: Function; + saveFilter: (filter: SourceFiltersTableFilter) => any; + isSaving: boolean; +} + +export interface TableState { + editingFilterId: string | number; + editingFilterValue: string; +} - constructor(props) { +export class Table extends Component { + constructor(props: TableProps) { super(props); this.state = { - editingFilterId: null, - editingFilterValue: null, + editingFilterId: '', + editingFilterValue: '', }; } - startEditingFilter = (id, value) => - this.setState({ editingFilterId: id, editingFilterValue: value }); - stopEditingFilter = () => this.setState({ editingFilterId: null }); - onEditingFilterChange = e => this.setState({ editingFilterValue: e.target.value }); + startEditingFilter = ( + editingFilterId: TableState['editingFilterId'], + editingFilterValue: TableState['editingFilterValue'] + ) => this.setState({ editingFilterId, editingFilterValue }); + + stopEditingFilter = () => this.setState({ editingFilterId: '' }); + onEditingFilterChange = (e: React.ChangeEvent) => + this.setState({ editingFilterValue: e.target.value }); - onEditFieldKeyDown = ({ keyCode }) => { - if (keyCodes.ENTER === keyCode) { + onEditFieldKeyDown = ({ keyCode }: React.KeyboardEvent) => { + if (keyCodes.ENTER === keyCode && this.state.editingFilterId && this.state.editingFilterValue) { this.props.saveFilter({ - filterId: this.state.editingFilterId, - newFilterValue: this.state.editingFilterValue, + clientId: this.state.editingFilterId, + value: this.state.editingFilterValue, }); this.stopEditingFilter(); } @@ -67,23 +113,18 @@ export class Table extends Component { } }; - getColumns() { + getColumns(): Array> { const { deleteFilter, fieldWildcardMatcher, indexPattern, saveFilter } = this.props; return [ { field: 'value', - name: i18n.translate('kbn.management.editIndexPattern.source.table.filterHeader', { - defaultMessage: 'Filter', - }), - description: i18n.translate( - 'kbn.management.editIndexPattern.source.table.filterDescription', - { defaultMessage: 'Filter name' } - ), + name: filterHeader, + description: filterDescription, dataType: 'string', sortable: true, render: (value, filter) => { - if (this.state.editingFilterId === filter.clientId) { + if (this.state.editingFilterId && this.state.editingFilterId === filter.clientId) { return ( { - const realtimeValue = - this.state.editingFilterId === filter.clientId ? this.state.editingFilterValue : value; - const matcher = fieldWildcardMatcher([realtimeValue]); + const wildcardMatcher = fieldWildcardMatcher([ + this.state.editingFilterId === filter.clientId ? this.state.editingFilterValue : value, + ]); const matches = indexPattern .getNonScriptedFields() - .map(f => f.name) - .filter(matcher) + .map((currentFilter: any) => currentFilter.name) + .filter(wildcardMatcher) .sort(); + if (matches.length) { return {matches.join(', ')}; } @@ -135,24 +172,21 @@ export class Table extends Component { name: '', align: RIGHT_ALIGNMENT, width: '100', - render: filter => { + render: (filter: SourceFiltersTableFilter) => { if (this.state.editingFilterId === filter.clientId) { return ( - + <> { saveFilter({ - filterId: this.state.editingFilterId, - newFilterValue: this.state.editingFilterValue, + clientId: this.state.editingFilterId, + value: this.state.editingFilterValue, }); this.stopEditingFilter(); }} iconType="checkInCircleFilled" - aria-label={i18n.translate( - 'kbn.management.editIndexPattern.source.table.saveAria', - { defaultMessage: 'Save' } - )} + aria-label={saveAria} /> - + ); } return ( - + <> deleteFilter(filter)} - iconType="trash" - aria-label={i18n.translate( - 'kbn.management.editIndexPattern.source.table.deleteAria', - { defaultMessage: 'Delete' } - )} + onClick={() => this.startEditingFilter(filter.clientId, filter.value)} + iconType="pencil" + aria-label={editAria} /> this.startEditingFilter(filter.clientId, filter.value)} - iconType="pencil" - aria-label={i18n.translate( - 'kbn.management.editIndexPattern.source.table.editAria', - { defaultMessage: 'Edit' } - )} + color="danger" + onClick={() => deleteFilter(filter)} + iconType="trash" + aria-label={deleteAria} /> - + ); }, }, diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/index.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/index.ts diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/__jest__/source_filters_table.test.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/source_filters_table.test.tsx similarity index 64% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/__jest__/source_filters_table.test.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/source_filters_table.test.tsx index a39958a77abbf..1b68dd13566d3 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/__jest__/source_filters_table.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/source_filters_table.test.tsx @@ -20,13 +20,13 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { SourceFiltersTable } from '../source_filters_table'; +import { SourceFiltersTable } from './source_filters_table'; +import { IIndexPattern } from '../../../../../../../../../plugins/data/public'; jest.mock('@elastic/eui', () => ({ EuiButton: 'eui-button', EuiTitle: 'eui-title', EuiText: 'eui-text', - EuiButton: 'eui-button', EuiHorizontalRule: 'eui-horizontal-rule', EuiSpacer: 'eui-spacer', EuiCallOut: 'eui-call-out', @@ -39,42 +39,54 @@ jest.mock('@elastic/eui', () => ({ default: () => {}, }, })); -jest.mock('../components/header', () => ({ Header: 'header' })); -jest.mock('../components/table', () => ({ + +jest.mock('./components/header', () => ({ Header: 'header' })); +jest.mock('./components/table', () => ({ // Note: this seems to fix React complaining about non lowercase attributes Table: () => { return 'table'; }, })); -const indexPattern = { - sourceFilters: [{ value: 'time*' }, { value: 'nam*' }, { value: 'age*' }], -}; +const getIndexPatternMock = (mockedFields: any = {}) => + ({ + sourceFilters: [{ value: 'time*' }, { value: 'nam*' }, { value: 'age*' }], + ...mockedFields, + } as IIndexPattern); describe('SourceFiltersTable', () => { - it('should render normally', async () => { + test('should render normally', () => { const component = shallow( - {}} /> + {}} + filterFilter={''} + /> ); expect(component).toMatchSnapshot(); }); - it('should filter based on the query bar', async () => { + test('should filter based on the query bar', () => { const component = shallow( - {}} /> + {}} + filterFilter={''} + /> ); component.setProps({ filterFilter: 'ti' }); expect(component).toMatchSnapshot(); }); - it('should should a loading indicator when saving', async () => { + test('should should a loading indicator when saving', () => { const component = shallow( {}} /> ); @@ -83,34 +95,36 @@ describe('SourceFiltersTable', () => { expect(component).toMatchSnapshot(); }); - it('should show a delete modal', async () => { - const component = shallow( + test('should show a delete modal', () => { + const component = shallow( {}} /> ); - component.instance().startDeleteFilter({ value: 'tim*' }); + component.instance().startDeleteFilter({ value: 'tim*', clientId: 1 }); component.update(); // We are not calling `.setState` directly so we need to re-render expect(component).toMatchSnapshot(); }); - it('should remove a filter', async () => { + test('should remove a filter', async () => { const save = jest.fn(); - const component = shallow( + const component = shallow( {}} /> ); - component.instance().startDeleteFilter({ value: 'tim*' }); + component.instance().startDeleteFilter({ value: 'tim*', clientId: 1 }); component.update(); // We are not calling `.setState` directly so we need to re-render await component.instance().deleteFilter(); component.update(); // We are not calling `.setState` directly so we need to re-render @@ -119,14 +133,15 @@ describe('SourceFiltersTable', () => { expect(component).toMatchSnapshot(); }); - it('should add a filter', async () => { + test('should add a filter', async () => { const save = jest.fn(); - const component = shallow( + const component = shallow( {}} /> ); @@ -138,19 +153,20 @@ describe('SourceFiltersTable', () => { expect(component).toMatchSnapshot(); }); - it('should update a filter', async () => { + test('should update a filter', async () => { const save = jest.fn(); - const component = shallow( + const component = shallow( {}} /> ); - await component.instance().saveFilter({ oldFilterValue: 'tim*', newFilterValue: 'ti*' }); + await component.instance().saveFilter({ clientId: 'tim*', value: 'ti*' }); component.update(); // We are not calling `.setState` directly so we need to re-render expect(save).toBeCalled(); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/source_filters_table.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/source_filters_table.tsx similarity index 56% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/source_filters_table.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/source_filters_table.tsx index 3b485573f3821..dcf8ae9e1323f 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/source_filters_table.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/source_filters_table.tsx @@ -18,33 +18,40 @@ */ import React, { Component } from 'react'; -import PropTypes from 'prop-types'; import { createSelector } from 'reselect'; -import { EuiSpacer, EuiOverlayMask, EuiConfirmModal, EUI_MODAL_CONFIRM_BUTTON } from '@elastic/eui'; +import { EuiSpacer } from '@elastic/eui'; +import { AddFilter, Table, Header, DeleteFilterConfirmationModal } from './components'; +import { IIndexPattern } from '../../../../../../../../../plugins/data/public'; +import { SourceFiltersTableFilter } from './types'; -import { Table } from './components/table'; -import { Header } from './components/header'; -import { AddFilter } from './components/add_filter'; -import { FormattedMessage } from '@kbn/i18n/react'; +export interface SourceFiltersTableProps { + indexPattern: IIndexPattern; + filterFilter: string; + fieldWildcardMatcher: Function; + onAddOrRemoveFilter?: Function; +} -export class SourceFiltersTable extends Component { - static propTypes = { - indexPattern: PropTypes.object.isRequired, - filterFilter: PropTypes.string, - fieldWildcardMatcher: PropTypes.func.isRequired, - onAddOrRemoveFilter: PropTypes.func, - }; +export interface SourceFiltersTableState { + filterToDelete: any; + isDeleteConfirmationModalVisible: boolean; + isSaving: boolean; + filters: SourceFiltersTableFilter[]; +} - constructor(props) { +export class SourceFiltersTable extends Component< + SourceFiltersTableProps, + SourceFiltersTableState +> { + // Source filters do not have any unique ids, only the value is stored. + // To ensure we can create a consistent and expected UX when managing + // source filters, we are assigning a unique id to each filter on the + // client side only + private clientSideId: number = 0; + + constructor(props: SourceFiltersTableProps) { super(props); - // Source filters do not have any unique ids, only the value is stored. - // To ensure we can create a consistent and expected UX when managing - // source filters, we are assigning a unique id to each filter on the - // client side only - this.clientSideId = 0; - this.state = { filterToDelete: undefined, isDeleteConfirmationModalVisible: false, @@ -58,9 +65,9 @@ export class SourceFiltersTable extends Component { } updateFilters = () => { - const sourceFilters = this.props.indexPattern.sourceFilters || []; - const filters = sourceFilters.map(filter => ({ - ...filter, + const sourceFilters = this.props.indexPattern.sourceFilters; + const filters = (sourceFilters || []).map((sourceFilter: any) => ({ + ...sourceFilter, clientId: ++this.clientSideId, })); @@ -68,8 +75,8 @@ export class SourceFiltersTable extends Component { }; getFilteredFilters = createSelector( - state => state.filters, - (state, props) => props.filterFilter, + (state: SourceFiltersTableState) => state.filters, + (state: SourceFiltersTableState, props: SourceFiltersTableProps) => props.filterFilter, (filters, filterFilter) => { if (filterFilter) { const filterFilterToLowercase = filterFilter.toLowerCase(); @@ -82,7 +89,7 @@ export class SourceFiltersTable extends Component { } ); - startDeleteFilter = filter => { + startDeleteFilter = (filter: SourceFiltersTableFilter) => { this.setState({ filterToDelete: filter, isDeleteConfirmationModalVisible: true, @@ -106,35 +113,44 @@ export class SourceFiltersTable extends Component { this.setState({ isSaving: true }); await indexPattern.save(); - onAddOrRemoveFilter && onAddOrRemoveFilter(); + + if (onAddOrRemoveFilter) { + onAddOrRemoveFilter(); + } + this.updateFilters(); this.setState({ isSaving: false }); this.hideDeleteConfirmationModal(); }; - onAddFilter = async value => { + onAddFilter = async (value: string) => { const { indexPattern, onAddOrRemoveFilter } = this.props; indexPattern.sourceFilters = [...(indexPattern.sourceFilters || []), { value }]; this.setState({ isSaving: true }); await indexPattern.save(); - onAddOrRemoveFilter && onAddOrRemoveFilter(); + + if (onAddOrRemoveFilter) { + onAddOrRemoveFilter(); + } + this.updateFilters(); this.setState({ isSaving: false }); }; - saveFilter = async ({ filterId, newFilterValue }) => { + saveFilter = async ({ clientId, value }: SourceFiltersTableFilter) => { const { indexPattern } = this.props; const { filters } = this.state; indexPattern.sourceFilters = filters.map(filter => { - if (filter.clientId === filterId) { + if (filter.clientId === clientId) { return { - value: newFilterValue, - clientId: filter.clientId, + value, + clientId, }; } + return filter; }); @@ -144,55 +160,13 @@ export class SourceFiltersTable extends Component { this.setState({ isSaving: false }); }; - renderDeleteConfirmationModal() { - const { filterToDelete } = this.state; - - if (!filterToDelete) { - return null; - } - - return ( - - - } - onCancel={this.hideDeleteConfirmationModal} - onConfirm={this.deleteFilter} - cancelButtonText={ - - } - buttonColor="danger" - confirmButtonText={ - - } - defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON} - /> - - ); - } - render() { const { indexPattern, fieldWildcardMatcher } = this.props; - - const { isSaving } = this.state; - + const { isSaving, filterToDelete } = this.state; const filteredFilters = this.getFilteredFilters(this.state, this.props); return ( -
+ <>
@@ -205,8 +179,14 @@ export class SourceFiltersTable extends Component { saveFilter={this.saveFilter} /> - {this.renderDeleteConfirmationModal()} -
+ {filterToDelete && ( + + )} + ); } } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/types.ts b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/types.ts new file mode 100644 index 0000000000000..ee3689f017471 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/types.ts @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** @internal **/ +export interface SourceFiltersTableFilter { + value: string; + clientId: string | number; +} From 7ea3f12e31f198d6e79907f26518bbab612266b9 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Mon, 13 Apr 2020 19:02:22 +0300 Subject: [PATCH 05/11] Index pattern management UI -> TypeScript (scripted_fields_table) (#63247) * Index pattern management UI -> TypeScript (scripted_fields_table) --- .../scripted_field_table.test.tsx.snap} | 40 +++--- .../call_outs.test.tsx.snap} | 4 +- .../call_outs.test.js => call_outs.test.tsx} | 4 +- .../call_outs/{call_outs.js => call_outs.tsx} | 12 +- .../call_outs/{index.js => index.ts} | 0 .../confirmation_modal.test.tsx.snap | 14 ++ .../confirmation_modal.test.tsx | 37 ++++++ .../confirmation_modal/confirmation_modal.tsx | 63 +++++++++ .../components/confirmation_modal/index.ts | 20 +++ .../header.test.tsx.snap} | 0 .../header.test.js => header.test.tsx} | 4 +- .../header/{header.js => header.tsx} | 12 +- .../components/header/{index.js => index.ts} | 0 .../components/{index.js => index.ts} | 1 + .../table.test.tsx.snap} | 7 +- .../components/table/{index.js => index.ts} | 0 .../table.test.js => table.test.tsx} | 49 +++---- .../components/table/{table.js => table.tsx} | 35 +++-- .../{index.js => index.ts} | 0 ....test.js => scripted_field_table.test.tsx} | 67 ++++++---- ...lds_table.js => scripted_fields_table.tsx} | 122 ++++++++---------- .../scripted_fields_table/types.ts | 25 ++++ .../{index.js => index.ts} | 12 +- 23 files changed, 346 insertions(+), 182 deletions(-) rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/{__jest__/__snapshots__/scripted_field_table.test.js.snap => __snapshots__/scripted_field_table.test.tsx.snap} (89%) rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/{__jest__/__snapshots__/call_outs.test.js.snap => __snapshots__/call_outs.test.tsx.snap} (98%) rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/{__jest__/call_outs.test.js => call_outs.test.tsx} (92%) rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/{call_outs.js => call_outs.tsx} (94%) rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/{index.js => index.ts} (100%) create mode 100644 src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/confirmation_modal/__snapshots__/confirmation_modal.test.tsx.snap create mode 100644 src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/confirmation_modal/confirmation_modal.test.tsx create mode 100644 src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/confirmation_modal/confirmation_modal.tsx create mode 100644 src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/confirmation_modal/index.ts rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/{__jest__/__snapshots__/header.test.js.snap => __snapshots__/header.test.tsx.snap} (100%) rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/{__jest__/header.test.js => header.test.tsx} (92%) rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/{header.js => header.tsx} (92%) rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/{index.js => index.ts} (100%) rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/{index.js => index.ts} (92%) rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/{__jest__/__snapshots__/table.test.js.snap => __snapshots__/table.test.tsx.snap} (93%) rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/{index.js => index.ts} (100%) rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/{__jest__/table.test.js => table.test.tsx} (71%) rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/{table.js => table.tsx} (84%) rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/{index.js => index.ts} (100%) rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/{__jest__/scripted_field_table.test.js => scripted_field_table.test.tsx} (75%) rename src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/{scripted_fields_table.js => scripted_fields_table.tsx} (60%) create mode 100644 src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/types.ts rename src/legacy/ui/public/scripting_languages/{index.js => index.ts} (83%) diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/__jest__/__snapshots__/scripted_field_table.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/__snapshots__/scripted_field_table.test.tsx.snap similarity index 89% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/__jest__/__snapshots__/scripted_field_table.test.js.snap rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/__snapshots__/scripted_field_table.test.tsx.snap index a53f4d7f609cb..569b75c848c52 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/__jest__/__snapshots__/scripted_field_table.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/__snapshots__/scripted_field_table.test.tsx.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`ScriptedFieldsTable should filter based on the lang filter 1`] = ` -
+
@@ -39,11 +39,11 @@ exports[`ScriptedFieldsTable should filter based on the lang filter 1`] = ` ] } /> -
+ `; exports[`ScriptedFieldsTable should filter based on the query bar 1`] = ` -
+
@@ -72,11 +72,11 @@ exports[`ScriptedFieldsTable should filter based on the query bar 1`] = ` ] } /> -
+ `; exports[`ScriptedFieldsTable should hide the table if there are no scripted fields 1`] = ` -
+
@@ -97,11 +97,11 @@ exports[`ScriptedFieldsTable should hide the table if there are no scripted fiel } items={Array []} /> -
+ `; exports[`ScriptedFieldsTable should render normally 1`] = ` -
+
@@ -135,11 +135,11 @@ exports[`ScriptedFieldsTable should render normally 1`] = ` ] } /> -
+ `; exports[`ScriptedFieldsTable should show a delete modal 1`] = ` -
+
@@ -173,14 +173,16 @@ exports[`ScriptedFieldsTable should show a delete modal 1`] = ` ] } /> - - - -
+ + `; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/__jest__/__snapshots__/call_outs.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/__snapshots__/call_outs.test.tsx.snap similarity index 98% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/__jest__/__snapshots__/call_outs.test.js.snap rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/__snapshots__/call_outs.test.tsx.snap index e6f0d6cd819e3..4dfda1b9339b1 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/__jest__/__snapshots__/call_outs.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/__snapshots__/call_outs.test.tsx.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`CallOuts should render normally 1`] = ` -
+ -
+ `; exports[`CallOuts should render without any call outs 1`] = `""`; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/__jest__/call_outs.test.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/call_outs.test.tsx similarity index 92% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/__jest__/call_outs.test.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/call_outs.test.tsx index 12e0ee8839967..407928931191d 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/__jest__/call_outs.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/call_outs.test.tsx @@ -23,7 +23,7 @@ import { shallow } from 'enzyme'; import { CallOuts } from '../call_outs'; describe('CallOuts', () => { - it('should render normally', async () => { + test('should render normally', () => { const component = shallow( { expect(component).toMatchSnapshot(); }); - it('should render without any call outs', async () => { + test('should render without any call outs', () => { const component = shallow( ); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/call_outs.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/call_outs.tsx similarity index 94% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/call_outs.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/call_outs.tsx index 0c321c8ba8b01..8e38b569a32fa 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/call_outs.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/call_outs.tsx @@ -20,16 +20,20 @@ import React from 'react'; import { EuiCallOut, EuiLink, EuiSpacer } from '@elastic/eui'; - import { FormattedMessage } from '@kbn/i18n/react'; -export const CallOuts = ({ deprecatedLangsInUse, painlessDocLink }) => { +interface CallOutsProps { + deprecatedLangsInUse: string[]; + painlessDocLink: string; +} + +export const CallOuts = ({ deprecatedLangsInUse, painlessDocLink }: CallOutsProps) => { if (!deprecatedLangsInUse.length) { return null; } return ( -
+ <> {

-
+ ); }; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/index.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/call_outs/index.ts diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/confirmation_modal/__snapshots__/confirmation_modal.test.tsx.snap b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/confirmation_modal/__snapshots__/confirmation_modal.test.tsx.snap new file mode 100644 index 0000000000000..2b320782cb163 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/confirmation_modal/__snapshots__/confirmation_modal.test.tsx.snap @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`DeleteScritpedFieldConfirmationModal should render normally 1`] = ` + + + +`; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/confirmation_modal/confirmation_modal.test.tsx b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/confirmation_modal/confirmation_modal.test.tsx new file mode 100644 index 0000000000000..f3594e7507a6a --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/confirmation_modal/confirmation_modal.test.tsx @@ -0,0 +1,37 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { DeleteScritpedFieldConfirmationModal } from './confirmation_modal'; + +describe('DeleteScritpedFieldConfirmationModal', () => { + test('should render normally', () => { + const component = shallow( + {}} + hideDeleteConfirmationModal={() => {}} + /> + ); + + expect(component).toMatchSnapshot(); + }); +}); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/confirmation_modal/confirmation_modal.tsx b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/confirmation_modal/confirmation_modal.tsx new file mode 100644 index 0000000000000..1e82174f863b0 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/confirmation_modal/confirmation_modal.tsx @@ -0,0 +1,63 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EUI_MODAL_CONFIRM_BUTTON, EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; + +import { ScriptedFieldItem } from '../../types'; + +interface DeleteScritpedFieldConfirmationModalProps { + field: ScriptedFieldItem; + hideDeleteConfirmationModal: ( + event?: React.KeyboardEvent | React.MouseEvent + ) => void; + deleteField: (event: React.MouseEvent) => void; +} + +export const DeleteScritpedFieldConfirmationModal = ({ + field, + hideDeleteConfirmationModal, + deleteField, +}: DeleteScritpedFieldConfirmationModalProps) => { + const title = i18n.translate('kbn.management.editIndexPattern.scripted.deleteFieldLabel', { + defaultMessage: "Delete scripted field '{fieldName}'?", + values: { fieldName: field.name }, + }); + const cancelButtonText = i18n.translate( + 'kbn.management.editIndexPattern.scripted.deleteField.cancelButton', + { defaultMessage: 'Cancel' } + ); + const confirmButtonText = i18n.translate( + 'kbn.management.editIndexPattern.scripted.deleteField.deleteButton', + { defaultMessage: 'Delete' } + ); + + return ( + + + + ); +}; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/confirmation_modal/index.ts b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/confirmation_modal/index.ts new file mode 100644 index 0000000000000..b87b572333e6f --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/confirmation_modal/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { DeleteScritpedFieldConfirmationModal } from './confirmation_modal'; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/__jest__/__snapshots__/header.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/__snapshots__/header.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/__jest__/__snapshots__/header.test.js.snap rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/__snapshots__/header.test.tsx.snap diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/__jest__/header.test.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/header.test.tsx similarity index 92% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/__jest__/header.test.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/header.test.tsx index 3e377ccfbdd41..19479de8f2aa4 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/__jest__/header.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/header.test.tsx @@ -20,10 +20,10 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { Header } from '../header'; +import { Header } from './header'; describe('Header', () => { - it('should render normally', async () => { + test('should render normally', () => { const component = shallow(
); expect(component).toMatchSnapshot(); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/header.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/header.tsx similarity index 92% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/header.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/header.tsx index 97c235d82f870..b8f832dad72af 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/header.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/header.tsx @@ -18,13 +18,15 @@ */ import React from 'react'; -import PropTypes from 'prop-types'; - import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiText, EuiTitle } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -export const Header = ({ addScriptedFieldUrl }) => ( +interface HeaderProps { + addScriptedFieldUrl: string; +} + +export const Header = ({ addScriptedFieldUrl }: HeaderProps) => ( @@ -56,7 +58,3 @@ export const Header = ({ addScriptedFieldUrl }) => ( ); - -Header.propTypes = { - addScriptedFieldUrl: PropTypes.string.isRequired, -}; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/index.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/header/index.ts diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/index.ts similarity index 92% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/index.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/index.ts index 5c0bb41eab765..7d74776fb2bca 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/index.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/index.ts @@ -20,3 +20,4 @@ export { Table } from './table'; export { Header } from './header'; export { CallOuts } from './call_outs'; +export { DeleteScritpedFieldConfirmationModal } from './confirmation_modal'; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/__jest__/__snapshots__/table.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/__snapshots__/table.test.tsx.snap similarity index 93% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/__jest__/__snapshots__/table.test.js.snap rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/__snapshots__/table.test.tsx.snap index 2da4d84463b29..8439887dd468a 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/__jest__/__snapshots__/table.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/__snapshots__/table.test.tsx.snap @@ -41,6 +41,7 @@ exports[`Table should render normally 1`] = ` "icon": "pencil", "name": "Edit", "onClick": [Function], + "type": "icon", }, Object { "color": "danger", @@ -48,6 +49,7 @@ exports[`Table should render normally 1`] = ` "icon": "trash", "name": "Delete", "onClick": [Function], + "type": "icon", }, ], "name": "", @@ -58,8 +60,9 @@ exports[`Table should render normally 1`] = ` items={ Array [ Object { - "id": 1, - "name": "Elastic", + "lang": "Elastic", + "name": "1", + "script": "", }, ] } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/index.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/index.ts diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/__jest__/table.test.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/table.test.tsx similarity index 71% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/__jest__/table.test.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/table.test.tsx index 4545bfa8f64db..13b3875f58687 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/__jest__/table.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/table.test.tsx @@ -19,45 +19,50 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers'; import { Table } from '../table'; +import { ScriptedFieldItem } from '../../types'; +import { IIndexPattern } from '../../../../../../../../../../../plugins/data/public'; -const indexPattern = { - fieldFormatMap: { - Elastic: { - type: { - title: 'string', - }, - }, - }, -}; +const getIndexPatternMock = (mockedFields: any = {}) => ({ ...mockedFields } as IIndexPattern); -const items = [{ id: 1, name: 'Elastic' }]; +const items: ScriptedFieldItem[] = [{ name: '1', lang: 'Elastic', script: '' }]; describe('Table', () => { - it('should render normally', async () => { - const component = shallowWithI18nProvider( + let indexPattern: IIndexPattern; + + beforeEach(() => { + indexPattern = getIndexPatternMock({ + fieldFormatMap: { + Elastic: { + type: { + title: 'string', + }, + }, + }, + }); + }); + + test('should render normally', () => { + const component = shallow
(
{}} deleteField={() => {}} - onChange={() => {}} /> ); expect(component).toMatchSnapshot(); }); - it('should render the format', async () => { - const component = shallowWithI18nProvider( + test('should render the format', () => { + const component = shallow(
{}} deleteField={() => {}} - onChange={() => {}} /> ); @@ -65,16 +70,15 @@ describe('Table', () => { expect(formatTableCell).toMatchSnapshot(); }); - it('should allow edits', () => { + test('should allow edits', () => { const editField = jest.fn(); - const component = shallowWithI18nProvider( + const component = shallow(
{}} - onChange={() => {}} /> ); @@ -83,16 +87,15 @@ describe('Table', () => { expect(editField).toBeCalled(); }); - it('should allow deletes', () => { + test('should allow deletes', () => { const deleteField = jest.fn(); - const component = shallowWithI18nProvider( + const component = shallow(
{}} deleteField={deleteField} - onChange={() => {}} /> ); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/table.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/table.tsx similarity index 84% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/table.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/table.tsx index 5e05dd95827c7..14aed11b32203 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/table.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/table.tsx @@ -18,27 +18,24 @@ */ import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; - -import { EuiInMemoryTable } from '@elastic/eui'; - +import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; +import { EuiInMemoryTable, EuiBasicTableColumn } from '@elastic/eui'; -export class Table extends PureComponent { - static propTypes = { - indexPattern: PropTypes.object.isRequired, - items: PropTypes.array.isRequired, - editField: PropTypes.func.isRequired, - deleteField: PropTypes.func.isRequired, - }; +import { ScriptedFieldItem } from '../../types'; +import { IIndexPattern } from '../../../../../../../../../../../plugins/data/public'; - renderFormatCell = value => { - const { indexPattern } = this.props; +interface TableProps { + indexPattern: IIndexPattern; + items: ScriptedFieldItem[]; + editField: (field: ScriptedFieldItem) => void; + deleteField: (field: ScriptedFieldItem) => void; +} - const title = - indexPattern.fieldFormatMap[value] && indexPattern.fieldFormatMap[value].type - ? indexPattern.fieldFormatMap[value].type.title - : ''; +export class Table extends PureComponent { + renderFormatCell = (value: string) => { + const { indexPattern } = this.props; + const title = get(indexPattern, ['fieldFormatMap', value, 'type', 'title'], ''); return {title}; }; @@ -46,7 +43,7 @@ export class Table extends PureComponent { render() { const { items, editField, deleteField } = this.props; - const columns = [ + const columns: Array> = [ { field: 'displayName', name: i18n.translate('kbn.management.editIndexPattern.scripted.table.nameHeader', { @@ -101,6 +98,7 @@ export class Table extends PureComponent { name: '', actions: [ { + type: 'icon', name: i18n.translate('kbn.management.editIndexPattern.scripted.table.editHeader', { defaultMessage: 'Edit', }), @@ -112,6 +110,7 @@ export class Table extends PureComponent { onClick: editField, }, { + type: 'icon', name: i18n.translate('kbn.management.editIndexPattern.scripted.table.deleteHeader', { defaultMessage: 'Delete', }), diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/index.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/index.ts diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/__jest__/scripted_field_table.test.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/scripted_field_table.test.tsx similarity index 75% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/__jest__/scripted_field_table.test.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/scripted_field_table.test.tsx index 5be963ad94b7d..914d80f9f61d7 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/__jest__/scripted_field_table.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/scripted_field_table.test.tsx @@ -18,9 +18,10 @@ */ import React from 'react'; -import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers'; +import { shallow } from 'enzyme'; import { ScriptedFieldsTable } from '../scripted_fields_table'; +import { IIndexPattern } from '../../../../../../../../../plugins/data/common/index_patterns'; jest.mock('@elastic/eui', () => ({ EuiTitle: 'eui-title', @@ -36,18 +37,20 @@ jest.mock('@elastic/eui', () => ({ default: () => {}, }, })); -jest.mock('../components/header', () => ({ Header: 'header' })); -jest.mock('../components/call_outs', () => ({ CallOuts: 'call-outs' })); -jest.mock('../components/table', () => ({ +jest.mock('./components/header', () => ({ Header: 'header' })); +jest.mock('./components/call_outs', () => ({ CallOuts: 'call-outs' })); +jest.mock('./components/table', () => ({ // Note: this seems to fix React complaining about non lowercase attributes Table: () => { return 'table'; }, })); + jest.mock('ui/scripting_languages', () => ({ getSupportedScriptingLanguages: () => ['painless'], getDeprecatedScriptingLanguages: () => [], })); + jest.mock('ui/documentation_links', () => ({ documentationLinks: { scriptedFields: { @@ -61,16 +64,22 @@ const helpers = { getRouteHref: () => '#', }; -const indexPattern = { - getScriptedFields: () => [ - { name: 'ScriptedField', lang: 'painless', script: 'x++' }, - { name: 'JustATest', lang: 'painless', script: 'z++' }, - ], -}; +const getIndexPatternMock = (mockedFields: any = {}) => ({ ...mockedFields } as IIndexPattern); describe('ScriptedFieldsTable', () => { - it('should render normally', async () => { - const component = shallowWithI18nProvider( + let indexPattern: IIndexPattern; + + beforeEach(() => { + indexPattern = getIndexPatternMock({ + getScriptedFields: () => [ + { name: 'ScriptedField', lang: 'painless', script: 'x++' }, + { name: 'JustATest', lang: 'painless', script: 'z++' }, + ], + }); + }); + + test('should render normally', async () => { + const component = shallow( ); @@ -82,8 +91,8 @@ describe('ScriptedFieldsTable', () => { expect(component).toMatchSnapshot(); }); - it('should filter based on the query bar', async () => { - const component = shallowWithI18nProvider( + test('should filter based on the query bar', async () => { + const component = shallow( ); @@ -98,16 +107,16 @@ describe('ScriptedFieldsTable', () => { expect(component).toMatchSnapshot(); }); - it('should filter based on the lang filter', async () => { - const component = shallowWithI18nProvider( + test('should filter based on the lang filter', async () => { + const component = shallow( [ { name: 'ScriptedField', lang: 'painless', script: 'x++' }, { name: 'JustATest', lang: 'painless', script: 'z++' }, { name: 'Bad', lang: 'somethingElse', script: 'z++' }, ], - }} + })} helpers={helpers} /> ); @@ -123,12 +132,12 @@ describe('ScriptedFieldsTable', () => { expect(component).toMatchSnapshot(); }); - it('should hide the table if there are no scripted fields', async () => { - const component = shallowWithI18nProvider( + test('should hide the table if there are no scripted fields', async () => { + const component = shallow( [], - }} + })} helpers={helpers} /> ); @@ -141,22 +150,22 @@ describe('ScriptedFieldsTable', () => { expect(component).toMatchSnapshot(); }); - it('should show a delete modal', async () => { - const component = shallowWithI18nProvider( + test('should show a delete modal', async () => { + const component = shallow( ); await component.update(); // Fire `componentWillMount()` - component.instance().startDeleteField({ name: 'ScriptedField' }); + component.instance().startDeleteField({ name: 'ScriptedField', lang: '', script: '' }); await component.update(); // Ensure the modal is visible expect(component).toMatchSnapshot(); }); - it('should delete a field', async () => { + test('should delete a field', async () => { const removeScriptedField = jest.fn(); - const component = shallowWithI18nProvider( + const component = shallow( { ); await component.update(); // Fire `componentWillMount()` - component.instance().startDeleteField({ name: 'ScriptedField' }); + component.instance().startDeleteField({ name: 'ScriptedField', lang: '', script: '' }); + await component.update(); await component.instance().deleteField(); await component.update(); + expect(removeScriptedField).toBeCalled(); }); }); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/scripted_fields_table.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/scripted_fields_table.tsx similarity index 60% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/scripted_fields_table.js rename to src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/scripted_fields_table.tsx index 69343a5175a25..ba044296a693a 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/scripted_fields_table.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/scripted_fields_table.tsx @@ -18,31 +18,42 @@ */ import React, { Component } from 'react'; -import PropTypes from 'prop-types'; import { getSupportedScriptingLanguages, getDeprecatedScriptingLanguages, } from 'ui/scripting_languages'; import { documentationLinks } from 'ui/documentation_links'; -import { EuiSpacer, EuiOverlayMask, EuiConfirmModal, EUI_MODAL_CONFIRM_BUTTON } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { Table, Header, CallOuts } from './components'; - -export class ScriptedFieldsTable extends Component { - static propTypes = { - indexPattern: PropTypes.object.isRequired, - fieldFilter: PropTypes.string, - scriptedFieldLanguageFilter: PropTypes.string, - helpers: PropTypes.shape({ - redirectToRoute: PropTypes.func.isRequired, - getRouteHref: PropTypes.func.isRequired, - }), - onRemoveField: PropTypes.func, +import { EuiSpacer } from '@elastic/eui'; + +import { Table, Header, CallOuts, DeleteScritpedFieldConfirmationModal } from './components'; +import { ScriptedFieldItem } from './types'; + +import { IIndexPattern } from '../../../../../../../../../plugins/data/public'; + +interface ScriptedFieldsTableProps { + indexPattern: IIndexPattern; + fieldFilter?: string; + scriptedFieldLanguageFilter?: string; + helpers: { + redirectToRoute: Function; + getRouteHref: Function; }; + onRemoveField?: () => void; +} - constructor(props) { +interface ScriptedFieldsTableState { + deprecatedLangsInUse: string[]; + fieldToDelete: ScriptedFieldItem | undefined; + isDeleteConfirmationModalVisible: boolean; + fields: ScriptedFieldItem[]; +} + +export class ScriptedFieldsTable extends Component< + ScriptedFieldsTableProps, + ScriptedFieldsTableState +> { + constructor(props: ScriptedFieldsTableProps) { super(props); this.state = { @@ -64,7 +75,8 @@ export class ScriptedFieldsTable extends Component { const deprecatedLangs = getDeprecatedScriptingLanguages(); const supportedLangs = getSupportedScriptingLanguages(); - for (const { lang } of fields) { + for (const field of fields) { + const lang: string = field.lang; if (deprecatedLangs.includes(lang) || !supportedLangs.includes(lang)) { deprecatedLangsInUse.push(lang); } @@ -91,7 +103,8 @@ export class ScriptedFieldsTable extends Component { let filteredFields = languageFilteredFields; if (fieldFilter) { - const normalizedFieldFilter = this.props.fieldFilter.toLowerCase(); + const normalizedFieldFilter = fieldFilter.toLowerCase(); + filteredFields = languageFilteredFields.filter(field => field.name.toLowerCase().includes(normalizedFieldFilter) ); @@ -100,18 +113,7 @@ export class ScriptedFieldsTable extends Component { return filteredFields; }; - renderCallOuts() { - const { deprecatedLangsInUse } = this.state; - - return ( - - ); - } - - startDeleteField = field => { + startDeleteField = (field: ScriptedFieldItem) => { this.setState({ fieldToDelete: field, isDeleteConfirmationModalVisible: true }); }; @@ -124,55 +126,29 @@ export class ScriptedFieldsTable extends Component { const { fieldToDelete } = this.state; indexPattern.removeScriptedField(fieldToDelete); - onRemoveField && onRemoveField(); - this.fetchFields(); - this.hideDeleteConfirmationModal(); - }; - renderDeleteConfirmationModal() { - const { fieldToDelete } = this.state; - - if (!fieldToDelete) { - return null; + if (onRemoveField) { + onRemoveField(); } - const title = i18n.translate('kbn.management.editIndexPattern.scripted.deleteFieldLabel', { - defaultMessage: "Delete scripted field '{fieldName}'?", - values: { fieldName: fieldToDelete.name }, - }); - const cancelButtonText = i18n.translate( - 'kbn.management.editIndexPattern.scripted.deleteField.cancelButton', - { defaultMessage: 'Cancel' } - ); - const confirmButtonText = i18n.translate( - 'kbn.management.editIndexPattern.scripted.deleteField.deleteButton', - { defaultMessage: 'Delete' } - ); - - return ( - - - - ); - } + this.fetchFields(); + this.hideDeleteConfirmationModal(); + }; render() { const { helpers, indexPattern } = this.props; + const { fieldToDelete, deprecatedLangsInUse } = this.state; const items = this.getFilteredItems(); return ( -
+ <>
- {this.renderCallOuts()} + @@ -183,8 +159,14 @@ export class ScriptedFieldsTable extends Component { deleteField={this.startDeleteField} /> - {this.renderDeleteConfirmationModal()} -
+ {fieldToDelete && ( + + )} + ); } } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/types.ts b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/types.ts new file mode 100644 index 0000000000000..c1227393c561f --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/types.ts @@ -0,0 +1,25 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** @internal **/ +export interface ScriptedFieldItem { + name: string; + lang: string; + script: string; +} diff --git a/src/legacy/ui/public/scripting_languages/index.js b/src/legacy/ui/public/scripting_languages/index.ts similarity index 83% rename from src/legacy/ui/public/scripting_languages/index.js rename to src/legacy/ui/public/scripting_languages/index.ts index 2f43a44d66068..283a3273a2a5d 100644 --- a/src/legacy/ui/public/scripting_languages/index.js +++ b/src/legacy/ui/public/scripting_languages/index.ts @@ -17,23 +17,25 @@ * under the License. */ +import { IHttpService } from 'angular'; +import { i18n } from '@kbn/i18n'; + import chrome from '../chrome'; import { toastNotifications } from '../notify'; -import { i18n } from '@kbn/i18n'; -export function getSupportedScriptingLanguages() { +export function getSupportedScriptingLanguages(): string[] { return ['painless']; } -export function getDeprecatedScriptingLanguages() { +export function getDeprecatedScriptingLanguages(): string[] { return []; } -export function GetEnabledScriptingLanguagesProvider($http) { +export function GetEnabledScriptingLanguagesProvider($http: IHttpService) { return () => { return $http .get(chrome.addBasePath('/api/kibana/scripts/languages')) - .then(res => res.data) + .then((res: any) => res.data) .catch(() => { toastNotifications.addDanger( i18n.translate('common.ui.scriptingLanguages.errorFetchingToastDescription', { From 52747c9c1775c0e961a06ff808f8fac6fb189743 Mon Sep 17 00:00:00 2001 From: Brandon Kobel Date: Mon, 13 Apr 2020 09:02:48 -0700 Subject: [PATCH 06/11] Only fetching TaskManager's available tasks once per call to fillPool (#61991) Co-authored-by: Elastic Machine --- .../task_manager/server/lib/fill_pool.test.ts | 4 +- .../task_manager/server/lib/fill_pool.ts | 49 +++++++++---------- 2 files changed, 24 insertions(+), 29 deletions(-) diff --git a/x-pack/plugins/task_manager/server/lib/fill_pool.test.ts b/x-pack/plugins/task_manager/server/lib/fill_pool.test.ts index 3863fdaf9da62..ebb72c3ed36d6 100644 --- a/x-pack/plugins/task_manager/server/lib/fill_pool.test.ts +++ b/x-pack/plugins/task_manager/server/lib/fill_pool.test.ts @@ -10,7 +10,7 @@ import { fillPool } from './fill_pool'; import { TaskPoolRunResult } from '../task_pool'; describe('fillPool', () => { - test('stops filling when there are no more tasks in the store', async () => { + test('stops filling when pool runs all claimed tasks, even if there is more capacity', async () => { const tasks = [ [1, 2, 3], [4, 5], @@ -22,7 +22,7 @@ describe('fillPool', () => { await fillPool(fetchAvailableTasks, converter, run); - expect(_.flattenDeep(run.args)).toEqual([1, 2, 3, 4, 5]); + expect(_.flattenDeep(run.args)).toEqual([1, 2, 3]); }); test('stops filling when the pool has no more capacity', async () => { diff --git a/x-pack/plugins/task_manager/server/lib/fill_pool.ts b/x-pack/plugins/task_manager/server/lib/fill_pool.ts index 60470b22c00a9..9e4894587203d 100644 --- a/x-pack/plugins/task_manager/server/lib/fill_pool.ts +++ b/x-pack/plugins/task_manager/server/lib/fill_pool.ts @@ -5,12 +5,12 @@ */ import { performance } from 'perf_hooks'; -import { after } from 'lodash'; import { TaskPoolRunResult } from '../task_pool'; export enum FillPoolResult { NoTasksClaimed = 'NoTasksClaimed', RanOutOfCapacity = 'RanOutOfCapacity', + PoolFilled = 'PoolFilled', } type BatchRun = (tasks: T[]) => Promise; @@ -35,33 +35,28 @@ export async function fillPool( run: BatchRun ): Promise { performance.mark('fillPool.start'); - const markClaimedTasksOnRerunCycle = after(2, () => - performance.mark('fillPool.claimedOnRerunCycle') - ); - while (true) { - const instances = await fetchAvailableTasks(); + const instances = await fetchAvailableTasks(); - if (!instances.length) { - performance.mark('fillPool.bailNoTasks'); - performance.measure( - 'fillPool.activityDurationUntilNoTasks', - 'fillPool.start', - 'fillPool.bailNoTasks' - ); - return FillPoolResult.NoTasksClaimed; - } - markClaimedTasksOnRerunCycle(); - const tasks = instances.map(converter); + if (!instances.length) { + performance.mark('fillPool.bailNoTasks'); + performance.measure( + 'fillPool.activityDurationUntilNoTasks', + 'fillPool.start', + 'fillPool.bailNoTasks' + ); + return FillPoolResult.NoTasksClaimed; + } + const tasks = instances.map(converter); - if ((await run(tasks)) === TaskPoolRunResult.RanOutOfCapacity) { - performance.mark('fillPool.bailExhaustedCapacity'); - performance.measure( - 'fillPool.activityDurationUntilExhaustedCapacity', - 'fillPool.start', - 'fillPool.bailExhaustedCapacity' - ); - return FillPoolResult.RanOutOfCapacity; - } - performance.mark('fillPool.cycle'); + if ((await run(tasks)) === TaskPoolRunResult.RanOutOfCapacity) { + performance.mark('fillPool.bailExhaustedCapacity'); + performance.measure( + 'fillPool.activityDurationUntilExhaustedCapacity', + 'fillPool.start', + 'fillPool.bailExhaustedCapacity' + ); + return FillPoolResult.RanOutOfCapacity; } + performance.mark('fillPool.cycle'); + return FillPoolResult.PoolFilled; } From 0c09a7756fb76f26b3da81b58af00b4555d0a213 Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Mon, 13 Apr 2020 09:47:03 -0700 Subject: [PATCH 07/11] Added connectors loading spinner to show the actions forms only when connectors is loaded (#63211) * Added connectors loading spinner to show the actions forms only when connectors is loaded * Added warning message for actions with removed connectors * Fixed loading connectors spinner --- .../action_connector_form/action_form.tsx | 180 +++++++++++------- 1 file changed, 106 insertions(+), 74 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx index 5890d9fe07f0e..87a8d572fda0f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx @@ -24,6 +24,7 @@ import { EuiToolTip, EuiIconTip, EuiLink, + EuiCallOut, } from '@elastic/eui'; import { HttpSetup, ToastsApi } from 'kibana/public'; import { loadActionTypes, loadAllActions } from '../../lib/action_connector_api'; @@ -85,8 +86,10 @@ export const ActionForm = ({ ); const [isAddActionPanelOpen, setIsAddActionPanelOpen] = useState(true); const [connectors, setConnectors] = useState([]); + const [isLoadingConnectors, setIsLoadingConnectors] = useState(false); const [isLoadingActionTypes, setIsLoadingActionTypes] = useState(false); const [actionTypesIndex, setActionTypesIndex] = useState(undefined); + const [emptyActionsIds, setEmptyActionsIds] = useState([]); // load action types useEffect(() => { @@ -128,6 +131,7 @@ export const ActionForm = ({ async function loadConnectors() { try { + setIsLoadingConnectors(true); const actionsResponse = await loadAllActions({ http }); setConnectors(actionsResponse); } catch (e) { @@ -139,6 +143,8 @@ export const ActionForm = ({ } ), }); + } finally { + setIsLoadingConnectors(false); } } const preconfiguredMessage = i18n.translate( @@ -387,13 +393,25 @@ export const ActionForm = ({ > + emptyActionsIds.find((emptyId: string) => actionItem.id === emptyId) ? ( + + ) : ( + + ) } actions={[ - {actions.map((actionItem: AlertAction, index: number) => { - const actionConnector = connectors.find(field => field.id === actionItem.id); - // connectors doesn't exists - if (!actionConnector) { - return getAddConnectorsForm(actionItem, index); - } + const alertActionsList = actions.map((actionItem: AlertAction, index: number) => { + const actionConnector = connectors.find(field => field.id === actionItem.id); + // connectors doesn't exists + if (!actionConnector) { + return getAddConnectorsForm(actionItem, index); + } + + const actionErrors: { errors: IErrorObject } = actionTypeRegistry + .get(actionItem.actionTypeId) + ?.validateParams(actionItem.params); - const actionErrors: { errors: IErrorObject } = actionTypeRegistry - .get(actionItem.actionTypeId) - ?.validateParams(actionItem.params); + return getActionTypeForm(actionItem, actionConnector, actionErrors, index); + }); - return getActionTypeForm(actionItem, actionConnector, actionErrors, index); - })} - - {isAddActionPanelOpen === false ? ( - setIsAddActionPanelOpen(true)} - > + return ( + + {isLoadingConnectors ? ( + - - ) : null} - {isAddActionPanelOpen ? ( + + ) : ( - - - -
- -
-
-
- {hasDisabledByLicenseActionTypes && ( - - -
- + {alertActionsList} + + {isAddActionPanelOpen === false ? ( + setIsAddActionPanelOpen(true)} + > + + + ) : null} + {isAddActionPanelOpen ? ( + + + + +
- -
-
-
- )} -
- - - {isLoadingActionTypes ? ( - - - - ) : ( - actionTypeNodes - )} - +
+
+
+ {hasDisabledByLicenseActionTypes && ( + + +
+ + + +
+
+
+ )} +
+ + + {isLoadingActionTypes ? ( + + + + ) : ( + actionTypeNodes + )} + +
+ ) : null} - ) : null} + )} {actionTypesIndex && activeActionItem ? ( Date: Mon, 13 Apr 2020 11:22:11 -0600 Subject: [PATCH 08/11] [Maps] turn on blended layer for geojson upload (#63200) Co-authored-by: Elastic Machine --- .../client_file_source/geojson_file_source.js | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js b/x-pack/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js index 1003f8329da22..df11fe9f32770 100644 --- a/x-pack/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js +++ b/x-pack/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js @@ -10,11 +10,11 @@ import { ES_GEO_FIELD_TYPE, GEOJSON_FILE, DEFAULT_MAX_RESULT_WINDOW, + SCALING_TYPES, } from '../../../../common/constants'; import { ClientFileCreateSourceEditor } from './create_client_file_source_editor'; import { ESSearchSource } from '../es_search_source'; import uuid from 'uuid/v4'; -import _ from 'lodash'; import { i18n } from '@kbn/i18n'; import { registerSource } from '../source_registry'; @@ -91,23 +91,22 @@ const viewIndexedData = ( importErrorHandler(indexResponses); return; } - const { fields, id } = indexPatternResp; - const geoFieldArr = fields.filter(field => - Object.values(ES_GEO_FIELD_TYPE).includes(field.type) - ); - const geoField = _.get(geoFieldArr, '[0].name'); - const indexPatternId = id; + const { fields, id: indexPatternId } = indexPatternResp; + const geoField = fields.find(field => Object.values(ES_GEO_FIELD_TYPE).includes(field.type)); if (!indexPatternId || !geoField) { addAndViewSource(null); } else { - // Only turn on bounds filter for large doc counts - const filterByMapBounds = indexDataResp.docCount > DEFAULT_MAX_RESULT_WINDOW; const source = new ESSearchSource( { id: uuid(), indexPatternId, - geoField, - filterByMapBounds, + geoField: geoField.name, + // Only turn on bounds filter for large doc counts + filterByMapBounds: indexDataResp.docCount > DEFAULT_MAX_RESULT_WINDOW, + scalingType: + geoField.type === ES_GEO_FIELD_TYPE.GEO_POINT + ? SCALING_TYPES.CLUSTERS + : SCALING_TYPES.LIMIT, }, inspectorAdapters ); From 5559b09dccbf682686a48d7bfc3b4123e3aa245c Mon Sep 17 00:00:00 2001 From: Ryland Herrick Date: Mon, 13 Apr 2020 12:24:09 -0500 Subject: [PATCH 09/11] Consistent terminology around cypress test data (#63279) * Uses "data" or "test data" when referring to the general idea * Uses "archive" when referring to the specific data/implementation * Adds a few grammar/spelling tweaks --- x-pack/legacy/plugins/siem/cypress/README.md | 32 +++++++------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/x-pack/legacy/plugins/siem/cypress/README.md b/x-pack/legacy/plugins/siem/cypress/README.md index a031fea172be5..89bafce9c9dc8 100644 --- a/x-pack/legacy/plugins/siem/cypress/README.md +++ b/x-pack/legacy/plugins/siem/cypress/README.md @@ -129,31 +129,21 @@ yarn cypress:run-as-ci ``` Note that with this type of execution you don't need to have running a kibana and elasticsearch instance. This is because - the command, as it would happen in the CI, will launch the instances. The elasticsearch instance will be fed with the data - placed in: `x-pack/test/siem_cypress/es_archives` + the command, as it would happen in the CI, will launch the instances. The elasticsearch instance will be fed data + found in: `x-pack/test/siem_cypress/es_archives` As in this case we want to mimic a CI execution we want to execute the tests with the same set of data, this is why in this case does not make sense to override Cypress environment variables. ### Test data -As said before when running the tests as Jenkins the tests are fed with the data placed in: `x-pack/test/siem_cypress/es_archives`. +As mentioned above, when running the tests as Jenkins the tests are populated with data ("archives") found in: `x-pack/test/siem_cypress/es_archives`. -Currently there are two different ways of feeding data: -1. By default -2. Specifying a specific set of data for a specific test +By default, each test is populated with some base data: an empty kibana index and a set of auditbeat data (the `empty_kibana` and `auditbeat` archives, respectively). This is usually enough to cover most of the scenarios that we are testing. -#### By default +#### Running tests with additional archives -When a execution of the test is going to be done an empty kibana and a set of audibteat data are loaded (empty_kibana and auditbeat). With this data usually is enough to cover most of the scenarios that we are testing. - -#### Running tests with custom data - -Sometimes the default data is not enough and we need a specific set of data in order to being able to test the desired behaviour. - -In that case in the hooks of the test use the function `esArchiverLoad` to load the set of data neeed and `esArchiverUnload` to remove the changes done in the data. - -Example: +When the base data is insufficient, one can specify additional archives. Use `esArchiverLoad` to load the necessary archive, and `esArchiverUnload` to remove the archive from elasticsearch: ```typescript import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; @@ -174,11 +164,11 @@ describe('This are going to be a set of tests', () => { ``` -Note that loading and unloading data takes a signifcant amount of time so try to minimize the use of it when possible. +Note that loading and unloading data take a significant amount of time, so try to minimize their use. -### Current sets of data +### Current archives -The current sets of data can be found in: `x-pack/test/siem_cypress/es_archives` folder. +The current archives can be found in `x-pack/test/siem_cypress/es_archives/`. - auditbeat - Auditbeat data generated in Sep, 2019 with the following hosts present: @@ -197,9 +187,9 @@ The current sets of data can be found in: `x-pack/test/siem_cypress/es_archives` - signals - Set of data with 108 opened signals linked to "Signals test" custom rule. -### How to generate new test data +### How to generate a new archive -We are using es_archiver in order to generate the data that our Cypress tests needs. +We are using es_archiver in order to manage the data that our Cypress tests needs. 1. Setup if possible a clean instance of kibana and elasticsearch (if not, possible please try to clean the data that you are going to generate). 2. With the kibana and elasticsearch instance up and running, create the data that you need for your test. From a58cc5da12a250610aae1cee078124ee350fd29b Mon Sep 17 00:00:00 2001 From: patrykkopycinski Date: Mon, 13 Apr 2020 19:40:48 +0200 Subject: [PATCH 10/11] [SIEM] Fix AlertsTable id (#63368) --- .../siem/public/components/alerts_viewer/alerts_table.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/siem/public/components/alerts_viewer/alerts_table.tsx b/x-pack/legacy/plugins/siem/public/components/alerts_viewer/alerts_table.tsx index 05d8f97bb8849..dd608babef48f 100644 --- a/x-pack/legacy/plugins/siem/public/components/alerts_viewer/alerts_table.tsx +++ b/x-pack/legacy/plugins/siem/public/components/alerts_viewer/alerts_table.tsx @@ -17,7 +17,7 @@ export interface OwnProps { start: number; } -const ALERTS_TABLE_ID = 'timeline-alerts-table'; +const ALERTS_TABLE_ID = 'alerts-table'; const defaultAlertsFilters: Filter[] = [ { meta: { From 1084b1c7b9ec09c98baa74ad56db3f5d2a394826 Mon Sep 17 00:00:00 2001 From: Maggie Ghamry <46542915+maggieghamry@users.noreply.github.com> Date: Mon, 13 Apr 2020 13:41:12 -0400 Subject: [PATCH 11/11] Update to pagination for workpad and templates (#62050) Added logic to hide pagination if no Canvas workpads exists, and disable the previous/next arrows if there is only one page, for both workapds and templates --- .../components/workpad_loader/workpad_loader.js | 16 +++++++++++----- .../workpad_templates/workpad_templates.js | 12 +++++++----- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_loader/workpad_loader.js b/x-pack/legacy/plugins/canvas/public/components/workpad_loader/workpad_loader.js index 9b30b3e1ec7ca..30d4ded8571c5 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_loader/workpad_loader.js +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_loader/workpad_loader.js @@ -266,11 +266,17 @@ export class WorkpadLoader extends React.PureComponent { data-test-subj="canvasWorkpadLoaderTable" /> - - - - - + {rows.length > 0 && ( + + + + + + )} ); diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_templates/workpad_templates.js b/x-pack/legacy/plugins/canvas/public/components/workpad_templates/workpad_templates.js index c80db544bf370..a9a157f5675f8 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_templates/workpad_templates.js +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_templates/workpad_templates.js @@ -113,11 +113,13 @@ export class WorkpadTemplates extends React.PureComponent { className="canvasWorkpad__dropzoneTable canvasWorkpad__dropzoneTable--tags" /> - - - - - + {rows.length > 0 && ( + + + + + + )} ); };