diff --git a/package.json b/package.json index 29f34767ba409..acfeec966f0a0 100644 --- a/package.json +++ b/package.json @@ -1007,6 +1007,7 @@ "suricata-sid-db": "^1.0.2", "symbol-observable": "^1.2.0", "tar": "^6.1.15", + "textarea-caret": "^3.1.0", "tinycolor2": "1.4.1", "tinygradient": "0.4.3", "ts-easing": "^0.2.0", @@ -1368,6 +1369,7 @@ "@types/tar": "^6.1.5", "@types/tempy": "^0.2.0", "@types/testing-library__jest-dom": "^5.14.7", + "@types/textarea-caret": "^3.0.1", "@types/tinycolor2": "^1.4.1", "@types/tough-cookie": "^4.0.2", "@types/type-detect": "^4.0.1", diff --git a/packages/kbn-alerts-ui-shared/index.ts b/packages/kbn-alerts-ui-shared/index.ts index 6815a66bce902..6daec0f82b108 100644 --- a/packages/kbn-alerts-ui-shared/index.ts +++ b/packages/kbn-alerts-ui-shared/index.ts @@ -9,3 +9,4 @@ export { AlertLifecycleStatusBadge } from './src/alert_lifecycle_status_badge'; export type { AlertLifecycleStatusBadgeProps } from './src/alert_lifecycle_status_badge'; export { MaintenanceWindowCallout } from './src/maintenance_window_callout'; +export { AddMessageVariables } from './src/add_message_variables'; diff --git a/packages/kbn-alerts-ui-shared/jest.config.js b/packages/kbn-alerts-ui-shared/jest.config.js index 31062b3280e41..54f2c74a56d3a 100644 --- a/packages/kbn-alerts-ui-shared/jest.config.js +++ b/packages/kbn-alerts-ui-shared/jest.config.js @@ -10,4 +10,5 @@ module.exports = { preset: '@kbn/test', rootDir: '../..', roots: ['/packages/kbn-alerts-ui-shared'], + setupFilesAfterEnv: ['/packages/kbn-alerts-ui-shared/setup_tests.ts'], }; diff --git a/packages/kbn-alerts-ui-shared/setup_tests.ts b/packages/kbn-alerts-ui-shared/setup_tests.ts new file mode 100644 index 0000000000000..8d1acb9232934 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/setup_tests.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +// eslint-disable-next-line import/no-extraneous-dependencies +import '@testing-library/jest-dom'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.scss b/packages/kbn-alerts-ui-shared/src/add_message_variables/add_message_variables.scss similarity index 100% rename from x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.scss rename to packages/kbn-alerts-ui-shared/src/add_message_variables/add_message_variables.scss diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.test.tsx b/packages/kbn-alerts-ui-shared/src/add_message_variables/index.test.tsx similarity index 95% rename from x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.test.tsx rename to packages/kbn-alerts-ui-shared/src/add_message_variables/index.test.tsx index 641587c349382..80603d9eb8615 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.test.tsx +++ b/packages/kbn-alerts-ui-shared/src/add_message_variables/index.test.tsx @@ -1,13 +1,14 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import React from 'react'; import { render, fireEvent, screen } from '@testing-library/react'; -import { AddMessageVariables } from './add_message_variables'; +import { AddMessageVariables } from '.'; describe('AddMessageVariables', () => { test('it renders variables and filter bar', async () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.tsx b/packages/kbn-alerts-ui-shared/src/add_message_variables/index.tsx similarity index 96% rename from x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.tsx rename to packages/kbn-alerts-ui-shared/src/add_message_variables/index.tsx index f986930470acc..0aa9bae65c29e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.tsx +++ b/packages/kbn-alerts-ui-shared/src/add_message_variables/index.tsx @@ -1,8 +1,9 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import React, { useMemo, useState } from 'react'; @@ -23,7 +24,7 @@ import { } from '@elastic/eui'; import { ActionVariable } from '@kbn/alerting-plugin/common'; import './add_message_variables.scss'; -import { TruncatedText } from '../../common/truncated_text'; +import { TruncatedText } from './truncated_text'; import * as i18n from './translations'; interface Props { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/translations.js b/packages/kbn-alerts-ui-shared/src/add_message_variables/translations.ts similarity index 57% rename from x-pack/plugins/triggers_actions_ui/public/application/components/translations.js rename to packages/kbn-alerts-ui-shared/src/add_message_variables/translations.ts index 0e089f1a830e8..b19e9173797a9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/translations.js +++ b/packages/kbn-alerts-ui-shared/src/add_message_variables/translations.ts @@ -1,70 +1,71 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import { i18n } from '@kbn/i18n'; export const LOADING_VARIABLES = i18n.translate( - 'xpack.triggersActionsUI.components.addMessageVariables.loadingMessage', + 'alertsUIShared.components.addMessageVariables.loadingMessage', { defaultMessage: 'Loading variables', } ); export const NO_VARIABLES_FOUND = i18n.translate( - 'xpack.triggersActionsUI.components.addMessageVariables.noVariablesFound', + 'alertsUIShared.components.addMessageVariables.noVariablesFound', { defaultMessage: 'No variables found', } ); export const NO_VARIABLES_AVAILABLE = i18n.translate( - 'xpack.triggersActionsUI.components.addMessageVariables.noVariablesAvailable', + 'alertsUIShared.components.addMessageVariables.noVariablesAvailable', { defaultMessage: 'No variables available', } ); export const DEPRECATED_VARIABLES_ARE_SHOWN = i18n.translate( - 'xpack.triggersActionsUI.components.addMessageVariables.deprecatedVariablesAreShown', + 'alertsUIShared.components.addMessageVariables.deprecatedVariablesAreShown', { defaultMessage: 'Deprecated variables are shown', } ); export const DEPRECATED_VARIABLES_ARE_HIDDEN = i18n.translate( - 'xpack.triggersActionsUI.components.addMessageVariables.deprecatedVariablesAreHidden', + 'alertsUIShared.components.addMessageVariables.deprecatedVariablesAreHidden', { defaultMessage: 'Deprecated variables are hidden', } ); export const HIDE = i18n.translate( - 'xpack.triggersActionsUI.components.addMessageVariables.hideDeprecatedVariables', + 'alertsUIShared.components.addMessageVariables.hideDeprecatedVariables', { defaultMessage: 'Hide', } ); export const SHOW_ALL = i18n.translate( - 'xpack.triggersActionsUI.components.addMessageVariables.showAllDeprecatedVariables', + 'alertsUIShared.components.addMessageVariables.showAllDeprecatedVariables', { defaultMessage: 'Show all', } ); export const ADD_VARIABLE_POPOVER_BUTTON = i18n.translate( - 'xpack.triggersActionsUI.components.addMessageVariables.addVariablePopoverButton', + 'alertsUIShared.components.addMessageVariables.addVariablePopoverButton', { defaultMessage: 'Add variable', } ); export const ADD_VARIABLE_TITLE = i18n.translate( - 'xpack.triggersActionsUI.components.addMessageVariables.addRuleVariableTitle', + 'alertsUIShared.components.addMessageVariables.addRuleVariableTitle', { defaultMessage: 'Add variable', } diff --git a/packages/kbn-alerts-ui-shared/src/add_message_variables/truncated_text.tsx b/packages/kbn-alerts-ui-shared/src/add_message_variables/truncated_text.tsx new file mode 100644 index 0000000000000..52535ab435e68 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/add_message_variables/truncated_text.tsx @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { css } from '@emotion/react'; +import { EuiText } from '@elastic/eui'; + +const LINE_CLAMP = 2; + +const styles = { + truncatedText: css` + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: ${LINE_CLAMP}; + -webkit-box-orient: vertical; + overflow: hidden; + word-break: break-word; + `, +}; + +const TruncatedTextComponent: React.FC<{ text: string }> = ({ text }) => ( + + {text} + +); + +TruncatedTextComponent.displayName = 'TruncatedText'; + +export const TruncatedText = React.memo(TruncatedTextComponent); diff --git a/packages/kbn-alerts-ui-shared/src/maintenance_window_callout/index.test.tsx b/packages/kbn-alerts-ui-shared/src/maintenance_window_callout/index.test.tsx index b29f0ddc613c4..9a05a6bd09222 100644 --- a/packages/kbn-alerts-ui-shared/src/maintenance_window_callout/index.test.tsx +++ b/packages/kbn-alerts-ui-shared/src/maintenance_window_callout/index.test.tsx @@ -106,7 +106,6 @@ describe('MaintenanceWindowCallout', () => { { wrapper: TestProviders } ); - // @ts-expect-error Jest types are incomplete in packages expect(await findByText('Maintenance window is running')).toBeInTheDocument(); expect(fetchActiveMaintenanceWindowsMock).toHaveBeenCalledTimes(1); }); @@ -119,7 +118,7 @@ describe('MaintenanceWindowCallout', () => { const { container } = render(, { wrapper: TestProviders, }); - // @ts-expect-error Jest types are incomplete in packages + expect(container).toBeEmptyDOMElement(); expect(fetchActiveMaintenanceWindowsMock).toHaveBeenCalledTimes(1); }); @@ -130,7 +129,7 @@ describe('MaintenanceWindowCallout', () => { const { container } = render(, { wrapper: TestProviders, }); - // @ts-expect-error Jest types are incomplete in packages + expect(container).toBeEmptyDOMElement(); expect(fetchActiveMaintenanceWindowsMock).toHaveBeenCalledTimes(1); }); @@ -192,7 +191,7 @@ describe('MaintenanceWindowCallout', () => { const { container } = render(, { wrapper: TestProviders, }); - // @ts-expect-error Jest types are incomplete in packages + expect(container).toBeEmptyDOMElement(); }); @@ -213,7 +212,7 @@ describe('MaintenanceWindowCallout', () => { const { findByText } = render(, { wrapper: TestProviders, }); - // @ts-expect-error Jest types are incomplete in packages + expect(await findByText('Maintenance window is running')).toBeInTheDocument(); }); }); diff --git a/test/plugin_functional/test_suites/core_plugins/rendering.ts b/test/plugin_functional/test_suites/core_plugins/rendering.ts index 03928a378f6f3..729baf5023f58 100644 --- a/test/plugin_functional/test_suites/core_plugins/rendering.ts +++ b/test/plugin_functional/test_suites/core_plugins/rendering.ts @@ -280,6 +280,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) { 'xpack.securitySolution.prebuiltRulesPackageVersion (string)', 'xpack.snapshot_restore.slm_ui.enabled (boolean)', 'xpack.snapshot_restore.ui.enabled (boolean)', + 'xpack.stack_connectors.enableExperimental (array)', 'xpack.trigger_actions_ui.enableExperimental (array)', 'xpack.trigger_actions_ui.enableGeoTrackingThresholdAlert (boolean)', 'xpack.upgrade_assistant.featureSet.migrateSystemIndices (boolean)', diff --git a/x-pack/plugins/stack_connectors/common/experimental_features.ts b/x-pack/plugins/stack_connectors/common/experimental_features.ts new file mode 100644 index 0000000000000..61b63cff732a6 --- /dev/null +++ b/x-pack/plugins/stack_connectors/common/experimental_features.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export type ExperimentalFeatures = typeof allowedExperimentalValues; + +/** + * A list of allowed values that can be used in `xpack.stack_connectors.enableExperimental`. + * This object is then used to validate and parse the value entered. + */ +export const allowedExperimentalValues = Object.freeze({ + isMustacheAutocompleteOn: false, +}); + +type ExperimentalConfigKeys = Array; +type Mutable = { -readonly [P in keyof T]: T[P] }; + +const InvalidExperimentalValue = class extends Error {}; +const allowedKeys = Object.keys(allowedExperimentalValues) as Readonly; + +/** + * Parses the string value used in `xpack.stack_connectors.enableExperimental` kibana configuration, + * which should be a string of values delimited by a comma (`,`) + * + * @param configValue + * @throws InvalidExperimentalValue + */ +export const parseExperimentalConfigValue = (configValue: string[]): ExperimentalFeatures => { + const enabledFeatures: Mutable> = {}; + + for (const value of configValue) { + if (!isValidExperimentalValue(value)) { + throw new InvalidExperimentalValue(`[${value}] is not valid.`); + } + // @ts-expect-error ts upgrade v4.7.4 + enabledFeatures[value as keyof ExperimentalFeatures] = true; + } + + return { + ...allowedExperimentalValues, + ...enabledFeatures, + }; +}; + +export const isValidExperimentalValue = (value: string): boolean => { + return allowedKeys.includes(value as keyof ExperimentalFeatures); +}; + +export const getExperimentalAllowedValues = (): string[] => [...allowedKeys]; diff --git a/x-pack/plugins/stack_connectors/common/types.ts b/x-pack/plugins/stack_connectors/common/types.ts new file mode 100644 index 0000000000000..6bc9ef7eb72e6 --- /dev/null +++ b/x-pack/plugins/stack_connectors/common/types.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export interface StackConnectorsConfigType { + enableExperimental: string[]; +} diff --git a/x-pack/plugins/stack_connectors/kibana.jsonc b/x-pack/plugins/stack_connectors/kibana.jsonc index dc4023890d656..da8e973b6f990 100644 --- a/x-pack/plugins/stack_connectors/kibana.jsonc +++ b/x-pack/plugins/stack_connectors/kibana.jsonc @@ -13,6 +13,7 @@ "requiredPlugins": [ "actions", "esUiShared", + "kibanaReact", "triggersActionsUi" ], "extraPublicDirs": [ diff --git a/x-pack/plugins/stack_connectors/public/common/experimental_features_service.ts b/x-pack/plugins/stack_connectors/public/common/experimental_features_service.ts new file mode 100644 index 0000000000000..c701af1376dbb --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/common/experimental_features_service.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ExperimentalFeatures } from '../../common/experimental_features'; + +export class ExperimentalFeaturesService { + private static experimentalFeatures?: ExperimentalFeatures; + + public static init({ experimentalFeatures }: { experimentalFeatures: ExperimentalFeatures }) { + this.experimentalFeatures = experimentalFeatures; + } + + public static get(): ExperimentalFeatures { + if (!this.experimentalFeatures) { + this.throwUninitializedError(); + } + + return this.experimentalFeatures; + } + + private static throwUninitializedError(): never { + throw new Error( + 'Experimental features services not initialized - are you trying to import this module from outside of the stack connectors?' + ); + } +} diff --git a/x-pack/plugins/stack_connectors/public/common/get_experimental_features.ts b/x-pack/plugins/stack_connectors/public/common/get_experimental_features.ts new file mode 100644 index 0000000000000..41a321bbd2981 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/common/get_experimental_features.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + ExperimentalFeatures, + isValidExperimentalValue, + getExperimentalAllowedValues, +} from '../../common/experimental_features'; +import { ExperimentalFeaturesService } from './experimental_features_service'; + +const allowedExperimentalValueKeys = getExperimentalAllowedValues(); + +export const getIsExperimentalFeatureEnabled = (feature: keyof ExperimentalFeatures): boolean => { + if (!isValidExperimentalValue(feature)) { + throw new Error( + `Invalid enable value ${feature}. Allowed values are: ${allowedExperimentalValueKeys.join( + ', ' + )}` + ); + } + + return ExperimentalFeaturesService.get()[feature]; +}; diff --git a/x-pack/plugins/stack_connectors/public/components/text_area_with_autocomplete.tsx b/x-pack/plugins/stack_connectors/public/components/text_area_with_autocomplete.tsx new file mode 100644 index 0000000000000..0f51b3ced86c6 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/components/text_area_with_autocomplete.tsx @@ -0,0 +1,366 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState, useMemo, useCallback, useEffect } from 'react'; +import getCaretCoordinates from 'textarea-caret'; +import { Properties } from 'csstype'; +import { + EuiTextArea, + EuiFormRow, + EuiSelectable, + EuiSelectableOption, + EuiPortal, + EuiHighlight, + EuiOutsideClickDetector, + useEuiTheme, + useEuiBackgroundColor, +} from '@elastic/eui'; +import { ActionVariable } from '@kbn/alerting-plugin/common'; +import { AddMessageVariables } from '@kbn/alerts-ui-shared'; +import { euiThemeVars } from '@kbn/ui-theme'; +import { filterSuggestions } from '../lib/filter_suggestions_for_autocomplete'; +import { templateActionVariable } from '../lib/template_action_variable'; + +export interface TextAreaWithAutocompleteProps { + editAction: (property: string, value: any, index: number) => void; + errors?: string[]; + index: number; + inputTargetValue?: string; + isDisabled?: boolean; + label: string; + messageVariables?: ActionVariable[]; + paramsProperty: string; +} +const selectableListProps = { className: 'euiSelectableMsgAutoComplete' }; + +export const TextAreaWithAutocomplete: React.FunctionComponent = ({ + editAction, + errors, + index, + inputTargetValue, + isDisabled = false, + label, + messageVariables, + paramsProperty, +}) => { + const { euiTheme } = useEuiTheme(); + const backgroundColor = useEuiBackgroundColor('plain'); + + const textAreaRef = React.useRef(null); + const selectableRef = React.useRef(null); + + const [matches, setMatches] = useState([]); + const [popupPosition, setPopupPosition] = useState({ top: 0, left: 0, height: 0, width: 0 }); + const [isListOpen, setListOpen] = useState(false); + const [autoCompleteIndex, setAutoCompleteIndex] = useState(-1); + const [selectableHasFocus, setSelectableHasFocus] = useState(false); + const [searchWord, setSearchWord] = useState(''); + + const optionsToShow: EuiSelectableOption[] = useMemo(() => { + return matches?.map((variable) => ({ + label: variable, + data: { + description: variable, + }, + 'data-test-subj': `${variable}-selectableOption`, + })); + }, [matches]); + + const closeList = useCallback((doNotResetAutoCompleteIndex = false) => { + if (!doNotResetAutoCompleteIndex) { + setAutoCompleteIndex(-1); + } + setListOpen(false); + setSelectableHasFocus(false); + }, []); + + const onOptionPick = useCallback( + (newOptions: EuiSelectableOption[]) => { + if (!textAreaRef.current) return; + const { value, selectionStart, scrollTop } = textAreaRef.current; + const lastSpaceIndex = value.slice(0, selectionStart).lastIndexOf(' '); + const lastOpenDoubleCurlyBracketsIndex = value.slice(0, selectionStart).lastIndexOf('{{'); + const currentWordStartIndex = Math.max(lastSpaceIndex, lastOpenDoubleCurlyBracketsIndex); + + const checkedElement = newOptions.find(({ checked }) => checked === 'on'); + if (checkedElement) { + const newInputText = + value.slice(0, currentWordStartIndex) + + '{{' + + checkedElement.label + + '}}' + + value.slice(selectionStart); + + editAction(paramsProperty, newInputText.trim(), index); + setMatches([]); + closeList(); + textAreaRef.current.focus(); + // We use setTimeout here, because editAction is async function and we need to wait before it executes + setTimeout(() => { + if (textAreaRef.current) { + textAreaRef.current.selectionStart = + currentWordStartIndex + checkedElement.label.length + 4; + textAreaRef.current.selectionEnd = textAreaRef.current.selectionStart; + textAreaRef.current.scrollTop = scrollTop; + } + }, 0); + } + }, + [editAction, index, paramsProperty, closeList] + ); + + const recalcMenuPosition = useCallback(() => { + if (!textAreaRef.current) return; + const newPosition = getCaretCoordinates( + textAreaRef.current, + textAreaRef.current.selectionStart + ); + const textAreaClientRect = textAreaRef.current?.getBoundingClientRect(); + + const top = + textAreaClientRect.top - + textAreaRef.current.scrollTop + + window.scrollY + + newPosition.top + + newPosition.height; + const left = textAreaClientRect.left + window.pageXOffset; + const height = newPosition.height; + const width = textAreaClientRect.width; + setPopupPosition({ top, left, width, height }); + setListOpen(true); + }, []); + + const onChangeWithMessageVariable = useCallback(() => { + if (!textAreaRef.current) return; + const { value, selectionStart } = textAreaRef.current; + const lastTwoLetter = value.slice(selectionStart - 2, selectionStart); + + const currentWord = + autoCompleteIndex !== -1 ? value.slice(autoCompleteIndex, selectionStart) : ''; + + if (lastTwoLetter === '{{' || currentWord.startsWith('{{')) { + if (lastTwoLetter === '{{') { + setAutoCompleteIndex(selectionStart - 2); + } + const filteredMatches = filterSuggestions({ + actionVariablesList: messageVariables + ?.filter(({ deprecated }) => !deprecated) + .map(({ name }) => name), + propertyPath: currentWord.slice(2), + }); + setSearchWord(currentWord.slice(2)); + setMatches(filteredMatches); + setTimeout(() => recalcMenuPosition(), 0); + } else if (lastTwoLetter === '}}') { + closeList(); + } else { + setMatches([]); + } + editAction(paramsProperty, value, index); + }, [ + autoCompleteIndex, + closeList, + editAction, + index, + messageVariables, + paramsProperty, + recalcMenuPosition, + ]); + + const textareaOnKeyPress = useCallback( + (event) => { + if (selectableRef.current && isListOpen) { + if (!selectableHasFocus && (event.code === 'ArrowUp' || event.code === 'ArrowDown')) { + event.preventDefault(); + event.stopPropagation(); + selectableRef.current.onFocus(); + setSelectableHasFocus(true); + } else if (event.code === 'ArrowUp') { + event.preventDefault(); + event.stopPropagation(); + selectableRef.current.incrementActiveOptionIndex(-1); + } else if (event.code === 'ArrowDown') { + event.preventDefault(); + event.stopPropagation(); + selectableRef.current.incrementActiveOptionIndex(1); + } else if (event.code === 'Escape') { + event.preventDefault(); + event.stopPropagation(); + closeList(); + } else if (event.code === 'Enter' || event.code === 'Space') { + const optionIndex = selectableRef.current.state.activeOptionIndex; + onOptionPick( + optionsToShow.map((ots, idx) => { + if (idx === optionIndex) { + return { + ...ots, + checked: 'on', + }; + } + return ots; + }) + ); + closeList(); + } + } else { + setSelectableHasFocus((prevValue) => { + if (prevValue) { + return false; + } + return prevValue; + }); + } + }, + [closeList, isListOpen, onOptionPick, optionsToShow, selectableHasFocus] + ); + + const clickOutSideTextArea = useCallback( + (event) => { + const box = document + .querySelector('.euiSelectableMsgAutoComplete') + ?.getBoundingClientRect() || { + left: 0, + right: 0, + top: 0, + bottom: 0, + }; + if ( + event.clientX > box.left && + event.clientX < box.right && + event.clientY > box.top && + event.clientY < box.bottom + ) { + return; + } + closeList(); + }, + [closeList] + ); + + const onSelectMessageVariable = useCallback( + (variable: ActionVariable) => { + if (!textAreaRef.current) return; + const { selectionStart: startPosition, selectionEnd: endPosition } = textAreaRef.current; + const templatedVar = templateActionVariable(variable); + + const newValue = + (inputTargetValue ?? '').substring(0, startPosition) + + templatedVar + + (inputTargetValue ?? '').substring(endPosition, (inputTargetValue ?? '').length); + + editAction(paramsProperty, newValue, index); + }, + [editAction, index, inputTargetValue, paramsProperty] + ); + + const renderSelectableOption = (option: any) => { + if (searchWord) { + return {option.label}; + } + return option.label; + }; + + const selectableStyle: Properties = useMemo( + () => ({ + position: 'absolute', + top: popupPosition.top, + width: popupPosition.width, + left: popupPosition.left, + border: `${euiTheme.border.width.thin} solid ${euiTheme.border.color}`, + background: backgroundColor, + zIndex: euiThemeVars.euiZLevel1, + }), + [ + backgroundColor, + euiTheme.border.color, + euiTheme.border.width.thin, + popupPosition.left, + popupPosition.top, + popupPosition.width, + ] + ); + + const onFocus = useCallback(() => setListOpen(true), []); + const onBlur = useCallback(() => { + if (!inputTargetValue && !isListOpen) { + editAction(paramsProperty, '', index); + } + }, [editAction, index, inputTargetValue, isListOpen, paramsProperty]); + const onClick = useCallback(() => closeList(), [closeList]); + + const onScroll = useCallback( + (evt) => { + // FUTURE ENGINEER -> we need to make sure to not close the autocomplete option list + if (selectableRef?.current?.listId !== evt.target?.firstElementChild?.id) { + closeList(true); + } + }, + [closeList] + ); + + useEffect(() => { + window.addEventListener('scroll', onScroll, { passive: true, capture: true }); + return () => { + window.removeEventListener('scroll', onScroll, { capture: true }); + }; + }, [onScroll]); + + return ( + 0 && inputTargetValue !== undefined} + label={label} + labelAppend={ + + } + > + <> + + 0 && inputTargetValue !== undefined} + name={paramsProperty} + value={inputTargetValue || ''} + data-test-subj={`${paramsProperty}TextArea`} + onChange={onChangeWithMessageVariable} + onFocus={onFocus} + onKeyDown={textareaOnKeyPress} + onBlur={onBlur} + onClick={onClick} + /> + + {matches.length > 0 && isListOpen && ( + + 5 ? 32 * 5.5 : matches.length * 32} + options={optionsToShow} + onChange={onOptionPick} + singleSelection + renderOption={renderSelectableOption} + listProps={selectableListProps} + > + {(list) => list} + + + )} + + + ); +}; + +// eslint-disable-next-line import/no-default-export +export { TextAreaWithAutocomplete as default }; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/email/email_params.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/email/email_params.test.tsx index a0a41a57a8331..76cc3b136455a 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/email/email_params.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/email/email_params.test.tsx @@ -7,10 +7,35 @@ import React from 'react'; import { mountWithIntl } from '@kbn/test-jest-helpers'; +import { render, fireEvent, screen } from '@testing-library/react'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { triggersActionsUiMock } from '@kbn/triggers-actions-ui-plugin/public/mocks'; import EmailParamsFields from './email_params'; +import { getIsExperimentalFeatureEnabled } from '../../common/get_experimental_features'; + +jest.mock('@kbn/kibana-react-plugin/public', () => ({ + useKibana: jest.fn(), +})); +jest.mock('../../common/get_experimental_features'); + +const useKibanaMock = useKibana as jest.Mock; +const mockKibana = () => { + useKibanaMock.mockReturnValue({ + services: { + triggersActionsUi: triggersActionsUiMock.createStart(), + }, + }); +}; describe('EmailParamsFields renders', () => { - test('all params fields is rendered', () => { + beforeEach(() => { + jest.clearAllMocks(); + mockKibana(); + (getIsExperimentalFeatureEnabled as jest.Mock).mockImplementation(() => true); + }); + + test('all params fields is rendered', async () => { const actionParams = { cc: [], bcc: [], @@ -19,21 +44,22 @@ describe('EmailParamsFields renders', () => { message: 'test message', }; - const wrapper = mountWithIntl( - {}} - index={0} - /> + render( + + {}} + defaultMessage={'Some default message'} + index={0} + /> + ); - expect(wrapper.find('[data-test-subj="toEmailAddressInput"]').length > 0).toBeTruthy(); - expect( - wrapper.find('[data-test-subj="toEmailAddressInput"]').first().prop('selectedOptions') - ).toStrictEqual([{ label: 'test@test.com' }]); - expect(wrapper.find('[data-test-subj="subjectInput"]').length > 0).toBeTruthy(); - expect(wrapper.find('[data-test-subj="messageTextArea"]').length > 0).toBeTruthy(); + expect(screen.getByTestId('toEmailAddressInput')).toBeVisible(); + expect(screen.getByTestId('toEmailAddressInput').textContent).toStrictEqual('test@test.com'); + expect(screen.getByTestId('subjectInput')).toBeVisible(); + expect(await screen.findByTestId('messageTextArea')).toBeVisible(); }); test('message param field is rendered with default value if not set', () => { @@ -95,36 +121,57 @@ describe('EmailParamsFields renders', () => { }; const editAction = jest.fn(); - const wrapper = mountWithIntl( - + const { rerender } = render( + + + ); expect(editAction).toHaveBeenCalledWith('message', 'Some default message', 0); // simulate value being updated const valueToSimulate = 'some new value'; - wrapper - .find('[data-test-subj="messageTextArea"]') - .last() - .simulate('change', { target: { value: valueToSimulate } }); - expect(editAction).toHaveBeenCalledWith('message', valueToSimulate, 0); - wrapper.setProps({ - actionParams: { - ...actionParams, - message: valueToSimulate, - }, + fireEvent.change(screen.getByTestId('messageTextArea'), { + target: { value: valueToSimulate }, }); - // simulate default changing - wrapper.setProps({ - defaultMessage: 'Some different default message', - }); + expect(editAction).toHaveBeenCalledWith('message', valueToSimulate, 0); + + rerender( + + + + ); + + rerender( + + + + ); expect(editAction).not.toHaveBeenCalledWith('message', 'Some different default message', 0); }); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/email/email_params.tsx b/x-pack/plugins/stack_connectors/public/connector_types/email/email_params.tsx index a8df45ba0e33f..2100e2b0d823c 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/email/email_params.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/email/email_params.tsx @@ -5,16 +5,18 @@ * 2.0. */ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiComboBox, EuiButtonEmpty, EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import type { ActionParamsProps } from '@kbn/triggers-actions-ui-plugin/public'; import { - TextAreaWithMessageVariables, TextFieldWithMessageVariables, + TextAreaWithMessageVariables, } from '@kbn/triggers-actions-ui-plugin/public'; import { EmailActionParams } from '../types'; +import { getIsExperimentalFeatureEnabled } from '../../common/get_experimental_features'; +import { TextAreaWithAutocomplete } from '../../components/text_area_with_autocomplete'; const noop = () => {}; @@ -31,6 +33,7 @@ export const EmailParamsFields = ({ showEmailSubjectAndMessage = true, useDefaultMessage, }: ActionParamsProps) => { + const isMustacheAutocompleteOn = getIsExperimentalFeatureEnabled('isMustacheAutocompleteOn'); const { to, cc, bcc, subject, message } = actionParams; const toOptions = to ? to.map((label: string) => ({ label })) : []; const ccOptions = cc ? cc.map((label: string) => ({ label })) : []; @@ -60,6 +63,11 @@ export const EmailParamsFields = ({ const isCCInvalid: boolean = errors.cc !== undefined && errors.cc.length > 0 && cc !== undefined; const isBCCInvalid: boolean = errors.bcc !== undefined && errors.bcc.length > 0 && bcc !== undefined; + + const TextAreaComponent = useMemo(() => { + return isMustacheAutocompleteOn ? TextAreaWithAutocomplete : TextAreaWithMessageVariables; + }, [isMustacheAutocompleteOn]); + return ( <> )} {showEmailSubjectAndMessage && ( - new StackConnectorsPublicPlugin(); +export const plugin = (context: PluginInitializerContext) => + new StackConnectorsPublicPlugin(context); diff --git a/x-pack/plugins/stack_connectors/public/lib/filter_suggestions_for_autocomplete.test.ts b/x-pack/plugins/stack_connectors/public/lib/filter_suggestions_for_autocomplete.test.ts new file mode 100644 index 0000000000000..b51aa70475515 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/lib/filter_suggestions_for_autocomplete.test.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { filterSuggestions } from './filter_suggestions_for_autocomplete'; + +const defaultActionVariablesList = [ + 'kibana.alert.id', + 'kibana.context.cloud.group', + 'context.container', + 'context.originalAlertState', + 'date', + 'rule.spaceId', + 'kibana.alertActionGroup', + 'tags', +]; +describe('Unit tests for filterSuggestions function', () => { + test('should return empty list if actionVariablesList argument is undefined', () => { + expect(filterSuggestions({ propertyPath: 'alert.id' })).toEqual([]); + }); + + test('should return full sorted list of suggestions if propertyPath is empty string', () => { + expect( + filterSuggestions({ actionVariablesList: defaultActionVariablesList, propertyPath: '' }) + ).toEqual([ + 'context', + 'context.container', + 'context.originalAlertState', + 'date', + 'kibana', + 'kibana.alert', + 'kibana.alert.id', + 'kibana.alertActionGroup', + 'kibana.context', + 'kibana.context.cloud', + 'kibana.context.cloud.group', + 'rule', + 'rule.spaceId', + 'tags', + ]); + }); + + test('should return sorted of filtered suggestions, v1', () => { + expect( + filterSuggestions({ actionVariablesList: defaultActionVariablesList, propertyPath: 'ki' }) + ).toEqual([ + 'kibana', + 'kibana.alert', + 'kibana.alert.id', + 'kibana.alertActionGroup', + 'kibana.context', + 'kibana.context.cloud', + 'kibana.context.cloud.group', + ]); + }); + + test('should return sorted of filtered suggestions, v2', () => { + expect( + filterSuggestions({ + actionVariablesList: defaultActionVariablesList, + propertyPath: 'kibana.al', + }) + ).toEqual(['kibana.alert', 'kibana.alert.id', 'kibana.alertActionGroup']); + }); + + test('should return sorted of filtered suggestions, v3', () => { + expect( + filterSuggestions({ + actionVariablesList: defaultActionVariablesList, + propertyPath: 'kibana.context.cloud.g', + }) + ).toEqual(['kibana.context.cloud.group']); + }); +}); diff --git a/x-pack/plugins/stack_connectors/public/lib/filter_suggestions_for_autocomplete.ts b/x-pack/plugins/stack_connectors/public/lib/filter_suggestions_for_autocomplete.ts new file mode 100644 index 0000000000000..80362d2b6770b --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/lib/filter_suggestions_for_autocomplete.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const filterSuggestions = ({ + actionVariablesList, + propertyPath, +}: { + actionVariablesList?: string[]; + propertyPath: string; +}) => { + if (!actionVariablesList) return []; + const allSuggestions: string[] = []; + actionVariablesList.forEach((suggestion: string) => { + const splittedWords = suggestion.split('.'); + for (let i = 0; i < splittedWords.length; i++) { + const currentSuggestion = splittedWords.slice(0, i + 1).join('.'); + if (!allSuggestions.includes(currentSuggestion)) { + allSuggestions.push(currentSuggestion); + } + } + }); + return allSuggestions.sort().filter((suggestion) => suggestion.startsWith(propertyPath)); +}; diff --git a/x-pack/plugins/stack_connectors/public/lib/template_action_variable.test.ts b/x-pack/plugins/stack_connectors/public/lib/template_action_variable.test.ts new file mode 100644 index 0000000000000..3ac06967b641a --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/lib/template_action_variable.test.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { templateActionVariable } from './template_action_variable'; + +describe('templateActionVariable', () => { + const actionVariable = { + name: 'myVar', + description: 'My variable description', + }; + + test('variable returns with double braces by default', () => { + expect(templateActionVariable(actionVariable)).toEqual('{{myVar}}'); + }); + + test('variable returns with triple braces when specified', () => { + expect( + templateActionVariable({ ...actionVariable, useWithTripleBracesInTemplates: true }) + ).toEqual('{{{myVar}}}'); + }); +}); diff --git a/x-pack/plugins/stack_connectors/public/lib/template_action_variable.ts b/x-pack/plugins/stack_connectors/public/lib/template_action_variable.ts new file mode 100644 index 0000000000000..887564a8213c4 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/lib/template_action_variable.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ActionVariable } from '@kbn/alerting-plugin/common'; + +export function templateActionVariable(variable: ActionVariable) { + return variable.useWithTripleBracesInTemplates + ? `{{{${variable.name}}}}` + : `{{${variable.name}}}`; +} diff --git a/x-pack/plugins/stack_connectors/public/plugin.ts b/x-pack/plugins/stack_connectors/public/plugin.ts index bc9d855a14303..3c153d0de2573 100644 --- a/x-pack/plugins/stack_connectors/public/plugin.ts +++ b/x-pack/plugins/stack_connectors/public/plugin.ts @@ -5,10 +5,16 @@ * 2.0. */ -import { CoreSetup, Plugin } from '@kbn/core/public'; +import { CoreSetup, Plugin, PluginInitializerContext } from '@kbn/core/public'; import { TriggersAndActionsUIPublicPluginSetup } from '@kbn/triggers-actions-ui-plugin/public'; import { ActionsPublicPluginSetup } from '@kbn/actions-plugin/public'; import { registerConnectorTypes } from './connector_types'; +import { ExperimentalFeaturesService } from './common/experimental_features_service'; +import { + ExperimentalFeatures, + parseExperimentalConfigValue, +} from '../common/experimental_features'; +import { StackConnectorsConfigType } from '../common/types'; export type Setup = void; export type Start = void; @@ -21,6 +27,13 @@ export interface StackConnectorsPublicSetupDeps { export class StackConnectorsPublicPlugin implements Plugin { + private config: StackConnectorsConfigType; + readonly experimentalFeatures: ExperimentalFeatures; + + constructor(ctx: PluginInitializerContext) { + this.config = ctx.config.get(); + this.experimentalFeatures = parseExperimentalConfigValue(this.config.enableExperimental || []); + } public setup(core: CoreSetup, { triggersActionsUi, actions }: StackConnectorsPublicSetupDeps) { registerConnectorTypes({ connectorTypeRegistry: triggersActionsUi.actionTypeRegistry, @@ -28,6 +41,7 @@ export class StackConnectorsPublicPlugin validateEmailAddresses: actions.validateEmailAddresses, }, }); + ExperimentalFeaturesService.init({ experimentalFeatures: this.experimentalFeatures }); } public start() {} diff --git a/x-pack/plugins/stack_connectors/server/config.ts b/x-pack/plugins/stack_connectors/server/config.ts new file mode 100644 index 0000000000000..d58e58b0e450c --- /dev/null +++ b/x-pack/plugins/stack_connectors/server/config.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; +import { PluginInitializerContext } from '@kbn/core/server'; + +import { + ExperimentalFeatures, + getExperimentalAllowedValues, + isValidExperimentalValue, + parseExperimentalConfigValue, +} from '../common/experimental_features'; + +const allowedExperimentalValues = getExperimentalAllowedValues(); + +export const configSchema = schema.object({ + enableExperimental: schema.arrayOf(schema.string(), { + defaultValue: () => [], + validate(list) { + for (const key of list) { + if (!isValidExperimentalValue(key)) { + return `[${key}] is not allowed. Allowed values are: ${allowedExperimentalValues.join( + ', ' + )}`; + } + } + }, + }), +}); + +export type ConfigSchema = TypeOf; + +export type ConfigType = ConfigSchema & { + experimentalFeatures: ExperimentalFeatures; +}; + +export const createConfig = (context: PluginInitializerContext): ConfigType => { + const pluginConfig = context.config.get>(); + const experimentalFeatures = parseExperimentalConfigValue(pluginConfig.enableExperimental); + + return { + ...pluginConfig, + experimentalFeatures, + }; +}; diff --git a/x-pack/plugins/stack_connectors/server/index.ts b/x-pack/plugins/stack_connectors/server/index.ts index 2cc792da9f9a3..c41b13ee2b15c 100644 --- a/x-pack/plugins/stack_connectors/server/index.ts +++ b/x-pack/plugins/stack_connectors/server/index.ts @@ -4,8 +4,16 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { PluginInitializerContext } from '@kbn/core/server'; +import { PluginConfigDescriptor, PluginInitializerContext } from '@kbn/core/server'; import { StackConnectorsPlugin } from './plugin'; +import { configSchema, ConfigSchema } from './config'; + +export const config: PluginConfigDescriptor = { + exposeToBrowser: { + enableExperimental: true, + }, + schema: configSchema, +}; export const plugin = (initContext: PluginInitializerContext) => new StackConnectorsPlugin(initContext); diff --git a/x-pack/plugins/stack_connectors/tsconfig.json b/x-pack/plugins/stack_connectors/tsconfig.json index f18dfaea77cca..0009b86c8348c 100644 --- a/x-pack/plugins/stack_connectors/tsconfig.json +++ b/x-pack/plugins/stack_connectors/tsconfig.json @@ -33,7 +33,10 @@ "@kbn/core-saved-objects-common", "@kbn/core-http-browser-mocks", "@kbn/core-saved-objects-api-server-mocks", + "@kbn/alerts-ui-shared", + "@kbn/alerting-plugin", "@kbn/securitysolution-ecs", + "@kbn/ui-theme", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 178d5f003f81c..9ade8eaf3ee41 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -38430,15 +38430,6 @@ "xpack.triggersActionsUI.common.expressionItems.threshold.andLabel": "AND", "xpack.triggersActionsUI.common.expressionItems.threshold.descriptionLabel": "quand", "xpack.triggersActionsUI.common.expressionItems.threshold.popoverTitle": "quand", - "xpack.triggersActionsUI.components.addMessageVariables.addRuleVariableTitle": "Ajouter une variable", - "xpack.triggersActionsUI.components.addMessageVariables.addVariablePopoverButton": "Ajouter une variable", - "xpack.triggersActionsUI.components.addMessageVariables.deprecatedVariablesAreHidden": "Les variables déclassées sont masquées", - "xpack.triggersActionsUI.components.addMessageVariables.deprecatedVariablesAreShown": "Les variables déclassées sont affichées", - "xpack.triggersActionsUI.components.addMessageVariables.hideDeprecatedVariables": "Masquer", - "xpack.triggersActionsUI.components.addMessageVariables.loadingMessage": "Chargement des variables", - "xpack.triggersActionsUI.components.addMessageVariables.noVariablesAvailable": "Aucune variable disponible", - "xpack.triggersActionsUI.components.addMessageVariables.noVariablesFound": "Aucune variable trouvée", - "xpack.triggersActionsUI.components.addMessageVariables.showAllDeprecatedVariables": "Afficher tout", "xpack.triggersActionsUI.components.alertTable.useFetchAlerts.errorMessageText": "Une erreur s'est produite lors de la recherche des alertes", "xpack.triggersActionsUI.components.alertTable.useFetchBrowserFieldsCapabilities.errorMessageText": "Une erreur s'est produite lors du chargement des champs du navigateur", "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.chooseLabel": "Choisir…", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index aca7c6cf736d0..81c28867cb7bc 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -38421,15 +38421,6 @@ "xpack.triggersActionsUI.common.expressionItems.threshold.andLabel": "AND", "xpack.triggersActionsUI.common.expressionItems.threshold.descriptionLabel": "タイミング", "xpack.triggersActionsUI.common.expressionItems.threshold.popoverTitle": "タイミング", - "xpack.triggersActionsUI.components.addMessageVariables.addRuleVariableTitle": "変数を追加", - "xpack.triggersActionsUI.components.addMessageVariables.addVariablePopoverButton": "変数を追加", - "xpack.triggersActionsUI.components.addMessageVariables.deprecatedVariablesAreHidden": "廃止予定の変数は非表示です", - "xpack.triggersActionsUI.components.addMessageVariables.deprecatedVariablesAreShown": "廃止予定の変数は表示されます", - "xpack.triggersActionsUI.components.addMessageVariables.hideDeprecatedVariables": "非表示", - "xpack.triggersActionsUI.components.addMessageVariables.loadingMessage": "変数を読み込み中", - "xpack.triggersActionsUI.components.addMessageVariables.noVariablesAvailable": "変数がありません", - "xpack.triggersActionsUI.components.addMessageVariables.noVariablesFound": "変数が見つかりません", - "xpack.triggersActionsUI.components.addMessageVariables.showAllDeprecatedVariables": "すべて表示", "xpack.triggersActionsUI.components.alertTable.useFetchAlerts.errorMessageText": "アラート検索でエラーが発生しました", "xpack.triggersActionsUI.components.alertTable.useFetchBrowserFieldsCapabilities.errorMessageText": "ブラウザーフィールドの読み込み中にエラーが発生しました", "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.chooseLabel": "選択…", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index cf5593d5334e3..5a7410a88fcee 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -38415,15 +38415,6 @@ "xpack.triggersActionsUI.common.expressionItems.threshold.andLabel": "且", "xpack.triggersActionsUI.common.expressionItems.threshold.descriptionLabel": "当", "xpack.triggersActionsUI.common.expressionItems.threshold.popoverTitle": "当", - "xpack.triggersActionsUI.components.addMessageVariables.addRuleVariableTitle": "添加变量", - "xpack.triggersActionsUI.components.addMessageVariables.addVariablePopoverButton": "添加变量", - "xpack.triggersActionsUI.components.addMessageVariables.deprecatedVariablesAreHidden": "将隐藏已弃用变量", - "xpack.triggersActionsUI.components.addMessageVariables.deprecatedVariablesAreShown": "将显示已弃用变量", - "xpack.triggersActionsUI.components.addMessageVariables.hideDeprecatedVariables": "隐藏", - "xpack.triggersActionsUI.components.addMessageVariables.loadingMessage": "正在加载变量", - "xpack.triggersActionsUI.components.addMessageVariables.noVariablesAvailable": "无变量可用", - "xpack.triggersActionsUI.components.addMessageVariables.noVariablesFound": "找不到变量", - "xpack.triggersActionsUI.components.addMessageVariables.showAllDeprecatedVariables": "全部显示", "xpack.triggersActionsUI.components.alertTable.useFetchAlerts.errorMessageText": "搜索告警时发生错误", "xpack.triggersActionsUI.components.alertTable.useFetchBrowserFieldsCapabilities.errorMessageText": "加载浏览器字段时出错", "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.chooseLabel": "选择……", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/index.ts index 41ca1f51c735a..08ab85eece444 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/index.ts @@ -4,7 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - export { JsonEditorWithMessageVariables } from './json_editor_with_message_variables'; export { TextFieldWithMessageVariables } from './text_field_with_message_variables'; export { TextAreaWithMessageVariables } from './text_area_with_message_variables'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/json_editor_with_message_variables.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/json_editor_with_message_variables.tsx index 1acd3e6392450..4459735acb927 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/json_editor_with_message_variables.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/json_editor_with_message_variables.tsx @@ -11,12 +11,11 @@ import { EuiFormRow, EuiCallOut, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { monaco, XJsonLang } from '@kbn/monaco'; -import './add_message_variables.scss'; import { XJson } from '@kbn/es-ui-shared-plugin/public'; import { CodeEditor } from '@kbn/kibana-react-plugin/public'; import { ActionVariable } from '@kbn/alerting-plugin/common'; -import { AddMessageVariables } from './add_message_variables'; +import { AddMessageVariables } from '@kbn/alerts-ui-shared'; import { templateActionVariable } from '../lib'; const NO_EDITOR_ERROR_TITLE = i18n.translate( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/text_area_with_message_variables.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/text_area_with_message_variables.tsx index 346dce44af60b..c19ac416b4695 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/text_area_with_message_variables.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/text_area_with_message_variables.tsx @@ -7,9 +7,8 @@ import React, { useState } from 'react'; import { EuiTextArea, EuiFormRow } from '@elastic/eui'; -import './add_message_variables.scss'; import { ActionVariable } from '@kbn/alerting-plugin/common'; -import { AddMessageVariables } from './add_message_variables'; +import { AddMessageVariables } from '@kbn/alerts-ui-shared'; import { templateActionVariable } from '../lib'; interface Props { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/text_field_with_message_variables.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/text_field_with_message_variables.tsx index 0efe53603085e..e2dc816987d3d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/text_field_with_message_variables.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/text_field_with_message_variables.tsx @@ -7,9 +7,8 @@ import React, { useCallback, useMemo, useState } from 'react'; import { EuiFieldText, EuiFormRow } from '@elastic/eui'; -import './add_message_variables.scss'; import { ActionVariable } from '@kbn/alerting-plugin/common'; -import { AddMessageVariables } from './add_message_variables'; +import { AddMessageVariables } from '@kbn/alerts-ui-shared'; import { templateActionVariable } from '../lib'; interface Props { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/validate_params_for_warnings.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/validate_params_for_warnings.ts index e0f552f6bb0f7..5a5dc6f33e076 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/validate_params_for_warnings.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/validate_params_for_warnings.ts @@ -43,10 +43,10 @@ export function validateParamsForWarnings( return publicUrlWarning; } } catch (e) { - /* - * do nothing, we don't care if the mustache is invalid - */ + // Better to set the warning msg if you do not know if the mustache template is invalid + return publicUrlWarning; } } + return null; } diff --git a/yarn.lock b/yarn.lock index 52e67729e7891..b7b5e99534103 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9780,6 +9780,11 @@ dependencies: "@types/jest" "*" +"@types/textarea-caret@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/textarea-caret/-/textarea-caret-3.0.1.tgz#5afd4b1c1b3bacb001d76a1e6ef192c710709a86" + integrity sha512-JjrXYzk4t6dM/5nz1hHkZXmd3xSdJM6mOIDSBUrpg4xThwKNryiu4CqHx81LwUJHxEEoQWHTu4fMV4em+c5bXg== + "@types/through@*": version "0.0.30" resolved "https://registry.yarnpkg.com/@types/through/-/through-0.0.30.tgz#e0e42ce77e897bd6aead6f6ea62aeb135b8a3895" @@ -28504,6 +28509,11 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= +textarea-caret@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/textarea-caret/-/textarea-caret-3.1.0.tgz#5d5a35bb035fd06b2ff0e25d5359e97f2655087f" + integrity sha512-cXAvzO9pP5CGa6NKx0WYHl+8CHKZs8byMkt3PCJBCmq2a34YA9pO1NrQET5pzeqnBjBdToF5No4rrmkDUgQC2Q== + throttle-debounce@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-2.1.0.tgz#257e648f0a56bd9e54fe0f132c4ab8611df4e1d5"