From d477e825698352b6b3b56bd601af9b02cd9b242e Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Mon, 17 Feb 2020 10:46:10 +0100 Subject: [PATCH 01/42] creating custom action index --- .../create_custom_action_index.ts | 95 +++++++++++++++++++ .../custom_action/custom_action_types.d.ts | 16 ++++ .../settings/apm_indices/get_apm_indices.ts | 4 +- 3 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 x-pack/legacy/plugins/apm/server/lib/settings/custom_action/create_custom_action_index.ts create mode 100644 x-pack/legacy/plugins/apm/server/lib/settings/custom_action/custom_action_types.d.ts diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/custom_action/create_custom_action_index.ts b/x-pack/legacy/plugins/apm/server/lib/settings/custom_action/create_custom_action_index.ts new file mode 100644 index 0000000000000..2c143c2477c90 --- /dev/null +++ b/x-pack/legacy/plugins/apm/server/lib/settings/custom_action/create_custom_action_index.ts @@ -0,0 +1,95 @@ +/* + * 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 { IClusterClient } from 'kibana/server'; +import { CallCluster } from 'src/legacy/core_plugins/elasticsearch'; +import { APMConfig } from '../../../../../../../plugins/apm/server'; +import { getApmIndicesConfig } from '../apm_indices/get_apm_indices'; + +export const createApmCustomActionIndex = async ({ + esClient, + config +}: { + esClient: IClusterClient; + config: APMConfig; +}) => { + try { + const index = getApmIndicesConfig(config).apmCustomActionIndex; + const { callAsInternalUser } = esClient; + const indexExists = await callAsInternalUser('indices.exists', { index }); + const result = indexExists + ? await updateExistingIndex(index, callAsInternalUser) + : await createNewIndex(index, callAsInternalUser); + + if (!result.acknowledged) { + const resultError = + result && result.error && JSON.stringify(result.error); + throw new Error( + `Unable to create APM Custom Actions index '${index}': ${resultError}` + ); + } + } catch (e) { + // eslint-disable-next-line no-console + console.error('Could not create APM Custom Actions index:', e.message); + } +}; + +const createNewIndex = (index: string, callWithInternalUser: CallCluster) => + callWithInternalUser('indices.create', { + index, + body: { + settings: { 'index.auto_expand_replicas': '0-1' }, + mappings: { properties: mappingProperties } + } + }); + +const updateExistingIndex = ( + index: string, + callWithInternalUser: CallCluster +) => + callWithInternalUser('indices.putMapping', { + index, + body: { properties: mappingProperties } + }); + +const mappingProperties = { + '@timestamp': { + type: 'date' + }, + label: { + type: 'text' + }, + url: { + type: 'keyword' + }, + actionId: { + type: 'keyword' + }, + filters: { + properties: { + service: { + properties: { + environment: { + type: 'keyword' + }, + name: { + type: 'keyword' + } + } + }, + transaction: { + properties: { + name: { + type: 'keyword' + }, + type: { + type: 'keyword' + } + } + } + } + } +}; diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/custom_action/custom_action_types.d.ts b/x-pack/legacy/plugins/apm/server/lib/settings/custom_action/custom_action_types.d.ts new file mode 100644 index 0000000000000..7bd85b7aadc0f --- /dev/null +++ b/x-pack/legacy/plugins/apm/server/lib/settings/custom_action/custom_action_types.d.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface CustomAction { + '@timestamp': number; + label: string; + url: string; + actionId: 'TRACE'; + filters?: { + service?: { name?: string; environment?: string }; + transaction?: { type?: string; name?: string }; + }; +} diff --git a/x-pack/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts b/x-pack/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts index 00493e53f06dd..0bcf440a4d0ac 100644 --- a/x-pack/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts +++ b/x-pack/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts @@ -25,6 +25,7 @@ export interface ApmIndicesConfig { 'apm_oss.transactionIndices': string; 'apm_oss.metricsIndices': string; apmAgentConfigurationIndex: string; + apmCustomActionIndex: string; } export type ApmIndicesName = keyof ApmIndicesConfig; @@ -52,7 +53,8 @@ export function getApmIndicesConfig(config: APMConfig): ApmIndicesConfig { 'apm_oss.transactionIndices': config['apm_oss.transactionIndices'], 'apm_oss.metricsIndices': config['apm_oss.metricsIndices'], // system indices, not configurable - apmAgentConfigurationIndex: '.apm-agent-configuration' + apmAgentConfigurationIndex: '.apm-agent-configuration', + apmCustomActionIndex: '.apm-custom-action' }; } From f1fe01af0113f33f5bc80ed4f12a5ad3e5b45132 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Mon, 17 Feb 2020 13:02:45 +0100 Subject: [PATCH 02/42] reverting service form to service section --- .../AddEditFlyout/ServiceSection.tsx} | 22 ++++++++--------- .../AddEditFlyout/index.tsx | 4 ++-- .../CustomActionsFlyout/index.tsx | 24 +++++++++---------- 3 files changed, 25 insertions(+), 25 deletions(-) rename x-pack/legacy/plugins/apm/public/components/{shared/ServiceForm/index.tsx => app/Settings/AgentConfigurations/AddEditFlyout/ServiceSection.tsx} (82%) diff --git a/x-pack/legacy/plugins/apm/public/components/shared/ServiceForm/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AddEditFlyout/ServiceSection.tsx similarity index 82% rename from x-pack/legacy/plugins/apm/public/components/shared/ServiceForm/index.tsx rename to x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AddEditFlyout/ServiceSection.tsx index ab3accec90d1d..dfa093b401924 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/ServiceForm/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AddEditFlyout/ServiceSection.tsx @@ -10,12 +10,12 @@ import { i18n } from '@kbn/i18n'; import { omitAllOption, getOptionLabel -} from '../../../../../../../plugins/apm/common/agent_configuration_constants'; -import { useFetcher } from '../../../hooks/useFetcher'; -import { SelectWithPlaceholder } from '../SelectWithPlaceholder'; +} from '../../../../../../common/agent_configuration_constants'; +import { useFetcher } from '../../../../../hooks/useFetcher'; +import { SelectWithPlaceholder } from '../../../../shared/SelectWithPlaceholder'; const SELECT_PLACEHOLDER_LABEL = `- ${i18n.translate( - 'xpack.apm.settings.agentConf.flyOut.serviceForm.selectPlaceholder', + 'xpack.apm.settings.agentConf.flyOut.serviceSection.selectPlaceholder', { defaultMessage: 'Select' } )} -`; @@ -27,7 +27,7 @@ interface Props { onEnvironmentChange: (env: string) => void; } -export function ServiceForm({ +export function ServiceSection({ isReadOnly, serviceName, onServiceNameChange, @@ -60,7 +60,7 @@ export function ServiceForm({ ); const ALREADY_CONFIGURED_TRANSLATED = i18n.translate( - 'xpack.apm.settings.agentConf.flyOut.serviceForm.alreadyConfiguredOption', + 'xpack.apm.settings.agentConf.flyOut.serviceSection.alreadyConfiguredOption', { defaultMessage: 'already configured' } ); @@ -83,7 +83,7 @@ export function ServiceForm({

{i18n.translate( - 'xpack.apm.settings.agentConf.flyOut.serviceForm.title', + 'xpack.apm.settings.agentConf.flyOut.serviceSection.title', { defaultMessage: 'Service' } )}

@@ -93,13 +93,13 @@ export function ServiceForm({ - void; } export const CustomActionsFlyout = ({ onClose }: Props) => { - const [serviceName, setServiceName] = useState(''); - const [environment, setEnvironment] = useState(''); - const [label, setLabel] = useState(''); - const [url, setURL] = useState(''); + // const [serviceName, setServiceName] = useState(''); + // const [environment, setEnvironment] = useState(''); + // const [label, setLabel] = useState(''); + // const [url, setURL] = useState(''); return ( @@ -58,23 +58,23 @@ export const CustomActionsFlyout = ({ onClose }: Props) => { )}

- + {/* + /> */} - + {/* + /> */} From 0618007e26df81bc494497ed607b5a30c072c57f Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Wed, 19 Feb 2020 14:26:03 +0100 Subject: [PATCH 03/42] creating useForm hooks and fields section --- .../CustomActionsFlyout/FieldsSection.tsx | 127 ++++++++++++++++++ .../CustomActionsFlyout/SettingsSection.tsx | 81 ----------- .../CustomActionsFlyout/index.tsx | 67 ++++----- .../CustomActionsFlyout/useForm.tsx | 82 +++++++++++ 4 files changed, 246 insertions(+), 111 deletions(-) create mode 100644 x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FieldsSection.tsx delete mode 100644 x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/SettingsSection.tsx create mode 100644 x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/useForm.tsx diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FieldsSection.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FieldsSection.tsx new file mode 100644 index 0000000000000..d4948fdf403de --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FieldsSection.tsx @@ -0,0 +1,127 @@ +/* + * 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 { + EuiFlexGroup, + EuiFlexItem, + EuiSelect, + EuiFieldText, + EuiButtonEmpty +} from '@elastic/eui'; +import React, { useState, useEffect } from 'react'; +import { i18n } from '@kbn/i18n'; +import { isEmpty } from 'lodash'; + +type FieldKeys = 'fieldName' | 'value'; +export type Field = { + [key in FieldKeys]: string; +}; + +const DEFAULT_OPTION = { + value: 'DEFAULT', + text: i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.flyOut.fields.label', + { + defaultMessage: 'Select fields...' + } + ) +}; + +const emptyField = { fieldName: '', value: '' }; + +const fieldsOptions = [ + DEFAULT_OPTION, + { value: 'service.name', text: 'service.name' }, + { value: 'service.environment', text: 'service.environment' }, + { value: 'transaction.type', text: 'transaction.type' }, + { value: 'transaction.name', text: 'transaction.name' } +]; + +export const FieldsSection = ({ + onFieldsChange +}: { + onFieldsChange: (fields: Field[]) => void; +}) => { + const [fields, setFields] = useState([emptyField] as Field[]); + + const onChangeField = (key: FieldKeys, value: string, idx: number) => { + const copyOfFields = [...fields]; + copyOfFields[idx][key] = value; + setFields(copyOfFields); + }; + + const onRemoveField = (idx: number) => { + const copyOfFields = [...fields]; + copyOfFields.splice(idx, 1); + // When empty, means that it was the last field that got removed, + // so instead on showing empty list, will add a new empty field. + if (isEmpty(copyOfFields)) { + copyOfFields.push(emptyField); + } + setFields(copyOfFields); + }; + + useEffect(() => { + // Sync parent state everytime a change happens in the fields. + onFieldsChange(fields); + }, [fields, onFieldsChange]); + + return ( + <> + {fields.map((field, idx) => { + const fieldId = `field-${idx}`; + return ( + + + { + const indexUsedField = fields.findIndex( + _field => _field.fieldName === option.value + ); + return indexUsedField === -1 || idx === indexUsedField; + })} + value={field.fieldName} + onChange={e => onChangeField('fieldName', e.target.value, idx)} + /> + + + onChangeField('value', e.target.value, idx)} + value={field.value} + /> + + + onRemoveField(idx)} + disabled={!field.fieldName && fields.length === 1} + /> + + + ); + })} + { + setFields(currentFields => [...currentFields, emptyField]); + }} + disabled={fields.length === fieldsOptions.length - 1} + > + {i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.flyout.addAnotherField', + { + defaultMessage: 'Add another field' + } + )} + + + ); +}; diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/SettingsSection.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/SettingsSection.tsx deleted file mode 100644 index 8cb604d367549..0000000000000 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/SettingsSection.tsx +++ /dev/null @@ -1,81 +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 { EuiFieldText, EuiFormRow, EuiSpacer, EuiTitle } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import React from 'react'; - -interface Props { - label: string; - onLabelChange: (label: string) => void; - url: string; - onURLChange: (url: string) => void; -} - -export const SettingsSection = ({ - label, - onLabelChange, - url, - onURLChange -}: Props) => { - return ( - <> - -

- {i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.flyout.settingsSection.title', - { defaultMessage: 'Action' } - )} -

-
- - - { - onLabelChange(e.target.value); - }} - /> - - - { - onURLChange(e.target.value); - }} - /> - - - ); -}; diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx index 0a54fcabdbb52..318e711036285 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx @@ -13,27 +13,51 @@ import { EuiFlyoutFooter, EuiFlyoutHeader, EuiPortal, - // EuiSpacer, EuiText, - EuiTitle + EuiTitle, + EuiSelect, + EuiFieldText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React from 'react'; -// import { SettingsSection } from './SettingsSection'; -// import { ServiceForm } from '../../../../../shared/ServiceForm'; +import React, { useState } from 'react'; +import { isEmpty } from 'lodash'; +import { FormType, useForm } from './useForm'; +import { FieldsSection, Field } from './FieldsSection'; interface Props { onClose: () => void; } +const actionFields: FormType = [ + { + type: 'text', + name: 'label', + label: 'Label', + helpText: 'Labels can be a maximum of 128 characters', + placeholder: 'e.g. Support tickets' + }, + { + type: 'text', + name: 'url', + label: 'URL', + helpText: 'You can use relative paths by prefixing with e.g. /dashboards', + placeholder: 'e.g. https://www.elastic.co/' + } +]; + export const CustomActionsFlyout = ({ onClose }: Props) => { - // const [serviceName, setServiceName] = useState(''); - // const [environment, setEnvironment] = useState(''); - // const [label, setLabel] = useState(''); - // const [url, setURL] = useState(''); + const { label, url, Form } = useForm({ + fields: actionFields, + title: i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.flyOut.action.title', + { defaultMessage: 'Action' } + ) + }); + const [fields, setFields] = useState([] as Field[]); + return ( - +

@@ -58,23 +82,9 @@ export const CustomActionsFlyout = ({ onClose }: Props) => { )}

- {/* - */} - - {/* +
- */} + @@ -89,10 +99,7 @@ export const CustomActionsFlyout = ({ onClose }: Props) => { - + {i18n.translate( 'xpack.apm.settings.customizeUI.customActions.flyout.save', { diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/useForm.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/useForm.tsx new file mode 100644 index 0000000000000..892ec6e5250e7 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/useForm.tsx @@ -0,0 +1,82 @@ +/* + * 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. + */ +/* eslint-disable react-hooks/exhaustive-deps */ +import { + EuiFieldText, + EuiForm, + EuiFormRow, + EuiTitle, + EuiText +} from '@elastic/eui'; +import React, { useState, useMemo, useEffect } from 'react'; + +interface Field { + type: 'text'; + name: string; + label: string; + helpText: string; + placeholder: string; + required?: boolean; +} + +export type FormType = Field[]; + +export const useForm = ({ + fields, + title, + subtitle +}: { + fields: FormType; + title?: string; + subtitle?: string; +}) => { + const [data, setData] = useState({}); + + const syncData = (values: any) => { + setData(values); + }; + + const Form = () => { + const [formValues, setFormValues] = useState({}); + useEffect(() => { + syncData(formValues); + }, [formValues]); + return ( + <> + {title && ( + +

{title}

+
+ )} + {subtitle && {subtitle}} + + {fields.map(field => { + return ( + + { + setFormValues(values => ({ + ...values, + [field.name]: e.target.value + })); + }} + /> + + ); + })} + + + ); + }; + + return { ...data, Form: useMemo(() => Form, fields) }; +}; From d27587f430464ce86d6134bfceb22e5eed9aaa6d Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Thu, 20 Feb 2020 09:52:52 +0100 Subject: [PATCH 04/42] adding react-hook-form --- package.json | 1 + .../CustomActionsFlyout/FieldsSection.tsx | 127 ------------- .../CustomActionsFlyout/FiltersSection.tsx | 149 +++++++++++++++ .../CustomActionsFlyout/index.tsx | 172 +++++++++++------- yarn.lock | 5 + 5 files changed, 265 insertions(+), 189 deletions(-) delete mode 100644 x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FieldsSection.tsx create mode 100644 x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx diff --git a/package.json b/package.json index 2c401724c72cd..181e1f961f8d0 100644 --- a/package.json +++ b/package.json @@ -231,6 +231,7 @@ "react-color": "^2.13.8", "react-dom": "^16.12.0", "react-grid-layout": "^0.16.2", + "react-hook-form": "^4.9.6", "react-input-range": "^1.3.0", "react-markdown": "^3.4.1", "react-monaco-editor": "~0.27.0", diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FieldsSection.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FieldsSection.tsx deleted file mode 100644 index d4948fdf403de..0000000000000 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FieldsSection.tsx +++ /dev/null @@ -1,127 +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 { - EuiFlexGroup, - EuiFlexItem, - EuiSelect, - EuiFieldText, - EuiButtonEmpty -} from '@elastic/eui'; -import React, { useState, useEffect } from 'react'; -import { i18n } from '@kbn/i18n'; -import { isEmpty } from 'lodash'; - -type FieldKeys = 'fieldName' | 'value'; -export type Field = { - [key in FieldKeys]: string; -}; - -const DEFAULT_OPTION = { - value: 'DEFAULT', - text: i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.flyOut.fields.label', - { - defaultMessage: 'Select fields...' - } - ) -}; - -const emptyField = { fieldName: '', value: '' }; - -const fieldsOptions = [ - DEFAULT_OPTION, - { value: 'service.name', text: 'service.name' }, - { value: 'service.environment', text: 'service.environment' }, - { value: 'transaction.type', text: 'transaction.type' }, - { value: 'transaction.name', text: 'transaction.name' } -]; - -export const FieldsSection = ({ - onFieldsChange -}: { - onFieldsChange: (fields: Field[]) => void; -}) => { - const [fields, setFields] = useState([emptyField] as Field[]); - - const onChangeField = (key: FieldKeys, value: string, idx: number) => { - const copyOfFields = [...fields]; - copyOfFields[idx][key] = value; - setFields(copyOfFields); - }; - - const onRemoveField = (idx: number) => { - const copyOfFields = [...fields]; - copyOfFields.splice(idx, 1); - // When empty, means that it was the last field that got removed, - // so instead on showing empty list, will add a new empty field. - if (isEmpty(copyOfFields)) { - copyOfFields.push(emptyField); - } - setFields(copyOfFields); - }; - - useEffect(() => { - // Sync parent state everytime a change happens in the fields. - onFieldsChange(fields); - }, [fields, onFieldsChange]); - - return ( - <> - {fields.map((field, idx) => { - const fieldId = `field-${idx}`; - return ( - - - { - const indexUsedField = fields.findIndex( - _field => _field.fieldName === option.value - ); - return indexUsedField === -1 || idx === indexUsedField; - })} - value={field.fieldName} - onChange={e => onChangeField('fieldName', e.target.value, idx)} - /> - - - onChangeField('value', e.target.value, idx)} - value={field.value} - /> - - - onRemoveField(idx)} - disabled={!field.fieldName && fields.length === 1} - /> - - - ); - })} - { - setFields(currentFields => [...currentFields, emptyField]); - }} - disabled={fields.length === fieldsOptions.length - 1} - > - {i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.flyout.addAnotherField', - { - defaultMessage: 'Add another field' - } - )} - - - ); -}; diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx new file mode 100644 index 0000000000000..653672331d642 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx @@ -0,0 +1,149 @@ +/* + * 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 { + EuiFlexGroup, + EuiFlexItem, + EuiSelect, + EuiFieldText, + EuiButtonEmpty, + EuiSpacer, + EuiTitle, + EuiText +} from '@elastic/eui'; +import React, { useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { isEmpty } from 'lodash'; + +type Keys = 'key' | 'value'; +export type Filter = { + [key in Keys]: string; +}; + +const DEFAULT_OPTION = { + value: 'DEFAULT', + text: i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.flyOut.filters.defaultOption', + { + defaultMessage: 'Select fields...' + } + ) +}; + +const filterOptions = [ + DEFAULT_OPTION, + { value: 'service.name', text: 'service.name' }, + { value: 'service.environment', text: 'service.environment' }, + { value: 'transaction.type', text: 'transaction.type' }, + { value: 'transaction.name', text: 'transaction.name' } +]; + +export const FiltersSection = ({ + onFiltersChange +}: { + onFiltersChange: (filters: Filter[]) => void; +}) => { + const [filters, setFilters] = useState([{ key: '', value: '' }]); + + const onChangeFilter = (key: Keys, value: string, idx: number) => { + const copyOfFilters = [...filters]; + copyOfFilters[idx][key] = value; + setFilters(copyOfFilters); + onFiltersChange(copyOfFilters); + }; + + const onRemoveFilter = (idx: number) => { + const copyOfFilters = [...filters]; + copyOfFilters.splice(idx, 1); + // When empty, means that it was the last filter that got removed, + // so instead on showing empty list, will add a new empty filter. + if (isEmpty(copyOfFilters)) { + copyOfFilters.push({ key: '', value: '' }); + } + setFilters(copyOfFilters); + onFiltersChange(copyOfFilters); + }; + + return ( + <> + +

+ {i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.flyout.filters.title', + { + defaultMessage: 'Filters' + } + )} +

+
+ + + {i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.flyout.filters.subtitle', + { + defaultMessage: + 'Add additional values within the same field by comma separating values.' + } + )} + + {filters.map((filter, idx) => { + const filterId = `filter-${idx}`; + return ( + + + { + const indexUsedFilter = filters.findIndex( + _filter => _filter.key === option.value + ); + return indexUsedFilter === -1 || idx === indexUsedFilter; + })} + value={filter.key} + onChange={e => onChangeFilter('key', e.target.value, idx)} + /> + + + onChangeFilter('value', e.target.value, idx)} + value={filter.value} + /> + + + onRemoveFilter(idx)} + disabled={!filter.key && filters.length === 1} + /> + + + ); + })} + + { + setFilters(currentFilters => [ + ...currentFilters, + { key: '', value: '' } + ]); + }} + disabled={filters.length === filterOptions.length - 1} + > + {i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.flyout.filters.addAnotherFilter', + { + defaultMessage: 'Add another filter' + } + )} + + + ); +}; diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx index 318e711036285..5f0784d535884 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx @@ -15,101 +15,149 @@ import { EuiPortal, EuiText, EuiTitle, - EuiSelect, - EuiFieldText + EuiSpacer, + EuiFormRow, + EuiFieldText, + EuiForm } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React, { useState } from 'react'; -import { isEmpty } from 'lodash'; -import { FormType, useForm } from './useForm'; -import { FieldsSection, Field } from './FieldsSection'; +import React, { useEffect } from 'react'; +import { useForm } from 'react-hook-form'; +import { Filter, FiltersSection } from './FiltersSection'; interface Props { onClose: () => void; } -const actionFields: FormType = [ +const actionFields = [ { type: 'text', name: 'label', label: 'Label', helpText: 'Labels can be a maximum of 128 characters', - placeholder: 'e.g. Support tickets' + placeholder: 'e.g. Support tickets', + register: { required: true, maxLength: 128 } }, { type: 'text', name: 'url', label: 'URL', - helpText: 'You can use relative paths by prefixing with e.g. /dashboards', - placeholder: 'e.g. https://www.elastic.co/' + helpText: + 'Add fieldname variables to your URL to apply values e.g. {{trace.id}}. TODO: Learn more in the docs.', + placeholder: 'e.g. https://www.elastic.co/', + register: { required: true } } ]; +interface FormData { + label: string; + url: string; + filters: Filter[]; +} + +const FILTERS = 'filters'; + export const CustomActionsFlyout = ({ onClose }: Props) => { - const { label, url, Form } = useForm({ - fields: actionFields, - title: i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.flyOut.action.title', - { defaultMessage: 'Action' } - ) - }); - const [fields, setFields] = useState([] as Field[]); + const { register, handleSubmit, watch, errors, setValue } = useForm< + FormData + >(); + const onSubmit = (data: FormData) => { + console.log('#########', data); + }; + + const handleFiltersChange = (filters: Filter[]) => { + setValue(FILTERS, filters); + }; + + useEffect(() => { + register({ name: FILTERS }); + }, [register]); return ( - - -

- {i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.flyout.title', - { - defaultMessage: 'Create custom action' - } - )} -

-
-
- - -

- {i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.flyout.label', - { - defaultMessage: - "This action will be shown in the 'Actions' context menu for the trace and error detail components. You can specify any number of links, but only the first three will be shown, in alphabetical order." - } - )} -

-
- - - -
- - - - + + + +

{i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.flyout.close', + 'xpack.apm.settings.customizeUI.customActions.flyout.title', { - defaultMessage: 'Close' + defaultMessage: 'Create custom action' } )} - - - - +

+
+
+ + +

+ {i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.flyout.label', + { + defaultMessage: + "This action will be shown in the 'Actions' context menu for the trace and error detail components. You can specify any number of links, but only the first three will be shown, in alphabetical order." + } + )} +

+
+ + + +

{i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.flyout.save', + 'xpack.apm.settings.customizeUI.customActions.flyout.action.title', { - defaultMessage: 'Save' + defaultMessage: 'Action' } )} - - - - +

+
+ + {actionFields.map(field => { + return ( + Required} + > + + + ); + })} + + +
+ + + + + {i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.flyout.close', + { + defaultMessage: 'Close' + } + )} + + + + + {i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.flyout.save', + { + defaultMessage: 'Save' + } + )} + + + + +
); diff --git a/yarn.lock b/yarn.lock index dde08490d62f0..0d62abf64b9fd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -24382,6 +24382,11 @@ react-helmet-async@^1.0.2: react-fast-compare "2.0.4" shallowequal "1.1.0" +react-hook-form@^4.9.6: + version "4.9.6" + resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-4.9.6.tgz#2057b4af7028f09d50c9bd1db46d1ff4e453c046" + integrity sha512-t5EQdfATwOOlj0rsBmHYBgf6s5L/dzakPzhzr2kdqTVhRnxSI73ZthJofZ+dYiXrXa6ds0VRso49GCHwDXL66Q== + react-hotkeys@2.0.0-pre4: version "2.0.0-pre4" resolved "https://registry.yarnpkg.com/react-hotkeys/-/react-hotkeys-2.0.0-pre4.tgz#a1c248a51bdba4282c36bf3204f80d58abc73333" From e49853bbb1c12ce2b1c5a58d140b0ae5281e28ae Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Thu, 20 Feb 2020 10:46:59 +0100 Subject: [PATCH 05/42] refactoring --- .../CustomActionsFlyout/FiltersSection.tsx | 39 ++++++------ .../CustomActionsFlyout/index.tsx | 62 ++++++++++--------- 2 files changed, 55 insertions(+), 46 deletions(-) diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx index 653672331d642..627493b5ffa51 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx @@ -4,18 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ import { + EuiButtonEmpty, + EuiFieldText, EuiFlexGroup, EuiFlexItem, EuiSelect, - EuiFieldText, - EuiButtonEmpty, EuiSpacer, - EuiTitle, - EuiText + EuiText, + EuiTitle } from '@elastic/eui'; -import React, { useState } from 'react'; import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; +import React from 'react'; type Keys = 'key' | 'value'; export type Filter = { @@ -41,29 +41,31 @@ const filterOptions = [ ]; export const FiltersSection = ({ - onFiltersChange + filters = [{ key: '', value: '' }], + onChange }: { - onFiltersChange: (filters: Filter[]) => void; + filters: Filter[]; + onChange?: (filters: Filter[]) => void; }) => { - const [filters, setFilters] = useState([{ key: '', value: '' }]); - const onChangeFilter = (key: Keys, value: string, idx: number) => { const copyOfFilters = [...filters]; copyOfFilters[idx][key] = value; - setFilters(copyOfFilters); - onFiltersChange(copyOfFilters); + if (typeof onChange === 'function') { + onChange(copyOfFilters); + } }; const onRemoveFilter = (idx: number) => { const copyOfFilters = [...filters]; copyOfFilters.splice(idx, 1); // When empty, means that it was the last filter that got removed, - // so instead on showing empty list, will add a new empty filter. + // so instead of showing an empty list, will add a new empty filter. if (isEmpty(copyOfFilters)) { copyOfFilters.push({ key: '', value: '' }); } - setFilters(copyOfFilters); - onFiltersChange(copyOfFilters); + if (typeof onChange === 'function') { + onChange(copyOfFilters); + } }; return ( @@ -100,6 +102,7 @@ export const FiltersSection = ({ const indexUsedFilter = filters.findIndex( _filter => _filter.key === option.value ); + // Filter out all items already added, besides the one selected in the current filter. return indexUsedFilter === -1 || idx === indexUsedFilter; })} value={filter.key} @@ -130,11 +133,11 @@ export const FiltersSection = ({ { - setFilters(currentFilters => [ - ...currentFilters, - { key: '', value: '' } - ]); + if (typeof onChange === 'function') { + onChange([...filters, { key: '', value: '' }]); + } }} + // Disable button when user has already added all items available disabled={filters.length === filterOptions.length - 1} > {i18n.translate( diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx index 5f0784d535884..f035cabaf34f8 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx @@ -6,32 +6,45 @@ import { EuiButton, EuiButtonEmpty, + EuiFieldText, EuiFlexGroup, EuiFlexItem, EuiFlyout, EuiFlyoutBody, EuiFlyoutFooter, EuiFlyoutHeader, + EuiFormRow, EuiPortal, - EuiText, - EuiTitle, EuiSpacer, - EuiFormRow, - EuiFieldText, - EuiForm + EuiText, + EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React, { useEffect } from 'react'; -import { useForm } from 'react-hook-form'; +import { isEmpty } from 'lodash'; +import React from 'react'; +import { Controller, useForm, ValidationOptions } from 'react-hook-form'; import { Filter, FiltersSection } from './FiltersSection'; interface Props { onClose: () => void; } -const actionFields = [ +interface FormData { + label: string; + url: string; + filters: Filter[]; +} + +interface ActionField { + name: keyof FormData; + label: string; + helpText: string; + placeholder: string; + register: ValidationOptions; +} + +const actionFields: ActionField[] = [ { - type: 'text', name: 'label', label: 'Label', helpText: 'Labels can be a maximum of 128 characters', @@ -39,7 +52,6 @@ const actionFields = [ register: { required: true, maxLength: 128 } }, { - type: 'text', name: 'url', label: 'URL', helpText: @@ -49,29 +61,15 @@ const actionFields = [ } ]; -interface FormData { - label: string; - url: string; - filters: Filter[]; -} - -const FILTERS = 'filters'; - export const CustomActionsFlyout = ({ onClose }: Props) => { - const { register, handleSubmit, watch, errors, setValue } = useForm< + const { register, handleSubmit, errors, control, watch } = useForm< FormData >(); const onSubmit = (data: FormData) => { console.log('#########', data); }; - const handleFiltersChange = (filters: Filter[]) => { - setValue(FILTERS, filters); - }; - - useEffect(() => { - register({ name: FILTERS }); - }, [register]); + const filters = watch('filters'); return ( @@ -114,7 +112,7 @@ export const CustomActionsFlyout = ({ onClose }: Props) => {

- {actionFields.map(field => { + {actionFields.map((field: ActionField) => { return ( { inputRef={register(field.register)} placeholder={field.placeholder} name={field.name} + fullWidth + isInvalid={!isEmpty(errors[field.name])} /> ); })} - + + } + name="filters" + control={control} + defaultValue={filters} + /> From 32efd3f9ddea937cf03086a7003d8b34af873774 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Thu, 20 Feb 2020 12:44:08 +0100 Subject: [PATCH 06/42] validating filters --- .../CustomActionsFlyout/FiltersSection.tsx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx index 627493b5ffa51..5d70151b3e3e5 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx @@ -15,7 +15,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; -import React from 'react'; +import React, { useRef } from 'react'; type Keys = 'key' | 'value'; export type Filter = { @@ -47,7 +47,12 @@ export const FiltersSection = ({ filters: Filter[]; onChange?: (filters: Filter[]) => void; }) => { + const filterValueRefs = useRef([]); + const onChangeFilter = (key: Keys, value: string, idx: number) => { + if (filterValueRefs.current[idx]) { + filterValueRefs.current[idx].focus(); + } const copyOfFilters = [...filters]; copyOfFilters[idx][key] = value; if (typeof onChange === 'function') { @@ -107,6 +112,7 @@ export const FiltersSection = ({ })} value={filter.key} onChange={e => onChangeFilter('key', e.target.value, idx)} + isInvalid={isEmpty(filter.key) && !isEmpty(filter.value)} /> @@ -117,6 +123,14 @@ export const FiltersSection = ({ )} onChange={e => onChangeFilter('value', e.target.value, idx)} value={filter.value} + isInvalid={!isEmpty(filter.key) && isEmpty(filter.value)} + inputRef={ref => { + if (ref) { + filterValueRefs.current.push(ref); + } else { + filterValueRefs.current.splice(idx, 1); + } + }} /> From 1f2680cde91a135750e354ba45ce5c65126dd895 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Thu, 20 Feb 2020 13:02:03 +0100 Subject: [PATCH 07/42] fixing imports --- .../AgentConfigurations/AddEditFlyout/ServiceSection.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AddEditFlyout/ServiceSection.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AddEditFlyout/ServiceSection.tsx index dfa093b401924..537bdace50e24 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AddEditFlyout/ServiceSection.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AddEditFlyout/ServiceSection.tsx @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; import { omitAllOption, getOptionLabel -} from '../../../../../../common/agent_configuration_constants'; +} from '../../../../../../../../../plugins/apm/common/agent_configuration_constants'; import { useFetcher } from '../../../../../hooks/useFetcher'; import { SelectWithPlaceholder } from '../../../../shared/SelectWithPlaceholder'; From 2475234d746006cf3bb4f19e81560e3fba011c5e Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Thu, 20 Feb 2020 14:05:48 +0100 Subject: [PATCH 08/42] refactoring to NP and creating save custom action --- .../CustomActionsFlyout/index.tsx | 14 +++++---- .../CustomActionsFlyout/saveCustomAction.ts | 30 +++++++++++++++++++ .../create_custom_action_index.ts | 2 +- .../custom_action/custom_action_types.d.ts | 0 x-pack/plugins/apm/server/plugin.ts | 1 + 5 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/saveCustomAction.ts rename x-pack/{legacy => }/plugins/apm/server/lib/settings/custom_action/create_custom_action_index.ts (97%) rename x-pack/{legacy => }/plugins/apm/server/lib/settings/custom_action/custom_action_types.d.ts (100%) diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx index f035cabaf34f8..07e5828c880a7 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx @@ -23,20 +23,22 @@ import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; import React from 'react'; import { Controller, useForm, ValidationOptions } from 'react-hook-form'; +import { useCallApmApi } from '../../../../../../hooks/useCallApmApi'; import { Filter, FiltersSection } from './FiltersSection'; +import { saveCustomAction } from './saveCustomAction'; interface Props { onClose: () => void; } -interface FormData { +export interface CustomAction { label: string; url: string; filters: Filter[]; } interface ActionField { - name: keyof FormData; + name: keyof CustomAction; label: string; helpText: string; placeholder: string; @@ -62,11 +64,13 @@ const actionFields: ActionField[] = [ ]; export const CustomActionsFlyout = ({ onClose }: Props) => { + const callApmApiFromHook = useCallApmApi(); const { register, handleSubmit, errors, control, watch } = useForm< - FormData + CustomAction >(); - const onSubmit = (data: FormData) => { - console.log('#########', data); + + const onSubmit = (customAction: CustomAction) => { + saveCustomAction({ callApmApi: callApmApiFromHook, customAction }); }; const filters = watch('filters'); diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/saveCustomAction.ts b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/saveCustomAction.ts new file mode 100644 index 0000000000000..16f00456bbadd --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/saveCustomAction.ts @@ -0,0 +1,30 @@ +/* + * 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 { isEmpty } from 'lodash'; +import { APMClient } from '../../../../../../services/rest/createCallApmApi'; +import { CustomAction } from './'; + +export const saveCustomAction = ({ + callApmApi, + customAction +}: { + callApmApi: APMClient; + customAction: CustomAction; +}) => { + const { label, url, filters } = customAction; + const customActionBody = { + label, + url, + filters: filters + .filter(({ key, value }) => !isEmpty(key) && !isEmpty(value)) + .reduce((acc: Record, { key, value }) => { + acc[key] = value; + return acc; + }, {}) + }; + console.log('### caue: customAction', customActionBody); +}; diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/custom_action/create_custom_action_index.ts b/x-pack/plugins/apm/server/lib/settings/custom_action/create_custom_action_index.ts similarity index 97% rename from x-pack/legacy/plugins/apm/server/lib/settings/custom_action/create_custom_action_index.ts rename to x-pack/plugins/apm/server/lib/settings/custom_action/create_custom_action_index.ts index 2c143c2477c90..efbedab2ad34a 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/custom_action/create_custom_action_index.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_action/create_custom_action_index.ts @@ -6,8 +6,8 @@ import { IClusterClient } from 'kibana/server'; import { CallCluster } from 'src/legacy/core_plugins/elasticsearch'; -import { APMConfig } from '../../../../../../../plugins/apm/server'; import { getApmIndicesConfig } from '../apm_indices/get_apm_indices'; +import { APMConfig } from '../../..'; export const createApmCustomActionIndex = async ({ esClient, diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/custom_action/custom_action_types.d.ts b/x-pack/plugins/apm/server/lib/settings/custom_action/custom_action_types.d.ts similarity index 100% rename from x-pack/legacy/plugins/apm/server/lib/settings/custom_action/custom_action_types.d.ts rename to x-pack/plugins/apm/server/lib/settings/custom_action/custom_action_types.d.ts diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts index 773f0d4e6fac5..23d9e40e0c849 100644 --- a/x-pack/plugins/apm/server/plugin.ts +++ b/x-pack/plugins/apm/server/plugin.ts @@ -12,6 +12,7 @@ import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { APMOSSPluginSetup } from '../../../../src/plugins/apm_oss/server'; import { makeApmUsageCollector } from './lib/apm_telemetry'; import { createApmAgentConfigurationIndex } from './lib/settings/agent_configuration/create_agent_config_index'; +import { createApmCustomActionIndex } from './lib/settings/custom_action/create_custom_action_index'; import { createApmApi } from './routes/create_apm_api'; import { getApmIndices } from './lib/settings/apm_indices/get_apm_indices'; import { APMConfig, mergeConfigs, APMXPackConfig } from '.'; From 5c48f4933c9c39045d998a3d5557f354a5403c1d Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Fri, 21 Feb 2020 10:46:02 +0100 Subject: [PATCH 09/42] creating basic apis for custom actions --- .../CustomActionsFlyout/index.tsx | 5 +- .../CustomActionsFlyout/saveCustomAction.ts | 31 ++++- .../create_or_update_custom_action.ts | 40 ++++++ .../custom_action/custom_action_types.d.ts | 2 +- .../custom_action/delete_custom_action.ts | 25 ++++ .../custom_action/list_custom_actions.ts | 21 +++ .../apm/server/routes/create_apm_api.ts | 14 +- .../server/routes/settings/custom_actions.ts | 123 ++++++++++++++++++ 8 files changed, 255 insertions(+), 6 deletions(-) create mode 100644 x-pack/plugins/apm/server/lib/settings/custom_action/create_or_update_custom_action.ts create mode 100644 x-pack/plugins/apm/server/lib/settings/custom_action/delete_custom_action.ts create mode 100644 x-pack/plugins/apm/server/lib/settings/custom_action/list_custom_actions.ts create mode 100644 x-pack/plugins/apm/server/routes/settings/custom_actions.ts diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx index 07e5828c880a7..ab33951acb58b 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx @@ -23,6 +23,7 @@ import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; import React from 'react'; import { Controller, useForm, ValidationOptions } from 'react-hook-form'; +import { useApmPluginContext } from '../../../../../../hooks/useApmPluginContext'; import { useCallApmApi } from '../../../../../../hooks/useCallApmApi'; import { Filter, FiltersSection } from './FiltersSection'; import { saveCustomAction } from './saveCustomAction'; @@ -65,12 +66,14 @@ const actionFields: ActionField[] = [ export const CustomActionsFlyout = ({ onClose }: Props) => { const callApmApiFromHook = useCallApmApi(); + const { toasts } = useApmPluginContext().core.notifications; + const { register, handleSubmit, errors, control, watch } = useForm< CustomAction >(); const onSubmit = (customAction: CustomAction) => { - saveCustomAction({ callApmApi: callApmApiFromHook, customAction }); + saveCustomAction({ callApmApi: callApmApiFromHook, customAction, toasts }); }; const filters = watch('filters'); diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/saveCustomAction.ts b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/saveCustomAction.ts index 16f00456bbadd..61c2e62b516bf 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/saveCustomAction.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/saveCustomAction.ts @@ -5,15 +5,19 @@ */ import { isEmpty } from 'lodash'; +import { i18n } from '@kbn/i18n'; +import { NotificationsStart } from 'kibana/public'; import { APMClient } from '../../../../../../services/rest/createCallApmApi'; import { CustomAction } from './'; -export const saveCustomAction = ({ +export const saveCustomAction = async ({ callApmApi, - customAction + customAction, + toasts }: { callApmApi: APMClient; customAction: CustomAction; + toasts: NotificationsStart['toasts']; }) => { const { label, url, filters } = customAction; const customActionBody = { @@ -26,5 +30,26 @@ export const saveCustomAction = ({ return acc; }, {}) }; - console.log('### caue: customAction', customActionBody); + + await callApmApi({ + pathname: '/api/apm/settings/custom-actions', + method: 'POST', + params: { + body: customActionBody + } + }); + + // TODO: caue change the toast messages + toasts.addSuccess({ + title: i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.succeeded.title', + { defaultMessage: 'Created a new custom action!' } + ), + text: i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.succeeded.text', + { + defaultMessage: 'We have succesfully created a custom action.' + } + ) + }); }; diff --git a/x-pack/plugins/apm/server/lib/settings/custom_action/create_or_update_custom_action.ts b/x-pack/plugins/apm/server/lib/settings/custom_action/create_or_update_custom_action.ts new file mode 100644 index 0000000000000..124a0800fa976 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/settings/custom_action/create_or_update_custom_action.ts @@ -0,0 +1,40 @@ +/* + * 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 { APMIndexDocumentParams } from '../../helpers/es_client'; +import { Setup } from '../../helpers/setup_request'; +import { CustomAction } from './custom_action_types'; + +export async function createOrUpdateCustomAction({ + customActionId, + customAction, + setup +}: { + customActionId?: string; + customAction: Omit; + setup: Setup; +}) { + const { internalClient, indices } = setup; + + const params: APMIndexDocumentParams = { + refresh: true, + index: indices.apmCustomActionIndex, + body: { + '@timestamp': Date.now(), + label: customAction.label, + url: customAction.url, + filters: customAction.filters, + actionId: 'trace' + } + }; + + // by specifying an id elasticsearch will delete the previous doc and insert the updated doc + if (customActionId) { + params.id = customActionId; + } + + return internalClient.index(params); +} diff --git a/x-pack/plugins/apm/server/lib/settings/custom_action/custom_action_types.d.ts b/x-pack/plugins/apm/server/lib/settings/custom_action/custom_action_types.d.ts index 7bd85b7aadc0f..d9f45cbff730b 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_action/custom_action_types.d.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_action/custom_action_types.d.ts @@ -8,7 +8,7 @@ export interface CustomAction { '@timestamp': number; label: string; url: string; - actionId: 'TRACE'; + actionId: 'trace'; filters?: { service?: { name?: string; environment?: string }; transaction?: { type?: string; name?: string }; diff --git a/x-pack/plugins/apm/server/lib/settings/custom_action/delete_custom_action.ts b/x-pack/plugins/apm/server/lib/settings/custom_action/delete_custom_action.ts new file mode 100644 index 0000000000000..e1193646539a1 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/settings/custom_action/delete_custom_action.ts @@ -0,0 +1,25 @@ +/* + * 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 { Setup } from '../../helpers/setup_request'; + +export async function deleteCustomAction({ + customActionId, + setup +}: { + customActionId: string; + setup: Setup; +}) { + const { internalClient, indices } = setup; + + const params = { + refresh: 'wait_for', + index: indices.apmCustomActionIndex, + id: customActionId + }; + + return internalClient.delete(params); +} diff --git a/x-pack/plugins/apm/server/lib/settings/custom_action/list_custom_actions.ts b/x-pack/plugins/apm/server/lib/settings/custom_action/list_custom_actions.ts new file mode 100644 index 0000000000000..a283d5e84fd00 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/settings/custom_action/list_custom_actions.ts @@ -0,0 +1,21 @@ +/* + * 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 { CustomAction } from './custom_action_types'; +import { Setup } from '../../helpers/setup_request'; + +export async function listCustomActions({ setup }: { setup: Setup }) { + const { internalClient, indices } = setup; + const params = { + index: indices.apmCustomActionIndex, + size: 200 + }; + const resp = await internalClient.search(params); + return resp.hits.hits.map(item => ({ + id: item._id, + ...item._source + })); +} diff --git a/x-pack/plugins/apm/server/routes/create_apm_api.ts b/x-pack/plugins/apm/server/routes/create_apm_api.ts index 21392edbb2c48..4473754ef648a 100644 --- a/x-pack/plugins/apm/server/routes/create_apm_api.ts +++ b/x-pack/plugins/apm/server/routes/create_apm_api.ts @@ -59,6 +59,12 @@ import { import { createApi } from './create_api'; import { serviceMapRoute, serviceMapServiceNodeRoute } from './service_map'; import { indicesPrivilegesRoute } from './security'; +import { + createCustomActionRoute, + updateCustomActionRoute, + deleteCustomActionRoute, + listCustomActionsRoute +} from './settings/custom_actions'; const createApmApi = () => { const api = createApi() @@ -126,7 +132,13 @@ const createApmApi = () => { .add(serviceMapServiceNodeRoute) // security - .add(indicesPrivilegesRoute); + .add(indicesPrivilegesRoute) + + // Custom actions + .add(createCustomActionRoute) + .add(updateCustomActionRoute) + .add(deleteCustomActionRoute) + .add(listCustomActionsRoute); return api; }; diff --git a/x-pack/plugins/apm/server/routes/settings/custom_actions.ts b/x-pack/plugins/apm/server/routes/settings/custom_actions.ts new file mode 100644 index 0000000000000..6436947bcfbca --- /dev/null +++ b/x-pack/plugins/apm/server/routes/settings/custom_actions.ts @@ -0,0 +1,123 @@ +/* + * 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 t from 'io-ts'; +import { createRoute } from '../create_route'; +import { setupRequest } from '../../lib/helpers/setup_request'; +import { createOrUpdateCustomAction } from '../../lib/settings/custom_action/create_or_update_custom_action'; +import { deleteCustomAction } from '../../lib/settings/custom_action/delete_custom_action'; +import { listCustomActions } from '../../lib/settings/custom_action/list_custom_actions'; + +// { value: '{ value: 'service.name', text: 'service.name' }, +// { value: 'service.environment', text: 'service.environment' }, +// { value: 'transaction.type', text: 'transaction.type' }, +// { value: 'transaction.name', text: 'transaction.name' }', text: 'service.name' }, +// { value: 'service.environment', text: 'service.environment' }, +// { value: 'transaction.type', text: 'transaction.type' }, +// { value: 'transaction.name', text: 'transaction.name' } + +export const listCustomActionsRoute = createRoute(core => ({ + path: '/api/apm/settings/custom-actions', + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + return await listCustomActions({ setup }); + } +})); + +const agentPayloadRt = t.intersection([ + t.partial({ agent_name: t.string }), + t.type({ + service: t.intersection([ + t.partial({ name: t.string }), + t.partial({ environment: t.string }) + ]) + }), + t.type({ + settings: t.intersection([ + t.partial({ transaction_sample_rate: t.string }), + t.partial({ capture_body: t.string }), + t.partial({ transaction_max_spans: t.string }) + ]) + }) +]); + +const payload = t.intersection([ + t.type({ + label: t.string, + url: t.string + }), + t.partial({ + filters: t.intersection([ + t.partial({ + service: t.partial({ name: t.string, environment: t.string }) + }), + t.partial({ transaction: t.partial({ name: t.string, type: t.string }) }) + ]) + }) +]); + +export const createCustomActionRoute = createRoute(() => ({ + method: 'POST', + path: '/api/apm/settings/custom-actions', + params: { + body: payload + }, + options: { + tags: ['access:apm', 'access:apm_write'] + }, + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + const customAction = context.params.body; + const res = await createOrUpdateCustomAction({ customAction, setup }); + return res; + } +})); + +export const updateCustomActionRoute = createRoute(() => ({ + method: 'PUT', + path: '/api/apm/settings/custom-actions/{id}', + params: { + path: t.type({ + id: t.string + }), + body: payload + }, + options: { + tags: ['access:apm', 'access:apm_write'] + }, + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + const { id } = context.params.path; + const customAction = context.params.body; + const res = await createOrUpdateCustomAction({ + customActionId: id, + customAction, + setup + }); + return res; + } +})); + +export const deleteCustomActionRoute = createRoute(() => ({ + method: 'DELETE', + path: '/api/apm/settings/custom-actions/{id}', + params: { + path: t.type({ + id: t.string + }) + }, + options: { + tags: ['access:apm', 'access:apm_write'] + }, + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + const { id } = context.params.path; + const res = await deleteCustomAction({ + customActionId: id, + setup + }); + return res; + } +})); From a1e4df947c503b45887b8c345f2b2fd6385679b3 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Fri, 21 Feb 2020 14:40:42 +0100 Subject: [PATCH 10/42] refactoring --- .../CreateCustomActionButton.tsx | 21 +++ .../CustomActionsFlyout/ActionSection.tsx | 96 +++++++++++++ .../CustomActionsFlyout/FiltersSection.tsx | 132 +++++++++++------ .../CustomActionsFlyout/Flyoutfooter.tsx | 43 ++++++ .../CustomActionsFlyout/index.tsx | 134 +++++------------- .../CustomActionsTable.tsx | 90 ++++++++++++ .../CustomActionsOverview/EmptyPrompt.tsx | 10 +- .../CustomActionsOverview/index.tsx | 96 +++++++------ 8 files changed, 425 insertions(+), 197 deletions(-) create mode 100644 x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CreateCustomActionButton.tsx create mode 100644 x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/ActionSection.tsx create mode 100644 x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/Flyoutfooter.tsx create mode 100644 x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsTable.tsx diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CreateCustomActionButton.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CreateCustomActionButton.tsx new file mode 100644 index 0000000000000..7c7ccb7546284 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CreateCustomActionButton.tsx @@ -0,0 +1,21 @@ +/* + * 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 { EuiButton } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +export const CreateCustomActionButton = ({ + onClick +}: { + onClick: () => void; +}) => ( + + {i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.createCustomAction', + { defaultMessage: 'Create custom action' } + )} + +); diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/ActionSection.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/ActionSection.tsx new file mode 100644 index 0000000000000..e6f4f69f37dba --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/ActionSection.tsx @@ -0,0 +1,96 @@ +/* + * 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 { + ValidationOptions, + FormContextValues, + NestDataObject +} from 'react-hook-form'; +import { + EuiTitle, + EuiSpacer, + EuiText, + EuiFieldText, + EuiFormRow +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { isEmpty } from 'lodash'; +import { CustomAction } from '../../../../../../../../../../plugins/apm/server/lib/settings/custom_action/custom_action_types'; +import { FormData } from './'; + +interface ActionField { + name: keyof Omit; + label: string; + helpText: string; + placeholder: string; + register: ValidationOptions; +} + +const actionFields: ActionField[] = [ + { + name: 'label', + label: 'Label', + helpText: 'Labels can be a maximum of 128 characters', + placeholder: 'e.g. Support tickets', + register: { required: true, maxLength: 128 } + }, + { + name: 'url', + label: 'URL', + helpText: + 'Add fieldname variables to your URL to apply values e.g. {{trace.id}}. TODO: Learn more in the docs.', + placeholder: 'e.g. https://www.elastic.co/', + register: { required: true } + } +]; + +interface ActionSectionProps { + register: FormContextValues['register']; + errors: NestDataObject; + customAction?: CustomAction; +} + +export const ActionSection = ({ + register, + errors, + customAction +}: ActionSectionProps) => { + return ( + <> + +

+ {i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.flyout.action.title', + { + defaultMessage: 'Action' + } + )} +

+
+ + {actionFields.map((field: ActionField) => { + const value = (customAction && customAction[field.name]) || ''; + return ( + Required} + > + + + ); + })} + + ); +}; diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx index 5d70151b3e3e5..b905e98ba5dec 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx @@ -16,6 +16,7 @@ import { import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; import React, { useRef } from 'react'; +import { CustomAction } from '../../../../../../../../../../plugins/apm/server/lib/settings/custom_action/custom_action_types'; type Keys = 'key' | 'value'; export type Filter = { @@ -42,6 +43,88 @@ const filterOptions = [ export const FiltersSection = ({ filters = [{ key: '', value: '' }], + onChange, + customAction +}: { + filters: Filter[]; + onChange?: (filters: Filter[]) => void; + customAction?: CustomAction; +}) => { + const handleAddFilter = () => { + if (typeof onChange === 'function') { + onChange([...filters, { key: '', value: '' }]); + } + }; + + return ( + <> + +

+ {i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.flyout.filters.title', + { + defaultMessage: 'Filters' + } + )} +

+
+ + + {i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.flyout.filters.subtitle', + { + defaultMessage: + 'Add additional values within the same field by comma separating values.' + } + )} + + + + + + + + + ); +}; + +const AddFilterButton = ({ + onClick, + isDisabled +}: { + onClick: () => void; + isDisabled: boolean; +}) => ( + + {i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.flyout.filters.addAnotherFilter', + { + defaultMessage: 'Add another filter' + } + )} + +); + +const getSelectOptions = (filters: Filter[], idx: number) => { + return filterOptions.filter(option => { + const indexUsedFilter = filters.findIndex( + _filter => _filter.key === option.value + ); + // Filter out all items already added, besides the one selected in the current filter. + return indexUsedFilter === -1 || idx === indexUsedFilter; + }); +}; + +const Filters = ({ + filters, onChange }: { filters: Filter[]; @@ -75,41 +158,16 @@ export const FiltersSection = ({ return ( <> - -

- {i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.flyout.filters.title', - { - defaultMessage: 'Filters' - } - )} -

-
- - - {i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.flyout.filters.subtitle', - { - defaultMessage: - 'Add additional values within the same field by comma separating values.' - } - )} - - {filters.map((filter, idx) => { + {filters.map((filter: Filter, idx: number) => { const filterId = `filter-${idx}`; + const selectOptions = getSelectOptions(filters, idx); return ( { - const indexUsedFilter = filters.findIndex( - _filter => _filter.key === option.value - ); - // Filter out all items already added, besides the one selected in the current filter. - return indexUsedFilter === -1 || idx === indexUsedFilter; - })} + options={selectOptions} value={filter.key} onChange={e => onChangeFilter('key', e.target.value, idx)} isInvalid={isEmpty(filter.key) && !isEmpty(filter.value)} @@ -143,24 +201,6 @@ export const FiltersSection = ({ ); })} - - { - if (typeof onChange === 'function') { - onChange([...filters, { key: '', value: '' }]); - } - }} - // Disable button when user has already added all items available - disabled={filters.length === filterOptions.length - 1} - > - {i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.flyout.filters.addAnotherFilter', - { - defaultMessage: 'Add another filter' - } - )} - ); }; diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/Flyoutfooter.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/Flyoutfooter.tsx new file mode 100644 index 0000000000000..df9854a0bf060 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/Flyoutfooter.tsx @@ -0,0 +1,43 @@ +/* + * 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 { + EuiFlyoutFooter, + EuiFlexGroup, + EuiFlexItem, + EuiButtonEmpty, + EuiButton +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +export const FlyoutFooter = ({ onClose }: { onClose: () => void }) => { + return ( + + + + + {i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.flyout.close', + { + defaultMessage: 'Close' + } + )} + + + + + {i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.flyout.save', + { + defaultMessage: 'Save' + } + )} + + + + + ); +}; diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx index ab33951acb58b..05a5f32b73c4f 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx @@ -4,91 +4,64 @@ * you may not use this file except in compliance with the Elastic License. */ import { - EuiButton, - EuiButtonEmpty, - EuiFieldText, - EuiFlexGroup, - EuiFlexItem, EuiFlyout, EuiFlyoutBody, - EuiFlyoutFooter, EuiFlyoutHeader, - EuiFormRow, EuiPortal, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { isEmpty } from 'lodash'; import React from 'react'; -import { Controller, useForm, ValidationOptions } from 'react-hook-form'; +import { Controller, useForm } from 'react-hook-form'; +import { CustomAction } from '../../../../../../../../../../plugins/apm/server/lib/settings/custom_action/custom_action_types'; import { useApmPluginContext } from '../../../../../../hooks/useApmPluginContext'; import { useCallApmApi } from '../../../../../../hooks/useCallApmApi'; +import { ActionSection } from './ActionSection'; import { Filter, FiltersSection } from './FiltersSection'; -import { saveCustomAction } from './saveCustomAction'; +import { FlyoutFooter } from './Flyoutfooter'; interface Props { onClose: () => void; + customActionSelected?: CustomAction; } -export interface CustomAction { +export interface FormData { label: string; url: string; filters: Filter[]; } -interface ActionField { - name: keyof CustomAction; - label: string; - helpText: string; - placeholder: string; - register: ValidationOptions; -} - -const actionFields: ActionField[] = [ - { - name: 'label', - label: 'Label', - helpText: 'Labels can be a maximum of 128 characters', - placeholder: 'e.g. Support tickets', - register: { required: true, maxLength: 128 } - }, - { - name: 'url', - label: 'URL', - helpText: - 'Add fieldname variables to your URL to apply values e.g. {{trace.id}}. TODO: Learn more in the docs.', - placeholder: 'e.g. https://www.elastic.co/', - register: { required: true } - } -]; - -export const CustomActionsFlyout = ({ onClose }: Props) => { +export const CustomActionsFlyout = ({ + onClose, + customActionSelected +}: Props) => { const callApmApiFromHook = useCallApmApi(); const { toasts } = useApmPluginContext().core.notifications; - const { register, handleSubmit, errors, control, watch } = useForm< - CustomAction + FormData >(); - const onSubmit = (customAction: CustomAction) => { - saveCustomAction({ callApmApi: callApmApiFromHook, customAction, toasts }); - }; + const onSubmit = handleSubmit((customAction: FormData) => { + console.log('### caue: onSubmit -> customAction', customAction); + // saveCustomAction({ callApmApi: callApmApiFromHook, customAction, toasts }); + }); + // Watch for any change on filters to render the component const filters = watch('filters'); return ( -
+

{i18n.translate( 'xpack.apm.settings.customizeUI.customActions.flyout.title', { - defaultMessage: 'Create custom action' + defaultMessage: 'Create action' } )}

@@ -101,75 +74,36 @@ export const CustomActionsFlyout = ({ onClose }: Props) => { 'xpack.apm.settings.customizeUI.customActions.flyout.label', { defaultMessage: - "This action will be shown in the 'Actions' context menu for the trace and error detail components. You can specify any number of links, but only the first three will be shown, in alphabetical order." + 'Actions will be shown in the context of trace and error details througout the APM app. You can specify an unlimited amount of links, but we will opt to only show the first 3 alphabetically.' } )}

- -

- {i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.flyout.action.title', - { - defaultMessage: 'Action' - } - )} -

-
- - {actionFields.map((field: ActionField) => { - return ( - Required} - > - - - ); - })} + + + } + as={ + + } name="filters" control={control} defaultValue={filters} /> - - - - - {i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.flyout.close', - { - defaultMessage: 'Close' - } - )} - - - - - {i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.flyout.save', - { - defaultMessage: 'Save' - } - )} - - - - + +
diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsTable.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsTable.tsx new file mode 100644 index 0000000000000..5c5dd11f6f493 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsTable.tsx @@ -0,0 +1,90 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { CustomAction } from '../../../../../../../../../plugins/apm/server/lib/settings/custom_action/custom_action_types'; +import { ManagedTable } from '../../../../shared/ManagedTable'; +import { TimestampTooltip } from '../../../../shared/TimestampTooltip'; + +interface Props { + items: CustomAction[]; + onCustomActionSelected: (customAction: CustomAction) => void; +} + +export const CustomActionsTable = ({ + items, + onCustomActionSelected +}: Props) => { + const columns = [ + { + field: 'label', + name: i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.table.actionName', + { defaultMessage: 'Action Name' } + ), + truncateText: true + }, + { + field: 'url', + name: i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.table.actionURL', + { defaultMessage: 'Action URL' } + ), + truncateText: true + }, + { + align: 'right', + field: '@timestamp', + name: i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.table.lastUpdated', + { defaultMessage: 'Last updated' } + ), + sortable: true, + render: (value: number) => ( + + ) + }, + { + field: 'actions', + name: 'Actions' + }, + { + name: i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.table.actions', + { defaultMessage: 'Actions' } + ), + actions: [ + { + name: i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.table.editButtonLabel', + { defaultMessage: 'Edit' } + ), + description: i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.table.editButtonDescription', + { defaultMessage: 'Edit this custom action' } + ), + icon: 'pencil', + color: 'primary', + type: 'icon', + onClick: (customAction: CustomAction) => { + onCustomActionSelected(customAction); + } + } + ] + } + ]; + + return ( + + ); +}; diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/EmptyPrompt.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/EmptyPrompt.tsx index f39e4b307b24c..6c06d70958944 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/EmptyPrompt.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/EmptyPrompt.tsx @@ -6,6 +6,7 @@ import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; +import { CreateCustomActionButton } from './CreateCustomActionButton'; export const EmptyPrompt = ({ onCreateCustomActionClick @@ -39,14 +40,7 @@ export const EmptyPrompt = ({

} - actions={ - - {i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.createCustomAction', - { defaultMessage: 'Create custom action' } - )} - - } + actions={} /> ); }; diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/index.tsx index ae2972f251fc2..9a2e2018b424f 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/index.tsx @@ -4,46 +4,36 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiPanel, EuiSpacer } from '@elastic/eui'; +import { EuiPanel, EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { isEmpty } from 'lodash'; -import React, { useState } from 'react'; -import { ManagedTable } from '../../../../shared/ManagedTable'; -import { Title } from './Title'; -import { EmptyPrompt } from './EmptyPrompt'; +import React, { useEffect, useState } from 'react'; +import { CustomAction } from '../../../../../../../../../plugins/apm/server/lib/settings/custom_action/custom_action_types'; +import { useFetcher } from '../../../../../hooks/useFetcher'; import { CustomActionsFlyout } from './CustomActionsFlyout'; +import { CustomActionsTable } from './CustomActionsTable'; +import { EmptyPrompt } from './EmptyPrompt'; +import { Title } from './Title'; +import { CreateCustomActionButton } from './CreateCustomActionButton'; export const CustomActionsOverview = () => { const [isFlyoutOpen, setIsFlyoutOpen] = useState(false); + const [customActionSelected, setCustomActionSelected] = useState< + CustomAction | undefined + >(); - // TODO: change it to correct fields fetched from ES - const columns = [ - { - field: 'actionName', - name: 'Action Name', - truncateText: true - }, - { - field: 'serviceName', - name: 'Service Name' - }, - { - field: 'environment', - name: 'Environment' - }, - { - field: 'lastUpdate', - name: 'Last update' - }, - { - field: 'actions', - name: 'Actions' - } - ]; + const { data: customActions } = useFetcher( + callApmApi => callApmApi({ pathname: '/api/apm/settings/custom-actions' }), + [] + ); - // TODO: change to items fetched from ES. - const items: object[] = []; + useEffect(() => { + if (customActionSelected) { + setIsFlyoutOpen(true); + } + }, [customActionSelected]); const onCloseFlyout = () => { + setCustomActionSelected(undefined); setIsFlyoutOpen(false); }; @@ -51,23 +41,43 @@ export const CustomActionsOverview = () => { setIsFlyoutOpen(true); }; + const hasCustomActions = !isEmpty(customActions); + return ( <> + {isFlyoutOpen && ( + + )} - + <EuiFlexGroup alignItems="center"> + <EuiFlexItem grow={false}> + <Title /> + </EuiFlexItem> + {hasCustomActions && ( + <EuiFlexItem> + <EuiFlexGroup alignItems="center" justifyContent="flexEnd"> + <EuiFlexItem grow={false}> + <CreateCustomActionButton + onClick={onCreateCustomActionClick} + /> + </EuiFlexItem> + </EuiFlexGroup> + </EuiFlexItem> + )} + </EuiFlexGroup> + <EuiSpacer size="m" /> - {isFlyoutOpen && <CustomActionsFlyout onClose={onCloseFlyout} />} - {isEmpty(items) ? ( - <EmptyPrompt onCreateCustomActionClick={onCreateCustomActionClick} /> - ) : ( - <ManagedTable - items={items} - columns={columns} - initialPageSize={25} - initialSortField="occurrenceCount" - initialSortDirection="desc" - sortItems={false} + + {hasCustomActions ? ( + <CustomActionsTable + items={customActions} + onCustomActionSelected={setCustomActionSelected} /> + ) : ( + <EmptyPrompt onCreateCustomActionClick={onCreateCustomActionClick} /> )} </EuiPanel> </> From 541a62793973c63177c658a7cac9ce5319ba105c Mon Sep 17 00:00:00 2001 From: cauemarcondes <caue.marcondes@elastic.co> Date: Mon, 24 Feb 2020 10:42:15 +0100 Subject: [PATCH 11/42] changing custom action filters type --- .../CustomActionsFlyout/ActionSection.tsx | 16 ++--- .../CustomActionsFlyout/FiltersSection.tsx | 49 ++++++++------- .../CustomActionsFlyout/index.tsx | 63 ++++++++++++------- .../CustomActionsFlyout/saveCustomAction.ts | 16 +---- .../CustomActionsTable.tsx | 4 -- .../custom_action/custom_action_types.d.ts | 9 ++- 6 files changed, 81 insertions(+), 76 deletions(-) diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/ActionSection.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/ActionSection.tsx index e6f4f69f37dba..8ea805bf132f0 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/ActionSection.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/ActionSection.tsx @@ -18,11 +18,10 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; -import { CustomAction } from '../../../../../../../../../../plugins/apm/server/lib/settings/custom_action/custom_action_types'; -import { FormData } from './'; +import { CustomActionFormData } from './'; interface ActionField { - name: keyof Omit<FormData, 'filters'>; + name: keyof CustomActionFormData; label: string; helpText: string; placeholder: string; @@ -49,15 +48,10 @@ const actionFields: ActionField[] = [ interface ActionSectionProps { register: FormContextValues['register']; - errors: NestDataObject<FormData>; - customAction?: CustomAction; + errors: NestDataObject<CustomActionFormData>; } -export const ActionSection = ({ - register, - errors, - customAction -}: ActionSectionProps) => { +export const ActionSection = ({ register, errors }: ActionSectionProps) => { return ( <> <EuiTitle size="xs"> @@ -72,7 +66,6 @@ export const ActionSection = ({ </EuiTitle> <EuiSpacer size="l" /> {actionFields.map((field: ActionField) => { - const value = (customAction && customAction[field.name]) || ''; return ( <EuiFormRow key={field.name} @@ -86,7 +79,6 @@ export const ActionSection = ({ name={field.name} fullWidth isInvalid={!isEmpty(errors[field.name])} - value={value} /> </EuiFormRow> ); diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx index b905e98ba5dec..3e8db8b8c1b21 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx @@ -16,25 +16,20 @@ import { import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; import React, { useRef } from 'react'; -import { CustomAction } from '../../../../../../../../../../plugins/apm/server/lib/settings/custom_action/custom_action_types'; -type Keys = 'key' | 'value'; -export type Filter = { - [key in Keys]: string; -}; - -const DEFAULT_OPTION = { - value: 'DEFAULT', - text: i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.flyOut.filters.defaultOption', - { - defaultMessage: 'Select fields...' - } - ) -}; +export interface Filter { + key: string; + value: string; +} const filterOptions = [ - DEFAULT_OPTION, + { + value: 'DEFAULT', + text: i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.flyOut.filters.defaultOption', + { defaultMessage: 'Select fields...' } + ) + }, { value: 'service.name', text: 'service.name' }, { value: 'service.environment', text: 'service.environment' }, { value: 'transaction.type', text: 'transaction.type' }, @@ -43,12 +38,10 @@ const filterOptions = [ export const FiltersSection = ({ filters = [{ key: '', value: '' }], - onChange, - customAction + onChange }: { filters: Filter[]; onChange?: (filters: Filter[]) => void; - customAction?: CustomAction; }) => { const handleAddFilter = () => { if (typeof onChange === 'function') { @@ -132,12 +125,12 @@ const Filters = ({ }) => { const filterValueRefs = useRef<HTMLInputElement[]>([]); - const onChangeFilter = (key: Keys, value: string, idx: number) => { + const onChangeFilter = (filter: Filter, idx: number) => { if (filterValueRefs.current[idx]) { filterValueRefs.current[idx].focus(); } const copyOfFilters = [...filters]; - copyOfFilters[idx][key] = value; + copyOfFilters[idx] = filter; if (typeof onChange === 'function') { onChange(copyOfFilters); } @@ -169,7 +162,12 @@ const Filters = ({ fullWidth options={selectOptions} value={filter.key} - onChange={e => onChangeFilter('key', e.target.value, idx)} + onChange={e => + onChangeFilter( + { key: e.target.value, value: filter.value || '' }, + idx + ) + } isInvalid={isEmpty(filter.key) && !isEmpty(filter.value)} /> </EuiFlexItem> @@ -179,7 +177,12 @@ const Filters = ({ 'xpack.apm.settings.customizeUI.customActions.flyOut.filters.defaultOption.value', { defaultMessage: 'Value' } )} - onChange={e => onChangeFilter('value', e.target.value, idx)} + onChange={e => + onChangeFilter( + { key: filter.key, value: e.target.value }, + idx + ) + } value={filter.value} isInvalid={!isEmpty(filter.key) && isEmpty(filter.value)} inputRef={ref => { diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx index 05a5f32b73c4f..f2456943affbb 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx @@ -15,24 +15,45 @@ import { import { i18n } from '@kbn/i18n'; import React from 'react'; import { Controller, useForm } from 'react-hook-form'; +import { isEmpty } from 'lodash'; import { CustomAction } from '../../../../../../../../../../plugins/apm/server/lib/settings/custom_action/custom_action_types'; import { useApmPluginContext } from '../../../../../../hooks/useApmPluginContext'; import { useCallApmApi } from '../../../../../../hooks/useCallApmApi'; import { ActionSection } from './ActionSection'; import { Filter, FiltersSection } from './FiltersSection'; import { FlyoutFooter } from './Flyoutfooter'; +import { saveCustomAction } from './saveCustomAction'; interface Props { onClose: () => void; customActionSelected?: CustomAction; } -export interface FormData { - label: string; - url: string; +export interface CustomActionFormData extends Omit<CustomAction, 'filters'> { filters: Filter[]; } +const convertFiltersToArray = (filters?: CustomAction['filters']) => { + if (filters) { + return Object.keys(filters).map(key => { + return { key, value: filters[key] || '' }; + }); + } +}; + +const convertFiltersToObject = ( + filters: CustomActionFormData['filters'] +): CustomAction['filters'] => { + if (filters.length) { + return filters + .filter(({ key, value }) => !isEmpty(key) && !isEmpty(value)) + .reduce((acc: Record<string, string>, { key, value }) => { + acc[key] = value; + return acc; + }, {}) as CustomAction['filters']; + } +}; + export const CustomActionsFlyout = ({ onClose, customActionSelected @@ -40,14 +61,24 @@ export const CustomActionsFlyout = ({ const callApmApiFromHook = useCallApmApi(); const { toasts } = useApmPluginContext().core.notifications; const { register, handleSubmit, errors, control, watch } = useForm< - FormData - >(); - - const onSubmit = handleSubmit((customAction: FormData) => { - console.log('### caue: onSubmit -> customAction', customAction); - // saveCustomAction({ callApmApi: callApmApiFromHook, customAction, toasts }); + CustomActionFormData + >({ + defaultValues: { + ...customActionSelected, + filters: convertFiltersToArray(customActionSelected?.filters) + } }); + const onSubmit = handleSubmit((customAction: CustomActionFormData) => { + saveCustomAction({ + callApmApi: callApmApiFromHook, + customAction: { + ...customAction, + filters: convertFiltersToObject(customAction.filters) + }, + toasts + }); + }); // Watch for any change on filters to render the component const filters = watch('filters'); @@ -82,24 +113,14 @@ export const CustomActionsFlyout = ({ <EuiSpacer size="l" /> - <ActionSection - register={register} - errors={errors} - customAction={customActionSelected} - /> + <ActionSection register={register} errors={errors} /> <EuiSpacer size="l" /> <Controller - as={ - <FiltersSection - filters={filters} - customAction={customActionSelected} - /> - } + as={<FiltersSection filters={filters} />} name="filters" control={control} - defaultValue={filters} /> </EuiFlyoutBody> diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/saveCustomAction.ts b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/saveCustomAction.ts index 61c2e62b516bf..bead2cd768b2c 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/saveCustomAction.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/saveCustomAction.ts @@ -7,8 +7,8 @@ import { isEmpty } from 'lodash'; import { i18n } from '@kbn/i18n'; import { NotificationsStart } from 'kibana/public'; +import { CustomAction } from '../../../../../../../../../../plugins/apm/server/lib/settings/custom_action/custom_action_types'; import { APMClient } from '../../../../../../services/rest/createCallApmApi'; -import { CustomAction } from './'; export const saveCustomAction = async ({ callApmApi, @@ -19,23 +19,11 @@ export const saveCustomAction = async ({ customAction: CustomAction; toasts: NotificationsStart['toasts']; }) => { - const { label, url, filters } = customAction; - const customActionBody = { - label, - url, - filters: filters - .filter(({ key, value }) => !isEmpty(key) && !isEmpty(value)) - .reduce((acc: Record<string, string>, { key, value }) => { - acc[key] = value; - return acc; - }, {}) - }; - await callApmApi({ pathname: '/api/apm/settings/custom-actions', method: 'POST', params: { - body: customActionBody + body: customAction } }); diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsTable.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsTable.tsx index 5c5dd11f6f493..c736957a9bbc4 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsTable.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsTable.tsx @@ -47,10 +47,6 @@ export const CustomActionsTable = ({ <TimestampTooltip time={value} timeUnit="minutes" /> ) }, - { - field: 'actions', - name: 'Actions' - }, { name: i18n.translate( 'xpack.apm.settings.customizeUI.customActions.table.actions', diff --git a/x-pack/plugins/apm/server/lib/settings/custom_action/custom_action_types.d.ts b/x-pack/plugins/apm/server/lib/settings/custom_action/custom_action_types.d.ts index d9f45cbff730b..24481e3d0e836 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_action/custom_action_types.d.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_action/custom_action_types.d.ts @@ -10,7 +10,12 @@ export interface CustomAction { url: string; actionId: 'trace'; filters?: { - service?: { name?: string; environment?: string }; - transaction?: { type?: string; name?: string }; + [key: string]: string; + // 'service.name': string; + // 'service.environment': string; + // 'transacttion.type': string; + // 'transaction.name': string; + // service?: { name?: string; environment?: string }; + // transaction?: { type?: string; name?: string }; }; } From b2e5fff66a5fc002524c07d7bef217172610842a Mon Sep 17 00:00:00 2001 From: cauemarcondes <caue.marcondes@elastic.co> Date: Mon, 24 Feb 2020 11:29:02 +0100 Subject: [PATCH 12/42] adding delete option --- .../CustomActionsFlyout/DeleteButton.tsx | 74 +++++++++++++++++++ .../CustomActionsFlyout/Flyoutfooter.tsx | 39 ++++++++-- .../CustomActionsFlyout/index.tsx | 32 +++++--- .../CustomActionsFlyout/saveCustomAction.ts | 52 +++++++------ .../CustomActionsOverview/index.tsx | 10 ++- 5 files changed, 168 insertions(+), 39 deletions(-) create mode 100644 x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/DeleteButton.tsx diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/DeleteButton.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/DeleteButton.tsx new file mode 100644 index 0000000000000..4aefe127a8bde --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/DeleteButton.tsx @@ -0,0 +1,74 @@ +/* + * 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 { EuiButtonEmpty } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { NotificationsStart } from 'kibana/public'; +import React, { useState } from 'react'; +import { useCallApmApi } from '../../../../../../hooks/useCallApmApi'; +import { APMClient } from '../../../../../../services/rest/createCallApmApi'; +import { useApmPluginContext } from '../../../../../../hooks/useApmPluginContext'; + +interface Props { + onDelete: () => void; + customActionId: string; +} + +export function DeleteButton({ onDelete, customActionId }: Props) { + const [isDeleting, setIsDeleting] = useState(false); + const { toasts } = useApmPluginContext().core.notifications; + const callApmApi = useCallApmApi(); + + return ( + <EuiButtonEmpty + color="danger" + isLoading={isDeleting} + iconSide="right" + onClick={async () => { + setIsDeleting(true); + await deleteConfig(callApmApi, customActionId, toasts); + setIsDeleting(false); + onDelete(); + }} + > + {i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.buttonLabel', + { defaultMessage: 'Delete' } + )} + </EuiButtonEmpty> + ); +} + +async function deleteConfig( + callApmApi: APMClient, + customActionId: string, + toasts: NotificationsStart['toasts'] +) { + try { + await callApmApi({ + pathname: '/api/apm/settings/custom-actions/{customActionId}', + method: 'DELETE', + params: { + path: { customActionId } + } + }); + toasts.addSuccess({ + iconType: 'trash', + title: i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.delete.successed', + { defaultMessage: 'Deleted custom action.' } + ) + }); + } catch (error) { + toasts.addDanger({ + iconType: 'cross', + title: i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.delete.failed', + { defaultMessage: 'Custom action could not be deleted' } + ) + }); + } +} diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/Flyoutfooter.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/Flyoutfooter.tsx index df9854a0bf060..430f39bee638c 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/Flyoutfooter.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/Flyoutfooter.tsx @@ -12,8 +12,19 @@ import { EuiButton } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { DeleteButton } from './DeleteButton'; -export const FlyoutFooter = ({ onClose }: { onClose: () => void }) => { +export const FlyoutFooter = ({ + onClose, + isLoading, + onDelete, + customActionId +}: { + onClose: () => void; + isLoading: boolean; + onDelete: () => void; + customActionId?: string; +}) => { return ( <EuiFlyoutFooter> <EuiFlexGroup justifyContent="spaceBetween"> @@ -28,14 +39,26 @@ export const FlyoutFooter = ({ onClose }: { onClose: () => void }) => { </EuiButtonEmpty> </EuiFlexItem> <EuiFlexItem grow={false}> - <EuiButton fill type="submit"> - {i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.flyout.save', - { - defaultMessage: 'Save' - } + <EuiFlexGroup> + {customActionId && ( + <EuiFlexItem> + <DeleteButton + customActionId={customActionId} + onDelete={onDelete} + /> + </EuiFlexItem> )} - </EuiButton> + <EuiFlexItem> + <EuiButton fill type="submit" isLoading={isLoading}> + {i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.flyout.save', + { + defaultMessage: 'Save' + } + )} + </EuiButton> + </EuiFlexItem> + </EuiFlexGroup> </EuiFlexItem> </EuiFlexGroup> </EuiFlyoutFooter> diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx index f2456943affbb..eac8093151f33 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx @@ -13,7 +13,7 @@ import { EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React from 'react'; +import React, { useState } from 'react'; import { Controller, useForm } from 'react-hook-form'; import { isEmpty } from 'lodash'; import { CustomAction } from '../../../../../../../../../../plugins/apm/server/lib/settings/custom_action/custom_action_types'; @@ -21,12 +21,14 @@ import { useApmPluginContext } from '../../../../../../hooks/useApmPluginContext import { useCallApmApi } from '../../../../../../hooks/useCallApmApi'; import { ActionSection } from './ActionSection'; import { Filter, FiltersSection } from './FiltersSection'; -import { FlyoutFooter } from './Flyoutfooter'; +import { FlyoutFooter } from './FlyoutFooter'; import { saveCustomAction } from './saveCustomAction'; interface Props { onClose: () => void; customActionSelected?: CustomAction; + onSave: () => void; + onDelete: () => void; } export interface CustomActionFormData extends Omit<CustomAction, 'filters'> { @@ -44,7 +46,7 @@ const convertFiltersToArray = (filters?: CustomAction['filters']) => { const convertFiltersToObject = ( filters: CustomActionFormData['filters'] ): CustomAction['filters'] => { - if (filters.length) { + if (filters && filters.length) { return filters .filter(({ key, value }) => !isEmpty(key) && !isEmpty(value)) .reduce((acc: Record<string, string>, { key, value }) => { @@ -56,9 +58,12 @@ const convertFiltersToObject = ( export const CustomActionsFlyout = ({ onClose, - customActionSelected + customActionSelected, + onSave, + onDelete }: Props) => { const callApmApiFromHook = useCallApmApi(); + const [isLoading, setIsLoading] = useState(false); const { toasts } = useApmPluginContext().core.notifications; const { register, handleSubmit, errors, control, watch } = useForm< CustomActionFormData @@ -69,23 +74,27 @@ export const CustomActionsFlyout = ({ } }); - const onSubmit = handleSubmit((customAction: CustomActionFormData) => { - saveCustomAction({ + const onSubmit = async (customAction: CustomActionFormData) => { + setIsLoading(true); + await saveCustomAction({ callApmApi: callApmApiFromHook, customAction: { + ...customActionSelected, ...customAction, filters: convertFiltersToObject(customAction.filters) }, toasts }); - }); + setIsLoading(false); + onSave(); + }; // Watch for any change on filters to render the component const filters = watch('filters'); return ( <EuiPortal> <EuiFlyout ownFocus onClose={onClose} size="m"> - <form onSubmit={onSubmit}> + <form onSubmit={handleSubmit(onSubmit)}> <EuiFlyoutHeader hasBorder> <EuiTitle size="s"> <h2> @@ -124,7 +133,12 @@ export const CustomActionsFlyout = ({ /> </EuiFlyoutBody> - <FlyoutFooter onClose={onClose} /> + <FlyoutFooter + onClose={onClose} + isLoading={isLoading} + onDelete={onDelete} + customActionId={customActionSelected?.id} + /> </form> </EuiFlyout> </EuiPortal> diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/saveCustomAction.ts b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/saveCustomAction.ts index bead2cd768b2c..23cef29ecca1b 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/saveCustomAction.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/saveCustomAction.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { isEmpty } from 'lodash'; import { i18n } from '@kbn/i18n'; import { NotificationsStart } from 'kibana/public'; import { CustomAction } from '../../../../../../../../../../plugins/apm/server/lib/settings/custom_action/custom_action_types'; @@ -19,25 +18,36 @@ export const saveCustomAction = async ({ customAction: CustomAction; toasts: NotificationsStart['toasts']; }) => { - await callApmApi({ - pathname: '/api/apm/settings/custom-actions', - method: 'POST', - params: { - body: customAction - } - }); - - // TODO: caue change the toast messages - toasts.addSuccess({ - title: i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.succeeded.title', - { defaultMessage: 'Created a new custom action!' } - ), - text: i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.succeeded.text', - { - defaultMessage: 'We have succesfully created a custom action.' + if ('id' in customAction) { + await callApmApi({ + pathname: '/api/apm/settings/custom-actions/{customActionId}', + method: 'PUT', + params: { + path: { customActionId: customAction.id }, + body: customAction + } + }); + toasts.addSuccess({ + iconType: 'check', + title: i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.update.successed', + { defaultMessage: 'Changes saved!' } + ) + }); + } else { + await callApmApi({ + pathname: '/api/apm/settings/custom-actions', + method: 'POST', + params: { + body: customAction } - ) - }); + }); + toasts.addSuccess({ + iconType: 'check', + title: i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.create.successed', + { defaultMessage: 'Created a new custom action!' } + ) + }); + } }; diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/index.tsx index 9a2e2018b424f..5214f04adcf0e 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/index.tsx @@ -21,7 +21,7 @@ export const CustomActionsOverview = () => { CustomAction | undefined >(); - const { data: customActions } = useFetcher( + const { data: customActions, refetch } = useFetcher( callApmApi => callApmApi({ pathname: '/api/apm/settings/custom-actions' }), [] ); @@ -49,6 +49,14 @@ export const CustomActionsOverview = () => { <CustomActionsFlyout onClose={onCloseFlyout} customActionSelected={customActionSelected} + onSave={() => { + onCloseFlyout(); + refetch(); + }} + onDelete={() => { + onCloseFlyout(); + refetch(); + }} /> )} <EuiPanel> From 1d196947bed13bc992b601032b7c7f972a8ea5c1 Mon Sep 17 00:00:00 2001 From: cauemarcondes <caue.marcondes@elastic.co> Date: Mon, 24 Feb 2020 11:29:47 +0100 Subject: [PATCH 13/42] removing useForm --- .../CustomActionsFlyout/useForm.tsx | 82 ------------------- 1 file changed, 82 deletions(-) delete mode 100644 x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/useForm.tsx diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/useForm.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/useForm.tsx deleted file mode 100644 index 892ec6e5250e7..0000000000000 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/useForm.tsx +++ /dev/null @@ -1,82 +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. - */ -/* eslint-disable react-hooks/exhaustive-deps */ -import { - EuiFieldText, - EuiForm, - EuiFormRow, - EuiTitle, - EuiText -} from '@elastic/eui'; -import React, { useState, useMemo, useEffect } from 'react'; - -interface Field { - type: 'text'; - name: string; - label: string; - helpText: string; - placeholder: string; - required?: boolean; -} - -export type FormType = Field[]; - -export const useForm = ({ - fields, - title, - subtitle -}: { - fields: FormType; - title?: string; - subtitle?: string; -}) => { - const [data, setData] = useState({}); - - const syncData = (values: any) => { - setData(values); - }; - - const Form = () => { - const [formValues, setFormValues] = useState({}); - useEffect(() => { - syncData(formValues); - }, [formValues]); - return ( - <> - {title && ( - <EuiTitle size="xs"> - <h3>{title}</h3> - </EuiTitle> - )} - {subtitle && <EuiText size="xs">{subtitle}</EuiText>} - <EuiForm> - {fields.map(field => { - return ( - <EuiFormRow - key={field.name} - label={field.label} - helpText={field.helpText} - > - <EuiFieldText - placeholder={field.placeholder} - name={field.name} - onChange={e => { - setFormValues(values => ({ - ...values, - [field.name]: e.target.value - })); - }} - /> - </EuiFormRow> - ); - })} - </EuiForm> - </> - ); - }; - - return { ...data, Form: useMemo(() => Form, fields) }; -}; From f9ef63ae8d1b5b97ae7efd7b12dda7d704ff3f33 Mon Sep 17 00:00:00 2001 From: cauemarcondes <caue.marcondes@elastic.co> Date: Mon, 24 Feb 2020 11:53:28 +0100 Subject: [PATCH 14/42] fixing flyout view --- .../CustomActionsFlyout/ActionSection.tsx | 1 + .../CustomActionsFlyout/FiltersSection.tsx | 10 ++++++++-- .../CustomActionsFlyout/index.tsx | 8 ++++---- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/ActionSection.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/ActionSection.tsx index 8ea805bf132f0..df667fd33f250 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/ActionSection.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/ActionSection.tsx @@ -68,6 +68,7 @@ export const ActionSection = ({ register, errors }: ActionSectionProps) => { {actionFields.map((field: ActionField) => { return ( <EuiFormRow + fullWidth key={field.name} label={field.label} helpText={field.helpText} diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx index 3e8db8b8c1b21..2cd8ff3c16365 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx @@ -156,12 +156,18 @@ const Filters = ({ const selectOptions = getSelectOptions(filters, idx); return ( <EuiFlexGroup key={filterId} gutterSize="s" alignItems="center"> - <EuiFlexItem grow={false}> + <EuiFlexItem> <EuiSelect id={filterId} fullWidth options={selectOptions} value={filter.key} + prepend={i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.flyout.filters.prepend', + { + defaultMessage: 'Field' + } + )} onChange={e => onChangeFilter( { key: e.target.value, value: filter.value || '' }, @@ -171,7 +177,7 @@ const Filters = ({ isInvalid={isEmpty(filter.key) && !isEmpty(filter.value)} /> </EuiFlexItem> - <EuiFlexItem grow={false}> + <EuiFlexItem> <EuiFieldText placeholder={i18n.translate( 'xpack.apm.settings.customizeUI.customActions.flyOut.filters.defaultOption.value', diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx index eac8093151f33..e152be66193dc 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx @@ -93,8 +93,8 @@ export const CustomActionsFlyout = ({ return ( <EuiPortal> - <EuiFlyout ownFocus onClose={onClose} size="m"> - <form onSubmit={handleSubmit(onSubmit)}> + <form onSubmit={handleSubmit(onSubmit)}> + <EuiFlyout ownFocus onClose={onClose} size="m"> <EuiFlyoutHeader hasBorder> <EuiTitle size="s"> <h2> @@ -139,8 +139,8 @@ export const CustomActionsFlyout = ({ onDelete={onDelete} customActionId={customActionSelected?.id} /> - </form> - </EuiFlyout> + </EuiFlyout> + </form> </EuiPortal> ); }; From e4f8bdfb869061ce728895f332e357668d7da398 Mon Sep 17 00:00:00 2001 From: cauemarcondes <caue.marcondes@elastic.co> Date: Mon, 24 Feb 2020 12:04:27 +0100 Subject: [PATCH 15/42] filters are invalid when selecting the default value --- .../CustomActionsFlyout/ActionSection.tsx | 5 +++-- .../CustomActionsFlyout/FiltersSection.tsx | 21 ++++++++++++------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/ActionSection.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/ActionSection.tsx index df667fd33f250..74deaa66b976f 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/ActionSection.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/ActionSection.tsx @@ -32,9 +32,10 @@ const actionFields: ActionField[] = [ { name: 'label', label: 'Label', - helpText: 'Labels can be a maximum of 128 characters', + helpText: + 'Keep it as short as possible. This is the label shown in the actions context menu.', placeholder: 'e.g. Support tickets', - register: { required: true, maxLength: 128 } + register: { required: true } }, { name: 'url', diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx index 2cd8ff3c16365..7cb0091cf82cc 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx @@ -22,14 +22,16 @@ export interface Filter { value: string; } +const DEFAULT_OPTION = { + value: 'DEFAULT', + text: i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.flyOut.filters.defaultOption', + { defaultMessage: 'Select fields...' } + ) +}; + const filterOptions = [ - { - value: 'DEFAULT', - text: i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.flyOut.filters.defaultOption', - { defaultMessage: 'Select fields...' } - ) - }, + DEFAULT_OPTION, { value: 'service.name', text: 'service.name' }, { value: 'service.environment', text: 'service.environment' }, { value: 'transaction.type', text: 'transaction.type' }, @@ -174,7 +176,10 @@ const Filters = ({ idx ) } - isInvalid={isEmpty(filter.key) && !isEmpty(filter.value)} + isInvalid={ + !isEmpty(filter.value) && + (isEmpty(filter.key) || filter.key === DEFAULT_OPTION.value) + } /> </EuiFlexItem> <EuiFlexItem> From df3dbb954693d0ed6b568285506a2d6015eb2bb9 Mon Sep 17 00:00:00 2001 From: cauemarcondes <caue.marcondes@elastic.co> Date: Mon, 24 Feb 2020 13:32:48 +0100 Subject: [PATCH 16/42] ui fixes --- .../CustomActionsFlyout/index.tsx | 2 +- .../CustomActionsOverview/CustomActionsTable.tsx | 12 ++++++++---- .../CustomizeUI/CustomActionsOverview/index.tsx | 15 ++++++++------- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx index e152be66193dc..f771f4eac9094 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx @@ -36,7 +36,7 @@ export interface CustomActionFormData extends Omit<CustomAction, 'filters'> { } const convertFiltersToArray = (filters?: CustomAction['filters']) => { - if (filters) { + if (filters && Object.keys(filters).length) { return Object.keys(filters).map(key => { return { key, value: filters[key] || '' }; }); diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsTable.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsTable.tsx index c736957a9bbc4..c0bf855e4e8ba 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsTable.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsTable.tsx @@ -5,9 +5,11 @@ */ import React from 'react'; import { i18n } from '@kbn/i18n'; +import { units, px } from '../../../../../style/variables'; import { CustomAction } from '../../../../../../../../../plugins/apm/server/lib/settings/custom_action/custom_action_types'; import { ManagedTable } from '../../../../shared/ManagedTable'; import { TimestampTooltip } from '../../../../shared/TimestampTooltip'; +import { LoadingStatePrompt } from '../../../../shared/LoadingStatePrompt'; interface Props { items: CustomAction[]; @@ -15,7 +17,7 @@ interface Props { } export const CustomActionsTable = ({ - items, + items = [], onCustomActionSelected }: Props) => { const columns = [ @@ -36,6 +38,7 @@ export const CustomActionsTable = ({ truncateText: true }, { + width: px(160), align: 'right', field: '@timestamp', name: i18n.translate( @@ -48,6 +51,7 @@ export const CustomActionsTable = ({ ) }, { + width: px(units.quadruple), name: i18n.translate( 'xpack.apm.settings.customizeUI.customActions.table.actions', { defaultMessage: 'Actions' } @@ -75,12 +79,12 @@ export const CustomActionsTable = ({ return ( <ManagedTable + noItemsMessage={<LoadingStatePrompt />} items={items} columns={columns} initialPageSize={10} - initialSortField="occurrenceCount" - initialSortDirection="desc" - sortItems={false} + initialSortField="@timestamp" + initialSortDirection="asc" /> ); }; diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/index.tsx index 5214f04adcf0e..db96288a57686 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/index.tsx @@ -8,7 +8,7 @@ import { EuiPanel, EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { isEmpty } from 'lodash'; import React, { useEffect, useState } from 'react'; import { CustomAction } from '../../../../../../../../../plugins/apm/server/lib/settings/custom_action/custom_action_types'; -import { useFetcher } from '../../../../../hooks/useFetcher'; +import { useFetcher, FETCH_STATUS } from '../../../../../hooks/useFetcher'; import { CustomActionsFlyout } from './CustomActionsFlyout'; import { CustomActionsTable } from './CustomActionsTable'; import { EmptyPrompt } from './EmptyPrompt'; @@ -21,7 +21,7 @@ export const CustomActionsOverview = () => { CustomAction | undefined >(); - const { data: customActions, refetch } = useFetcher( + const { data: customActions, status, refetch } = useFetcher( callApmApi => callApmApi({ pathname: '/api/apm/settings/custom-actions' }), [] ); @@ -41,7 +41,8 @@ export const CustomActionsOverview = () => { setIsFlyoutOpen(true); }; - const hasCustomActions = !isEmpty(customActions); + const showEmptyPrompt = + status === FETCH_STATUS.SUCCESS && isEmpty(customActions); return ( <> @@ -64,7 +65,7 @@ export const CustomActionsOverview = () => { <EuiFlexItem grow={false}> <Title /> </EuiFlexItem> - {hasCustomActions && ( + {!showEmptyPrompt && ( <EuiFlexItem> <EuiFlexGroup alignItems="center" justifyContent="flexEnd"> <EuiFlexItem grow={false}> @@ -79,13 +80,13 @@ export const CustomActionsOverview = () => { <EuiSpacer size="m" /> - {hasCustomActions ? ( + {showEmptyPrompt ? ( + <EmptyPrompt onCreateCustomActionClick={onCreateCustomActionClick} /> + ) : ( <CustomActionsTable items={customActions} onCustomActionSelected={setCustomActionSelected} /> - ) : ( - <EmptyPrompt onCreateCustomActionClick={onCreateCustomActionClick} /> )} </EuiPanel> </> From 5b9d7f3daeb6e1524f77ce4545c0f603ae895ea8 Mon Sep 17 00:00:00 2001 From: cauemarcondes <caue.marcondes@elastic.co> Date: Mon, 24 Feb 2020 14:12:20 +0100 Subject: [PATCH 17/42] ui fixes --- .../CustomActionsFlyout/ActionSection.tsx | 11 ++++++- .../CustomActionsFlyout/DeleteButton.tsx | 7 ++-- .../CustomActionsFlyout/FiltersSection.tsx | 2 +- .../server/routes/settings/custom_actions.ts | 32 +------------------ 4 files changed, 15 insertions(+), 37 deletions(-) diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/ActionSection.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/ActionSection.tsx index 74deaa66b976f..ce5151e90a2e8 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/ActionSection.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/ActionSection.tsx @@ -73,7 +73,16 @@ export const ActionSection = ({ register, errors }: ActionSectionProps) => { key={field.name} label={field.label} helpText={field.helpText} - labelAppend={<EuiText size="xs">Required</EuiText>} + labelAppend={ + <EuiText size="xs"> + {i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.flyout.required', + { + defaultMessage: 'Required' + } + )} + </EuiText> + } > <EuiFieldText inputRef={register(field.register)} diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/DeleteButton.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/DeleteButton.tsx index 4aefe127a8bde..e4ff8c4a991e2 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/DeleteButton.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/DeleteButton.tsx @@ -34,10 +34,9 @@ export function DeleteButton({ onDelete, customActionId }: Props) { onDelete(); }} > - {i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.buttonLabel', - { defaultMessage: 'Delete' } - )} + {i18n.translate('xpack.apm.settings.customizeUI.customActions.delete', { + defaultMessage: 'Delete' + })} </EuiButtonEmpty> ); } diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx index 7cb0091cf82cc..fd6cf1ac87d0d 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx @@ -172,7 +172,7 @@ const Filters = ({ )} onChange={e => onChangeFilter( - { key: e.target.value, value: filter.value || '' }, + { key: e.target.value, value: filter.value }, idx ) } diff --git a/x-pack/plugins/apm/server/routes/settings/custom_actions.ts b/x-pack/plugins/apm/server/routes/settings/custom_actions.ts index 6436947bcfbca..eeeb7bd6144cf 100644 --- a/x-pack/plugins/apm/server/routes/settings/custom_actions.ts +++ b/x-pack/plugins/apm/server/routes/settings/custom_actions.ts @@ -10,14 +10,6 @@ import { createOrUpdateCustomAction } from '../../lib/settings/custom_action/cre import { deleteCustomAction } from '../../lib/settings/custom_action/delete_custom_action'; import { listCustomActions } from '../../lib/settings/custom_action/list_custom_actions'; -// { value: '{ value: 'service.name', text: 'service.name' }, -// { value: 'service.environment', text: 'service.environment' }, -// { value: 'transaction.type', text: 'transaction.type' }, -// { value: 'transaction.name', text: 'transaction.name' }', text: 'service.name' }, -// { value: 'service.environment', text: 'service.environment' }, -// { value: 'transaction.type', text: 'transaction.type' }, -// { value: 'transaction.name', text: 'transaction.name' } - export const listCustomActionsRoute = createRoute(core => ({ path: '/api/apm/settings/custom-actions', handler: async ({ context, request }) => { @@ -26,35 +18,13 @@ export const listCustomActionsRoute = createRoute(core => ({ } })); -const agentPayloadRt = t.intersection([ - t.partial({ agent_name: t.string }), - t.type({ - service: t.intersection([ - t.partial({ name: t.string }), - t.partial({ environment: t.string }) - ]) - }), - t.type({ - settings: t.intersection([ - t.partial({ transaction_sample_rate: t.string }), - t.partial({ capture_body: t.string }), - t.partial({ transaction_max_spans: t.string }) - ]) - }) -]); - const payload = t.intersection([ t.type({ label: t.string, url: t.string }), t.partial({ - filters: t.intersection([ - t.partial({ - service: t.partial({ name: t.string, environment: t.string }) - }), - t.partial({ transaction: t.partial({ name: t.string, type: t.string }) }) - ]) + filters: t.record(t.string, t.string) }) ]); From dc3d4e27fd373e35aa40d5359e8567b87298019d Mon Sep 17 00:00:00 2001 From: cauemarcondes <caue.marcondes@elastic.co> Date: Mon, 24 Feb 2020 15:03:33 +0100 Subject: [PATCH 18/42] fixing typescript --- .../CustomActionsFlyout/DeleteButton.tsx | 4 ++-- .../CustomActionsFlyout/saveCustomAction.ts | 6 +++--- .../lib/settings/custom_action/custom_action_types.d.ts | 7 +------ 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/DeleteButton.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/DeleteButton.tsx index e4ff8c4a991e2..84941a77e25b6 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/DeleteButton.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/DeleteButton.tsx @@ -48,10 +48,10 @@ async function deleteConfig( ) { try { await callApmApi({ - pathname: '/api/apm/settings/custom-actions/{customActionId}', + pathname: '/api/apm/settings/custom-actions/{id}', method: 'DELETE', params: { - path: { customActionId } + path: { id: customActionId } } }); toasts.addSuccess({ diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/saveCustomAction.ts b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/saveCustomAction.ts index 23cef29ecca1b..549ba75e3c6a7 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/saveCustomAction.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/saveCustomAction.ts @@ -18,12 +18,12 @@ export const saveCustomAction = async ({ customAction: CustomAction; toasts: NotificationsStart['toasts']; }) => { - if ('id' in customAction) { + if (customAction?.id) { await callApmApi({ - pathname: '/api/apm/settings/custom-actions/{customActionId}', + pathname: '/api/apm/settings/custom-actions/{id}', method: 'PUT', params: { - path: { customActionId: customAction.id }, + path: { id: customAction.id }, body: customAction } }); diff --git a/x-pack/plugins/apm/server/lib/settings/custom_action/custom_action_types.d.ts b/x-pack/plugins/apm/server/lib/settings/custom_action/custom_action_types.d.ts index 24481e3d0e836..c84547edcaa07 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_action/custom_action_types.d.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_action/custom_action_types.d.ts @@ -5,17 +5,12 @@ */ export interface CustomAction { + id?: string; '@timestamp': number; label: string; url: string; actionId: 'trace'; filters?: { [key: string]: string; - // 'service.name': string; - // 'service.environment': string; - // 'transacttion.type': string; - // 'transaction.name': string; - // service?: { name?: string; environment?: string }; - // transaction?: { type?: string; name?: string }; }; } From 3925c6759a610487112a89daeaf2d8ba1a5cf979 Mon Sep 17 00:00:00 2001 From: cauemarcondes <caue.marcondes@elastic.co> Date: Mon, 24 Feb 2020 15:06:05 +0100 Subject: [PATCH 19/42] fixing typescript --- .../Settings/CustomizeUI/CustomActionsOverview/EmptyPrompt.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/EmptyPrompt.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/EmptyPrompt.tsx index 6c06d70958944..3bf218d98ff44 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/EmptyPrompt.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/EmptyPrompt.tsx @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; +import { EuiEmptyPrompt } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { CreateCustomActionButton } from './CreateCustomActionButton'; From b86e0cd2e0688d1c55b8d5b0ad036db4ef72961c Mon Sep 17 00:00:00 2001 From: cauemarcondes <caue.marcondes@elastic.co> Date: Mon, 24 Feb 2020 15:37:21 +0100 Subject: [PATCH 20/42] fixing labels and adding space btw components --- .../CustomActionsFlyout/ActionSection.tsx | 10 +++++----- .../CustomActionsFlyout/FiltersSection.tsx | 2 ++ .../CustomActionsFlyout/index.tsx | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/ActionSection.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/ActionSection.tsx index ce5151e90a2e8..c32cafdf86703 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/ActionSection.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/ActionSection.tsx @@ -25,7 +25,7 @@ interface ActionField { label: string; helpText: string; placeholder: string; - register: ValidationOptions; + validations: ValidationOptions; } const actionFields: ActionField[] = [ @@ -33,9 +33,9 @@ const actionFields: ActionField[] = [ name: 'label', label: 'Label', helpText: - 'Keep it as short as possible. This is the label shown in the actions context menu.', + 'This is the label shown in the actions context menu. Keep it as short as possible.', placeholder: 'e.g. Support tickets', - register: { required: true } + validations: { required: true } }, { name: 'url', @@ -43,7 +43,7 @@ const actionFields: ActionField[] = [ helpText: 'Add fieldname variables to your URL to apply values e.g. {{trace.id}}. TODO: Learn more in the docs.', placeholder: 'e.g. https://www.elastic.co/', - register: { required: true } + validations: { required: true } } ]; @@ -85,7 +85,7 @@ export const ActionSection = ({ register, errors }: ActionSectionProps) => { } > <EuiFieldText - inputRef={register(field.register)} + inputRef={register(field.validations)} placeholder={field.placeholder} name={field.name} fullWidth diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx index fd6cf1ac87d0d..e496a43a0ff1b 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx @@ -74,6 +74,8 @@ export const FiltersSection = ({ )} </EuiText> + <EuiSpacer size="s" /> + <Filters filters={filters} onChange={onChange} /> <EuiSpacer size="xs" /> diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx index f771f4eac9094..5dee0501f8ec7 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx @@ -114,7 +114,7 @@ export const CustomActionsFlyout = ({ 'xpack.apm.settings.customizeUI.customActions.flyout.label', { defaultMessage: - 'Actions will be shown in the context of trace and error details througout the APM app. You can specify an unlimited amount of links, but we will opt to only show the first 3 alphabetically.' + 'Actions will be available in the context of transaction details throughout the APM app. You can create an unlimited number of actions and use the filter options to scope them to only appear for specific services. You can refer to dynamic variables by using any of the transaction metadata to fill in your URLs. TODO: Learn more about it in the docs.' } )} </p> From 11c77e239821a9f61705658efdf2d61244a5e654 Mon Sep 17 00:00:00 2001 From: cauemarcondes <caue.marcondes@elastic.co> Date: Tue, 25 Feb 2020 09:39:52 +0100 Subject: [PATCH 21/42] refactoring filters structure --- .../CustomActionsFlyout/FiltersSection.tsx | 67 +++++++------------ .../CustomActionsFlyout/Flyoutfooter.tsx | 6 +- .../CustomActionsFlyout/index.tsx | 39 +++++------ 3 files changed, 45 insertions(+), 67 deletions(-) diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx index e496a43a0ff1b..55f417514c073 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx @@ -16,11 +16,9 @@ import { import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; import React, { useRef } from 'react'; +import { CustomActionFormData } from '.'; -export interface Filter { - key: string; - value: string; -} +type FiltersType = CustomActionFormData['filters']; const DEFAULT_OPTION = { value: 'DEFAULT', @@ -39,16 +37,14 @@ const filterOptions = [ ]; export const FiltersSection = ({ - filters = [{ key: '', value: '' }], + filters, onChange }: { - filters: Filter[]; - onChange?: (filters: Filter[]) => void; + filters: FiltersType; + onChange: (filters: FiltersType) => void; }) => { const handleAddFilter = () => { - if (typeof onChange === 'function') { - onChange([...filters, { key: '', value: '' }]); - } + onChange([...filters, ['', '']]); }; return ( @@ -110,10 +106,10 @@ const AddFilterButton = ({ </EuiButtonEmpty> ); -const getSelectOptions = (filters: Filter[], idx: number) => { +const getSelectOptions = (filters: FiltersType, idx: number) => { return filterOptions.filter(option => { const indexUsedFilter = filters.findIndex( - _filter => _filter.key === option.value + filter => filter[0] === option.value ); // Filter out all items already added, besides the one selected in the current filter. return indexUsedFilter === -1 || idx === indexUsedFilter; @@ -124,20 +120,18 @@ const Filters = ({ filters, onChange }: { - filters: Filter[]; - onChange?: (filters: Filter[]) => void; + filters: FiltersType; + onChange: (filters: FiltersType) => void; }) => { const filterValueRefs = useRef<HTMLInputElement[]>([]); - const onChangeFilter = (filter: Filter, idx: number) => { + const onChangeFilter = (filter: FiltersType[0], idx: number) => { if (filterValueRefs.current[idx]) { filterValueRefs.current[idx].focus(); } const copyOfFilters = [...filters]; copyOfFilters[idx] = filter; - if (typeof onChange === 'function') { - onChange(copyOfFilters); - } + onChange(copyOfFilters); }; const onRemoveFilter = (idx: number) => { @@ -146,16 +140,16 @@ const Filters = ({ // When empty, means that it was the last filter that got removed, // so instead of showing an empty list, will add a new empty filter. if (isEmpty(copyOfFilters)) { - copyOfFilters.push({ key: '', value: '' }); - } - if (typeof onChange === 'function') { - onChange(copyOfFilters); + copyOfFilters.push(['', '']); } + + onChange(copyOfFilters); }; return ( <> - {filters.map((filter: Filter, idx: number) => { + {filters.map((filter, idx) => { + const [key, value] = filter; const filterId = `filter-${idx}`; const selectOptions = getSelectOptions(filters, idx); return ( @@ -165,39 +159,30 @@ const Filters = ({ id={filterId} fullWidth options={selectOptions} - value={filter.key} + value={key} prepend={i18n.translate( 'xpack.apm.settings.customizeUI.customActions.flyout.filters.prepend', { defaultMessage: 'Field' } )} - onChange={e => - onChangeFilter( - { key: e.target.value, value: filter.value }, - idx - ) - } + onChange={e => onChangeFilter([e.target.value, value], idx)} isInvalid={ - !isEmpty(filter.value) && - (isEmpty(filter.key) || filter.key === DEFAULT_OPTION.value) + !isEmpty(value) && + (isEmpty(key) || key === DEFAULT_OPTION.value) } /> </EuiFlexItem> <EuiFlexItem> <EuiFieldText + fullWidth placeholder={i18n.translate( 'xpack.apm.settings.customizeUI.customActions.flyOut.filters.defaultOption.value', { defaultMessage: 'Value' } )} - onChange={e => - onChangeFilter( - { key: filter.key, value: e.target.value }, - idx - ) - } - value={filter.value} - isInvalid={!isEmpty(filter.key) && isEmpty(filter.value)} + onChange={e => onChangeFilter([key, e.target.value], idx)} + value={value} + isInvalid={!isEmpty(key) && isEmpty(value)} inputRef={ref => { if (ref) { filterValueRefs.current.push(ref); @@ -211,7 +196,7 @@ const Filters = ({ <EuiButtonEmpty iconType="trash" onClick={() => onRemoveFilter(idx)} - disabled={!filter.key && filters.length === 1} + disabled={!key && filters.length === 1} /> </EuiFlexItem> </EuiFlexGroup> diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/Flyoutfooter.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/Flyoutfooter.tsx index 430f39bee638c..e27e08b288cca 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/Flyoutfooter.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/Flyoutfooter.tsx @@ -16,12 +16,12 @@ import { DeleteButton } from './DeleteButton'; export const FlyoutFooter = ({ onClose, - isLoading, + isSaving, onDelete, customActionId }: { onClose: () => void; - isLoading: boolean; + isSaving: boolean; onDelete: () => void; customActionId?: string; }) => { @@ -49,7 +49,7 @@ export const FlyoutFooter = ({ </EuiFlexItem> )} <EuiFlexItem> - <EuiButton fill type="submit" isLoading={isLoading}> + <EuiButton fill type="submit" isLoading={isSaving}> {i18n.translate( 'xpack.apm.settings.customizeUI.customActions.flyout.save', { diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx index 5dee0501f8ec7..747186b473365 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx @@ -20,7 +20,7 @@ import { CustomAction } from '../../../../../../../../../../plugins/apm/server/l import { useApmPluginContext } from '../../../../../../hooks/useApmPluginContext'; import { useCallApmApi } from '../../../../../../hooks/useCallApmApi'; import { ActionSection } from './ActionSection'; -import { Filter, FiltersSection } from './FiltersSection'; +import { FiltersSection } from './FiltersSection'; import { FlyoutFooter } from './FlyoutFooter'; import { saveCustomAction } from './saveCustomAction'; @@ -32,29 +32,20 @@ interface Props { } export interface CustomActionFormData extends Omit<CustomAction, 'filters'> { - filters: Filter[]; + filters: Array<[string, string]>; } -const convertFiltersToArray = (filters?: CustomAction['filters']) => { - if (filters && Object.keys(filters).length) { - return Object.keys(filters).map(key => { - return { key, value: filters[key] || '' }; - }); +const convertFiltersToArray = (filters: CustomAction['filters'] = {}) => { + const convertedFilters = Object.entries(filters); + // When convertedFilters is empty, initiate the filters filled with one item. + if (isEmpty(convertedFilters)) { + convertedFilters.push(['', '']); } + return convertedFilters; }; -const convertFiltersToObject = ( - filters: CustomActionFormData['filters'] -): CustomAction['filters'] => { - if (filters && filters.length) { - return filters - .filter(({ key, value }) => !isEmpty(key) && !isEmpty(value)) - .reduce((acc: Record<string, string>, { key, value }) => { - acc[key] = value; - return acc; - }, {}) as CustomAction['filters']; - } -}; +const convertFiltersToObject = (filters: CustomActionFormData['filters']) => + Object.fromEntries(filters); export const CustomActionsFlyout = ({ onClose, @@ -63,8 +54,9 @@ export const CustomActionsFlyout = ({ onDelete }: Props) => { const callApmApiFromHook = useCallApmApi(); - const [isLoading, setIsLoading] = useState(false); + const [isSaving, setIsSaving] = useState(false); const { toasts } = useApmPluginContext().core.notifications; + const { register, handleSubmit, errors, control, watch } = useForm< CustomActionFormData >({ @@ -75,7 +67,7 @@ export const CustomActionsFlyout = ({ }); const onSubmit = async (customAction: CustomActionFormData) => { - setIsLoading(true); + setIsSaving(true); await saveCustomAction({ callApmApi: callApmApiFromHook, customAction: { @@ -85,7 +77,7 @@ export const CustomActionsFlyout = ({ }, toasts }); - setIsLoading(false); + setIsSaving(false); onSave(); }; // Watch for any change on filters to render the component @@ -127,6 +119,7 @@ export const CustomActionsFlyout = ({ <EuiSpacer size="l" /> <Controller + // @ts-ignore: onChange function property will be automatically handled by react-hook-form Controller. as={<FiltersSection filters={filters} />} name="filters" control={control} @@ -135,7 +128,7 @@ export const CustomActionsFlyout = ({ <FlyoutFooter onClose={onClose} - isLoading={isLoading} + isSaving={isSaving} onDelete={onDelete} customActionId={customActionSelected?.id} /> From 920b5bebf178f5baf1cc43c50f8f9bbf8cd1286e Mon Sep 17 00:00:00 2001 From: cauemarcondes <caue.marcondes@elastic.co> Date: Tue, 25 Feb 2020 11:26:18 +0100 Subject: [PATCH 22/42] removing reach-hook-form --- .../CustomActionsFlyout/ActionSection.tsx | 79 +++++----- .../CustomActionsFlyout/FiltersSection.tsx | 136 ++++++++---------- .../CustomActionsFlyout/index.tsx | 54 ++++--- .../CustomActionsFlyout/saveCustomAction.ts | 26 +++- .../CustomActionsTable.tsx | 2 +- .../custom_action/custom_action_types.d.ts | 2 +- .../server/routes/settings/custom_actions.ts | 1 + 7 files changed, 162 insertions(+), 138 deletions(-) diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/ActionSection.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/ActionSection.tsx index c32cafdf86703..cdfdcb7b4d720 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/ActionSection.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/ActionSection.tsx @@ -3,56 +3,64 @@ * 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 { - ValidationOptions, - FormContextValues, - NestDataObject -} from 'react-hook-form'; import { - EuiTitle, + EuiFieldText, + EuiFormRow, EuiSpacer, EuiText, - EuiFieldText, - EuiFormRow + EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; +import React from 'react'; +import { NestDataObject } from 'react-hook-form'; import { CustomActionFormData } from './'; -interface ActionField { +interface InputField { name: keyof CustomActionFormData; label: string; helpText: string; placeholder: string; - validations: ValidationOptions; + onChange: (value: string) => void; + value?: string; } -const actionFields: ActionField[] = [ - { - name: 'label', - label: 'Label', - helpText: - 'This is the label shown in the actions context menu. Keep it as short as possible.', - placeholder: 'e.g. Support tickets', - validations: { required: true } - }, - { - name: 'url', - label: 'URL', - helpText: - 'Add fieldname variables to your URL to apply values e.g. {{trace.id}}. TODO: Learn more in the docs.', - placeholder: 'e.g. https://www.elastic.co/', - validations: { required: true } - } -]; - -interface ActionSectionProps { - register: FormContextValues['register']; +interface Props { errors: NestDataObject<CustomActionFormData>; + label?: string; + onChangeLabel: (label: string) => void; + url?: string; + onChangeUrl: (url: string) => void; } -export const ActionSection = ({ register, errors }: ActionSectionProps) => { +export const ActionSection = ({ + errors, + label, + onChangeLabel, + url, + onChangeUrl +}: Props) => { + const inputFields: InputField[] = [ + { + name: 'label', + label: 'Label', + helpText: + 'This is the label shown in the actions context menu. Keep it as short as possible.', + placeholder: 'e.g. Support tickets', + value: label, + onChange: onChangeLabel + }, + { + name: 'url', + label: 'URL', + helpText: + 'Add fieldname variables to your URL to apply values e.g. {{trace.id}}. TODO: Learn more in the docs.', + placeholder: 'e.g. https://www.elastic.co/', + value: url, + onChange: onChangeUrl + } + ]; + return ( <> <EuiTitle size="xs"> @@ -66,7 +74,7 @@ export const ActionSection = ({ register, errors }: ActionSectionProps) => { </h3> </EuiTitle> <EuiSpacer size="l" /> - {actionFields.map((field: ActionField) => { + {inputFields.map(field => { return ( <EuiFormRow fullWidth @@ -85,11 +93,12 @@ export const ActionSection = ({ register, errors }: ActionSectionProps) => { } > <EuiFieldText - inputRef={register(field.validations)} placeholder={field.placeholder} name={field.name} fullWidth isInvalid={!isEmpty(errors[field.name])} + value={field.value} + onChange={e => field.onChange(e.target.value)} /> </EuiFormRow> ); diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx index 55f417514c073..a8d53231946d3 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx @@ -36,76 +36,6 @@ const filterOptions = [ { value: 'transaction.name', text: 'transaction.name' } ]; -export const FiltersSection = ({ - filters, - onChange -}: { - filters: FiltersType; - onChange: (filters: FiltersType) => void; -}) => { - const handleAddFilter = () => { - onChange([...filters, ['', '']]); - }; - - return ( - <> - <EuiTitle size="xs"> - <h3> - {i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.flyout.filters.title', - { - defaultMessage: 'Filters' - } - )} - </h3> - </EuiTitle> - <EuiSpacer size="s" /> - <EuiText size="xs"> - {i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.flyout.filters.subtitle', - { - defaultMessage: - 'Add additional values within the same field by comma separating values.' - } - )} - </EuiText> - - <EuiSpacer size="s" /> - - <Filters filters={filters} onChange={onChange} /> - - <EuiSpacer size="xs" /> - - <AddFilterButton - onClick={handleAddFilter} - // Disable button when user has already added all items available - isDisabled={filters.length === filterOptions.length - 1} - /> - </> - ); -}; - -const AddFilterButton = ({ - onClick, - isDisabled -}: { - onClick: () => void; - isDisabled: boolean; -}) => ( - <EuiButtonEmpty - iconType="plusInCircle" - onClick={onClick} - disabled={isDisabled} - > - {i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.flyout.filters.addAnotherFilter', - { - defaultMessage: 'Add another filter' - } - )} - </EuiButtonEmpty> -); - const getSelectOptions = (filters: FiltersType, idx: number) => { return filterOptions.filter(option => { const indexUsedFilter = filters.findIndex( @@ -116,12 +46,12 @@ const getSelectOptions = (filters: FiltersType, idx: number) => { }); }; -const Filters = ({ +export const FiltersSection = ({ filters, - onChange + onChangeFilters }: { filters: FiltersType; - onChange: (filters: FiltersType) => void; + onChangeFilters: (filters: FiltersType) => void; }) => { const filterValueRefs = useRef<HTMLInputElement[]>([]); @@ -131,7 +61,7 @@ const Filters = ({ } const copyOfFilters = [...filters]; copyOfFilters[idx] = filter; - onChange(copyOfFilters); + onChangeFilters(copyOfFilters); }; const onRemoveFilter = (idx: number) => { @@ -143,11 +73,38 @@ const Filters = ({ copyOfFilters.push(['', '']); } - onChange(copyOfFilters); + onChangeFilters(copyOfFilters); + }; + + const handleAddFilter = () => { + onChangeFilters([...filters, ['', '']]); }; return ( <> + <EuiTitle size="xs"> + <h3> + {i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.flyout.filters.title', + { + defaultMessage: 'Filters' + } + )} + </h3> + </EuiTitle> + <EuiSpacer size="s" /> + <EuiText size="xs"> + {i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.flyout.filters.subtitle', + { + defaultMessage: + 'Add additional values within the same field by comma separating values.' + } + )} + </EuiText> + + <EuiSpacer size="s" /> + {filters.map((filter, idx) => { const [key, value] = filter; const filterId = `filter-${idx}`; @@ -202,6 +159,35 @@ const Filters = ({ </EuiFlexGroup> ); })} + + <EuiSpacer size="xs" /> + + <AddFilterButton + onClick={handleAddFilter} + // Disable button when user has already added all items available + isDisabled={filters.length === filterOptions.length - 1} + /> </> ); }; + +const AddFilterButton = ({ + onClick, + isDisabled +}: { + onClick: () => void; + isDisabled: boolean; +}) => ( + <EuiButtonEmpty + iconType="plusInCircle" + onClick={onClick} + disabled={isDisabled} + > + {i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.flyout.filters.addAnotherFilter', + { + defaultMessage: 'Add another filter' + } + )} + </EuiButtonEmpty> +); diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx index 747186b473365..aad173977bce3 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx @@ -44,8 +44,14 @@ const convertFiltersToArray = (filters: CustomAction['filters'] = {}) => { return convertedFilters; }; -const convertFiltersToObject = (filters: CustomActionFormData['filters']) => - Object.fromEntries(filters); +const convertFiltersToObject = (filters: CustomActionFormData['filters']) => { + const convertedFilters = Object.fromEntries( + filters.filter(([key, value]) => !isEmpty(key) && !isEmpty(value)) + ); + if (!isEmpty(convertedFilters)) { + return convertedFilters; + } +}; export const CustomActionsFlyout = ({ onClose, @@ -54,8 +60,15 @@ export const CustomActionsFlyout = ({ onDelete }: Props) => { const callApmApiFromHook = useCallApmApi(); - const [isSaving, setIsSaving] = useState(false); const { toasts } = useApmPluginContext().core.notifications; + const [isSaving, setIsSaving] = useState(false); + + // form fields + const [label, setLabel] = useState(customActionSelected?.label || ''); + const [url, setUrl] = useState(customActionSelected?.url || ''); + const [filters, setFilters] = useState( + convertFiltersToArray(customActionSelected?.filters) + ); const { register, handleSubmit, errors, control, watch } = useForm< CustomActionFormData @@ -66,26 +79,28 @@ export const CustomActionsFlyout = ({ } }); - const onSubmit = async (customAction: CustomActionFormData) => { + const onSubmit = async ( + event: + | React.FormEvent<HTMLFormElement> + | React.MouseEvent<HTMLButtonElement> + ) => { + event.preventDefault(); setIsSaving(true); await saveCustomAction({ + id: customActionSelected?.id, + label, + url, + filters: convertFiltersToObject(filters), callApmApi: callApmApiFromHook, - customAction: { - ...customActionSelected, - ...customAction, - filters: convertFiltersToObject(customAction.filters) - }, toasts }); setIsSaving(false); onSave(); }; - // Watch for any change on filters to render the component - const filters = watch('filters'); return ( <EuiPortal> - <form onSubmit={handleSubmit(onSubmit)}> + <form onSubmit={onSubmit}> <EuiFlyout ownFocus onClose={onClose} size="m"> <EuiFlyoutHeader hasBorder> <EuiTitle size="s"> @@ -114,16 +129,17 @@ export const CustomActionsFlyout = ({ <EuiSpacer size="l" /> - <ActionSection register={register} errors={errors} /> + <ActionSection + errors={errors} + label={label} + onChangeLabel={setLabel} + url={url} + onChangeUrl={setUrl} + /> <EuiSpacer size="l" /> - <Controller - // @ts-ignore: onChange function property will be automatically handled by react-hook-form Controller. - as={<FiltersSection filters={filters} />} - name="filters" - control={control} - /> + <FiltersSection filters={filters} onChangeFilters={setFilters} /> </EuiFlyoutBody> <FlyoutFooter diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/saveCustomAction.ts b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/saveCustomAction.ts index 549ba75e3c6a7..f61ce7dd0fbd2 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/saveCustomAction.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/saveCustomAction.ts @@ -9,21 +9,33 @@ import { NotificationsStart } from 'kibana/public'; import { CustomAction } from '../../../../../../../../../../plugins/apm/server/lib/settings/custom_action/custom_action_types'; import { APMClient } from '../../../../../../services/rest/createCallApmApi'; -export const saveCustomAction = async ({ +export async function saveCustomAction({ + id, + label, + url, + filters, callApmApi, - customAction, toasts }: { + id?: string; + label: string; + url: string; + filters?: CustomAction['filters']; callApmApi: APMClient; - customAction: CustomAction; toasts: NotificationsStart['toasts']; -}) => { - if (customAction?.id) { +}) { + const customAction = { + actionId: 'trace', + label, + url, + filters + }; + if (id) { await callApmApi({ pathname: '/api/apm/settings/custom-actions/{id}', method: 'PUT', params: { - path: { id: customAction.id }, + path: { id }, body: customAction } }); @@ -50,4 +62,4 @@ export const saveCustomAction = async ({ ) }); } -}; +} diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsTable.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsTable.tsx index c0bf855e4e8ba..c56797f252f6e 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsTable.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsTable.tsx @@ -84,7 +84,7 @@ export const CustomActionsTable = ({ columns={columns} initialPageSize={10} initialSortField="@timestamp" - initialSortDirection="asc" + initialSortDirection="desc" /> ); }; diff --git a/x-pack/plugins/apm/server/lib/settings/custom_action/custom_action_types.d.ts b/x-pack/plugins/apm/server/lib/settings/custom_action/custom_action_types.d.ts index c84547edcaa07..2505bc8baa809 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_action/custom_action_types.d.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_action/custom_action_types.d.ts @@ -9,7 +9,7 @@ export interface CustomAction { '@timestamp': number; label: string; url: string; - actionId: 'trace'; + actionId: string; filters?: { [key: string]: string; }; diff --git a/x-pack/plugins/apm/server/routes/settings/custom_actions.ts b/x-pack/plugins/apm/server/routes/settings/custom_actions.ts index eeeb7bd6144cf..dbaaac9093b81 100644 --- a/x-pack/plugins/apm/server/routes/settings/custom_actions.ts +++ b/x-pack/plugins/apm/server/routes/settings/custom_actions.ts @@ -20,6 +20,7 @@ export const listCustomActionsRoute = createRoute(core => ({ const payload = t.intersection([ t.type({ + actionId: t.string, label: t.string, url: t.string }), From 0fd2e22902d0da253af11415217f7d2b672e5cb5 Mon Sep 17 00:00:00 2001 From: cauemarcondes <caue.marcondes@elastic.co> Date: Tue, 25 Feb 2020 13:16:31 +0100 Subject: [PATCH 23/42] removing reach-hook-form --- package.json | 1 - .../CustomActionsFlyout/ActionSection.tsx | 5 ----- .../CustomActionsFlyout/Flyoutfooter.tsx | 11 +++++++++-- .../CustomActionsFlyout/index.tsx | 14 +++----------- yarn.lock | 5 ----- 5 files changed, 12 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index 181e1f961f8d0..2c401724c72cd 100644 --- a/package.json +++ b/package.json @@ -231,7 +231,6 @@ "react-color": "^2.13.8", "react-dom": "^16.12.0", "react-grid-layout": "^0.16.2", - "react-hook-form": "^4.9.6", "react-input-range": "^1.3.0", "react-markdown": "^3.4.1", "react-monaco-editor": "~0.27.0", diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/ActionSection.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/ActionSection.tsx index cdfdcb7b4d720..608e17f9a13e1 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/ActionSection.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/ActionSection.tsx @@ -11,9 +11,7 @@ import { EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { isEmpty } from 'lodash'; import React from 'react'; -import { NestDataObject } from 'react-hook-form'; import { CustomActionFormData } from './'; interface InputField { @@ -26,7 +24,6 @@ interface InputField { } interface Props { - errors: NestDataObject<CustomActionFormData>; label?: string; onChangeLabel: (label: string) => void; url?: string; @@ -34,7 +31,6 @@ interface Props { } export const ActionSection = ({ - errors, label, onChangeLabel, url, @@ -96,7 +92,6 @@ export const ActionSection = ({ placeholder={field.placeholder} name={field.name} fullWidth - isInvalid={!isEmpty(errors[field.name])} value={field.value} onChange={e => field.onChange(e.target.value)} /> diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/Flyoutfooter.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/Flyoutfooter.tsx index e27e08b288cca..3285931ce21f8 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/Flyoutfooter.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/Flyoutfooter.tsx @@ -18,12 +18,14 @@ export const FlyoutFooter = ({ onClose, isSaving, onDelete, - customActionId + customActionId, + isSaveButtonEnabled }: { onClose: () => void; isSaving: boolean; onDelete: () => void; customActionId?: string; + isSaveButtonEnabled: boolean; }) => { return ( <EuiFlyoutFooter> @@ -49,7 +51,12 @@ export const FlyoutFooter = ({ </EuiFlexItem> )} <EuiFlexItem> - <EuiButton fill type="submit" isLoading={isSaving}> + <EuiButton + fill + type="submit" + isLoading={isSaving} + isDisabled={!isSaveButtonEnabled} + > {i18n.translate( 'xpack.apm.settings.customizeUI.customActions.flyout.save', { diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx index aad173977bce3..a992e82d8599d 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx @@ -13,9 +13,8 @@ import { EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React, { useState } from 'react'; -import { Controller, useForm } from 'react-hook-form'; import { isEmpty } from 'lodash'; +import React, { useState } from 'react'; import { CustomAction } from '../../../../../../../../../../plugins/apm/server/lib/settings/custom_action/custom_action_types'; import { useApmPluginContext } from '../../../../../../hooks/useApmPluginContext'; import { useCallApmApi } from '../../../../../../hooks/useCallApmApi'; @@ -70,14 +69,7 @@ export const CustomActionsFlyout = ({ convertFiltersToArray(customActionSelected?.filters) ); - const { register, handleSubmit, errors, control, watch } = useForm< - CustomActionFormData - >({ - defaultValues: { - ...customActionSelected, - filters: convertFiltersToArray(customActionSelected?.filters) - } - }); + const isFormValid = !!label && !!url; const onSubmit = async ( event: @@ -130,7 +122,6 @@ export const CustomActionsFlyout = ({ <EuiSpacer size="l" /> <ActionSection - errors={errors} label={label} onChangeLabel={setLabel} url={url} @@ -143,6 +134,7 @@ export const CustomActionsFlyout = ({ </EuiFlyoutBody> <FlyoutFooter + isSaveButtonEnabled={isFormValid} onClose={onClose} isSaving={isSaving} onDelete={onDelete} diff --git a/yarn.lock b/yarn.lock index 0d62abf64b9fd..dde08490d62f0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -24382,11 +24382,6 @@ react-helmet-async@^1.0.2: react-fast-compare "2.0.4" shallowequal "1.1.0" -react-hook-form@^4.9.6: - version "4.9.6" - resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-4.9.6.tgz#2057b4af7028f09d50c9bd1db46d1ff4e453c046" - integrity sha512-t5EQdfATwOOlj0rsBmHYBgf6s5L/dzakPzhzr2kdqTVhRnxSI73ZthJofZ+dYiXrXa6ds0VRso49GCHwDXL66Q== - react-hotkeys@2.0.0-pre4: version "2.0.0-pre4" resolved "https://registry.yarnpkg.com/react-hotkeys/-/react-hotkeys-2.0.0-pre4.tgz#a1c248a51bdba4282c36bf3204f80d58abc73333" From 59c5c378d73a475cf000524ca8902d057b7d3fab Mon Sep 17 00:00:00 2001 From: cauemarcondes <caue.marcondes@elastic.co> Date: Tue, 25 Feb 2020 17:27:42 +0100 Subject: [PATCH 24/42] adding unit tests --- .../__test__/CustomActions.test.tsx | 95 +++++++++++++++++-- .../plugins/apm/public/utils/testHelpers.tsx | 3 +- .../list_custom_actions.test.ts.snap | 8 ++ .../create_or_update_custom_action.test.ts | 79 +++++++++++++++ .../__test__/list_custom_actions.test.ts | 26 +++++ .../create_or_update_custom_action.ts | 4 +- .../custom_action/list_custom_actions.ts | 2 +- 7 files changed, 204 insertions(+), 13 deletions(-) create mode 100644 x-pack/plugins/apm/server/lib/settings/custom_action/__test__/__snapshots__/list_custom_actions.test.ts.snap create mode 100644 x-pack/plugins/apm/server/lib/settings/custom_action/__test__/create_or_update_custom_action.test.ts create mode 100644 x-pack/plugins/apm/server/lib/settings/custom_action/__test__/list_custom_actions.test.ts diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/__test__/CustomActions.test.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/__test__/CustomActions.test.tsx index 970de66c64a9a..9199ddf0ba47b 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/__test__/CustomActions.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/__test__/CustomActions.test.tsx @@ -5,29 +5,106 @@ */ import React from 'react'; -import { fireEvent, render } from '@testing-library/react'; +import { fireEvent, render, getAllByLabelText } from '@testing-library/react'; import { CustomActionsOverview } from '../'; -import { expectTextsInDocument } from '../../../../../../utils/testHelpers'; +import { + expectTextsInDocument, + MockApmPluginContextWrapper +} from '../../../../../../utils/testHelpers'; import * as hooks from '../../../../../../hooks/useFetcher'; describe('CustomActions', () => { afterEach(() => jest.restoreAllMocks()); - describe('empty prompt', () => { - it('shows when any actions are available', () => { - // TODO: mock return items + describe('empty prompt page', () => { + beforeAll(() => { + spyOn(hooks, 'useFetcher').and.returnValue({ + data: [], + status: 'success' + }); + }); + + afterAll(() => { + jest.clearAllMocks(); + }); + it('shows when no action is available', () => { const component = render(<CustomActionsOverview />); expectTextsInDocument(component, ['No actions found.']); }); it('opens flyout when click to create new action', () => { + const { queryByText, getByText } = render( + <MockApmPluginContextWrapper> + <CustomActionsOverview /> + </MockApmPluginContextWrapper> + ); + expect(queryByText('Create action')).not.toBeInTheDocument(); + fireEvent.click(getByText('Create custom action')); + expect(queryByText('Create action')).toBeInTheDocument(); + }); + }); + + describe('overview page', () => { + beforeAll(() => { spyOn(hooks, 'useFetcher').and.returnValue({ - data: [], + data: [ + { + id: '1', + label: 'label 1', + url: 'url 1', + filters: { + 'service.name': 'opbeans-java' + } + }, + { + id: '2', + label: 'label 2', + url: 'url 2', + filters: { + 'transaction.type': 'request' + } + } + ], status: 'success' }); - const { queryByText, getByText } = render(<CustomActionsOverview />); - expect(queryByText('Service')).not.toBeInTheDocument(); + }); + + it('shows a table with all custom actions', () => { + const component = render( + <MockApmPluginContextWrapper> + <CustomActionsOverview /> + </MockApmPluginContextWrapper> + ); + expectTextsInDocument(component, [ + 'label 1', + 'url 1', + 'label 2', + 'url 2' + ]); + // expect(queryByText('Create action')).not.toBeInTheDocument(); + }); + + it('checks if create custom action button is available and working', () => { + const { queryByText, getByText } = render( + <MockApmPluginContextWrapper> + <CustomActionsOverview /> + </MockApmPluginContextWrapper> + ); + expect(queryByText('Create action')).not.toBeInTheDocument(); fireEvent.click(getByText('Create custom action')); - expect(queryByText('Service')).toBeInTheDocument(); + expect(queryByText('Create action')).toBeInTheDocument(); + }); + + it('opens flyout to edit a custom action', () => { + const container = render( + <MockApmPluginContextWrapper> + <CustomActionsOverview /> + </MockApmPluginContextWrapper> + ); + expect(container.queryByText('Create action')).not.toBeInTheDocument(); + const editButtons = container.getAllByLabelText('Edit'); + expect(editButtons.length).toEqual(2); + fireEvent.click(editButtons[0]); + expect(container.queryByText('Create action')).toBeInTheDocument(); }); }); }); diff --git a/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx b/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx index dec2257746e50..9dc0ec29fc66a 100644 --- a/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx +++ b/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx @@ -162,7 +162,8 @@ export async function inspectSearchParams( 'apm_oss.spanIndices': 'myIndex', 'apm_oss.transactionIndices': 'myIndex', 'apm_oss.metricsIndices': 'myIndex', - apmAgentConfigurationIndex: 'myIndex' + apmAgentConfigurationIndex: 'myIndex', + apmCustomActionIndex: 'myIndex' }, dynamicIndexPattern: null as any }; diff --git a/x-pack/plugins/apm/server/lib/settings/custom_action/__test__/__snapshots__/list_custom_actions.test.ts.snap b/x-pack/plugins/apm/server/lib/settings/custom_action/__test__/__snapshots__/list_custom_actions.test.ts.snap new file mode 100644 index 0000000000000..01f5313fcdacb --- /dev/null +++ b/x-pack/plugins/apm/server/lib/settings/custom_action/__test__/__snapshots__/list_custom_actions.test.ts.snap @@ -0,0 +1,8 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`List Custom Actions fetches custom actions 1`] = ` +Object { + "index": "myIndex", + "size": 500, +} +`; diff --git a/x-pack/plugins/apm/server/lib/settings/custom_action/__test__/create_or_update_custom_action.test.ts b/x-pack/plugins/apm/server/lib/settings/custom_action/__test__/create_or_update_custom_action.test.ts new file mode 100644 index 0000000000000..7c8692328e97b --- /dev/null +++ b/x-pack/plugins/apm/server/lib/settings/custom_action/__test__/create_or_update_custom_action.test.ts @@ -0,0 +1,79 @@ +/* + * 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 { createOrUpdateCustomAction } from '../create_or_update_custom_action'; +import { CustomAction } from '../custom_action_types'; +import { Setup } from '../../../helpers/setup_request'; +import { mockNow } from '../../../../../../../legacy/plugins/apm/public/utils/testHelpers'; + +describe('Create or Update Custom Action', () => { + const internalClientIndexMock = jest.fn(); + const mockedSetup = ({ + internalClient: { + index: internalClientIndexMock + }, + indices: { + apmCustomActionIndex: 'apmCustomActionIndex' + } + } as unknown) as Setup; + + const customAction = ({ + label: 'foo', + url: 'http://elastic.com/{{trace.id}}', + filters: { + 'service.name': 'opbeans-java', + 'transaction.type': 'Request' + }, + actionId: 'trace' + } as unknown) as CustomAction; + afterEach(() => { + internalClientIndexMock.mockClear(); + }); + + beforeAll(() => { + mockNow(1570737000000); + }); + + it('creates a new custom action', () => { + createOrUpdateCustomAction({ customAction, setup: mockedSetup }); + expect(internalClientIndexMock).toHaveBeenCalledWith({ + refresh: true, + index: 'apmCustomActionIndex', + body: { + '@timestamp': 1570737000000, + label: 'foo', + url: 'http://elastic.com/{{trace.id}}', + filters: { + 'service.name': 'opbeans-java', + 'transaction.type': 'Request' + }, + actionId: 'trace' + } + }); + }); + it('update a new custom action', () => { + createOrUpdateCustomAction({ + customActionId: 'bar', + customAction, + setup: mockedSetup + }); + expect(internalClientIndexMock).toHaveBeenCalledWith({ + refresh: true, + index: 'apmCustomActionIndex', + id: 'bar', + body: { + '@timestamp': 1570737000000, + label: 'foo', + url: 'http://elastic.com/{{trace.id}}', + filters: { + 'service.name': 'opbeans-java', + 'transaction.type': 'Request' + }, + actionId: 'trace' + } + }); + }); +}); diff --git a/x-pack/plugins/apm/server/lib/settings/custom_action/__test__/list_custom_actions.test.ts b/x-pack/plugins/apm/server/lib/settings/custom_action/__test__/list_custom_actions.test.ts new file mode 100644 index 0000000000000..59c3eb6e0a9af --- /dev/null +++ b/x-pack/plugins/apm/server/lib/settings/custom_action/__test__/list_custom_actions.test.ts @@ -0,0 +1,26 @@ +/* + * 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 { listCustomActions } from '../list_custom_actions'; +import { + inspectSearchParams, + SearchParamsMock +} from '../../../../../../../legacy/plugins/apm/public/utils/testHelpers'; +import { Setup } from '../../../helpers/setup_request'; + +describe('List Custom Actions', () => { + let mock: SearchParamsMock; + + it('fetches custom actions', async () => { + mock = await inspectSearchParams(setup => + listCustomActions({ + setup: (setup as unknown) as Setup + }) + ); + + expect(mock.params).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/apm/server/lib/settings/custom_action/create_or_update_custom_action.ts b/x-pack/plugins/apm/server/lib/settings/custom_action/create_or_update_custom_action.ts index 124a0800fa976..ecf055c2ac542 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_action/create_or_update_custom_action.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_action/create_or_update_custom_action.ts @@ -14,7 +14,7 @@ export async function createOrUpdateCustomAction({ setup }: { customActionId?: string; - customAction: Omit<CustomAction, '@timestamp' | 'actionId'>; + customAction: Omit<CustomAction, '@timestamp'>; setup: Setup; }) { const { internalClient, indices } = setup; @@ -27,7 +27,7 @@ export async function createOrUpdateCustomAction({ label: customAction.label, url: customAction.url, filters: customAction.filters, - actionId: 'trace' + actionId: customAction.actionId } }; diff --git a/x-pack/plugins/apm/server/lib/settings/custom_action/list_custom_actions.ts b/x-pack/plugins/apm/server/lib/settings/custom_action/list_custom_actions.ts index a283d5e84fd00..acf9a3bb32cb4 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_action/list_custom_actions.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_action/list_custom_actions.ts @@ -11,7 +11,7 @@ export async function listCustomActions({ setup }: { setup: Setup }) { const { internalClient, indices } = setup; const params = { index: indices.apmCustomActionIndex, - size: 200 + size: 500 }; const resp = await internalClient.search<CustomAction>(params); return resp.hits.hits.map(item => ({ From cf9edd5ebd98fe4a86f9314d261bf3fd0af09156 Mon Sep 17 00:00:00 2001 From: cauemarcondes <caue.marcondes@elastic.co> Date: Fri, 28 Feb 2020 10:30:36 +0100 Subject: [PATCH 25/42] adding unit tests --- .../__test__/CustomActions.test.tsx | 69 +++++++++++++++---- 1 file changed, 57 insertions(+), 12 deletions(-) diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/__test__/CustomActions.test.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/__test__/CustomActions.test.tsx index 9199ddf0ba47b..32428314cfb7f 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/__test__/CustomActions.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/__test__/CustomActions.test.tsx @@ -4,19 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ +import { fireEvent, render } from '@testing-library/react'; import React from 'react'; -import { fireEvent, render, getAllByLabelText } from '@testing-library/react'; +import { act } from 'react-dom/test-utils'; import { CustomActionsOverview } from '../'; +import * as hooks from '../../../../../../hooks/useFetcher'; import { expectTextsInDocument, MockApmPluginContextWrapper } from '../../../../../../utils/testHelpers'; -import * as hooks from '../../../../../../hooks/useFetcher'; describe('CustomActions', () => { afterEach(() => jest.restoreAllMocks()); - describe('empty prompt page', () => { + describe('empty prompt', () => { beforeAll(() => { spyOn(hooks, 'useFetcher').and.returnValue({ data: [], @@ -38,12 +39,14 @@ describe('CustomActions', () => { </MockApmPluginContextWrapper> ); expect(queryByText('Create action')).not.toBeInTheDocument(); - fireEvent.click(getByText('Create custom action')); + act(() => { + fireEvent.click(getByText('Create custom action')); + }); expect(queryByText('Create action')).toBeInTheDocument(); }); }); - describe('overview page', () => { + describe('overview', () => { beforeAll(() => { spyOn(hooks, 'useFetcher').and.returnValue({ data: [ @@ -80,7 +83,6 @@ describe('CustomActions', () => { 'label 2', 'url 2' ]); - // expect(queryByText('Create action')).not.toBeInTheDocument(); }); it('checks if create custom action button is available and working', () => { @@ -90,21 +92,64 @@ describe('CustomActions', () => { </MockApmPluginContextWrapper> ); expect(queryByText('Create action')).not.toBeInTheDocument(); - fireEvent.click(getByText('Create custom action')); + act(() => { + fireEvent.click(getByText('Create custom action')); + }); expect(queryByText('Create action')).toBeInTheDocument(); }); it('opens flyout to edit a custom action', () => { - const container = render( + const component = render( <MockApmPluginContextWrapper> <CustomActionsOverview /> </MockApmPluginContextWrapper> ); - expect(container.queryByText('Create action')).not.toBeInTheDocument(); - const editButtons = container.getAllByLabelText('Edit'); + expect(component.queryByText('Create action')).not.toBeInTheDocument(); + const editButtons = component.getAllByLabelText('Edit'); expect(editButtons.length).toEqual(2); - fireEvent.click(editButtons[0]); - expect(container.queryByText('Create action')).toBeInTheDocument(); + act(() => { + fireEvent.click(editButtons[0]); + }); + expect(component.queryByText('Create action')).toBeInTheDocument(); + }); + }); + + describe('Flyout', () => { + const openFlyout = () => { + const component = render( + <MockApmPluginContextWrapper> + <CustomActionsOverview /> + </MockApmPluginContextWrapper> + ); + expect(component.queryByText('Create action')).not.toBeInTheDocument(); + act(() => { + fireEvent.click(component.getByText('Create custom action')); + }); + expect(component.queryByText('Create action')).toBeInTheDocument(); + return component; + }; + + describe('Filters', () => { + const addFilterField = ( + component: ReturnType<typeof openFlyout>, + amount: number + ) => { + for (let i = 1; i <= amount; i++) { + fireEvent.click(component.getByText('Add another filter')); + } + }; + it('checks if add filter button is disabled after all elements have been added', () => { + const component = openFlyout(); + expect(component.getAllByText('service.name').length).toEqual(1); + addFilterField(component, 1); + expect(component.getAllByText('service.name').length).toEqual(2); + addFilterField(component, 2); + expect(component.getAllByText('service.name').length).toEqual(4); + // After 4 items, the button is disabled + // Even adding a new filter, it still has only 4 items. + addFilterField(component, 2); + expect(component.getAllByText('service.name').length).toEqual(4); + }); }); }); }); From 0752379d988ad413e1e7de7130b0f62c38d5cf99 Mon Sep 17 00:00:00 2001 From: cauemarcondes <caue.marcondes@elastic.co> Date: Fri, 28 Feb 2020 10:37:45 +0100 Subject: [PATCH 26/42] create custom action index --- x-pack/plugins/apm/server/plugin.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts index 23d9e40e0c849..272012743e194 100644 --- a/x-pack/plugins/apm/server/plugin.ts +++ b/x-pack/plugins/apm/server/plugin.ts @@ -67,6 +67,11 @@ export class APMPlugin implements Plugin<APMPluginContract> { config: currentConfig, logger }); + // create custom action index without blocking setup lifecycle + createApmCustomActionIndex({ + esClient: core.elasticsearch.dataClient, + config: currentConfig + }); plugins.home.tutorials.registerTutorial( tutorialProvider({ From 5ed863d159849d0456dec018b7e3d6c04c135c0e Mon Sep 17 00:00:00 2001 From: cauemarcondes <caue.marcondes@elastic.co> Date: Fri, 28 Feb 2020 12:21:05 +0100 Subject: [PATCH 27/42] adding filter option --- .../CustomActionsTable.tsx | 68 ++++++++++++++++--- 1 file changed, 59 insertions(+), 9 deletions(-) diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsTable.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsTable.tsx index c56797f252f6e..a877494bb2650 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsTable.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsTable.tsx @@ -3,8 +3,16 @@ * 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 React, { useState } from 'react'; import { i18n } from '@kbn/i18n'; +import { + EuiFieldSearch, + EuiFlexGroup, + EuiFlexItem, + EuiText, + EuiSpacer +} from '@elastic/eui'; +import { isEmpty } from 'lodash'; import { units, px } from '../../../../../style/variables'; import { CustomAction } from '../../../../../../../../../plugins/apm/server/lib/settings/custom_action/custom_action_types'; import { ManagedTable } from '../../../../shared/ManagedTable'; @@ -20,6 +28,8 @@ export const CustomActionsTable = ({ items = [], onCustomActionSelected }: Props) => { + const [searchTerm, setSearchTerm] = useState(''); + const columns = [ { field: 'label', @@ -77,14 +87,54 @@ export const CustomActionsTable = ({ } ]; + const filteredItems = items.filter(({ label, url }) => { + return ( + label.toLowerCase().includes(searchTerm) || + url.toLowerCase().includes(searchTerm) + ); + }); + return ( - <ManagedTable - noItemsMessage={<LoadingStatePrompt />} - items={items} - columns={columns} - initialPageSize={10} - initialSortField="@timestamp" - initialSortDirection="desc" - /> + <> + <EuiSpacer size="m" /> + <EuiFieldSearch + fullWidth + onChange={e => setSearchTerm(e.target.value)} + placeholder={i18n.translate('xpack.apm.searchInput.filter', { + defaultMessage: 'Filter actions...' + })} + /> + <EuiSpacer size="s" /> + <ManagedTable + noItemsMessage={ + isEmpty(items) ? ( + <LoadingStatePrompt /> + ) : ( + <NoResultFound value={searchTerm} /> + ) + } + items={filteredItems} + columns={columns} + initialPageSize={10} + initialSortField="@timestamp" + initialSortDirection="desc" + /> + </> ); }; + +const NoResultFound = ({ value }: { value: string }) => ( + <EuiFlexGroup justifyContent="spaceAround"> + <EuiFlexItem grow={false}> + <EuiText size="s"> + {i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.table.noResultFound', + { + defaultMessage: `No results for "{value}".`, + values: { value } + } + )} + </EuiText> + </EuiFlexItem> + </EuiFlexGroup> +); From 6fb638fbe969e665c196c2a7f4f370920f15cb4d Mon Sep 17 00:00:00 2001 From: cauemarcondes <caue.marcondes@elastic.co> Date: Fri, 28 Feb 2020 16:01:22 +0100 Subject: [PATCH 28/42] refactoring create index, creating filter links --- .../CustomActionsFlyout/saveCustomAction.ts | 70 ++++++---- .../lib/helpers/create_or_update_index.ts | 91 +++++++++++++ .../create_agent_config_index.ts | 126 +++++++----------- .../create_custom_action_index.ts | 105 ++++----------- .../custom_action/list_custom_actions.ts | 33 ++++- x-pack/plugins/apm/server/plugin.ts | 3 +- 6 files changed, 241 insertions(+), 187 deletions(-) create mode 100644 x-pack/plugins/apm/server/lib/helpers/create_or_update_index.ts diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/saveCustomAction.ts b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/saveCustomAction.ts index f61ce7dd0fbd2..549985159ed14 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/saveCustomAction.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/saveCustomAction.ts @@ -24,41 +24,53 @@ export async function saveCustomAction({ callApmApi: APMClient; toasts: NotificationsStart['toasts']; }) { - const customAction = { - actionId: 'trace', - label, - url, - filters - }; - if (id) { - await callApmApi({ - pathname: '/api/apm/settings/custom-actions/{id}', - method: 'PUT', - params: { - path: { id }, - body: customAction - } - }); + try { + const customAction = { + actionId: 'trace', + label, + url, + filters + }; + if (id) { + await callApmApi({ + pathname: '/api/apm/settings/custom-actions/{id}', + method: 'PUT', + params: { + path: { id }, + body: customAction + } + }); + } else { + await callApmApi({ + pathname: '/api/apm/settings/custom-actions', + method: 'POST', + params: { + body: customAction + } + }); + } toasts.addSuccess({ iconType: 'check', title: i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.update.successed', - { defaultMessage: 'Changes saved!' } + 'xpack.apm.settings.customizeUI.customActions.create.successed', + { defaultMessage: 'Link saved!' } ) }); - } else { - await callApmApi({ - pathname: '/api/apm/settings/custom-actions', - method: 'POST', - params: { - body: customAction - } - }); - toasts.addSuccess({ - iconType: 'check', + } catch (error) { + toasts.addDanger({ title: i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.create.successed', - { defaultMessage: 'Created a new custom action!' } + 'xpack.apm.settings.customizeUI.customActions.create.failed', + { defaultMessage: 'Link could not be saved!' } + ), + text: i18n.translate( + 'xpack.apm.settings.customizeUI.customActions.create.failed', + { + defaultMessage: + 'Something went wrong when saving the link. Error: "{errorMessage}"', + values: { + errorMessage: error.message + } + } ) }); } diff --git a/x-pack/plugins/apm/server/lib/helpers/create_or_update_index.ts b/x-pack/plugins/apm/server/lib/helpers/create_or_update_index.ts new file mode 100644 index 0000000000000..0a0da332e73ae --- /dev/null +++ b/x-pack/plugins/apm/server/lib/helpers/create_or_update_index.ts @@ -0,0 +1,91 @@ +/* + * 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 { IClusterClient, Logger } from 'src/core/server'; +import { CallCluster } from 'src/legacy/core_plugins/elasticsearch'; + +export type Mappings = + | { + dynamic?: boolean; + properties: Record<string, Mappings>; + } + | { + type: string; + ignore_above?: number; + scaling_factor?: number; + ignore_malformed?: boolean; + coerce?: boolean; + }; + +export async function createOrUpdateIndex({ + index, + mappings, + esClient, + logger +}: { + index: string; + mappings: Mappings; + esClient: IClusterClient; + logger: Logger; +}) { + try { + const { callAsInternalUser } = esClient; + const indexExists = await callAsInternalUser('indices.exists', { index }); + const result = indexExists + ? await updateExistingIndex({ + index, + callAsInternalUser, + mappings + }) + : await createNewIndex({ + index, + callAsInternalUser, + mappings + }); + + if (!result.acknowledged) { + const resultError = + result && result.error && JSON.stringify(result.error); + throw new Error(resultError); + } + } catch (e) { + logger.error(`Could not create APM index: '${index}'. Error: ${e.message}`); + } +} + +function createNewIndex({ + index, + callAsInternalUser, + mappings +}: { + index: string; + callAsInternalUser: CallCluster; + mappings: Mappings; +}) { + return callAsInternalUser('indices.create', { + index, + body: { + // auto_expand_replicas: Allows cluster to not have replicas for this index + settings: { 'index.auto_expand_replicas': '0-1' }, + mappings + } + }); +} + +function updateExistingIndex({ + index, + callAsInternalUser, + mappings +}: { + index: string; + callAsInternalUser: CallCluster; + mappings: Mappings; +}) { + return callAsInternalUser('indices.putMapping', { + index, + body: mappings + }); +} diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts b/x-pack/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts index 8cfb7e7edb4c6..bc03138e0c247 100644 --- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts +++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts @@ -5,8 +5,11 @@ */ import { IClusterClient, Logger } from 'src/core/server'; -import { CallCluster } from 'src/legacy/core_plugins/elasticsearch'; import { APMConfig } from '../../..'; +import { + createOrUpdateIndex, + Mappings +} from '../../helpers/create_or_update_index'; import { getApmIndicesConfig } from '../apm_indices/get_apm_indices'; export async function createApmAgentConfigurationIndex({ @@ -18,87 +21,54 @@ export async function createApmAgentConfigurationIndex({ config: APMConfig; logger: Logger; }) { - try { - const index = getApmIndicesConfig(config).apmAgentConfigurationIndex; - const { callAsInternalUser } = esClient; - const indexExists = await callAsInternalUser('indices.exists', { index }); - const result = indexExists - ? await updateExistingIndex(index, callAsInternalUser) - : await createNewIndex(index, callAsInternalUser); - - if (!result.acknowledged) { - const resultError = - result && result.error && JSON.stringify(result.error); - throw new Error( - `Unable to create APM Agent Configuration index '${index}': ${resultError}` - ); - } - } catch (e) { - logger.error(`Could not create APM Agent configuration: ${e.message}`); - } + const index = getApmIndicesConfig(config).apmAgentConfigurationIndex; + return createOrUpdateIndex({ index, esClient, logger, mappings }); } -function createNewIndex(index: string, callWithInternalUser: CallCluster) { - return callWithInternalUser('indices.create', { - index, - body: { - settings: { 'index.auto_expand_replicas': '0-1' }, - mappings: { properties: mappingProperties } - } - }); -} - -// Necessary for migration reasons -// Added in 7.5: `capture_body`, `transaction_max_spans`, `applied_by_agent`, `agent_name` and `etag` -function updateExistingIndex(index: string, callWithInternalUser: CallCluster) { - return callWithInternalUser('indices.putMapping', { - index, - body: { properties: mappingProperties } - }); -} - -const mappingProperties = { - '@timestamp': { - type: 'date' - }, - service: { - properties: { - name: { - type: 'keyword', - ignore_above: 1024 - }, - environment: { - type: 'keyword', - ignore_above: 1024 +const mappings: Mappings = { + properties: { + '@timestamp': { + type: 'date' + }, + service: { + properties: { + name: { + type: 'keyword', + ignore_above: 1024 + }, + environment: { + type: 'keyword', + ignore_above: 1024 + } } - } - }, - settings: { - properties: { - transaction_sample_rate: { - type: 'scaled_float', - scaling_factor: 1000, - ignore_malformed: true, - coerce: false - }, - capture_body: { - type: 'keyword', - ignore_above: 1024 - }, - transaction_max_spans: { - type: 'short' + }, + settings: { + properties: { + transaction_sample_rate: { + type: 'scaled_float', + scaling_factor: 1000, + ignore_malformed: true, + coerce: false + }, + capture_body: { + type: 'keyword', + ignore_above: 1024 + }, + transaction_max_spans: { + type: 'short' + } } + }, + applied_by_agent: { + type: 'boolean' + }, + agent_name: { + type: 'keyword', + ignore_above: 1024 + }, + etag: { + type: 'keyword', + ignore_above: 1024 } - }, - applied_by_agent: { - type: 'boolean' - }, - agent_name: { - type: 'keyword', - ignore_above: 1024 - }, - etag: { - type: 'keyword', - ignore_above: 1024 } }; diff --git a/x-pack/plugins/apm/server/lib/settings/custom_action/create_custom_action_index.ts b/x-pack/plugins/apm/server/lib/settings/custom_action/create_custom_action_index.ts index efbedab2ad34a..89fa740fd0a9b 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_action/create_custom_action_index.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_action/create_custom_action_index.ts @@ -4,92 +4,45 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IClusterClient } from 'kibana/server'; -import { CallCluster } from 'src/legacy/core_plugins/elasticsearch'; -import { getApmIndicesConfig } from '../apm_indices/get_apm_indices'; +import { IClusterClient, Logger } from 'src/core/server'; import { APMConfig } from '../../..'; +import { + createOrUpdateIndex, + Mappings +} from '../../helpers/create_or_update_index'; +import { getApmIndicesConfig } from '../apm_indices/get_apm_indices'; export const createApmCustomActionIndex = async ({ esClient, - config + config, + logger }: { esClient: IClusterClient; config: APMConfig; + logger: Logger; }) => { - try { - const index = getApmIndicesConfig(config).apmCustomActionIndex; - const { callAsInternalUser } = esClient; - const indexExists = await callAsInternalUser('indices.exists', { index }); - const result = indexExists - ? await updateExistingIndex(index, callAsInternalUser) - : await createNewIndex(index, callAsInternalUser); - - if (!result.acknowledged) { - const resultError = - result && result.error && JSON.stringify(result.error); - throw new Error( - `Unable to create APM Custom Actions index '${index}': ${resultError}` - ); - } - } catch (e) { - // eslint-disable-next-line no-console - console.error('Could not create APM Custom Actions index:', e.message); - } + const index = getApmIndicesConfig(config).apmCustomActionIndex; + return createOrUpdateIndex({ index, esClient, logger, mappings }); }; -const createNewIndex = (index: string, callWithInternalUser: CallCluster) => - callWithInternalUser('indices.create', { - index, - body: { - settings: { 'index.auto_expand_replicas': '0-1' }, - mappings: { properties: mappingProperties } - } - }); - -const updateExistingIndex = ( - index: string, - callWithInternalUser: CallCluster -) => - callWithInternalUser('indices.putMapping', { - index, - body: { properties: mappingProperties } - }); - -const mappingProperties = { - '@timestamp': { - type: 'date' - }, - label: { - type: 'text' - }, - url: { - type: 'keyword' - }, - actionId: { - type: 'keyword' - }, - filters: { - properties: { - service: { - properties: { - environment: { - type: 'keyword' - }, - name: { - type: 'keyword' - } - } - }, - transaction: { - properties: { - name: { - type: 'keyword' - }, - type: { - type: 'keyword' - } - } - } +const mappings: Mappings = { + dynamic: false, + properties: { + '@timestamp': { + type: 'date' + }, + label: { + type: 'text' + }, + url: { + type: 'keyword' + }, + actionId: { + type: 'keyword' + }, + filters: { + dynamic: true, + properties: {} } } }; diff --git a/x-pack/plugins/apm/server/lib/settings/custom_action/list_custom_actions.ts b/x-pack/plugins/apm/server/lib/settings/custom_action/list_custom_actions.ts index acf9a3bb32cb4..170291ac33725 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_action/list_custom_actions.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_action/list_custom_actions.ts @@ -4,14 +4,41 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CustomAction } from './custom_action_types'; import { Setup } from '../../helpers/setup_request'; +import { CustomAction } from './custom_action_types'; -export async function listCustomActions({ setup }: { setup: Setup }) { +export async function listCustomActions({ + setup, + filters = {} +}: { + setup: Setup; + filters: Record<string, string>; +}) { const { internalClient, indices } = setup; + + const esFilters = Object.entries(filters).map(([key, value]) => { + const field = `filters.${key}`; + return { + bool: { + minimum_should_match: 1, + should: [ + { term: { [field]: value } }, + { bool: { must_not: [{ exists: { field } }] } } + ] + } + }; + }); + const params = { index: indices.apmCustomActionIndex, - size: 500 + size: 500, + body: { + query: { + bool: { + filter: esFilters + } + } + } }; const resp = await internalClient.search<CustomAction>(params); return resp.hits.hits.map(item => ({ diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts index 272012743e194..4947838d94ab5 100644 --- a/x-pack/plugins/apm/server/plugin.ts +++ b/x-pack/plugins/apm/server/plugin.ts @@ -70,7 +70,8 @@ export class APMPlugin implements Plugin<APMPluginContract> { // create custom action index without blocking setup lifecycle createApmCustomActionIndex({ esClient: core.elasticsearch.dataClient, - config: currentConfig + config: currentConfig, + logger }); plugins.home.tutorials.registerTutorial( From 5f65c1e4aa05b09e5bedad3bc2d0b33f70b28921 Mon Sep 17 00:00:00 2001 From: cauemarcondes <caue.marcondes@elastic.co> Date: Mon, 2 Mar 2020 11:13:23 +0100 Subject: [PATCH 29/42] creating list api --- .../CustomActionsFlyout/FiltersSection.tsx | 25 ++++++++++++++----- .../CustomActionsFlyout/saveCustomAction.ts | 1 - .../create_custom_action_index.ts | 3 --- .../create_or_update_custom_action.ts | 3 +-- .../custom_action/custom_action_types.d.ts | 1 - .../custom_action/list_custom_actions.ts | 19 +++++++++++++- .../server/routes/settings/custom_actions.ts | 12 ++++++--- 7 files changed, 47 insertions(+), 17 deletions(-) diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx index a8d53231946d3..44421785c2bdc 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx @@ -16,11 +16,24 @@ import { import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; import React, { useRef } from 'react'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { FilterOptionsType } from '../../../../../../../../../../plugins/apm/server/lib/settings/custom_action/list_custom_actions'; +import { + SERVICE_NAME, + SERVICE_ENVIRONMENT, + TRANSACTION_NAME, + TRANSACTION_TYPE +} from '../../../../../../../../../../plugins/apm/common/elasticsearch_fieldnames'; import { CustomActionFormData } from '.'; type FiltersType = CustomActionFormData['filters']; -const DEFAULT_OPTION = { +interface FilterOption { + value: 'DEFAULT' | keyof FilterOptionsType; + text: string; +} + +const DEFAULT_OPTION: FilterOption = { value: 'DEFAULT', text: i18n.translate( 'xpack.apm.settings.customizeUI.customActions.flyOut.filters.defaultOption', @@ -28,12 +41,12 @@ const DEFAULT_OPTION = { ) }; -const filterOptions = [ +const filterOptions: FilterOption[] = [ DEFAULT_OPTION, - { value: 'service.name', text: 'service.name' }, - { value: 'service.environment', text: 'service.environment' }, - { value: 'transaction.type', text: 'transaction.type' }, - { value: 'transaction.name', text: 'transaction.name' } + { value: SERVICE_NAME, text: SERVICE_NAME }, + { value: SERVICE_ENVIRONMENT, text: SERVICE_ENVIRONMENT }, + { value: TRANSACTION_TYPE, text: TRANSACTION_TYPE }, + { value: TRANSACTION_NAME, text: TRANSACTION_NAME } ]; const getSelectOptions = (filters: FiltersType, idx: number) => { diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/saveCustomAction.ts b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/saveCustomAction.ts index 549985159ed14..ac4467d070f10 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/saveCustomAction.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/saveCustomAction.ts @@ -26,7 +26,6 @@ export async function saveCustomAction({ }) { try { const customAction = { - actionId: 'trace', label, url, filters diff --git a/x-pack/plugins/apm/server/lib/settings/custom_action/create_custom_action_index.ts b/x-pack/plugins/apm/server/lib/settings/custom_action/create_custom_action_index.ts index 89fa740fd0a9b..d24a4b84b65dc 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_action/create_custom_action_index.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_action/create_custom_action_index.ts @@ -37,9 +37,6 @@ const mappings: Mappings = { url: { type: 'keyword' }, - actionId: { - type: 'keyword' - }, filters: { dynamic: true, properties: {} diff --git a/x-pack/plugins/apm/server/lib/settings/custom_action/create_or_update_custom_action.ts b/x-pack/plugins/apm/server/lib/settings/custom_action/create_or_update_custom_action.ts index ecf055c2ac542..001daab2cbc51 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_action/create_or_update_custom_action.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_action/create_or_update_custom_action.ts @@ -26,8 +26,7 @@ export async function createOrUpdateCustomAction({ '@timestamp': Date.now(), label: customAction.label, url: customAction.url, - filters: customAction.filters, - actionId: customAction.actionId + filters: customAction.filters } }; diff --git a/x-pack/plugins/apm/server/lib/settings/custom_action/custom_action_types.d.ts b/x-pack/plugins/apm/server/lib/settings/custom_action/custom_action_types.d.ts index 2505bc8baa809..67d0f65442db3 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_action/custom_action_types.d.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_action/custom_action_types.d.ts @@ -9,7 +9,6 @@ export interface CustomAction { '@timestamp': number; label: string; url: string; - actionId: string; filters?: { [key: string]: string; }; diff --git a/x-pack/plugins/apm/server/lib/settings/custom_action/list_custom_actions.ts b/x-pack/plugins/apm/server/lib/settings/custom_action/list_custom_actions.ts index 170291ac33725..8e1d4edd0e842 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_action/list_custom_actions.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_action/list_custom_actions.ts @@ -4,15 +4,32 @@ * you may not use this file except in compliance with the Elastic License. */ +import * as t from 'io-ts'; + +import { + SERVICE_NAME, + TRANSACTION_NAME, + TRANSACTION_TYPE, + SERVICE_ENVIRONMENT +} from '../../../../common/elasticsearch_fieldnames'; import { Setup } from '../../helpers/setup_request'; import { CustomAction } from './custom_action_types'; +export const FilterOptions = t.partial({ + [SERVICE_NAME]: t.string, + [SERVICE_ENVIRONMENT]: t.string, + [TRANSACTION_NAME]: t.string, + [TRANSACTION_TYPE]: t.string +}); + +export type FilterOptionsType = t.TypeOf<typeof FilterOptions>; + export async function listCustomActions({ setup, filters = {} }: { setup: Setup; - filters: Record<string, string>; + filters?: FilterOptionsType; }) { const { internalClient, indices } = setup; diff --git a/x-pack/plugins/apm/server/routes/settings/custom_actions.ts b/x-pack/plugins/apm/server/routes/settings/custom_actions.ts index dbaaac9093b81..713e1f48d1797 100644 --- a/x-pack/plugins/apm/server/routes/settings/custom_actions.ts +++ b/x-pack/plugins/apm/server/routes/settings/custom_actions.ts @@ -8,19 +8,25 @@ import { createRoute } from '../create_route'; import { setupRequest } from '../../lib/helpers/setup_request'; import { createOrUpdateCustomAction } from '../../lib/settings/custom_action/create_or_update_custom_action'; import { deleteCustomAction } from '../../lib/settings/custom_action/delete_custom_action'; -import { listCustomActions } from '../../lib/settings/custom_action/list_custom_actions'; +import { + listCustomActions, + FilterOptions +} from '../../lib/settings/custom_action/list_custom_actions'; export const listCustomActionsRoute = createRoute(core => ({ path: '/api/apm/settings/custom-actions', + params: { + query: FilterOptions + }, handler: async ({ context, request }) => { const setup = await setupRequest(context, request); - return await listCustomActions({ setup }); + const { params } = context; + return await listCustomActions({ setup, filters: params.query }); } })); const payload = t.intersection([ t.type({ - actionId: t.string, label: t.string, url: t.string }), From 2d07b7d5405d8ad9bec03a0708acabf272ae564b Mon Sep 17 00:00:00 2001 From: cauemarcondes <caue.marcondes@elastic.co> Date: Mon, 2 Mar 2020 13:40:30 +0100 Subject: [PATCH 30/42] rename custom action to custom link --- .../CreateCustomLinkButton.tsx} | 6 +- .../CustomLinkFlyout}/DeleteButton.tsx | 22 +++---- .../CustomLinkFlyout}/FiltersSection.tsx | 18 +++--- .../CustomLinkFlyout}/Flyoutfooter.tsx | 15 ++--- .../CustomLinkFlyout/LinkSection.tsx} | 59 ++++++++++++++----- .../CustomLinkFlyout}/index.tsx | 40 ++++++------- .../CustomLinkFlyout/saveCustomLink.ts} | 22 +++---- .../CustomLinkTable.tsx} | 38 ++++++------ .../EmptyPrompt.tsx | 16 ++--- .../Title.tsx | 8 +-- .../__test__/CustomLink.test.tsx} | 50 ++++++++-------- .../index.tsx | 44 +++++++------- .../app/Settings/CustomizeUI/index.tsx | 4 +- .../plugins/apm/public/utils/testHelpers.tsx | 2 +- .../settings/apm_indices/get_apm_indices.ts | 4 +- .../list_custom_links.test.ts.snap} | 2 +- .../create_or_update_custom_link.test.ts} | 20 +++---- .../__test__/list_custom_links.test.ts} | 8 +-- .../create_custom_link_index.ts} | 4 +- .../create_or_update_custom_link.ts} | 26 ++++---- .../custom_link_types.d.ts} | 2 +- .../delete_custom_link.ts} | 10 ++-- .../list_custom_links.ts} | 6 +- x-pack/plugins/apm/server/plugin.ts | 4 +- .../apm/server/routes/create_apm_api.ts | 20 +++---- .../{custom_actions.ts => custom_link.ts} | 42 ++++++------- 26 files changed, 259 insertions(+), 233 deletions(-) rename x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/{CustomActionsOverview/CreateCustomActionButton.tsx => CustomLink/CreateCustomLinkButton.tsx} (74%) rename x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/{CustomActionsOverview/CustomActionsFlyout => CustomLink/CustomLinkFlyout}/DeleteButton.tsx (71%) rename x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/{CustomActionsOverview/CustomActionsFlyout => CustomLink/CustomLinkFlyout}/FiltersSection.tsx (88%) rename x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/{CustomActionsOverview/CustomActionsFlyout => CustomLink/CustomLinkFlyout}/Flyoutfooter.tsx (81%) rename x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/{CustomActionsOverview/CustomActionsFlyout/ActionSection.tsx => CustomLink/CustomLinkFlyout/LinkSection.tsx} (53%) rename x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/{CustomActionsOverview/CustomActionsFlyout => CustomLink/CustomLinkFlyout}/index.tsx (67%) rename x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/{CustomActionsOverview/CustomActionsFlyout/saveCustomAction.ts => CustomLink/CustomLinkFlyout/saveCustomLink.ts} (69%) rename x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/{CustomActionsOverview/CustomActionsTable.tsx => CustomLink/CustomLinkTable.tsx} (71%) rename x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/{CustomActionsOverview => CustomLink}/EmptyPrompt.tsx (54%) rename x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/{CustomActionsOverview => CustomLink}/Title.tsx (81%) rename x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/{CustomActionsOverview/__test__/CustomActions.test.tsx => CustomLink/__test__/CustomLink.test.tsx} (70%) rename x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/{CustomActionsOverview => CustomLink}/index.tsx (60%) rename x-pack/plugins/apm/server/lib/settings/{custom_action/__test__/__snapshots__/list_custom_actions.test.ts.snap => custom_link/__test__/__snapshots__/list_custom_links.test.ts.snap} (61%) rename x-pack/plugins/apm/server/lib/settings/{custom_action/__test__/create_or_update_custom_action.test.ts => custom_link/__test__/create_or_update_custom_link.test.ts} (77%) rename x-pack/plugins/apm/server/lib/settings/{custom_action/__test__/list_custom_actions.test.ts => custom_link/__test__/list_custom_links.test.ts} (78%) rename x-pack/plugins/apm/server/lib/settings/{custom_action/create_custom_action_index.ts => custom_link/create_custom_link_index.ts} (88%) rename x-pack/plugins/apm/server/lib/settings/{custom_action/create_or_update_custom_action.ts => custom_link/create_or_update_custom_link.ts} (58%) rename x-pack/plugins/apm/server/lib/settings/{custom_action/custom_action_types.d.ts => custom_link/custom_link_types.d.ts} (91%) rename x-pack/plugins/apm/server/lib/settings/{custom_action/delete_custom_action.ts => custom_link/delete_custom_link.ts} (75%) rename x-pack/plugins/apm/server/lib/settings/{custom_action/list_custom_actions.ts => custom_link/list_custom_links.ts} (91%) rename x-pack/plugins/apm/server/routes/settings/{custom_actions.ts => custom_link.ts} (59%) diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CreateCustomActionButton.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateCustomLinkButton.tsx similarity index 74% rename from x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CreateCustomActionButton.tsx rename to x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateCustomLinkButton.tsx index 7c7ccb7546284..415d2557c23c3 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CreateCustomActionButton.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateCustomLinkButton.tsx @@ -7,15 +7,15 @@ import React from 'react'; import { EuiButton } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -export const CreateCustomActionButton = ({ +export const CreateCustomLinkButton = ({ onClick }: { onClick: () => void; }) => ( <EuiButton color="primary" fill onClick={onClick}> {i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.createCustomAction', - { defaultMessage: 'Create custom action' } + 'xpack.apm.settings.customizeUI.customLink.createCustomLink', + { defaultMessage: 'Create custom link' } )} </EuiButton> ); diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/DeleteButton.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/DeleteButton.tsx similarity index 71% rename from x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/DeleteButton.tsx rename to x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/DeleteButton.tsx index 84941a77e25b6..d8b55974e1a83 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/DeleteButton.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/DeleteButton.tsx @@ -14,10 +14,10 @@ import { useApmPluginContext } from '../../../../../../hooks/useApmPluginContext interface Props { onDelete: () => void; - customActionId: string; + customLinkId: string; } -export function DeleteButton({ onDelete, customActionId }: Props) { +export function DeleteButton({ onDelete, customLinkId }: Props) { const [isDeleting, setIsDeleting] = useState(false); const { toasts } = useApmPluginContext().core.notifications; const callApmApi = useCallApmApi(); @@ -29,12 +29,12 @@ export function DeleteButton({ onDelete, customActionId }: Props) { iconSide="right" onClick={async () => { setIsDeleting(true); - await deleteConfig(callApmApi, customActionId, toasts); + await deleteConfig(callApmApi, customLinkId, toasts); setIsDeleting(false); onDelete(); }} > - {i18n.translate('xpack.apm.settings.customizeUI.customActions.delete', { + {i18n.translate('xpack.apm.settings.customizeUI.customLink.delete', { defaultMessage: 'Delete' })} </EuiButtonEmpty> @@ -43,30 +43,30 @@ export function DeleteButton({ onDelete, customActionId }: Props) { async function deleteConfig( callApmApi: APMClient, - customActionId: string, + customLinkId: string, toasts: NotificationsStart['toasts'] ) { try { await callApmApi({ - pathname: '/api/apm/settings/custom-actions/{id}', + pathname: '/api/apm/settings/custom-links/{id}', method: 'DELETE', params: { - path: { id: customActionId } + path: { id: customLinkId } } }); toasts.addSuccess({ iconType: 'trash', title: i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.delete.successed', - { defaultMessage: 'Deleted custom action.' } + 'xpack.apm.settings.customizeUI.customLink.delete.successed', + { defaultMessage: 'Deleted custom link.' } ) }); } catch (error) { toasts.addDanger({ iconType: 'cross', title: i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.delete.failed', - { defaultMessage: 'Custom action could not be deleted' } + 'xpack.apm.settings.customizeUI.customLink.delete.failed', + { defaultMessage: 'Custom link could not be deleted' } ) }); } diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/FiltersSection.tsx similarity index 88% rename from x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx rename to x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/FiltersSection.tsx index 44421785c2bdc..2858e13d02182 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/FiltersSection.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/FiltersSection.tsx @@ -17,16 +17,16 @@ import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; import React, { useRef } from 'react'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { FilterOptionsType } from '../../../../../../../../../../plugins/apm/server/lib/settings/custom_action/list_custom_actions'; +import { FilterOptionsType } from '../../../../../../../../../../plugins/apm/server/lib/settings/custom_link/list_custom_links'; import { SERVICE_NAME, SERVICE_ENVIRONMENT, TRANSACTION_NAME, TRANSACTION_TYPE } from '../../../../../../../../../../plugins/apm/common/elasticsearch_fieldnames'; -import { CustomActionFormData } from '.'; +import { CustomLinkFormData } from '.'; -type FiltersType = CustomActionFormData['filters']; +type FiltersType = CustomLinkFormData['filters']; interface FilterOption { value: 'DEFAULT' | keyof FilterOptionsType; @@ -36,7 +36,7 @@ interface FilterOption { const DEFAULT_OPTION: FilterOption = { value: 'DEFAULT', text: i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.flyOut.filters.defaultOption', + 'xpack.apm.settings.customizeUI.customLink.flyOut.filters.defaultOption', { defaultMessage: 'Select fields...' } ) }; @@ -98,7 +98,7 @@ export const FiltersSection = ({ <EuiTitle size="xs"> <h3> {i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.flyout.filters.title', + 'xpack.apm.settings.customizeUI.customLink.flyout.filters.title', { defaultMessage: 'Filters' } @@ -108,7 +108,7 @@ export const FiltersSection = ({ <EuiSpacer size="s" /> <EuiText size="xs"> {i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.flyout.filters.subtitle', + 'xpack.apm.settings.customizeUI.customLink.flyout.filters.subtitle', { defaultMessage: 'Add additional values within the same field by comma separating values.' @@ -131,7 +131,7 @@ export const FiltersSection = ({ options={selectOptions} value={key} prepend={i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.flyout.filters.prepend', + 'xpack.apm.settings.customizeUI.customLink.flyout.filters.prepend', { defaultMessage: 'Field' } @@ -147,7 +147,7 @@ export const FiltersSection = ({ <EuiFieldText fullWidth placeholder={i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.flyOut.filters.defaultOption.value', + 'xpack.apm.settings.customizeUI.customLink.flyOut.filters.defaultOption.value', { defaultMessage: 'Value' } )} onChange={e => onChangeFilter([key, e.target.value], idx)} @@ -197,7 +197,7 @@ const AddFilterButton = ({ disabled={isDisabled} > {i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.flyout.filters.addAnotherFilter', + 'xpack.apm.settings.customizeUI.customLink.flyout.filters.addAnotherFilter', { defaultMessage: 'Add another filter' } diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/Flyoutfooter.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/Flyoutfooter.tsx similarity index 81% rename from x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/Flyoutfooter.tsx rename to x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/Flyoutfooter.tsx index 3285931ce21f8..cb27221309812 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/Flyoutfooter.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/Flyoutfooter.tsx @@ -18,13 +18,13 @@ export const FlyoutFooter = ({ onClose, isSaving, onDelete, - customActionId, + customLinkId, isSaveButtonEnabled }: { onClose: () => void; isSaving: boolean; onDelete: () => void; - customActionId?: string; + customLinkId?: string; isSaveButtonEnabled: boolean; }) => { return ( @@ -33,7 +33,7 @@ export const FlyoutFooter = ({ <EuiFlexItem grow={false}> <EuiButtonEmpty iconType="cross" onClick={onClose} flush="left"> {i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.flyout.close', + 'xpack.apm.settings.customizeUI.customLink.flyout.close', { defaultMessage: 'Close' } @@ -42,12 +42,9 @@ export const FlyoutFooter = ({ </EuiFlexItem> <EuiFlexItem grow={false}> <EuiFlexGroup> - {customActionId && ( + {customLinkId && ( <EuiFlexItem> - <DeleteButton - customActionId={customActionId} - onDelete={onDelete} - /> + <DeleteButton customLinkId={customLinkId} onDelete={onDelete} /> </EuiFlexItem> )} <EuiFlexItem> @@ -58,7 +55,7 @@ export const FlyoutFooter = ({ isDisabled={!isSaveButtonEnabled} > {i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.flyout.save', + 'xpack.apm.settings.customizeUI.customLink.flyout.save', { defaultMessage: 'Save' } diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/ActionSection.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/LinkSection.tsx similarity index 53% rename from x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/ActionSection.tsx rename to x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/LinkSection.tsx index 608e17f9a13e1..6dcc5d547928a 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/ActionSection.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/LinkSection.tsx @@ -12,10 +12,10 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { CustomActionFormData } from './'; +import { CustomLinkFormData } from './'; interface InputField { - name: keyof CustomActionFormData; + name: keyof CustomLinkFormData; label: string; helpText: string; placeholder: string; @@ -30,7 +30,7 @@ interface Props { onChangeUrl: (url: string) => void; } -export const ActionSection = ({ +export const LinkSection = ({ label, onChangeLabel, url, @@ -39,19 +39,50 @@ export const ActionSection = ({ const inputFields: InputField[] = [ { name: 'label', - label: 'Label', - helpText: - 'This is the label shown in the actions context menu. Keep it as short as possible.', - placeholder: 'e.g. Support tickets', + label: i18n.translate( + 'xpack.apm.settings.customizeUI.customLink.flyout.link.label', + { + defaultMessage: 'Label' + } + ), + helpText: i18n.translate( + 'xpack.apm.settings.customizeUI.customLink.flyout.link.label.helpText', + { + defaultMessage: + 'This is the label shown in the actions context menu. Keep it as short as possible.' + } + ), + placeholder: i18n.translate( + 'xpack.apm.settings.customizeUI.customLink.flyout.link.label.placeholder', + { + defaultMessage: 'e.g. Support tickets' + } + ), value: label, onChange: onChangeLabel }, { name: 'url', - label: 'URL', - helpText: - 'Add fieldname variables to your URL to apply values e.g. {{trace.id}}. TODO: Learn more in the docs.', - placeholder: 'e.g. https://www.elastic.co/', + label: i18n.translate( + 'xpack.apm.settings.customizeUI.customLink.flyout.link.url', + { + defaultMessage: 'URL' + } + ), + helpText: i18n.translate( + 'xpack.apm.settings.customizeUI.customLink.flyout.link.url.helpText', + { + defaultMessage: + 'Add fieldname variables to your URL to apply values e.g. {sample}. TODO: Learn more in the docs.', + values: { sample: '{{trace.id}}' } + } + ), + placeholder: i18n.translate( + 'xpack.apm.settings.customizeUI.customLink.flyout.link.url.placeholder', + { + defaultMessage: 'e.g. https://www.elastic.co/' + } + ), value: url, onChange: onChangeUrl } @@ -62,9 +93,9 @@ export const ActionSection = ({ <EuiTitle size="xs"> <h3> {i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.flyout.action.title', + 'xpack.apm.settings.customizeUI.customLink.flyout.action.title', { - defaultMessage: 'Action' + defaultMessage: 'Link' } )} </h3> @@ -80,7 +111,7 @@ export const ActionSection = ({ labelAppend={ <EuiText size="xs"> {i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.flyout.required', + 'xpack.apm.settings.customizeUI.customLink.flyout.required', { defaultMessage: 'Required' } diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/index.tsx similarity index 67% rename from x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx rename to x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/index.tsx index a992e82d8599d..d08f1f724f4f6 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/index.tsx @@ -15,26 +15,26 @@ import { import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; import React, { useState } from 'react'; -import { CustomAction } from '../../../../../../../../../../plugins/apm/server/lib/settings/custom_action/custom_action_types'; +import { CustomLink } from '../../../../../../../../../../plugins/apm/server/lib/settings/custom_link/custom_link_types'; import { useApmPluginContext } from '../../../../../../hooks/useApmPluginContext'; import { useCallApmApi } from '../../../../../../hooks/useCallApmApi'; -import { ActionSection } from './ActionSection'; +import { LinkSection } from './LinkSection'; import { FiltersSection } from './FiltersSection'; import { FlyoutFooter } from './FlyoutFooter'; -import { saveCustomAction } from './saveCustomAction'; +import { saveCustomLink } from './saveCustomLink'; interface Props { onClose: () => void; - customActionSelected?: CustomAction; + customLinkSelected?: CustomLink; onSave: () => void; onDelete: () => void; } -export interface CustomActionFormData extends Omit<CustomAction, 'filters'> { +export interface CustomLinkFormData extends Omit<CustomLink, 'filters'> { filters: Array<[string, string]>; } -const convertFiltersToArray = (filters: CustomAction['filters'] = {}) => { +const convertFiltersToArray = (filters: CustomLink['filters'] = {}) => { const convertedFilters = Object.entries(filters); // When convertedFilters is empty, initiate the filters filled with one item. if (isEmpty(convertedFilters)) { @@ -43,7 +43,7 @@ const convertFiltersToArray = (filters: CustomAction['filters'] = {}) => { return convertedFilters; }; -const convertFiltersToObject = (filters: CustomActionFormData['filters']) => { +const convertFiltersToObject = (filters: CustomLinkFormData['filters']) => { const convertedFilters = Object.fromEntries( filters.filter(([key, value]) => !isEmpty(key) && !isEmpty(value)) ); @@ -52,9 +52,9 @@ const convertFiltersToObject = (filters: CustomActionFormData['filters']) => { } }; -export const CustomActionsFlyout = ({ +export const CustomLinkFlyout = ({ onClose, - customActionSelected, + customLinkSelected, onSave, onDelete }: Props) => { @@ -63,10 +63,10 @@ export const CustomActionsFlyout = ({ const [isSaving, setIsSaving] = useState(false); // form fields - const [label, setLabel] = useState(customActionSelected?.label || ''); - const [url, setUrl] = useState(customActionSelected?.url || ''); + const [label, setLabel] = useState(customLinkSelected?.label || ''); + const [url, setUrl] = useState(customLinkSelected?.url || ''); const [filters, setFilters] = useState( - convertFiltersToArray(customActionSelected?.filters) + convertFiltersToArray(customLinkSelected?.filters) ); const isFormValid = !!label && !!url; @@ -78,8 +78,8 @@ export const CustomActionsFlyout = ({ ) => { event.preventDefault(); setIsSaving(true); - await saveCustomAction({ - id: customActionSelected?.id, + await saveCustomLink({ + id: customLinkSelected?.id, label, url, filters: convertFiltersToObject(filters), @@ -98,9 +98,9 @@ export const CustomActionsFlyout = ({ <EuiTitle size="s"> <h2> {i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.flyout.title', + 'xpack.apm.settings.customizeUI.customLink.flyout.title', { - defaultMessage: 'Create action' + defaultMessage: 'Create link' } )} </h2> @@ -110,10 +110,10 @@ export const CustomActionsFlyout = ({ <EuiText> <p> {i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.flyout.label', + 'xpack.apm.settings.customizeUI.customLink.flyout.label', { defaultMessage: - 'Actions will be available in the context of transaction details throughout the APM app. You can create an unlimited number of actions and use the filter options to scope them to only appear for specific services. You can refer to dynamic variables by using any of the transaction metadata to fill in your URLs. TODO: Learn more about it in the docs.' + 'Links will be available in the context of transaction details throughout the APM app. You can create an unlimited number of links and use the filter options to scope them to only appear for specific services. You can refer to dynamic variables by using any of the transaction metadata to fill in your URLs. TODO: Learn more about it in the docs.' } )} </p> @@ -121,7 +121,7 @@ export const CustomActionsFlyout = ({ <EuiSpacer size="l" /> - <ActionSection + <LinkSection label={label} onChangeLabel={setLabel} url={url} @@ -138,7 +138,7 @@ export const CustomActionsFlyout = ({ onClose={onClose} isSaving={isSaving} onDelete={onDelete} - customActionId={customActionSelected?.id} + customLinkId={customLinkSelected?.id} /> </EuiFlyout> </form> diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/saveCustomAction.ts b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/saveCustomLink.ts similarity index 69% rename from x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/saveCustomAction.ts rename to x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/saveCustomLink.ts index ac4467d070f10..34269c6a6251c 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsFlyout/saveCustomAction.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/saveCustomLink.ts @@ -6,10 +6,10 @@ import { i18n } from '@kbn/i18n'; import { NotificationsStart } from 'kibana/public'; -import { CustomAction } from '../../../../../../../../../../plugins/apm/server/lib/settings/custom_action/custom_action_types'; +import { CustomLink } from '../../../../../../../../../../plugins/apm/server/lib/settings/custom_link/custom_link_types'; import { APMClient } from '../../../../../../services/rest/createCallApmApi'; -export async function saveCustomAction({ +export async function saveCustomLink({ id, label, url, @@ -20,49 +20,49 @@ export async function saveCustomAction({ id?: string; label: string; url: string; - filters?: CustomAction['filters']; + filters?: CustomLink['filters']; callApmApi: APMClient; toasts: NotificationsStart['toasts']; }) { try { - const customAction = { + const customLink = { label, url, filters }; if (id) { await callApmApi({ - pathname: '/api/apm/settings/custom-actions/{id}', + pathname: '/api/apm/settings/custom-links/{id}', method: 'PUT', params: { path: { id }, - body: customAction + body: customLink } }); } else { await callApmApi({ - pathname: '/api/apm/settings/custom-actions', + pathname: '/api/apm/settings/custom-links', method: 'POST', params: { - body: customAction + body: customLink } }); } toasts.addSuccess({ iconType: 'check', title: i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.create.successed', + 'xpack.apm.settings.customizeUI.customLink.create.successed', { defaultMessage: 'Link saved!' } ) }); } catch (error) { toasts.addDanger({ title: i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.create.failed', + 'xpack.apm.settings.customizeUI.customLink.create.failed', { defaultMessage: 'Link could not be saved!' } ), text: i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.create.failed', + 'xpack.apm.settings.customizeUI.customLink.create.failed', { defaultMessage: 'Something went wrong when saving the link. Error: "{errorMessage}"', diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsTable.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkTable.tsx similarity index 71% rename from x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsTable.tsx rename to x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkTable.tsx index a877494bb2650..ab6503bf3bd78 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/CustomActionsTable.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkTable.tsx @@ -14,19 +14,19 @@ import { } from '@elastic/eui'; import { isEmpty } from 'lodash'; import { units, px } from '../../../../../style/variables'; -import { CustomAction } from '../../../../../../../../../plugins/apm/server/lib/settings/custom_action/custom_action_types'; +import { CustomLink } from '../../../../../../../../../plugins/apm/server/lib/settings/custom_link/custom_link_types'; import { ManagedTable } from '../../../../shared/ManagedTable'; import { TimestampTooltip } from '../../../../shared/TimestampTooltip'; import { LoadingStatePrompt } from '../../../../shared/LoadingStatePrompt'; interface Props { - items: CustomAction[]; - onCustomActionSelected: (customAction: CustomAction) => void; + items: CustomLink[]; + onCustomLinkSelected: (customLink: CustomLink) => void; } -export const CustomActionsTable = ({ +export const CustomLinkTable = ({ items = [], - onCustomActionSelected + onCustomLinkSelected }: Props) => { const [searchTerm, setSearchTerm] = useState(''); @@ -34,16 +34,16 @@ export const CustomActionsTable = ({ { field: 'label', name: i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.table.actionName', - { defaultMessage: 'Action Name' } + 'xpack.apm.settings.customizeUI.customLink.table.name', + { defaultMessage: 'Name' } ), truncateText: true }, { field: 'url', name: i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.table.actionURL', - { defaultMessage: 'Action URL' } + 'xpack.apm.settings.customizeUI.customLink.table.url', + { defaultMessage: 'URL' } ), truncateText: true }, @@ -52,7 +52,7 @@ export const CustomActionsTable = ({ align: 'right', field: '@timestamp', name: i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.table.lastUpdated', + 'xpack.apm.settings.customizeUI.customLink.table.lastUpdated', { defaultMessage: 'Last updated' } ), sortable: true, @@ -63,24 +63,24 @@ export const CustomActionsTable = ({ { width: px(units.quadruple), name: i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.table.actions', - { defaultMessage: 'Actions' } + 'xpack.apm.settings.customizeUI.customLink.table.links', + { defaultMessage: 'Links' } ), actions: [ { name: i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.table.editButtonLabel', + 'xpack.apm.settings.customizeUI.customLink.table.editButtonLabel', { defaultMessage: 'Edit' } ), description: i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.table.editButtonDescription', - { defaultMessage: 'Edit this custom action' } + 'xpack.apm.settings.customizeUI.customLink.table.editButtonDescription', + { defaultMessage: 'Edit this custom link' } ), icon: 'pencil', color: 'primary', type: 'icon', - onClick: (customAction: CustomAction) => { - onCustomActionSelected(customAction); + onClick: (customLink: CustomLink) => { + onCustomLinkSelected(customLink); } } ] @@ -101,7 +101,7 @@ export const CustomActionsTable = ({ fullWidth onChange={e => setSearchTerm(e.target.value)} placeholder={i18n.translate('xpack.apm.searchInput.filter', { - defaultMessage: 'Filter actions...' + defaultMessage: 'Filter links by Name or URL...' })} /> <EuiSpacer size="s" /> @@ -128,7 +128,7 @@ const NoResultFound = ({ value }: { value: string }) => ( <EuiFlexItem grow={false}> <EuiText size="s"> {i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.table.noResultFound', + 'xpack.apm.settings.customizeUI.customLink.table.noResultFound', { defaultMessage: `No results for "{value}".`, values: { value } diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/EmptyPrompt.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/EmptyPrompt.tsx similarity index 54% rename from x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/EmptyPrompt.tsx rename to x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/EmptyPrompt.tsx index 3bf218d98ff44..79917553b7e66 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/EmptyPrompt.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/EmptyPrompt.tsx @@ -6,12 +6,12 @@ import { EuiEmptyPrompt } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { CreateCustomActionButton } from './CreateCustomActionButton'; +import { CreateCustomLinkButton } from './CreateCustomLinkButton'; export const EmptyPrompt = ({ - onCreateCustomActionClick + onCreateCustomLinkClick }: { - onCreateCustomActionClick: () => void; + onCreateCustomLinkClick: () => void; }) => { return ( <EuiEmptyPrompt @@ -20,9 +20,9 @@ export const EmptyPrompt = ({ title={ <h2> {i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.emptyPromptTitle', + 'xpack.apm.settings.customizeUI.customLink.emptyPromptTitle', { - defaultMessage: 'No actions found.' + defaultMessage: 'No links found.' } )} </h2> @@ -31,16 +31,16 @@ export const EmptyPrompt = ({ <> <p> {i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.emptyPromptText', + 'xpack.apm.settings.customizeUI.customLink.emptyPromptText', { defaultMessage: - "Let's change that! You can add custom actions to the Actions context menu by the trace and error details for each service. This could be linking to a Kibana dashboard or going to your organization's support portal" + "Let's change that! You can add custom links to the Actions context menu by the trace and error details for each service. This could be linking to a Kibana dashboard or going to your organization's support portal" } )} </p> </> } - actions={<CreateCustomActionButton onClick={onCreateCustomActionClick} />} + actions={<CreateCustomLinkButton onClick={onCreateCustomLinkClick} />} /> ); }; diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/Title.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/Title.tsx similarity index 81% rename from x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/Title.tsx rename to x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/Title.tsx index d7f90e0919733..8ff51c1594cc3 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/Title.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/Title.tsx @@ -14,8 +14,8 @@ export const Title = () => ( <EuiFlexGroup alignItems="center" gutterSize="s" responsive={false}> <EuiFlexItem grow={false}> <h1> - {i18n.translate('xpack.apm.settings.customizeUI.customActions', { - defaultMessage: 'Custom actions' + {i18n.translate('xpack.apm.settings.customizeUI.customLink', { + defaultMessage: 'Custom Links' })} </h1> </EuiFlexItem> @@ -25,10 +25,10 @@ export const Title = () => ( type="iInCircle" position="top" content={i18n.translate( - 'xpack.apm.settings.customizeUI.customActions.info', + 'xpack.apm.settings.customizeUI.customLink.info', { defaultMessage: - "These actions will be shown in the 'Actions' context menu for the trace and error detail components." + "These links will be shown in the 'Actions' context menu for the trace and error detail components." } )} /> diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/__test__/CustomActions.test.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/__test__/CustomLink.test.tsx similarity index 70% rename from x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/__test__/CustomActions.test.tsx rename to x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/__test__/CustomLink.test.tsx index 32428314cfb7f..8140a21370288 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/__test__/CustomActions.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/__test__/CustomLink.test.tsx @@ -7,14 +7,14 @@ import { fireEvent, render } from '@testing-library/react'; import React from 'react'; import { act } from 'react-dom/test-utils'; -import { CustomActionsOverview } from '../'; +import { CustomLinkOverview } from '../'; import * as hooks from '../../../../../../hooks/useFetcher'; import { expectTextsInDocument, MockApmPluginContextWrapper } from '../../../../../../utils/testHelpers'; -describe('CustomActions', () => { +describe('CustomLink', () => { afterEach(() => jest.restoreAllMocks()); describe('empty prompt', () => { @@ -28,21 +28,21 @@ describe('CustomActions', () => { afterAll(() => { jest.clearAllMocks(); }); - it('shows when no action is available', () => { - const component = render(<CustomActionsOverview />); - expectTextsInDocument(component, ['No actions found.']); + it('shows when no link is available', () => { + const component = render(<CustomLinkOverview />); + expectTextsInDocument(component, ['No links found.']); }); - it('opens flyout when click to create new action', () => { + it('opens flyout when click to create new link', () => { const { queryByText, getByText } = render( <MockApmPluginContextWrapper> - <CustomActionsOverview /> + <CustomLinkOverview /> </MockApmPluginContextWrapper> ); - expect(queryByText('Create action')).not.toBeInTheDocument(); + expect(queryByText('Create link')).not.toBeInTheDocument(); act(() => { - fireEvent.click(getByText('Create custom action')); + fireEvent.click(getByText('Create custom link')); }); - expect(queryByText('Create action')).toBeInTheDocument(); + expect(queryByText('Create link')).toBeInTheDocument(); }); }); @@ -71,10 +71,10 @@ describe('CustomActions', () => { }); }); - it('shows a table with all custom actions', () => { + it('shows a table with all custom link', () => { const component = render( <MockApmPluginContextWrapper> - <CustomActionsOverview /> + <CustomLinkOverview /> </MockApmPluginContextWrapper> ); expectTextsInDocument(component, [ @@ -85,32 +85,32 @@ describe('CustomActions', () => { ]); }); - it('checks if create custom action button is available and working', () => { + it('checks if create custom link button is available and working', () => { const { queryByText, getByText } = render( <MockApmPluginContextWrapper> - <CustomActionsOverview /> + <CustomLinkOverview /> </MockApmPluginContextWrapper> ); - expect(queryByText('Create action')).not.toBeInTheDocument(); + expect(queryByText('Create link')).not.toBeInTheDocument(); act(() => { - fireEvent.click(getByText('Create custom action')); + fireEvent.click(getByText('Create custom link')); }); - expect(queryByText('Create action')).toBeInTheDocument(); + expect(queryByText('Create link')).toBeInTheDocument(); }); - it('opens flyout to edit a custom action', () => { + it('opens flyout to edit a custom link', () => { const component = render( <MockApmPluginContextWrapper> - <CustomActionsOverview /> + <CustomLinkOverview /> </MockApmPluginContextWrapper> ); - expect(component.queryByText('Create action')).not.toBeInTheDocument(); + expect(component.queryByText('Create link')).not.toBeInTheDocument(); const editButtons = component.getAllByLabelText('Edit'); expect(editButtons.length).toEqual(2); act(() => { fireEvent.click(editButtons[0]); }); - expect(component.queryByText('Create action')).toBeInTheDocument(); + expect(component.queryByText('Create link')).toBeInTheDocument(); }); }); @@ -118,14 +118,14 @@ describe('CustomActions', () => { const openFlyout = () => { const component = render( <MockApmPluginContextWrapper> - <CustomActionsOverview /> + <CustomLinkOverview /> </MockApmPluginContextWrapper> ); - expect(component.queryByText('Create action')).not.toBeInTheDocument(); + expect(component.queryByText('Create link')).not.toBeInTheDocument(); act(() => { - fireEvent.click(component.getByText('Create custom action')); + fireEvent.click(component.getByText('Create custom link')); }); - expect(component.queryByText('Create action')).toBeInTheDocument(); + expect(component.queryByText('Create link')).toBeInTheDocument(); return component; }; diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx similarity index 60% rename from x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/index.tsx rename to x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx index db96288a57686..8f37c67c567bf 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomActionsOverview/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx @@ -7,49 +7,49 @@ import { EuiPanel, EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { isEmpty } from 'lodash'; import React, { useEffect, useState } from 'react'; -import { CustomAction } from '../../../../../../../../../plugins/apm/server/lib/settings/custom_action/custom_action_types'; +import { CustomLink } from '../../../../../../../../../plugins/apm/server/lib/settings/custom_link/custom_link_types'; import { useFetcher, FETCH_STATUS } from '../../../../../hooks/useFetcher'; -import { CustomActionsFlyout } from './CustomActionsFlyout'; -import { CustomActionsTable } from './CustomActionsTable'; +import { CustomLinkFlyout } from './CustomLinkFlyout'; +import { CustomLinkTable } from './CustomLinkTable'; import { EmptyPrompt } from './EmptyPrompt'; import { Title } from './Title'; -import { CreateCustomActionButton } from './CreateCustomActionButton'; +import { CreateCustomLinkButton } from './CreateCustomLinkButton'; -export const CustomActionsOverview = () => { +export const CustomLinkOverview = () => { const [isFlyoutOpen, setIsFlyoutOpen] = useState(false); - const [customActionSelected, setCustomActionSelected] = useState< - CustomAction | undefined + const [customLinkSelected, setCustomLinkSelected] = useState< + CustomLink | undefined >(); - const { data: customActions, status, refetch } = useFetcher( - callApmApi => callApmApi({ pathname: '/api/apm/settings/custom-actions' }), + const { data: customLinks, status, refetch } = useFetcher( + callApmApi => callApmApi({ pathname: '/api/apm/settings/custom-links' }), [] ); useEffect(() => { - if (customActionSelected) { + if (customLinkSelected) { setIsFlyoutOpen(true); } - }, [customActionSelected]); + }, [customLinkSelected]); const onCloseFlyout = () => { - setCustomActionSelected(undefined); + setCustomLinkSelected(undefined); setIsFlyoutOpen(false); }; - const onCreateCustomActionClick = () => { + const onCreateCustomLinkClick = () => { setIsFlyoutOpen(true); }; const showEmptyPrompt = - status === FETCH_STATUS.SUCCESS && isEmpty(customActions); + status === FETCH_STATUS.SUCCESS && isEmpty(customLinks); return ( <> {isFlyoutOpen && ( - <CustomActionsFlyout + <CustomLinkFlyout onClose={onCloseFlyout} - customActionSelected={customActionSelected} + customLinkSelected={customLinkSelected} onSave={() => { onCloseFlyout(); refetch(); @@ -69,9 +69,7 @@ export const CustomActionsOverview = () => { <EuiFlexItem> <EuiFlexGroup alignItems="center" justifyContent="flexEnd"> <EuiFlexItem grow={false}> - <CreateCustomActionButton - onClick={onCreateCustomActionClick} - /> + <CreateCustomLinkButton onClick={onCreateCustomLinkClick} /> </EuiFlexItem> </EuiFlexGroup> </EuiFlexItem> @@ -81,11 +79,11 @@ export const CustomActionsOverview = () => { <EuiSpacer size="m" /> {showEmptyPrompt ? ( - <EmptyPrompt onCreateCustomActionClick={onCreateCustomActionClick} /> + <EmptyPrompt onCreateCustomLinkClick={onCreateCustomLinkClick} /> ) : ( - <CustomActionsTable - items={customActions} - onCustomActionSelected={setCustomActionSelected} + <CustomLinkTable + items={customLinks} + onCustomLinkSelected={setCustomLinkSelected} /> )} </EuiPanel> diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/index.tsx index 17a4b2f847679..1cd1298fdd549 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/index.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { EuiTitle, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { CustomActionsOverview } from './CustomActionsOverview'; +import { CustomLinkOverview } from './CustomLink'; export const CustomizeUI = () => { return ( @@ -20,7 +20,7 @@ export const CustomizeUI = () => { </h1> </EuiTitle> <EuiSpacer size="l" /> - <CustomActionsOverview /> + <CustomLinkOverview /> </> ); }; diff --git a/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx b/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx index 9dc0ec29fc66a..fceaebe203e64 100644 --- a/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx +++ b/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx @@ -163,7 +163,7 @@ export async function inspectSearchParams( 'apm_oss.transactionIndices': 'myIndex', 'apm_oss.metricsIndices': 'myIndex', apmAgentConfigurationIndex: 'myIndex', - apmCustomActionIndex: 'myIndex' + apmCustomLinkIndex: 'myIndex' }, dynamicIndexPattern: null as any }; diff --git a/x-pack/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts b/x-pack/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts index 0bcf440a4d0ac..f338ee058842c 100644 --- a/x-pack/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts +++ b/x-pack/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts @@ -25,7 +25,7 @@ export interface ApmIndicesConfig { 'apm_oss.transactionIndices': string; 'apm_oss.metricsIndices': string; apmAgentConfigurationIndex: string; - apmCustomActionIndex: string; + apmCustomLinkIndex: string; } export type ApmIndicesName = keyof ApmIndicesConfig; @@ -54,7 +54,7 @@ export function getApmIndicesConfig(config: APMConfig): ApmIndicesConfig { 'apm_oss.metricsIndices': config['apm_oss.metricsIndices'], // system indices, not configurable apmAgentConfigurationIndex: '.apm-agent-configuration', - apmCustomActionIndex: '.apm-custom-action' + apmCustomLinkIndex: '.apm-custom-link' }; } diff --git a/x-pack/plugins/apm/server/lib/settings/custom_action/__test__/__snapshots__/list_custom_actions.test.ts.snap b/x-pack/plugins/apm/server/lib/settings/custom_link/__test__/__snapshots__/list_custom_links.test.ts.snap similarity index 61% rename from x-pack/plugins/apm/server/lib/settings/custom_action/__test__/__snapshots__/list_custom_actions.test.ts.snap rename to x-pack/plugins/apm/server/lib/settings/custom_link/__test__/__snapshots__/list_custom_links.test.ts.snap index 01f5313fcdacb..e4419608a91d5 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_action/__test__/__snapshots__/list_custom_actions.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/settings/custom_link/__test__/__snapshots__/list_custom_links.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`List Custom Actions fetches custom actions 1`] = ` +exports[`List Custom Actions fetches custom links 1`] = ` Object { "index": "myIndex", "size": 500, diff --git a/x-pack/plugins/apm/server/lib/settings/custom_action/__test__/create_or_update_custom_action.test.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/__test__/create_or_update_custom_link.test.ts similarity index 77% rename from x-pack/plugins/apm/server/lib/settings/custom_action/__test__/create_or_update_custom_action.test.ts rename to x-pack/plugins/apm/server/lib/settings/custom_link/__test__/create_or_update_custom_link.test.ts index 7c8692328e97b..aff482d042a39 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_action/__test__/create_or_update_custom_action.test.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_link/__test__/create_or_update_custom_link.test.ts @@ -4,19 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import { createOrUpdateCustomAction } from '../create_or_update_custom_action'; -import { CustomAction } from '../custom_action_types'; +import { createOrUpdateCustomLink } from '../create_or_update_custom_link'; +import { CustomLink } from '../custom_link_types'; import { Setup } from '../../../helpers/setup_request'; import { mockNow } from '../../../../../../../legacy/plugins/apm/public/utils/testHelpers'; -describe('Create or Update Custom Action', () => { +describe('Create or Update Custom link', () => { const internalClientIndexMock = jest.fn(); const mockedSetup = ({ internalClient: { index: internalClientIndexMock }, indices: { - apmCustomActionIndex: 'apmCustomActionIndex' + apmCustomLinkIndex: 'apmCustomLinkIndex' } } as unknown) as Setup; @@ -37,11 +37,11 @@ describe('Create or Update Custom Action', () => { mockNow(1570737000000); }); - it('creates a new custom action', () => { - createOrUpdateCustomAction({ customAction, setup: mockedSetup }); + it('creates a new custom link', () => { + createOrUpdateCustomLink({ customAction, setup: mockedSetup }); expect(internalClientIndexMock).toHaveBeenCalledWith({ refresh: true, - index: 'apmCustomActionIndex', + index: 'apmCustomLinkIndex', body: { '@timestamp': 1570737000000, label: 'foo', @@ -54,15 +54,15 @@ describe('Create or Update Custom Action', () => { } }); }); - it('update a new custom action', () => { - createOrUpdateCustomAction({ + it('update a new custom link', () => { + createOrUpdateCustomLink({ customActionId: 'bar', customAction, setup: mockedSetup }); expect(internalClientIndexMock).toHaveBeenCalledWith({ refresh: true, - index: 'apmCustomActionIndex', + index: 'apmCustomLinkIndex', id: 'bar', body: { '@timestamp': 1570737000000, diff --git a/x-pack/plugins/apm/server/lib/settings/custom_action/__test__/list_custom_actions.test.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/__test__/list_custom_links.test.ts similarity index 78% rename from x-pack/plugins/apm/server/lib/settings/custom_action/__test__/list_custom_actions.test.ts rename to x-pack/plugins/apm/server/lib/settings/custom_link/__test__/list_custom_links.test.ts index 59c3eb6e0a9af..2e747844219c3 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_action/__test__/list_custom_actions.test.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_link/__test__/list_custom_links.test.ts @@ -4,19 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import { listCustomActions } from '../list_custom_actions'; +import { listCustomLinks } from '../list_custom_links'; import { inspectSearchParams, SearchParamsMock } from '../../../../../../../legacy/plugins/apm/public/utils/testHelpers'; import { Setup } from '../../../helpers/setup_request'; -describe('List Custom Actions', () => { +describe('List Custom Links', () => { let mock: SearchParamsMock; - it('fetches custom actions', async () => { + it('fetches custom links', async () => { mock = await inspectSearchParams(setup => - listCustomActions({ + listCustomLinks({ setup: (setup as unknown) as Setup }) ); diff --git a/x-pack/plugins/apm/server/lib/settings/custom_action/create_custom_action_index.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/create_custom_link_index.ts similarity index 88% rename from x-pack/plugins/apm/server/lib/settings/custom_action/create_custom_action_index.ts rename to x-pack/plugins/apm/server/lib/settings/custom_link/create_custom_link_index.ts index d24a4b84b65dc..d43dcef485cfc 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_action/create_custom_action_index.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_link/create_custom_link_index.ts @@ -12,7 +12,7 @@ import { } from '../../helpers/create_or_update_index'; import { getApmIndicesConfig } from '../apm_indices/get_apm_indices'; -export const createApmCustomActionIndex = async ({ +export const createApmCustomLinkIndex = async ({ esClient, config, logger @@ -21,7 +21,7 @@ export const createApmCustomActionIndex = async ({ config: APMConfig; logger: Logger; }) => { - const index = getApmIndicesConfig(config).apmCustomActionIndex; + const index = getApmIndicesConfig(config).apmCustomLinkIndex; return createOrUpdateIndex({ index, esClient, logger, mappings }); }; diff --git a/x-pack/plugins/apm/server/lib/settings/custom_action/create_or_update_custom_action.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/create_or_update_custom_link.ts similarity index 58% rename from x-pack/plugins/apm/server/lib/settings/custom_action/create_or_update_custom_action.ts rename to x-pack/plugins/apm/server/lib/settings/custom_link/create_or_update_custom_link.ts index 001daab2cbc51..1fee163030981 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_action/create_or_update_custom_action.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_link/create_or_update_custom_link.ts @@ -6,33 +6,33 @@ import { APMIndexDocumentParams } from '../../helpers/es_client'; import { Setup } from '../../helpers/setup_request'; -import { CustomAction } from './custom_action_types'; +import { CustomLink } from './custom_link_types'; -export async function createOrUpdateCustomAction({ - customActionId, - customAction, +export async function createOrUpdateCustomLink({ + customLinkId, + customLink, setup }: { - customActionId?: string; - customAction: Omit<CustomAction, '@timestamp'>; + customLinkId?: string; + customLink: Omit<CustomLink, '@timestamp'>; setup: Setup; }) { const { internalClient, indices } = setup; - const params: APMIndexDocumentParams<CustomAction> = { + const params: APMIndexDocumentParams<CustomLink> = { refresh: true, - index: indices.apmCustomActionIndex, + index: indices.apmCustomLinkIndex, body: { '@timestamp': Date.now(), - label: customAction.label, - url: customAction.url, - filters: customAction.filters + label: customLink.label, + url: customLink.url, + filters: customLink.filters } }; // by specifying an id elasticsearch will delete the previous doc and insert the updated doc - if (customActionId) { - params.id = customActionId; + if (customLinkId) { + params.id = customLinkId; } return internalClient.index(params); diff --git a/x-pack/plugins/apm/server/lib/settings/custom_action/custom_action_types.d.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/custom_link_types.d.ts similarity index 91% rename from x-pack/plugins/apm/server/lib/settings/custom_action/custom_action_types.d.ts rename to x-pack/plugins/apm/server/lib/settings/custom_link/custom_link_types.d.ts index 67d0f65442db3..bdff7ef6fe465 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_action/custom_action_types.d.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_link/custom_link_types.d.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export interface CustomAction { +export interface CustomLink { id?: string; '@timestamp': number; label: string; diff --git a/x-pack/plugins/apm/server/lib/settings/custom_action/delete_custom_action.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/delete_custom_link.ts similarity index 75% rename from x-pack/plugins/apm/server/lib/settings/custom_action/delete_custom_action.ts rename to x-pack/plugins/apm/server/lib/settings/custom_link/delete_custom_link.ts index e1193646539a1..2f3ea0940cb26 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_action/delete_custom_action.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_link/delete_custom_link.ts @@ -6,19 +6,19 @@ import { Setup } from '../../helpers/setup_request'; -export async function deleteCustomAction({ - customActionId, +export async function deleteCustomLink({ + customLinkId, setup }: { - customActionId: string; + customLinkId: string; setup: Setup; }) { const { internalClient, indices } = setup; const params = { refresh: 'wait_for', - index: indices.apmCustomActionIndex, - id: customActionId + index: indices.apmCustomLinkIndex, + id: customLinkId }; return internalClient.delete(params); diff --git a/x-pack/plugins/apm/server/lib/settings/custom_action/list_custom_actions.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/list_custom_links.ts similarity index 91% rename from x-pack/plugins/apm/server/lib/settings/custom_action/list_custom_actions.ts rename to x-pack/plugins/apm/server/lib/settings/custom_link/list_custom_links.ts index 8e1d4edd0e842..bfed051bdc9a1 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_action/list_custom_actions.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_link/list_custom_links.ts @@ -13,7 +13,7 @@ import { SERVICE_ENVIRONMENT } from '../../../../common/elasticsearch_fieldnames'; import { Setup } from '../../helpers/setup_request'; -import { CustomAction } from './custom_action_types'; +import { CustomAction } from './custom_link_types'; export const FilterOptions = t.partial({ [SERVICE_NAME]: t.string, @@ -24,7 +24,7 @@ export const FilterOptions = t.partial({ export type FilterOptionsType = t.TypeOf<typeof FilterOptions>; -export async function listCustomActions({ +export async function listCustomLinks({ setup, filters = {} }: { @@ -47,7 +47,7 @@ export async function listCustomActions({ }); const params = { - index: indices.apmCustomActionIndex, + index: indices.apmCustomLinkIndex, size: 500, body: { query: { diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts index 4947838d94ab5..db14730f802a9 100644 --- a/x-pack/plugins/apm/server/plugin.ts +++ b/x-pack/plugins/apm/server/plugin.ts @@ -12,7 +12,7 @@ import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { APMOSSPluginSetup } from '../../../../src/plugins/apm_oss/server'; import { makeApmUsageCollector } from './lib/apm_telemetry'; import { createApmAgentConfigurationIndex } from './lib/settings/agent_configuration/create_agent_config_index'; -import { createApmCustomActionIndex } from './lib/settings/custom_action/create_custom_action_index'; +import { createApmCustomLinkIndex } from './lib/settings/custom_link/create_custom_link_index'; import { createApmApi } from './routes/create_apm_api'; import { getApmIndices } from './lib/settings/apm_indices/get_apm_indices'; import { APMConfig, mergeConfigs, APMXPackConfig } from '.'; @@ -68,7 +68,7 @@ export class APMPlugin implements Plugin<APMPluginContract> { logger }); // create custom action index without blocking setup lifecycle - createApmCustomActionIndex({ + createApmCustomLinkIndex({ esClient: core.elasticsearch.dataClient, config: currentConfig, logger diff --git a/x-pack/plugins/apm/server/routes/create_apm_api.ts b/x-pack/plugins/apm/server/routes/create_apm_api.ts index 4473754ef648a..34f0536a90b4d 100644 --- a/x-pack/plugins/apm/server/routes/create_apm_api.ts +++ b/x-pack/plugins/apm/server/routes/create_apm_api.ts @@ -60,11 +60,11 @@ import { createApi } from './create_api'; import { serviceMapRoute, serviceMapServiceNodeRoute } from './service_map'; import { indicesPrivilegesRoute } from './security'; import { - createCustomActionRoute, - updateCustomActionRoute, - deleteCustomActionRoute, - listCustomActionsRoute -} from './settings/custom_actions'; + createCustomLinkRoute, + updateCustomLinkRoute, + deleteCustomLinkRoute, + listCustomLinksRoute +} from './settings/custom_link'; const createApmApi = () => { const api = createApi() @@ -134,11 +134,11 @@ const createApmApi = () => { // security .add(indicesPrivilegesRoute) - // Custom actions - .add(createCustomActionRoute) - .add(updateCustomActionRoute) - .add(deleteCustomActionRoute) - .add(listCustomActionsRoute); + // Custom links + .add(createCustomLinkRoute) + .add(updateCustomLinkRoute) + .add(deleteCustomLinkRoute) + .add(listCustomLinksRoute); return api; }; diff --git a/x-pack/plugins/apm/server/routes/settings/custom_actions.ts b/x-pack/plugins/apm/server/routes/settings/custom_link.ts similarity index 59% rename from x-pack/plugins/apm/server/routes/settings/custom_actions.ts rename to x-pack/plugins/apm/server/routes/settings/custom_link.ts index 713e1f48d1797..feefe34185189 100644 --- a/x-pack/plugins/apm/server/routes/settings/custom_actions.ts +++ b/x-pack/plugins/apm/server/routes/settings/custom_link.ts @@ -6,22 +6,22 @@ import * as t from 'io-ts'; import { createRoute } from '../create_route'; import { setupRequest } from '../../lib/helpers/setup_request'; -import { createOrUpdateCustomAction } from '../../lib/settings/custom_action/create_or_update_custom_action'; -import { deleteCustomAction } from '../../lib/settings/custom_action/delete_custom_action'; +import { createOrUpdateCustomLink } from '../../lib/settings/custom_link/create_or_update_custom_link'; +import { deleteCustomLink } from '../../lib/settings/custom_link/delete_custom_link'; import { - listCustomActions, + listCustomLinks, FilterOptions -} from '../../lib/settings/custom_action/list_custom_actions'; +} from '../../lib/settings/custom_link/list_custom_links'; -export const listCustomActionsRoute = createRoute(core => ({ - path: '/api/apm/settings/custom-actions', +export const listCustomLinksRoute = createRoute(core => ({ + path: '/api/apm/settings/custom-links', params: { query: FilterOptions }, handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const { params } = context; - return await listCustomActions({ setup, filters: params.query }); + return await listCustomLinks({ setup, filters: params.query }); } })); @@ -35,9 +35,9 @@ const payload = t.intersection([ }) ]); -export const createCustomActionRoute = createRoute(() => ({ +export const createCustomLinkRoute = createRoute(() => ({ method: 'POST', - path: '/api/apm/settings/custom-actions', + path: '/api/apm/settings/custom-links', params: { body: payload }, @@ -46,15 +46,15 @@ export const createCustomActionRoute = createRoute(() => ({ }, handler: async ({ context, request }) => { const setup = await setupRequest(context, request); - const customAction = context.params.body; - const res = await createOrUpdateCustomAction({ customAction, setup }); + const customLink = context.params.body; + const res = await createOrUpdateCustomLink({ customLink, setup }); return res; } })); -export const updateCustomActionRoute = createRoute(() => ({ +export const updateCustomLinkRoute = createRoute(() => ({ method: 'PUT', - path: '/api/apm/settings/custom-actions/{id}', + path: '/api/apm/settings/custom-links/{id}', params: { path: t.type({ id: t.string @@ -67,19 +67,19 @@ export const updateCustomActionRoute = createRoute(() => ({ handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const { id } = context.params.path; - const customAction = context.params.body; - const res = await createOrUpdateCustomAction({ - customActionId: id, - customAction, + const customLink = context.params.body; + const res = await createOrUpdateCustomLink({ + customLinkId: id, + customLink, setup }); return res; } })); -export const deleteCustomActionRoute = createRoute(() => ({ +export const deleteCustomLinkRoute = createRoute(() => ({ method: 'DELETE', - path: '/api/apm/settings/custom-actions/{id}', + path: '/api/apm/settings/custom-links/{id}', params: { path: t.type({ id: t.string @@ -91,8 +91,8 @@ export const deleteCustomActionRoute = createRoute(() => ({ handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const { id } = context.params.path; - const res = await deleteCustomAction({ - customActionId: id, + const res = await deleteCustomLink({ + customLinkId: id, setup }); return res; From 30ba7c59f6eda095a3540f7dc4c5ef04416523ff Mon Sep 17 00:00:00 2001 From: cauemarcondes <caue.marcondes@elastic.co> Date: Mon, 2 Mar 2020 13:56:04 +0100 Subject: [PATCH 31/42] fixing unit tests --- .../list_custom_links.test.ts.snap | 70 ++++++++++++++++++- .../create_or_update_custom_link.test.ts | 19 +++-- .../__test__/list_custom_links.test.ts | 21 +++++- 3 files changed, 97 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/apm/server/lib/settings/custom_link/__test__/__snapshots__/list_custom_links.test.ts.snap b/x-pack/plugins/apm/server/lib/settings/custom_link/__test__/__snapshots__/list_custom_links.test.ts.snap index e4419608a91d5..da0a2d076ddd1 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_link/__test__/__snapshots__/list_custom_links.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/settings/custom_link/__test__/__snapshots__/list_custom_links.test.ts.snap @@ -1,7 +1,75 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`List Custom Actions fetches custom links 1`] = ` +exports[`List Custom Links fetches all custom links 1`] = ` Object { + "body": Object { + "query": Object { + "bool": Object { + "filter": Array [], + }, + }, + }, + "index": "myIndex", + "size": 500, +} +`; + +exports[`List Custom Links filters custom links 1`] = ` +Object { + "body": Object { + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "term": Object { + "filters.service.name": "foo", + }, + }, + Object { + "bool": Object { + "must_not": Array [ + Object { + "exists": Object { + "field": "filters.service.name", + }, + }, + ], + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "term": Object { + "filters.transaction.name": "bar", + }, + }, + Object { + "bool": Object { + "must_not": Array [ + Object { + "exists": Object { + "field": "filters.transaction.name", + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + }, "index": "myIndex", "size": 500, } diff --git a/x-pack/plugins/apm/server/lib/settings/custom_link/__test__/create_or_update_custom_link.test.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/__test__/create_or_update_custom_link.test.ts index aff482d042a39..05e463f4b48f0 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_link/__test__/create_or_update_custom_link.test.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_link/__test__/create_or_update_custom_link.test.ts @@ -20,15 +20,14 @@ describe('Create or Update Custom link', () => { } } as unknown) as Setup; - const customAction = ({ + const customLink = ({ label: 'foo', url: 'http://elastic.com/{{trace.id}}', filters: { 'service.name': 'opbeans-java', 'transaction.type': 'Request' - }, - actionId: 'trace' - } as unknown) as CustomAction; + } + } as unknown) as CustomLink; afterEach(() => { internalClientIndexMock.mockClear(); }); @@ -38,7 +37,7 @@ describe('Create or Update Custom link', () => { }); it('creates a new custom link', () => { - createOrUpdateCustomLink({ customAction, setup: mockedSetup }); + createOrUpdateCustomLink({ customLink, setup: mockedSetup }); expect(internalClientIndexMock).toHaveBeenCalledWith({ refresh: true, index: 'apmCustomLinkIndex', @@ -49,15 +48,14 @@ describe('Create or Update Custom link', () => { filters: { 'service.name': 'opbeans-java', 'transaction.type': 'Request' - }, - actionId: 'trace' + } } }); }); it('update a new custom link', () => { createOrUpdateCustomLink({ - customActionId: 'bar', - customAction, + customLinkId: 'bar', + customLink, setup: mockedSetup }); expect(internalClientIndexMock).toHaveBeenCalledWith({ @@ -71,8 +69,7 @@ describe('Create or Update Custom link', () => { filters: { 'service.name': 'opbeans-java', 'transaction.type': 'Request' - }, - actionId: 'trace' + } } }); }); diff --git a/x-pack/plugins/apm/server/lib/settings/custom_link/__test__/list_custom_links.test.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/__test__/list_custom_links.test.ts index 2e747844219c3..5466225dc3211 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_link/__test__/list_custom_links.test.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_link/__test__/list_custom_links.test.ts @@ -10,13 +10,32 @@ import { SearchParamsMock } from '../../../../../../../legacy/plugins/apm/public/utils/testHelpers'; import { Setup } from '../../../helpers/setup_request'; +import { + SERVICE_NAME, + TRANSACTION_NAME +} from '../../../../../common/elasticsearch_fieldnames'; describe('List Custom Links', () => { let mock: SearchParamsMock; - it('fetches custom links', async () => { + it('fetches all custom links', async () => { + mock = await inspectSearchParams(setup => + listCustomLinks({ + setup: (setup as unknown) as Setup + }) + ); + + expect(mock.params).toMatchSnapshot(); + }); + + it('filters custom links', async () => { + const filters = { + [SERVICE_NAME]: 'foo', + [TRANSACTION_NAME]: 'bar' + }; mock = await inspectSearchParams(setup => listCustomLinks({ + filters, setup: (setup as unknown) as Setup }) ); From 8e8bfc641e9d283074cd62c50c73bde624e58464 Mon Sep 17 00:00:00 2001 From: cauemarcondes <caue.marcondes@elastic.co> Date: Mon, 2 Mar 2020 17:09:36 +0100 Subject: [PATCH 32/42] adding unit tests --- .../CustomLinkFlyout/FiltersSection.tsx | 1 + .../CustomLinkFlyout/LinkSection.tsx | 1 + .../CustomLink/__test__/CustomLink.test.tsx | 169 ++++++++++++++---- .../plugins/apm/public/utils/testHelpers.tsx | 3 +- 4 files changed, 141 insertions(+), 33 deletions(-) diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/FiltersSection.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/FiltersSection.tsx index 2858e13d02182..4624935c011ef 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/FiltersSection.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/FiltersSection.tsx @@ -126,6 +126,7 @@ export const FiltersSection = ({ <EuiFlexGroup key={filterId} gutterSize="s" alignItems="center"> <EuiFlexItem> <EuiSelect + aria-label={filterId} id={filterId} fullWidth options={selectOptions} diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/LinkSection.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/LinkSection.tsx index 6dcc5d547928a..17884633355dc 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/LinkSection.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/LinkSection.tsx @@ -125,6 +125,7 @@ export const LinkSection = ({ fullWidth value={field.value} onChange={e => field.onChange(e.target.value)} + aria-label={field.name} /> </EuiFormRow> ); diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/__test__/CustomLink.test.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/__test__/CustomLink.test.tsx index 8140a21370288..df5a310d677ec 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/__test__/CustomLink.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/__test__/CustomLink.test.tsx @@ -4,19 +4,43 @@ * you may not use this file except in compliance with the Elastic License. */ -import { fireEvent, render } from '@testing-library/react'; +import { + fireEvent, + render, + wait, + waitForElement +} from '@testing-library/react'; import React from 'react'; import { act } from 'react-dom/test-utils'; import { CustomLinkOverview } from '../'; import * as hooks from '../../../../../../hooks/useFetcher'; +import * as callApmApi from '../../../../../../hooks/useCallApmApi'; import { expectTextsInDocument, MockApmPluginContextWrapper } from '../../../../../../utils/testHelpers'; +import * as saveCustomLink from '../CustomLinkFlyout/saveCustomLink'; -describe('CustomLink', () => { - afterEach(() => jest.restoreAllMocks()); +const data = [ + { + id: '1', + label: 'label 1', + url: 'url 1', + filters: { + 'service.name': 'opbeans-java' + } + }, + { + id: '2', + label: 'label 2', + url: 'url 2', + filters: { + 'transaction.type': 'request' + } + } +]; +describe('CustomLink', () => { describe('empty prompt', () => { beforeAll(() => { spyOn(hooks, 'useFetcher').and.returnValue({ @@ -49,28 +73,15 @@ describe('CustomLink', () => { describe('overview', () => { beforeAll(() => { spyOn(hooks, 'useFetcher').and.returnValue({ - data: [ - { - id: '1', - label: 'label 1', - url: 'url 1', - filters: { - 'service.name': 'opbeans-java' - } - }, - { - id: '2', - label: 'label 2', - url: 'url 2', - filters: { - 'transaction.type': 'request' - } - } - ], + data, status: 'success' }); }); + afterAll(() => { + jest.clearAllMocks(); + }); + it('shows a table with all custom link', () => { const component = render( <MockApmPluginContextWrapper> @@ -97,37 +108,78 @@ describe('CustomLink', () => { }); expect(queryByText('Create link')).toBeInTheDocument(); }); + }); - it('opens flyout to edit a custom link', () => { + describe('Flyout', () => { + const refetch = jest.fn(); + let callApmApiSpy: Function; + let saveCustomLinkSpy: Function; + beforeAll(() => { + callApmApiSpy = spyOn(callApmApi, 'useCallApmApi'); + saveCustomLinkSpy = spyOn(saveCustomLink, 'saveCustomLink'); + spyOn(hooks, 'useFetcher').and.returnValue({ + data, + status: 'success', + refetch + }); + }); + afterEach(() => { + jest.resetAllMocks(); + }); + + const openFlyout = () => { const component = render( <MockApmPluginContextWrapper> <CustomLinkOverview /> </MockApmPluginContextWrapper> ); expect(component.queryByText('Create link')).not.toBeInTheDocument(); - const editButtons = component.getAllByLabelText('Edit'); - expect(editButtons.length).toEqual(2); act(() => { - fireEvent.click(editButtons[0]); + fireEvent.click(component.getByText('Create custom link')); }); expect(component.queryByText('Create link')).toBeInTheDocument(); + return component; + }; + + it('creates a custom link', async () => { + const component = openFlyout(); + const labelInput = component.getByLabelText('label'); + act(() => { + fireEvent.change(labelInput, { + target: { value: 'foo' } + }); + }); + const urlInput = component.getByLabelText('url'); + act(() => { + fireEvent.change(urlInput, { + target: { value: 'bar' } + }); + }); + await act(async () => { + await wait(() => fireEvent.submit(component.getByText('Save'))); + }); + expect(saveCustomLinkSpy).toHaveBeenCalledTimes(1); }); - }); - describe('Flyout', () => { - const openFlyout = () => { + it('deletes a custom link', async () => { const component = render( <MockApmPluginContextWrapper> <CustomLinkOverview /> </MockApmPluginContextWrapper> ); expect(component.queryByText('Create link')).not.toBeInTheDocument(); + const editButtons = component.getAllByLabelText('Edit'); + expect(editButtons.length).toEqual(2); act(() => { - fireEvent.click(component.getByText('Create custom link')); + fireEvent.click(editButtons[0]); }); expect(component.queryByText('Create link')).toBeInTheDocument(); - return component; - }; + await act(async () => { + await wait(() => fireEvent.submit(component.getByText('Delete'))); + }); + expect(callApmApiSpy).toHaveBeenCalled(); + expect(refetch).toHaveBeenCalled(); + }); describe('Filters', () => { const addFilterField = ( @@ -146,10 +198,63 @@ describe('CustomLink', () => { addFilterField(component, 2); expect(component.getAllByText('service.name').length).toEqual(4); // After 4 items, the button is disabled - // Even adding a new filter, it still has only 4 items. addFilterField(component, 2); expect(component.getAllByText('service.name').length).toEqual(4); }); + it('removes items already selected', () => { + const component = openFlyout(); + + const addFieldAndCheck = ( + fieldName: string, + selectValue: string, + addNewFilter: boolean, + optionsExpected: string[] + ) => { + if (addNewFilter) { + addFilterField(component, 1); + } + const field = component.getByLabelText( + fieldName + ) as HTMLSelectElement; + const optionsAvailable = Object.values(field) + .map(option => (option as HTMLOptionElement).text) + .filter(option => option); + + act(() => { + fireEvent.change(field, { + target: { value: selectValue } + }); + }); + expect(field.value).toEqual(selectValue); + expect(optionsAvailable).toEqual(optionsExpected); + }; + + addFieldAndCheck('filter-0', 'transaction.name', false, [ + 'Select fields...', + 'service.name', + 'service.environment', + 'transaction.type', + 'transaction.name' + ]); + + addFieldAndCheck('filter-1', 'service.name', true, [ + 'Select fields...', + 'service.name', + 'service.environment', + 'transaction.type' + ]); + + addFieldAndCheck('filter-2', 'transaction.type', true, [ + 'Select fields...', + 'service.environment', + 'transaction.type' + ]); + + addFieldAndCheck('filter-3', 'service.environment', true, [ + 'Select fields...', + 'service.environment' + ]); + }); }); }); }); diff --git a/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx b/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx index fceaebe203e64..c743b26b639fe 100644 --- a/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx +++ b/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx @@ -196,7 +196,8 @@ const mockCore = { }, notifications: { toasts: { - addWarning: () => {} + addWarning: () => {}, + addDanger: () => {} } } }; From 5f4e9996a560c0dc8b2c9b0396426c7f309cd501 Mon Sep 17 00:00:00 2001 From: cauemarcondes <caue.marcondes@elastic.co> Date: Tue, 3 Mar 2020 13:19:39 +0100 Subject: [PATCH 33/42] refactoring callApmApi --- .../app/Home/__snapshots__/Home.test.tsx.snap | 2 ++ .../public/components/app/ServiceMap/index.tsx | 5 ++--- .../AddEditFlyout/DeleteButton.tsx | 7 ++----- .../AddEditFlyout/index.tsx | 4 ---- .../AddEditFlyout/saveConfig.ts | 4 +--- .../app/Settings/ApmIndices/index.tsx | 18 ++++++------------ .../CustomLinkFlyout/DeleteButton.tsx | 7 ++----- .../CustomLink/CustomLinkFlyout/index.tsx | 3 --- .../CustomLinkFlyout/saveCustomLink.ts | 4 +--- .../CustomLink/__test__/CustomLink.test.tsx | 15 ++++++--------- .../plugins/apm/public/hooks/useFetcher.tsx | 5 +---- .../plugins/apm/public/new-platform/plugin.tsx | 6 ++++-- .../services/__test__/callApmApi.test.ts | 5 ++--- .../public/services/rest/createCallApmApi.ts | 11 +++++++++-- .../apm/public/services/rest/index_pattern.ts | 6 ++---- .../plugins/apm/public/services/rest/ml.ts | 3 +-- .../plugins/apm/public/utils/testHelpers.tsx | 5 +++++ 17 files changed, 46 insertions(+), 64 deletions(-) diff --git a/x-pack/legacy/plugins/apm/public/components/app/Home/__snapshots__/Home.test.tsx.snap b/x-pack/legacy/plugins/apm/public/components/app/Home/__snapshots__/Home.test.tsx.snap index 7809734dbf2ad..d5764001a7f18 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Home/__snapshots__/Home.test.tsx.snap +++ b/x-pack/legacy/plugins/apm/public/components/app/Home/__snapshots__/Home.test.tsx.snap @@ -22,6 +22,7 @@ exports[`Home component should render services 1`] = ` }, "notifications": Object { "toasts": Object { + "addDanger": [Function], "addWarning": [Function], }, }, @@ -61,6 +62,7 @@ exports[`Home component should render traces 1`] = ` }, "notifications": Object { "toasts": Object { + "addDanger": [Function], "addWarning": [Function], }, }, diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/index.tsx index d5f0728a7ff12..9a93c67f08187 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/index.tsx @@ -21,7 +21,6 @@ import { isValidPlatinumLicense } from '../../../../../../../plugins/apm/common/ // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { ServiceMapAPIResponse } from '../../../../../../../plugins/apm/server/lib/service_map/get_service_map'; import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; -import { useCallApmApi } from '../../../hooks/useCallApmApi'; import { useDeepObjectIdentity } from '../../../hooks/useDeepObjectIdentity'; import { useLicense } from '../../../hooks/useLicense'; import { useLoadingIndicator } from '../../../hooks/useLoadingIndicator'; @@ -33,6 +32,7 @@ import { getCytoscapeElements } from './get_cytoscape_elements'; import { PlatinumLicensePrompt } from './PlatinumLicensePrompt'; import { Popover } from './Popover'; import { useRefDimensions } from './useRefDimensions'; +import { callApmApi } from '../../../services/rest/createCallApmApi'; interface ServiceMapProps { serviceName?: string; @@ -61,7 +61,6 @@ ${theme.euiColorLightShade}`, const MAX_REQUESTS = 5; export function ServiceMap({ serviceName }: ServiceMapProps) { - const callApmApi = useCallApmApi(); const license = useLicense(); const { search } = useLocation(); const { urlParams, uiFilters } = useUrlParams(); @@ -137,7 +136,7 @@ export function ServiceMap({ serviceName }: ServiceMapProps) { } } }, - [params, setIsLoading, callApmApi, responses.length, notifications.toasts] + [params, setIsLoading, responses.length, notifications.toasts] ); useEffect(() => { diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AddEditFlyout/DeleteButton.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AddEditFlyout/DeleteButton.tsx index 1564f1ae746a9..997df371b51ed 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AddEditFlyout/DeleteButton.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AddEditFlyout/DeleteButton.tsx @@ -8,10 +8,9 @@ import React, { useState } from 'react'; import { EuiButtonEmpty } from '@elastic/eui'; import { NotificationsStart } from 'kibana/public'; import { i18n } from '@kbn/i18n'; -import { useCallApmApi } from '../../../../../hooks/useCallApmApi'; import { Config } from '../index'; import { getOptionLabel } from '../../../../../../../../../plugins/apm/common/agent_configuration_constants'; -import { APMClient } from '../../../../../services/rest/createCallApmApi'; +import { callApmApi } from '../../../../../services/rest/createCallApmApi'; import { useApmPluginContext } from '../../../../../hooks/useApmPluginContext'; interface Props { @@ -22,7 +21,6 @@ interface Props { export function DeleteButton({ onDeleted, selectedConfig }: Props) { const [isDeleting, setIsDeleting] = useState(false); const { toasts } = useApmPluginContext().core.notifications; - const callApmApi = useCallApmApi(); return ( <EuiButtonEmpty @@ -31,7 +29,7 @@ export function DeleteButton({ onDeleted, selectedConfig }: Props) { iconSide="right" onClick={async () => { setIsDeleting(true); - await deleteConfig(callApmApi, selectedConfig, toasts); + await deleteConfig(selectedConfig, toasts); setIsDeleting(false); onDeleted(); }} @@ -45,7 +43,6 @@ export function DeleteButton({ onDeleted, selectedConfig }: Props) { } async function deleteConfig( - callApmApi: APMClient, selectedConfig: Config, toasts: NotificationsStart['toasts'] ) { diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AddEditFlyout/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AddEditFlyout/index.tsx index 041c388cb9782..a034ca543390f 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AddEditFlyout/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AddEditFlyout/index.tsx @@ -22,7 +22,6 @@ import { import React, { useState } from 'react'; import { i18n } from '@kbn/i18n'; import { isRight } from 'fp-ts/lib/Either'; -import { useCallApmApi } from '../../../../../hooks/useCallApmApi'; import { transactionSampleRateRt } from '../../../../../../../../../plugins/apm/common/runtime_types/transaction_sample_rate_rt'; import { Config } from '../index'; import { SettingsSection } from './SettingsSection'; @@ -58,8 +57,6 @@ export function AddEditFlyout({ const { toasts } = useApmPluginContext().core.notifications; const [isSaving, setIsSaving] = useState(false); - const callApmApiFromHook = useCallApmApi(); - // get a telemetry UI event tracker const trackApmEvent = useUiTracker({ app: 'apm' }); @@ -129,7 +126,6 @@ export function AddEditFlyout({ setIsSaving(true); await saveConfig({ - callApmApi: callApmApiFromHook, serviceName, environment, sampleRate, diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AddEditFlyout/saveConfig.ts b/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AddEditFlyout/saveConfig.ts index d36120a054795..229394cb5da8c 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AddEditFlyout/saveConfig.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AddEditFlyout/saveConfig.ts @@ -6,7 +6,7 @@ import { i18n } from '@kbn/i18n'; import { NotificationsStart } from 'kibana/public'; -import { APMClient } from '../../../../../services/rest/createCallApmApi'; +import { callApmApi } from '../../../../../services/rest/createCallApmApi'; import { isRumAgentName } from '../../../../../../../../../plugins/apm/common/agent_name'; import { getOptionLabel, @@ -21,7 +21,6 @@ interface Settings { } export async function saveConfig({ - callApmApi, serviceName, environment, sampleRate, @@ -32,7 +31,6 @@ export async function saveConfig({ toasts, trackApmEvent }: { - callApmApi: APMClient; serviceName: string; environment: string; sampleRate: string; diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx index 14670fe1885f6..882968eb25591 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx @@ -20,8 +20,7 @@ import { EuiButtonEmpty } from '@elastic/eui'; import { useFetcher } from '../../../../hooks/useFetcher'; -import { useCallApmApi } from '../../../../hooks/useCallApmApi'; -import { APMClient } from '../../../../services/rest/createCallApmApi'; +import { callApmApi } from '../../../../services/rest/createCallApmApi'; import { clearCache } from '../../../../services/rest/callApi'; import { useApmPluginContext } from '../../../../hooks/useApmPluginContext'; @@ -68,10 +67,8 @@ const APM_INDEX_LABELS = [ ]; async function saveApmIndices({ - callApmApi, apmIndices }: { - callApmApi: APMClient; apmIndices: Record<string, string>; }) { await callApmApi({ @@ -94,11 +91,11 @@ export function ApmIndices() { const [apmIndices, setApmIndices] = useState<Record<string, string>>({}); const [isSaving, setIsSaving] = useState(false); - const callApmApiFromHook = useCallApmApi(); - const { data = INITIAL_STATE, status, refetch } = useFetcher( - callApmApi => - callApmApi({ pathname: `/api/apm/settings/apm-index-settings` }), + callApmApiFromFetcher => + callApmApiFromFetcher({ + pathname: `/api/apm/settings/apm-index-settings` + }), [] ); @@ -122,10 +119,7 @@ export function ApmIndices() { event.preventDefault(); setIsSaving(true); try { - await saveApmIndices({ - callApmApi: callApmApiFromHook, - apmIndices - }); + await saveApmIndices({ apmIndices }); toasts.addSuccess({ title: i18n.translate( 'xpack.apm.settings.apmIndices.applyChanges.succeeded.title', diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/DeleteButton.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/DeleteButton.tsx index d8b55974e1a83..84b923fbf815c 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/DeleteButton.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/DeleteButton.tsx @@ -8,8 +8,7 @@ import { EuiButtonEmpty } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { NotificationsStart } from 'kibana/public'; import React, { useState } from 'react'; -import { useCallApmApi } from '../../../../../../hooks/useCallApmApi'; -import { APMClient } from '../../../../../../services/rest/createCallApmApi'; +import { callApmApi } from '../../../../../../services/rest/createCallApmApi'; import { useApmPluginContext } from '../../../../../../hooks/useApmPluginContext'; interface Props { @@ -20,7 +19,6 @@ interface Props { export function DeleteButton({ onDelete, customLinkId }: Props) { const [isDeleting, setIsDeleting] = useState(false); const { toasts } = useApmPluginContext().core.notifications; - const callApmApi = useCallApmApi(); return ( <EuiButtonEmpty @@ -29,7 +27,7 @@ export function DeleteButton({ onDelete, customLinkId }: Props) { iconSide="right" onClick={async () => { setIsDeleting(true); - await deleteConfig(callApmApi, customLinkId, toasts); + await deleteConfig(customLinkId, toasts); setIsDeleting(false); onDelete(); }} @@ -42,7 +40,6 @@ export function DeleteButton({ onDelete, customLinkId }: Props) { } async function deleteConfig( - callApmApi: APMClient, customLinkId: string, toasts: NotificationsStart['toasts'] ) { diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/index.tsx index d08f1f724f4f6..09fc32a5c38a0 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/index.tsx @@ -17,7 +17,6 @@ import { isEmpty } from 'lodash'; import React, { useState } from 'react'; import { CustomLink } from '../../../../../../../../../../plugins/apm/server/lib/settings/custom_link/custom_link_types'; import { useApmPluginContext } from '../../../../../../hooks/useApmPluginContext'; -import { useCallApmApi } from '../../../../../../hooks/useCallApmApi'; import { LinkSection } from './LinkSection'; import { FiltersSection } from './FiltersSection'; import { FlyoutFooter } from './FlyoutFooter'; @@ -58,7 +57,6 @@ export const CustomLinkFlyout = ({ onSave, onDelete }: Props) => { - const callApmApiFromHook = useCallApmApi(); const { toasts } = useApmPluginContext().core.notifications; const [isSaving, setIsSaving] = useState(false); @@ -83,7 +81,6 @@ export const CustomLinkFlyout = ({ label, url, filters: convertFiltersToObject(filters), - callApmApi: callApmApiFromHook, toasts }); setIsSaving(false); diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/saveCustomLink.ts b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/saveCustomLink.ts index 34269c6a6251c..3036e161c4d52 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/saveCustomLink.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/saveCustomLink.ts @@ -7,21 +7,19 @@ import { i18n } from '@kbn/i18n'; import { NotificationsStart } from 'kibana/public'; import { CustomLink } from '../../../../../../../../../../plugins/apm/server/lib/settings/custom_link/custom_link_types'; -import { APMClient } from '../../../../../../services/rest/createCallApmApi'; +import { callApmApi } from '../../../../../../services/rest/createCallApmApi'; export async function saveCustomLink({ id, label, url, filters, - callApmApi, toasts }: { id?: string; label: string; url: string; filters?: CustomLink['filters']; - callApmApi: APMClient; toasts: NotificationsStart['toasts']; }) { try { diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/__test__/CustomLink.test.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/__test__/CustomLink.test.tsx index df5a310d677ec..5e5e49f2b4ed0 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/__test__/CustomLink.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/__test__/CustomLink.test.tsx @@ -4,22 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - fireEvent, - render, - wait, - waitForElement -} from '@testing-library/react'; +import { fireEvent, render, wait } from '@testing-library/react'; import React from 'react'; import { act } from 'react-dom/test-utils'; import { CustomLinkOverview } from '../'; import * as hooks from '../../../../../../hooks/useFetcher'; -import * as callApmApi from '../../../../../../hooks/useCallApmApi'; +// import * as callApmApi from '../../../../../../hooks/useCallApmApi'; + import { expectTextsInDocument, MockApmPluginContextWrapper } from '../../../../../../utils/testHelpers'; import * as saveCustomLink from '../CustomLinkFlyout/saveCustomLink'; +import * as apmApi from '../../../../../../services/rest/createCallApmApi'; const data = [ { @@ -115,7 +112,7 @@ describe('CustomLink', () => { let callApmApiSpy: Function; let saveCustomLinkSpy: Function; beforeAll(() => { - callApmApiSpy = spyOn(callApmApi, 'useCallApmApi'); + callApmApiSpy = spyOn(apmApi, 'callApmApi'); saveCustomLinkSpy = spyOn(saveCustomLink, 'saveCustomLink'); spyOn(hooks, 'useFetcher').and.returnValue({ data, @@ -175,7 +172,7 @@ describe('CustomLink', () => { }); expect(component.queryByText('Create link')).toBeInTheDocument(); await act(async () => { - await wait(() => fireEvent.submit(component.getByText('Delete'))); + await wait(() => fireEvent.click(component.getByText('Delete'))); }); expect(callApmApiSpy).toHaveBeenCalled(); expect(refetch).toHaveBeenCalled(); diff --git a/x-pack/legacy/plugins/apm/public/hooks/useFetcher.tsx b/x-pack/legacy/plugins/apm/public/hooks/useFetcher.tsx index d2202fff996b1..c2530d6982c3b 100644 --- a/x-pack/legacy/plugins/apm/public/hooks/useFetcher.tsx +++ b/x-pack/legacy/plugins/apm/public/hooks/useFetcher.tsx @@ -9,8 +9,7 @@ import { i18n } from '@kbn/i18n'; import { IHttpFetchError } from 'src/core/public'; import { toMountPoint } from '../../../../../../src/plugins/kibana_react/public'; import { LoadingIndicatorContext } from '../context/LoadingIndicatorContext'; -import { APMClient } from '../services/rest/createCallApmApi'; -import { useCallApmApi } from './useCallApmApi'; +import { APMClient, callApmApi } from '../services/rest/createCallApmApi'; import { useApmPluginContext } from './useApmPluginContext'; import { useLoadingIndicator } from './useLoadingIndicator'; @@ -46,8 +45,6 @@ export function useFetcher<TReturn>( const { preservePreviousData = true } = options; const { setIsLoading } = useLoadingIndicator(); - const callApmApi = useCallApmApi(); - const { dispatchStatus } = useContext(LoadingIndicatorContext); const [result, setResult] = useState<Result<InferResponseType<TReturn>>>({ data: undefined, diff --git a/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx b/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx index 0054f963ba8f2..55eabfcc26e18 100644 --- a/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx +++ b/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useMemo } from 'react'; import ReactDOM from 'react-dom'; import { Route, Router, Switch } from 'react-router-dom'; import { ApmRoute } from '@elastic/apm-rum-react'; @@ -39,6 +39,7 @@ import { toggleAppLinkInNav } from './toggleAppLinkInNav'; import { setReadonlyBadge } from './updateBadge'; import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; import { APMIndicesPermission } from '../components/app/APMIndicesPermission'; +import { createCallApmApi } from '../services/rest/createCallApmApi'; export const REACT_APP_ROOT_ID = 'react-apm-root'; @@ -104,6 +105,7 @@ export class ApmPlugin public start(core: CoreStart) { const i18nCore = core.i18n; const plugins = this.setupPlugins; + createCallApmApi(core.http); // Once we're actually an NP plugin we'll get the config from the // initializerContext like: @@ -157,7 +159,7 @@ export class ApmPlugin ); // create static index pattern and store as saved object. Not needed by APM UI but for legacy reasons in Discover, Dashboard etc. - createStaticIndexPattern(core.http).catch(e => { + createStaticIndexPattern().catch(e => { // eslint-disable-next-line no-console console.log('Error fetching static index pattern', e); }); diff --git a/x-pack/legacy/plugins/apm/public/services/__test__/callApmApi.test.ts b/x-pack/legacy/plugins/apm/public/services/__test__/callApmApi.test.ts index 9cca9469bba0e..2d4fd83003179 100644 --- a/x-pack/legacy/plugins/apm/public/services/__test__/callApmApi.test.ts +++ b/x-pack/legacy/plugins/apm/public/services/__test__/callApmApi.test.ts @@ -5,7 +5,7 @@ */ import * as callApiExports from '../rest/callApi'; -import { createCallApmApi, APMClient } from '../rest/createCallApmApi'; +import { createCallApmApi, callApmApi } from '../rest/createCallApmApi'; import { HttpSetup } from 'kibana/public'; const callApi = jest @@ -13,9 +13,8 @@ const callApi = jest .mockImplementation(() => Promise.resolve(null)); describe('callApmApi', () => { - let callApmApi: APMClient; beforeEach(() => { - callApmApi = createCallApmApi({} as HttpSetup); + createCallApmApi({} as HttpSetup); }); afterEach(() => { diff --git a/x-pack/legacy/plugins/apm/public/services/rest/createCallApmApi.ts b/x-pack/legacy/plugins/apm/public/services/rest/createCallApmApi.ts index 220320216788a..2fffb40d353fc 100644 --- a/x-pack/legacy/plugins/apm/public/services/rest/createCallApmApi.ts +++ b/x-pack/legacy/plugins/apm/public/services/rest/createCallApmApi.ts @@ -19,8 +19,14 @@ export type APMClientOptions = Omit<FetchOptions, 'query' | 'body'> & { }; }; -export const createCallApmApi = (http: HttpSetup) => - ((options: APMClientOptions) => { +export let callApmApi: APMClient = () => { + throw new Error( + 'callApmApi has to be initialized before used. Call createCallApmApi first.' + ); +}; + +export function createCallApmApi(http: HttpSetup) { + callApmApi = ((options: APMClientOptions) => { const { pathname, params = {}, ...opts } = options; const path = (params.path || {}) as Record<string, any>; @@ -36,3 +42,4 @@ export const createCallApmApi = (http: HttpSetup) => query: params.query }); }) as APMClient; +} diff --git a/x-pack/legacy/plugins/apm/public/services/rest/index_pattern.ts b/x-pack/legacy/plugins/apm/public/services/rest/index_pattern.ts index 8e1234dd55e69..1efcc98bbbd66 100644 --- a/x-pack/legacy/plugins/apm/public/services/rest/index_pattern.ts +++ b/x-pack/legacy/plugins/apm/public/services/rest/index_pattern.ts @@ -4,11 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { HttpSetup } from 'kibana/public'; -import { createCallApmApi } from './createCallApmApi'; +import { callApmApi } from './createCallApmApi'; -export const createStaticIndexPattern = async (http: HttpSetup) => { - const callApmApi = createCallApmApi(http); +export const createStaticIndexPattern = async () => { return await callApmApi({ method: 'POST', pathname: '/api/apm/index_pattern/static' diff --git a/x-pack/legacy/plugins/apm/public/services/rest/ml.ts b/x-pack/legacy/plugins/apm/public/services/rest/ml.ts index 5e64d7e1ce716..1c618098b36e3 100644 --- a/x-pack/legacy/plugins/apm/public/services/rest/ml.ts +++ b/x-pack/legacy/plugins/apm/public/services/rest/ml.ts @@ -16,7 +16,7 @@ import { } from '../../../../../../plugins/apm/common/ml_job_constants'; import { callApi } from './callApi'; import { ESFilter } from '../../../../../../plugins/apm/typings/elasticsearch'; -import { createCallApmApi, APMClient } from './createCallApmApi'; +import { callApmApi } from './createCallApmApi'; interface MlResponseItem { id: string; @@ -36,7 +36,6 @@ interface StartedMLJobApiResponse { } async function getTransactionIndices(http: HttpSetup) { - const callApmApi: APMClient = createCallApmApi(http); const indices = await callApmApi({ method: 'GET', pathname: `/api/apm/settings/apm-indices` diff --git a/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx b/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx index c743b26b639fe..d011aa28a3836 100644 --- a/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx +++ b/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx @@ -15,6 +15,7 @@ import React, { ReactNode } from 'react'; import { render, waitForElement } from '@testing-library/react'; import '@testing-library/jest-dom/extend-expect'; import { MemoryRouter } from 'react-router-dom'; +import { HttpSetup } from 'kibana/public'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { APMConfig } from '../../../../../plugins/apm/server'; import { LocationProvider } from '../context/LocationContext'; @@ -29,6 +30,7 @@ import { ApmPluginContextValue } from '../context/ApmPluginContext'; import { ConfigSchema } from '../new-platform/plugin'; +import { createCallApmApi } from '../services/rest/createCallApmApi'; export function toJson(wrapper: ReactWrapper) { return enzymeToJson(wrapper, { @@ -224,6 +226,9 @@ export function MockApmPluginContextWrapper({ children?: ReactNode; value?: ApmPluginContextValue; }) { + if (value.core?.http) { + createCallApmApi(value.core?.http); + } return ( <ApmPluginContext.Provider value={{ From 5f17093b5e046a9b3782466670237c807b847d19 Mon Sep 17 00:00:00 2001 From: cauemarcondes <caue.marcondes@elastic.co> Date: Tue, 3 Mar 2020 13:23:55 +0100 Subject: [PATCH 34/42] removing useCallApmApi hook --- .../CustomLink/__test__/CustomLink.test.tsx | 2 -- .../plugins/apm/public/hooks/useCallApmApi.ts | 17 ----------------- .../plugins/apm/public/new-platform/plugin.tsx | 2 +- 3 files changed, 1 insertion(+), 20 deletions(-) delete mode 100644 x-pack/legacy/plugins/apm/public/hooks/useCallApmApi.ts diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/__test__/CustomLink.test.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/__test__/CustomLink.test.tsx index 5e5e49f2b4ed0..cabadc9d6ee4a 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/__test__/CustomLink.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/__test__/CustomLink.test.tsx @@ -9,8 +9,6 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import { CustomLinkOverview } from '../'; import * as hooks from '../../../../../../hooks/useFetcher'; -// import * as callApmApi from '../../../../../../hooks/useCallApmApi'; - import { expectTextsInDocument, MockApmPluginContextWrapper diff --git a/x-pack/legacy/plugins/apm/public/hooks/useCallApmApi.ts b/x-pack/legacy/plugins/apm/public/hooks/useCallApmApi.ts deleted file mode 100644 index b28b295d8189e..0000000000000 --- a/x-pack/legacy/plugins/apm/public/hooks/useCallApmApi.ts +++ /dev/null @@ -1,17 +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 { useMemo } from 'react'; -import { createCallApmApi } from '../services/rest/createCallApmApi'; -import { useApmPluginContext } from './useApmPluginContext'; - -export function useCallApmApi() { - const { http } = useApmPluginContext().core; - - return useMemo(() => { - return createCallApmApi(http); - }, [http]); -} diff --git a/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx b/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx index 55eabfcc26e18..0103dd72a3fea 100644 --- a/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx +++ b/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useMemo } from 'react'; +import React from 'react'; import ReactDOM from 'react-dom'; import { Route, Router, Switch } from 'react-router-dom'; import { ApmRoute } from '@elastic/apm-rum-react'; From f045527f089938d3bcf627aa65d6a4fb4e969138 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Wed, 4 Mar 2020 11:32:40 +0100 Subject: [PATCH 35/42] Rename Flyoutfooter.tsx to FlyoutFooter.tsx --- .../CustomLinkFlyout/{Flyoutfooter.tsx => FlyoutFooter.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/{Flyoutfooter.tsx => FlyoutFooter.tsx} (100%) diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/Flyoutfooter.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/FlyoutFooter.tsx similarity index 100% rename from x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/Flyoutfooter.tsx rename to x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/FlyoutFooter.tsx From 0059e2531697be4a8508a68382563f6201ec3b85 Mon Sep 17 00:00:00 2001 From: cauemarcondes <caue.marcondes@elastic.co> Date: Wed, 4 Mar 2020 11:34:33 +0100 Subject: [PATCH 36/42] removing unused import --- x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx b/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx index d011aa28a3836..9db7eaf4655a5 100644 --- a/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx +++ b/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx @@ -15,7 +15,6 @@ import React, { ReactNode } from 'react'; import { render, waitForElement } from '@testing-library/react'; import '@testing-library/jest-dom/extend-expect'; import { MemoryRouter } from 'react-router-dom'; -import { HttpSetup } from 'kibana/public'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { APMConfig } from '../../../../../plugins/apm/server'; import { LocationProvider } from '../context/LocationContext'; From de2b225da3e4473cb68aaed5f079480e75d0f53b Mon Sep 17 00:00:00 2001 From: cauemarcondes <caue.marcondes@elastic.co> Date: Wed, 4 Mar 2020 13:32:37 +0100 Subject: [PATCH 37/42] fixing typescript errors --- x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx | 1 + .../lib/errors/distribution/__tests__/get_buckets.test.ts | 3 ++- .../apm/server/lib/settings/custom_link/list_custom_links.ts | 4 ++-- .../plugins/apm/server/lib/transaction_groups/fetcher.test.ts | 3 ++- .../apm/server/lib/transactions/breakdown/index.test.ts | 3 ++- .../lib/transactions/charts/get_anomaly_data/index.test.ts | 3 ++- .../transactions/charts/get_timeseries_data/fetcher.test.ts | 3 ++- 7 files changed, 13 insertions(+), 7 deletions(-) diff --git a/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx b/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx index 9db7eaf4655a5..4ee45f7b3330b 100644 --- a/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx +++ b/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx @@ -119,6 +119,7 @@ interface MockSetup { 'apm_oss.transactionIndices': string; 'apm_oss.metricsIndices': string; apmAgentConfigurationIndex: string; + apmCustomLinkIndex: string; }; } diff --git a/x-pack/plugins/apm/server/lib/errors/distribution/__tests__/get_buckets.test.ts b/x-pack/plugins/apm/server/lib/errors/distribution/__tests__/get_buckets.test.ts index 3ac47004279b3..ef1934235807b 100644 --- a/x-pack/plugins/apm/server/lib/errors/distribution/__tests__/get_buckets.test.ts +++ b/x-pack/plugins/apm/server/lib/errors/distribution/__tests__/get_buckets.test.ts @@ -53,7 +53,8 @@ describe('timeseriesFetcher', () => { 'apm_oss.spanIndices': 'apm-*', 'apm_oss.transactionIndices': 'apm-*', 'apm_oss.metricsIndices': 'apm-*', - apmAgentConfigurationIndex: '.apm-agent-configuration' + apmAgentConfigurationIndex: '.apm-agent-configuration', + apmCustomLinkIndex: '.apm-custom-link' }, dynamicIndexPattern: null as any } diff --git a/x-pack/plugins/apm/server/lib/settings/custom_link/list_custom_links.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/list_custom_links.ts index bfed051bdc9a1..6ad465d57781c 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_link/list_custom_links.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_link/list_custom_links.ts @@ -13,7 +13,7 @@ import { SERVICE_ENVIRONMENT } from '../../../../common/elasticsearch_fieldnames'; import { Setup } from '../../helpers/setup_request'; -import { CustomAction } from './custom_link_types'; +import { CustomLink } from './custom_link_types'; export const FilterOptions = t.partial({ [SERVICE_NAME]: t.string, @@ -57,7 +57,7 @@ export async function listCustomLinks({ } } }; - const resp = await internalClient.search<CustomAction>(params); + const resp = await internalClient.search<CustomLink>(params); return resp.hits.hits.map(item => ({ id: item._id, ...item._source diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.test.ts b/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.test.ts index c4a0be0f48c14..02bf60d3605bd 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.test.ts +++ b/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.test.ts @@ -28,7 +28,8 @@ function getSetup() { 'apm_oss.spanIndices': 'myIndex', 'apm_oss.transactionIndices': 'myIndex', 'apm_oss.metricsIndices': 'myIndex', - apmAgentConfigurationIndex: 'myIndex' + apmAgentConfigurationIndex: 'myIndex', + apmCustomLinkIndex: 'myIndex' }, dynamicIndexPattern: null as any }; diff --git a/x-pack/plugins/apm/server/lib/transactions/breakdown/index.test.ts b/x-pack/plugins/apm/server/lib/transactions/breakdown/index.test.ts index 9ab31be9f7219..5e443b92aa91a 100644 --- a/x-pack/plugins/apm/server/lib/transactions/breakdown/index.test.ts +++ b/x-pack/plugins/apm/server/lib/transactions/breakdown/index.test.ts @@ -17,7 +17,8 @@ const mockIndices = { 'apm_oss.spanIndices': 'myIndex', 'apm_oss.transactionIndices': 'myIndex', 'apm_oss.metricsIndices': 'myIndex', - apmAgentConfigurationIndex: 'myIndex' + apmAgentConfigurationIndex: 'myIndex', + apmCustomLinkIndex: 'myIndex' }; function getMockSetup(esResponse: any) { diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.test.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.test.ts index cc8fabe33e63d..7a3277965ef8e 100644 --- a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.test.ts +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.test.ts @@ -42,7 +42,8 @@ describe('getAnomalySeries', () => { 'apm_oss.spanIndices': 'myIndex', 'apm_oss.transactionIndices': 'myIndex', 'apm_oss.metricsIndices': 'myIndex', - apmAgentConfigurationIndex: 'myIndex' + apmAgentConfigurationIndex: 'myIndex', + apmCustomLinkIndex: 'myIndex' }, dynamicIndexPattern: null as any } diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.test.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.test.ts index 1970e39a2752e..a87a277eb0c0e 100644 --- a/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.test.ts +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.test.ts @@ -41,7 +41,8 @@ describe('timeseriesFetcher', () => { 'apm_oss.spanIndices': 'myIndex', 'apm_oss.transactionIndices': 'myIndex', 'apm_oss.metricsIndices': 'myIndex', - apmAgentConfigurationIndex: 'myIndex' + apmAgentConfigurationIndex: 'myIndex', + apmCustomLinkIndex: 'myIndex' }, dynamicIndexPattern: null as any } From 78c2cfb2b3239b10727175a0ed181ec5eb9e8380 Mon Sep 17 00:00:00 2001 From: cauemarcondes <caue.marcondes@elastic.co> Date: Wed, 4 Mar 2020 14:54:34 +0100 Subject: [PATCH 38/42] fixing duplicate messages --- .../CustomLink/CustomLinkFlyout/saveCustomLink.ts | 2 +- .../Settings/CustomizeUI/CustomLink/CustomLinkTable.tsx | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/saveCustomLink.ts b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/saveCustomLink.ts index 3036e161c4d52..e6b12a7fa75f1 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/saveCustomLink.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/saveCustomLink.ts @@ -60,7 +60,7 @@ export async function saveCustomLink({ { defaultMessage: 'Link could not be saved!' } ), text: i18n.translate( - 'xpack.apm.settings.customizeUI.customLink.create.failed', + 'xpack.apm.settings.customizeUI.customLink.create.failed.message', { defaultMessage: 'Something went wrong when saving the link. Error: "{errorMessage}"', diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkTable.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkTable.tsx index ab6503bf3bd78..ced6f437ded59 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkTable.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkTable.tsx @@ -100,9 +100,12 @@ export const CustomLinkTable = ({ <EuiFieldSearch fullWidth onChange={e => setSearchTerm(e.target.value)} - placeholder={i18n.translate('xpack.apm.searchInput.filter', { - defaultMessage: 'Filter links by Name or URL...' - })} + placeholder={i18n.translate( + 'xpack.apm.settings.customizeUI.customLink.searchInput.filter', + { + defaultMessage: 'Filter links by Name or URL...' + } + )} /> <EuiSpacer size="s" /> <ManagedTable From 0ef45143d244316d0cb530a8125f668feffce215 Mon Sep 17 00:00:00 2001 From: cauemarcondes <caue.marcondes@elastic.co> Date: Thu, 5 Mar 2020 13:53:03 +0100 Subject: [PATCH 39/42] removing filters --- .../CustomLinkFlyout/FiltersSection.tsx | 60 +++------- .../CustomLinkFlyout/LinkSection.tsx | 4 +- .../CustomLink/CustomLinkFlyout/helper.ts | 107 ++++++++++++++++++ .../CustomLink/CustomLinkFlyout/index.tsx | 29 +---- .../CustomLinkFlyout/saveCustomLink.ts | 3 +- .../CustomLink/__test__/CustomLink.test.tsx | 8 +- .../list_custom_links.test.ts.snap | 8 +- .../create_or_update_custom_link.test.ts | 18 +-- .../custom_link/create_custom_link_index.ts | 23 +++- .../create_or_update_custom_link.ts | 11 +- .../custom_link/custom_link_types.d.ts | 11 +- .../settings/custom_link/list_custom_links.ts | 24 +--- .../apm/server/routes/settings/custom_link.ts | 22 ++-- 13 files changed, 193 insertions(+), 135 deletions(-) create mode 100644 x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/helper.ts diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/FiltersSection.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/FiltersSection.tsx index 4624935c011ef..1139f85242a7d 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/FiltersSection.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/FiltersSection.tsx @@ -16,59 +16,24 @@ import { import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; import React, { useRef } from 'react'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { FilterOptionsType } from '../../../../../../../../../../plugins/apm/server/lib/settings/custom_link/list_custom_links'; +import { FilterOptionsType } from '../../../../../../../../../../plugins/apm/server/lib/settings/custom_link/custom_link_types'; import { - SERVICE_NAME, - SERVICE_ENVIRONMENT, - TRANSACTION_NAME, - TRANSACTION_TYPE -} from '../../../../../../../../../../plugins/apm/common/elasticsearch_fieldnames'; -import { CustomLinkFormData } from '.'; - -type FiltersType = CustomLinkFormData['filters']; - -interface FilterOption { - value: 'DEFAULT' | keyof FilterOptionsType; - text: string; -} - -const DEFAULT_OPTION: FilterOption = { - value: 'DEFAULT', - text: i18n.translate( - 'xpack.apm.settings.customizeUI.customLink.flyOut.filters.defaultOption', - { defaultMessage: 'Select fields...' } - ) -}; - -const filterOptions: FilterOption[] = [ + getSelectOptions, DEFAULT_OPTION, - { value: SERVICE_NAME, text: SERVICE_NAME }, - { value: SERVICE_ENVIRONMENT, text: SERVICE_ENVIRONMENT }, - { value: TRANSACTION_TYPE, text: TRANSACTION_TYPE }, - { value: TRANSACTION_NAME, text: TRANSACTION_NAME } -]; - -const getSelectOptions = (filters: FiltersType, idx: number) => { - return filterOptions.filter(option => { - const indexUsedFilter = filters.findIndex( - filter => filter[0] === option.value - ); - // Filter out all items already added, besides the one selected in the current filter. - return indexUsedFilter === -1 || idx === indexUsedFilter; - }); -}; + filterSelectOptions, + Filters +} from './helper'; export const FiltersSection = ({ filters, onChangeFilters }: { - filters: FiltersType; - onChangeFilters: (filters: FiltersType) => void; + filters: Filters; + onChangeFilters: (filters: Filters) => void; }) => { const filterValueRefs = useRef<HTMLInputElement[]>([]); - const onChangeFilter = (filter: FiltersType[0], idx: number) => { + const onChangeFilter = (filter: Filters[0], idx: number) => { if (filterValueRefs.current[idx]) { filterValueRefs.current[idx].focus(); } @@ -137,7 +102,12 @@ export const FiltersSection = ({ defaultMessage: 'Field' } )} - onChange={e => onChangeFilter([e.target.value, value], idx)} + onChange={e => + onChangeFilter( + [e.target.value as keyof FilterOptionsType, value], + idx + ) + } isInvalid={ !isEmpty(value) && (isEmpty(key) || key === DEFAULT_OPTION.value) @@ -179,7 +149,7 @@ export const FiltersSection = ({ <AddFilterButton onClick={handleAddFilter} // Disable button when user has already added all items available - isDisabled={filters.length === filterOptions.length - 1} + isDisabled={filters.length === filterSelectOptions.length - 1} /> </> ); diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/LinkSection.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/LinkSection.tsx index 17884633355dc..89f55a6c682ca 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/LinkSection.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/LinkSection.tsx @@ -12,10 +12,10 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { CustomLinkFormData } from './'; +import { CustomLink } from '../../../../../../../../../../plugins/apm/server/lib/settings/custom_link/custom_link_types'; interface InputField { - name: keyof CustomLinkFormData; + name: keyof CustomLink; label: string; helpText: string; placeholder: string; diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/helper.ts b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/helper.ts new file mode 100644 index 0000000000000..3873dc1259ccb --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/helper.ts @@ -0,0 +1,107 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { i18n } from '@kbn/i18n'; +import { isEmpty, pick } from 'lodash'; +import { + FilterOptionsType, + CustomLink +} from '../../../../../../../../../../plugins/apm/server/lib/settings/custom_link/custom_link_types'; +import { + SERVICE_NAME, + SERVICE_ENVIRONMENT, + TRANSACTION_NAME, + TRANSACTION_TYPE +} from '../../../../../../../../../../plugins/apm/common/elasticsearch_fieldnames'; + +export type Filters = Array<[keyof FilterOptionsType | '', string]>; + +interface FilterSelectOption { + value: 'DEFAULT' | keyof FilterOptionsType; + text: string; +} + +export const filterOptions: Array<keyof FilterOptionsType> = [ + SERVICE_NAME, + SERVICE_ENVIRONMENT, + TRANSACTION_TYPE, + TRANSACTION_NAME +]; + +/** + * Converts available filters from the Custom Link to Array of filters. + * e.g. + * customLink = { + * id: '1', + * label: 'foo', + * url: 'http://www.elastic.co', + * service.name: 'opbeans-java', + * transaction.type: 'request' + * } + * + * results: [['service.name', 'opbeans-java'],['transaction.type', 'request']] + * @param customLink + */ +export const convertFiltersToArray = (customLink?: CustomLink): Filters => { + if (customLink) { + const filters = Object.entries(pick(customLink, filterOptions)) as Filters; + if (!isEmpty(filters)) { + return filters; + } + } + return [['', '']]; +}; + +/** + * Converts array of filters into object. + * e.g. + * filters: [['service.name', 'opbeans-java'],['transaction.type', 'request']] + * + * results: { + * 'service.name': 'opbeans-java', + * 'transaction.type': 'request' + * } + * @param filters + */ +export const convertFiltersToObject = (filters: Filters) => { + const convertedFilters = Object.fromEntries( + filters.filter(([key, value]) => !isEmpty(key) && !isEmpty(value)) + ); + if (!isEmpty(convertedFilters)) { + return convertedFilters; + } +}; + +export const DEFAULT_OPTION: FilterSelectOption = { + value: 'DEFAULT', + text: i18n.translate( + 'xpack.apm.settings.customizeUI.customLink.flyOut.filters.defaultOption', + { defaultMessage: 'Select fields...' } + ) +}; + +export const filterSelectOptions: FilterSelectOption[] = [ + DEFAULT_OPTION, + ...filterOptions.map(filter => ({ + value: filter as keyof FilterOptionsType, + text: filter + })) +]; + +/** + * Returns the options available, removing filters already added, but keeping the selected filter. + * + * @param filters + * @param idx + */ +export const getSelectOptions = (filters: Filters, idx: number) => { + return filterSelectOptions.filter(option => { + const indexUsedFilter = filters.findIndex( + filter => filter[0] === option.value + ); + // Filter out all items already added, besides the one selected in the current filter. + return indexUsedFilter === -1 || idx === indexUsedFilter; + }); +}; diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/index.tsx index 09fc32a5c38a0..88358c888160b 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/index.tsx @@ -13,14 +13,14 @@ import { EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { isEmpty } from 'lodash'; import React, { useState } from 'react'; import { CustomLink } from '../../../../../../../../../../plugins/apm/server/lib/settings/custom_link/custom_link_types'; import { useApmPluginContext } from '../../../../../../hooks/useApmPluginContext'; -import { LinkSection } from './LinkSection'; import { FiltersSection } from './FiltersSection'; import { FlyoutFooter } from './FlyoutFooter'; +import { LinkSection } from './LinkSection'; import { saveCustomLink } from './saveCustomLink'; +import { convertFiltersToArray, convertFiltersToObject } from './helper'; interface Props { onClose: () => void; @@ -29,28 +29,6 @@ interface Props { onDelete: () => void; } -export interface CustomLinkFormData extends Omit<CustomLink, 'filters'> { - filters: Array<[string, string]>; -} - -const convertFiltersToArray = (filters: CustomLink['filters'] = {}) => { - const convertedFilters = Object.entries(filters); - // When convertedFilters is empty, initiate the filters filled with one item. - if (isEmpty(convertedFilters)) { - convertedFilters.push(['', '']); - } - return convertedFilters; -}; - -const convertFiltersToObject = (filters: CustomLinkFormData['filters']) => { - const convertedFilters = Object.fromEntries( - filters.filter(([key, value]) => !isEmpty(key) && !isEmpty(value)) - ); - if (!isEmpty(convertedFilters)) { - return convertedFilters; - } -}; - export const CustomLinkFlyout = ({ onClose, customLinkSelected, @@ -60,11 +38,10 @@ export const CustomLinkFlyout = ({ const { toasts } = useApmPluginContext().core.notifications; const [isSaving, setIsSaving] = useState(false); - // form fields const [label, setLabel] = useState(customLinkSelected?.label || ''); const [url, setUrl] = useState(customLinkSelected?.url || ''); const [filters, setFilters] = useState( - convertFiltersToArray(customLinkSelected?.filters) + convertFiltersToArray(customLinkSelected) ); const isFormValid = !!label && !!url; diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/saveCustomLink.ts b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/saveCustomLink.ts index e6b12a7fa75f1..5f8a7b4f05ee4 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/saveCustomLink.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/saveCustomLink.ts @@ -6,7 +6,6 @@ import { i18n } from '@kbn/i18n'; import { NotificationsStart } from 'kibana/public'; -import { CustomLink } from '../../../../../../../../../../plugins/apm/server/lib/settings/custom_link/custom_link_types'; import { callApmApi } from '../../../../../../services/rest/createCallApmApi'; export async function saveCustomLink({ @@ -19,7 +18,7 @@ export async function saveCustomLink({ id?: string; label: string; url: string; - filters?: CustomLink['filters']; + filters?: { [key: string]: string }; toasts: NotificationsStart['toasts']; }) { try { diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/__test__/CustomLink.test.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/__test__/CustomLink.test.tsx index cabadc9d6ee4a..de492e22e110a 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/__test__/CustomLink.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/__test__/CustomLink.test.tsx @@ -21,17 +21,13 @@ const data = [ id: '1', label: 'label 1', url: 'url 1', - filters: { - 'service.name': 'opbeans-java' - } + 'service.name': 'opbeans-java' }, { id: '2', label: 'label 2', url: 'url 2', - filters: { - 'transaction.type': 'request' - } + 'transaction.type': 'request' } ]; diff --git a/x-pack/plugins/apm/server/lib/settings/custom_link/__test__/__snapshots__/list_custom_links.test.ts.snap b/x-pack/plugins/apm/server/lib/settings/custom_link/__test__/__snapshots__/list_custom_links.test.ts.snap index da0a2d076ddd1..b3819ace40d6c 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_link/__test__/__snapshots__/list_custom_links.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/settings/custom_link/__test__/__snapshots__/list_custom_links.test.ts.snap @@ -26,7 +26,7 @@ Object { "should": Array [ Object { "term": Object { - "filters.service.name": "foo", + "service.name": "foo", }, }, Object { @@ -34,7 +34,7 @@ Object { "must_not": Array [ Object { "exists": Object { - "field": "filters.service.name", + "field": "service.name", }, }, ], @@ -49,7 +49,7 @@ Object { "should": Array [ Object { "term": Object { - "filters.transaction.name": "bar", + "transaction.name": "bar", }, }, Object { @@ -57,7 +57,7 @@ Object { "must_not": Array [ Object { "exists": Object { - "field": "filters.transaction.name", + "field": "transaction.name", }, }, ], diff --git a/x-pack/plugins/apm/server/lib/settings/custom_link/__test__/create_or_update_custom_link.test.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/__test__/create_or_update_custom_link.test.ts index 05e463f4b48f0..624f01c649322 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_link/__test__/create_or_update_custom_link.test.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_link/__test__/create_or_update_custom_link.test.ts @@ -23,10 +23,8 @@ describe('Create or Update Custom link', () => { const customLink = ({ label: 'foo', url: 'http://elastic.com/{{trace.id}}', - filters: { - 'service.name': 'opbeans-java', - 'transaction.type': 'Request' - } + 'service.name': 'opbeans-java', + 'transaction.type': 'Request' } as unknown) as CustomLink; afterEach(() => { internalClientIndexMock.mockClear(); @@ -45,10 +43,8 @@ describe('Create or Update Custom link', () => { '@timestamp': 1570737000000, label: 'foo', url: 'http://elastic.com/{{trace.id}}', - filters: { - 'service.name': 'opbeans-java', - 'transaction.type': 'Request' - } + 'service.name': 'opbeans-java', + 'transaction.type': 'Request' } }); }); @@ -66,10 +62,8 @@ describe('Create or Update Custom link', () => { '@timestamp': 1570737000000, label: 'foo', url: 'http://elastic.com/{{trace.id}}', - filters: { - 'service.name': 'opbeans-java', - 'transaction.type': 'Request' - } + 'service.name': 'opbeans-java', + 'transaction.type': 'Request' } }); }); diff --git a/x-pack/plugins/apm/server/lib/settings/custom_link/create_custom_link_index.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/create_custom_link_index.ts index d43dcef485cfc..cdb3cff616030 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_link/create_custom_link_index.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_link/create_custom_link_index.ts @@ -26,7 +26,6 @@ export const createApmCustomLinkIndex = async ({ }; const mappings: Mappings = { - dynamic: false, properties: { '@timestamp': { type: 'date' @@ -37,9 +36,25 @@ const mappings: Mappings = { url: { type: 'keyword' }, - filters: { - dynamic: true, - properties: {} + service: { + properties: { + name: { + type: 'keyword' + }, + environment: { + type: 'keyword' + } + } + }, + transaction: { + properties: { + name: { + type: 'keyword' + }, + type: { + type: 'keyword' + } + } } } }; diff --git a/x-pack/plugins/apm/server/lib/settings/custom_link/create_or_update_custom_link.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/create_or_update_custom_link.ts index 1fee163030981..a95fa9d148aef 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_link/create_or_update_custom_link.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_link/create_or_update_custom_link.ts @@ -4,6 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ +import { + SERVICE_NAME, + SERVICE_ENVIRONMENT, + TRANSACTION_NAME, + TRANSACTION_TYPE +} from '../../../../common/elasticsearch_fieldnames'; import { APMIndexDocumentParams } from '../../helpers/es_client'; import { Setup } from '../../helpers/setup_request'; import { CustomLink } from './custom_link_types'; @@ -26,7 +32,10 @@ export async function createOrUpdateCustomLink({ '@timestamp': Date.now(), label: customLink.label, url: customLink.url, - filters: customLink.filters + [SERVICE_NAME]: customLink[SERVICE_NAME], + [SERVICE_ENVIRONMENT]: customLink[SERVICE_ENVIRONMENT], + [TRANSACTION_NAME]: customLink[TRANSACTION_NAME], + [TRANSACTION_TYPE]: customLink[TRANSACTION_TYPE] } }; diff --git a/x-pack/plugins/apm/server/lib/settings/custom_link/custom_link_types.d.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/custom_link_types.d.ts index bdff7ef6fe465..4e27c29371b48 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_link/custom_link_types.d.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_link/custom_link_types.d.ts @@ -3,13 +3,14 @@ * 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 t from 'io-ts'; +import { FilterOptions } from '../../../routes/settings/custom_link'; -export interface CustomLink { +export type FilterOptionsType = t.TypeOf<typeof FilterOptions>; + +export type CustomLink = { id?: string; '@timestamp': number; label: string; url: string; - filters?: { - [key: string]: string; - }; -} +} & FilterOptionsType; diff --git a/x-pack/plugins/apm/server/lib/settings/custom_link/list_custom_links.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/list_custom_links.ts index 6ad465d57781c..ec2a32de0154d 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_link/list_custom_links.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_link/list_custom_links.ts @@ -4,25 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as t from 'io-ts'; - -import { - SERVICE_NAME, - TRANSACTION_NAME, - TRANSACTION_TYPE, - SERVICE_ENVIRONMENT -} from '../../../../common/elasticsearch_fieldnames'; import { Setup } from '../../helpers/setup_request'; -import { CustomLink } from './custom_link_types'; - -export const FilterOptions = t.partial({ - [SERVICE_NAME]: t.string, - [SERVICE_ENVIRONMENT]: t.string, - [TRANSACTION_NAME]: t.string, - [TRANSACTION_TYPE]: t.string -}); - -export type FilterOptionsType = t.TypeOf<typeof FilterOptions>; +import { CustomLink, FilterOptionsType } from './custom_link_types'; export async function listCustomLinks({ setup, @@ -34,13 +17,12 @@ export async function listCustomLinks({ const { internalClient, indices } = setup; const esFilters = Object.entries(filters).map(([key, value]) => { - const field = `filters.${key}`; return { bool: { minimum_should_match: 1, should: [ - { term: { [field]: value } }, - { bool: { must_not: [{ exists: { field } }] } } + { term: { [key]: value } }, + { bool: { must_not: [{ exists: { field: key } }] } } ] } }; diff --git a/x-pack/plugins/apm/server/routes/settings/custom_link.ts b/x-pack/plugins/apm/server/routes/settings/custom_link.ts index feefe34185189..19021671c361f 100644 --- a/x-pack/plugins/apm/server/routes/settings/custom_link.ts +++ b/x-pack/plugins/apm/server/routes/settings/custom_link.ts @@ -4,14 +4,24 @@ * you may not use this file except in compliance with the Elastic License. */ import * as t from 'io-ts'; +import { + SERVICE_NAME, + SERVICE_ENVIRONMENT, + TRANSACTION_NAME, + TRANSACTION_TYPE +} from '../../../common/elasticsearch_fieldnames'; import { createRoute } from '../create_route'; import { setupRequest } from '../../lib/helpers/setup_request'; import { createOrUpdateCustomLink } from '../../lib/settings/custom_link/create_or_update_custom_link'; import { deleteCustomLink } from '../../lib/settings/custom_link/delete_custom_link'; -import { - listCustomLinks, - FilterOptions -} from '../../lib/settings/custom_link/list_custom_links'; +import { listCustomLinks } from '../../lib/settings/custom_link/list_custom_links'; + +export const FilterOptions = t.partial({ + [SERVICE_NAME]: t.string, + [SERVICE_ENVIRONMENT]: t.string, + [TRANSACTION_NAME]: t.string, + [TRANSACTION_TYPE]: t.string +}); export const listCustomLinksRoute = createRoute(core => ({ path: '/api/apm/settings/custom-links', @@ -30,9 +40,7 @@ const payload = t.intersection([ label: t.string, url: t.string }), - t.partial({ - filters: t.record(t.string, t.string) - }) + FilterOptions ]); export const createCustomLinkRoute = createRoute(() => ({ From 01d5196d125cb3e75d702e972d879490d5eb6962 Mon Sep 17 00:00:00 2001 From: cauemarcondes <caue.marcondes@elastic.co> Date: Thu, 5 Mar 2020 14:35:16 +0100 Subject: [PATCH 40/42] fixing save functionality --- .../CustomLinkFlyout/FiltersSection.tsx | 3 ++- .../CustomLink/CustomLinkFlyout/helper.ts | 19 ++++--------------- .../CustomLinkFlyout/saveCustomLink.ts | 2 +- .../create_or_update_custom_link.ts | 13 +++---------- .../custom_link/custom_link_types.d.ts | 4 +--- .../settings/custom_link/list_custom_links.ts | 3 ++- .../apm/server/routes/settings/custom_link.ts | 11 ++++++++++- 7 files changed, 23 insertions(+), 32 deletions(-) diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/FiltersSection.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/FiltersSection.tsx index 1139f85242a7d..7a6d2413a28d6 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/FiltersSection.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/FiltersSection.tsx @@ -16,7 +16,8 @@ import { import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; import React, { useRef } from 'react'; -import { FilterOptionsType } from '../../../../../../../../../../plugins/apm/server/lib/settings/custom_link/custom_link_types'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { FilterOptionsType } from '../../../../../../../../../../plugins/apm/server/routes/settings/custom_link'; import { getSelectOptions, DEFAULT_OPTION, diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/helper.ts b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/helper.ts index 3873dc1259ccb..a658754e74904 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/helper.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/helper.ts @@ -7,14 +7,10 @@ import { i18n } from '@kbn/i18n'; import { isEmpty, pick } from 'lodash'; import { FilterOptionsType, - CustomLink -} from '../../../../../../../../../../plugins/apm/server/lib/settings/custom_link/custom_link_types'; -import { - SERVICE_NAME, - SERVICE_ENVIRONMENT, - TRANSACTION_NAME, - TRANSACTION_TYPE -} from '../../../../../../../../../../plugins/apm/common/elasticsearch_fieldnames'; + filterOptions + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../../../../../../plugins/apm/server/routes/settings/custom_link'; +import { CustomLink } from '../../../../../../../../../../plugins/apm/server/lib/settings/custom_link/custom_link_types'; export type Filters = Array<[keyof FilterOptionsType | '', string]>; @@ -23,13 +19,6 @@ interface FilterSelectOption { text: string; } -export const filterOptions: Array<keyof FilterOptionsType> = [ - SERVICE_NAME, - SERVICE_ENVIRONMENT, - TRANSACTION_TYPE, - TRANSACTION_NAME -]; - /** * Converts available filters from the Custom Link to Array of filters. * e.g. diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/saveCustomLink.ts b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/saveCustomLink.ts index 5f8a7b4f05ee4..768186fdf5ba1 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/saveCustomLink.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/saveCustomLink.ts @@ -25,7 +25,7 @@ export async function saveCustomLink({ const customLink = { label, url, - filters + ...filters }; if (id) { await callApmApi({ diff --git a/x-pack/plugins/apm/server/lib/settings/custom_link/create_or_update_custom_link.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/create_or_update_custom_link.ts index a95fa9d148aef..809fe2050a072 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_link/create_or_update_custom_link.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_link/create_or_update_custom_link.ts @@ -4,12 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - SERVICE_NAME, - SERVICE_ENVIRONMENT, - TRANSACTION_NAME, - TRANSACTION_TYPE -} from '../../../../common/elasticsearch_fieldnames'; +import { pick } from 'lodash'; +import { filterOptions } from '../../../routes/settings/custom_link'; import { APMIndexDocumentParams } from '../../helpers/es_client'; import { Setup } from '../../helpers/setup_request'; import { CustomLink } from './custom_link_types'; @@ -32,10 +28,7 @@ export async function createOrUpdateCustomLink({ '@timestamp': Date.now(), label: customLink.label, url: customLink.url, - [SERVICE_NAME]: customLink[SERVICE_NAME], - [SERVICE_ENVIRONMENT]: customLink[SERVICE_ENVIRONMENT], - [TRANSACTION_NAME]: customLink[TRANSACTION_NAME], - [TRANSACTION_TYPE]: customLink[TRANSACTION_TYPE] + ...pick(customLink, filterOptions) } }; diff --git a/x-pack/plugins/apm/server/lib/settings/custom_link/custom_link_types.d.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/custom_link_types.d.ts index 4e27c29371b48..b9e45267f35b6 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_link/custom_link_types.d.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_link/custom_link_types.d.ts @@ -4,9 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import * as t from 'io-ts'; -import { FilterOptions } from '../../../routes/settings/custom_link'; - -export type FilterOptionsType = t.TypeOf<typeof FilterOptions>; +import { FilterOptionsType } from '../../../routes/settings/custom_link'; export type CustomLink = { id?: string; diff --git a/x-pack/plugins/apm/server/lib/settings/custom_link/list_custom_links.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/list_custom_links.ts index ec2a32de0154d..d6dce0a360947 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_link/list_custom_links.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_link/list_custom_links.ts @@ -5,7 +5,8 @@ */ import { Setup } from '../../helpers/setup_request'; -import { CustomLink, FilterOptionsType } from './custom_link_types'; +import { CustomLink } from './custom_link_types'; +import { FilterOptionsType } from '../../../routes/settings/custom_link'; export async function listCustomLinks({ setup, diff --git a/x-pack/plugins/apm/server/routes/settings/custom_link.ts b/x-pack/plugins/apm/server/routes/settings/custom_link.ts index 19021671c361f..a825e7a4eeb6d 100644 --- a/x-pack/plugins/apm/server/routes/settings/custom_link.ts +++ b/x-pack/plugins/apm/server/routes/settings/custom_link.ts @@ -16,13 +16,22 @@ import { createOrUpdateCustomLink } from '../../lib/settings/custom_link/create_ import { deleteCustomLink } from '../../lib/settings/custom_link/delete_custom_link'; import { listCustomLinks } from '../../lib/settings/custom_link/list_custom_links'; -export const FilterOptions = t.partial({ +const FilterOptions = t.partial({ [SERVICE_NAME]: t.string, [SERVICE_ENVIRONMENT]: t.string, [TRANSACTION_NAME]: t.string, [TRANSACTION_TYPE]: t.string }); +export type FilterOptionsType = t.TypeOf<typeof FilterOptions>; + +export const filterOptions: Array<keyof FilterOptionsType> = [ + SERVICE_NAME, + SERVICE_ENVIRONMENT, + TRANSACTION_TYPE, + TRANSACTION_NAME +]; + export const listCustomLinksRoute = createRoute(core => ({ path: '/api/apm/settings/custom-links', params: { From 718fdf5820a92bd4f8298354feb90be91ecb04b0 Mon Sep 17 00:00:00 2001 From: cauemarcondes <caue.marcondes@elastic.co> Date: Thu, 5 Mar 2020 16:25:45 +0100 Subject: [PATCH 41/42] fixing pr comments --- .../CustomizeUI/CustomLink/CustomLinkFlyout/helper.ts | 2 +- .../Settings/CustomizeUI/CustomLink/CustomLinkTable.tsx | 9 +++------ .../app/Settings/CustomizeUI/CustomLink/EmptyPrompt.tsx | 4 ++-- .../app/Settings/CustomizeUI/CustomLink/Title.tsx | 2 +- .../CustomizeUI/CustomLink/__test__/CustomLink.test.tsx | 8 ++++---- 5 files changed, 11 insertions(+), 14 deletions(-) diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/helper.ts b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/helper.ts index a658754e74904..e1d0b05bb0b92 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/helper.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/helper.ts @@ -67,7 +67,7 @@ export const DEFAULT_OPTION: FilterSelectOption = { value: 'DEFAULT', text: i18n.translate( 'xpack.apm.settings.customizeUI.customLink.flyOut.filters.defaultOption', - { defaultMessage: 'Select fields...' } + { defaultMessage: 'Select field...' } ) }; diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkTable.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkTable.tsx index ced6f437ded59..f7d8c4baa71e9 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkTable.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkTable.tsx @@ -61,11 +61,8 @@ export const CustomLinkTable = ({ ) }, { - width: px(units.quadruple), - name: i18n.translate( - 'xpack.apm.settings.customizeUI.customLink.table.links', - { defaultMessage: 'Links' } - ), + width: px(units.triple), + name: '', actions: [ { name: i18n.translate( @@ -103,7 +100,7 @@ export const CustomLinkTable = ({ placeholder={i18n.translate( 'xpack.apm.settings.customizeUI.customLink.searchInput.filter', { - defaultMessage: 'Filter links by Name or URL...' + defaultMessage: 'Filter links by Name and URL...' } )} /> diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/EmptyPrompt.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/EmptyPrompt.tsx index 79917553b7e66..e75004918f430 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/EmptyPrompt.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/EmptyPrompt.tsx @@ -15,7 +15,7 @@ export const EmptyPrompt = ({ }) => { return ( <EuiEmptyPrompt - iconType="boxesHorizontal" + iconType="link" iconColor="" title={ <h2> @@ -34,7 +34,7 @@ export const EmptyPrompt = ({ 'xpack.apm.settings.customizeUI.customLink.emptyPromptText', { defaultMessage: - "Let's change that! You can add custom links to the Actions context menu by the trace and error details for each service. This could be linking to a Kibana dashboard or going to your organization's support portal" + "Let's change that! You can add custom links to the Actions context menu by the transaction details for each service. Create a helpful link to your company's support portal or open a new bug report. Learn more about it in our docs." } )} </p> diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/Title.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/Title.tsx index 8ff51c1594cc3..17ec42b3e2016 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/Title.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/Title.tsx @@ -28,7 +28,7 @@ export const Title = () => ( 'xpack.apm.settings.customizeUI.customLink.info', { defaultMessage: - "These links will be shown in the 'Actions' context menu for the trace and error detail components." + "These links will be shown in the 'Actions' context menu for the transaction detail." } )} /> diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/__test__/CustomLink.test.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/__test__/CustomLink.test.tsx index de492e22e110a..f02cc2be8268d 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/__test__/CustomLink.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/__test__/CustomLink.test.tsx @@ -221,7 +221,7 @@ describe('CustomLink', () => { }; addFieldAndCheck('filter-0', 'transaction.name', false, [ - 'Select fields...', + 'Select field...', 'service.name', 'service.environment', 'transaction.type', @@ -229,20 +229,20 @@ describe('CustomLink', () => { ]); addFieldAndCheck('filter-1', 'service.name', true, [ - 'Select fields...', + 'Select field...', 'service.name', 'service.environment', 'transaction.type' ]); addFieldAndCheck('filter-2', 'transaction.type', true, [ - 'Select fields...', + 'Select field...', 'service.environment', 'transaction.type' ]); addFieldAndCheck('filter-3', 'service.environment', true, [ - 'Select fields...', + 'Select field...', 'service.environment' ]); }); From 6933e8dd80bc49a1e3b274721d29932b18803c71 Mon Sep 17 00:00:00 2001 From: cauemarcondes <caue.marcondes@elastic.co> Date: Fri, 6 Mar 2020 09:02:37 +0100 Subject: [PATCH 42/42] fixing pr comments --- .../app/Settings/ApmIndices/index.tsx | 4 +- .../CustomLinkFlyout/DeleteButton.tsx | 2 +- .../CustomLinkFlyout/FiltersSection.tsx | 45 +++++++------------ .../CustomLink/CustomLinkFlyout/helper.ts | 8 ++-- .../CustomLinkFlyout/saveCustomLink.ts | 4 +- .../Settings/CustomizeUI/CustomLink/index.tsx | 2 +- .../custom_link/custom_link_types.d.ts | 4 +- .../settings/custom_link/list_custom_links.ts | 4 +- .../apm/server/routes/settings/custom_link.ts | 18 ++++---- 9 files changed, 40 insertions(+), 51 deletions(-) diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx index 882968eb25591..ac8908b6e425c 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx @@ -92,8 +92,8 @@ export function ApmIndices() { const [isSaving, setIsSaving] = useState(false); const { data = INITIAL_STATE, status, refetch } = useFetcher( - callApmApiFromFetcher => - callApmApiFromFetcher({ + _callApmApi => + _callApmApi({ pathname: `/api/apm/settings/apm-index-settings` }), [] diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/DeleteButton.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/DeleteButton.tsx index 84b923fbf815c..2b3a5cbe87992 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/DeleteButton.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/DeleteButton.tsx @@ -45,7 +45,7 @@ async function deleteConfig( ) { try { await callApmApi({ - pathname: '/api/apm/settings/custom-links/{id}', + pathname: '/api/apm/settings/custom_links/{id}', method: 'DELETE', params: { path: { id: customLinkId } diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/FiltersSection.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/FiltersSection.tsx index 7a6d2413a28d6..69fecf25f5143 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/FiltersSection.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/FiltersSection.tsx @@ -15,14 +15,14 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; -import React, { useRef } from 'react'; +import React from 'react'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { FilterOptionsType } from '../../../../../../../../../../plugins/apm/server/routes/settings/custom_link'; +import { FilterOptions } from '../../../../../../../../../../plugins/apm/server/routes/settings/custom_link'; import { - getSelectOptions, DEFAULT_OPTION, + Filters, filterSelectOptions, - Filters + getSelectOptions } from './helper'; export const FiltersSection = ({ @@ -32,27 +32,23 @@ export const FiltersSection = ({ filters: Filters; onChangeFilters: (filters: Filters) => void; }) => { - const filterValueRefs = useRef<HTMLInputElement[]>([]); - const onChangeFilter = (filter: Filters[0], idx: number) => { - if (filterValueRefs.current[idx]) { - filterValueRefs.current[idx].focus(); - } - const copyOfFilters = [...filters]; - copyOfFilters[idx] = filter; - onChangeFilters(copyOfFilters); + const newFilters = [...filters]; + newFilters[idx] = filter; + onChangeFilters(newFilters); }; const onRemoveFilter = (idx: number) => { - const copyOfFilters = [...filters]; - copyOfFilters.splice(idx, 1); - // When empty, means that it was the last filter that got removed, - // so instead of showing an empty list, will add a new empty filter. - if (isEmpty(copyOfFilters)) { - copyOfFilters.push(['', '']); - } + // remove without mutating original array + const newFilters = [...filters].splice(idx, 1); - onChangeFilters(copyOfFilters); + // if there is only one item left it should not be removed + // but reset to empty + if (isEmpty(newFilters)) { + onChangeFilters([['', '']]); + } else { + onChangeFilters(newFilters); + } }; const handleAddFilter = () => { @@ -105,7 +101,7 @@ export const FiltersSection = ({ )} onChange={e => onChangeFilter( - [e.target.value as keyof FilterOptionsType, value], + [e.target.value as keyof FilterOptions, value], idx ) } @@ -125,13 +121,6 @@ export const FiltersSection = ({ onChange={e => onChangeFilter([key, e.target.value], idx)} value={value} isInvalid={!isEmpty(key) && isEmpty(value)} - inputRef={ref => { - if (ref) { - filterValueRefs.current.push(ref); - } else { - filterValueRefs.current.splice(idx, 1); - } - }} /> </EuiFlexItem> <EuiFlexItem grow={false}> diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/helper.ts b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/helper.ts index e1d0b05bb0b92..bb86a251594ab 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/helper.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/helper.ts @@ -6,16 +6,16 @@ import { i18n } from '@kbn/i18n'; import { isEmpty, pick } from 'lodash'; import { - FilterOptionsType, + FilterOptions, filterOptions // eslint-disable-next-line @kbn/eslint/no-restricted-paths } from '../../../../../../../../../../plugins/apm/server/routes/settings/custom_link'; import { CustomLink } from '../../../../../../../../../../plugins/apm/server/lib/settings/custom_link/custom_link_types'; -export type Filters = Array<[keyof FilterOptionsType | '', string]>; +export type Filters = Array<[keyof FilterOptions | '', string]>; interface FilterSelectOption { - value: 'DEFAULT' | keyof FilterOptionsType; + value: 'DEFAULT' | keyof FilterOptions; text: string; } @@ -74,7 +74,7 @@ export const DEFAULT_OPTION: FilterSelectOption = { export const filterSelectOptions: FilterSelectOption[] = [ DEFAULT_OPTION, ...filterOptions.map(filter => ({ - value: filter as keyof FilterOptionsType, + value: filter as keyof FilterOptions, text: filter })) ]; diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/saveCustomLink.ts b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/saveCustomLink.ts index 768186fdf5ba1..f255840e1d734 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/saveCustomLink.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/saveCustomLink.ts @@ -29,7 +29,7 @@ export async function saveCustomLink({ }; if (id) { await callApmApi({ - pathname: '/api/apm/settings/custom-links/{id}', + pathname: '/api/apm/settings/custom_links/{id}', method: 'PUT', params: { path: { id }, @@ -38,7 +38,7 @@ export async function saveCustomLink({ }); } else { await callApmApi({ - pathname: '/api/apm/settings/custom-links', + pathname: '/api/apm/settings/custom_links', method: 'POST', params: { body: customLink diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx index 8f37c67c567bf..bc1882c8c2785 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx @@ -22,7 +22,7 @@ export const CustomLinkOverview = () => { >(); const { data: customLinks, status, refetch } = useFetcher( - callApmApi => callApmApi({ pathname: '/api/apm/settings/custom-links' }), + callApmApi => callApmApi({ pathname: '/api/apm/settings/custom_links' }), [] ); diff --git a/x-pack/plugins/apm/server/lib/settings/custom_link/custom_link_types.d.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/custom_link_types.d.ts index b9e45267f35b6..60b97712713a9 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_link/custom_link_types.d.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_link/custom_link_types.d.ts @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ import * as t from 'io-ts'; -import { FilterOptionsType } from '../../../routes/settings/custom_link'; +import { FilterOptions } from '../../../routes/settings/custom_link'; export type CustomLink = { id?: string; '@timestamp': number; label: string; url: string; -} & FilterOptionsType; +} & FilterOptions; diff --git a/x-pack/plugins/apm/server/lib/settings/custom_link/list_custom_links.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/list_custom_links.ts index d6dce0a360947..e6052da73b0db 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_link/list_custom_links.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_link/list_custom_links.ts @@ -6,14 +6,14 @@ import { Setup } from '../../helpers/setup_request'; import { CustomLink } from './custom_link_types'; -import { FilterOptionsType } from '../../../routes/settings/custom_link'; +import { FilterOptions } from '../../../routes/settings/custom_link'; export async function listCustomLinks({ setup, filters = {} }: { setup: Setup; - filters?: FilterOptionsType; + filters?: FilterOptions; }) { const { internalClient, indices } = setup; diff --git a/x-pack/plugins/apm/server/routes/settings/custom_link.ts b/x-pack/plugins/apm/server/routes/settings/custom_link.ts index a825e7a4eeb6d..5988d7f85b186 100644 --- a/x-pack/plugins/apm/server/routes/settings/custom_link.ts +++ b/x-pack/plugins/apm/server/routes/settings/custom_link.ts @@ -16,16 +16,16 @@ import { createOrUpdateCustomLink } from '../../lib/settings/custom_link/create_ import { deleteCustomLink } from '../../lib/settings/custom_link/delete_custom_link'; import { listCustomLinks } from '../../lib/settings/custom_link/list_custom_links'; -const FilterOptions = t.partial({ +const FilterOptionsRt = t.partial({ [SERVICE_NAME]: t.string, [SERVICE_ENVIRONMENT]: t.string, [TRANSACTION_NAME]: t.string, [TRANSACTION_TYPE]: t.string }); -export type FilterOptionsType = t.TypeOf<typeof FilterOptions>; +export type FilterOptions = t.TypeOf<typeof FilterOptionsRt>; -export const filterOptions: Array<keyof FilterOptionsType> = [ +export const filterOptions: Array<keyof FilterOptions> = [ SERVICE_NAME, SERVICE_ENVIRONMENT, TRANSACTION_TYPE, @@ -33,9 +33,9 @@ export const filterOptions: Array<keyof FilterOptionsType> = [ ]; export const listCustomLinksRoute = createRoute(core => ({ - path: '/api/apm/settings/custom-links', + path: '/api/apm/settings/custom_links', params: { - query: FilterOptions + query: FilterOptionsRt }, handler: async ({ context, request }) => { const setup = await setupRequest(context, request); @@ -49,12 +49,12 @@ const payload = t.intersection([ label: t.string, url: t.string }), - FilterOptions + FilterOptionsRt ]); export const createCustomLinkRoute = createRoute(() => ({ method: 'POST', - path: '/api/apm/settings/custom-links', + path: '/api/apm/settings/custom_links', params: { body: payload }, @@ -71,7 +71,7 @@ export const createCustomLinkRoute = createRoute(() => ({ export const updateCustomLinkRoute = createRoute(() => ({ method: 'PUT', - path: '/api/apm/settings/custom-links/{id}', + path: '/api/apm/settings/custom_links/{id}', params: { path: t.type({ id: t.string @@ -96,7 +96,7 @@ export const updateCustomLinkRoute = createRoute(() => ({ export const deleteCustomLinkRoute = createRoute(() => ({ method: 'DELETE', - path: '/api/apm/settings/custom-links/{id}', + path: '/api/apm/settings/custom_links/{id}', params: { path: t.type({ id: t.string