From 93d45fc6ff5d5e0a8a01b81464f6f36eb6ab7883 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Tue, 28 Jul 2020 18:49:05 -0400 Subject: [PATCH 01/10] [Ingest Manager] Update fleet instructions to run agent as a service (#73491) --- .../enrollment_instructions/manual/index.tsx | 85 ++++++++++++++----- .../translations/translations/ja-JP.json | 2 - .../translations/translations/zh-CN.json | 2 - 3 files changed, 65 insertions(+), 24 deletions(-) diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/enrollment_instructions/manual/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/enrollment_instructions/manual/index.tsx index 78f4f73cf18be87..fe11c4cb08d13ef 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/enrollment_instructions/manual/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/enrollment_instructions/manual/index.tsx @@ -5,7 +5,8 @@ */ import React from 'react'; -import { EuiText, EuiSpacer, EuiCode, EuiCodeBlock, EuiCopy, EuiButton } from '@elastic/eui'; +import styled from 'styled-components'; +import { EuiText, EuiSpacer, EuiCode, EuiTitle, EuiCodeBlock } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { EnrollmentAPIKey } from '../../../types'; @@ -15,42 +16,86 @@ interface Props { kibanaCASha256?: string; } +// Otherwise the copy button is over the text +const CommandCode = styled.pre({ + overflow: 'scroll', +}); + export const ManualInstructions: React.FunctionComponent = ({ kibanaUrl, apiKey, kibanaCASha256, }) => { - const command = ` -./elastic-agent enroll ${kibanaUrl} ${apiKey.api_key}${ + const enrollArgs = `${kibanaUrl} ${apiKey.api_key}${ kibanaCASha256 ? ` --ca_sha256=${kibanaCASha256}` : '' - } + }`; + const macOsLinuxTarCommand = `./elastic-agent enroll ${enrollArgs} ./elastic-agent run`; + + const linuxDebRpmCommand = `./elastic-agent enroll ${enrollArgs} +systemctl enable elastic-agent +systemctl start elastic-agent`; + + const windowsCommand = `./elastic-agent enroll ${enrollArgs} +./install-service-elastic-agent.ps1`; + return ( <> + + + +

+ +

+
+ + + {windowsCommand} + + + +

+ +

+
+ + + {linuxDebRpmCommand} + + + +

+ +

+
+ + + agent enroll, + command: ./elastic-agent run, }} /> - - -
{command}
+ + + {macOsLinuxTarCommand} - - - {(copy) => ( - - - - )} - ); }; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index ee7d1e0298d0013..b6aaa2065c79541 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -8177,8 +8177,6 @@ "xpack.ingestManager.editPackageConfig.updatedNotificationTitle": "正常に'{packageConfigName}'を更新しました", "xpack.ingestManager.enrollemntAPIKeyList.emptyMessage": "登録トークンが見つかりません。", "xpack.ingestManager.enrollemntAPIKeyList.loadingTokensMessage": "登録トークンを読み込んでいます...", - "xpack.ingestManager.enrollmentInstructions.copyButton": "コマンドをコピー", - "xpack.ingestManager.enrollmentInstructions.descriptionText": "エージェントのディレクトリから、これらのコマンドを実行して、Elasticエージェントを登録して起動します。{enrollCommand}はエージェントの構成ファイルに書き込み、正しい設定になるようにします。このコマンドを使用すると、複数のホストでエージェントを設定できます。", "xpack.ingestManager.enrollmentStepAgentConfig.configSelectAriaLabel": "エージェント構成", "xpack.ingestManager.enrollmentStepAgentConfig.configSelectLabel": "エージェント構成", "xpack.ingestManager.enrollmentStepAgentConfig.enrollmentTokenSelectLabel": "登録トークン", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 30c932c362a4f56..dcbcda120587fa5 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -8182,8 +8182,6 @@ "xpack.ingestManager.editPackageConfig.updatedNotificationTitle": "已成功更新“{packageConfigName}”", "xpack.ingestManager.enrollemntAPIKeyList.emptyMessage": "未找到任何注册令牌。", "xpack.ingestManager.enrollemntAPIKeyList.loadingTokensMessage": "正在加载注册令牌......", - "xpack.ingestManager.enrollmentInstructions.copyButton": "复制命令", - "xpack.ingestManager.enrollmentInstructions.descriptionText": "从代理的目录,运行这些命令以注册并启动 Elastic 代理。{enrollCommand} 将写入代理的配置文件,以便其具有正确的设置。可以使用此命令在多个主机上设置代理。", "xpack.ingestManager.enrollmentStepAgentConfig.configSelectAriaLabel": "代理配置", "xpack.ingestManager.enrollmentStepAgentConfig.configSelectLabel": "代理配置", "xpack.ingestManager.enrollmentStepAgentConfig.enrollmentTokenSelectLabel": "注册令牌", From 7a3e800aaab50969f7d53ac77f781a2544463f06 Mon Sep 17 00:00:00 2001 From: Clint Andrew Hall Date: Tue, 28 Jul 2020 18:58:58 -0400 Subject: [PATCH 02/10] [Canvas][tech-debt] Kill Recompose:Pure - Part 1 (#73303) Co-authored-by: Elastic Machine --- .../arg_add/{arg_add.js => arg_add.tsx} | 14 +- .../components/arg_add/{index.js => index.ts} | 6 +- .../arg_add_popover/arg_add_popover.tsx | 7 +- .../components/arg_add_popover/index.ts | 5 +- .../asset_manager.stories.storyshot | 539 ---------- .../public/components/color_dot/color_dot.tsx | 12 +- .../public/components/color_dot/index.ts | 7 +- .../color_manager/color_manager.tsx | 22 +- .../public/components/color_manager/index.ts | 7 +- .../color_palette/color_palette.tsx | 8 +- .../public/components/color_palette/index.ts | 6 +- .../components/color_picker/color_picker.tsx | 5 +- .../public/components/color_picker/index.ts | 7 +- .../color_picker_popover.tsx | 9 +- .../components/color_picker_popover/index.ts | 7 +- .../datatable/{datatable.js => datatable.tsx} | 33 +- .../datatable/{index.js => index.ts} | 5 +- .../font_picker.stories.storyshot | 0 .../{ => __stories__}/font_picker.stories.tsx | 4 +- .../components/font_picker/font_picker.tsx | 14 +- .../font_picker/{index.js => index.ts} | 6 +- .../canvas/public/components/loading/index.ts | 5 +- .../public/components/loading/loading.tsx | 6 +- .../public/components/shape_picker/index.ts | 6 +- .../components/shape_picker/shape_picker.tsx | 6 +- .../components/shape_picker_popover/index.tsx | 6 +- .../shape_picker_popover.tsx | 7 +- .../public/components/shape_preview/index.ts | 6 +- .../shape_preview/shape_preview.tsx | 4 +- .../text_style_picker.stories.storyshot | 975 ++++++++++++++++++ .../__stories__/text_style_picker.stories.tsx | 21 + .../{font_sizes.js => font_sizes.ts} | 0 .../text_style_picker/{index.js => index.ts} | 5 +- ..._style_picker.js => text_style_picker.tsx} | 160 +-- .../public/components/tooltip_icon/index.ts | 6 +- .../components/tooltip_icon/tooltip_icon.tsx | 4 +- 36 files changed, 1188 insertions(+), 752 deletions(-) rename x-pack/plugins/canvas/public/components/arg_add/{arg_add.js => arg_add.tsx} (71%) rename x-pack/plugins/canvas/public/components/arg_add/{index.js => index.ts} (66%) rename x-pack/plugins/canvas/public/components/datatable/{datatable.js => datatable.tsx} (78%) rename x-pack/plugins/canvas/public/components/datatable/{index.js => index.ts} (64%) rename x-pack/plugins/canvas/public/components/font_picker/{ => __stories__}/__snapshots__/font_picker.stories.storyshot (100%) rename x-pack/plugins/canvas/public/components/font_picker/{ => __stories__}/font_picker.stories.tsx (84%) rename x-pack/plugins/canvas/public/components/font_picker/{index.js => index.ts} (64%) create mode 100644 x-pack/plugins/canvas/public/components/text_style_picker/__stories__/__snapshots__/text_style_picker.stories.storyshot create mode 100644 x-pack/plugins/canvas/public/components/text_style_picker/__stories__/text_style_picker.stories.tsx rename x-pack/plugins/canvas/public/components/text_style_picker/{font_sizes.js => font_sizes.ts} (100%) rename x-pack/plugins/canvas/public/components/text_style_picker/{index.js => index.ts} (61%) rename x-pack/plugins/canvas/public/components/text_style_picker/{text_style_picker.js => text_style_picker.tsx} (54%) diff --git a/x-pack/plugins/canvas/public/components/arg_add/arg_add.js b/x-pack/plugins/canvas/public/components/arg_add/arg_add.tsx similarity index 71% rename from x-pack/plugins/canvas/public/components/arg_add/arg_add.js rename to x-pack/plugins/canvas/public/components/arg_add/arg_add.tsx index 2d6d7d1046fddd7..e85a2915a82b1c6 100644 --- a/x-pack/plugins/canvas/public/components/arg_add/arg_add.js +++ b/x-pack/plugins/canvas/public/components/arg_add/arg_add.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { FC, ReactEventHandler } from 'react'; import PropTypes from 'prop-types'; import { EuiDescriptionList, @@ -12,7 +12,13 @@ import { EuiDescriptionListDescription, } from '@elastic/eui'; -export const ArgAdd = ({ onValueAdd, displayName, help }) => { +interface Props { + displayName: string; + help: string; + onValueAdd?: ReactEventHandler; +} + +export const ArgAdd: FC = ({ onValueAdd = () => {}, displayName, help }) => { return ( -
-
-
- Manage workpad assets -
-
-
-
-
- -
- -
-
-
-
-
-
-
-
-
-

- Below are the image assets in this workpad. Any assets that are currently in use cannot be determined at this time. To reclaim space, delete assets. -

-
-
-
-
-
-
-
-
- Asset thumbnail -
-
-
-
-

- - airplane - -
- - - ( - 1 - kb) - - -

-
-
-
-
- - - -
-
- -
- -
-
-
-
- -
- -
-
-
-
- - - -
-
-
-
-
-
-
-
- Asset thumbnail -
-
-
-
-

- - marker - -
- - - ( - 1 - kb) - - -

-
-
-
-
- - - -
-
- -
- -
-
-
-
- -
- -
-
-
-
- - - -
-
-
-
-
-
-
-
-
-
- -
-
-
- 0% space used -
-
-
- -
-
-
-
, -
, -] -`; - exports[`Storyshots components/Assets/AssetManager two assets 1`] = ` Array [
= ({ value, children }) => { +export const ColorDot: FC = ({ value, children }) => { const tc = tinycolor(value); let style = {}; @@ -34,6 +34,6 @@ export const ColorDot: FunctionComponent = ({ value, children }) => { }; ColorDot.propTypes = { - value: PropTypes.string, children: PropTypes.node, + value: PropTypes.string, }; diff --git a/x-pack/plugins/canvas/public/components/color_dot/index.ts b/x-pack/plugins/canvas/public/components/color_dot/index.ts index aacfdf4e0cc74ae..72936f6133886f8 100644 --- a/x-pack/plugins/canvas/public/components/color_dot/index.ts +++ b/x-pack/plugins/canvas/public/components/color_dot/index.ts @@ -4,9 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import { pure } from 'recompose'; - -import { ColorDot as Component } from './color_dot'; - -export { Props } from './color_dot'; -export const ColorDot = pure(Component); +export { ColorDot } from './color_dot'; diff --git a/x-pack/plugins/canvas/public/components/color_manager/color_manager.tsx b/x-pack/plugins/canvas/public/components/color_manager/color_manager.tsx index 8855bffc5e771ef..88bf93a3ca84a28 100644 --- a/x-pack/plugins/canvas/public/components/color_manager/color_manager.tsx +++ b/x-pack/plugins/canvas/public/components/color_manager/color_manager.tsx @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiButtonIcon, EuiFieldText, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import React, { FC } from 'react'; import PropTypes from 'prop-types'; -import React, { FunctionComponent } from 'react'; +import { EuiButtonIcon, EuiFieldText, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import tinycolor from 'tinycolor2'; import { ColorDot } from '../color_dot/color_dot'; @@ -15,17 +15,17 @@ import { ComponentStrings } from '../../../i18n/components'; const { ColorManager: strings } = ComponentStrings; export interface Props { + /** + * Determines if the add/remove buttons are displayed. + * @default false + */ + hasButtons?: boolean; /** The function to call when the Add Color button is clicked. The button will be disabled if there is no handler. */ onAddColor?: (value: string) => void; /** The function to call when the value is changed */ onChange: (value: string) => void; /** The function to call when the Remove Color button is clicked. The button will be disabled if there is no handler. */ onRemoveColor?: (value: string) => void; - /** - * Determines if the add/remove buttons are displayed. - * @default false - */ - hasButtons?: boolean; /** * The value of the color manager. Only honors valid CSS values. * @default '' @@ -33,12 +33,12 @@ export interface Props { value?: string; } -export const ColorManager: FunctionComponent = ({ - value = '', +export const ColorManager: FC = ({ + hasButtons = false, onAddColor, - onRemoveColor, onChange, - hasButtons = false, + onRemoveColor, + value = '', }) => { const tc = tinycolor(value); const validColor = tc.isValid(); diff --git a/x-pack/plugins/canvas/public/components/color_manager/index.ts b/x-pack/plugins/canvas/public/components/color_manager/index.ts index d7f59b38a74c597..9958c17cf19419a 100644 --- a/x-pack/plugins/canvas/public/components/color_manager/index.ts +++ b/x-pack/plugins/canvas/public/components/color_manager/index.ts @@ -4,9 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import { pure } from 'recompose'; - -import { ColorManager as Component } from './color_manager'; - -export { Props } from './color_manager'; -export const ColorManager = pure(Component); +export { ColorManager, Props } from './color_manager'; diff --git a/x-pack/plugins/canvas/public/components/color_palette/color_palette.tsx b/x-pack/plugins/canvas/public/components/color_palette/color_palette.tsx index 09bc08f9ae541db..d3b1936d4c3651e 100644 --- a/x-pack/plugins/canvas/public/components/color_palette/color_palette.tsx +++ b/x-pack/plugins/canvas/public/components/color_palette/color_palette.tsx @@ -4,15 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiIcon, EuiLink } from '@elastic/eui'; +import React, { FC } from 'react'; import PropTypes from 'prop-types'; -import React, { FunctionComponent } from 'react'; +import { EuiIcon, EuiLink } from '@elastic/eui'; import tinycolor from 'tinycolor2'; import { readableColor } from '../../lib/readable_color'; import { ColorDot } from '../color_dot'; import { ItemGrid } from '../item_grid'; -export interface Props { +interface Props { /** * An array of hexadecimal color values. Non-hex will be ignored. * @default [] @@ -32,7 +32,7 @@ export interface Props { value?: string; } -export const ColorPalette: FunctionComponent = ({ +export const ColorPalette: FC = ({ colors = [], colorsPerRow = 6, onChange, diff --git a/x-pack/plugins/canvas/public/components/color_palette/index.ts b/x-pack/plugins/canvas/public/components/color_palette/index.ts index fa71bc8b3b9b0e7..2605868b94279cc 100644 --- a/x-pack/plugins/canvas/public/components/color_palette/index.ts +++ b/x-pack/plugins/canvas/public/components/color_palette/index.ts @@ -4,8 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import { pure } from 'recompose'; -import { ColorPalette as Component } from './color_palette'; - -export { Props } from './color_palette'; -export const ColorPalette = pure(Component); +export { ColorPalette } from './color_palette'; diff --git a/x-pack/plugins/canvas/public/components/color_picker/color_picker.tsx b/x-pack/plugins/canvas/public/components/color_picker/color_picker.tsx index 2bf17301b7b3872..8de3ddb3d03a411 100644 --- a/x-pack/plugins/canvas/public/components/color_picker/color_picker.tsx +++ b/x-pack/plugins/canvas/public/components/color_picker/color_picker.tsx @@ -4,9 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +import React, { FC } from 'react'; import PropTypes from 'prop-types'; -import React, { FunctionComponent } from 'react'; import tinycolor from 'tinycolor2'; + import { ColorManager, Props as ColorManagerProps } from '../color_manager'; import { ColorPalette } from '../color_palette'; @@ -18,7 +19,7 @@ export interface Props extends ColorManagerProps { colors?: string[]; } -export const ColorPicker: FunctionComponent = ({ +export const ColorPicker: FC = ({ colors = [], hasButtons = false, onAddColor, diff --git a/x-pack/plugins/canvas/public/components/color_picker/index.ts b/x-pack/plugins/canvas/public/components/color_picker/index.ts index 88968d11a665c6b..35dd067ab6d3943 100644 --- a/x-pack/plugins/canvas/public/components/color_picker/index.ts +++ b/x-pack/plugins/canvas/public/components/color_picker/index.ts @@ -4,9 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import { pure } from 'recompose'; - -import { ColorPicker as Component } from './color_picker'; - -export { Props } from './color_picker'; -export const ColorPicker = pure(Component); +export { ColorPicker, Props } from './color_picker'; diff --git a/x-pack/plugins/canvas/public/components/color_picker_popover/color_picker_popover.tsx b/x-pack/plugins/canvas/public/components/color_picker_popover/color_picker_popover.tsx index 9e8a6e88b649b7f..143e1a7cee6acdc 100644 --- a/x-pack/plugins/canvas/public/components/color_picker_popover/color_picker_popover.tsx +++ b/x-pack/plugins/canvas/public/components/color_picker_popover/color_picker_popover.tsx @@ -4,20 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiLink, PopoverAnchorPosition } from '@elastic/eui'; +import React, { FC } from 'react'; import PropTypes from 'prop-types'; -import React, { FunctionComponent } from 'react'; +import { EuiLink, PopoverAnchorPosition } from '@elastic/eui'; import tinycolor from 'tinycolor2'; + import { ColorDot } from '../color_dot'; import { ColorPicker, Props as ColorPickerProps } from '../color_picker'; import { Popover } from '../popover'; export interface Props extends ColorPickerProps { - anchorPosition: PopoverAnchorPosition; + anchorPosition?: PopoverAnchorPosition; ariaLabel?: string; } -export const ColorPickerPopover: FunctionComponent = (props: Props) => { +export const ColorPickerPopover: FC = (props: Props) => { const { value, anchorPosition, ariaLabel, ...rest } = props; const button = (handleClick: React.MouseEventHandler) => ( { +const getIcon = (type: IconType) => { if (type === null) { return; } @@ -36,19 +39,31 @@ const getIcon = (type) => { return ; }; -const getColumnName = (col) => (typeof col === 'string' ? col : col.name); +const getColumnName = (col: DatatableColumn) => (typeof col === 'string' ? col : col.name); -const getColumnType = (col) => col.type || null; +const getColumnType = (col: DatatableColumn) => col.type || null; -const getFormattedValue = (val, type) => { +const getFormattedValue = (val: any, type: any) => { if (type === 'date') { return moment(val).format(); } return String(val); }; -export const Datatable = ({ datatable, perPage, paginate, showHeader }) => ( - +interface Props { + datatable: DatatableType; + paginate?: boolean; + perPage?: number; + showHeader?: boolean; +} + +export const Datatable: FC = ({ + datatable, + paginate = false, + perPage = 10, + showHeader = false, +}) => ( + {({ rows, setPage, pageNumber, totalPages }) => (
@@ -91,7 +106,7 @@ export const Datatable = ({ datatable, perPage, paginate, showHeader }) => ( Datatable.propTypes = { datatable: PropTypes.object.isRequired, - perPage: PropTypes.number, paginate: PropTypes.bool, + perPage: PropTypes.number, showHeader: PropTypes.bool, }; diff --git a/x-pack/plugins/canvas/public/components/datatable/index.js b/x-pack/plugins/canvas/public/components/datatable/index.ts similarity index 64% rename from x-pack/plugins/canvas/public/components/datatable/index.js rename to x-pack/plugins/canvas/public/components/datatable/index.ts index c7837005368e594..e39b909c5346063 100644 --- a/x-pack/plugins/canvas/public/components/datatable/index.js +++ b/x-pack/plugins/canvas/public/components/datatable/index.ts @@ -4,7 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import { pure } from 'recompose'; -import { Datatable as Component } from './datatable'; - -export const Datatable = pure(Component); +export { Datatable } from './datatable'; diff --git a/x-pack/plugins/canvas/public/components/font_picker/__snapshots__/font_picker.stories.storyshot b/x-pack/plugins/canvas/public/components/font_picker/__stories__/__snapshots__/font_picker.stories.storyshot similarity index 100% rename from x-pack/plugins/canvas/public/components/font_picker/__snapshots__/font_picker.stories.storyshot rename to x-pack/plugins/canvas/public/components/font_picker/__stories__/__snapshots__/font_picker.stories.storyshot diff --git a/x-pack/plugins/canvas/public/components/font_picker/font_picker.stories.tsx b/x-pack/plugins/canvas/public/components/font_picker/__stories__/font_picker.stories.tsx similarity index 84% rename from x-pack/plugins/canvas/public/components/font_picker/font_picker.stories.tsx rename to x-pack/plugins/canvas/public/components/font_picker/__stories__/font_picker.stories.tsx index 0ad1e01252002f8..34cb3d644cccbdc 100644 --- a/x-pack/plugins/canvas/public/components/font_picker/font_picker.stories.tsx +++ b/x-pack/plugins/canvas/public/components/font_picker/__stories__/font_picker.stories.tsx @@ -7,8 +7,8 @@ import { action } from '@storybook/addon-actions'; import { storiesOf } from '@storybook/react'; import React from 'react'; -import { americanTypewriter } from '../../../common/lib/fonts'; -import { FontPicker } from './font_picker'; +import { americanTypewriter } from '../../../../common/lib/fonts'; +import { FontPicker } from '../font_picker'; storiesOf('components/FontPicker', module) .add('default', () => ) diff --git a/x-pack/plugins/canvas/public/components/font_picker/font_picker.tsx b/x-pack/plugins/canvas/public/components/font_picker/font_picker.tsx index 556a3c545216073..2b75841e1b7a59c 100644 --- a/x-pack/plugins/canvas/public/components/font_picker/font_picker.tsx +++ b/x-pack/plugins/canvas/public/components/font_picker/font_picker.tsx @@ -4,14 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiSuperSelect } from '@elastic/eui'; +import React, { FC } from 'react'; import PropTypes from 'prop-types'; -import React, { FunctionComponent } from 'react'; +import { EuiSuperSelect } from '@elastic/eui'; import { fonts, FontValue } from '../../../common/lib/fonts'; interface DisplayedFont { - value: string; label: string; + value: string; } interface Props { @@ -19,9 +19,7 @@ interface Props { value?: FontValue; } -export const FontPicker: FunctionComponent = (props) => { - const { value, onSelect } = props; - +export const FontPicker: FC = ({ value, onSelect }) => { // While fonts are strongly-typed, we also support custom fonts someone might type in. // So let's cast the fonts and allow for additions. const displayedFonts: DisplayedFont[] = fonts; @@ -46,10 +44,10 @@ export const FontPicker: FunctionComponent = (props) => { }; FontPicker.propTypes = { - /** Initial value of the Font Picker. */ - value: PropTypes.string, /** Function to execute when a Font is selected. */ onSelect: PropTypes.func, + /** Initial value of the Font Picker. */ + value: PropTypes.string, }; FontPicker.displayName = 'FontPicker'; diff --git a/x-pack/plugins/canvas/public/components/font_picker/index.js b/x-pack/plugins/canvas/public/components/font_picker/index.ts similarity index 64% rename from x-pack/plugins/canvas/public/components/font_picker/index.js rename to x-pack/plugins/canvas/public/components/font_picker/index.ts index 5ccb7846b7a77a3..339021a7e5712d2 100644 --- a/x-pack/plugins/canvas/public/components/font_picker/index.js +++ b/x-pack/plugins/canvas/public/components/font_picker/index.ts @@ -4,8 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import { pure } from 'recompose'; - -import { FontPicker as Component } from './font_picker'; - -export const FontPicker = pure(Component); +export { FontPicker } from './font_picker'; diff --git a/x-pack/plugins/canvas/public/components/loading/index.ts b/x-pack/plugins/canvas/public/components/loading/index.ts index 81fedf328718431..745639955dcbaaa 100644 --- a/x-pack/plugins/canvas/public/components/loading/index.ts +++ b/x-pack/plugins/canvas/public/components/loading/index.ts @@ -4,7 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import { pure } from 'recompose'; -import { Loading as Component } from './loading'; - -export const Loading = pure(Component); +export { Loading } from './loading'; diff --git a/x-pack/plugins/canvas/public/components/loading/loading.tsx b/x-pack/plugins/canvas/public/components/loading/loading.tsx index 67db16d40d4264d..403be84295312ee 100644 --- a/x-pack/plugins/canvas/public/components/loading/loading.tsx +++ b/x-pack/plugins/canvas/public/components/loading/loading.tsx @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiIcon, EuiLoadingSpinner, isColorDark } from '@elastic/eui'; +import React, { FC } from 'react'; import PropTypes from 'prop-types'; -import React, { FunctionComponent } from 'react'; +import { EuiIcon, EuiLoadingSpinner, isColorDark } from '@elastic/eui'; import { hexToRgb } from '../../../common/lib/hex_to_rgb'; interface Props { @@ -15,7 +15,7 @@ interface Props { text?: string; } -export const Loading: FunctionComponent = ({ +export const Loading: FC = ({ animated = false, text = '', backgroundColor = '#000000', diff --git a/x-pack/plugins/canvas/public/components/shape_picker/index.ts b/x-pack/plugins/canvas/public/components/shape_picker/index.ts index d3ed85831cbe204..3ec86e45af236e0 100644 --- a/x-pack/plugins/canvas/public/components/shape_picker/index.ts +++ b/x-pack/plugins/canvas/public/components/shape_picker/index.ts @@ -4,8 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import { pure } from 'recompose'; - -import { ShapePicker as Component } from './shape_picker'; - -export const ShapePicker = pure(Component); +export { ShapePicker } from './shape_picker'; diff --git a/x-pack/plugins/canvas/public/components/shape_picker/shape_picker.tsx b/x-pack/plugins/canvas/public/components/shape_picker/shape_picker.tsx index 56874fd3080f7ab..263654522c05998 100644 --- a/x-pack/plugins/canvas/public/components/shape_picker/shape_picker.tsx +++ b/x-pack/plugins/canvas/public/components/shape_picker/shape_picker.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { FC } from 'react'; import PropTypes from 'prop-types'; import { EuiFlexGrid, EuiFlexItem, EuiLink } from '@elastic/eui'; import { ShapePreview } from '../shape_preview'; @@ -16,7 +16,7 @@ interface Props { onChange?: (key: string) => void; } -export const ShapePicker = ({ shapes, onChange = () => {} }: Props) => { +export const ShapePicker: FC = ({ shapes, onChange = () => {} }) => { return ( {Object.keys(shapes) @@ -33,6 +33,6 @@ export const ShapePicker = ({ shapes, onChange = () => {} }: Props) => { }; ShapePicker.propTypes = { - shapes: PropTypes.object.isRequired, onChange: PropTypes.func, + shapes: PropTypes.object.isRequired, }; diff --git a/x-pack/plugins/canvas/public/components/shape_picker_popover/index.tsx b/x-pack/plugins/canvas/public/components/shape_picker_popover/index.tsx index 1d4ae25a38fa2de..06619c0626daff9 100644 --- a/x-pack/plugins/canvas/public/components/shape_picker_popover/index.tsx +++ b/x-pack/plugins/canvas/public/components/shape_picker_popover/index.tsx @@ -4,8 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import { pure } from 'recompose'; - -import { ShapePickerPopover as Component } from './shape_picker_popover'; - -export const ShapePickerPopover = pure(Component); +export { ShapePickerPopover } from './shape_picker_popover'; diff --git a/x-pack/plugins/canvas/public/components/shape_picker_popover/shape_picker_popover.tsx b/x-pack/plugins/canvas/public/components/shape_picker_popover/shape_picker_popover.tsx index d42e08d2bc85241..d61d9e47a3a78b8 100644 --- a/x-pack/plugins/canvas/public/components/shape_picker_popover/shape_picker_popover.tsx +++ b/x-pack/plugins/canvas/public/components/shape_picker_popover/shape_picker_popover.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { FC } from 'react'; import PropTypes from 'prop-types'; import { EuiLink, EuiPanel } from '@elastic/eui'; import { Popover } from '../popover'; @@ -20,7 +20,7 @@ interface Props { ariaLabel?: string; } -export const ShapePickerPopover = ({ shapes, onChange, value, ariaLabel }: Props) => { +export const ShapePickerPopover: FC = ({ shapes, onChange, value, ariaLabel }) => { const button = (handleClick: React.MouseEventHandler) => ( @@ -37,7 +37,8 @@ export const ShapePickerPopover = ({ shapes, onChange, value, ariaLabel }: Props }; ShapePickerPopover.propTypes = { + ariaLabel: PropTypes.string, + onChange: PropTypes.func, shapes: PropTypes.object.isRequired, value: PropTypes.string, - onChange: PropTypes.func, }; diff --git a/x-pack/plugins/canvas/public/components/shape_preview/index.ts b/x-pack/plugins/canvas/public/components/shape_preview/index.ts index 4320a10d97a8538..6027b1227a99a3b 100644 --- a/x-pack/plugins/canvas/public/components/shape_preview/index.ts +++ b/x-pack/plugins/canvas/public/components/shape_preview/index.ts @@ -4,8 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import { pure } from 'recompose'; - -import { ShapePreview as Component } from './shape_preview'; - -export const ShapePreview = pure(Component); +export { ShapePreview } from './shape_preview'; diff --git a/x-pack/plugins/canvas/public/components/shape_preview/shape_preview.tsx b/x-pack/plugins/canvas/public/components/shape_preview/shape_preview.tsx index 4f67945e9ce13a3..3ff18f3aa4bc427 100644 --- a/x-pack/plugins/canvas/public/components/shape_preview/shape_preview.tsx +++ b/x-pack/plugins/canvas/public/components/shape_preview/shape_preview.tsx @@ -4,14 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { FC } from 'react'; import PropTypes from 'prop-types'; interface Props { shape?: string; } -export const ShapePreview = ({ shape }: Props) => { +export const ShapePreview: FC = ({ shape }) => { if (!shape) { return
; } diff --git a/x-pack/plugins/canvas/public/components/text_style_picker/__stories__/__snapshots__/text_style_picker.stories.storyshot b/x-pack/plugins/canvas/public/components/text_style_picker/__stories__/__snapshots__/text_style_picker.stories.storyshot new file mode 100644 index 000000000000000..ad236e701ceb01d --- /dev/null +++ b/x-pack/plugins/canvas/public/components/text_style_picker/__stories__/__snapshots__/text_style_picker.stories.storyshot @@ -0,0 +1,975 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots components/TextStylePicker default 1`] = ` +
+
+
+
+
+
+ +
+
+ + Select an option: + , is selected + +
+
+
+
+
+
+ +
+ +
+ + +
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+
+
+`; + +exports[`Storyshots components/TextStylePicker interactive 1`] = ` +
+
+
+
+
+
+ +
+
+ + Select an option: + , is selected + +
+
+
+
+
+
+ +
+ +
+ + +
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+
+
+`; diff --git a/x-pack/plugins/canvas/public/components/text_style_picker/__stories__/text_style_picker.stories.tsx b/x-pack/plugins/canvas/public/components/text_style_picker/__stories__/text_style_picker.stories.tsx new file mode 100644 index 000000000000000..b33a34fcd5e65f7 --- /dev/null +++ b/x-pack/plugins/canvas/public/components/text_style_picker/__stories__/text_style_picker.stories.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, { useState } from 'react'; +import { storiesOf } from '@storybook/react'; +import { action } from '@storybook/addon-actions'; + +import { TextStylePicker } from '../text_style_picker'; + +const Interactive = () => { + const [props, setProps] = useState({}); + return ; +}; + +storiesOf('components/TextStylePicker', module) + .addDecorator((fn) =>
{fn()}
) + .add('default', () => ) + .add('interactive', () => ); diff --git a/x-pack/plugins/canvas/public/components/text_style_picker/font_sizes.js b/x-pack/plugins/canvas/public/components/text_style_picker/font_sizes.ts similarity index 100% rename from x-pack/plugins/canvas/public/components/text_style_picker/font_sizes.js rename to x-pack/plugins/canvas/public/components/text_style_picker/font_sizes.ts diff --git a/x-pack/plugins/canvas/public/components/text_style_picker/index.js b/x-pack/plugins/canvas/public/components/text_style_picker/index.ts similarity index 61% rename from x-pack/plugins/canvas/public/components/text_style_picker/index.js rename to x-pack/plugins/canvas/public/components/text_style_picker/index.ts index 79bde95723682a4..16fb39b660a0c8b 100644 --- a/x-pack/plugins/canvas/public/components/text_style_picker/index.js +++ b/x-pack/plugins/canvas/public/components/text_style_picker/index.ts @@ -4,7 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import { pure } from 'recompose'; -import { TextStylePicker as Component } from './text_style_picker'; - -export const TextStylePicker = pure(Component); +export { TextStylePicker } from './text_style_picker'; diff --git a/x-pack/plugins/canvas/public/components/text_style_picker/text_style_picker.js b/x-pack/plugins/canvas/public/components/text_style_picker/text_style_picker.tsx similarity index 54% rename from x-pack/plugins/canvas/public/components/text_style_picker/text_style_picker.js rename to x-pack/plugins/canvas/public/components/text_style_picker/text_style_picker.tsx index 48d52abb0312503..3dfc55919395d47 100644 --- a/x-pack/plugins/canvas/public/components/text_style_picker/text_style_picker.js +++ b/x-pack/plugins/canvas/public/components/text_style_picker/text_style_picker.tsx @@ -4,9 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { FC, useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import { EuiFlexGroup, EuiFlexItem, EuiSelect, EuiSpacer, EuiButtonGroup } from '@elastic/eui'; +import { FontValue } from 'src/plugins/expressions'; import { ComponentStrings } from '../../../i18n'; import { FontPicker } from '../font_picker'; import { ColorPickerPopover } from '../color_picker_popover'; @@ -14,54 +15,75 @@ import { fontSizes } from './font_sizes'; const { TextStylePicker: strings } = ComponentStrings; -export const TextStylePicker = ({ - family, - size, - align, - color, - weight, - underline, - italic, - onChange, - colors, -}) => { - const alignmentButtons = [ - { - id: 'left', - label: strings.getAlignLeftOption(), - iconType: 'editorAlignLeft', - }, - { - id: 'center', - label: strings.getAlignCenterOption(), - iconType: 'editorAlignCenter', - }, - { - id: 'right', - label: strings.getAlignRightOption(), - iconType: 'editorAlignRight', - }, - ]; - - const styleButtons = [ - { - id: 'bold', - label: strings.getStyleBoldOption(), - iconType: 'editorBold', - }, - { - id: 'italic', - label: strings.getStyleItalicOption(), - iconType: 'editorItalic', - }, - { - id: 'underline', - label: strings.getStyleUnderlineOption(), - iconType: 'editorUnderline', - }, - ]; - - const stylesSelectedMap = { +interface BaseProps { + family?: FontValue; + size?: number; + align?: 'left' | 'center' | 'right'; + color?: string; + weight?: 'bold' | 'normal'; + underline?: boolean; + italic?: boolean; +} + +interface Props extends BaseProps { + colors?: string[]; + onChange: (props: BaseProps) => void; +} + +type StyleType = 'bold' | 'italic' | 'underline'; + +const alignmentButtons = [ + { + id: 'left', + label: strings.getAlignLeftOption(), + iconType: 'editorAlignLeft', + }, + { + id: 'center', + label: strings.getAlignCenterOption(), + iconType: 'editorAlignCenter', + }, + { + id: 'right', + label: strings.getAlignRightOption(), + iconType: 'editorAlignRight', + }, +]; + +const styleButtons = [ + { + id: 'bold', + label: strings.getStyleBoldOption(), + iconType: 'editorBold', + }, + { + id: 'italic', + label: strings.getStyleItalicOption(), + iconType: 'editorItalic', + }, + { + id: 'underline', + label: strings.getStyleUnderlineOption(), + iconType: 'editorUnderline', + }, +]; + +export const TextStylePicker: FC = (props) => { + const [style, setStyle] = useState(props); + + const { + align = 'left', + color, + colors, + family, + italic = false, + onChange, + size = 14, + underline = false, + weight = 'normal', + } = style; + + const stylesSelectedMap: Record = { ['bold']: weight === 'bold', ['italic']: Boolean(italic), ['underline']: Boolean(underline), @@ -72,31 +94,22 @@ export const TextStylePicker = ({ fontSizes.sort((a, b) => a - b); } - const doChange = (propName, value) => { - onChange({ - family, - size, - align, - color, - weight: weight || 'normal', - underline: underline || false, - italic: italic || false, - [propName]: value, - }); - }; + useEffect(() => onChange(style), [onChange, style]); - const onAlignmentChange = (optionId) => doChange('align', optionId); + const doChange = (propName: keyof Props, value: string | boolean | number) => { + setStyle({ ...style, [propName]: value }); + }; - const onStyleChange = (optionId) => { - let prop; + const onStyleChange = (optionId: string) => { + let prop: 'weight' | 'italic' | 'underline'; let value; if (optionId === 'bold') { prop = 'weight'; value = !stylesSelectedMap[optionId] ? 'bold' : 'normal'; } else { - prop = optionId; - value = !stylesSelectedMap[optionId]; + prop = optionId as 'italic' | 'underline'; + value = !stylesSelectedMap[prop]; } doChange(prop, value); @@ -106,14 +119,18 @@ export const TextStylePicker = ({
- doChange('family', value)} /> + {family ? ( + doChange('family', value)} /> + ) : ( + doChange('family', value)} /> + )} doChange('size', Number(e.target.value))} - options={fontSizes.map((size) => ({ text: String(size), value: size }))} + options={fontSizes.map((fontSize) => ({ text: String(fontSize), value: fontSize }))} prepend="Size" /> @@ -147,7 +164,7 @@ export const TextStylePicker = ({ buttonSize="compressed" isIconOnly idSelected={align} - onChange={onAlignmentChange} + onChange={(optionId: string) => doChange('align', optionId)} className="canvasSidebar__buttonGroup" /> @@ -159,9 +176,9 @@ export const TextStylePicker = ({ TextStylePicker.propTypes = { family: PropTypes.string, size: PropTypes.number, - align: PropTypes.string, + align: PropTypes.oneOf(['left', 'center', 'right']), color: PropTypes.string, - weight: PropTypes.string, + weight: PropTypes.oneOf(['normal', 'bold']), underline: PropTypes.bool, italic: PropTypes.bool, onChange: PropTypes.func.isRequired, @@ -171,4 +188,5 @@ TextStylePicker.propTypes = { TextStylePicker.defaultProps = { align: 'left', size: 14, + weight: 'normal', }; diff --git a/x-pack/plugins/canvas/public/components/tooltip_icon/index.ts b/x-pack/plugins/canvas/public/components/tooltip_icon/index.ts index 6e71baa3647852d..55c2f7090629c81 100644 --- a/x-pack/plugins/canvas/public/components/tooltip_icon/index.ts +++ b/x-pack/plugins/canvas/public/components/tooltip_icon/index.ts @@ -4,8 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import { pure } from 'recompose'; -import { TooltipIcon as Component } from './tooltip_icon'; -export { IconType } from './tooltip_icon'; - -export const TooltipIcon = pure(Component); +export { TooltipIcon, IconType } from './tooltip_icon'; diff --git a/x-pack/plugins/canvas/public/components/tooltip_icon/tooltip_icon.tsx b/x-pack/plugins/canvas/public/components/tooltip_icon/tooltip_icon.tsx index 78c2b0ec53c9f02..d91bb4bc9add963 100644 --- a/x-pack/plugins/canvas/public/components/tooltip_icon/tooltip_icon.tsx +++ b/x-pack/plugins/canvas/public/components/tooltip_icon/tooltip_icon.tsx @@ -5,7 +5,7 @@ */ /* eslint react/forbid-elements: 0 */ -import React from 'react'; +import React, { FC } from 'react'; import PropTypes from 'prop-types'; import { EuiIconTip, PropsOf } from '@elastic/eui'; @@ -21,7 +21,7 @@ interface Props extends Omit { icon: IconType; } -export const TooltipIcon = ({ icon = IconType.info, ...rest }: Props) => { +export const TooltipIcon: FC = ({ icon = IconType.info, ...rest }) => { const icons = { [IconType.error]: { type: 'alert', color: 'danger' }, [IconType.warning]: { type: 'alert', color: 'warning' }, From b65ec4e07d0c47e86f930e9e72f5c3fd030aac46 Mon Sep 17 00:00:00 2001 From: John Schulz Date: Tue, 28 Jul 2020 19:44:30 -0400 Subject: [PATCH 03/10] Get branch name from platform vs disk (#73534) https://github.com/elastic/kibana/blob/fa93a81ba67f5177024f1ab3b4ac68919a7824dc/src/core/server/plugins/types.ts#L280 & https://github.com/elastic/kibana/blob/27dbcb27964353619f686066c6ba8f25954a0881/src/core/server/config/types.ts#L25 Co-authored-by: Elastic Machine --- x-pack/plugins/ingest_manager/server/plugin.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/x-pack/plugins/ingest_manager/server/plugin.ts b/x-pack/plugins/ingest_manager/server/plugin.ts index 5664a875010166a..e7495df254a0904 100644 --- a/x-pack/plugins/ingest_manager/server/plugin.ts +++ b/x-pack/plugins/ingest_manager/server/plugin.ts @@ -15,7 +15,6 @@ import { HttpServiceSetup, } from 'kibana/server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; -import packageJSON from '../../../../package.json'; import { LicensingPluginSetup, ILicense } from '../../licensing/server'; import { EncryptedSavedObjectsPluginStart, @@ -155,7 +154,7 @@ export class IngestManagerPlugin this.config$ = this.initializerContext.config.create(); this.isProductionMode = this.initializerContext.env.mode.prod; this.kibanaVersion = this.initializerContext.env.packageInfo.version; - this.kibanaBranch = packageJSON.branch; + this.kibanaBranch = this.initializerContext.env.packageInfo.branch; this.logger = this.initializerContext.logger.get(); } From b399fb03d1677bba0cd61cb5de4e0957fa1910fc Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Tue, 28 Jul 2020 17:47:41 -0600 Subject: [PATCH 04/10] [SIEM][Detection Engine][Lists] Adds the ability to change the timeout limits from 10 seconds for loads for imports (#73103) ## Summary By default the upload time limit for payloads is 10 seconds. This is really too short and we were getting internal QA bug reports that uploads are timing out on large value list importing. This PR adds the plumbing and unit tests to make the timeout configurable for routes. * Adds a single timeout option for routes and then normalizes that through Hapi for the socket, payload, and server timeouts. * Adds unit tests which test the various options * Adds integration tests which test the various options * Adds some NOTES about where there are odd behaviors/bugs within Hapi around validations and the timeouts * Adds a configurable 5 minute timeout to the large value lists route **Manual testing of the feature** You can manually test this by adding a configurable option to your chrome network throttle like so below where you throttle upload by some configurable amount. I chose to use 300 kbs/s upload Screen Shot 2020-07-23 at 11 26 01 AM And then run an import of large value lists using a large enough file that it will exceed 5 minutes: ![screen-shot-upload](https://user-images.githubusercontent.com/1151048/88318584-28ef6000-ccd8-11ea-90a1-8ca4aafabcb4.png) After 5 minutes you should see this message within your server side messages if you have configured your kibana.dev.yml to allow for these messages: ```ts server respons [10:52:31.377] [access:lists-all] POST /api/lists/items/_import?type=keyword 408 318292ms - 9.0B ``` Note that it should show you that it is trying to return a `408` after `318292ms` the timeout period. Sometimes you will get the 408 in the browser and sometimes the browser actually will not respect the 408 and continue staying in a pending state forever. This seems to be browser side issue and not a client/user land issue. If you get the browser message it will be this error toaster ![timeout-message](https://user-images.githubusercontent.com/1151048/88318760-74a20980-ccd8-11ea-9b7b-0d27f8eb6bce.png) ### Checklist - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md) - [x] [Documentation](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#writing-documentation) was added for features that require explanation or tutorials - [x] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios ### For maintainers - [x] This was checked for breaking API changes and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process) --- ...a-plugin-core-server.routeconfigoptions.md | 1 + ...-core-server.routeconfigoptions.timeout.md | 13 ++ src/core/server/http/http_server.test.ts | 127 ++++++++++++++++++ src/core/server/http/http_server.ts | 22 ++- .../http/integration_tests/router.test.ts | 124 +++++++++++++++++ src/core/server/http/router/request.ts | 2 + src/core/server/http/router/route.ts | 6 + src/core/server/server.api.md | 1 + x-pack/plugins/lists/common/constants.mock.ts | 4 +- x-pack/plugins/lists/server/config.mock.ts | 2 + x-pack/plugins/lists/server/config.test.ts | 22 +++ x-pack/plugins/lists/server/config.ts | 10 ++ .../server/routes/import_list_item_route.ts | 1 + .../server/services/lists/list_client.mock.ts | 2 + 14 files changed, 333 insertions(+), 4 deletions(-) create mode 100644 docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.timeout.md diff --git a/docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.md b/docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.md index 5a6103dfc57d4c4..fee6124f8d86623 100644 --- a/docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.md +++ b/docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.md @@ -19,5 +19,6 @@ export interface RouteConfigOptions | [authRequired](./kibana-plugin-core-server.routeconfigoptions.authrequired.md) | boolean | 'optional' | Defines authentication mode for a route: - true. A user has to have valid credentials to access a resource - false. A user can access a resource without any credentials. - 'optional'. A user can access a resource if has valid credentials or no credentials at all. Can be useful when we grant access to a resource but want to identify a user if possible.Defaults to true if an auth mechanism is registered. | | [body](./kibana-plugin-core-server.routeconfigoptions.body.md) | Method extends 'get' | 'options' ? undefined : RouteConfigOptionsBody | Additional body options [RouteConfigOptionsBody](./kibana-plugin-core-server.routeconfigoptionsbody.md). | | [tags](./kibana-plugin-core-server.routeconfigoptions.tags.md) | readonly string[] | Additional metadata tag strings to attach to the route. | +| [timeout](./kibana-plugin-core-server.routeconfigoptions.timeout.md) | number | Timeouts for processing durations. Response timeout is in milliseconds. Default value: 2 minutes | | [xsrfRequired](./kibana-plugin-core-server.routeconfigoptions.xsrfrequired.md) | Method extends 'get' ? never : boolean | Defines xsrf protection requirements for a route: - true. Requires an incoming POST/PUT/DELETE request to contain kbn-xsrf header. - false. Disables xsrf protection.Set to true by default | diff --git a/docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.timeout.md b/docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.timeout.md new file mode 100644 index 000000000000000..479fcf883ec4d79 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.timeout.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [RouteConfigOptions](./kibana-plugin-core-server.routeconfigoptions.md) > [timeout](./kibana-plugin-core-server.routeconfigoptions.timeout.md) + +## RouteConfigOptions.timeout property + +Timeouts for processing durations. Response timeout is in milliseconds. Default value: 2 minutes + +Signature: + +```typescript +timeout?: number; +``` diff --git a/src/core/server/http/http_server.test.ts b/src/core/server/http/http_server.test.ts index 601eba835a54e85..007d75a69b955ae 100644 --- a/src/core/server/http/http_server.test.ts +++ b/src/core/server/http/http_server.test.ts @@ -992,6 +992,133 @@ describe('body options', () => { }); }); +describe('timeout options', () => { + test('should accept a socket "timeout" which is 3 minutes in milliseconds, "300000" for a POST', async () => { + const { registerRouter, server: innerServer } = await server.setup(config); + + const router = new Router('', logger, enhanceWithContext); + router.post( + { + path: '/', + validate: false, + options: { timeout: 300000 }, + }, + (context, req, res) => { + try { + return res.ok({ body: { timeout: req.route.options.timeout } }); + } catch (err) { + return res.internalError({ body: err.message }); + } + } + ); + registerRouter(router); + await server.start(); + await supertest(innerServer.listener).post('/').send({ test: 1 }).expect(200, { + timeout: 300000, + }); + }); + + test('should accept a socket "timeout" which is 3 minutes in milliseconds, "300000" for a GET', async () => { + const { registerRouter, server: innerServer } = await server.setup(config); + + const router = new Router('', logger, enhanceWithContext); + router.get( + { + path: '/', + validate: false, + options: { timeout: 300000 }, + }, + (context, req, res) => { + try { + return res.ok({ body: { timeout: req.route.options.timeout } }); + } catch (err) { + return res.internalError({ body: err.message }); + } + } + ); + registerRouter(router); + await server.start(); + await supertest(innerServer.listener).get('/').expect(200, { + timeout: 300000, + }); + }); + + test('should accept a socket "timeout" which is 3 minutes in milliseconds, "300000" for a DELETE', async () => { + const { registerRouter, server: innerServer } = await server.setup(config); + + const router = new Router('', logger, enhanceWithContext); + router.delete( + { + path: '/', + validate: false, + options: { timeout: 300000 }, + }, + (context, req, res) => { + try { + return res.ok({ body: { timeout: req.route.options.timeout } }); + } catch (err) { + return res.internalError({ body: err.message }); + } + } + ); + registerRouter(router); + await server.start(); + await supertest(innerServer.listener).delete('/').expect(200, { + timeout: 300000, + }); + }); + + test('should accept a socket "timeout" which is 3 minutes in milliseconds, "300000" for a PUT', async () => { + const { registerRouter, server: innerServer } = await server.setup(config); + + const router = new Router('', logger, enhanceWithContext); + router.put( + { + path: '/', + validate: false, + options: { timeout: 300000 }, + }, + (context, req, res) => { + try { + return res.ok({ body: { timeout: req.route.options.timeout } }); + } catch (err) { + return res.internalError({ body: err.message }); + } + } + ); + registerRouter(router); + await server.start(); + await supertest(innerServer.listener).put('/').expect(200, { + timeout: 300000, + }); + }); + + test('should accept a socket "timeout" which is 3 minutes in milliseconds, "300000" for a PATCH', async () => { + const { registerRouter, server: innerServer } = await server.setup(config); + + const router = new Router('', logger, enhanceWithContext); + router.patch( + { + path: '/', + validate: false, + options: { timeout: 300000 }, + }, + (context, req, res) => { + try { + return res.ok({ body: { timeout: req.route.options.timeout } }); + } catch (err) { + return res.internalError({ body: err.message }); + } + } + ); + registerRouter(router); + await server.start(); + await supertest(innerServer.listener).patch('/').expect(200, { + timeout: 300000, + }); + }); +}); + test('should return a stream in the body', async () => { const { registerRouter, server: innerServer } = await server.setup(config); diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts index 9c16162d6933483..4b70f58deba99e5 100644 --- a/src/core/server/http/http_server.ts +++ b/src/core/server/http/http_server.ts @@ -161,8 +161,10 @@ export class HttpServer { this.log.debug(`registering route handler for [${route.path}]`); // Hapi does not allow payload validation to be specified for 'head' or 'get' requests const validate = isSafeMethod(route.method) ? undefined : { payload: true }; - const { authRequired, tags, body = {} } = route.options; + const { authRequired, tags, body = {}, timeout } = route.options; const { accepts: allow, maxBytes, output, parse } = body; + // Hapi does not allow timeouts on payloads to be specified for 'head' or 'get' requests + const payloadTimeout = isSafeMethod(route.method) || timeout == null ? undefined : timeout; const kibanaRouteState: KibanaRouteState = { xsrfRequired: route.options.xsrfRequired ?? !isSafeMethod(route.method), @@ -181,9 +183,23 @@ export class HttpServer { // validation applied in ./http_tools#getServerOptions // (All NP routes are already required to specify their own validation in order to access the payload) validate, - payload: [allow, maxBytes, output, parse].some((v) => typeof v !== 'undefined') - ? { allow, maxBytes, output, parse } + payload: [allow, maxBytes, output, parse, payloadTimeout].some( + (v) => typeof v !== 'undefined' + ) + ? { + allow, + maxBytes, + output, + parse, + timeout: payloadTimeout, + } : undefined, + timeout: + timeout != null + ? { + socket: timeout + 1, // Hapi server requires the socket to be greater than payload settings so we add 1 millisecond + } + : undefined, }, }); } diff --git a/src/core/server/http/integration_tests/router.test.ts b/src/core/server/http/integration_tests/router.test.ts index bb36fefa96611e6..434e22e3cf6f5ba 100644 --- a/src/core/server/http/integration_tests/router.test.ts +++ b/src/core/server/http/integration_tests/router.test.ts @@ -302,6 +302,130 @@ describe('Options', () => { }); }); }); + + describe('timeout', () => { + it('should timeout if configured with a small timeout value for a POST', async () => { + const { server: innerServer, createRouter } = await server.setup(setupDeps); + const router = createRouter('/'); + + router.post( + { path: '/a', validate: false, options: { timeout: 1000 } }, + async (context, req, res) => { + await new Promise((resolve) => setTimeout(resolve, 2000)); + return res.ok({}); + } + ); + router.post({ path: '/b', validate: false }, (context, req, res) => res.ok({})); + await server.start(); + expect(supertest(innerServer.listener).post('/a')).rejects.toThrow('socket hang up'); + await supertest(innerServer.listener).post('/b').expect(200, {}); + }); + + it('should timeout if configured with a small timeout value for a PUT', async () => { + const { server: innerServer, createRouter } = await server.setup(setupDeps); + const router = createRouter('/'); + + router.put( + { path: '/a', validate: false, options: { timeout: 1000 } }, + async (context, req, res) => { + await new Promise((resolve) => setTimeout(resolve, 2000)); + return res.ok({}); + } + ); + router.put({ path: '/b', validate: false }, (context, req, res) => res.ok({})); + await server.start(); + + expect(supertest(innerServer.listener).put('/a')).rejects.toThrow('socket hang up'); + await supertest(innerServer.listener).put('/b').expect(200, {}); + }); + + it('should timeout if configured with a small timeout value for a DELETE', async () => { + const { server: innerServer, createRouter } = await server.setup(setupDeps); + const router = createRouter('/'); + + router.delete( + { path: '/a', validate: false, options: { timeout: 1000 } }, + async (context, req, res) => { + await new Promise((resolve) => setTimeout(resolve, 2000)); + return res.ok({}); + } + ); + router.delete({ path: '/b', validate: false }, (context, req, res) => res.ok({})); + await server.start(); + expect(supertest(innerServer.listener).delete('/a')).rejects.toThrow('socket hang up'); + await supertest(innerServer.listener).delete('/b').expect(200, {}); + }); + + it('should timeout if configured with a small timeout value for a GET', async () => { + const { server: innerServer, createRouter } = await server.setup(setupDeps); + const router = createRouter('/'); + + router.get( + // Note: There is a bug within Hapi Server where it cannot set the payload timeout for a GET call but it also cannot configure a timeout less than the payload body + // so the least amount of possible time to configure the timeout is 10 seconds. + { path: '/a', validate: false, options: { timeout: 100000 } }, + async (context, req, res) => { + // Cause a wait of 20 seconds to cause the socket hangup + await new Promise((resolve) => setTimeout(resolve, 200000)); + return res.ok({}); + } + ); + router.get({ path: '/b', validate: false }, (context, req, res) => res.ok({})); + await server.start(); + + expect(supertest(innerServer.listener).get('/a')).rejects.toThrow('socket hang up'); + await supertest(innerServer.listener).get('/b').expect(200, {}); + }); + + it('should not timeout if configured with a 5 minute timeout value for a POST', async () => { + const { server: innerServer, createRouter } = await server.setup(setupDeps); + const router = createRouter('/'); + + router.post( + { path: '/a', validate: false, options: { timeout: 300000 } }, + async (context, req, res) => res.ok({}) + ); + await server.start(); + await supertest(innerServer.listener).post('/a').expect(200, {}); + }); + + it('should not timeout if configured with a 5 minute timeout value for a PUT', async () => { + const { server: innerServer, createRouter } = await server.setup(setupDeps); + const router = createRouter('/'); + + router.put( + { path: '/a', validate: false, options: { timeout: 300000 } }, + async (context, req, res) => res.ok({}) + ); + await server.start(); + + await supertest(innerServer.listener).put('/a').expect(200, {}); + }); + + it('should not timeout if configured with a 5 minute timeout value for a DELETE', async () => { + const { server: innerServer, createRouter } = await server.setup(setupDeps); + const router = createRouter('/'); + + router.delete( + { path: '/a', validate: false, options: { timeout: 300000 } }, + async (context, req, res) => res.ok({}) + ); + await server.start(); + await supertest(innerServer.listener).delete('/a').expect(200, {}); + }); + + it('should not timeout if configured with a 5 minute timeout value for a GET', async () => { + const { server: innerServer, createRouter } = await server.setup(setupDeps); + const router = createRouter('/'); + + router.get( + { path: '/a', validate: false, options: { timeout: 300000 } }, + async (context, req, res) => res.ok({}) + ); + await server.start(); + await supertest(innerServer.listener).get('/a').expect(200, {}); + }); + }); }); describe('Cache-Control', () => { diff --git a/src/core/server/http/router/request.ts b/src/core/server/http/router/request.ts index fefd75ad9710e9b..0e73431fe7c6d79 100644 --- a/src/core/server/http/router/request.ts +++ b/src/core/server/http/router/request.ts @@ -197,12 +197,14 @@ export class KibanaRequest< private getRouteInfo(request: Request): KibanaRequestRoute { const method = request.method as Method; const { parse, maxBytes, allow, output } = request.route.settings.payload || {}; + const timeout = request.route.settings.timeout?.socket; const options = ({ authRequired: this.getAuthRequired(request), // some places in LP call KibanaRequest.from(request) manually. remove fallback to true before v8 xsrfRequired: (request.route.settings.app as KibanaRouteState)?.xsrfRequired ?? true, tags: request.route.settings.tags || [], + timeout: typeof timeout === 'number' ? timeout - 1 : undefined, // We are forced to have the timeout be 1 millisecond greater than the server and payload so we subtract one here to give the user consist settings body: isSafeMethod(method) ? undefined : { diff --git a/src/core/server/http/router/route.ts b/src/core/server/http/router/route.ts index 9789d266587afc2..676c494bec522d9 100644 --- a/src/core/server/http/router/route.ts +++ b/src/core/server/http/router/route.ts @@ -144,6 +144,12 @@ export interface RouteConfigOptions { * Additional body options {@link RouteConfigOptionsBody}. */ body?: Method extends 'get' | 'options' ? undefined : RouteConfigOptionsBody; + + /** + * Timeouts for processing durations. Response timeout is in milliseconds. + * Default value: 2 minutes + */ + timeout?: number; } /** diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index bb4f2f30ac18f12..c94151f8cee1796 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -1859,6 +1859,7 @@ export interface RouteConfigOptions { authRequired?: boolean | 'optional'; body?: Method extends 'get' | 'options' ? undefined : RouteConfigOptionsBody; tags?: readonly string[]; + timeout?: number; xsrfRequired?: Method extends 'get' ? never : boolean; } diff --git a/x-pack/plugins/lists/common/constants.mock.ts b/x-pack/plugins/lists/common/constants.mock.ts index 22706890e20209c..b7609b5a3602a5d 100644 --- a/x-pack/plugins/lists/common/constants.mock.ts +++ b/x-pack/plugins/lists/common/constants.mock.ts @@ -3,8 +3,9 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { EntriesArray } from './schemas/types'; +import moment from 'moment'; +import { EntriesArray } from './schemas/types'; export const DATE_NOW = '2020-04-20T15:25:31.830Z'; export const OLD_DATE_RELATIVE_TO_DATE_NOW = '2020-04-19T15:25:31.830Z'; export const USER = 'some user'; @@ -64,3 +65,4 @@ export const CURSOR = 'c29tZXN0cmluZ2ZvcnlvdQ=='; export const _VERSION = 'WzI5NywxXQ=='; export const VERSION = 1; export const IMMUTABLE = false; +export const IMPORT_TIMEOUT = moment.duration(5, 'minutes'); diff --git a/x-pack/plugins/lists/server/config.mock.ts b/x-pack/plugins/lists/server/config.mock.ts index 3cf5040c73675fa..b272f18c4e80904 100644 --- a/x-pack/plugins/lists/server/config.mock.ts +++ b/x-pack/plugins/lists/server/config.mock.ts @@ -6,6 +6,7 @@ import { IMPORT_BUFFER_SIZE, + IMPORT_TIMEOUT, LIST_INDEX, LIST_ITEM_INDEX, MAX_IMPORT_PAYLOAD_BYTES, @@ -21,6 +22,7 @@ export const getConfigMock = (): Partial => ({ export const getConfigMockDecoded = (): ConfigType => ({ enabled: true, importBufferSize: IMPORT_BUFFER_SIZE, + importTimeout: IMPORT_TIMEOUT, listIndex: LIST_INDEX, listItemIndex: LIST_ITEM_INDEX, maxImportPayloadBytes: MAX_IMPORT_PAYLOAD_BYTES, diff --git a/x-pack/plugins/lists/server/config.test.ts b/x-pack/plugins/lists/server/config.test.ts index 60501322dcfa2fe..40b04edd4c0075f 100644 --- a/x-pack/plugins/lists/server/config.test.ts +++ b/x-pack/plugins/lists/server/config.test.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import moment from 'moment'; + import { ConfigSchema, ConfigType } from './config'; import { getConfigMock, getConfigMockDecoded } from './config.mock'; @@ -61,4 +63,24 @@ describe('config_schema', () => { '[importBufferSize]: Value must be equal to or greater than [1].' ); }); + + test('it throws if the "importTimeout" value is less than 2 minutes', () => { + const mock: ConfigType = { + ...getConfigMockDecoded(), + importTimeout: moment.duration(2, 'minutes').subtract(1, 'second'), + }; + expect(() => ConfigSchema.validate(mock)).toThrow( + '[importTimeout]: duration cannot be less than 2 minutes' + ); + }); + + test('it throws if the "importTimeout" value is greater than 1 hour', () => { + const mock: ConfigType = { + ...getConfigMockDecoded(), + importTimeout: moment.duration(1, 'hour').add(1, 'second'), + }; + expect(() => ConfigSchema.validate(mock)).toThrow( + '[importTimeout]: duration cannot be greater than 30 minutes' + ); + }); }); diff --git a/x-pack/plugins/lists/server/config.ts b/x-pack/plugins/lists/server/config.ts index 394f85ecfb6425b..6e36d135abfcc3c 100644 --- a/x-pack/plugins/lists/server/config.ts +++ b/x-pack/plugins/lists/server/config.ts @@ -9,6 +9,16 @@ import { TypeOf, schema } from '@kbn/config-schema'; export const ConfigSchema = schema.object({ enabled: schema.boolean({ defaultValue: true }), importBufferSize: schema.number({ defaultValue: 1000, min: 1 }), + importTimeout: schema.duration({ + defaultValue: '5m', + validate: (value) => { + if (value.asMinutes() < 2) { + throw new Error('duration cannot be less than 2 minutes'); + } else if (value.asMinutes() > 30) { + throw new Error('duration cannot be greater than 30 minutes'); + } + }, + }), listIndex: schema.string({ defaultValue: '.lists' }), listItemIndex: schema.string({ defaultValue: '.items' }), maxImportPayloadBytes: schema.number({ defaultValue: 9000000, min: 1 }), diff --git a/x-pack/plugins/lists/server/routes/import_list_item_route.ts b/x-pack/plugins/lists/server/routes/import_list_item_route.ts index 1003a0c52a794fe..e162e7829e4562d 100644 --- a/x-pack/plugins/lists/server/routes/import_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/import_list_item_route.ts @@ -27,6 +27,7 @@ export const importListItemRoute = (router: IRouter, config: ConfigType): void = parse: false, }, tags: ['access:lists-all'], + timeout: config.importTimeout.asMilliseconds(), }, path: `${LIST_ITEM_URL}/_import`, validate: { diff --git a/x-pack/plugins/lists/server/services/lists/list_client.mock.ts b/x-pack/plugins/lists/server/services/lists/list_client.mock.ts index e5036d561ddc6e6..a2959a024f292e1 100644 --- a/x-pack/plugins/lists/server/services/lists/list_client.mock.ts +++ b/x-pack/plugins/lists/server/services/lists/list_client.mock.ts @@ -11,6 +11,7 @@ import { getListResponseMock } from '../../../common/schemas/response/list_schem import { getCallClusterMock } from '../../../common/get_call_cluster.mock'; import { IMPORT_BUFFER_SIZE, + IMPORT_TIMEOUT, LIST_INDEX, LIST_ITEM_INDEX, MAX_IMPORT_PAYLOAD_BYTES, @@ -65,6 +66,7 @@ export const getListClientMock = (): ListClient => { config: { enabled: true, importBufferSize: IMPORT_BUFFER_SIZE, + importTimeout: IMPORT_TIMEOUT, listIndex: LIST_INDEX, listItemIndex: LIST_ITEM_INDEX, maxImportPayloadBytes: MAX_IMPORT_PAYLOAD_BYTES, From 89a392bd7cc1e6010c7518abcf948232ce855b79 Mon Sep 17 00:00:00 2001 From: John Schulz Date: Tue, 28 Jul 2020 19:55:13 -0400 Subject: [PATCH 05/10] [Ingest Manager] API sends 404 when package config id is missing (#73212) * Add test to confirm missing config responds w/ 404 Currently failing with a 500 as in https://github.com/elastic/kibana/issues/66388 * Use after() to remove items added by test. The test initally failed with a 500 when the `after` was added. Debugging narrowed it down to a missing default config. getDefaultAgentConfigId errors if there isn't a default config. The config is added by `setupIngestManager` which _was_ always called during plugin#start but is no longer. We could add the setup call to the test/suite, but instead I changed AgentConfigService.delete to use ensureDefaultAgentConfig instead of getDefaultAgentConfigId. ensureDefaultAgentConfig adds one if it's missing. The check in delete is to make sure we don't delete the default config. We can still do that and now we add a config if it wasn't already there (which seems like A Good Thing) * Fix package config path in OpenApi spec * Return 404 if package config id is invalid/missing * Change test for error displayed text Co-authored-by: Elastic Machine --- .../common/openapi/spec_oas3.json | 4 +- .../server/routes/package_config/handlers.ts | 23 +++-- .../server/services/agent_config.ts | 2 +- .../apis/index.js | 1 + .../apis/package_config/get.ts | 89 +++++++++++++++++++ .../apps/endpoint/policy_details.ts | 2 +- 6 files changed, 108 insertions(+), 13 deletions(-) create mode 100644 x-pack/test/ingest_manager_api_integration/apis/package_config/get.ts diff --git a/x-pack/plugins/ingest_manager/common/openapi/spec_oas3.json b/x-pack/plugins/ingest_manager/common/openapi/spec_oas3.json index e16edac5ddb7a49..cfae2c450c824ee 100644 --- a/x-pack/plugins/ingest_manager/common/openapi/spec_oas3.json +++ b/x-pack/plugins/ingest_manager/common/openapi/spec_oas3.json @@ -922,7 +922,7 @@ }, "parameters": [] }, - "/packageConfigs": { + "/package_configs": { "get": { "summary": "PackageConfigs - List", "tags": [], @@ -1237,7 +1237,7 @@ ] } }, - "/packageConfigs/{packageConfigId}": { + "/package_configs/{packageConfigId}": { "get": { "summary": "PackageConfigs - Info", "tags": [], diff --git a/x-pack/plugins/ingest_manager/server/routes/package_config/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/package_config/handlers.ts index 6b0c2fe9c2ff7c1..d2820cdbeb6c8a9 100644 --- a/x-pack/plugins/ingest_manager/server/routes/package_config/handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/package_config/handlers.ts @@ -5,7 +5,7 @@ */ import { TypeOf } from '@kbn/config-schema'; import Boom from 'boom'; -import { RequestHandler } from 'src/core/server'; +import { RequestHandler, SavedObjectsErrorHelpers } from '../../../../../../src/core/server'; import { appContextService, packageConfigService } from '../../services'; import { getPackageInfo } from '../../services/epm/packages'; import { @@ -49,8 +49,12 @@ export const getOnePackageConfigHandler: RequestHandler> = async (context, request, response) => { const soClient = context.core.savedObjects.client; + const { packageConfigId } = request.params; + const notFoundResponse = () => + response.notFound({ body: { message: `Package config ${packageConfigId} not found` } }); + try { - const packageConfig = await packageConfigService.get(soClient, request.params.packageConfigId); + const packageConfig = await packageConfigService.get(soClient, packageConfigId); if (packageConfig) { return response.ok({ body: { @@ -58,17 +62,18 @@ export const getOnePackageConfigHandler: RequestHandler {} here + // because `this` has to point to the Mocha context + // see https://mochajs.org/#arrow-functions + + describe('Package Config - get by id', async function () { + skipIfNoDockerRegistry(providerContext); + let agentConfigId: string; + let packageConfigId: string; + + before(async function () { + if (!server.enabled) { + return; + } + const { body: agentConfigResponse } = await supertest + .post(`/api/ingest_manager/agent_configs`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'Test config', + namespace: 'default', + }); + agentConfigId = agentConfigResponse.item.id; + + const { body: packageConfigResponse } = await supertest + .post(`/api/ingest_manager/package_configs`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'filetest-1', + description: '', + namespace: 'default', + config_id: agentConfigId, + enabled: true, + output_id: '', + inputs: [], + package: { + name: 'filetest', + title: 'For File Tests', + version: '0.1.0', + }, + }); + packageConfigId = packageConfigResponse.item.id; + }); + + after(async function () { + if (!server.enabled) { + return; + } + + await supertest + .post(`/api/ingest_manager/agent_configs/delete`) + .set('kbn-xsrf', 'xxxx') + .send({ agentConfigId }) + .expect(200); + + await supertest + .post(`/api/ingest_manager/package_configs/delete`) + .set('kbn-xsrf', 'xxxx') + .send({ packageConfigIds: [packageConfigId] }) + .expect(200); + }); + + it('should succeed with a valid id', async function () { + const { body: apiResponse } = await supertest + .get(`/api/ingest_manager/package_configs/${packageConfigId}`) + .expect(200); + + expect(apiResponse.success).to.be(true); + }); + + it('should return a 404 with an invalid id', async function () { + await supertest.get(`/api/ingest_manager/package_configs/IS_NOT_PRESENT`).expect(404); + }); + }); +} diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts index cf76f297d83be14..8b197a6c69a305a 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts @@ -27,7 +27,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await pageObjects.policy.navigateToPolicyDetails('invalid-id'); await testSubjects.existOrFail('policyDetailsIdNotFoundMessage'); expect(await testSubjects.getVisibleText('policyDetailsIdNotFoundMessage')).to.equal( - 'Saved object [ingest-package-configs/invalid-id] not found' + 'Package config invalid-id not found' ); }); }); From 14b2cbb15575be3d488ce36dac25244629b180a6 Mon Sep 17 00:00:00 2001 From: Michael Olorunnisola Date: Tue, 28 Jul 2020 19:58:26 -0400 Subject: [PATCH 06/10] [Security Solution][Resolver] Handle iso time strings (#73551) Co-authored-by: Elastic Machine --- .../public/resolver/lib/date.test.ts | 56 ++++++++++++++----- .../public/resolver/lib/date.ts | 29 ++++++++-- 2 files changed, 66 insertions(+), 19 deletions(-) diff --git a/x-pack/plugins/security_solution/public/resolver/lib/date.test.ts b/x-pack/plugins/security_solution/public/resolver/lib/date.test.ts index 7a48245fcfc414a..5555578e44f7b58 100644 --- a/x-pack/plugins/security_solution/public/resolver/lib/date.test.ts +++ b/x-pack/plugins/security_solution/public/resolver/lib/date.test.ts @@ -3,38 +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 { getFriendlyElapsedTime } from './date'; +import { getFriendlyElapsedTime, getUnixTime } from './date'; describe('date', () => { - describe('getFriendlyElapsedTime', () => { - const second = 1000; - const minute = second * 60; - const hour = minute * 60; - const day = hour * 24; - const week = day * 7; - const month = day * 30; - const year = day * 365; + const second = 1000; + const minute = second * 60; + const hour = minute * 60; + const day = hour * 24; + const week = day * 7; + const month = day * 30; + const year = day * 365; - const initialTime = new Date('6/1/2020').getTime(); + describe('getUnixTime', () => { + const unixTime = new Date('6/1/2020').getTime(); + const unixStringTime = String(unixTime); + const isoTime = new Date('6/1/2020').toISOString(); + const notATime = 'imLate'; + + it('should return the time if already a unix timestamp', () => { + expect(getUnixTime(unixTime)).toEqual(unixTime); + }); + + it('should properly convert a unix timestamp string to a number', () => { + expect(getUnixTime(unixStringTime)).toEqual(unixTime); + }); + + it('should properly convert an ISO string to a unix timestamp', () => { + expect(getUnixTime(isoTime)).toEqual(unixTime); + }); - const oneMillisecond = new Date(initialTime + 1).getTime(); + it('should return NaN if an invalid time is provided', () => { + expect(getUnixTime(notATime)).toBeNaN(); + }); + }); + + describe('getFriendlyElapsedTime', () => { + const initialTime = new Date('6/1/2020').getTime(); + const oneMillisecond = new Date(initialTime + 1).toISOString(); const oneSecond = new Date(initialTime + 1 * second).getTime(); const oneMinute = new Date(initialTime + 1 * minute).getTime(); - const oneHour = new Date(initialTime + 1 * hour).getTime(); + const oneHour = new Date(initialTime + 1 * hour).toISOString(); const oneDay = new Date(initialTime + 1 * day).getTime(); - const oneWeek = new Date(initialTime + 1 * week).getTime(); + const oneWeek = `${new Date(initialTime + 1 * week).getTime()}`; const oneMonth = new Date(initialTime + 1 * month).getTime(); const oneYear = new Date(initialTime + 1 * year).getTime(); const almostASecond = new Date(initialTime + 999).getTime(); - const almostAMinute = new Date(initialTime + 59.9 * second).getTime(); + const almostAMinute = new Date(initialTime + 59.9 * second).toISOString(); const almostAnHour = new Date(initialTime + 59.9 * minute).getTime(); const almostADay = new Date(initialTime + 23.9 * hour).getTime(); - const almostAWeek = new Date(initialTime + 6.9 * day).getTime(); + const almostAWeek = new Date(initialTime + 6.9 * day).toISOString(); const almostAMonth = new Date(initialTime + 3.9 * week).getTime(); const almostAYear = new Date(initialTime + 11.9 * month).getTime(); const threeYears = new Date(initialTime + 3 * year).getTime(); + it('should return null if invalid times are given', () => { + expect(getFriendlyElapsedTime(initialTime, 'ImTimeless')).toEqual(null); + }); + it('should return the correct singular relative time', () => { expect(getFriendlyElapsedTime(initialTime, initialTime)).toEqual({ duration: '<1', diff --git a/x-pack/plugins/security_solution/public/resolver/lib/date.ts b/x-pack/plugins/security_solution/public/resolver/lib/date.ts index a5e07e6a02a885b..3cd0c910f46f319 100644 --- a/x-pack/plugins/security_solution/public/resolver/lib/date.ts +++ b/x-pack/plugins/security_solution/public/resolver/lib/date.ts @@ -6,6 +6,26 @@ import { DurationDetails, DurationTypes } from '../types'; +/** + * Given a time, it will convert it to a unix timestamp if not one already. If it is unable to do so, it will return NaN + */ +export const getUnixTime = (time: number | string): number | typeof NaN => { + if (!time) { + return NaN; + } + if (typeof time === 'number') { + return time; + } + // If it's a date string just get the time in MS + let unixTime = Date.parse(time); + if (Number.isNaN(unixTime)) { + // If not an ISO date string, last check will be if it's a unix timestamp string + unixTime = parseInt(time, 10); + } + + return unixTime; +}; + /* * Given two unix timestamps, it will return an object containing the time difference and properly pluralized friendly version of the time difference. * i.e. a time difference of 1000ms will yield => { duration: 1, durationType: 'second' } and 10000ms will yield => { duration: 10, durationType: 'seconds' } @@ -15,12 +35,13 @@ export const getFriendlyElapsedTime = ( from: number | string, to: number | string ): DurationDetails | null => { - const startTime = typeof from === 'number' ? from : parseInt(from, 10); - const endTime = typeof to === 'number' ? to : parseInt(to, 10); - const elapsedTimeInMs = endTime - startTime; - if (Number.isNaN(elapsedTimeInMs)) { + const startTime = getUnixTime(from); + const endTime = getUnixTime(to); + + if (Number.isNaN(startTime) || Number.isNaN(endTime)) { return null; } + const elapsedTimeInMs = endTime - startTime; const second = 1000; const minute = second * 60; From 17ec168c287733253345854eff2e75f9b151a932 Mon Sep 17 00:00:00 2001 From: Michael Olorunnisola Date: Tue, 28 Jul 2020 19:58:58 -0400 Subject: [PATCH 07/10] [Security Solution][Resolver] Undo origin panel update (#73501) Co-authored-by: Elastic Machine --- .../public/resolver/view/map.tsx | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/security_solution/public/resolver/view/map.tsx b/x-pack/plugins/security_solution/public/resolver/view/map.tsx index 19c403f1257bec9..0ca71c5bf60cef9 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/map.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/map.tsx @@ -8,7 +8,7 @@ /* eslint-disable react/display-name */ -import React, { useContext, useEffect } from 'react'; +import React, { useContext } from 'react'; import { useSelector } from 'react-redux'; import { useEffectOnce } from 'react-use'; import { EuiLoadingSpinner } from '@elastic/eui'; @@ -68,25 +68,12 @@ export const ResolverMap = React.memo(function ({ const hasError = useSelector(selectors.hasError); const activeDescendantId = useSelector(selectors.ariaActiveDescendant); const { colorMap } = useResolverTheme(); - const { - cleanUpQueryParams, - queryParams: { crumbId }, - pushToQueryParams, - } = useResolverQueryParams(); + const { cleanUpQueryParams } = useResolverQueryParams(); useEffectOnce(() => { return () => cleanUpQueryParams(); }); - useEffect(() => { - // When you refresh the page after selecting a process in the table view (not the timeline view) - // The old crumbId still exists in the query string even though a resolver is no longer visible - // This just makes sure the activeDescendant and crumbId are in sync on load for that view as well as the timeline - if (activeDescendantId && crumbId !== activeDescendantId) { - pushToQueryParams({ crumbId: activeDescendantId, crumbEvent: '' }); - } - }, [crumbId, activeDescendantId, pushToQueryParams]); - return ( {isLoading ? ( From 774d3591c083e14bdd56339f2a93eb27e1d531f5 Mon Sep 17 00:00:00 2001 From: Angela Chuang <6295984+angorayc@users.noreply.github.com> Date: Wed, 29 Jul 2020 01:03:45 +0100 Subject: [PATCH 08/10] [Security Solution] Update security overview splash (#73050) ## Summary https://github.com/elastic/endpoint-app-team/issues/591 How to verify: 1. go to: x-pack/test/security_solution_cypress/runner.ts 2. comment line 20 (await esArchiver.load('auditbeat');) 3. in line 25 change cypress:run for cypress:open 4. then in our directory run yarn cypress:run-as-ci when the cypress is open, 5. you can access the Kibana instance in port 5620 with username elastic and password changeme Screenshot 2020-07-23 at 14 48 34 ### Checklist Delete any items that are not applicable to this PR. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md) - [ ] ~[Documentation](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#writing-documentation) was added for features that require explanation or tutorials~ - [ ] ~[Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios~ - [ ] ~This was checked for [keyboard-only and screenreader accessibility](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Accessibility_testing_checklist)~ - [ ] ~This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)~ - [ ] ~This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers)~ ### For maintainers - [ ] ~This was checked for breaking API changes and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process)~ --- .../public/doc_links/doc_links_service.ts | 2 +- .../pages/saved_object_no_permissions.tsx | 18 +- .../__snapshots__/index.test.tsx.snap | 24 ++- .../components/empty_page/index.test.tsx | 14 +- .../common/components/empty_page/index.tsx | 157 +++++++++++------- .../public/common/translations.ts | 39 ++++- .../detection_engine.test.tsx | 3 + .../detection_engine_no_signal_index.tsx | 19 ++- .../detection_engine_user_unauthenticated.tsx | 19 ++- .../pages/detection_engine/translations.ts | 4 +- .../components/overview_empty/index.test.tsx | 83 +++++++++ .../components/overview_empty/index.tsx | 53 ++++-- .../public/overview/pages/overview.test.tsx | 4 +- .../public/overview/pages/overview.tsx | 2 +- .../translations/translations/ja-JP.json | 4 +- .../translations/translations/zh-CN.json | 4 +- 16 files changed, 331 insertions(+), 118 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/overview/components/overview_empty/index.test.tsx diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 70b25cb78787ae5..eb54983d0be1359 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -112,7 +112,7 @@ export class DocLinksService { kibana: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/index.html`, siem: { guide: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/index.html`, - gettingStarted: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/install-siem.html`, + gettingStarted: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/index.html`, }, query: { luceneQuerySyntax: `${ELASTICSEARCH_DOCS}query-dsl-query-string-query.html#query-string-syntax`, diff --git a/x-pack/plugins/security_solution/public/cases/pages/saved_object_no_permissions.tsx b/x-pack/plugins/security_solution/public/cases/pages/saved_object_no_permissions.tsx index 7129aa04bdf696a..c61ff6d18caab8b 100644 --- a/x-pack/plugins/security_solution/public/cases/pages/saved_object_no_permissions.tsx +++ b/x-pack/plugins/security_solution/public/cases/pages/saved_object_no_permissions.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 { EmptyPage } from '../../common/components/empty_page'; import * as i18n from './translations'; @@ -12,13 +12,21 @@ import { useKibana } from '../../common/lib/kibana'; export const CaseSavedObjectNoPermissions = React.memo(() => { const docLinks = useKibana().services.docLinks; + const actions = useMemo( + () => ({ + savedObject: { + icon: 'documents', + label: i18n.GO_TO_DOCUMENTATION, + url: `${docLinks.ELASTIC_WEBSITE_URL}guide/en/security/${docLinks.DOC_LINK_VERSION}s`, + target: '_blank', + }, + }), + [docLinks] + ); return ( - - Do Something - + + Do Something + + } + title={false} + /> } diff --git a/x-pack/plugins/security_solution/public/common/components/empty_page/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/empty_page/index.test.tsx index 6a14c12cee0f85f..8e025faefeabe32 100644 --- a/x-pack/plugins/security_solution/public/common/components/empty_page/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/empty_page/index.test.tsx @@ -10,12 +10,12 @@ import React from 'react'; import { EmptyPage } from './index'; test('renders correctly', () => { - const EmptyComponent = shallow( - - ); + const actions = { + actions: { + label: 'Do Something', + url: 'my/url/from/nowwhere', + }, + }; + const EmptyComponent = shallow(); expect(EmptyComponent).toMatchSnapshot(); }); diff --git a/x-pack/plugins/security_solution/public/common/components/empty_page/index.tsx b/x-pack/plugins/security_solution/public/common/components/empty_page/index.tsx index f6d6752729b6d9b..89f4b125e930cd4 100644 --- a/x-pack/plugins/security_solution/public/common/components/empty_page/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/empty_page/index.tsx @@ -4,84 +4,123 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiButton, EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, IconType } from '@elastic/eui'; -import React, { MouseEventHandler, ReactNode } from 'react'; +import { + EuiButton, + EuiEmptyPrompt, + EuiFlexGroup, + EuiFlexItem, + IconType, + EuiCard, +} from '@elastic/eui'; +import React, { MouseEventHandler, ReactNode, useMemo } from 'react'; import styled from 'styled-components'; const EmptyPrompt = styled(EuiEmptyPrompt)` align-self: center; /* Corrects horizontal centering in IE11 */ + max-width: 60em; `; EmptyPrompt.displayName = 'EmptyPrompt'; +interface EmptyPageActions { + icon?: IconType; + label: string; + target?: string; + url: string; + descriptionTitle?: string; + description?: string; + fill?: boolean; + onClick?: MouseEventHandler; +} + +export type EmptyPageActionsProps = Record; + interface EmptyPageProps { - actionPrimaryIcon?: IconType; - actionPrimaryLabel: string; - actionPrimaryTarget?: string; - actionPrimaryUrl: string; - actionPrimaryFill?: boolean; - actionSecondaryIcon?: IconType; - actionSecondaryLabel?: string; - actionSecondaryTarget?: string; - actionSecondaryUrl?: string; - actionSecondaryOnClick?: MouseEventHandler; + actions: EmptyPageActionsProps; 'data-test-subj'?: string; message?: ReactNode; title: string; } -export const EmptyPage = React.memo( - ({ - actionPrimaryIcon, - actionPrimaryLabel, - actionPrimaryTarget, - actionPrimaryUrl, - actionPrimaryFill = true, - actionSecondaryIcon, - actionSecondaryLabel, - actionSecondaryTarget, - actionSecondaryUrl, - actionSecondaryOnClick, - message, - title, - ...rest - }) => ( +const EmptyPageComponent = React.memo(({ actions, message, title, ...rest }) => { + const titles = Object.keys(actions); + const maxItemWidth = 283; + const renderActions = useMemo( + () => + Object.values(actions) + .filter((a) => a.label && a.url) + .map( + ( + { + icon, + label, + target, + url, + descriptionTitle = false, + description = false, + onClick, + fill = true, + }, + idx + ) => + descriptionTitle != null || description != null ? ( + + + {label} + + } + /> + + ) : ( + + {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} + + {label} + + + ) + ), + [actions, titles] + ); + + return ( {title}} body={message &&

{message}

} - actions={ - - - - {actionPrimaryLabel} - - - - {actionSecondaryLabel && actionSecondaryUrl && ( - - {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} - - {actionSecondaryLabel} - - - )} - - } + actions={{renderActions}} {...rest} /> - ) -); + ); +}); + +EmptyPageComponent.displayName = 'EmptyPageComponent'; +export const EmptyPage = React.memo(EmptyPageComponent); EmptyPage.displayName = 'EmptyPage'; diff --git a/x-pack/plugins/security_solution/public/common/translations.ts b/x-pack/plugins/security_solution/public/common/translations.ts index 413119fb40f1419..3b94ac8959496f6 100644 --- a/x-pack/plugins/security_solution/public/common/translations.ts +++ b/x-pack/plugins/security_solution/public/common/translations.ts @@ -7,16 +7,39 @@ import { i18n } from '@kbn/i18n'; export const EMPTY_TITLE = i18n.translate('xpack.securitySolution.pages.common.emptyTitle', { - defaultMessage: 'Welcome to Security Solution. Let’s get you started.', + defaultMessage: 'Welcome to Elastic Security. Let’s get you started.', }); -export const EMPTY_ACTION_PRIMARY = i18n.translate( - 'xpack.securitySolution.pages.common.emptyActionPrimary', +export const EMPTY_ACTION_ELASTIC_AGENT = i18n.translate( + 'xpack.securitySolution.pages.common.emptyActionElasticAgent', + { + defaultMessage: 'Add data with Elastic Agent', + } +); + +export const EMPTY_ACTION_ELASTIC_AGENT_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.pages.common.emptyActionElasticAgentDescription', + { + defaultMessage: + 'The Elastic Agent provides a simple, unified way to add monitoring to your hosts.', + } +); + +export const EMPTY_ACTION_BEATS = i18n.translate( + 'xpack.securitySolution.pages.common.emptyActionBeats', { defaultMessage: 'Add data with Beats', } ); +export const EMPTY_ACTION_BEATS_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.pages.common.emptyActionBeatsDescription', + { + defaultMessage: + 'Lightweight Beats can send data from hundreds or thousands of machines and systems', + } +); + export const EMPTY_ACTION_SECONDARY = i18n.translate( 'xpack.securitySolution.pages.common.emptyActionSecondary', { @@ -27,6 +50,14 @@ export const EMPTY_ACTION_SECONDARY = i18n.translate( export const EMPTY_ACTION_ENDPOINT = i18n.translate( 'xpack.securitySolution.pages.common.emptyActionEndpoint', { - defaultMessage: 'Add data with Elastic Agent (Beta)', + defaultMessage: 'Add Elastic Endpoint Security', + } +); + +export const EMPTY_ACTION_ENDPOINT_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.pages.common.emptyActionEndpointDescription', + { + defaultMessage: + 'Protect your hosts with threat prevention, detection, and deep security data visibility.', } ); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx index 110620fad7eba81..982712cbe97975b 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx @@ -53,6 +53,9 @@ jest.mock('react-router-dom', () => { useHistory: jest.fn(), }; }); +jest.mock('../../components/alerts_info', () => ({ + useAlertInfo: jest.fn().mockReturnValue([]), +})); const state: State = { ...mockGlobalState, diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine_no_signal_index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine_no_signal_index.tsx index 32ae585aec19105..c315361b294c787 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine_no_signal_index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine_no_signal_index.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 { EmptyPage } from '../../../common/components/empty_page'; import * as i18n from './translations'; @@ -12,12 +12,21 @@ import { useKibana } from '../../../common/lib/kibana'; export const DetectionEngineNoIndex = React.memo(() => { const docLinks = useKibana().services.docLinks; + const actions = useMemo( + () => ({ + detections: { + icon: 'documents', + label: i18n.GO_TO_DOCUMENTATION, + url: `${docLinks.ELASTIC_WEBSITE_URL}guide/en/security/${docLinks.DOC_LINK_VERSION}/detection-engine-overview.html#detections-permissions`, + target: '_blank', + }, + }), + [docLinks] + ); + return ( { const docLinks = useKibana().services.docLinks; - + const actions = useMemo( + () => ({ + detectionUnauthenticated: { + icon: 'documents', + label: i18n.GO_TO_DOCUMENTATION, + url: `${docLinks.ELASTIC_WEBSITE_URL}guide/en/security/${docLinks.DOC_LINK_VERSION}/detection-engine-overview.html#detections-permissions`, + target: '_blank', + }, + }), + [docLinks] + ); return ( ({ + useIngestUrl: jest + .fn() + .mockReturnValue({ appId: 'ingestAppId', appPath: 'ingestPath', url: 'ingestUrl' }), +})); + +jest.mock('../../../common/hooks/endpoint/ingest_enabled', () => ({ + useIngestEnabledCheck: jest.fn().mockReturnValue({ allEnabled: true }), +})); + +jest.mock('../../../common/hooks/endpoint/use_navigate_to_app_event_handler', () => ({ + useNavigateToAppEventHandler: jest.fn(), +})); + +describe('OverviewEmpty', () => { + describe('When isIngestEnabled = true', () => { + let wrapper: ShallowWrapper; + beforeAll(() => { + wrapper = shallow(); + }); + + afterAll(() => { + (useIngestEnabledCheck as jest.Mock).mockReset(); + }); + + test('render with correct actions ', () => { + expect(wrapper.find('[data-test-subj="empty-page"]').prop('actions')).toEqual({ + beats: { + description: + 'Lightweight Beats can send data from hundreds or thousands of machines and systems', + fill: false, + label: 'Add data with Beats', + url: '/app/home#/tutorial_directory/security', + }, + elasticAgent: { + description: + 'The Elastic Agent provides a simple, unified way to add monitoring to your hosts.', + fill: false, + label: 'Add data with Elastic Agent', + url: 'ingestUrl', + }, + endpoint: { + description: + 'Protect your hosts with threat prevention, detection, and deep security data visibility.', + fill: false, + label: 'Add Elastic Endpoint Security', + onClick: undefined, + url: '/app/home#/tutorial_directory/security', + }, + }); + }); + }); + + describe('When isIngestEnabled = false', () => { + let wrapper: ShallowWrapper; + beforeAll(() => { + (useIngestEnabledCheck as jest.Mock).mockReturnValue({ allEnabled: false }); + wrapper = shallow(); + }); + + test('render with correct actions ', () => { + expect(wrapper.find('[data-test-subj="empty-page"]').prop('actions')).toEqual({ + beats: { + description: + 'Lightweight Beats can send data from hundreds or thousands of machines and systems', + fill: false, + label: 'Add data with Beats', + url: '/app/home#/tutorial_directory/security', + }, + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.tsx index 33413be10079e51..1d2c6889213f139 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.tsx @@ -4,11 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useMemo } from 'react'; +import { omit } from 'lodash/fp'; + import { FormattedMessage } from '@kbn/i18n/react'; import { EuiLink } from '@elastic/eui'; import * as i18nCommon from '../../../common/translations'; -import { EmptyPage } from '../../../common/components/empty_page'; +import { EmptyPage, EmptyPageActionsProps } from '../../../common/components/empty_page'; import { useKibana } from '../../../common/lib/kibana'; import { ADD_DATA_PATH } from '../../../../common/constants'; import { useIngestUrl } from '../../../management/pages/endpoint_hosts/view/hooks'; @@ -23,23 +25,45 @@ const OverviewEmptyComponent: React.FC = () => { ); const handleOnClick = useNavigateToAppEventHandler(ingestAppId, { path: ingestPath }); const { allEnabled: isIngestEnabled } = useIngestEnabledCheck(); + const emptyPageActions: EmptyPageActionsProps = useMemo( + () => ({ + elasticAgent: { + label: i18nCommon.EMPTY_ACTION_ELASTIC_AGENT, + url: ingestUrl, + description: i18nCommon.EMPTY_ACTION_ELASTIC_AGENT_DESCRIPTION, + fill: false, + }, + beats: { + label: i18nCommon.EMPTY_ACTION_BEATS, + url: `${basePath}${ADD_DATA_PATH}`, + description: i18nCommon.EMPTY_ACTION_BEATS_DESCRIPTION, + fill: false, + }, + endpoint: { + label: i18nCommon.EMPTY_ACTION_ENDPOINT, + url: `${basePath}${ADD_DATA_PATH}`, + description: i18nCommon.EMPTY_ACTION_ENDPOINT_DESCRIPTION, + onClick: handleOnClick, + fill: false, + }, + }), + [basePath, ingestUrl, handleOnClick] + ); + + const emptyPageIngestDisabledActions = useMemo( + () => omit(['elasticAgent', 'endpoint'], emptyPageActions), + [emptyPageActions] + ); return isIngestEnabled === true ? ( {i18nCommon.EMPTY_ACTION_SECONDARY} @@ -50,16 +74,13 @@ const OverviewEmptyComponent: React.FC = () => { /> ) : ( {i18nCommon.EMPTY_ACTION_SECONDARY} diff --git a/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx b/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx index 286cc870378e163..74225c4e4f82328 100644 --- a/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx @@ -83,7 +83,7 @@ describe('Overview', () => { ); - expect(wrapper.find('[data-test-subj="empty-page-secondary-action"]').exists()).toBe(false); + expect(wrapper.find('[data-test-subj="empty-page-endpoint-action"]').exists()).toBe(false); }); it('shows Endpoint get ready button when ingest is enabled', () => { @@ -95,7 +95,7 @@ describe('Overview', () => { ); - expect(wrapper.find('[data-test-subj="empty-page-secondary-action"]').exists()).toBe(true); + expect(wrapper.find('[data-test-subj="empty-page-endpoint-action"]').exists()).toBe(true); }); }); diff --git a/x-pack/plugins/security_solution/public/overview/pages/overview.tsx b/x-pack/plugins/security_solution/public/overview/pages/overview.tsx index 423aa597a0129b6..1b743c259555acf 100644 --- a/x-pack/plugins/security_solution/public/overview/pages/overview.tsx +++ b/x-pack/plugins/security_solution/public/overview/pages/overview.tsx @@ -17,6 +17,7 @@ import { SiemSearchBar } from '../../common/components/search_bar'; import { WrapperPage } from '../../common/components/wrapper_page'; import { useGlobalTime } from '../../common/containers/use_global_time'; import { useWithSource } from '../../common/containers/source'; + import { EventsByDataset } from '../components/events_by_dataset'; import { EventCounts } from '../components/event_counts'; import { OverviewEmpty } from '../components/overview_empty'; @@ -66,7 +67,6 @@ const OverviewComponent: React.FC = ({ addMessage('management', 'dismissEndpointNotice'); }, [addMessage]); const { allEnabled: isIngestEnabled } = useIngestEnabledCheck(); - return ( <> {indicesExist ? ( diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index b6aaa2065c79541..8fbcb3f1122cc17 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -13572,7 +13572,7 @@ "xpack.securitySolution.detectionEngine.editRule.errorMsgDescription": "申し訳ありません", "xpack.securitySolution.detectionEngine.editRule.pageTitle": "ルール設定の編集", "xpack.securitySolution.detectionEngine.editRule.saveChangeTitle": "変更を保存", - "xpack.securitySolution.detectionEngine.emptyActionPrimary": "セットアップの手順を表示", + "xpack.securitySolution.detectionEngine.emptyActionBeats": "セットアップの手順を表示", "xpack.securitySolution.detectionEngine.emptyActionSecondary": "ドキュメントに移動", "xpack.securitySolution.detectionEngine.emptyTitle": "Securityアプリケーションの検出エンジンに関連したインデックスがないようです", "xpack.securitySolution.detectionEngine.goToDocumentationButton": "ドキュメンテーションを表示", @@ -14349,7 +14349,7 @@ "xpack.securitySolution.overview.viewEventsButtonLabel": "イベントを表示", "xpack.securitySolution.overview.winlogbeatMWSysmonOperational": "Microsoft-Windows-Sysmon/Operational", "xpack.securitySolution.overview.winlogbeatSecurityTitle": "セキュリティ", - "xpack.securitySolution.pages.common.emptyActionPrimary": "Beatsでデータを表示", + "xpack.securitySolution.pages.common.emptyActionBeats": "Beatsでデータを表示", "xpack.securitySolution.pages.common.emptyActionSecondary": "入門ガイドを表示", "xpack.securitySolution.pages.common.emptyTitle": "SIEMへようこそ。始めましょう。", "xpack.securitySolution.pages.fourohfour.noContentFoundDescription": "コンテンツがありません", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index dcbcda120587fa5..b69763449a06f64 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -13578,7 +13578,7 @@ "xpack.securitySolution.detectionEngine.editRule.errorMsgDescription": "抱歉", "xpack.securitySolution.detectionEngine.editRule.pageTitle": "编辑规则设置", "xpack.securitySolution.detectionEngine.editRule.saveChangeTitle": "保存更改", - "xpack.securitySolution.detectionEngine.emptyActionPrimary": "查看设置说明", + "xpack.securitySolution.detectionEngine.emptyActionBeats": "查看设置说明", "xpack.securitySolution.detectionEngine.emptyActionSecondary": "前往文档", "xpack.securitySolution.detectionEngine.emptyTitle": "似乎您没有与 Security 应用程序的检测引擎相关的索引", "xpack.securitySolution.detectionEngine.goToDocumentationButton": "查看文档", @@ -14355,7 +14355,7 @@ "xpack.securitySolution.overview.viewEventsButtonLabel": "查看事件", "xpack.securitySolution.overview.winlogbeatMWSysmonOperational": "Microsoft-Windows-Sysmon/Operational", "xpack.securitySolution.overview.winlogbeatSecurityTitle": "安全", - "xpack.securitySolution.pages.common.emptyActionPrimary": "使用 Beats 添加数据", + "xpack.securitySolution.pages.common.emptyActionBeats": "使用 Beats 添加数据", "xpack.securitySolution.pages.common.emptyActionSecondary": "查看入门指南", "xpack.securitySolution.pages.common.emptyTitle": "欢迎使用 SIEM。让我们教您如何入门。", "xpack.securitySolution.pages.fourohfour.noContentFoundDescription": "未找到任何内容", From f1c08939ead12341ccb2dabfe2bb9f8681de4e65 Mon Sep 17 00:00:00 2001 From: Candace Park <56409205+parkiino@users.noreply.github.com> Date: Tue, 28 Jul 2020 20:21:11 -0400 Subject: [PATCH 09/10] [SECURITY_SOLUTION][Administration] Task/remove policy tab (#73352) --- .../components/management_empty_state.tsx | 63 ++----------------- .../components/management_page_view.tsx | 45 +------------ .../view/details/host_details.tsx | 6 +- .../endpoint_hosts/view/details/index.tsx | 4 +- .../pages/endpoint_hosts/view/index.test.tsx | 4 +- .../pages/endpoint_hosts/view/index.tsx | 4 +- .../public/management/pages/policy/index.tsx | 8 +-- .../configure_package_config.tsx | 58 +++++++---------- .../pages/policy/view/policy_details.test.tsx | 14 ++--- .../pages/policy/view/policy_details.tsx | 19 +++--- .../pages/policy/view/policy_list.test.tsx | 3 +- .../endpoint_overview/translations.ts | 4 +- .../apps/endpoint/endpoint_list.ts | 10 +-- .../apps/endpoint/index.ts | 1 - .../apps/endpoint/policy_details.ts | 4 +- 15 files changed, 68 insertions(+), 179 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/components/management_empty_state.tsx b/x-pack/plugins/security_solution/public/management/components/management_empty_state.tsx index fb9f97f3f7570e5..a4518d1a1f49377 100644 --- a/x-pack/plugins/security_solution/public/management/components/management_empty_state.tsx +++ b/x-pack/plugins/security_solution/public/management/components/management_empty_state.tsx @@ -77,57 +77,6 @@ const PolicyEmptyState = React.memo<{ /> - - - - - - - - -

- -

-
-
-
- - - - -
- - - - - - - -

- -

-
-
-
- - - - -
-
- [ { title: i18n.translate('xpack.securitySolution.endpoint.hostList.stepOneTitle', { - defaultMessage: 'Select the policy you want to use to protect your hosts', + defaultMessage: 'Select the integration you want to use', }), children: ( <> @@ -203,7 +152,7 @@ const HostsEmptyState = React.memo<{ ) : selectionOptions.length ? ( @@ -211,7 +160,7 @@ const HostsEmptyState = React.memo<{ ) : ( ); }} @@ -263,13 +212,13 @@ const HostsEmptyState = React.memo<{ headerComponent={ } bodyComponent={ } /> diff --git a/x-pack/plugins/security_solution/public/management/components/management_page_view.tsx b/x-pack/plugins/security_solution/public/management/components/management_page_view.tsx index 42341b524362df1..aa562b9a2020179 100644 --- a/x-pack/plugins/security_solution/public/management/components/management_page_view.tsx +++ b/x-pack/plugins/security_solution/public/management/components/management_page_view.tsx @@ -4,52 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { memo, useMemo } from 'react'; -import { i18n } from '@kbn/i18n'; -import { useParams } from 'react-router-dom'; +import React, { memo } from 'react'; import { PageView, PageViewProps } from '../../common/components/endpoint/page_view'; -import { AdministrationSubTab } from '../types'; -import { SecurityPageName } from '../../app/types'; -import { useFormatUrl } from '../../common/components/link_to'; -import { getHostListPath, getPoliciesPath } from '../common/routing'; -import { useNavigateByRouterEventHandler } from '../../common/hooks/endpoint/use_navigate_by_router_event_handler'; export const ManagementPageView = memo>((options) => { - const { formatUrl, search } = useFormatUrl(SecurityPageName.administration); - const { tabName } = useParams<{ tabName: AdministrationSubTab }>(); - - const goToEndpoint = useNavigateByRouterEventHandler( - getHostListPath({ name: 'hostList' }, search) - ); - - const goToPolicies = useNavigateByRouterEventHandler(getPoliciesPath(search)); - - const tabs = useMemo((): PageViewProps['tabs'] | undefined => { - if (options.viewType === 'details') { - return undefined; - } - return [ - { - name: i18n.translate('xpack.securitySolution.managementTabs.hosts', { - defaultMessage: 'Hosts', - }), - id: AdministrationSubTab.hosts, - isSelected: tabName === AdministrationSubTab.hosts, - href: formatUrl(getHostListPath({ name: 'hostList' })), - onClick: goToEndpoint, - }, - { - name: i18n.translate('xpack.securitySolution.managementTabs.policies', { - defaultMessage: 'Policies', - }), - id: AdministrationSubTab.policies, - isSelected: tabName === AdministrationSubTab.policies, - href: formatUrl(getPoliciesPath()), - onClick: goToPolicies, - }, - ]; - }, [formatUrl, goToEndpoint, goToPolicies, options.viewType, tabName]); - return ; + return ; }); ManagementPageView.displayName = 'ManagementPageView'; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/host_details.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/host_details.tsx index cea66acbef8cad6..109392cb7a92975 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/host_details.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/host_details.tsx @@ -121,7 +121,7 @@ export const HostDetails = memo(({ details }: { details: HostMetadata }) => { return [ { title: i18n.translate('xpack.securitySolution.endpoint.host.details.policy', { - defaultMessage: 'Policy', + defaultMessage: 'Integration', }), description: ( <> @@ -136,7 +136,7 @@ export const HostDetails = memo(({ details }: { details: HostMetadata }) => { }, { title: i18n.translate('xpack.securitySolution.endpoint.host.details.policyStatus', { - defaultMessage: 'Policy Status', + defaultMessage: 'Configuration response', }), description: ( { diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/index.tsx index 71b388530855817..212c8977a885268 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/index.tsx @@ -158,7 +158,7 @@ const PolicyResponseFlyoutPanel = memo<{

@@ -167,7 +167,7 @@ const PolicyResponseFlyoutPanel = memo<{ title={ } /> diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx index 47227244b706601..9d49c8705affe21 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx @@ -431,7 +431,7 @@ describe('when on the hosts page', () => { const renderResult = render(); const linkToReassign = await renderResult.findByTestId('hostDetailsLinkToIngest'); expect(linkToReassign).not.toBeNull(); - expect(linkToReassign.textContent).toEqual('Reassign Policy'); + expect(linkToReassign.textContent).toEqual('Reassign Configuration'); expect(linkToReassign.getAttribute('href')).toEqual( `/app/ingestManager#/fleet/agents/${agentId}/activity?openReassignFlyout=true` ); @@ -492,7 +492,7 @@ describe('when on the hosts page', () => { it('should include the sub-panel title', async () => { expect( (await renderResult.findByTestId('hostDetailsPolicyResponseFlyoutTitle')).textContent - ).toBe('Policy Response'); + ).toBe('Configuration Response'); }); it('should show a configuration section for each protection', async () => { diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx index e38ef1bd5fe8640..2692f7791b7c05a 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx @@ -237,7 +237,7 @@ export const HostList = () => { { field: 'metadata.Endpoint.policy.applied', name: i18n.translate('xpack.securitySolution.endpointList.policy', { - defaultMessage: 'Policy', + defaultMessage: 'Integration', }), truncateText: true, // eslint-disable-next-line react/display-name @@ -256,7 +256,7 @@ export const HostList = () => { { field: 'metadata.Endpoint.policy.applied', name: i18n.translate('xpack.securitySolution.endpointList.policyStatus', { - defaultMessage: 'Policy Status', + defaultMessage: 'Configuration Status', }), // eslint-disable-next-line react/display-name render: (policy: HostInfo['metadata']['Endpoint']['policy']['applied'], item: HostInfo) => { diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/index.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/index.tsx index 5122bbcd5d55d55..681f1f1430926b2 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/index.tsx @@ -6,17 +6,13 @@ import React, { memo } from 'react'; import { Route, Switch } from 'react-router-dom'; -import { PolicyDetails, PolicyList } from './view'; -import { - MANAGEMENT_ROUTING_POLICIES_PATH, - MANAGEMENT_ROUTING_POLICY_DETAILS_PATH, -} from '../../common/constants'; +import { PolicyDetails } from './view'; +import { MANAGEMENT_ROUTING_POLICY_DETAILS_PATH } from '../../common/constants'; import { NotFoundPage } from '../../../app/404'; export const PolicyContainer = memo(() => { return ( - diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/configure_package_config.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/configure_package_config.tsx index 67f24977406c697..2b08bfd2b282b1d 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/configure_package_config.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/configure_package_config.tsx @@ -6,8 +6,7 @@ import React, { memo, useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiCallOut, EuiText, EuiTitle, EuiSpacer } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; +import { EuiCallOut, EuiText, EuiSpacer } from '@elastic/eui'; import { LinkToApp } from '../../../../../common/components/endpoint/link_to_app'; import { CustomConfigurePackageConfigContent, @@ -50,52 +49,37 @@ export const ConfigureEndpointPackageConfig = memo - -

- -

-

{from === 'edit' ? ( - <> - - - - - - + + + + ), + }} + /> ) : ( )}

diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.test.tsx index 4f7c14735fe2107..6ed4e06ee51c5c3 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.test.tsx @@ -10,7 +10,7 @@ import { mount } from 'enzyme'; import { PolicyDetails } from './policy_details'; import { EndpointDocGenerator } from '../../../../../common/endpoint/generate_data'; import { AppContextTestRender, createAppRootMockRenderer } from '../../../../common/mock/endpoint'; -import { getPolicyDetailPath, getPoliciesPath } from '../../../common/routing'; +import { getPolicyDetailPath, getHostListPath } from '../../../common/routing'; import { apiPathMockResponseProviders } from '../store/policy_list/test_mock_utils'; jest.mock('../../../../common/components/link_to'); @@ -19,7 +19,7 @@ describe('Policy Details', () => { type FindReactWrapperResponse = ReturnType['find']>; const policyDetailsPathUrl = getPolicyDetailPath('1'); - const policyListPathUrl = getPoliciesPath(); + const hostListPath = getHostListPath({ name: 'hostList' }); const sleep = (ms = 100) => new Promise((wakeup) => setTimeout(wakeup, ms)); const generator = new EndpointDocGenerator(); let history: AppContextTestRender['history']; @@ -127,8 +127,8 @@ describe('Policy Details', () => { const backToListButton = pageHeaderLeft.find('EuiButtonEmpty'); expect(backToListButton.prop('iconType')).toBe('arrowLeft'); - expect(backToListButton.prop('href')).toBe(policyListPathUrl); - expect(backToListButton.text()).toBe('Back to policy list'); + expect(backToListButton.prop('href')).toBe(hostListPath); + expect(backToListButton.text()).toBe('Back to endpoint hosts'); const pageTitle = pageHeaderLeft.find('[data-test-subj="pageViewHeaderLeftTitle"]'); expect(pageTitle).toHaveLength(1); @@ -141,7 +141,7 @@ describe('Policy Details', () => { ); expect(history.location.pathname).toEqual(policyDetailsPathUrl); backToListButton.simulate('click', { button: 0 }); - expect(history.location.pathname).toEqual(policyListPathUrl); + expect(history.location.pathname).toEqual(hostListPath); }); it('should display agent stats', async () => { await asyncActions; @@ -173,7 +173,7 @@ describe('Policy Details', () => { const navigateToAppMockedCalls = coreStart.application.navigateToApp.mock.calls; expect(navigateToAppMockedCalls[navigateToAppMockedCalls.length - 1]).toEqual([ 'securitySolution:administration', - { path: policyListPathUrl }, + { path: hostListPath }, ]); }); it('should display save button', async () => { @@ -232,7 +232,7 @@ describe('Policy Details', () => { ); expect(warningCallout).toHaveLength(1); expect(warningCallout.text()).toEqual( - 'This action will update 5 hostsSaving these changes will apply updates to all endpoints assigned to this policy' + 'This action will update 5 hostsSaving these changes will apply updates to all endpoints assigned to this agent configuration.' ); }); it('should close dialog if cancel button is clicked', () => { diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx index 288bc484c23b51c..cd63991dbac93f5 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx @@ -42,7 +42,7 @@ import { PageViewHeaderTitle } from '../../../../common/components/endpoint/page import { ManagementPageView } from '../../../components/management_page_view'; import { SpyRoute } from '../../../../common/utils/route/spy_routes'; import { SecurityPageName } from '../../../../app/types'; -import { getPoliciesPath } from '../../../common/routing'; +import { getHostListPath } from '../../../common/routing'; import { useFormatUrl } from '../../../../common/components/link_to'; import { useNavigateToAppEventHandler } from '../../../../common/hooks/endpoint/use_navigate_to_app_event_handler'; import { MANAGEMENT_APP_ID } from '../../../common/constants'; @@ -56,7 +56,7 @@ export const PolicyDetails = React.memo(() => { application: { navigateToApp }, }, } = useKibana(); - const { formatUrl, search } = useFormatUrl(SecurityPageName.administration); + const { formatUrl } = useFormatUrl(SecurityPageName.administration); const { state: locationRouteState } = useLocation(); // Store values @@ -70,6 +70,7 @@ export const PolicyDetails = React.memo(() => { const [showConfirm, setShowConfirm] = useState(false); const [routeState, setRouteState] = useState(); const policyName = policyItem?.name ?? ''; + const hostListRouterPath = getHostListPath({ name: 'hostList' }); // Handle showing update statuses useEffect(() => { @@ -87,7 +88,7 @@ export const PolicyDetails = React.memo(() => { @@ -109,11 +110,11 @@ export const PolicyDetails = React.memo(() => { } }, [navigateToApp, notifications.toasts, policyName, policyUpdateStatus, routeState]); - const handleBackToListOnClick = useNavigateByRouterEventHandler(getPoliciesPath()); + const handleBackToListOnClick = useNavigateByRouterEventHandler(hostListRouterPath); const navigateToAppArguments = useMemo((): Parameters => { - return routeState?.onCancelNavigateTo ?? [MANAGEMENT_APP_ID, { path: getPoliciesPath() }]; - }, [routeState?.onCancelNavigateTo]); + return routeState?.onCancelNavigateTo ?? [MANAGEMENT_APP_ID, { path: hostListRouterPath }]; + }, [hostListRouterPath, routeState?.onCancelNavigateTo]); const handleCancelOnClick = useNavigateToAppEventHandler(...navigateToAppArguments); const handleSaveOnClick = useCallback(() => { @@ -162,11 +163,11 @@ export const PolicyDetails = React.memo(() => { iconType="arrowLeft" contentProps={{ style: { paddingLeft: '0' } }} onClick={handleBackToListOnClick} - href={formatUrl(getPoliciesPath(search))} + href={formatUrl(hostListRouterPath)} > {policyItem.name} @@ -306,7 +307,7 @@ const ConfirmUpdate = React.memo<{ >
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.test.tsx index 047aa6918736e0e..e35c97698f5cbfd 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.test.tsx @@ -14,7 +14,8 @@ import { AppAction } from '../../../../common/store/actions'; jest.mock('../../../../common/components/link_to'); -describe('when on the policies page', () => { +// Skipping these test now that the Policy List has been hidden +describe.skip('when on the policies page', () => { let render: () => ReturnType; let history: AppContextTestRender['history']; let store: AppContextTestRender['store']; diff --git a/x-pack/plugins/security_solution/public/overview/components/host_overview/endpoint_overview/translations.ts b/x-pack/plugins/security_solution/public/overview/components/host_overview/endpoint_overview/translations.ts index 34e3347b5ff9a3e..a7f1be3debb8303 100644 --- a/x-pack/plugins/security_solution/public/overview/components/host_overview/endpoint_overview/translations.ts +++ b/x-pack/plugins/security_solution/public/overview/components/host_overview/endpoint_overview/translations.ts @@ -9,14 +9,14 @@ import { i18n } from '@kbn/i18n'; export const ENDPOINT_POLICY = i18n.translate( 'xpack.securitySolution.host.details.endpoint.endpointPolicy', { - defaultMessage: 'Endpoint policy', + defaultMessage: 'Integration', } ); export const POLICY_STATUS = i18n.translate( 'xpack.securitySolution.host.details.endpoint.policyStatus', { - defaultMessage: 'Policy status', + defaultMessage: 'Configuration Status', } ); diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts index 07667a140d09026..85d0e56231643e1 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts @@ -36,8 +36,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { [ 'Hostname', 'Host Status', - 'Policy', - 'Policy Status', + 'Integration', + 'Configuration Status', 'Operating System', 'IP Address', 'Version', @@ -119,7 +119,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); // The integration does not work properly yet. Skipping this test for now. - it.skip('navigates to ingest fleet when the Reassign Policy link is clicked', async () => { + it.skip('navigates to ingest fleet when the Reassign Configuration link is clicked', async () => { await (await testSubjects.find('hostnameCellLink')).click(); await (await testSubjects.find('hostDetailsLinkToIngest')).click(); await testSubjects.existOrFail('fleetAgentListTable'); @@ -145,8 +145,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { 'OS', 'Last Seen', 'Alerts', - 'Policy', - 'Policy Status', + 'Integration', + 'Configuration Status', 'IP Address', 'Hostname', 'Sensor Version', diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts index eec3da4ce1c5ea5..7962ec60ff57e83 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts @@ -29,7 +29,6 @@ export default function (providerContext: FtrProviderContext) { await ingestManager.setup(); }); loadTestFile(require.resolve('./endpoint_list')); - loadTestFile(require.resolve('./policy_list')); loadTestFile(require.resolve('./policy_details')); }); } diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts index 8b197a6c69a305a..ca84384390a2911 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts @@ -73,7 +73,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await testSubjects.existOrFail('policyDetailsSuccessMessage'); expect(await testSubjects.getVisibleText('policyDetailsSuccessMessage')).to.equal( - `Policy ${policyInfo.packageConfig.name} has been updated.` + `Integration ${policyInfo.packageConfig.name} has been updated.` ); }); it('should persist update on the screen', async () => { @@ -81,7 +81,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await pageObjects.policy.confirmAndSave(); await testSubjects.existOrFail('policyDetailsSuccessMessage'); - await pageObjects.policy.navigateToPolicyList(); + await pageObjects.endpoint.navigateToHostList(); await pageObjects.policy.navigateToPolicyDetails(policyInfo.packageConfig.id); expect(await (await testSubjects.find('policyWindowsEvent_process')).isSelected()).to.equal( From 78aa24dbd6e61c05559d7432d4357af1c2ad81b3 Mon Sep 17 00:00:00 2001 From: Phillip Burch Date: Tue, 28 Jul 2020 19:30:42 -0500 Subject: [PATCH 10/10] Make button appear clickable, change state of empty text (#73496) Co-authored-by: Elastic Machine --- .../public/components/saved_views/toolbar_control.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx b/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx index db9f92532645fe8..2e06ee55189d9e3 100644 --- a/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx +++ b/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx @@ -161,7 +161,11 @@ export function SavedViewsToolbarControls(props: Props) { /> - + (props: Props) { {currentView ? currentView.name : i18n.translate('xpack.infra.savedView.unknownView', { - defaultMessage: 'Unknown', + defaultMessage: 'No view seleted', })}